import { FakeFS , NativePath , PortablePath , PosixFS , ppath , VirtualFS , ZipFS , ZipOpenFS } from '@yarnpkg/fslib' ; import { getLibzipSync } from '@yarnpkg/libzip' ; import * as fs from 'fs' ; import * as tmp from 'tmp' ; import { URI } from 'vscode-uri' ; import { WorkspaceMap } from '../workspaceMap' ; import { ConsoleInterface , NullConsole } from './console' ; import { isDefined } from './core' ; import { FileSystem , FileWatcher , FileWatcherEventHandler , FileWatcherEventType , FileWatcherProvider , MkDirOptions , nullFileWatcherProvider , TmpfileOptions , } from './fileSystem' ; import { containsPath } from './pathUtils' ; import { getRootLength } from './pathUtils' ; tmp . setGracefulCleanup ( ) ; export function createFromRealFileSystem ( console ? , fileWatcherProvider ? ) { console = console ?? new NullConsole ( ) ; return new RealFileSystem ( fileWatcherProvider ?? nullFileWatcherProvider , console ) ; } const DOT_ZIP = `.zip` ; const DOT_EGG = `.egg` ; function getArchivePart ( path ) { let idx = path . indexOf ( DOT_ZIP ) ; if ( idx <= 0 ) { idx = path . indexOf ( DOT_EGG ) ; if ( idx <= 0 ) { return null ; } } if ( path [ idx - 1 ] === ppath . sep ) return null ; const nextCharIdx = idx + DOT_ZIP . length ; if ( path . length > nextCharIdx && path [ nextCharIdx ] !== ppath . sep ) return null ; return path . slice ( 0 , nextCharIdx ) as PortablePath ; } function hasZipOrEggExtension ( p ) { return p . endsWith ( DOT_ZIP ) || p . endsWith ( DOT_EGG ) ; } const zipMagic = [ Buffer . from ( [ 0x50 , 0x4b , 0x03 , 0x04 ] ) , Buffer . from ( [ 0x50 , 0x4b , 0x05 , 0x06 ] ) , Buffer . from ( [ 0x50 , 0x4b , 0x07 , 0x08 ] ) , ] ; function hasZipMagic ( fs , p ) { let fd ; try { fd = fs . openSync ( p , 'r' ) ; const buffer = Buffer . alloc ( 4 ) ; const bytesRead = fs . readSync ( fd , buffer , 0 , 4 , 0 ) ; if ( bytesRead < 4 ) { return false ; } for ( const magic of zipMagic ) { if ( buffer . compare ( magic ) === 0 ) { return true ; } } return false ; } catch { return false ; } finally { if ( fd !== undefined ) { fs . closeSync ( fd ) ; } } } class EggZipOpenFS extends ZipOpenFS { private override readonly baseFs ! : FakeFS < PortablePath > ; private override readonly filter ! : RegExp | null ; private override isZip ! : Set < PortablePath > ; private override notZip ! : Set < PortablePath > ; private override getZipSync < T > ( p , accept ) { return super . getZipSync ( p , accept ) ; } override findZip ( p ) { if ( this . filter && ! this . filter . test ( p ) ) return null ; let filePath = `` as PortablePath ; while ( true ) { const archivePart = getArchivePart ( p . substr ( filePath . length ) ) ; if ( ! archivePart ) return null ; filePath = this . pathUtils . join ( filePath , archivePart ) ; if ( this . isZip . has ( filePath ) === false ) { if ( this . notZip . has ( filePath ) ) continue ; try { if ( ! this . baseFs . lstatSync ( filePath ) . isFile ( ) ) { this . notZip . add ( filePath ) ; continue ; } if ( ! hasZipMagic ( this . baseFs , filePath ) ) { this . notZip . add ( filePath ) ; continue ; } try { this . getZipSync ( filePath , ( ) => { } ) ; } catch { this . notZip . add ( filePath ) ; continue ; } } catch { return null ; } this . isZip . add ( filePath ) ; } return { archivePath : filePath , subPath : this . pathUtils . join ( PortablePath . root , p . substr ( filePath . length ) as PortablePath ) , } ; } } } class YarnFS extends PosixFS { private readonly _eggZipOpenFS : EggZipOpenFS ; constructor ( ) { const eggZipOpenFS = new EggZipOpenFS ( { libzip : ( ) => getLibzipSync ( ) , useCache : true , maxOpenFiles : 80 , readOnlyArchives : true , } ) ; super ( new VirtualFS ( { baseFs : eggZipOpenFS , } ) ) ; this . _eggZipOpenFS = eggZipOpenFS ; } isZip ( p ) { return ! ! this . _eggZipOpenFS . findZip ( this . mapToBase ( p ) ) ; } } const yarnFS = new YarnFS ( ) ; class RealFileSystem implements FileSystem { private _tmpdir ? : string ; constructor ( private _fileWatcherProvider , private _console ) { } existsSync ( path ) { try { return yarnFS . existsSync ( path ) ; } catch { return false ; } } mkdirSync ( path , options ? ) { yarnFS . mkdirSync ( path , options ) ; } chdir ( path ) { process . chdir ( path ) ; } readdirSync ( path ) { return yarnFS . readdirSync ( path ) ; } readdirEntriesSync ( path ) { return yarnFS . readdirSync ( path , { withFileTypes : true } ) . map ( ( entry ) : fs . Dirent => { if ( hasZipOrEggExtension ( entry . name ) ) { if ( entry . isFile ( ) && yarnFS . isZip ( path ) ) { return { name : entry . name , isFile : ( ) => false , isDirectory : ( ) => true , isBlockDevice : ( ) => false , isCharacterDevice : ( ) => false , isSymbolicLink : ( ) => false , isFIFO : ( ) => false , isSocket : ( ) => false , } ; } } return entry ; } ) ; } readFileSync ( path , encoding ? ) ; readFileSync ( path , encoding ) ; readFileSync ( path , encoding ? ) ; readFileSync ( path , encoding = null ) { if ( encoding === 'utf8' || encoding === 'utf-8' ) { return yarnFS . readFileSync ( path , 'utf8' ) ; } return yarnFS . readFileSync ( path ) ; } writeFileSync ( path , data , encoding ) { yarnFS . writeFileSync ( path , data , encoding || undefined ) ; } statSync ( path ) { const stat = yarnFS . statSync ( path ) ; if ( hasZipOrEggExtension ( path ) ) { if ( stat . isFile ( ) && yarnFS . isZip ( path ) ) { return { ... stat , isFile : ( ) => false , isDirectory : ( ) => true , } ; } } return stat ; } unlinkSync ( path ) { yarnFS . unlinkSync ( path ) ; } realpathSync ( path ) { return yarnFS . realpathSync ( path ) ; } getModulePath ( ) { return ( global as any ) . __rootDirectory ; } createFileSystemWatcher ( paths , listener ) { return this . _fileWatcherProvider . createFileWatcher ( paths , listener ) ; } createReadStream ( path ) { return yarnFS . createReadStream ( path ) ; } createWriteStream ( path ) { return yarnFS . createWriteStream ( path ) ; } copyFileSync ( src , dst ) { yarnFS . copyFileSync ( src , dst ) ; } readFile ( path ) { return yarnFS . readFilePromise ( path ) ; } async readFileText ( path , encoding ) { if ( encoding === 'utf8' || encoding === 'utf-8' ) { return yarnFS . readFilePromise ( path , 'utf8' ) ; } const buffer = await yarnFS . readFilePromise ( path ) ; return buffer . toString ( encoding ) ; } tmpdir ( ) { if ( ! this . _tmpdir ) { const dir = tmp . dirSync ( { prefix : 'pyright' } ) ; this . _tmpdir = dir . name ; } return this . _tmpdir ; } tmpfile ( options ? ) { const f = tmp . fileSync ( { dir : this . tmpdir ( ) , discardDescriptor : true , ... options } ) ; return f . name ; } realCasePath ( path ) { try { if ( ! fs . existsSync ( path ) ) { return path ; } const realPath = fs . realpathSync . native ( path ) ; const rootLength = getRootLength ( realPath ) ; if ( rootLength <= 0 ) { return realPath ; } return realPath . substr ( 0 , rootLength ) . toLowerCase ( ) + realPath . substr ( rootLength ) ; } catch ( e ) { this . _console . error ( `Failed to get real file system casing for ${ path }: ${ e }` ) ; return path ; } } isMappedFilePath ( filepath ) { return false ; } getOriginalFilePath ( mappedFilePath ) { return mappedFilePath ; } getMappedFilePath ( originalFilepath ) { return originalFilepath ; } getUri ( path ) { return URI . file ( path ) . toString ( ) ; } isInZipOrEgg ( path ) { return /[^\\/]\.(?:egg|zip)[\\/]/ . test ( path ) && yarnFS . isZip ( path ) ; } } interface WorkspaceFileWatcher extends FileWatcher { workspacePaths : string [ ] ; eventHandler : FileWatcherEventHandler ; } export class WorkspaceFileWatcherProvider implements FileWatcherProvider { private _fileWatchers : WorkspaceFileWatcher [ ] = [ ] ; constructor ( private _workspaceMap , private _console ) { } createFileWatcher ( paths , listener ) { const workspacePaths = [ ] ; const nonWorkspacePaths = [ ] ; const workspaces = this . _workspaceMap . getNonDefaultWorkspaces ( ) ; paths . forEach ( ( path ) => { if ( workspaces . some ( ( workspace ) => containsPath ( workspace . rootPath , path ) ) ) { workspacePaths . push ( path ) ; } else { nonWorkspacePaths . push ( path ) ; } } ) ; const nodeWatchers = nonWorkspacePaths . map ( ( path ) => { if ( ! fs . existsSync ( path ) ) { return undefined ; } try { return fs . watch ( path , { recursive : true } , ( event , filename ) => listener ( event as FileWatcherEventType , filename ) ) ; } catch ( e ) { this . _console . warn ( `Exception received when installing file system watcher: ${ e }` ) ; return undefined ; } } ) . filter ( isDefined ) ; const self = this ; const fileWatcher = { close ( ) { self . _fileWatchers = self . _fileWatchers . filter ( ( watcher ) => watcher !== fileWatcher ) ; nodeWatchers . forEach ( ( watcher ) => { watcher . close ( ) ; } ) ; } , workspacePaths , eventHandler : listener , } ; self . _fileWatchers . push ( fileWatcher ) ; return fileWatcher ; } onFileChange ( eventType , filePath ) { this . _fileWatchers . forEach ( ( watcher ) => { if ( watcher . workspacePaths . some ( ( dirPath ) => containsPath ( dirPath , filePath ) ) ) { watcher . eventHandler ( eventType , filePath ) ; } } ) ; } }