Repository: TypeScript
Issue: Expose a function for constructing a new System based on a custom FS implementation
Labels: Suggestion,Awaiting More Feedback

# Suggestion

Currently TypeScript constructs a `System` and sets it on the `sys` property on the TypeScript module. Many parts of the Compiler APIs accept a custom implementation for `System`, which is useful for example for testing purposes when a virtual file system is being used.

But it is not easily mockable, as the `System` interface is quite extensive. Internally, TypeScript constructs this by invoking an anonymous internal function, like so:

```
ts.sys = (function () {
 // ...
})();
```

<!--
  Please fill in each section completely. Thank you!
-->

## 🔍 Search Terms

<!--
  💡 Did you know? TypeScript has over 2,000 open suggestions!
  🔎 Please search thoroughly before logging new feature requests as most common ideas already have a proposal in progress.
  The "Common Feature Requests" section of the FAQ lists many popular requests: https://github.com/Microsoft/TypeScript/wiki/FAQ#common-feature-requests

  Replace the text below:
-->

createSystem, Mock System, Virtual Filesystem

## ✅ Viability Checklist

<!--
   Suggestions that don't meet all these criteria are very, very unlikely to be accepted.
   We always recommend reviewing the TypeScript design goals before investing time writing
   a proposal for ideas outside the scope of the project.
-->
My suggestion meets these guidelines:

* [x] This wouldn't be a breaking change in existing TypeScript/JavaScript code
* [x] This wouldn't change the runtime behavior of existing JavaScript code
* [x] This could be implemented without emitting different JS based on the types of the expressions
* [x] This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
* [x] This feature would agree with the rest of [TypeScript's Design Goals](https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals).


## ⭐ Suggestion

<!-- A summary of what you'd like to see added or changed -->

I would like to propose exposing a function, `createSystem`, that takes an Partial of the following structure:

```ts
interface CreateSystemOptions {
    // An FS implementation
    fs: typeof import("fs");

    // These are the only two properties you use from the os module
    os: Pick<typeof import("os"), "platform"|"EOL">;
    
    // You default to using process.cwd() here, but the current working directory could in theory be something else
    cwd: string;
}

export function createSystem (options: Partial<CreateSystemOptions>): System {
    // ...
}
```
This function should also be used internally by TypeScript, and constructs a new `System`. This would allow for easier mocking.

## 📃 Motivating Example

<!--
  If you were announcing this feature in a blog post, what's a short explanation that shows
  a developer why this feature improves the language?
-->

Imagine building a `Program` programmatically that relies on an incremental `CompilerHost`. With the `createSystem` function, you would very easily be able to swap out the `fs` implementation and pass it a virtual file system based on the test(s) you're running:

```ts
import * as ts from "typescript";
import virtualFs from "memfs";
import realFs from "fs";

function createCompilerHost (fs: typeof import("fs") = realFs): ts.CompilerHost {
    return ts.createIncrementalCompilerHost({}, ts.createSystem({fs}));
}

const program = ts.createProgram({
    host: createCompilerHost(virtualFs),
    rootNames: ["/foo/bar/baz.ts"],
    options: {}
})
```

## 💻 Use Cases

<!--
  What do you want to use this for?
  What shortcomings exist with current approaches?
  What workarounds are you using in the meantime?
-->

Use cases:
- Testing
- Tooling around TypeScript's Compiler APIs
- Virtual file systems, such as is the case with some bundlers running in watch mode

Workarounds:
- Manually reimplementing most of the `System` interface, and manually maintaining it across TypeScript versions.