import * as ts from 'typescript' ; import { FromWhat , getFromText } from './common' ; import { findAllChildrenOfKind , findFirstChildOfKind , recurseIntoChildren , } from './util' ; import { namespaceBlacklist } from './namespaceBlacklist' ; export const mayContainDynamicImports = ( node ) : boolean => ! namespaceBlacklist . includes ( node . kind ) && node . getText ( ) . includes ( 'import(' ) ; type WithExpression = ts . Node & { expression : ts . Expression ; } ; function isWithExpressionBoolean ( node ) { const myInterface = node as WithExpression ; return ! ! myInterface . expression ; } function isWithExpression ( node ) { return isWithExpressionBoolean ( node ) ; } const parseDereferencedLambdaParamsToTypes = ( paramName , lambda ) : string [ ] => { const types = [ ] ; const usagePrefix = `${ paramName }.` ; recurseIntoChildren ( lambda , ( child ) => { if ( child . getText ( ) . startsWith ( usagePrefix ) ) { const usage = child . getText ( ) . substring ( usagePrefix . length ) ; types . push ( usage ) ; } return true ; } ) ; return types ; } ; const hasBracesLike = ( paramList , openBrace , closeBrace ) : boolean => { return paramList . startsWith ( openBrace ) && paramList . endsWith ( closeBrace ) ; } ; const parseDestructuredLambdaParamsToTypes = ( paramList ) : string [ ] => { if ( hasBracesLike ( paramList , '{' , '}' ) ) { const names = paramList . substring ( 1 , paramList . length - 1 ) ; return names . split ( ',' ) . map ( ( n ) => ( n . includes ( ':' ) ? n . split ( ':' ) [ 0 ] : n ) ) . map ( ( n ) => n . trim ( ) ) ; } return [ paramList ] ; } ; const findLambdasWithDereferencing = ( node ) : string [ ] => { const what = [ ] ; const processLambda = ( lambda ) : void => { if ( lambda . getChildCount ( ) === 3 ) { const paramName = lambda . getChildren ( ) [ 0 ] . getText ( ) ; parseDereferencedLambdaParamsToTypes ( paramName , lambda ) . forEach ( ( t ) => what . push ( t ) ) ; } else if ( lambda . getChildCount ( ) === 5 && lambda . getChildAt ( 1 ) . kind == ts . SyntaxKind . SyntaxList ) { const paramNames = lambda . getChildren ( ) [ 1 ] . getText ( ) ; parseDestructuredLambdaParamsToTypes ( paramNames ) . forEach ( ( p ) => { what . push ( p ) ; parseDereferencedLambdaParamsToTypes ( p , lambda ) . forEach ( ( t ) => what . push ( t ) ) ; } ) ; } } ; const firstArrow = findFirstChildOfKind ( node , ts . SyntaxKind . ArrowFunction ) ; if ( firstArrow ) { processLambda ( firstArrow ) ; } return what ; } ; const addImportViaLambda = ( node , from , addImport ) : boolean => { const whatFromLambda = findLambdasWithDereferencing ( node ) ; const what = [ 'default' ] . concat ( whatFromLambda ) ; addImport ( { from : getFromText ( from ) , what , } ) ; return whatFromLambda . length !== 0 ; } ; type ExpressionParser = ( expr : ts . Expression , addImport : ( fw : FromWhat ) => void ) => boolean ; const tryParseImportExpression = ( expr , addImport ) : boolean => { const callExpression = findFirstChildOfKind ( expr , ts . SyntaxKind . CallExpression ) ; if ( ! callExpression ?. getText ( ) . startsWith ( 'import' ) ) { return false ; } const syntaxListWithFrom = findFirstChildOfKind ( callExpression , ts . SyntaxKind . SyntaxList ) ; if ( ! syntaxListWithFrom ) { return false ; } const from = syntaxListWithFrom . getText ( ) ; return addImportViaLambda ( expr , from , addImport ) ; } ; const tryParseExpression = ( expr , addImport ) : boolean => { if ( expr . getText ( ) . startsWith ( 'import' ) ) { return tryParseImportExpression ( expr , addImport ) ; } recurseIntoChildren ( expr , ( node ) => { if ( isWithExpressionBoolean ( node ) && node . getText ( ) . startsWith ( 'import' ) ) { tryParseImportExpression ( node as ts . Expression , addImport ) ; } return true ; } ) ; return false ; } ; const handleImportWithJsxAttributes = ( attributes , addImport ) : void => { attributes . properties . forEach ( ( prop ) => { if ( ts . isJsxAttribute ( prop ) ) { if ( prop . initializer && ts . isJsxExpression ( prop . initializer ) && prop . initializer . expression ) { tryParseExpression ( prop . initializer . expression , addImport ) ; } } } ) ; } ; const handleImportWithinExpression = ( node , addImport ) : void => { let expression1 = null ; if ( node . kind === ts . SyntaxKind . CallExpression ) expression1 = node as ts . Expression ; else if ( isWithExpression ( node ) ) expression1 = node . expression ; while ( ! ! expression1 ) { if ( ! tryParseExpression ( expression1 , addImport ) ) { if ( ts . isJsxElement ( expression1 ) || ts . isJsxFragment ( expression1 ) ) { const jsxExpressions = findAllChildrenOfKind ( expression1 , ts . SyntaxKind . JsxExpression ) ; jsxExpressions . forEach ( ( j ) => { const jsxExpr = j as ts . JsxExpression ; if ( jsxExpr . expression ) { tryParseExpression ( jsxExpr . expression , addImport ) ; } } ) ; } const selfClosingElements = findAllChildrenOfKind ( expression1 , ts . SyntaxKind . JsxSelfClosingElement ) ; selfClosingElements . forEach ( ( elem ) => { if ( ts . isJsxSelfClosingElement ( elem ) ) { handleImportWithJsxAttributes ( elem . attributes , addImport ) ; } } ) ; } if ( isWithExpression ( expression1 ) ) { expression1 = expression1 . expression ; } else { break ; } } } ; export const addDynamicImports = ( node , addImport ) : void => { const addImportsInAnyExpression = ( node ) : boolean => { if ( isWithExpression ( node ) ) { handleImportWithinExpression ( node , addImport ) ; } return true ; } ; recurseIntoChildren ( node , addImportsInAnyExpression ) ; } ;