import * as pluralize from "pluralize" ; import { setUnion , setMap , setSortBy } from "collection-utils" ; import { TypeGraph } from "./TypeGraph" ; import { Type , ObjectType } from "./Type" ; import { matchCompoundType , nullableFromUnion } from "./TypeUtils" ; import { TypeNames , namesTypeAttributeKind , TooManyTypeNames , tooManyNamesThreshold } from "./attributes/TypeNames" ; import { defined , panic , assert } from "./support/Support" ; import { transformationForType } from "./Transformers" ; class UniqueQueue < T > { private readonly _present = new Set < T > ( ) ; private _queue : ( T | undefined ) [ ] = [ ] ; private _front = 0 ; get size ( ) : number { return this . _queue . length - this . _front ; } get isEmpty ( ) : boolean { return this . size <= 0 ; } push ( v ) { if ( this . _present . has ( v ) ) return ; this . _queue . push ( v ) ; this . _present . add ( v ) ; } unshift ( ) { assert ( ! this . isEmpty , "Trying to unshift from an empty queue" ) ; const v = this . _queue [ this . _front ] ; if ( v === undefined ) { return panic ( "Value should have been present in queue" ) ; } this . _queue [ this . _front ] = undefined ; this . _front += 1 ; this . _present . delete ( v ) ; if ( this . _front > this . size ) { this . _queue = this . _queue . slice ( this . _front ) ; this . _front = 0 ; } return v ; } } export function gatherNames ( graph , destructive , debugPrint ) { function setNames ( t , tn ) { graph . attributeStore . set ( namesTypeAttributeKind , t , tn ) ; } if ( destructive ) { for ( const t of graph . allTypesUnordered ( ) ) { if ( t . hasNames ) { setNames ( t , t . getNames ( ) . clearInferred ( ) ) ; } } } const queue = new UniqueQueue < Type > ( ) ; const namesForType = new Map < Type , ReadonlySet < string > | null > ( ) ; function addNames ( t , names ) { if ( t . hasNames ) { const originalNames = t . getNames ( ) ; if ( ! originalNames . areInferred ) { names = originalNames . names ; } } const oldNames = namesForType . get ( t ) ; if ( oldNames === null ) return ; let newNames ; if ( oldNames === undefined ) { newNames = names ; } else if ( names === null ) { newNames = null ; } else { newNames = setUnion ( oldNames , names ) ; } if ( newNames !== null && newNames . size >= tooManyNamesThreshold ) { newNames = null ; } namesForType . set ( t , newNames ) ; const transformation = transformationForType ( t ) ; if ( transformation !== undefined ) { addNames ( transformation . targetType , names ) ; } if ( oldNames !== undefined && newNames !== null ) { if ( oldNames . size === newNames . size ) { return ; } } else if ( oldNames === newNames ) { return ; } queue . push ( t ) ; } for ( const [ name , t ] of graph . topLevels ) { addNames ( t , new Set ( [ name ] ) ) ; } while ( ! queue . isEmpty ) { const t = queue . unshift ( ) ; const names = defined ( namesForType . get ( t ) ) ; if ( t instanceof ObjectType ) { const properties = t . getSortedProperties ( ) ; for ( const [ propertyName , property ] of properties ) { addNames ( property . type , new Set ( [ propertyName ] ) ) ; } const values = t . getAdditionalProperties ( ) ; if ( values !== undefined ) { addNames ( values , names === null ? null : setMap ( names , pluralize . singular ) ) ; } } else { matchCompoundType ( t , arrayType => { addNames ( arrayType . items , names === null ? null : setMap ( names , pluralize . singular ) ) ; } , _classType => panic ( "We handled this above" ) , _mapType => panic ( "We handled this above" ) , _objectType => panic ( "We handled this above" ) , unionType => { const members = setSortBy ( unionType . members , member => member . kind ) ; for ( const memberType of members ) { addNames ( memberType , names ) ; } } ) ; } } if ( debugPrint ) { for ( const t of graph . allTypesUnordered ( ) ) { const names = namesForType . get ( t ) ; if ( names === undefined ) return ; const index = t . index ; console . log ( `${ index }: ${ names === null ? "*** too many ***" : Array . from ( names ) . join ( " " ) }` ) ; } } const directAlternativesForType = new Map < Type , ReadonlySet < string > | null > ( ) ; const ancestorAlternativesForType = new Map < Type , ReadonlySet < string > | null > ( ) ; const pairsProcessed = new Map < Type | undefined , Set < Type > > ( ) ; function addAlternatives ( existing , alternatives ) { if ( alternatives . length === 0 ) { return existing ; } if ( existing === undefined ) { existing = new Set ( ) ; } existing = setUnion ( existing , alternatives ) ; if ( existing . size < tooManyNamesThreshold ) { return existing ; } return null ; } function processType ( ancestor , t , alternativeSuffix ) { const names = defined ( namesForType . get ( t ) ) ; let processedEntry = pairsProcessed . get ( ancestor ) ; if ( processedEntry === undefined ) processedEntry = new Set ( ) ; if ( processedEntry . has ( t ) ) return ; processedEntry . add ( t ) ; pairsProcessed . set ( ancestor , processedEntry ) ; const transformation = transformationForType ( t ) ; if ( transformation !== undefined ) { processType ( ancestor , transformation . targetType , alternativeSuffix ) ; } let ancestorAlternatives = ancestorAlternativesForType . get ( t ) ; let directAlternatives = directAlternativesForType . get ( t ) ; if ( names === null ) { ancestorAlternatives = null ; directAlternatives = null ; } else { if ( ancestor !== undefined && ancestorAlternatives !== null ) { const ancestorNames = namesForType . get ( ancestor ) ; if ( ancestorNames === null ) { ancestorAlternatives = null ; } else if ( ancestorNames !== undefined ) { const alternatives = [ ] ; for ( const name of names ) { alternatives . push ( ... Array . from ( ancestorNames ) . map ( an => `${ an }_${ name }` ) ) ; alternatives . push ( ... Array . from ( ancestorNames ) . map ( an => `${ an }_${ name }_${ t . kind }` ) ) ; } ancestorAlternatives = addAlternatives ( ancestorAlternatives , alternatives ) ; } } if ( alternativeSuffix !== undefined && directAlternatives !== null ) { const alternatives = [ ] ; for ( const name of names ) { alternatives . push ( `${ name }_${ alternativeSuffix }` ) ; } directAlternatives = addAlternatives ( directAlternatives , alternatives ) ; } } if ( ancestorAlternatives !== undefined ) { ancestorAlternativesForType . set ( t , ancestorAlternatives ) ; } if ( directAlternatives !== undefined ) { directAlternativesForType . set ( t , directAlternatives ) ; } if ( t instanceof ObjectType ) { const properties = t . getSortedProperties ( ) ; for ( const [ , property ] of properties ) { processType ( t , property . type , undefined ) ; } const values = t . getAdditionalProperties ( ) ; if ( values !== undefined ) { processType ( properties . size === 0 ? ancestor : t , values , "value" ) ; } } else { matchCompoundType ( t , arrayType => { processType ( ancestor , arrayType . items , "element" ) ; } , _classType => panic ( "We handled this above" ) , _mapType => panic ( "We handled this above" ) , _objectType => panic ( "We handled this above" ) , unionType => { const members = setSortBy ( unionType . members , member => member . kind ) ; const unionHasGivenName = unionType . hasNames && ! unionType . getNames ( ) . areInferred ; const unionIsAncestor = unionHasGivenName || nullableFromUnion ( unionType ) === null ; const ancestorForMembers = unionIsAncestor ? unionType : ancestor ; for ( const memberType of members ) { processType ( ancestorForMembers , memberType , undefined ) ; } } ) ; } } for ( const [ , t ] of graph . topLevels ) { processType ( undefined , t , undefined ) ; } for ( const t of graph . allTypesUnordered ( ) ) { const names = namesForType . get ( t ) ; if ( names === undefined ) continue ; if ( names === null ) { directAlternativesForType . set ( t , null ) ; continue ; } let alternatives = directAlternativesForType . get ( t ) ; if ( alternatives === null ) continue ; if ( alternatives === undefined ) { alternatives = new Set ( ) ; } alternatives = setUnion ( alternatives , setMap ( names , name => `${ name }_${ t . kind }` ) ) ; directAlternativesForType . set ( t , alternatives ) ; } for ( const t of graph . allTypesUnordered ( ) ) { const names = namesForType . get ( t ) ; if ( names === undefined ) continue ; let typeNames ; if ( names === null ) { typeNames = new TooManyTypeNames ( 1 ) ; } else { const ancestorAlternatives = ancestorAlternativesForType . get ( t ) ; const directAlternatives = directAlternativesForType . get ( t ) ; let alternatives ; if ( ancestorAlternatives === null && directAlternatives === null ) { alternatives = undefined ; } else { if ( directAlternatives !== null && directAlternatives !== undefined ) { alternatives = directAlternatives ; } else { alternatives = new Set ( ) ; } if ( ancestorAlternatives !== null && ancestorAlternatives !== undefined ) { alternatives = setUnion ( alternatives , ancestorAlternatives ) ; } } typeNames = TypeNames . makeWithDistance ( names , alternatives , destructive ? 1 : 10 ) ; } setNames ( t , t . hasNames ? t . getNames ( ) . add ( [ typeNames ] ) : typeNames ) ; } }