11import getExePath from "#getExePath" ;
22import { dirname } from "node:path" ;
3+ import type {
4+ RootedDirectoryPath ,
5+ RootedFilePath ,
6+ RootedPath ,
7+ } from "../ast/index.ts" ;
38import {
4- createGetCanonicalFileName ,
9+ canonicalize ,
10+ CaseSensitivity ,
511 getPathComponents ,
612 normalizePath ,
13+ toRootedFilePath ,
714} from "./path.ts" ;
815import type {
916 RequestDirectoryEntries ,
@@ -21,19 +28,29 @@ export interface FileSystemEntries {
2128}
2229
2330export interface FileSystem {
24- directoryExists ?: ( ( directoryName : string ) => boolean | undefined ) | undefined ;
25- fileExists ?: ( ( fileName : string ) => boolean | undefined ) | undefined ;
26- getAccessibleEntries ?: ( ( directoryName : string ) => FileSystemEntries | undefined ) | undefined ;
31+ directoryExists ?: ( directoryName : RootedDirectoryPath ) => boolean | undefined ;
32+ fileExists ?: ( fileName : RootedFilePath ) => boolean | undefined ;
33+ getAccessibleEntries ?: ( directoryName : RootedDirectoryPath ) => FileSystemEntries | undefined ;
2734 /**
2835 * Read a file's content.
2936 * - Return the file content as a `string` (including `""` for empty files).
3037 * - Return `null` to indicate the file does not exist (without falling back to the real FS).
3138 * - Return `undefined` to fall back to the real filesystem.
3239 */
33- readFile ?: ( ( fileName : string ) => string | null | undefined ) | undefined ;
34- realpath ?: ( ( path : string ) => string | undefined ) | undefined ;
35- writeFile ?: ( ( path : string , content : string ) => void ) | undefined ;
36- removeFile ?: ( ( path : string ) => void ) | undefined ;
40+ readFile ?: ( fileName : RootedFilePath ) => string | null | undefined ;
41+ realpath ?: ( path : RootedPath ) => RootedPath | undefined ;
42+ writeFile ?: ( path : RootedFilePath , content : string ) => void ;
43+ removeFile ?: ( path : RootedFilePath ) => void ;
44+ }
45+
46+ export interface VirtualFileSystem extends FileSystem {
47+ directoryExists ( directoryName : RootedDirectoryPath ) : boolean ;
48+ fileExists ( fileName : RootedFilePath ) : boolean ;
49+ getAccessibleEntries ( directoryName : RootedDirectoryPath ) : FileSystemEntries | undefined ;
50+ readFile ( fileName : RootedFilePath ) : string | undefined ;
51+ realpath ( path : RootedPath ) : RootedPath ;
52+ writeFile ( path : RootedFilePath , content : string ) : void ;
53+ removeFile ( path : RootedFilePath ) : void ;
3754}
3855
3956/** The callback names supported by the Go server for virtual FS delegation. */
@@ -53,7 +70,7 @@ export interface CreateFileSystemWithLibOptions extends CreateFileSystemOptions
5370}
5471
5572export interface CreateVirtualFileSystemOptions {
56- useCaseSensitiveFileNames ?: boolean ;
73+ caseSensitivity ?: CaseSensitivity ;
5774}
5875
5976/**
@@ -201,13 +218,14 @@ function createVDirectory(name: string): VDirectory {
201218export function createVirtualFileSystem (
202219 files : Record < string , string > ,
203220 options : CreateVirtualFileSystemOptions = { } ,
204- ) : FileSystem {
205- const getCanonicalFileName = createGetCanonicalFileName ( options . useCaseSensitiveFileNames !== false ) ;
221+ ) : VirtualFileSystem {
222+ const caseSensitivity = options . caseSensitivity ?? CaseSensitivity . Sensitive ;
206223 const root = createVDirectory ( "" ) ;
207224 const content = new Map < string , string > ( ) ;
208225
209- for ( const [ filePath , data ] of Object . entries ( files ) ) {
210- const key = getCanonicalFileName ( filePath ) ;
226+ for ( const [ rawFilePath , data ] of Object . entries ( files ) ) {
227+ const filePath = toRootedFilePath ( rawFilePath , undefined ) ;
228+ const key = getKey ( filePath ) ;
211229 if ( content . has ( key ) ) {
212230 throw new Error ( `Duplicate virtual filesystem path: ${ filePath } ` ) ;
213231 }
@@ -225,15 +243,22 @@ export function createVirtualFileSystem(
225243 removeFile,
226244 } ;
227245
246+ function getKey ( path : RootedPath ) : string {
247+ return canonicalize ( path , caseSensitivity ) ;
248+ }
249+
228250 function getSegmentKey ( segment : string ) : string {
229- return getCanonicalFileName ( segment ) ;
251+ return canonicalize ( segment , caseSensitivity ) ;
230252 }
231253
232- function getNodeFromPath ( path : string ) : VNode | undefined {
254+ function getNodeFromPath ( path : RootedPath ) : VNode | undefined {
233255 if ( ! path || path === "/" ) {
234256 return root ;
235257 }
236- const segments = getPathComponents ( path ) . slice ( 1 ) ;
258+ return getNodeFromSegments ( getPathComponents ( path ) . slice ( 1 ) ) ;
259+ }
260+
261+ function getNodeFromSegments ( segments : readonly string [ ] ) : VNode | undefined {
237262 let current : VNode = root ;
238263 for ( const segment of segments ) {
239264 if ( current . type !== "directory" ) {
@@ -263,7 +288,7 @@ export function createVirtualFileSystem(
263288 return current ;
264289 }
265290
266- function addToTree ( path : string ) : void {
291+ function addToTree ( path : RootedFilePath ) : void {
267292 const segments = getPathComponents ( path ) . slice ( 1 ) ;
268293 if ( segments . length === 0 ) {
269294 throw new Error ( `Invalid file path: "${ path } "` ) ;
@@ -275,32 +300,32 @@ export function createVirtualFileSystem(
275300 dirNode . children [ key ] = { type : "file" , name : existing ?. name ?? filename } ;
276301 }
277302
278- function writeFile ( path : string , data : string ) : void {
279- content . set ( getCanonicalFileName ( path ) , data ) ;
303+ function writeFile ( path : RootedFilePath , data : string ) : void {
304+ content . set ( getKey ( path ) , data ) ;
280305 addToTree ( path ) ;
281306 }
282307
283- function removeFile ( path : string ) : void {
284- content . delete ( getCanonicalFileName ( path ) ) ;
308+ function removeFile ( path : RootedFilePath ) : void {
309+ content . delete ( getKey ( path ) ) ;
285310 const segments = getPathComponents ( path ) . slice ( 1 ) ;
286311 if ( segments . length === 0 ) return ;
287312 const filename = segments . pop ( ) ! ;
288- const dirNode = getNodeFromPath ( "/" + segments . join ( "/" ) ) ;
313+ const dirNode = getNodeFromSegments ( segments ) ;
289314 if ( dirNode && dirNode . type === "directory" ) {
290315 delete dirNode . children [ getSegmentKey ( filename ) ] ;
291316 }
292317 }
293318
294- function directoryExists ( directoryName : string ) : boolean {
319+ function directoryExists ( directoryName : RootedDirectoryPath ) : boolean {
295320 const node = getNodeFromPath ( directoryName ) ;
296321 return ! ! node && node . type === "directory" ;
297322 }
298323
299- function fileExists ( fileName : string ) : boolean {
300- return content . has ( getCanonicalFileName ( fileName ) ) ;
324+ function fileExists ( fileName : RootedFilePath ) : boolean {
325+ return content . has ( getKey ( fileName ) ) ;
301326 }
302327
303- function getAccessibleEntries ( directoryName : string ) : FileSystemEntries | undefined {
328+ function getAccessibleEntries ( directoryName : RootedDirectoryPath ) : FileSystemEntries | undefined {
304329 const node = getNodeFromPath ( directoryName ) ;
305330 if ( ! node || node . type !== "directory" ) {
306331 return undefined ;
@@ -318,7 +343,7 @@ export function createVirtualFileSystem(
318343 return { files : fileEntries , directories } ;
319344 }
320345
321- function readFile ( fileName : string ) : string | undefined {
322- return content . get ( getCanonicalFileName ( fileName ) ) ;
346+ function readFile ( fileName : RootedFilePath ) : string | undefined {
347+ return content . get ( getKey ( fileName ) ) ;
323348 }
324349}
0 commit comments