Skip to content

Commit

Permalink
feat: #177 - Ability to use virtual file system.
Browse files Browse the repository at this point in the history
  • Loading branch information
dsherret committed Dec 25, 2017
1 parent c73dd05 commit ae27f5b
Show file tree
Hide file tree
Showing 22 changed files with 215 additions and 164 deletions.
31 changes: 20 additions & 11 deletions src/TsSimpleAst.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import * as compiler from "./compiler";
import * as factories from "./factories";
import {SourceFileStructure} from "./structures";
import {getCompilerOptionsFromTsConfig, FileUtils, ArrayUtils} from "./utils";
import {DefaultFileSystemHost, FileSystemHost, Directory} from "./fileSystem";
import {DefaultFileSystemHost, VirtualFileSystemHost, FileSystemHost, Directory} from "./fileSystem";
import {ManipulationSettings, ManipulationSettingsContainer} from "./ManipulationSettings";
import {GlobalContainer} from "./GlobalContainer";

Expand All @@ -16,6 +16,8 @@ export interface Options {
tsConfigFilePath?: string;
/** Manipulation settings */
manipulationSettings?: Partial<ManipulationSettings>;
/** Whether to use a virtual file system. */
useVirtualFileSystem?: boolean;
}

/**
Expand All @@ -30,7 +32,14 @@ export class TsSimpleAst {
* @param options - Optional options.
* @param fileSystem - Optional file system host. Useful for mocking access to the file system.
*/
constructor(options: Options = {}, private readonly fileSystem: FileSystemHost = new DefaultFileSystemHost()) {
constructor(options: Options = {}, fileSystem?: FileSystemHost) {
if (fileSystem != null && options.useVirtualFileSystem)
throw new errors.InvalidOperationError("Cannot provide a file system when specifying to use a virtual file system.");
else if (options.useVirtualFileSystem)
fileSystem = new VirtualFileSystemHost();
else if (fileSystem == null)
fileSystem = new DefaultFileSystemHost();

this.global = new GlobalContainer(fileSystem, getCompilerOptionsFromOptions(options, fileSystem), { createLanguageService: true });
if (options.manipulationSettings != null)
this.global.manipulationSettings.set(options.manipulationSettings);
Expand All @@ -48,8 +57,8 @@ export class TsSimpleAst {
* @param dirPath - Path to add the directory at.
*/
addExistingDirectory(dirPath: string): Directory {
dirPath = FileUtils.getStandardizedAbsolutePath(dirPath);
if (!this.fileSystem.directoryExistsSync(dirPath))
dirPath = FileUtils.getStandardizedAbsolutePath(this.global.fileSystem, dirPath);
if (!this.global.fileSystem.directoryExistsSync(dirPath))
throw new errors.DirectoryNotFoundError(dirPath);
return this.global.compilerFactory.addDirectoryIfNotExists(dirPath);
}
Expand All @@ -61,7 +70,7 @@ export class TsSimpleAst {
* @throws - InvalidOperationError if a directory already exists at the provided file path.
*/
createDirectory(dirPath: string): Directory {
dirPath = FileUtils.getStandardizedAbsolutePath(dirPath);
dirPath = FileUtils.getStandardizedAbsolutePath(this.global.fileSystem, dirPath);
return this.global.compilerFactory.createDirectory(dirPath);
}

Expand All @@ -71,15 +80,15 @@ export class TsSimpleAst {
*/
getDirectoryOrThrow(dirPath: string): Directory {
return errors.throwIfNullOrUndefined(this.getDirectory(dirPath),
() => `Could not find a directory at the specified path: ${FileUtils.getStandardizedAbsolutePath(dirPath)}`);
() => `Could not find a directory at the specified path: ${FileUtils.getStandardizedAbsolutePath(this.global.fileSystem, dirPath)}`);
}

/**
* Gets a directory by the specified path or returns undefined if it doesn't exist.
* @param dirPath - Directory path.
*/
getDirectory(dirPath: string): Directory | undefined {
dirPath = FileUtils.getStandardizedAbsolutePath(dirPath);
dirPath = FileUtils.getStandardizedAbsolutePath(this.global.fileSystem, dirPath);
return this.global.compilerFactory.getDirectory(dirPath);
}

Expand All @@ -105,7 +114,7 @@ export class TsSimpleAst {
addExistingSourceFiles(...fileGlobs: string[]): compiler.SourceFile[] {
const sourceFiles: compiler.SourceFile[] = [];

for (const filePath of this.fileSystem.glob(fileGlobs)) {
for (const filePath of this.global.fileSystem.glob(fileGlobs)) {
// ignore any FileNotFoundErrors
try {
sourceFiles.push(this.addExistingSourceFile(filePath));
Expand All @@ -126,8 +135,8 @@ export class TsSimpleAst {
* @param filePath - File path to get the file from.
*/
addExistingSourceFile(filePath: string): compiler.SourceFile {
const absoluteFilePath = FileUtils.getStandardizedAbsolutePath(filePath);
if (!this.fileSystem.fileExistsSync(absoluteFilePath))
const absoluteFilePath = FileUtils.getStandardizedAbsolutePath(this.global.fileSystem, filePath);
if (!this.global.fileSystem.fileExistsSync(absoluteFilePath))
throw new errors.FileNotFoundError(absoluteFilePath);
return this.global.compilerFactory.getSourceFileFromFilePath(absoluteFilePath)!;
}
Expand Down Expand Up @@ -311,7 +320,7 @@ export class TsSimpleAst {
* Gets the file system.
*/
getFileSystem(): FileSystemHost {
return this.fileSystem;
return this.global.fileSystem;
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/base/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ export * from "./BodiedNode";
export * from "./BodyableNode";
export * from "./ChildOrderableNode";
export * from "./DecoratableNode";
export * from "./JSDocableNode";
export * from "./ExportableNode";
export * from "./ExtendsClauseableNode";
export * from "./GeneratorableNode";
export * from "./HeritageClauseableNode";
export * from "./ImplementsClauseableNode";
export * from "./JSDocableNode";
export * from "./LiteralLikeNode";
export * from "./ModifierableNode";
export * from "./ParameteredNode";
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/file/SourceFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ export class SourceFile extends SourceFileBase<ts.SourceFile> {
* @param filePath - A new file path. Can be relative to the original file or an absolute path.
*/
copy(filePath: string): SourceFile {
const absoluteFilePath = FileUtils.getStandardizedAbsolutePath(filePath, this.getDirectoryPath());
const absoluteFilePath = FileUtils.getStandardizedAbsolutePath(this.global.fileSystem, filePath, this.getDirectoryPath());
return this.global.compilerFactory.createSourceFileFromText(absoluteFilePath, this.getFullText());
}

Expand Down
14 changes: 11 additions & 3 deletions src/compiler/tools/LanguageService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import {GlobalContainer} from "./../../GlobalContainer";
import {replaceSourceFileTextForRename} from "./../../manipulation";
import * as errors from "./../../errors";
import {DefaultFileSystemHost} from "./../../fileSystem";
import {KeyValueCache, FileUtils, StringUtils, fillDefaultFormatCodeSettings} from "./../../utils";
import {SourceFile} from "./../file";
import {Node} from "./../common";
Expand Down Expand Up @@ -43,7 +44,12 @@ export class LanguageService {
return ts.ScriptSnapshot.fromString(this.global.compilerFactory.getSourceFileFromFilePath(fileName)!.getFullText());
},
getCurrentDirectory: () => global.fileSystem.getCurrentDirectory(),
getDefaultLibFileName: options => ts.getDefaultLibFilePath(global.compilerOptions),
getDefaultLibFileName: options => {
if (this.global.fileSystem instanceof DefaultFileSystemHost)
return ts.getDefaultLibFilePath(global.compilerOptions);
else
return FileUtils.pathJoin(global.fileSystem.getCurrentDirectory(), "node_modules/typescript/lib/" + ts.getDefaultLibFileName(global.compilerOptions));
},
useCaseSensitiveFileNames: () => true,
readFile: (path, encoding) => {
if (this.global.compilerFactory.containsSourceFileAtPath(path))
Expand Down Expand Up @@ -72,7 +78,7 @@ export class LanguageService {
},
fileExists: (fileName: string) => languageServiceHost.fileExists!(fileName),
readFile: (fileName: string) => languageServiceHost.readFile!(fileName),
getCanonicalFileName: (fileName: string) => FileUtils.getStandardizedAbsolutePath(fileName),
getCanonicalFileName: (fileName: string) => FileUtils.getStandardizedAbsolutePath(this.global.fileSystem, fileName),
useCaseSensitiveFileNames: () => languageServiceHost.useCaseSensitiveFileNames!(),
getNewLine: () => languageServiceHost.getNewLine!(),
getEnvironmentVariable: (name: string) => process.env[name]
Expand Down Expand Up @@ -256,7 +262,9 @@ export class LanguageService {
/** @internal */
getEmitOutput(filePathOrSourceFile: SourceFile | string, emitOnlyDtsFiles?: boolean): EmitOutput;
getEmitOutput(filePathOrSourceFile: SourceFile | string, emitOnlyDtsFiles?: boolean): EmitOutput {
const filePath = typeof filePathOrSourceFile === "string" ? FileUtils.getStandardizedAbsolutePath(filePathOrSourceFile) : filePathOrSourceFile.getFilePath();
const filePath = typeof filePathOrSourceFile === "string"
? FileUtils.getStandardizedAbsolutePath(this.global.fileSystem, filePathOrSourceFile)
: filePathOrSourceFile.getFilePath();
if (!this.global.compilerFactory.containsSourceFileAtPath(filePath))
throw new errors.FileNotFoundError(filePath);
return new EmitOutput(this.global, filePath, this.compilerObject.getEmitOutput(filePath, emitOnlyDtsFiles));
Expand Down
1 change: 1 addition & 0 deletions src/compiler/tools/Program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export class Program {
this._getOrCreateCompilerObject = () => {
if (this._createdCompilerObject == null)
this._createdCompilerObject = ts.createProgram(rootNames, compilerOptions, host);

// this needs to be on a separate line in case the program was reset between the line above and here
return this._createdCompilerObject || this._getOrCreateCompilerObject();
};
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/tools/results/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ export * from "./DefinitionInfo";
export * from "./Diagnostic";
export * from "./DiagnosticMessageChain";
export * from "./DocumentSpan";
export * from "./EmitResult";
export * from "./EmitOutput";
export * from "./EmitResult";
export * from "./ImplementationLocation";
export * from "./OutputFile";
export * from "./ReferencedSymbol";
Expand Down
8 changes: 4 additions & 4 deletions src/factories/CompilerFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ export class CompilerFactory {
* @param sourceText - Text to create the source file with.
*/
createSourceFileFromText(filePath: string, sourceText: string) {
filePath = FileUtils.getStandardizedAbsolutePath(filePath);
filePath = FileUtils.getStandardizedAbsolutePath(this.global.fileSystem, filePath);
if (this.containsSourceFileAtPath(filePath) || this.global.fileSystem.fileExistsSync(filePath))
throw new errors.InvalidOperationError(`A source file already exists at the provided file path: ${filePath}`);
const compilerSourceFile = ts.createSourceFile(filePath, sourceText, this.global.manipulationSettings.getScriptTarget(), true);
Expand All @@ -105,7 +105,7 @@ export class CompilerFactory {
* @param filePath - File path to get the file from.
*/
getSourceFileFromFilePath(filePath: string): compiler.SourceFile | undefined {
filePath = FileUtils.getStandardizedAbsolutePath(filePath);
filePath = FileUtils.getStandardizedAbsolutePath(this.global.fileSystem, filePath);
let sourceFile = this.sourceFileCacheByFilePath.get(filePath);
if (sourceFile == null) {
if (this.global.fileSystem.fileExistsSync(filePath)) {
Expand All @@ -130,7 +130,7 @@ export class CompilerFactory {
* @param filePath - File path to check.
*/
containsSourceFileAtPath(filePath: string) {
const absoluteFilePath = FileUtils.getStandardizedAbsolutePath(filePath);
const absoluteFilePath = FileUtils.getStandardizedAbsolutePath(this.global.fileSystem, filePath);
return this.sourceFileCacheByFilePath.has(absoluteFilePath);
}

Expand All @@ -139,7 +139,7 @@ export class CompilerFactory {
* @param dirPath - Directory path to check.
*/
containsDirectoryAtPath(dirPath: string) {
const normalizedDirPath = FileUtils.getStandardizedAbsolutePath(dirPath);
const normalizedDirPath = FileUtils.getStandardizedAbsolutePath(this.global.fileSystem, dirPath);
return this.directoryCache.has(normalizedDirPath);
}

Expand Down
5 changes: 3 additions & 2 deletions src/fileSystem/DefaultFileSystemHost.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as fs from "fs";
import * as nodePath from "path";
import * as globby from "globby";
import {FileSystemHost} from "./FileSystemHost";
import {FileUtils} from "./../utils";
Expand Down Expand Up @@ -104,8 +105,8 @@ export class DefaultFileSystemHost implements FileSystemHost {
}
}

getCurrentDirectory() {
return FileUtils.getCurrentDirectory();
getCurrentDirectory(): string {
return FileUtils.standardizeSlashes(nodePath.resolve());
}

glob(patterns: string[]) {
Expand Down
16 changes: 8 additions & 8 deletions src/fileSystem/Directory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ export class Directory {
getDirectoryOrThrow(pathOrCondition: string | ((directory: Directory) => boolean)) {
return errors.throwIfNullOrUndefined(this.getDirectory(pathOrCondition), () => {
if (typeof pathOrCondition === "string")
return `Could not find a directory at path '${FileUtils.getStandardizedAbsolutePath(pathOrCondition, this.getPath())}'.`;
return `Could not find a directory at path '${FileUtils.getStandardizedAbsolutePath(this.global.fileSystem, pathOrCondition, this.getPath())}'.`;
return "Could not find child directory that matched condition.";
});
}
Expand All @@ -110,7 +110,7 @@ export class Directory {
getDirectory(pathOrCondition: string | ((directory: Directory) => boolean)): Directory | undefined;
getDirectory(pathOrCondition: string | ((directory: Directory) => boolean)) {
if (typeof pathOrCondition === "string") {
const path = FileUtils.getStandardizedAbsolutePath(pathOrCondition, this.getPath());
const path = FileUtils.getStandardizedAbsolutePath(this.global.fileSystem, pathOrCondition, this.getPath());
return this.global.compilerFactory.getDirectory(path);
}

Expand Down Expand Up @@ -200,7 +200,7 @@ export class Directory {
* @param path - Directory name or path to the directory that should be added.
*/
addExistingDirectory(path: string) {
const dirPath = FileUtils.getStandardizedAbsolutePath(path, this.getPath());
const dirPath = FileUtils.getStandardizedAbsolutePath(this.global.fileSystem, path, this.getPath());
if (!this.global.fileSystem.directoryExistsSync(dirPath))
throw new errors.DirectoryNotFoundError(dirPath);
return this.global.compilerFactory.addDirectoryIfNotExists(dirPath);
Expand All @@ -211,7 +211,7 @@ export class Directory {
* @param path - Directory name or path to the directory that should be created.
*/
createDirectory(path: string) {
const dirPath = FileUtils.getStandardizedAbsolutePath(path, this.getPath());
const dirPath = FileUtils.getStandardizedAbsolutePath(this.global.fileSystem, path, this.getPath());
return this.global.compilerFactory.createDirectory(dirPath);
}

Expand Down Expand Up @@ -242,7 +242,7 @@ export class Directory {
*/
createSourceFile(relativeFilePath: string, structure: SourceFileStructure): SourceFile;
createSourceFile(relativeFilePath: string, structureOrText?: string | SourceFileStructure) {
const filePath = FileUtils.getStandardizedAbsolutePath(FileUtils.pathJoin(this.getPath(), relativeFilePath));
const filePath = FileUtils.getStandardizedAbsolutePath(this.global.fileSystem, FileUtils.pathJoin(this.getPath(), relativeFilePath));
return this.global.compilerFactory.createSourceFile(filePath, structureOrText);
}

Expand All @@ -253,7 +253,7 @@ export class Directory {
* @param relativeFilePath - Relative file path to add.
*/
addExistingSourceFile(relativeFilePath: string) {
const filePath = FileUtils.getStandardizedAbsolutePath(FileUtils.pathJoin(this.getPath(), relativeFilePath));
const filePath = FileUtils.getStandardizedAbsolutePath(this.global.fileSystem, FileUtils.pathJoin(this.getPath(), relativeFilePath));
if (!this.global.fileSystem.fileExistsSync(filePath))
throw new errors.FileNotFoundError(filePath);
return this.global.compilerFactory.getSourceFileFromFilePath(filePath)!;
Expand Down Expand Up @@ -309,7 +309,7 @@ export class Directory {
const isJsFile = options.outDir == null ? undefined : /\.js$/i;
const isMapFile = options.outDir == null ? undefined : /\.js\.map$/i;
const isDtsFile = options.declarationDir == null && options.outDir == null ? undefined : /\.d\.ts$/i;
const getStandardizedPath = (path: string | undefined) => path == null ? undefined : FileUtils.getStandardizedAbsolutePath(path, this.getPath());
const getStandardizedPath = (path: string | undefined) => path == null ? undefined : FileUtils.getStandardizedAbsolutePath(this.global.fileSystem, path, this.getPath());
const getSubDirPath = (path: string | undefined, dir: Directory) => path == null ? undefined : FileUtils.pathJoin(path, dir.getBaseName());
const hasDeclarationDir = this.global.compilerOptions.declarationDir != null || options.declarationDir != null;

Expand Down Expand Up @@ -347,7 +347,7 @@ export class Directory {
* @returns The directory the copy was made to.
*/
copy(relativeOrAbsolutePath: string) {
const newPath = FileUtils.getStandardizedAbsolutePath(relativeOrAbsolutePath, this.getPath());
const newPath = FileUtils.getStandardizedAbsolutePath(this.global.fileSystem, relativeOrAbsolutePath, this.getPath());
const directory = this.global.compilerFactory.addDirectoryIfNotExists(newPath);

for (const sourceFile of this.getSourceFiles())
Expand Down

0 comments on commit ae27f5b

Please sign in to comment.