import { Analysis , ExtraCommandLineOptions , File , LocationInFile , } from './types' ; import { removeExportStarPrefix } from './parser/util' ; export { Analysis } from './types' ; interface FileExport { usageCount : number ; location : LocationInFile ; } interface FileExports { [ index ] : FileExport ; } interface ExportItem { exports : FileExports ; path : string ; } interface ExportMap { [ index ] : ExportItem ; } const isExportArray = ( e ) : boolean => { return e . startsWith ( '[' ) && e . endsWith ( ']' ) ; } ; const parseExportArray = ( e ) : string [ ] => { return e . replace ( '[' , '' ) . replace ( ']' , '' ) . split ( ',' ) . map ( ( e ) => e . trim ( ) ) ; } ; const getFileExports = ( file ) : ExportItem => { const exports = { } ; file . exports . forEach ( ( e , index ) => { const addExport = ( exportName ) : void => { exports [ exportName ] = { usageCount : 0 , location : file . exportLocations [ index ] , } ; } ; if ( isExportArray ( e ) ) { const exportArray = parseExportArray ( e ) ; exportArray . forEach ( addExport ) ; } else { addExport ( e ) ; } } ) ; return { exports , path : file . fullPath } ; } ; const getExportMap = ( files ) : ExportMap => { const map = { } ; files . forEach ( ( file ) => { map [ file . path ] = getFileExports ( file ) ; } ) ; return map ; } ; const processImports = ( file , exportMap ) : void => { Object . keys ( file . imports ) . forEach ( ( key ) => { let ex = exportMap [ key ] ?. exports ; if ( ! ex && key === '.' ) { const indexCandidates = [ 'index' , 'index.ts' , 'index.tsx' ] ; for ( let c = 0 ; c < indexCandidates . length ; c ++ ) { const indexKey = indexCandidates [ c ] ; ex = exportMap [ indexKey ] ?. exports || undefined ; if ( ex ) break ; } } if ( ! ex ) return ; const addUsage = ( imp ) : void => { if ( ! ex [ imp ] ) { ex [ imp ] = { usageCount : 0 , location : { line : 1 , character : 1 , } , } ; } ex [ imp ] . usageCount ++ ; } ; file . imports [ key ] . forEach ( ( imp ) => imp === '*' ? Object . keys ( ex ) . filter ( ( e ) => e != 'default' ) . forEach ( addUsage ) : addUsage ( imp ) ) ; } ) ; } ; const expandExportFromStarOrStarAsForFile = ( file , exportMap , prefix , isWithAlias ) : void => { const fileExports = exportMap [ file . path ] ; file . exports . filter ( ( ex ) => ex . startsWith ( prefix ) ) . forEach ( ( ex ) => { delete fileExports . exports [ ex ] ; const exports = exportMap [ removeExportStarPrefix ( ex ) ] ?. exports ; if ( exports ) { Object . keys ( exports ) . filter ( ( e ) => e != 'default' ) . forEach ( ( key ) => { if ( ! isWithAlias ) { if ( ! fileExports . exports [ key ] ) { const export1 = exports [ key ] ; fileExports . exports [ key ] = { usageCount : 0 , location : export1 . location , } ; } fileExports . exports [ key ] . usageCount = 0 ; } const importedFileExports = exportMap [ removeExportStarPrefix ( ex ) ] ; if ( importedFileExports ) { importedFileExports . exports [ key ] . usageCount ++ ; } } ) ; } } ) ; } ; const expandExportFromStarForFile = ( file , exportMap ) : void => { expandExportFromStarOrStarAsForFile ( file , exportMap , '*:' , false ) ; } ; const expandExportFromStarAsForFile = ( file , exportMap ) : void => { expandExportFromStarOrStarAsForFile ( file , exportMap , '*as:' , true ) ; } ; const expandExportFromStar = ( files , exportMap ) : void => { files . forEach ( ( file ) => { expandExportFromStarForFile ( file , exportMap ) ; expandExportFromStarAsForFile ( file , exportMap ) ; } ) ; } ; const shouldPathBeExcludedFromResults = ( path , extraOptions ? ) : boolean => { if ( ! extraOptions || ! extraOptions . pathsToExcludeFromReport ) { return false ; } return extraOptions . pathsToExcludeFromReport . some ( ( ignore ) => path . includes ( ignore ) ) ; } ; const filterFiles = ( files , extraOptions ? ) : File [ ] => { if ( ! extraOptions ?. ignoreFilesRegex ) { return files ; } const regexes = extraOptions . ignoreFilesRegex ?. map ( ( rex ) => new RegExp ( rex ) ) ; const shouldIgnoreFile = ( fileName ) : boolean => { return regexes . some ( ( reg ) => { return reg . test ( fileName ) ; } ) ; } ; return files . filter ( ( f ) => ! shouldIgnoreFile ( f . path ) ) ; } ; export default ( files , extraOptions ? ) : Analysis => { const filteredFiles = filterFiles ( files , extraOptions ) ; const exportMap = getExportMap ( filteredFiles ) ; expandExportFromStar ( filteredFiles , exportMap ) ; filteredFiles . forEach ( ( file ) => processImports ( file , exportMap ) ) ; const analysis = { } ; Object . keys ( exportMap ) . forEach ( ( file ) => { const expItem = exportMap [ file ] ; const { exports , path } = expItem ; if ( shouldPathBeExcludedFromResults ( path , extraOptions ) ) return ; const unusedExports = Object . keys ( exports ) . filter ( ( k ) => exports [ k ] . usageCount === 0 ) ; if ( unusedExports . length === 0 ) { return ; } analysis [ path ] = [ ] ; unusedExports . forEach ( ( e ) => { analysis [ path ] . push ( { exportName : e , location : exports [ e ] . location , } ) ; } ) ; } ) ; return analysis ; } ;