refactor(compiler-cli): split the `FileSystem` interface up (#40281)

This interface now extends `ReadonlyFileSystem` which in turn
extends `PathManipulation`. This means consumers of these
interfaces can be more specific about what is needed, and so
providers do not need to implement unnecessary methods.

PR Close #40281
This commit is contained in:
Pete Bacon Darwin 2020-12-30 16:59:30 +00:00 committed by Andrew Scott
parent da6c739bb6
commit 80b1ba9f95
3 changed files with 51 additions and 25 deletions

View File

@ -3,7 +3,15 @@
To improve cross platform support, all file access (and path manipulation) To improve cross platform support, all file access (and path manipulation)
is now done through a well known interface (`FileSystem`). is now done through a well known interface (`FileSystem`).
For testing a number of `MockFileSystem` implementations are supplied. Note that `FileSystem` extends `ReadonlyFileSystem`, which itself extends
`PathManipulation`.
If you are using a file-system object you should only ask for the type that supports
all the methods that you require.
For example, if you have a function (`foo()`) that only needs to resolve paths then
it should only require `PathManipulation`: `foo(fs: PathManipulation)`.
This allows the caller to avoid implementing unneeded functionality.
For testing, a number of `MockFileSystem` implementations are supplied.
These provide an in-memory file-system which emulates operating systems These provide an in-memory file-system which emulates operating systems
like OS/X, Unix and Windows. like OS/X, Unix and Windows.
@ -16,6 +24,11 @@ To prevent this happening accidentally the current file system always starts out
as an instance of `InvalidFileSystem`, which will throw an error if any of its as an instance of `InvalidFileSystem`, which will throw an error if any of its
methods are called. methods are called.
Generally it is safer to explicitly pass file-system objects to constructors or
free-standing functions if possible. This avoids confusing bugs where the
global file-system has not been set-up correctly before calling functions that
expect there to be a file-system configured globally.
You can set the current file-system by calling `setFileSystem()`. You can set the current file-system by calling `setFileSystem()`.
During testing you can call the helper function `initMockFileSystem(os)` During testing you can call the helper function `initMockFileSystem(os)`
which takes a string name of the OS to emulate, and will also monkey-patch which takes a string name of the OS to emulate, and will also monkey-patch

View File

@ -9,5 +9,5 @@ export {NgtscCompilerHost} from './src/compiler_host';
export {absoluteFrom, absoluteFromSourceFile, basename, dirname, getFileSystem, isLocalRelativePath, isRoot, isRooted, join, relative, relativeFrom, resolve, setFileSystem, toRelativeImport} from './src/helpers'; export {absoluteFrom, absoluteFromSourceFile, basename, dirname, getFileSystem, isLocalRelativePath, isRoot, isRooted, join, relative, relativeFrom, resolve, setFileSystem, toRelativeImport} from './src/helpers';
export {LogicalFileSystem, LogicalProjectPath} from './src/logical'; export {LogicalFileSystem, LogicalProjectPath} from './src/logical';
export {NodeJSFileSystem} from './src/node_js_file_system'; export {NodeJSFileSystem} from './src/node_js_file_system';
export {AbsoluteFsPath, FileStats, FileSystem, PathSegment, PathString} from './src/types'; export {AbsoluteFsPath, FileStats, FileSystem, PathManipulation, PathSegment, PathString, ReadonlyFileSystem} from './src/types';
export {getSourceFileOrError} from './src/util'; export {getSourceFileOrError} from './src/util';

View File

@ -29,33 +29,14 @@ export type AbsoluteFsPath = BrandedPath<'AbsoluteFsPath'>;
export type PathSegment = BrandedPath<'PathSegment'>; export type PathSegment = BrandedPath<'PathSegment'>;
/** /**
* A basic interface to abstract the underlying file-system. * An abstraction over the path manipulation aspects of a file-system.
*
* This makes it easier to provide mock file-systems in unit tests,
* but also to create clever file-systems that have features such as caching.
*/ */
export interface FileSystem { export interface PathManipulation {
exists(path: AbsoluteFsPath): boolean;
readFile(path: AbsoluteFsPath): string;
readFileBuffer(path: AbsoluteFsPath): Uint8Array;
writeFile(path: AbsoluteFsPath, data: string|Uint8Array, exclusive?: boolean): void;
removeFile(path: AbsoluteFsPath): void;
symlink(target: AbsoluteFsPath, path: AbsoluteFsPath): void;
readdir(path: AbsoluteFsPath): PathSegment[];
lstat(path: AbsoluteFsPath): FileStats;
stat(path: AbsoluteFsPath): FileStats;
pwd(): AbsoluteFsPath;
chdir(path: AbsoluteFsPath): void;
extname(path: AbsoluteFsPath|PathSegment): string; extname(path: AbsoluteFsPath|PathSegment): string;
copyFile(from: AbsoluteFsPath, to: AbsoluteFsPath): void;
moveFile(from: AbsoluteFsPath, to: AbsoluteFsPath): void;
ensureDir(path: AbsoluteFsPath): void;
removeDeep(path: AbsoluteFsPath): void;
isCaseSensitive(): boolean;
isRoot(path: AbsoluteFsPath): boolean; isRoot(path: AbsoluteFsPath): boolean;
isRooted(path: string): boolean; isRooted(path: string): boolean;
resolve(...paths: string[]): AbsoluteFsPath;
dirname<T extends PathString>(file: T): T; dirname<T extends PathString>(file: T): T;
extname(path: AbsoluteFsPath|PathSegment): string;
join<T extends PathString>(basePath: T, ...paths: string[]): T; join<T extends PathString>(basePath: T, ...paths: string[]): T;
/** /**
* Compute the relative path between `from` and `to`. * Compute the relative path between `from` and `to`.
@ -66,9 +47,41 @@ export interface FileSystem {
*/ */
relative<T extends PathString>(from: T, to: T): PathSegment|AbsoluteFsPath; relative<T extends PathString>(from: T, to: T): PathSegment|AbsoluteFsPath;
basename(filePath: string, extension?: string): PathSegment; basename(filePath: string, extension?: string): PathSegment;
normalize<T extends PathString>(path: T): T;
resolve(...paths: string[]): AbsoluteFsPath;
pwd(): AbsoluteFsPath;
chdir(path: AbsoluteFsPath): void;
}
/**
* An abstraction over the read-only aspects of a file-system.
*/
export interface ReadonlyFileSystem extends PathManipulation {
isCaseSensitive(): boolean;
exists(path: AbsoluteFsPath): boolean;
readFile(path: AbsoluteFsPath): string;
readFileBuffer(path: AbsoluteFsPath): Uint8Array;
readdir(path: AbsoluteFsPath): PathSegment[];
lstat(path: AbsoluteFsPath): FileStats;
stat(path: AbsoluteFsPath): FileStats;
realpath(filePath: AbsoluteFsPath): AbsoluteFsPath; realpath(filePath: AbsoluteFsPath): AbsoluteFsPath;
getDefaultLibLocation(): AbsoluteFsPath; getDefaultLibLocation(): AbsoluteFsPath;
normalize<T extends PathString>(path: T): T; }
/**
* A basic interface to abstract the underlying file-system.
*
* This makes it easier to provide mock file-systems in unit tests,
* but also to create clever file-systems that have features such as caching.
*/
export interface FileSystem extends ReadonlyFileSystem {
writeFile(path: AbsoluteFsPath, data: string|Uint8Array, exclusive?: boolean): void;
removeFile(path: AbsoluteFsPath): void;
symlink(target: AbsoluteFsPath, path: AbsoluteFsPath): void;
copyFile(from: AbsoluteFsPath, to: AbsoluteFsPath): void;
moveFile(from: AbsoluteFsPath, to: AbsoluteFsPath): void;
ensureDir(path: AbsoluteFsPath): void;
removeDeep(path: AbsoluteFsPath): void;
} }
export type PathString = string|AbsoluteFsPath|PathSegment; export type PathString = string|AbsoluteFsPath|PathSegment;