/**
 * Code taken from the utils of @schematics/angular
 * with some additions and modifications
 */ import * as ts from 'typescript' ; import { Change , InsertChange , NoopChange } from './change' ; param source param arrayName param symbolName param allowDuplicates return export function appendToArrayDeclaration ( source , arrayName , symbolName , allowDuplicates ? ) { const arrays = findNodes ( source , ts . SyntaxKind . ArrayLiteralExpression ) as ts . ArrayLiteralExpression [ ] ; for ( let j = 0 ; j < arrays . length ; j ++ ) { const node = arrays [ j ] ; if ( node . parent . kind !== ts . SyntaxKind . VariableDeclaration ) { continue ; } const children = node . parent . getChildren ( ) ; for ( let i = 0 ; i < children . length ; i ++ ) { const child = children [ i ] ; if ( child . kind === ts . SyntaxKind . Identifier && ( child as ts . Identifier ) . escapedText === arrayName ) { if ( ! allowDuplicates ) { const matches = node . elements . filter ( ( component ) => { return component . getText ( ) === symbolName ; } ) ; if ( matches . length > 0 ) { return new NoopChange ( ) ; } } const toInsert = node . elements . hasTrailingComma ? ` ${ symbolName },` : `, ${ symbolName }` ; return new InsertChange ( source . fileName , node . elements . end , toInsert ) ; } } } return new NoopChange ( ) ; } param source param symbolName param fileName param insertPos return export function insertExport ( source , symbolName , fileName , insertPos ? ) { const rootNode = source ; const allExports = findNodes ( rootNode , ts . SyntaxKind . ExportDeclaration ) as ts . ExportDeclaration [ ] ; for ( let i = 0 ; i < allExports . length ; i ++ ) { const exportDeclaration = allExports [ i ] ; if ( exportDeclaration . exportClause ) { const exported = exportDeclaration . exportClause . elements ; for ( let j = 0 ; j < exported . length ; j ++ ) { if ( exported [ j ] . name . escapedText === symbolName ) { return new NoopChange ( ) ; } } } else { const exportFiles = exportDeclaration . getChildren ( ) . filter ( ( child ) => child . kind === ts . SyntaxKind . StringLiteral ) . map ( ( n ) => ( n as ts . StringLiteral ) . text ) ; if ( exportFiles . filter ( ( file ) => file === fileName ) . length === 1 ) { return new NoopChange ( ) ; } } } if ( insertPos === undefined ) { const importNode = findImportNode ( source , symbolName , fileName ) ; insertPos = importNode ? importNode . getEnd ( ) + 1 : 0 ; } const startSeparator = insertPos === 0 ? '' : '\n' ; const endSeparator = insertPos === 0 ? '\n' : '' ; return new InsertChange ( source . fileName , insertPos , `${ startSeparator }export { ${ symbolName } };${ endSeparator }` ) ; } return function findImportNode ( source , symbolName , filePath ) { const allNodes = getSourceNodes ( source ) ; const matchingNodes = allNodes . filter ( ( node ) => node . kind === ts . SyntaxKind . ImportDeclaration ) . filter ( ( imp ) => imp . moduleSpecifier . kind === ts . SyntaxKind . StringLiteral ) . filter ( ( imp ) => { return ( imp . moduleSpecifier as ts . StringLiteral ) . text === filePath ; } ) . filter ( ( imp ) => { if ( ! imp . importClause ) { return false ; } const nodes = findNodes ( imp . importClause , ts . SyntaxKind . ImportSpecifier ) . filter ( ( n ) => n . getText ( ) === symbolName ) ; return nodes . length > 0 ; } ) ; return matchingNodes . length > 0 ? matchingNodes [ 0 ] : undefined ; } param fileToEdit param symbolName param fileName param insertPos param isDefault return export function insertImport ( source , fileToEdit , symbolName , fileName , insertPos , isDefault = false ) { const rootNode = source ; const allImports = findNodes ( rootNode , ts . SyntaxKind . ImportDeclaration ) ; const relevantImports = allImports . filter ( ( node ) => { const importFiles = node . getChildren ( ) . filter ( ( child ) => child . kind === ts . SyntaxKind . StringLiteral ) . map ( ( n ) => ( n as ts . StringLiteral ) . text ) ; return importFiles . filter ( ( file ) => file === fileName ) . length === 1 ; } ) ; if ( relevantImports . length > 0 ) { let importsAsterisk = false ; const imports = [ ] ; relevantImports . forEach ( ( n ) => { Array . prototype . push . apply ( imports , findNodes ( n , ts . SyntaxKind . Identifier ) ) ; if ( findNodes ( n , ts . SyntaxKind . AsteriskToken ) . length > 0 ) { importsAsterisk = true ; } } ) ; if ( importsAsterisk ) { return new NoopChange ( ) ; } const importTextNodes = imports . filter ( ( n ) => ( n as ts . Identifier ) . text === symbolName ) ; if ( importTextNodes . length === 0 ) { const fallbackPos = findNodes ( relevantImports [ 0 ] , ts . SyntaxKind . CloseBraceToken ) [ 0 ] . getStart ( ) || findNodes ( relevantImports [ 0 ] , ts . SyntaxKind . FromKeyword ) [ 0 ] . getStart ( ) ; return insertAfterLastOccurrence ( imports , `, ${ symbolName }` , fileToEdit , fallbackPos ) ; } return new NoopChange ( ) ; } const open = isDefault ? '' : '{ ' ; const close = isDefault ? '' : ' }' ; const startSeparator = insertPos === 0 ? '' : '\n' ; const endSeparator = insertPos === 0 ? '\n' : '' ; const toInsert = `${ startSeparator }import ${ open }${ symbolName }${ close }` + ` from '${ fileName }';${ endSeparator }` ; return new InsertChange ( source . fileName , insertPos , toInsert ) ; } param node param kind param max return export function findNodes ( node , kind , max = Infinity ) { if ( ! node || max === 0 ) { return [ ] ; } const arr = [ ] ; if ( node . kind === kind ) { arr . push ( node ) ; max -- ; } if ( max > 0 ) { for ( const child of node . getChildren ( ) ) { findNodes ( child , kind , max ) . forEach ( ( subnode ) => { if ( max > 0 ) { arr . push ( subnode ) ; } max -- ; } ) ; if ( max <= 0 ) { break ; } } } return arr ; } param sourceFile returns { Observable < ts . Node > } export function getSourceNodes ( sourceFile ) { const nodes = [ sourceFile ] ; const result = [ ] ; while ( nodes . length > 0 ) { const node = nodes . shift ( ) ; if ( node ) { result . push ( node ) ; if ( node . getChildCount ( sourceFile ) >= 0 ) { nodes . unshift ( ... node . getChildren ( ) ) ; } } } return result ; } export function findNode ( node , kind , text ) { if ( node . kind === kind && node . getText ( ) === text ) { return node ; } let foundNode = null ; ts . forEachChild ( node , ( childNode ) => { foundNode = foundNode || findNode ( childNode , kind , text ) ; } ) ; return foundNode ; } return export function nodesByPosition ( first , second ) { return first . getStart ( ) - second . getStart ( ) ; } param nodes param toInsert param file param fallbackPos param syntaxKind return throw export function insertAfterLastOccurrence ( nodes , toInsert , file , fallbackPos , syntaxKind ? ) { let lastItem = [ ... nodes ] . sort ( nodesByPosition ) . pop ( ) ; if ( ! lastItem ) { throw new Error ( ) ; } if ( syntaxKind ) { lastItem = findNodes ( lastItem , syntaxKind ) . sort ( nodesByPosition ) . pop ( ) ; } if ( ! lastItem && fallbackPos === undefined ) { throw new Error ( `tried to insert ${ toInsert } ` + 'as first occurence with no fallback position' ) ; } const lastItemPosition = lastItem ? lastItem . getEnd ( ) : fallbackPos ; return new InsertChange ( file , lastItemPosition , toInsert ) ; } export function getContentOfKeyLiteral ( _source , node ) { if ( node . kind === ts . SyntaxKind . Identifier ) { return ( node as ts . Identifier ) . text ; } else if ( node . kind === ts . SyntaxKind . StringLiteral ) { return ( node as ts . StringLiteral ) . text ; } else { return null ; } }