import * as ts from 'typescript' ; import * as util from './util' ; import { ProgramError } from './errors' ; import { Factory } from './factory' ; import { Options , defaultOptions } from './options' ; import { Scanner } from './scanner' ; export class MutationContext { private _options : Options ; private _sourceFile : ts . SourceFile ; private _program : ts . Program ; private _checker : ts . TypeChecker ; private _host : ts . CompilerHost ; private _scanner : Scanner ; private _skipNodes : ts . Node [ ] ; private _factory : Factory ; private _transformationContext : ts . TransformationContext ; private _merged : Set < ts . Symbol > ; private _entryFiles : string [ ] ; private _commonDir : string ; constructor ( sourceFile , options , program , host , scanner , entryFiles , commonDir , context ? ) { this . _skipNodes = [ ] ; this . _sourceFile = sourceFile ; this . _options = options ; this . _program = program ; this . _checker = program . getTypeChecker ( ) ; this . _host = host ; this . _scanner = scanner ; this . _factory = new Factory ( this , options ) ; this . _transformationContext = context ; this . _merged = new Set ( ) ; this . _entryFiles = entryFiles ; this . _commonDir = commonDir ; } public skip < T extends ts . Node > ( node , recursive = false , ... exclude ) { if ( ! this . shouldSkip ( node ) && exclude . indexOf ( node ) === - 1 ) { this . _skipNodes . push ( node ) ; } if ( recursive ) { ts . forEachChild ( node , ( n ) => { this . skip ( n , recursive , ... exclude ) ; } ) ; } return node ; } public shouldSkip ( node ) { if ( this . _skipNodes . indexOf ( node ) !== - 1 ) { return true ; } return false ; } public dontSkip ( node ) { const index = this . _skipNodes . indexOf ( node ) ; if ( index !== - 1 ) { this . _skipNodes . splice ( index , 1 ) ; return true ; } return false ; } public isEntryFile ( fileName ) { return this . _entryFiles . indexOf ( fileName ) !== - 1 ; } public isImplementationOfOverload ( node ) { return ! ! ( ts . isFunctionLike ( node ) && this . checker . isImplementationOfOverload ( node ) ) ; } public isDeclared ( node ) { if ( ! node . getSourceFile ( ) ) { return false ; } const fileName = node . getSourceFile ( ) . fileName ; node = util . getBaseIdentifierOfEntityName ( node ) ; const typeInfo = this . scanner . getTypeInfo ( node ) ; const declarations = typeInfo . declarations . filter ( d => fileName === d . getSourceFile ( ) . fileName ) || [ ] ; if ( declarations . length > 0 ) { return true ; } return false ; } public wasDeclared ( node ) { if ( ! node . getSourceFile ( ) ) { return false ; } const fileName = node . getSourceFile ( ) . fileName ; node = util . getBaseIdentifierOfEntityName ( node ) ; const typeInfo = this . scanner . getTypeInfo ( node ) ; const declarations = typeInfo . declarations . filter ( d => fileName === d . getSourceFile ( ) . fileName ) ; for ( let declaration of declarations ) { if ( declaration . getEnd ( ) < node . getEnd ( ) ) { return true ; } } return false ; } public isAny ( node ) { if ( util . isAnyKeyword ( node ) ) { return true ; } const typeInfo = this . scanner . getTypeInfo ( node ) ; if ( ! typeInfo || ! typeInfo . typeNode ) { return false ; } if ( util . isAnyKeyword ( typeInfo . typeNode ) ) { return true ; } const enclsoingSymbol = this . scanner . getNodeSymbol ( typeInfo . enclosing ) ; if ( typeInfo . enclosing && this . scanner . getNodeSymbol ( typeInfo . enclosing ) ) { if ( enclsoingSymbol . valueDeclaration && util . canHaveType ( enclsoingSymbol . valueDeclaration ) ) { if ( enclsoingSymbol . valueDeclaration . type === undefined || util . isAnyKeyword ( enclsoingSymbol . valueDeclaration . type ) ) { return true ; } } } if ( typeInfo . symbol && typeInfo . symbol . valueDeclaration && util . canHaveType ( typeInfo . symbol . valueDeclaration ) ) { if ( typeInfo . symbol . valueDeclaration . type === undefined || util . isAnyKeyword ( typeInfo . symbol . valueDeclaration . type ) ) { return true ; } } return false ; } public isSelfReference ( node ) { let next = node ; const typeInfo = this . scanner . getTypeInfo ( node . typeName ) ; while ( next . parent ) { next = next . parent ; if ( ts . isHeritageClause ( node ) ) { return false ; } if ( ts . isClassDeclaration ( next ) || ts . isInterfaceDeclaration ( next ) || ts . isTypeAliasDeclaration ( next ) ) { const symbol = this . scanner . getNodeSymbol ( next . name ) ; if ( typeInfo . symbol === symbol && next . name . text === util . getIdentifierOfEntityName ( node . typeName ) . text ) { return true ; } } } return false ; } public hasSelfReference ( node ) { const symbol = this . scanner . getNodeSymbol ( ( node as any ) . name || ( node as any ) . typeName || node ) ; if ( ! symbol ) { return false ; } const declarations = symbol . getDeclarations ( ) ; if ( ! declarations ) { return false ; } for ( let decl of declarations || [ ] ) { if ( this . hasApparentSelfReference ( decl ) ) { return true ; } } return false ; } public hasApparentSelfReference ( node ) { const nodeSymbol = this . scanner . getNodeSymbol ( ( node as any ) . name || ( node as any ) . typeName || node ) ; const search = ( node ) : boolean => { if ( ts . isTypeReferenceNode ( node ) ) { const symbol = this . scanner . getNodeSymbol ( node . typeName ) ; if ( nodeSymbol === symbol ) { return true ; } } return ts . forEachChild ( node , child => search ( child ) ) ; } ; return search ( node ) || false ; } public hasProperty ( node , name ) { const nodeType = this . scanner . getType ( node ) ; if ( ! nodeType ) { return false ; } for ( let prop of nodeType . getProperties ( ) ) { if ( prop . name === name ) { return true ; } } return false ; } public getPropertyName ( node , name ) { const nodeType = this . scanner . getType ( node ) ; const originalName = name ; if ( ! nodeType ) { return name ; } const ids = nodeType . getProperties ( ) . map ( prop => { return prop . name ; } ) ; while ( ids && ids . indexOf ( name ) !== - 1 ) { name = `_${ name }` ; } name = `${ name }_${ originalName }` ; if ( ids && ids . indexOf ( name ) !== - 1 ) { return this . getPropertyName ( node , name ) ; } return name ; } public getIdentifier ( text ) { const ids = this . scanner . getIdentifiers ( this . sourceFile ) ; while ( ids && ids . has ( text ) ) { text = `_${ text }` ; } return text ; } public getTypeDeclarationName ( node ) { const name = typeof node === 'string' ? node : node . text ; return this . getIdentifier ( `${ this . options . declarationPrefix }${ name }Type` ) ; } public getReturnTypeDeclarationName ( ) { return this . getTypeDeclarationName ( 'return' ) ; } public getLibDeclarationName ( ) { return this . getIdentifier ( `${ this . options . libNamespace }${ this . options . libIdentifier }` ) ; } public getTypeSymbolDeclarationName ( node ) { const name = typeof node === 'string' ? node : node . text ; return this . getIdentifier ( `${ this . options . declarationPrefix }${ name }TypeParametersSymbol` ) ; } public getTypeSymbolDeclarationInitializer ( node ) { const name = typeof node === 'string' ? node : node . text ; return this . getIdentifier ( `${ name }TypeParameters` ) ; } public getTypeParametersDeclarationName ( ) { return this . getIdentifier ( `${ this . options . declarationPrefix }typeParameters` ) ; } public getMembers ( node ) { const nodeSymbol = this . scanner . getNodeSymbol ( node . name ) ; const merged = new Set ( ) ; if ( ! nodeSymbol ) { return util . arrayFromNodeArray ( node . members as ts . NodeArray < ts . TypeElement | ts . ClassElement > ) ; } const members = [ ] ; nodeSymbol . members . forEach ( ( member , key ) => { if ( member . flags & ts . SymbolFlags . TypeParameter ) { return ; } members . push ( member ) ; } ) ; members . forEach ( sym => { for ( let typeElement of ( ( sym . getDeclarations ( ) || [ ] ) as ( ts . TypeElement | ts . ClassElement ) [ ] ) ) { merged . add ( typeElement ) ; } } ) ; return Array . from ( merged ) ; } public setMerged ( symbol ) { return this . _merged . add ( symbol ) ; } public wasMerged ( symbol ) { return this . _merged . has ( symbol ) ; } get sourceFile ( ) : ts . SourceFile { return this . _sourceFile ; } set sourceFile ( sourceFile ) { if ( ! ts . isSourceFile ( sourceFile ) ) { throw new ProgramError ( `Attemt to set invalid node as SourceFile on MutationContext.` ) ; } this . _sourceFile = sourceFile ; } get transformationContext ( ) : ts . TransformationContext { return this . _transformationContext ; } set transformationContext ( transformationContext ) { this . _transformationContext = transformationContext ; } get compilerOptions ( ) : ts . CompilerOptions { return this . _options . compilerOptions ; } get options ( ) : Options { return this . _options ; } get program ( ) : ts . Program { return this . _program ; } get checker ( ) : ts . TypeChecker { return this . _checker ; } get host ( ) : ts . CompilerHost { return this . _host ; } get scanner ( ) : Scanner { return this . _scanner ; } get factory ( ) : Factory { return this . _factory ; } get map ( ) : < T extends ts . Node > ( alias : T , original : ts . Node ) => T { return this . _scanner . mapNode . bind ( this . _scanner ) ; } get entryFiles ( ) : string [ ] { return this . _entryFiles ; } get commonDir ( ) : string { return this . _commonDir ; } }