Skip to content

Commit

Permalink
feat: #763 - Add Directory#clear().
Browse files Browse the repository at this point in the history
  • Loading branch information
dsherret committed Nov 24, 2019
1 parent b1ff3ef commit df93db3
Show file tree
Hide file tree
Showing 8 changed files with 293 additions and 27 deletions.
22 changes: 22 additions & 0 deletions docs/navigation/directories.md
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,28 @@ directory.deleteImmediatelySync();

This isn't recommended though because it could possibly leave the file system in a halfway state if your code errors before it's done.

### Clearing

Call:

```ts
directory.clear();
```

This will delete the directory's descendants in memory and queue a delete and mkdir operation to the file system.

#### Clearing immediately

If you want to do this operation immediatley to the file system, then use the following:

```ts
await directory.clearImmediately();
// or
directory.clearImmediatelySync();
```

This isn't recommended though because it could possibly leave the file system in a halfway state if your code errors before it's done.

### Forgetting

Forgets the directory from main project object without deleting it:
Expand Down
4 changes: 4 additions & 0 deletions packages/common/lib/ts-morph-common.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -577,6 +577,10 @@ export declare class TransactionalFileSystem {
moveDirectoryImmediately(srcDirPath: StandardizedFilePath, destDirPath: StandardizedFilePath): Promise<void>;
moveDirectoryImmediatelySync(srcDirPath: StandardizedFilePath, destDirPath: StandardizedFilePath): void;
deleteDirectoryImmediately(dirPath: StandardizedFilePath): Promise<void>;
/** Recreates a directory on the underlying file system asynchronously. */
clearDirectoryImmediately(dirPath: StandardizedFilePath): Promise<void>;
/** Recreates a directory on the underlying file system synchronously. */
clearDirectoryImmediatelySync(dirPath: StandardizedFilePath): void;
deleteDirectoryImmediatelySync(dirPath: StandardizedFilePath): void;
private deleteSuppressNotFound;
private deleteSuppressNotFoundSync;
Expand Down
14 changes: 14 additions & 0 deletions packages/common/src/fileSystem/TransactionalFileSystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,20 @@ export class TransactionalFileSystem {
}
}

/** Recreates a directory on the underlying file system asynchronously. */
async clearDirectoryImmediately(dirPath: StandardizedFilePath) {
await this.deleteDirectoryImmediately(dirPath);
this.getOrCreateDirectory(dirPath).setIsDeleted(false);
await this.fileSystem.mkdir(dirPath);
}

/** Recreates a directory on the underlying file system synchronously. */
clearDirectoryImmediatelySync(dirPath: StandardizedFilePath) {
this.deleteDirectoryImmediatelySync(dirPath);
this.getOrCreateDirectory(dirPath).setIsDeleted(false);
this.fileSystem.mkdirSync(dirPath);
}

deleteDirectoryImmediatelySync(dirPath: StandardizedFilePath) {
const dir = this.getOrCreateDirectory(dirPath);

Expand Down
102 changes: 102 additions & 0 deletions packages/common/src/tests/fileSystem/transactionalFileSystemTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -564,6 +564,108 @@ describe(nameof(TransactionalFileSystem), () => {
});
});

describe(nameof<TransactionalFileSystem>(w => w.clearDirectoryImmediately), () => {
function doTests(clearDir: (wrapper: TransactionalFileSystem, dirPath: string, runChecks: (error?: any) => void) => void) {
it("should delete a child file that was queued for delete when immediately clearing a parent dir", () => {
const objs = setup();
const { wrapper } = objs;
const dirPath = "/dir";
const filePath = wrapper.getStandardizedAbsolutePath("/dir/file.ts");
wrapper.writeFileSync(filePath, "");
wrapper.queueFileDelete(filePath);
clearDir(wrapper, dirPath, err => {
expect(err).to.be.undefined;
checkStateForDir(objs, dirPath, [true, true]);
checkState(objs, filePath, [false, false]);
});
});

it("should maintain the list of files to delete when there's an error deleting a directory", () => {
const objs = setup();
const { wrapper, fileSystem } = objs;
const dirPath = "/dir";
const filePath = wrapper.getStandardizedAbsolutePath("/dir/file.ts");
wrapper.writeFileSync(filePath, "");
wrapper.queueFileDelete(filePath);
fileSystem.deleteSync = (path: string) => {
throw new Error();
};
clearDir(wrapper, dirPath, err => {
expect(err).to.be.instanceof(Error);
checkStateForDir(objs, dirPath, [true, true]);
checkState(objs, filePath, [false, true]);
});
});

it("should throw when clearing a directory in a directory with external operations", () => {
const objs = setup();
const { wrapper } = objs;
wrapper.writeFileSync(wrapper.getStandardizedAbsolutePath("/dir/subDir/file.ts"), "text");
wrapper.queueMoveDirectory(wrapper.getStandardizedAbsolutePath("/dir"), wrapper.getStandardizedAbsolutePath("/dir2"));
clearDir(wrapper, "/dir2/subDir", err => {
expect(err).to.be.instanceof(errors.InvalidOperationError);
});
});

it("should throw when clearing a directory in a directory whose parent was once marked for deletion", () => {
const objs = setup();
const { wrapper } = objs;
wrapper.writeFileSync(wrapper.getStandardizedAbsolutePath("/dir/subDir/file.ts"), "text");
wrapper.queueDirectoryDelete(wrapper.getStandardizedAbsolutePath("/dir"));
wrapper.removeFileDelete(wrapper.getStandardizedAbsolutePath("/dir/subDir/file.ts"));
clearDir(wrapper, "/dir/subDir", err => {
expect(err).to.be.instanceof(errors.InvalidOperationError);
});
});

it("should not throw when clearing a directory that contains queued moves that are internal", () => {
const objs = setup();
const { wrapper } = objs;
wrapper.writeFileSync(wrapper.getStandardizedAbsolutePath("/dir/subDir/file.ts"), "");
wrapper.writeFileSync(wrapper.getStandardizedAbsolutePath("/dir/subDir2/file.ts"), "");
wrapper.queueMoveDirectory(wrapper.getStandardizedAbsolutePath("/dir/subDir"), wrapper.getStandardizedAbsolutePath("/dir/newDir"));
wrapper.queueMoveDirectory(wrapper.getStandardizedAbsolutePath("/dir/subDir2"), wrapper.getStandardizedAbsolutePath("/dir/newDir/subSub"));
clearDir(wrapper, "/dir", err => {
expect(err).to.be.undefined;
});
});

it("should not throw when clearing a directory that contains queued deletes that are internal", () => {
const objs = setup();
const { wrapper } = objs;
wrapper.writeFileSync(wrapper.getStandardizedAbsolutePath("/dir/subDir/file.ts"), "");
wrapper.writeFileSync(wrapper.getStandardizedAbsolutePath("/dir/subDir2/file.ts"), "");
wrapper.queueDirectoryDelete(wrapper.getStandardizedAbsolutePath("/dir/subDir"));
wrapper.queueFileDelete(wrapper.getStandardizedAbsolutePath("/dir/subDir2/file.ts"));
clearDir(wrapper, "/dir", err => {
expect(err).to.be.undefined;
});
});
}

describe("async", () => {
doTests(async (wrapper, filePath, runChecks) => {
try {
await wrapper.clearDirectoryImmediately(wrapper.getStandardizedAbsolutePath(filePath));
runChecks();
} catch (err) {
runChecks(err);
}
});
});

describe("sync", () => {
doTests((wrapper, filePath, runChecks) => {
try {
wrapper.clearDirectoryImmediatelySync(wrapper.getStandardizedAbsolutePath(filePath));
runChecks();
} catch (err) {
runChecks(err);
}
});
});
});

describe(nameof<TransactionalFileSystem>(w => w.fileExistsSync), () => {
it("should not exist after queued for delete", () => {
const { wrapper } = setup();
Expand Down
42 changes: 21 additions & 21 deletions packages/ts-morph/lib/ts-morph.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,21 @@ export declare class Directory {
* @param options - Options for moving the directory.
*/
moveImmediatelySync(relativeOrAbsolutePath: string, options?: DirectoryMoveOptions): this;
/**
* Recreates the directory.
* @remarks This will delete all the descendant source files and directories in memory and queue a delete & mkdir to the file system.
*/
clear(): void;
/**
* Asynchronously recreates the directory.
* @remarks This will delete all the descendant source files and directories in memory and push a delete & mkdir to the file system.
*/
clearImmediately(): Promise<void>;
/**
* Synchronously recreates the directory.
* @remarks This will delete all the descendant source files and directories in memory and push a delete & mkdir to the file system.
*/
clearImmediatelySync(): void;
/**
* Queues a deletion of the directory to the file system.
*
Expand Down Expand Up @@ -3438,7 +3453,7 @@ export declare class Node<NodeType extends ts.Node = ts.Node> {
/**
* Gets if the node is a InferKeyword.
*/
static readonly isInferKeyword: (node: Node) => node is Node;
static readonly isInferKeyword: (node: Node) => node is Node<ts.Token<SyntaxKind.InferKeyword>>;
/**
* Gets if the node is a InterfaceDeclaration.
*/
Expand Down Expand Up @@ -3562,7 +3577,7 @@ export declare class Node<NodeType extends ts.Node = ts.Node> {
/**
* Gets if the node is a NeverKeyword.
*/
static readonly isNeverKeyword: (node: Node) => node is Expression;
static readonly isNeverKeyword: (node: Node) => node is Node<ts.Token<SyntaxKind.NeverKeyword>>;
/**
* Gets if the node is a NewExpression.
*/
Expand Down Expand Up @@ -3650,7 +3665,7 @@ export declare class Node<NodeType extends ts.Node = ts.Node> {
/**
* Gets if the node is a SemicolonToken.
*/
static readonly isSemicolonToken: (node: Node) => node is Node;
static readonly isSemicolonToken: (node: Node) => node is Node<ts.Token<SyntaxKind.SemicolonToken>>;
/**
* Gets if the node is a ShorthandPropertyAssignment.
*/
Expand Down Expand Up @@ -7031,7 +7046,6 @@ export interface ImplementedKindToNodeMappings {
[SyntaxKind.ExpressionStatement]: ExpressionStatement;
[SyntaxKind.ExternalModuleReference]: ExternalModuleReference;
[SyntaxKind.QualifiedName]: QualifiedName;
[SyntaxKind.FirstNode]: QualifiedName;
[SyntaxKind.ForInStatement]: ForInStatement;
[SyntaxKind.ForOfStatement]: ForOfStatement;
[SyntaxKind.ForStatement]: ForStatement;
Expand All @@ -7047,7 +7061,6 @@ export interface ImplementedKindToNodeMappings {
[SyntaxKind.ImportEqualsDeclaration]: ImportEqualsDeclaration;
[SyntaxKind.ImportSpecifier]: ImportSpecifier;
[SyntaxKind.ImportType]: ImportTypeNode;
[SyntaxKind.LastTypeNode]: ImportTypeNode;
[SyntaxKind.IndexedAccessType]: IndexedAccessTypeNode;
[SyntaxKind.IndexSignature]: IndexSignatureDeclaration;
[SyntaxKind.InferType]: InferTypeNode;
Expand All @@ -7059,15 +7072,11 @@ export interface ImplementedKindToNodeMappings {
[SyntaxKind.JSDocReturnTag]: JSDocReturnTag;
[SyntaxKind.JSDocSignature]: JSDocSignature;
[SyntaxKind.JSDocTag]: JSDocUnknownTag;
[SyntaxKind.FirstJSDocTagNode]: JSDocUnknownTag;
[SyntaxKind.JSDocTypeExpression]: JSDocTypeExpression;
[SyntaxKind.FirstJSDocNode]: JSDocTypeExpression;
[SyntaxKind.JSDocTypeTag]: JSDocTypeTag;
[SyntaxKind.JSDocTypedefTag]: JSDocTypedefTag;
[SyntaxKind.JSDocParameterTag]: JSDocParameterTag;
[SyntaxKind.JSDocPropertyTag]: JSDocPropertyTag;
[SyntaxKind.LastJSDocNode]: JSDocPropertyTag;
[SyntaxKind.LastJSDocTagNode]: JSDocPropertyTag;
[SyntaxKind.JsxAttribute]: JsxAttribute;
[SyntaxKind.JsxClosingElement]: JsxClosingElement;
[SyntaxKind.JsxClosingFragment]: JsxClosingFragment;
Expand All @@ -7093,10 +7102,7 @@ export interface ImplementedKindToNodeMappings {
[SyntaxKind.NonNullExpression]: NonNullExpression;
[SyntaxKind.NotEmittedStatement]: NotEmittedStatement;
[SyntaxKind.NoSubstitutionTemplateLiteral]: NoSubstitutionTemplateLiteral;
[SyntaxKind.LastLiteralToken]: NoSubstitutionTemplateLiteral;
[SyntaxKind.FirstTemplateToken]: NoSubstitutionTemplateLiteral;
[SyntaxKind.NumericLiteral]: NumericLiteral;
[SyntaxKind.FirstLiteralToken]: NumericLiteral;
[SyntaxKind.ObjectBindingPattern]: ObjectBindingPattern;
[SyntaxKind.ObjectLiteralExpression]: ObjectLiteralExpression;
[SyntaxKind.OmittedExpression]: OmittedExpression;
Expand Down Expand Up @@ -7125,7 +7131,6 @@ export interface ImplementedKindToNodeMappings {
[SyntaxKind.TemplateMiddle]: TemplateMiddle;
[SyntaxKind.TemplateSpan]: TemplateSpan;
[SyntaxKind.TemplateTail]: TemplateTail;
[SyntaxKind.LastTemplateToken]: TemplateTail;
[SyntaxKind.ThisType]: ThisTypeNode;
[SyntaxKind.ThrowStatement]: ThrowStatement;
[SyntaxKind.TryStatement]: TryStatement;
Expand All @@ -7135,22 +7140,21 @@ export interface ImplementedKindToNodeMappings {
[SyntaxKind.TypeLiteral]: TypeLiteralNode;
[SyntaxKind.TypeParameter]: TypeParameterDeclaration;
[SyntaxKind.TypePredicate]: TypePredicateNode;
[SyntaxKind.FirstTypeNode]: TypePredicateNode;
[SyntaxKind.TypeReference]: TypeReferenceNode;
[SyntaxKind.UnionType]: UnionTypeNode;
[SyntaxKind.VariableDeclaration]: VariableDeclaration;
[SyntaxKind.VariableDeclarationList]: VariableDeclarationList;
[SyntaxKind.VariableStatement]: VariableStatement;
[SyntaxKind.JSDocComment]: JSDoc;
[SyntaxKind.SemicolonToken]: Node;
[SyntaxKind.InferKeyword]: Node;
[SyntaxKind.TypeOfExpression]: TypeOfExpression;
[SyntaxKind.WhileStatement]: WhileStatement;
[SyntaxKind.WithStatement]: WithStatement;
[SyntaxKind.YieldExpression]: YieldExpression;
[SyntaxKind.SemicolonToken]: Node<ts.Token<SyntaxKind.SemicolonToken>>;
[SyntaxKind.InferKeyword]: Node<ts.Token<SyntaxKind.InferKeyword>>;
[SyntaxKind.NeverKeyword]: Node<ts.Token<SyntaxKind.NeverKeyword>>;
[SyntaxKind.AnyKeyword]: Expression;
[SyntaxKind.BooleanKeyword]: Expression;
[SyntaxKind.NeverKeyword]: Expression;
[SyntaxKind.NumberKeyword]: Expression;
[SyntaxKind.ObjectKeyword]: Expression;
[SyntaxKind.StringKeyword]: Expression;
Expand Down Expand Up @@ -7195,10 +7199,7 @@ export interface KindToExpressionMappings {
[SyntaxKind.NewExpression]: NewExpression;
[SyntaxKind.NonNullExpression]: NonNullExpression;
[SyntaxKind.NoSubstitutionTemplateLiteral]: NoSubstitutionTemplateLiteral;
[SyntaxKind.LastLiteralToken]: NoSubstitutionTemplateLiteral;
[SyntaxKind.FirstTemplateToken]: NoSubstitutionTemplateLiteral;
[SyntaxKind.NumericLiteral]: NumericLiteral;
[SyntaxKind.FirstLiteralToken]: NumericLiteral;
[SyntaxKind.ObjectLiteralExpression]: ObjectLiteralExpression;
[SyntaxKind.OmittedExpression]: OmittedExpression;
[SyntaxKind.ParenthesizedExpression]: ParenthesizedExpression;
Expand All @@ -7216,7 +7217,6 @@ export interface KindToExpressionMappings {
[SyntaxKind.YieldExpression]: YieldExpression;
[SyntaxKind.AnyKeyword]: Expression;
[SyntaxKind.BooleanKeyword]: Expression;
[SyntaxKind.NeverKeyword]: Expression;
[SyntaxKind.NumberKeyword]: Expression;
[SyntaxKind.ObjectKeyword]: Expression;
[SyntaxKind.StringKeyword]: Expression;
Expand Down
41 changes: 38 additions & 3 deletions packages/ts-morph/src/fileSystem/Directory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -707,20 +707,55 @@ export class Directory {
return this;
}

/**
* Recreates the directory.
* @remarks This will delete all the descendant source files and directories in memory and queue a delete & mkdir to the file system.
*/
clear() {
const path = this.getPath();
this._deleteDescendants();
this._context.fileSystemWrapper.queueDirectoryDelete(path);
this._context.fileSystemWrapper.queueMkdir(path);
}

/**
* Asynchronously recreates the directory.
* @remarks This will delete all the descendant source files and directories in memory and push a delete & mkdir to the file system.
*/
async clearImmediately() {
const path = this.getPath();
this._deleteDescendants();
await this._context.fileSystemWrapper.clearDirectoryImmediately(path);
}

/**
* Synchronously recreates the directory.
* @remarks This will delete all the descendant source files and directories in memory and push a delete & mkdir to the file system.
*/
clearImmediatelySync() {
const path = this.getPath();
this._deleteDescendants();
this._context.fileSystemWrapper.clearDirectoryImmediatelySync(path);
}

/**
* Queues a deletion of the directory to the file system.
*
* The directory will be deleted when calling ast.save(). If you wish to delete the file immediately, then use deleteImmediately().
*/
delete() {
const { fileSystemWrapper } = this._context;
const path = this.getPath();
this._deleteDescendants();
this._context.fileSystemWrapper.queueDirectoryDelete(path);
this.forget();
}

/** @internal */
private _deleteDescendants() {
for (const sourceFile of this.getSourceFiles())
sourceFile.delete();
for (const dir of this.getDirectories())
dir.delete();
fileSystemWrapper.queueDirectoryDelete(path);
this.forget();
}

/**
Expand Down

0 comments on commit df93db3

Please sign in to comment.