import _ from 'lodash' ; import ts from 'typescript' ; import { heritage , modifier , overload , parametered } from './base' ; import * as declaration from './declaration' ; import * as node_ from './node' ; import * as reference from './reference' ; import * as symbol_ from './symbol' ; import * as type_ from './type_' ; import * as utils from './utils' ; export type ClassPropertyType = ts . PropertyDeclaration | ts . GetAccessorDeclaration | ts . SetAccessorDeclaration ; export function isClassProperty ( node ) { return ts . isPropertyDeclaration ( node ) || ts . isGetAccessorDeclaration ( node ) || ts . isSetAccessorDeclaration ( node ) ; } export type ClassInstancePropertyType = ClassPropertyType | ts . ParameterPropertyDeclaration ; export function isClassInstanceProperty ( node ) { return ( isClassProperty ( node ) || ts . isParameterPropertyDeclaration ( node , node . parent ) ) && ! modifier . isStatic ( node ) ; } export type ClassInstanceMemberType = ts . MethodDeclaration | ClassInstancePropertyType ; export function isClassInstanceMember ( node ) { return ( ts . isMethodDeclaration ( node ) || isClassInstanceProperty ( node ) ) && ! modifier . isStatic ( node ) ; } export type ClassStaticPropertyType = ClassPropertyType ; export function isClassStaticProperty ( node ) { return ( ts . isMethodDeclaration ( node ) || isClassProperty ( node ) ) && modifier . isStatic ( node ) ; } export type ClassStaticMemberType = ts . MethodDeclaration | ClassStaticPropertyType ; export function isClassStaticMember ( node ) { return ( ts . isMethodDeclaration ( node ) || isClassProperty ( node ) ) && modifier . isStatic ( node ) ; } export type ClassMemberType = ClassInstanceMemberType | ClassStaticMemberType ; export function isClassMember ( node ) { return isClassInstanceMember ( node ) || isClassStaticMember ( node ) ; } export function getExtends ( node ) { const extendsClause = heritage . getHeritageClauseByKind ( node , ts . SyntaxKind . ExtendsKeyword ) ; if ( extendsClause === undefined ) { return undefined ; } const typeNodes = heritage . getTypeNodes ( extendsClause ) ; return typeNodes . length === 0 ? undefined : typeNodes [ 0 ] ; } export function getExtendsOrThrow ( node ) { return utils . throwIfNullOrUndefined ( getExtends ( node ) , 'extends expression' ) ; } export function getImplements ( node ) { const implementsClause = heritage . getHeritageClauseByKind ( node , ts . SyntaxKind . ImplementsKeyword ) ; if ( implementsClause === undefined ) { return undefined ; } return heritage . getTypeNodes ( implementsClause ) ; } export function getImplementsArray ( node ) { return utils . getArray ( getImplements ( node ) ) ; } export function getMembers ( node ) { const members = [ ... node . members ] ; const implementationCtors = members . filter ( ts . isConstructorDeclaration ) . filter ( ( c ) => overload . isImplementation ( c ) ) ; for ( const ctor of implementationCtors ) { let insertIndex = members . indexOf ( ctor ) + 1 ; for ( const param of parametered . getParameters ( ctor ) ) { if ( ts . isParameterPropertyDeclaration ( param , param . parent ) ) { members . splice ( insertIndex , 0 , param ) ; insertIndex += 1 ; } } } return members . filter ( isClassMember ) ; } export function getConcreteMembers ( node ) { return declaration . isAmbient ( node ) ? [ ] : getMembers ( node ) . filter ( ( member ) => { if ( ts . isMethodDeclaration ( member ) ) { return overload . isImplementation ( member ) ; } return true ; } ) ; } export function getInstanceProperties ( node ) { return getMembers ( node ) . filter ( isClassInstanceProperty ) ; } export function getInstanceMembers ( node ) { return getMembers ( node ) . filter ( isClassInstanceMember ) ; } export function getInstanceMethods ( node ) { return getInstanceMembers ( node ) . filter ( ts . isMethodDeclaration ) ; } export function getMethods ( node ) { return getMembers ( node ) . filter ( ts . isMethodDeclaration ) ; } export function getSetAccessors ( node ) { return getMembers ( node ) . filter ( ts . isSetAccessor ) ; } export function getInstanceMethod ( node , name ) { return getInstanceMethods ( node ) . find ( ( method ) => node_ . getName ( method ) === name ) ; } export function getConcreteInstanceProperties ( node ) { return getConcreteMembers ( node ) . filter ( isClassInstanceProperty ) ; } export function getConcreteInstanceMembers ( node ) { return getConcreteMembers ( node ) . filter ( isClassInstanceMember ) ; } export function getConcreteInstanceMethods ( node ) { return getConcreteInstanceMembers ( node ) . filter ( ts . isMethodDeclaration ) ; } export function getStaticProperties ( node ) { return getMembers ( node ) . filter ( isClassStaticProperty ) ; } export function getStaticMembers ( node ) { return getMembers ( node ) . filter ( isClassStaticMember ) ; } export function getConcreteStaticProperties ( node ) { return getConcreteMembers ( node ) . filter ( isClassStaticProperty ) ; } export function getConcreteStaticMembers ( node ) { return getConcreteMembers ( node ) . filter ( isClassStaticMember ) ; } export function getConcreteStaticMethods ( node ) { return getConcreteStaticMembers ( node ) . filter ( ts . isMethodDeclaration ) ; } export function getConstructors ( node ) { return node . members . filter ( ts . isConstructorDeclaration ) ; } export function getConcreteConstructor ( node ) { return getConstructors ( node ) . find ( ( ctor ) => overload . isImplementation ( ctor ) ) ; } export function getFirstConcreteConstructor ( typeChecker , node ) { const ctor = getConcreteConstructor ( node ) ; if ( ctor !== undefined ) { return ctor ; } const baseClass = getBaseClass ( typeChecker , node ) ; if ( baseClass === undefined ) { return undefined ; } return getFirstConcreteConstructor ( typeChecker , baseClass ) ; } function getDerivedClassesWorker ( program , languageService , node , seen = new Set < ts . ClassDeclaration > ( ) ) { if ( seen . has ( node ) ) { return [ ] ; } return reference . findReferencesAsNodes ( program , languageService , node ) . reduce < readonly ts . ClassDeclaration [ ] > ( ( acc , ref ) => { const parent = node_ . getParent ( ref ) as ts . Node | undefined ; if ( parent === undefined ) { return acc ; } const clause = node_ . getParent ( parent ) as ts . Node | undefined ; if ( clause === undefined || ! ts . isHeritageClause ( clause ) || ! heritage . isExtends ( clause ) ) { return acc ; } const derived = node_ . getFirstAncestorByKindOrThrow < ts . ClassDeclaration > ( clause , ts . SyntaxKind . ClassDeclaration ) ; return acc . concat ( getDerivedClassesWorker ( program , languageService , derived , seen ) ) ; } , [ ] ) . concat ( [ node ] ) ; } export function getDerivedClasses ( program , languageService , node ) { const result = getDerivedClassesWorker ( program , languageService , node ) ; return result . filter ( ( value ) => value !== node ) ; } function getImplementorsWorker ( program , languageService , node , seen = new Set < ts . ClassDeclaration | ts . InterfaceDeclaration > ( ) ) { if ( seen . has ( node ) ) { return [ ] ; } return reference . findReferencesAsNodes ( program , languageService , node ) . reduce < readonly ts . ClassDeclaration [ ] > ( ( acc , ref ) => { const parent = node_ . getParent ( ref ) as ts . Node | undefined ; if ( parent === undefined ) { return acc ; } const clause = node_ . getParent ( parent ) as ts . Node | undefined ; if ( ( clause === undefined || ! ts . isHeritageClause ( clause ) || ! heritage . isExtends ( clause ) ) && ! ( clause && ts . isExpressionWithTypeArguments ( clause ) ) ) { return acc ; } let derived = node_ . getFirstAncestorByKind < ts . ClassDeclaration > ( clause , ts . SyntaxKind . ClassDeclaration ) ; if ( derived === undefined ) { derived = node_ . getFirstAncestorByKindOrThrow < ts . InterfaceDeclaration > ( clause , ts . SyntaxKind . InterfaceDeclaration ) ; } return acc . concat ( getImplementorsWorker ( program , languageService , derived , seen ) ) ; } , [ ] ) . concat ( ts . isClassDeclaration ( node ) ? [ node ] : [ ] ) ; } export function getImplementors ( program , languageService , node ) { return getImplementorsWorker ( program , languageService , node ) ; } function getExtendorsWorker ( program , languageService , node , seen = new Set < ts . ClassDeclaration > ( ) ) { if ( seen . has ( node ) ) { return [ ] ; } return reference . findReferencesAsNodes ( program , languageService , node ) . reduce < readonly ts . ClassDeclaration [ ] > ( ( acc , ref ) => { const parent = node_ . getParent ( ref ) as ts . Node | undefined ; if ( parent === undefined ) { return acc ; } const clause = node_ . getParent ( parent ) as ts . Node | undefined ; if ( clause === undefined || ! ts . isHeritageClause ( clause ) || ! heritage . isExtends ( clause ) ) { return acc ; } const derived = node_ . getFirstAncestorByTestOrThrow ( clause , ts . isClassDeclaration ) ; return acc . concat ( getImplementorsWorker ( program , languageService , derived , seen ) ) ; } , [ ] ) . concat ( ts . isClassDeclaration ( node ) ? [ node ] : [ ] ) ; } export function getExtendors ( program , languageService , node ) { return getExtendorsWorker ( program , languageService , node ) ; } export function getBaseTypes ( typeChecker , node ) { return type_ . getBaseTypesArray ( type_ . getType ( typeChecker , node ) ) ; } export function getBaseTypesFlattened ( typeChecker , node ) { function getBaseTypesWorker ( type ) { if ( type_ . isIntersection ( type ) ) { return _ . flatten ( type_ . getIntersectionTypesArray ( type ) . map ( getBaseTypesWorker ) ) ; } const baseTypes = type_ . getBaseTypesArray ( type ) ; return [ type ] . concat ( _ . flatten ( baseTypes . map ( getBaseTypesWorker ) ) ) ; } return _ . flatten ( getBaseTypes ( typeChecker , node ) . map ( getBaseTypesWorker ) ) ; } export function getBaseClasses ( typeChecker , node ) { const baseTypes = getBaseTypesFlattened ( typeChecker , node ) ; return baseTypes . map ( ( type ) => type_ . getSymbol ( type ) ) . filter ( utils . notNull ) . map ( ( symbol ) => symbol_ . getDeclarations ( symbol ) ) . reduce < readonly ts . Declaration [ ] > ( ( a , b ) => a . concat ( b ) , [ ] ) . filter ( ts . isClassDeclaration ) ; } export function getBaseClass ( typeChecker , node ) { const declarations = getBaseClasses ( typeChecker , node ) ; return declarations . length === 1 ? declarations [ 0 ] : undefined ; }