Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@
"argsIgnorePattern": "^_",
"caughtErrors": "none"
}
]
],
"no-restricted-imports": ["warn", "fs", "fs/promises", "node:fs"]
},
"extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended", "prettier"],
"ignorePatterns": ["assets", "out", "dist", "**/*.d.ts"]
Expand Down
12 changes: 7 additions & 5 deletions docs/contributor/writing-tests-for-vscode-swift.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,9 @@ Mocking file system access can be a challenging endeavor that is prone to fail w
The [`mock-fs`](https://github.com/tschaub/mock-fs) module is a well-maintained library that can be used to mitigate these issues by temporarily replacing Node's built-in `fs` module with an in-memory file system. This can be useful for testing logic that uses the `fs` module without actually reaching out to the file system. Just a single function call can be used to configure what the fake file system will contain:

```typescript
import * as chai from "chai";
import { expect } from "chai";
import * as mockFS from "mock-fs";
import * as fs from "fs/promises";
import * as vscode from "vscode";

suite("mock-fs example", () => {
// This teardown step is also important to make sure your tests clean up the
Expand All @@ -137,8 +137,9 @@ suite("mock-fs example", () => {
mockFS({
"/path/to/some/file": "Some really cool file contents",
});
await expect(fs.readFile("/path/to/some/file", "utf-8"))
.to.eventually.equal("Some really cool file contents");
const uri = vscode.Uri.file("/path/to/some/file");
expect(Buffer.from(await vscode.workspace.fs.readFile(uri)).toString("utf-8"))
.to.equal("Some really cool file contents");
});
});
```
Expand All @@ -148,7 +149,8 @@ In order to test failure paths, you can either create an empty file system or us
```typescript
test("file is not readable by the current user", async () => {
mockFS({ "/path/to/file": mockFS.file({ mode: 0o000 }) });
await expect(fs.readFile("/path/to/file", "utf-8")).to.eventually.be.rejected;
const uri = vscode.Uri.file("/path/to/file");
await expect(vscode.workspace.fs.readFile(uri)).to.eventually.be.rejected;
});
```

Expand Down
9 changes: 5 additions & 4 deletions src/PackageWatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
//===----------------------------------------------------------------------===//

import * as path from "path";
import * as fs from "fs/promises";
import * as vscode from "vscode";
import { FolderContext } from "./FolderContext";
import { FolderOperation, WorkspaceContext } from "./WorkspaceContext";
Expand Down Expand Up @@ -151,10 +150,12 @@ export class PackageWatcher {
private async readSwiftVersionFile() {
const versionFile = path.join(this.folderContext.folder.fsPath, ".swift-version");
try {
const contents = await fs.readFile(versionFile);
return Version.fromString(contents.toString().trim());
const contents = Buffer.from(
await vscode.workspace.fs.readFile(vscode.Uri.file(versionFile))
);
return Version.fromString(contents.toString("utf-8").trim());
} catch (error) {
if ((error as NodeJS.ErrnoException).code !== "ENOENT") {
if ((error as vscode.FileSystemError).code !== "FileNotFound") {
this.workspaceContext.logger.error(
`Failed to read .swift-version file at ${versionFile}: ${error}`
);
Expand Down
9 changes: 4 additions & 5 deletions src/SwiftPackage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
//===----------------------------------------------------------------------===//

import * as vscode from "vscode";
import * as fs from "fs/promises";
import * as path from "path";
import { execSwift, getErrorDescription, hashString } from "./utilities/utilities";
import { isPathInsidePath } from "./utilities/filesystem";
Expand Down Expand Up @@ -301,8 +300,8 @@ export class SwiftPackage {
): Promise<PackageResolved | undefined> {
try {
const uri = vscode.Uri.joinPath(folder, "Package.resolved");
const contents = await fs.readFile(uri.fsPath, "utf8");
return new PackageResolved(contents);
const contents = Buffer.from(await vscode.workspace.fs.readFile(uri));
return new PackageResolved(contents.toString("utf-8"));
} catch {
// failed to load resolved file return undefined
return undefined;
Expand Down Expand Up @@ -351,8 +350,8 @@ export class SwiftPackage {
vscode.Uri.file(BuildFlags.buildDirectoryFromWorkspacePath(folder.fsPath, true)),
"workspace-state.json"
);
const contents = await fs.readFile(uri.fsPath, "utf8");
return JSON.parse(contents);
const contents = Buffer.from(await vscode.workspace.fs.readFile(uri));
return JSON.parse(contents.toString("utf8"));
} catch {
// failed to load resolved file return undefined
return undefined;
Expand Down
15 changes: 10 additions & 5 deletions src/TestExplorer/TestRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import * as vscode from "vscode";
import * as path from "path";
import * as stream from "stream";
import * as os from "os";
import * as asyncfs from "fs/promises";
import { FolderContext } from "../FolderContext";
import {
compactMap,
Expand Down Expand Up @@ -776,7 +775,7 @@ export class TestRunner {
);
const swiftTestingArgs = SwiftTestingBuildAguments.build(
fifoPipePath,
attachmentFolder
attachmentFolder?.fsPath
);
const testBuildConfig = await TestingConfigurationFactory.swiftTestingConfig(
this.folderContext,
Expand Down Expand Up @@ -1001,11 +1000,17 @@ export class TestRunner {
}
}

const buffer = await asyncfs.readFile(filename, "utf8");
const buffer = Buffer.from(
await vscode.workspace.fs.readFile(vscode.Uri.file(filename))
);
const xUnitParser = new TestXUnitParser(
this.folderContext.toolchain.hasMultiLineParallelTestOutput
);
const results = await xUnitParser.parse(buffer, runState, this.workspaceContext.logger);
const results = await xUnitParser.parse(
buffer.toString("utf-8"),
runState,
this.workspaceContext.logger
);
if (results) {
this.testRun.appendOutput(
`\r\nExecuted ${results.tests} tests, with ${results.failures} failures and ${results.errors} errors.\r\n`
Expand Down Expand Up @@ -1065,7 +1070,7 @@ export class TestRunner {
);
const swiftTestingArgs = SwiftTestingBuildAguments.build(
fifoPipePath,
attachmentFolder
attachmentFolder?.fsPath
);

const swiftTestBuildConfig = await TestingConfigurationFactory.swiftTestingConfig(
Expand Down
99 changes: 57 additions & 42 deletions src/commands/captureDiagnostics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@

import * as archiver from "archiver";
import * as fs from "fs";
import * as fsPromises from "fs/promises";
import * as path from "path";
import * as vscode from "vscode";
import { tmpdir } from "os";
Expand All @@ -31,7 +30,7 @@ import { DebugAdapter } from "../debugger/debugAdapter";
export async function captureDiagnostics(
ctx: WorkspaceContext,
allowMinimalCapture: boolean = true
): Promise<string | undefined> {
): Promise<vscode.Uri | undefined> {
try {
const captureMode = await captureDiagnosticsMode(ctx, allowMinimalCapture);

Expand All @@ -40,16 +39,18 @@ export async function captureDiagnostics(
return;
}

const diagnosticsDir = path.join(
tmpdir(),
`vscode-diagnostics-${formatDateString(new Date())}`
const diagnosticsDir = vscode.Uri.file(
path.join(tmpdir(), `vscode-diagnostics-${formatDateString(new Date())}`)
);

await fsPromises.mkdir(diagnosticsDir);
await vscode.workspace.fs.createDirectory(diagnosticsDir);

const singleFolderWorkspace = ctx.folders.length === 1;
const zipDir = await createDiagnosticsZipDir();
const zipFilePath = path.join(zipDir, `${path.basename(diagnosticsDir)}.zip`);
const zipFilePath = vscode.Uri.joinPath(
zipDir,
`${path.basename(diagnosticsDir.fsPath)}.zip`
);
const { archive, done: archivingDone } = configureZipArchiver(zipFilePath);

const archivedLldbDapLogFolders = new Set<string>();
Expand All @@ -58,10 +59,13 @@ export async function captureDiagnostics(
);
if (captureMode === "Full" && includeLldbDapLogs) {
for (const defaultLldbDapLogs of [defaultLldbDapLogFolder(ctx), lldbDapLogFolder()]) {
if (!defaultLldbDapLogs || archivedLldbDapLogFolders.has(defaultLldbDapLogs)) {
if (
!defaultLldbDapLogs ||
archivedLldbDapLogFolders.has(defaultLldbDapLogs.fsPath)
) {
continue;
}
archivedLldbDapLogFolders.add(defaultLldbDapLogs);
archivedLldbDapLogFolders.add(defaultLldbDapLogs.fsPath);
await copyLogFolder(ctx, diagnosticsDir, defaultLldbDapLogs);
}
}
Expand All @@ -71,8 +75,8 @@ export async function captureDiagnostics(
const guid = Math.random().toString(36).substring(2, 10);
const outputDir = singleFolderWorkspace
? diagnosticsDir
: path.join(diagnosticsDir, baseName);
await fsPromises.mkdir(outputDir, { recursive: true });
: vscode.Uri.joinPath(diagnosticsDir, baseName);
await vscode.workspace.fs.createDirectory(outputDir);
await writeLogFile(outputDir, `${baseName}-${guid}-settings.txt`, settingsLogs(folder));

if (captureMode === "Full") {
Expand Down Expand Up @@ -102,36 +106,36 @@ export async function captureDiagnostics(
}
// Copy lldb-dap logs
const lldbDapLogs = lldbDapLogFolder(folder.workspaceFolder);
if (lldbDapLogs && !archivedLldbDapLogFolders.has(lldbDapLogs)) {
archivedLldbDapLogFolders.add(lldbDapLogs);
if (lldbDapLogs && !archivedLldbDapLogFolders.has(lldbDapLogs.fsPath)) {
archivedLldbDapLogFolders.add(lldbDapLogs.fsPath);
await copyLogFolder(ctx, outputDir, lldbDapLogs);
}
}
}
// Leave at end in case log above
await copyLogFile(diagnosticsDir, extensionLogFile(ctx));

archive.directory(diagnosticsDir, false);
archive.directory(diagnosticsDir.fsPath, false);
void archive.finalize();
await archivingDone;

// Clean up the diagnostics directory, leaving `zipFilePath` with the zip file.
await fsPromises.rm(diagnosticsDir, { recursive: true, force: true });
await vscode.workspace.fs.delete(diagnosticsDir, { recursive: true, useTrash: false });

ctx.logger.info(`Saved diagnostics to ${zipFilePath}`);
await showCapturedDiagnosticsResults(zipFilePath);
await showCapturedDiagnosticsResults(zipFilePath.fsPath);

return zipFilePath;
} catch (error) {
void vscode.window.showErrorMessage(`Unable to capture diagnostic logs: ${error}`);
}
}

function configureZipArchiver(zipFilePath: string): {
function configureZipArchiver(zipFilePath: vscode.Uri): {
archive: archiver.Archiver;
done: Promise<void>;
} {
const output = fs.createWriteStream(zipFilePath);
const output = fs.createWriteStream(zipFilePath.fsPath);
// Create an archive with max compression
const archive = archiver.create("zip", {
zlib: { level: 9 },
Expand Down Expand Up @@ -232,49 +236,59 @@ async function showCapturedDiagnosticsResults(diagnosticsPath: string) {
}
}

async function writeLogFile(dir: string, name: string, logs: string) {
async function writeLogFile(dir: vscode.Uri, name: string, logs: string) {
if (logs.length === 0) {
return;
}
await fsPromises.writeFile(path.join(dir, name), logs);
await vscode.workspace.fs.writeFile(vscode.Uri.joinPath(dir, name), Buffer.from(logs));
}

async function copyLogFile(dir: string, filePath: string) {
await fsPromises.copyFile(filePath, path.join(dir, path.basename(filePath)));
async function copyLogFile(outputDir: vscode.Uri, file: vscode.Uri) {
await vscode.workspace.fs.copy(
file,
vscode.Uri.joinPath(outputDir, path.basename(file.fsPath))
);
}

async function copyLogFolder(ctx: WorkspaceContext, dir: string, folderPath: string) {
async function copyLogFolder(
ctx: WorkspaceContext,
outputDir: vscode.Uri,
folderToCopy: vscode.Uri
) {
try {
const lldbLogFiles = await fsPromises.readdir(folderPath);
await vscode.workspace.fs.stat(folderToCopy);
const lldbLogFiles = await vscode.workspace.fs.readDirectory(folderToCopy);
for (const log of lldbLogFiles) {
await copyLogFile(dir, path.join(folderPath, log));
await copyLogFile(outputDir, vscode.Uri.joinPath(folderToCopy, log[0]));
}
} catch (error) {
if ((error as NodeJS.ErrnoException).code !== "ENOENT") {
ctx.logger.error(`Failed to read log files from ${folderPath}: ${error}`);
if ((error as vscode.FileSystemError).code !== "FileNotFound") {
ctx.logger.error(`Failed to read log files from ${folderToCopy}: ${error}`);
}
}
}

/**
* Creates a directory for diagnostics zip files, located in the system's temporary directory.
*/
async function createDiagnosticsZipDir(): Promise<string> {
const diagnosticsDir = path.join(tmpdir(), "vscode-diagnostics", formatDateString(new Date()));
await fsPromises.mkdir(diagnosticsDir, { recursive: true });
async function createDiagnosticsZipDir(): Promise<vscode.Uri> {
const diagnosticsDir = vscode.Uri.file(
path.join(tmpdir(), "vscode-diagnostics", formatDateString(new Date()))
);
await vscode.workspace.fs.createDirectory(diagnosticsDir);
return diagnosticsDir;
}

function extensionLogFile(ctx: WorkspaceContext): string {
return ctx.logger.logFilePath;
function extensionLogFile(ctx: WorkspaceContext): vscode.Uri {
return vscode.Uri.file(ctx.logger.logFilePath);
}

function defaultLldbDapLogFolder(ctx: WorkspaceContext): string {
function defaultLldbDapLogFolder(ctx: WorkspaceContext): vscode.Uri {
const rootLogFolder = path.dirname(ctx.loggerFactory.logFolderUri.fsPath);
return path.join(rootLogFolder, Extension.LLDBDAP);
return vscode.Uri.file(path.join(rootLogFolder, Extension.LLDBDAP));
}

function lldbDapLogFolder(workspaceFolder?: vscode.WorkspaceFolder): string | undefined {
function lldbDapLogFolder(workspaceFolder?: vscode.WorkspaceFolder): vscode.Uri | undefined {
const config = vscode.workspace.workspaceFile
? vscode.workspace.getConfiguration("lldb-dap")
: vscode.workspace.getConfiguration("lldb-dap", workspaceFolder);
Expand All @@ -291,7 +305,7 @@ function lldbDapLogFolder(workspaceFolder?: vscode.WorkspaceFolder): string | un
logFolder = path.join(vscode.workspace.workspaceFolders[0].uri.fsPath, logFolder);
}
}
return logFolder;
return vscode.Uri.file(logFolder);
}

function settingsLogs(ctx: FolderContext): string {
Expand All @@ -312,14 +326,15 @@ function diagnosticLogs(): string {
.join("\n");
}

function sourceKitLogFile(folder: FolderContext) {
function sourceKitLogFile(folder: FolderContext): vscode.Uri | undefined {
const languageClient = folder.workspaceContext.languageClientManager.get(folder);
return languageClient.languageClientOutputChannel?.logFilePath;
const logPath = languageClient.languageClientOutputChannel?.logFilePath;
return logPath ? vscode.Uri.file(logPath) : undefined;
}

async function sourcekitDiagnose(ctx: FolderContext, dir: string) {
const sourcekitDiagnosticDir = path.join(dir, "sourcekit-lsp");
await fsPromises.mkdir(sourcekitDiagnosticDir);
async function sourcekitDiagnose(ctx: FolderContext, dir: vscode.Uri) {
const sourcekitDiagnosticDir = vscode.Uri.joinPath(dir, "sourcekit-lsp");
await vscode.workspace.fs.createDirectory(sourcekitDiagnosticDir);

const toolchainSourceKitLSP = ctx.toolchain.getToolchainExecutable("sourcekit-lsp");
const lspConfig = configuration.lsp;
Expand All @@ -341,7 +356,7 @@ async function sourcekitDiagnose(ctx: FolderContext, dir: string) {
[
"diagnose",
"--bundle-output-path",
sourcekitDiagnosticDir,
sourcekitDiagnosticDir.fsPath,
"--toolchain",
ctx.toolchain.toolchainPath,
],
Expand Down
7 changes: 4 additions & 3 deletions src/commands/createNewProject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
//===----------------------------------------------------------------------===//

import * as vscode from "vscode";
import * as fs from "fs/promises";
import configuration from "../configuration";
import { SwiftToolchain, SwiftProjectTemplate } from "../toolchain/toolchain";
import { showToolchainError } from "../ui/ToolchainSelection";
Expand Down Expand Up @@ -77,7 +76,9 @@ export async function createNewProject(toolchain: SwiftToolchain | undefined): P
}

// Prompt the user for the project name
const existingNames = await fs.readdir(selectedFolder[0].fsPath, { encoding: "utf-8" });
const existingNames = (await vscode.workspace.fs.readDirectory(selectedFolder[0])).map(
d => d[0]
);
let initialValue = `swift-${projectType}`;
for (let i = 1; ; i++) {
if (!existingNames.includes(initialValue)) {
Expand Down Expand Up @@ -111,7 +112,7 @@ export async function createNewProject(toolchain: SwiftToolchain | undefined): P

// Create the folder that will store the new project
const projectUri = vscode.Uri.joinPath(selectedFolder[0], projectName);
await fs.mkdir(projectUri.fsPath);
await vscode.workspace.fs.createDirectory(projectUri);

// Use swift package manager to initialize the swift project
await withDelayedProgress(
Expand Down
Loading