import { SourceFileProvider } from './SourceFileProvider' ; import { fdir } from 'fdir' ; import NormalizedPath from '../types/NormalizedPath' ; import { promisify } from 'util' ; import * as fs from 'fs' ; import * as path from 'path' ; const readFile = promisify ( fs . readFile ) ; const stat = promisify ( fs . stat ) ; import { createMatchPathAsync , MatchPathAsync } from 'tsconfig-paths' ; import { getScriptFileExtensions } from '../utils/getScriptFileExtensions' ; import { getParsedCommandLineOfConfigFile , JsxEmit , ParsedCommandLine , preProcessFile , } from 'typescript' ; import picomatch from 'picomatch' ; export class FDirSourceFileProvider implements SourceFileProvider { parsedCommandLine : ParsedCommandLine ; excludePatternsPicoMatchers : picomatch . Matcher [ ] ; matchPath : MatchPathAsync ; private sourceFileGlob : string ; private extensionsToCheckDuringImportResolution : string [ ] ; constructor ( configFileName , private rootDirs ) { this . parsedCommandLine = getParsedCommandLineOfConfigFile ( configFileName , { } , { getCurrentDirectory : process . cwd , fileExists : fs . existsSync , useCaseSensitiveFileNames : true , readFile : path => fs . readFileSync ( path , 'utf-8' ) , readDirectory : ( ) => { return [ ] ; } , onUnRecoverableConfigFileDiagnostic : diagnostic => { console . error ( diagnostic ) ; process . exit ( 1 ) ; } , } ) ; const baseUrl = this . parsedCommandLine . options . baseUrl ?? path . dirname ( configFileName ) ; this . excludePatternsPicoMatchers = ( this . parsedCommandLine . raw ?. exclude ?? [ ] ) . map ( ( excludePattern ) => { const matcher = picomatch ( excludePattern ) ; return ( pathToCheck ) => { return matcher ( path . relative ( baseUrl , pathToCheck ) ) ; } ; } ) ; this . sourceFileGlob = `**/*@(${ getScriptFileExtensions ( { allowJs : this . parsedCommandLine . options . allowJs || false , jsx : this . parsedCommandLine . options . jsx !== JsxEmit . None , includeJson : false , includeDefinitions : false , } ) . join ( '|' ) })` ; this . extensionsToCheckDuringImportResolution = getScriptFileExtensions ( { allowJs : this . parsedCommandLine . options . allowJs || false , jsx : this . parsedCommandLine . options . jsx !== JsxEmit . None , includeJson : this . parsedCommandLine . options . resolveJsonModule , includeDefinitions : true , } ) ; this . matchPath = createMatchPathAsync ( baseUrl , this . parsedCommandLine . options . paths ?? { } ) ; } async getSourceFiles ( searchRoots ? ) { const allRootsDiscoveredFiles = await Promise . all ( ( searchRoots || this . rootDirs ) . map ( ( rootDir ) => new fdir ( ) . glob ( this . sourceFileGlob ) . withFullPaths ( ) . crawl ( rootDir ) . withPromise ( ) as Promise < string [ ] > ) ) ; return [ ... new Set < string > ( allRootsDiscoveredFiles . reduce ( ( a , b ) => a . concat ( b ) , [ ] ) ) , ] . filter ( ( p ) => ! this . isPathExcluded ( p ) ) ; } private isPathExcluded ( path ) { return this . excludePatternsPicoMatchers . some ( isMatch => isMatch ( path ) ) ; } async getImportsForFile ( filePath ) { const fileInfo = preProcessFile ( await readFile ( filePath , 'utf-8' ) , true , true ) ; return fileInfo . importedFiles . map ( importedFile => importedFile . fileName ) ; } async resolveImportFromFile ( importer , importSpecifier ) { if ( importSpecifier . startsWith ( '.' ) ) { const directImportResult = await checkExtensions ( path . join ( path . dirname ( importer ) , importSpecifier ) , [ ... this . extensionsToCheckDuringImportResolution , '' , ... this . extensionsToCheckDuringImportResolution . map ( x => '/index' + x ) , ] ) ; if ( directImportResult && this . extensionsToCheckDuringImportResolution . some ( extension => directImportResult . endsWith ( extension ) ) ) { return directImportResult ; } else { return undefined ; } } else { return await new Promise ( ( resolve , reject ) => this . matchPath ( importSpecifier , undefined , undefined , [ ... this . extensionsToCheckDuringImportResolution , '' ] , async ( err , result ) => { if ( err ) { reject ( err ) ; } else if ( ! result ) { resolve ( undefined ) ; } else { if ( isFile ( result ) && this . extensionsToCheckDuringImportResolution . some ( extension => result . endsWith ( extension ) ) ) { resolve ( result ) ; } else { if ( await isDirectory ( result ) ) { resolve ( checkExtensions ( path . join ( result , 'index' ) , this . extensionsToCheckDuringImportResolution ) ) ; } else { resolve ( checkExtensions ( result , this . extensionsToCheckDuringImportResolution ) ) ; } } } } ) ) ; } } } async function isFile ( filePath ) { try { const statRes = await stat ( filePath ) ; if ( statRes . isFile ( ) ) { return true ; } } catch { return false ; } } async function isDirectory ( filePath ) { try { const statRes = await stat ( filePath ) ; if ( statRes . isDirectory ( ) ) { return true ; } } catch { return false ; } } async function checkExtensions ( filePathNoExt , extensions ) { for ( let ext of extensions ) { const joinedPath = filePathNoExt + ext ; if ( await isFile ( joinedPath ) ) { return joinedPath ; } } return undefined ; }