/**
 * 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 List from '../list' ; import * as Map from '../map' ; import * as Maybe from '../maybe' ; import * as ObjC from '../objc' ; import * as ObjCCommentUtils from '../objc-comment-utils' ; import * as ObjCImportUtils from '../objc-import-utils' ; import * as ObjCNullabilityUtils from '../objc-nullability-utils' ; import * as ObjectGeneration from '../object-generation' ; import * as ObjectSpecCodeUtils from '../object-spec-code-utils' ; import * as StringUtils from '../string-utils' ; const PUBLIC_FOUNDATION_IMPORT = { library : 'Foundation' , file : 'Foundation.h' , isPublic : true , requiresCPlusPlus : false } ; function nameOfObjectWithinInitializer ( ) { return 'object' ; } function nameOfKeywordParameterForAttribute ( attribute ) { return attribute . name ; } function internalValueSettingCodeForAttribute ( subtype , attribute ) { return ( nameOfObjectWithinInitializer ( ) + '->' + AlgebraicTypeUtils . valueAccessorForInstanceVariableForAttribute ( subtype , attribute ) + ' = ' + nameOfKeywordParameterForAttribute ( attribute ) + ';' ) ; } function keywordArgumentFromAttribute ( attribute ) { return { name : nameOfKeywordParameterForAttribute ( attribute ) , modifiers : ObjCNullabilityUtils . keywordArgumentModifiersForNullability ( attribute . nullability ) , type : { name : attribute . type . name , reference : attribute . type . reference } } ; } function firstInitializerKeyword ( subtype , attribute ) { return { argument : keywordArgumentFromAttribute ( attribute ) , name : StringUtils . lowercased ( subtype . name ) + 'With' + StringUtils . capitalize ( attribute . name ) } ; } function attributeToKeyword ( attribute ) { return { argument : keywordArgumentFromAttribute ( attribute ) , name : attribute . name } ; } function keywordsForNamedAttributeSubtype ( subtype ) { if ( subtype . attributes . length > 0 ) { return [ firstInitializerKeyword ( subtype , subtype . attributes [ 0 ] ) ] . concat ( subtype . attributes . slice ( 1 ) . map ( attributeToKeyword ) ) ; } else { return [ { name : StringUtils . lowercased ( subtype . name ) , argument : null } , ] ; } } function commentsForSubtype ( subtype ) { return subtype . match ( function ( namedAttributeCollectionSubtype ) { return ObjCCommentUtils . prefixedParamCommentsFromAttributes ( namedAttributeCollectionSubtype . comments , namedAttributeCollectionSubtype . attributes ) ; } , function ( attribute ) { return attribute . comments ; } ) ; } export function keywordsForSubtype ( subtype ) { return subtype . match ( function ( namedAttributeCollectionSubtype ) { return keywordsForNamedAttributeSubtype ( namedAttributeCollectionSubtype ) ; } , function ( attribute ) { return [ { argument : keywordArgumentFromAttribute ( attribute ) , name : StringUtils . lowercased ( attribute . name ) } , ] ; } ) ; } function swiftNameForSubtype ( subtype ) { return subtype . match ( ( collection ) => { if ( collection . attributes . length > 0 ) { const name = StringUtils . swiftCaseForString ( collection . name ) ; const keywords = collection . attributes . map ( ( attribute ) => `${ StringUtils . swiftCaseForString ( attribute . name ) }:` ) . join ( '' ) ; return `NS_SWIFT_NAME(${ name }(${ keywords }))` ; } else { return null ; } } , ( attribute ) => null ) ; } function canAssertExistenceForTypeOfAttribute ( attribute ) { return ObjCNullabilityUtils . canAssertExistenceForType ( AlgebraicTypeUtils . computeTypeOfAttribute ( attribute ) ) ; } function isRequiredAttribute ( assumeNonnull , attribute ) { return ObjCNullabilityUtils . shouldProtectFromNilValuesForNullability ( assumeNonnull , attribute . nullability ) ; } function toRequiredAssertion ( attribute ) { return 'RMParameterAssert(' + attribute . name + ' != nil);' ; } function initializationClassMethodForSubtype ( algebraicType , subtype ) { const openingCode = [ algebraicType . name + ' *' + nameOfObjectWithinInitializer ( ) + ' = [(Class)self new];' , nameOfObjectWithinInitializer ( ) + '->' + AlgebraicTypeUtils . valueAccessorForInstanceVariableStoringSubtype ( ) + ' = ' + AlgebraicTypeUtils . EnumerationValueNameForSubtype ( algebraicType , subtype ) + ';' , ] ; const assumeNonnull = algebraicType . includes . indexOf ( 'RMAssumeNonnull' ) >= 0 ; const attributes = AlgebraicTypeUtils . attributesFromSubtype ( subtype ) ; const requiredParameterAssertions = attributes . filter ( canAssertExistenceForTypeOfAttribute ) . filter ( ( attribute ) => isRequiredAttribute ( assumeNonnull , attribute ) ) . map ( toRequiredAssertion ) ; const setterStatements = attributes . map ( ( attribute ) => internalValueSettingCodeForAttribute ( subtype , attribute ) ) ; return { preprocessors : [ ] , belongsToProtocol : null , code : requiredParameterAssertions . concat ( openingCode ) . concat ( setterStatements ) . concat ( 'return object;' ) , comments : ObjCCommentUtils . commentsAsBlockFromStringArray ( commentsForSubtype ( subtype ) ) , compilerAttributes : Maybe . catMaybes ( [ swiftNameForSubtype ( subtype ) ] ) , keywords : keywordsForSubtype ( subtype ) , returnType : { type : { name : 'instancetype' , reference : 'instancetype' } , modifiers : [ ] } } ; } function internalImportForFileWithName ( name ) { return { library : null , file : name + '.h' , isPublic : false , requiresCPlusPlus : false } ; } function instanceVariableForEnumeration ( algebraicType ) { const enumerationName = AlgebraicTypeUtils . EnumerationNameForAlgebraicType ( algebraicType ) ; return { name : AlgebraicTypeUtils . nameForInstanceVariableStoringSubtype ( ) , comments : [ ] , returnType : { name : enumerationName , reference : enumerationName } , modifiers : [ ] , access : ObjC . InstanceVariableAccess . Private ( ) } ; } function instanceVariableFromAttribute ( subtype , attribute ) { return { name : AlgebraicTypeUtils . nameOfInstanceVariableForAttribute ( subtype , attribute ) , comments : [ ] , returnType : { name : attribute . type . name , reference : attribute . type . reference } , modifiers : [ ] , access : ObjC . InstanceVariableAccess . Private ( ) } ; } function instanceVariablesForImplementationOfAlgebraicType ( algebraicType ) { const enumerationInstanceVariable = instanceVariableForEnumeration ( algebraicType ) ; const attributeInstanceVariables = AlgebraicTypeUtils . mapAttributesWithSubtypeFromSubtypes ( algebraicType . subtypes , instanceVariableFromAttribute ) ; return [ enumerationInstanceVariable ] . concat ( attributeInstanceVariables ) ; } function makePublicImportsForAlgebraicType ( algebraicType ) { return algebraicType . includes . indexOf ( 'UseForwardDeclarations' ) === - 1 ; } function isImportRequiredForTypeLookup ( algebraicType , typeLookup ) { const needsImportsForAllTypeLookups = makePublicImportsForAlgebraicType ( algebraicType ) ; return ( typeLookup . name !== algebraicType . name && ( ! typeLookup . canForwardDeclare || needsImportsForAllTypeLookups ) ) ; } function forwardDeclarationForTypeLookup ( typeLookup ) { return ObjC . ForwardDeclaration . ForwardClassDeclaration ( typeLookup . name ) ; } function enumerationForSubtypesOfAlgebraicType ( algebraicType ) { return { name : AlgebraicTypeUtils . EnumerationNameForAlgebraicType ( algebraicType ) , underlyingType : 'NSUInteger' , values : algebraicType . subtypes . map ( ( subtype ) => AlgebraicTypeUtils . EnumerationValueNameForSubtype ( algebraicType , subtype ) ) , isPublic : false , comments : [ ] } ; } interface ValidationErrorReductionTracker { errors : List . List < Error . Error > ; seenSubtypeNames : Map . Map < string , string > ; } function buildAlgebraicTypeValidationErrors ( soFar , subtype ) { const subtypeName = AlgebraicTypeUtils . subtypeNameFromSubtype ( subtype ) ; if ( Map . containsKey ( subtypeName , soFar . seenSubtypeNames ) ) { const error = Error . Error ( 'Algebraic types cannot have two subtypes with the same name, but found two or more subtypes with the name ' + subtypeName ) ; return { errors : List . cons ( error , soFar . errors ) , seenSubtypeNames : soFar . seenSubtypeNames } ; } else { return { errors : soFar . errors , seenSubtypeNames : Map . insert ( subtypeName , subtypeName , soFar . seenSubtypeNames ) } ; } } 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 [ ] { return algebraicType . subtypes . map ( ( subtype ) => initializationClassMethodForSubtype ( algebraicType , subtype ) ) ; } , enumerations : function ( algebraicType ) : ObjC . Enumeration [ ] { return [ enumerationForSubtypesOfAlgebraicType ( algebraicType ) ] ; } , transformFileRequest : function ( request ) : FileWriter . Request { return request ; } , fileType : function ( algebraicType ) : Code . FileType | null { return null ; } , forwardDeclarations : function ( algebraicType ) : ObjC . ForwardDeclaration [ ] { if ( makePublicImportsForAlgebraicType ( algebraicType ) ) { return [ ] ; } const attributes = AlgebraicTypeUtils . allAttributesFromSubtypes ( algebraicType . subtypes ) ; return ( [ ] as ObjC . ForwardDeclaration [ ] ) . concat ( ... attributes . map ( ( a ) => ObjCImportUtils . forwardDeclarationsForAttributeType ( a . type . name , a . type . underlyingType , a . type . conformingProtocol , a . type . referencedGenericTypes , algebraicType . typeLookups ) ) ) ; } , functions : function ( algebraicType ) : ObjC . Function [ ] { return [ ] ; } , headerComments : function ( algebraicType ) : ObjC . Comment [ ] { return [ ] ; } , implementedProtocols : function ( algebraicType ) : ObjC . ImplementedProtocol [ ] { return [ ] ; } , imports : function imports ( algebraicType ) : ObjC . Import [ ] { const baseImports = [ PUBLIC_FOUNDATION_IMPORT , internalImportForFileWithName ( algebraicType . name ) , ] ; const makePublicImports = makePublicImportsForAlgebraicType ( algebraicType ) ; const typeLookupImports = algebraicType . typeLookups . filter ( ( typeLookup ) => isImportRequiredForTypeLookup ( algebraicType , typeLookup ) ) . map ( ( typeLookup ) => ObjCImportUtils . importForTypeLookup ( algebraicType . libraryName , makePublicImports || ! typeLookup . canForwardDeclare , typeLookup ) ) ; const skipAttributeImports = ! makePublicImports && algebraicType . includes . indexOf ( 'SkipImportsInImplementation' ) !== - 1 ; const attributeImports = skipAttributeImports ? [ ] : AlgebraicTypeUtils . allAttributesFromSubtypes ( algebraicType . subtypes ) . filter ( ( attribute ) => ObjCImportUtils . shouldIncludeImportForType ( algebraicType . typeLookups , attribute . type . name ) ) . map ( ( attribute ) => ObjCImportUtils . importForAttribute ( attribute . type . name , attribute . type . underlyingType , attribute . type . libraryTypeIsDefinedIn , attribute . type . fileTypeIsDefinedIn , algebraicType . libraryName , makePublicImports ) ) ; return baseImports . concat ( typeLookupImports ) . concat ( attributeImports ) ; } , instanceMethods : function ( algebraicType ) : ObjC . Method [ ] { return [ ] ; } , instanceVariables : function ( algebraicType ) : ObjC . InstanceVariable [ ] { return instanceVariablesForImplementationOfAlgebraicType ( algebraicType ) ; } , macros : function ( algebraicType ) : ObjC . Macro [ ] { return [ ] ; } , requiredIncludesToRun : [ 'AlgebraicTypeInitialization' ] , staticConstants : function ( algebraicType ) : ObjC . Constant [ ] { return [ ] ; } , validationErrors : function ( algebraicType ) : Error . Error [ ] { const initialReductionTracker = { errors : List . of < Error . Error > ( ) , seenSubtypeNames : Map . Empty < string , string > ( ) } ; return List . toArray ( algebraicType . subtypes . reduce ( buildAlgebraicTypeValidationErrors , initialReductionTracker ) . errors ) ; } , nullability : function ( algebraicType ) : ObjC . ClassNullability | null { return null ; } , subclassingRestricted : function ( algebraicType ) : boolean { return false ; } } ; }