@@ -45,6 +45,8 @@ pub struct Runtime {
4545 pub ( super ) linter : Linter ,
4646 resolver : Option < Resolver > ,
4747
48+ pub ( super ) file_system : Box < dyn RuntimeFileSystem + Sync + Send > ,
49+
4850 // The language server uses more up to date source_text provided by `workspace/didChange` request.
4951 // This is required to support `run: "onType"` configuration
5052 #[ cfg( feature = "language_server" ) ]
@@ -140,6 +142,35 @@ impl ModuleToLint {
140142 }
141143}
142144
145+ /// A simple trait for the `Runtime` to load and save file from a filesystem
146+ /// The `Runtime` uses OsFileSystem as a default
147+ /// The Tester and `oxc_language_server` would like to provide the content from memory
148+ pub trait RuntimeFileSystem {
149+ /// reads the content of a file path
150+ ///
151+ /// # Errors
152+ /// When no valid path is provided or the content is not valid UTF-8 Stream
153+ fn read_to_string ( & self , path : & Path ) -> Result < String , std:: io:: Error > ;
154+
155+ /// write a file to the file system
156+ ///
157+ /// # Errors
158+ /// When the program does not have write permission for the file system
159+ fn write_file ( & self , path : & Path , content : String ) -> Result < ( ) , std:: io:: Error > ;
160+ }
161+
162+ struct OsFileSystem ;
163+
164+ impl RuntimeFileSystem for OsFileSystem {
165+ fn read_to_string ( & self , path : & Path ) -> Result < String , std:: io:: Error > {
166+ read_to_string ( path)
167+ }
168+
169+ fn write_file ( & self , path : & Path , content : String ) -> Result < ( ) , std:: io:: Error > {
170+ fs:: write ( path, content)
171+ }
172+ }
173+
143174impl Runtime {
144175 pub ( super ) fn new ( linter : Linter , options : LintServiceOptions ) -> Self {
145176 let resolver = options. cross_module . then ( || {
@@ -150,13 +181,23 @@ impl Runtime {
150181 paths : options. paths . iter ( ) . cloned ( ) . collect ( ) ,
151182 linter,
152183 resolver,
184+ file_system : Box :: new ( OsFileSystem ) ,
153185 #[ cfg( feature = "language_server" ) ]
154186 source_text_cache : FxHashMap :: default ( ) ,
155187 #[ cfg( test) ]
156188 test_source : std:: sync:: RwLock :: new ( None ) ,
157189 }
158190 }
159191
192+ #[ expect( dead_code) ]
193+ pub fn with_file_system (
194+ mut self ,
195+ file_system : Box < dyn RuntimeFileSystem + Sync + Send > ,
196+ ) -> Self {
197+ self . file_system = file_system;
198+ self
199+ }
200+
160201 fn get_resolver ( tsconfig_path : Option < PathBuf > ) -> Resolver {
161202 use oxc_resolver:: { ResolveOptions , TsconfigOptions , TsconfigReferences } ;
162203 let tsconfig = tsconfig_path. and_then ( |path| {
@@ -209,7 +250,7 @@ impl Runtime {
209250 return Some ( Ok ( ( source_type, source_text. clone ( ) ) ) ) ;
210251 }
211252
212- let file_result = read_to_string ( path) . map_err ( |e| {
253+ let file_result = self . file_system . read_to_string ( path) . map_err ( |e| {
213254 Error :: new ( OxcDiagnostic :: error ( format ! (
214255 "Failed to open file {path:?} with error \" {e}\" "
215256 ) ) )
@@ -518,7 +559,7 @@ impl Runtime {
518559 // If the new source text is owned, that means it was modified,
519560 // so we write the new source text to the file.
520561 if let Cow :: Owned ( new_source_text) = new_source_text {
521- fs :: write ( path, new_source_text) . unwrap ( ) ;
562+ me . file_system . write_file ( path, new_source_text) . unwrap ( ) ;
522563 }
523564 } ) ;
524565 } ) ;
0 commit comments