/**
 * 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 Code from '../code' ; import * as Error from '../error' ; import * as FileWriter from '../file-writer' ; import * as ImmutableImportUtils from '../immutable-import-utils' ; import * as Maybe from '../maybe' ; import * as ObjC from '../objc' ; import * as ObjCImportUtils from '../objc-import-utils' ; import * as ObjCNullabilityUtils from '../objc-nullability-utils' ; import * as ObjectGeneration from '../object-generation' ; import * as StringUtils from '../string-utils' ; import * as ObjectSpec from '../object-spec' ; import * as ObjectSpecUtils from '../object-spec-utils' ; import * as ObjectSpecCodeUtils from '../object-spec-code-utils' ; function nameOfBuilderForValueTypeWithName ( valueTypeName ) { return valueTypeName + 'Builder' ; } function shortNameOfObjectToBuildForValueTypeWithName ( valueTypeName ) { return StringUtils . lowercased ( StringUtils . stringRemovingCapitalizedPrefix ( valueTypeName ) ) ; } function builderClassMethodForValueType ( objectType ) { return { preprocessors : [ ] , belongsToProtocol : null , code : [ 'return [' + nameOfBuilderForValueTypeWithName ( objectType . typeName ) + ' new];' , ] , comments : [ ] , compilerAttributes : [ ] , keywords : [ { name : shortNameOfObjectToBuildForValueTypeWithName ( objectType . typeName ) , argument : null } , ] , returnType : { type : { name : 'instancetype' , reference : 'instancetype' } , modifiers : [ ] } } ; } function keywordArgumentNameForBuilderFromExistingObjectClassMethodForValueType ( objectType ) { return ( 'existing' + StringUtils . capitalize ( shortNameOfObjectToBuildForValueTypeWithName ( objectType . typeName ) ) ) ; } function openingBrace ( ) { return '[' ; } function indentationForItemAtIndexWithOffset ( offset ) { return function ( index ) : string { const indentation = offset - index ; return StringUtils . stringContainingSpaces ( indentation > 0 ? indentation : 0 ) ; } ; } function toWithInvocationCallForBuilderFromExistingObjectClassMethodForAttribute ( indentationProvider , existingObjectName , soFar , attribute , index , array ) { return soFar . concat ( indentationProvider ( index ) + keywordNameForAttribute ( attribute ) + ':' + existingObjectName + '.' + attribute . name + ']' ) ; } function stringsWithLastItemContainingStringAtEnd ( strings , stringToIncludeAtEndOfLastString ) { const updatedStrings = strings . concat ( ) ; updatedStrings [ updatedStrings . length - 1 ] = updatedStrings [ updatedStrings . length - 1 ] + stringToIncludeAtEndOfLastString ; return updatedStrings ; } function codeForBuilderFromExistingObjectClassMethodForValueType ( objectType ) { const returnOpening = 'return ' ; const openingBracesForWithMethodInvocations = objectType . attributes . map ( openingBrace ) ; const builderCreationCall = '[' + nameOfBuilderForValueTypeWithName ( objectType . typeName ) + ' ' + shortNameOfObjectToBuildForValueTypeWithName ( objectType . typeName ) + ']' ; const openingLine = returnOpening + openingBracesForWithMethodInvocations . join ( '' ) + builderCreationCall ; const indentationProvider = indentationForItemAtIndexWithOffset ( returnOpening . length + openingBracesForWithMethodInvocations . length ) ; const existingObjectName = keywordArgumentNameForBuilderFromExistingObjectClassMethodForValueType ( objectType ) ; const linesForBuildingValuesIntoBuilder = objectType . attributes . reduce ( toWithInvocationCallForBuilderFromExistingObjectClassMethodForAttribute . bind ( null , indentationProvider , existingObjectName ) , [ ] ) ; const code = [ openingLine ] . concat ( linesForBuildingValuesIntoBuilder ) ; return stringsWithLastItemContainingStringAtEnd ( code , ';' ) ; } function builderFromExistingObjectClassMethodForValueType ( objectType ) { return { preprocessors : [ ] , belongsToProtocol : null , code : codeForBuilderFromExistingObjectClassMethodForValueType ( objectType ) , comments : [ ] , compilerAttributes : [ ] , keywords : [ { name : shortNameOfObjectToBuildForValueTypeWithName ( objectType . typeName ) + 'FromExisting' + StringUtils . capitalize ( shortNameOfObjectToBuildForValueTypeWithName ( objectType . typeName ) ) , argument : { name : keywordArgumentNameForBuilderFromExistingObjectClassMethodForValueType ( objectType ) , modifiers : [ ] , type : { name : objectType . typeName , reference : ObjectSpecUtils . typeReferenceForValueTypeWithName ( objectType . typeName ) } } } , ] , returnType : { type : { name : 'instancetype' , reference : 'instancetype' } , modifiers : [ ] } } ; } function valueGeneratorForInvokingInitializerWithAttribute ( attribute ) { return ObjectSpecCodeUtils . ivarForAttribute ( attribute ) ; } function buildObjectInstanceMethodForValueType ( objectType ) { return { preprocessors : [ ] , belongsToProtocol : null , code : [ 'return ' + ObjectSpecCodeUtils . methodInvocationForConstructor ( objectType , valueGeneratorForInvokingInitializerWithAttribute ) + ';' , ] , comments : [ ] , compilerAttributes : [ ] , keywords : [ { name : 'build' , argument : null } , ] , returnType : { type : { name : objectType . typeName , reference : ObjectSpecUtils . typeReferenceForValueTypeWithName ( objectType . typeName ) } , modifiers : [ ] } } ; } function keywordArgumentNameForAttribute ( attribute ) { return attribute . name ; } function keywordNameForAttribute ( attribute ) { return ( 'with' + StringUtils . capitalize ( keywordArgumentNameForAttribute ( attribute ) ) ) ; } function valueToAssignIntoInternalStateForAttribute ( supportsValueSemantics , attribute ) { const keywordArgumentName = keywordArgumentNameForAttribute ( attribute ) ; if ( ObjectSpecCodeUtils . shouldCopyIncomingValueForAttribute ( supportsValueSemantics , attribute ) ) { return '[' + keywordArgumentName + ' copy]' ; } else { return keywordArgumentName ; } } function withInstanceMethodForAttribute ( supportsValueSemantics , attribute ) { return { preprocessors : [ ] , belongsToProtocol : null , code : [ ObjectSpecCodeUtils . ivarForAttribute ( attribute ) + ' = ' + valueToAssignIntoInternalStateForAttribute ( supportsValueSemantics , attribute ) + ';' , 'return self;' , ] , comments : [ ] , compilerAttributes : [ ] , keywords : [ { name : keywordNameForAttribute ( attribute ) , argument : { name : keywordArgumentNameForAttribute ( attribute ) , modifiers : ObjCNullabilityUtils . keywordArgumentModifiersForNullability ( attribute . nullability ) , type : { name : attribute . type . name , reference : attribute . type . reference } } } , ] , returnType : { type : { name : 'instancetype' , reference : 'instancetype' } , modifiers : [ ] } } ; } function instanceVariableForAttribute ( attribute ) { return { name : attribute . name , comments : [ ] , returnType : { name : attribute . type . name , reference : attribute . type . reference } , modifiers : [ ] , access : ObjC . InstanceVariableAccess . Private ( ) } ; } export function importsForTypeLookupsOfObjectType ( objectType ) { const needsImportsForAllTypeLookups = objectType . includes . indexOf ( 'UseForwardDeclarations' ) !== - 1 ; return objectType . typeLookups . map ( function ( typeLookup ) : ObjC . Import { if ( ! typeLookup . canForwardDeclare ) { return ObjCImportUtils . importForTypeLookup ( objectType . libraryName , true , typeLookup ) ; } else if ( needsImportsForAllTypeLookups ) { return ObjCImportUtils . importForTypeLookup ( objectType . libraryName , false , typeLookup ) ; } else { return null ! ; } } ) . filter ( function ( maybeImport ) : boolean { return maybeImport != null ; } ) ; } function makePublicImportsForValueType ( objectType ) { return objectType . includes . indexOf ( 'UseForwardDeclarations' ) === - 1 ; } function importsForBuilder ( objectType , forBaseFile ) { const typeLookupImports = importsForTypeLookupsOfObjectType ( objectType ) ; const makePublicImports = makePublicImportsForValueType ( objectType ) ; const skipAttributeImports = ! makePublicImports && objectType . includes . indexOf ( 'SkipImportsInImplementation' ) !== - 1 ; const attributeImports = skipAttributeImports ? [ ] : objectType . attributes . filter ( ( attribute ) => ObjCImportUtils . shouldIncludeImportForType ( objectType . typeLookups , attribute . type . name ) ) . map ( function ( attribute ) : ObjC . Import { return ObjCImportUtils . importForAttribute ( attribute . type . name , attribute . type . underlyingType , attribute . type . libraryTypeIsDefinedIn , attribute . type . fileTypeIsDefinedIn , objectType . libraryName , false ) ; } ) ; return [ { file : 'Foundation.h' , isPublic : true , requiresCPlusPlus : false , library : 'Foundation' } , { file : objectType . typeName + '.h' , isPublic : false , requiresCPlusPlus : false , library : objectType . libraryName } , ... conditionallyAddToSpread ( ! forBaseFile , { file : nameOfBuilderForValueTypeWithName ( objectType . typeName ) + '.h' , isPublic : false , requiresCPlusPlus : false , library : null } ) , ] . concat ( typeLookupImports ) . concat ( attributeImports ) ; } function forwardDeclarationsForBuilder ( objectType ) { return [ ObjC . ForwardDeclaration . ForwardClassDeclaration ( objectType . typeName ) , ... ImmutableImportUtils . forwardClassDeclarationsForObjectType ( objectType ) , ] ; } function classesForBuilder ( objectType ) { return [ { baseClassName : 'NSObject' , covariantTypes : [ ] , classMethods : [ builderClassMethodForValueType ( objectType ) , builderFromExistingObjectClassMethodForValueType ( objectType ) , ] , comments : [ ] , inlineBlockTypedefs : [ ] , instanceMethods : [ buildObjectInstanceMethodForValueType ( objectType ) , ] . concat ( objectType . attributes . map ( ( attribute ) => withInstanceMethodForAttribute ( ObjectSpecUtils . typeSupportsValueObjectSemantics ( objectType ) , attribute ) ) ) , name : nameOfBuilderForValueTypeWithName ( objectType . typeName ) , properties : [ ] , instanceVariables : objectType . attributes . map ( instanceVariableForAttribute ) , implementedProtocols : [ ] , nullability : objectType . includes . indexOf ( 'RMAssumeNonnull' ) >= 0 ? ObjC . ClassNullability . assumeNonnull : ObjC . ClassNullability . default , subclassingRestricted : false } , ] ; } function conditionallyAddToSpread < T > ( addIt , value ) { return addIt ? [ value ] : [ ] ; } function builderFileForValueType ( objectType , forBaseFile ) { return { name : nameOfBuilderForValueTypeWithName ( objectType . typeName ) , type : Code . FileType . ObjectiveC , imports : importsForBuilder ( objectType , forBaseFile ) , forwardDeclarations : forwardDeclarationsForBuilder ( objectType ) , comments : [ ] , enumerations : [ ] , blockTypes : [ ] , staticConstants : [ ] , globalVariables : [ ] , functions : [ ] , classes : classesForBuilder ( objectType ) , diagnosticIgnores : [ ] , structs : [ ] , cppClasses : [ ] , namespaces : [ ] , macros : [ ] } ; } export function createPlugin ( ) { return { additionalFiles : function ( objectType ) : Code . File [ ] { return [ builderFileForValueType ( objectType , false ) ] ; } , transformBaseFile : function ( objectType , baseFile ) : Code . File { baseFile . imports = baseFile . imports . concat ( importsForBuilder ( objectType , true ) ) ; baseFile . forwardDeclarations = baseFile . forwardDeclarations . concat ( forwardDeclarationsForBuilder ( objectType ) ) ; baseFile . classes = baseFile . classes . concat ( classesForBuilder ( objectType ) ) ; return baseFile ; } , additionalTypes : function ( objectType ) : ObjectSpec . Type [ ] { return [ ] ; } , attributes : function ( objectType ) : ObjectSpec . Attribute [ ] { return [ ] ; } , classMethods : function ( objectType ) : ObjC . Method [ ] { 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 [ ] { return [ ] ; } , imports : function ( objectType ) : ObjC . Import [ ] { return [ ] ; } , instanceMethods : function ( objectType ) : ObjC . Method [ ] { return [ ] ; } , macros : function ( valueType ) : ObjC . Macro [ ] { return [ ] ; } , properties : function ( objectType ) : ObjC . Property [ ] { return [ ] ; } , requiredIncludesToRun : [ 'RMBuilder' ] , staticConstants : function ( objectType ) : ObjC . Constant [ ] { return [ ] ; } , validationErrors : function ( objectType ) : Error . Error [ ] { return [ ] ; } , nullability : function ( objectType ) : ObjC . ClassNullability | null { return null ; } , subclassingRestricted : function ( objectType ) : boolean { return false ; } } ; }