Skip to content

Commit 9ef20cc

Browse files
Use the new API for deleting files and directories.
1 parent fc2af89 commit 9ef20cc

File tree

4 files changed

+152
-90
lines changed

4 files changed

+152
-90
lines changed

src/client/common/platform/fileSystem.ts

Lines changed: 32 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,23 @@ function getFileType(stat: FileStat): FileType {
3535
}
3636
}
3737

38+
// This is the parts of the vscode.workspace.fs API that we use here.
39+
interface INewAPI {
40+
//copy(source: vscode.Uri, target: vscode.Uri, options?: {overwrite: boolean}): Thenable<void>;
41+
//createDirectory(uri: vscode.Uri): Thenable<void>;
42+
delete(uri: vscode.Uri, options?: {recursive: boolean; useTrash: boolean}): Thenable<void>;
43+
//readDirectory(uri: vscode.Uri): Thenable<[string, FileType][]>;
44+
//readFile(uri: vscode.Uri): Thenable<Uint8Array>;
45+
//rename(source: vscode.Uri, target: vscode.Uri, options?: {overwrite: boolean}): Thenable<void>;
46+
//stat(uri: vscode.Uri): Thenable<vscode.FileStat>;
47+
//writeFile(uri: vscode.Uri, content: Uint8Array): Thenable<void>;
48+
}
49+
3850
interface IRawFS {
3951
//tslint:disable-next-line:no-any
4052
open(filename: string, flags: number, callback: any): void;
4153
//tslint:disable-next-line:no-any
4254
close(fd: number, callback: any): void;
43-
//tslint:disable-next-line:no-any
44-
unlink(filename: string, callback: any): void;
4555

4656
// non-async
4757
createWriteStream(filePath: string): fs.WriteStream;
@@ -52,13 +62,10 @@ interface IRawFSExtra {
5262
readFile(path: string, encoding: string): Promise<string>;
5363
//tslint:disable-next-line:no-any
5464
writeFile(path: string, data: any, options: any): Promise<void>;
55-
unlink(filename: string): Promise<void>;
5665
stat(filename: string): Promise<fsextra.Stats>;
5766
lstat(filename: string): Promise<fsextra.Stats>;
5867
mkdirp(dirname: string): Promise<void>;
59-
rmdir(dirname: string): Promise<void>;
6068
readdir(dirname: string): Promise<string[]>;
61-
remove(dirname: string): Promise<void>;
6269

6370
// non-async
6471
statSync(filename: string): fsextra.Stats;
@@ -72,10 +79,30 @@ interface IRawFSExtra {
7279

7380
export class RawFileSystem implements IRawFileSystem {
7481
constructor(
82+
private newapi: INewAPI = vscode.workspace.fs,
7583
private readonly nodefs: IRawFS = fs,
7684
private readonly fsExtra: IRawFSExtra = fsextra
7785
) { }
7886

87+
//****************************
88+
// VS Code API
89+
90+
public async rmtree(dirname: string): Promise<void> {
91+
const uri = vscode.Uri.file(dirname);
92+
return this.newapi.delete(uri, {
93+
recursive: true,
94+
useTrash: false
95+
});
96+
}
97+
98+
public async rmfile(filename: string): Promise<void> {
99+
const uri = vscode.Uri.file(filename);
100+
return this.newapi.delete(uri, {
101+
recursive: false,
102+
useTrash: false
103+
});
104+
}
105+
79106
//****************************
80107
// fs-extra
81108

@@ -94,16 +121,6 @@ export class RawFileSystem implements IRawFileSystem {
94121
return this.fsExtra.mkdirp(dirname);
95122
}
96123

97-
public async rmtree(dirname: string): Promise<void> {
98-
return this.fsExtra.stat(dirname)
99-
.then(() => this.fsExtra.remove(dirname));
100-
//.catch((err) => this.fsExtra.rmdir(dirname));
101-
}
102-
103-
public async rmfile(filename: string): Promise<void> {
104-
return this.fsExtra.unlink(filename);
105-
}
106-
107124
public async chmod(filename: string, mode: string | number): Promise<void> {
108125
return this.fsExtra.chmod(filename, mode);
109126
}

src/test/common/platform/filesystem.functional.test.ts

Lines changed: 6 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,20 @@ const assertArrays = require('chai-arrays');
2020
use(require('chai-as-promised'));
2121
use(assertArrays);
2222

23+
// Note: all functional tests that trigger the VS Code "fs" API are
24+
// found in filesystem.test.ts.
25+
2326
// tslint:disable:max-func-body-length chai-vague-errors
2427

25-
const DOES_NOT_EXIST = 'this file does not exist';
28+
export const DOES_NOT_EXIST = 'this file does not exist';
2629

27-
async function ensureDoesNotExist(filename: string) {
30+
export async function ensureDoesNotExist(filename: string) {
2831
await expect(
2932
fsextra.stat(filename)
3033
).to.eventually.be.rejected;
3134
}
3235

33-
class FSFixture {
36+
export class FSFixture {
3437
public tempDir: tmp.SynchrounousResult | undefined;
3538
public sockServer: net.Server | undefined;
3639

@@ -188,41 +191,6 @@ suite('Raw FileSystem', () => {
188191
});
189192
});
190193

191-
suite('rmtree', () => {
192-
test('deletes the directory and everything in it', async () => {
193-
const dirname = await fix.createDirectory('x');
194-
const filename = await fix.createFile('x/y/z/spam.py');
195-
await fsextra.stat(filename); // This should not fail.
196-
197-
await filesystem.rmtree(dirname);
198-
199-
await ensureDoesNotExist(dirname);
200-
});
201-
202-
test('fails if the directory does not exist', async () => {
203-
const promise = filesystem.rmtree(DOES_NOT_EXIST);
204-
205-
await expect(promise).to.eventually.be.rejected;
206-
});
207-
});
208-
209-
suite('rmfile', () => {
210-
test('deletes the file', async () => {
211-
const filename = await fix.createFile('x/y/z/spam.py', '...');
212-
await fsextra.stat(filename); // This should not fail.
213-
214-
await filesystem.rmfile(filename);
215-
216-
await ensureDoesNotExist(filename);
217-
});
218-
219-
test('fails if the file does not exist', async () => {
220-
const promise = filesystem.rmfile(DOES_NOT_EXIST);
221-
222-
await expect(promise).to.eventually.be.rejected;
223-
});
224-
});
225-
226194
suite('chmod', () => {
227195
async function checkMode(filename: string, expected: number) {
228196
const stat = await fsextra.stat(filename);
@@ -918,34 +886,6 @@ suite('FileSystem Utils', () => {
918886
});
919887
});
920888

921-
suite('isDirReadonly', () => {
922-
if (!/^win/.test(process.platform)) {
923-
// On Windows, chmod won't have any effect on the file itself.
924-
test('is readonly', async () => {
925-
const dirname = await fix.createDirectory('x/y/z/spam');
926-
await fsextra.chmod(dirname, 0o444);
927-
928-
const isReadonly = await utils.isDirReadonly(dirname);
929-
930-
expect(isReadonly).to.equal(true);
931-
});
932-
}
933-
934-
test('is not readonly', async () => {
935-
const dirname = await fix.createDirectory('x/y/z/spam');
936-
937-
const isReadonly = await utils.isDirReadonly(dirname);
938-
939-
expect(isReadonly).to.equal(false);
940-
});
941-
942-
test('fails if the file does not exist', async () => {
943-
const promise = utils.isDirReadonly(DOES_NOT_EXIST);
944-
945-
await expect(promise).to.eventually.be.rejected;
946-
});
947-
});
948-
949889
suite('getFileHash', () => {
950890
test('Getting hash for a file should return non-empty string', async () => {
951891
const filename = await fix.createFile('x/y/z/spam.py');
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
import { expect } from 'chai';
5+
import * as fsextra from 'fs-extra';
6+
import {
7+
FileSystemUtils, RawFileSystem
8+
} from '../../../client/common/platform/fileSystem';
9+
import {
10+
IFileSystemUtils, IRawFileSystem
11+
} from '../../../client/common/platform/types';
12+
import {
13+
DOES_NOT_EXIST, ensureDoesNotExist, FSFixture
14+
} from './filesystem.functional.test';
15+
16+
// Note: all functional tests that do not trigger the VS Code "fs" API
17+
// are found in filesystem.functional.test.ts.
18+
19+
// tslint:disable:max-func-body-length chai-vague-errors
20+
21+
suite('Raw FileSystem', () => {
22+
let filesystem: IRawFileSystem;
23+
let fix: FSFixture;
24+
setup(() => {
25+
filesystem = new RawFileSystem();
26+
fix = new FSFixture();
27+
});
28+
teardown(async () => {
29+
await fix.cleanUp();
30+
});
31+
32+
suite('rmtree', () => {
33+
test('deletes the directory and everything in it', async () => {
34+
const dirname = await fix.createDirectory('x');
35+
const filename = await fix.createFile('x/y/z/spam.py');
36+
await fsextra.stat(filename); // This should not fail.
37+
38+
await filesystem.rmtree(dirname);
39+
40+
await ensureDoesNotExist(dirname);
41+
});
42+
43+
test('fails if the directory does not exist', async () => {
44+
const promise = filesystem.rmtree(DOES_NOT_EXIST);
45+
46+
await expect(promise).to.eventually.be.rejected;
47+
});
48+
});
49+
50+
suite('rmfile', () => {
51+
test('deletes the file', async () => {
52+
const filename = await fix.createFile('x/y/z/spam.py', '...');
53+
await fsextra.stat(filename); // This should not fail.
54+
55+
await filesystem.rmfile(filename);
56+
57+
await ensureDoesNotExist(filename);
58+
});
59+
60+
test('fails if the file does not exist', async () => {
61+
const promise = filesystem.rmfile(DOES_NOT_EXIST);
62+
63+
await expect(promise).to.eventually.be.rejected;
64+
});
65+
});
66+
});
67+
68+
suite('FileSystem Utils', () => {
69+
let utils: IFileSystemUtils;
70+
let fix: FSFixture;
71+
setup(() => {
72+
utils = new FileSystemUtils();
73+
fix = new FSFixture();
74+
});
75+
teardown(async () => {
76+
await fix.cleanUp();
77+
});
78+
79+
suite('isDirReadonly', () => {
80+
if (!/^win/.test(process.platform)) {
81+
// On Windows, chmod won't have any effect on the file itself.
82+
test('is readonly', async () => {
83+
const dirname = await fix.createDirectory('x/y/z/spam');
84+
await fsextra.chmod(dirname, 0o444);
85+
86+
const isReadonly = await utils.isDirReadonly(dirname);
87+
88+
expect(isReadonly).to.equal(true);
89+
});
90+
}
91+
92+
test('is not readonly', async () => {
93+
const dirname = await fix.createDirectory('x/y/z/spam');
94+
95+
const isReadonly = await utils.isDirReadonly(dirname);
96+
97+
expect(isReadonly).to.equal(false);
98+
});
99+
100+
test('fails if the file does not exist', async () => {
101+
const promise = utils.isDirReadonly(DOES_NOT_EXIST);
102+
103+
await expect(promise).to.eventually.be.rejected;
104+
});
105+
});
106+
});

src/test/common/platform/filesystem.unit.test.ts

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { expect } from 'chai';
55
import * as fs from 'fs';
66
import * as fsextra from 'fs-extra';
77
import * as TypeMoq from 'typemoq';
8+
import * as vscode from 'vscode';
89
import {
910
FileSystemPath, FileSystemUtils, RawFileSystem
1011
} from '../../../client/common/platform/fileSystem';
@@ -17,6 +18,9 @@ import {
1718
// tslint:disable:max-func-body-length chai-vague-errors
1819

1920
interface IRawFS {
21+
// VS Code
22+
delete(uri: vscode.Uri, options?: {recursive: boolean; useTrash: boolean}): Thenable<void>;
23+
2024
// node "fs"
2125
//tslint:disable-next-line:no-any
2226
open(filename: string, flags: number, callback: any): void;
@@ -28,13 +32,10 @@ interface IRawFS {
2832
readFile(path: string, encoding: string): Promise<string>;
2933
//tslint:disable-next-line:no-any
3034
writeFile(path: string, data: any, options: any): Promise<void>;
31-
unlink(filename: string): Promise<void>;
3235
stat(filename: string): Promise<fsextra.Stats>;
3336
lstat(filename: string): Promise<fsextra.Stats>;
3437
mkdirp(dirname: string): Promise<void>;
35-
rmdir(dirname: string): Promise<void>;
3638
readdir(dirname: string): Promise<string[]>;
37-
remove(dirname: string): Promise<void>;
3839
statSync(filename: string): fsextra.Stats;
3940
readFileSync(path: string, encoding: string): string;
4041
createReadStream(src: string): fsextra.ReadStream;
@@ -51,6 +52,7 @@ suite('Raw FileSystem', () => {
5152
setup(() => {
5253
raw = TypeMoq.Mock.ofType<IRawFS>(undefined, TypeMoq.MockBehavior.Strict);
5354
filesystem = new RawFileSystem(
55+
raw.object,
5456
raw.object,
5557
raw.object
5658
);
@@ -113,10 +115,7 @@ suite('Raw FileSystem', () => {
113115
suite('rmtree', () => {
114116
test('wraps the low-level function', async () => {
115117
const dirname = 'x/y/z/spam';
116-
raw.setup(r => r.stat(dirname))
117-
//tslint:disable-next-line:no-any
118-
.returns(() => Promise.resolve({} as any as FileStat));
119-
raw.setup(r => r.remove(dirname))
118+
raw.setup(r => r.delete(vscode.Uri.file(dirname), { recursive: true, useTrash: false }))
120119
.returns(() => Promise.resolve());
121120

122121
await filesystem.rmtree(dirname);
@@ -126,7 +125,7 @@ suite('Raw FileSystem', () => {
126125

127126
test('fails if the directory does not exist', async () => {
128127
const dirname = 'x/y/z/spam';
129-
raw.setup(r => r.stat(dirname))
128+
raw.setup(r => r.delete(vscode.Uri.file(dirname), { recursive: true, useTrash: false }))
130129
.throws(new Error('file not found'));
131130

132131
const promise = filesystem.rmtree(dirname);
@@ -138,7 +137,7 @@ suite('Raw FileSystem', () => {
138137
suite('rmfile', () => {
139138
test('wraps the low-level function', async () => {
140139
const filename = 'x/y/z/spam.py';
141-
raw.setup(r => r.unlink(filename))
140+
raw.setup(r => r.delete(vscode.Uri.file(filename), { recursive: false, useTrash: false }))
142141
.returns(() => Promise.resolve());
143142

144143
await filesystem.rmfile(filename);

0 commit comments

Comments
 (0)