diff --git a/.eslintrc.json b/.eslintrc.json index 76f5c51e1..b61af7ff3 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -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"] diff --git a/.vscode-test.js b/.vscode-test.js index f2563a2e0..f207985f7 100644 --- a/.vscode-test.js +++ b/.vscode-test.js @@ -55,7 +55,7 @@ if (vsixPath) { if (!path.isAbsolute(vsixPath)) { vsixPath = path.join(__dirname, vsixPath); } - console.log("Installing VSIX " + vsixPath); + !isDebugRun && console.log("Installing VSIX " + vsixPath); installExtensions.push(vsixPath); // Determine version to use @@ -63,16 +63,17 @@ if (vsixPath) { if (match) { versionStr = match[1]; } - console.log("Running tests against extension version " + versionStr); + !isDebugRun && console.log("Running tests against extension version " + versionStr); extensionDevelopmentPath = `${__dirname}/.vscode-test/extensions/${publisher}.${name}-${versionStr}`; - console.log("Running tests against extension development path " + extensionDevelopmentPath); + !isDebugRun && + console.log("Running tests against extension development path " + extensionDevelopmentPath); } else { extensionDependencies.push("vadimcn.vscode-lldb", "llvm-vs-code-extensions.lldb-dap"); } const vscodeVersion = process.env["VSCODE_VERSION"] ?? "stable"; -console.log("Running tests against VS Code version " + vscodeVersion); +!isDebugRun && console.log("Running tests against VS Code version " + vscodeVersion); const installConfigs = []; for (const ext of installExtensions) { @@ -91,7 +92,8 @@ const env = { ...process.env, RUNNING_UNDER_VSCODE_TEST_CLI: "1", }; -console.log("Running tests against environment:\n" + JSON.stringify(env, undefined, 2)); +!isDebugRun && + console.log("Running tests against environment:\n" + JSON.stringify(env, undefined, 2)); module.exports = defineConfig({ tests: [ diff --git a/docs/contributor/writing-tests-for-vscode-swift.md b/docs/contributor/writing-tests-for-vscode-swift.md index b9012e4f3..b1b9a340e 100644 --- a/docs/contributor/writing-tests-for-vscode-swift.md +++ b/docs/contributor/writing-tests-for-vscode-swift.md @@ -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 @@ -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"); }); }); ``` @@ -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; }); ``` diff --git a/src/PackageWatcher.ts b/src/PackageWatcher.ts index 5bf7a366a..ab3079aff 100644 --- a/src/PackageWatcher.ts +++ b/src/PackageWatcher.ts @@ -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"; @@ -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}` ); diff --git a/src/SwiftPackage.ts b/src/SwiftPackage.ts index 1824ffe1e..c927e4ef1 100644 --- a/src/SwiftPackage.ts +++ b/src/SwiftPackage.ts @@ -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"; @@ -301,8 +300,8 @@ export class SwiftPackage { ): Promise { 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; @@ -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; diff --git a/src/TestExplorer/TestRunner.ts b/src/TestExplorer/TestRunner.ts index b3b08ee2a..bb9703ed7 100644 --- a/src/TestExplorer/TestRunner.ts +++ b/src/TestExplorer/TestRunner.ts @@ -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, execFile, getErrorDescription } from "../utilities/utilities"; import { createSwiftTask } from "../tasks/SwiftTaskProvider"; @@ -695,7 +694,7 @@ export class TestRunner { ); const swiftTestingArgs = SwiftTestingBuildAguments.build( fifoPipePath, - attachmentFolder + attachmentFolder?.fsPath ); const testBuildConfig = await TestingConfigurationFactory.swiftTestingConfig( this.folderContext, @@ -919,11 +918,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` @@ -978,7 +983,7 @@ export class TestRunner { ); const swiftTestingArgs = SwiftTestingBuildAguments.build( fifoPipePath, - attachmentFolder + attachmentFolder?.fsPath ); const swiftTestBuildConfig = await TestingConfigurationFactory.swiftTestingConfig( diff --git a/src/commands/captureDiagnostics.ts b/src/commands/captureDiagnostics.ts index 4489c557c..465efcd97 100644 --- a/src/commands/captureDiagnostics.ts +++ b/src/commands/captureDiagnostics.ts @@ -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"; @@ -31,7 +30,7 @@ import { DebugAdapter } from "../debugger/debugAdapter"; export async function captureDiagnostics( ctx: WorkspaceContext, allowMinimalCapture: boolean = true -): Promise { +): Promise { try { const captureMode = await captureDiagnosticsMode(ctx, allowMinimalCapture); @@ -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(); @@ -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); } } @@ -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") { @@ -102,8 +106,8 @@ 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); } } @@ -111,15 +115,15 @@ export async function captureDiagnostics( // 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) { @@ -127,11 +131,11 @@ export async function captureDiagnostics( } } -function configureZipArchiver(zipFilePath: string): { +function configureZipArchiver(zipFilePath: vscode.Uri): { archive: archiver.Archiver; done: Promise; } { - 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 }, @@ -232,26 +236,34 @@ 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}`); } } } @@ -259,22 +271,24 @@ async function copyLogFolder(ctx: WorkspaceContext, dir: string, folderPath: str /** * Creates a directory for diagnostics zip files, located in the system's temporary directory. */ -async function createDiagnosticsZipDir(): Promise { - const diagnosticsDir = path.join(tmpdir(), "vscode-diagnostics", formatDateString(new Date())); - await fsPromises.mkdir(diagnosticsDir, { recursive: true }); +async function createDiagnosticsZipDir(): Promise { + 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); @@ -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 { @@ -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; @@ -341,7 +356,7 @@ async function sourcekitDiagnose(ctx: FolderContext, dir: string) { [ "diagnose", "--bundle-output-path", - sourcekitDiagnosticDir, + sourcekitDiagnosticDir.fsPath, "--toolchain", ctx.toolchain.toolchainPath, ], diff --git a/src/commands/createNewProject.ts b/src/commands/createNewProject.ts index 016bd346d..9109ae270 100644 --- a/src/commands/createNewProject.ts +++ b/src/commands/createNewProject.ts @@ -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"; @@ -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)) { @@ -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( diff --git a/src/commands/dependencies/unedit.ts b/src/commands/dependencies/unedit.ts index f254298b6..c12b50057 100644 --- a/src/commands/dependencies/unedit.ts +++ b/src/commands/dependencies/unedit.ts @@ -13,7 +13,6 @@ //===----------------------------------------------------------------------===// import * as vscode from "vscode"; -import * as fs from "fs/promises"; import { FolderOperation, WorkspaceContext } from "../../WorkspaceContext"; import { SwiftExecOperation } from "../../tasks/TaskQueue"; import { FolderContext } from "../../FolderContext"; @@ -72,7 +71,7 @@ async function uneditFolderDependency( if (folderIndex) { try { // check folder exists. if error thrown remove folder - await fs.stat(vscode.workspace.workspaceFolders![folderIndex].uri.fsPath); + await vscode.workspace.fs.stat(vscode.workspace.workspaceFolders![folderIndex].uri); } catch { vscode.workspace.updateWorkspaceFolders(folderIndex, 1); } diff --git a/src/commands/generateSourcekitConfiguration.ts b/src/commands/generateSourcekitConfiguration.ts index ed44b679c..9be91e8d4 100644 --- a/src/commands/generateSourcekitConfiguration.ts +++ b/src/commands/generateSourcekitConfiguration.ts @@ -61,7 +61,7 @@ async function createSourcekitConfiguration( await vscode.workspace.fs.stat(sourcekitConfigFile); return true; } catch (error) { - if ((error as NodeJS.ErrnoException).code !== "ENOENT") { + if ((error as vscode.FileSystemError).code !== "FileNotFound") { workspaceContext.logger.error( `Failed to read file at ${sourcekitConfigFile.fsPath}: ${error}` ); @@ -78,7 +78,7 @@ async function createSourcekitConfiguration( return false; } } catch (error) { - if ((error as NodeJS.ErrnoException).code !== "ENOENT") { + if ((error as vscode.FileSystemError).code !== "FileNotFound") { workspaceContext.logger.error( `Failed to read folder at ${sourcekitFolder.fsPath}: ${error}` ); @@ -147,7 +147,7 @@ async function checkDocumentSchema(doc: vscode.TextDocument, workspaceContext: W try { buffer = await vscode.workspace.fs.readFile(doc.uri); } catch (error) { - if ((error as NodeJS.ErrnoException).code !== "ENOENT") { + if ((error as vscode.FileSystemError).code !== "FileNotFound") { workspaceContext.logger.error(`Failed to read file at ${doc.uri.fsPath}: ${error}`); } return; diff --git a/src/commands/newFile.ts b/src/commands/newFile.ts index 69af38926..72ce52a27 100644 --- a/src/commands/newFile.ts +++ b/src/commands/newFile.ts @@ -12,7 +12,6 @@ // //===----------------------------------------------------------------------===// -import * as fs from "fs/promises"; import * as path from "path"; import * as vscode from "vscode"; @@ -39,7 +38,7 @@ export async function newSwiftFile( } try { - await fs.writeFile(targetUri.fsPath, "", "utf-8"); + await vscode.workspace.fs.writeFile(targetUri, Buffer.from("")); const document = await vscode.workspace.openTextDocument(targetUri); await vscode.languages.setTextDocumentLanguage(document, "swift"); await vscode.window.showTextDocument(document); diff --git a/src/commands/runSwiftScript.ts b/src/commands/runSwiftScript.ts index e522c4cd2..a0f7103c5 100644 --- a/src/commands/runSwiftScript.ts +++ b/src/commands/runSwiftScript.ts @@ -14,7 +14,6 @@ import * as vscode from "vscode"; import * as path from "path"; -import * as fs from "fs/promises"; import { createSwiftTask } from "../tasks/SwiftTaskProvider"; import { WorkspaceContext } from "../WorkspaceContext"; import { Version } from "../utilities/version"; @@ -69,13 +68,15 @@ export async function runSwiftScript(ctx: WorkspaceContext) { } let filename = document.fileName; + let file = document.uri; let isTempFile = false; if (document.isUntitled) { // if document hasn't been saved, save it to a temporary file isTempFile = true; filename = ctx.tempFolder.filename(document.fileName, "swift"); + file = vscode.Uri.file(filename); const text = document.getText(); - await fs.writeFile(filename, text); + await vscode.workspace.fs.writeFile(file, Buffer.from(text)); } else { // otherwise save document await document.save(); @@ -94,6 +95,6 @@ export async function runSwiftScript(ctx: WorkspaceContext) { // delete file after running swift if (isTempFile) { - await fs.rm(filename); + await vscode.workspace.fs.delete(file); } } diff --git a/src/debugger/buildConfig.ts b/src/debugger/buildConfig.ts index 738f5d00f..1db94e56e 100644 --- a/src/debugger/buildConfig.ts +++ b/src/debugger/buildConfig.ts @@ -15,7 +15,6 @@ import * as os from "os"; import * as path from "path"; import * as vscode from "vscode"; -import * as fs from "fs/promises"; import configuration from "../configuration"; import { FolderContext } from "../FolderContext"; import { BuildFlags } from "../toolchain/BuildFlags"; @@ -109,9 +108,9 @@ export class SwiftTestingBuildAguments { public static build( fifoPipePath: string, - attachmentPath: string | undefined + attachmentFolder: string | undefined ): SwiftTestingBuildAguments { - return new SwiftTestingBuildAguments(fifoPipePath, attachmentPath); + return new SwiftTestingBuildAguments(fifoPipePath, attachmentFolder); } } @@ -119,19 +118,19 @@ export class SwiftTestingConfigurationSetup { public static async setupAttachmentFolder( folderContext: FolderContext, testRunTime: number - ): Promise { - const attachmentPath = SwiftTestingConfigurationSetup.resolveAttachmentPath( + ): Promise { + const attachmentFile = SwiftTestingConfigurationSetup.resolveAttachmentFile( folderContext, testRunTime ); - if (attachmentPath) { + if (attachmentFile) { // Create the directory if it doesn't exist. - await fs.mkdir(attachmentPath, { recursive: true }); + await vscode.workspace.fs.createDirectory(attachmentFile); - return attachmentPath; + return attachmentFile; } - return attachmentPath; + return attachmentFile; } public static async cleanupAttachmentFolder( @@ -139,30 +138,34 @@ export class SwiftTestingConfigurationSetup { testRunTime: number, logger: SwiftLogger ): Promise { - const attachmentPath = SwiftTestingConfigurationSetup.resolveAttachmentPath( + const attachmentFile = SwiftTestingConfigurationSetup.resolveAttachmentFile( folderContext, testRunTime ); - if (attachmentPath) { + if (attachmentFile) { try { + // readDirectory always prints ugly error if not exists + await vscode.workspace.fs.stat(attachmentFile); // If no attachments were written during the test run clean up the folder // that was created to contain them to prevent accumulation of empty folders // after every run. - const files = await fs.readdir(attachmentPath); + const files = await vscode.workspace.fs.readDirectory(attachmentFile); if (files.length === 0) { - await fs.rmdir(attachmentPath); + await vscode.workspace.fs.delete(attachmentFile); } } catch (error) { - logger.error(`Failed to clean up attachment path: ${error}`); + if ((error as vscode.FileSystemError).code !== "FileNotFound") { + logger.error(`Failed to clean up attachment path: ${error}`); + } } } } - private static resolveAttachmentPath( + private static resolveAttachmentFile( folderContext: FolderContext, testRunTime: number - ): string | undefined { + ): vscode.Uri | undefined { let attachmentPath = configuration.folder(folderContext.workspaceFolder).attachmentsPath; if (attachmentPath.length > 0) { // If the attachment path is relative, resolve it relative to the workspace folder. @@ -171,7 +174,7 @@ export class SwiftTestingConfigurationSetup { } const dateString = this.dateString(testRunTime); - return path.join(attachmentPath, dateString); + return vscode.Uri.file(path.join(attachmentPath, dateString)); } return undefined; } diff --git a/src/debugger/lldb.ts b/src/debugger/lldb.ts index 7d56b79ba..b1490ba61 100644 --- a/src/debugger/lldb.ts +++ b/src/debugger/lldb.ts @@ -17,7 +17,6 @@ import * as vscode from "vscode"; import * as path from "path"; -import * as fs from "fs/promises"; import { execFile, IS_RUNNING_UNDER_TEST } from "../utilities/utilities"; import { Result } from "../utilities/result"; import { SwiftToolchain } from "../toolchain/toolchain"; @@ -81,8 +80,8 @@ export async function getLLDBLibPath(toolchain: SwiftToolchain): Promise { - const stat = await fs.stat(pathHint); - if (stat.isFile()) { + const stat = await vscode.workspace.fs.stat(vscode.Uri.file(pathHint)); + if (stat.type === vscode.FileType.File) { return pathHint; } @@ -112,10 +111,12 @@ export async function findLibLLDB(pathHint: string): Promise export async function findFileByPattern(path: string, pattern: RegExp): Promise { try { - const files = await fs.readdir(path); + const uri = vscode.Uri.file(path); + await vscode.workspace.fs.stat(uri); + const files = await vscode.workspace.fs.readDirectory(uri); for (const file of files) { - if (pattern.test(file)) { - return file; + if (pattern.test(file[0])) { + return file[0]; } } } catch (err) { diff --git a/src/documentation/DocumentationPreviewEditor.ts b/src/documentation/DocumentationPreviewEditor.ts index dffaa4213..9737be9a9 100644 --- a/src/documentation/DocumentationPreviewEditor.ts +++ b/src/documentation/DocumentationPreviewEditor.ts @@ -13,7 +13,6 @@ //===----------------------------------------------------------------------===// import * as vscode from "vscode"; -import * as fs from "fs/promises"; import * as path from "path"; import { RenderNode, WebviewContent, WebviewMessage } from "./webview/WebviewMessage"; import { WorkspaceContext } from "../WorkspaceContext"; @@ -36,6 +35,7 @@ export class DocumentationPreviewEditor implements vscode.Disposable { const swiftDoccRenderPath = extension.asAbsolutePath( path.join("assets", "swift-docc-render") ); + const swiftDoccRenderFile = vscode.Uri.file(swiftDoccRenderPath); const webviewPanel = vscode.window.createWebviewPanel( PreviewEditorConstant.VIEW_TYPE, PreviewEditorConstant.TITLE, @@ -51,7 +51,7 @@ export class DocumentationPreviewEditor implements vscode.Disposable { vscode.Uri.file( extension.asAbsolutePath(path.join("assets", "documentation-webview")) ), - vscode.Uri.file(swiftDoccRenderPath), + swiftDoccRenderFile, ...context.folders.map(f => f.folder), ], } @@ -68,18 +68,17 @@ export class DocumentationPreviewEditor implements vscode.Disposable { ) ), }; - const webviewBaseURI = webviewPanel.webview.asWebviewUri( - vscode.Uri.file(swiftDoccRenderPath) - ); + const webviewBaseURI = webviewPanel.webview.asWebviewUri(swiftDoccRenderFile); const scriptURI = webviewPanel.webview.asWebviewUri( vscode.Uri.file( extension.asAbsolutePath(path.join("assets", "documentation-webview", "index.js")) ) ); - let doccRenderHTML = await fs.readFile( - path.join(swiftDoccRenderPath, "index.html"), - "utf-8" - ); + let doccRenderHTML = Buffer.from( + await vscode.workspace.fs.readFile( + vscode.Uri.joinPath(swiftDoccRenderFile, "index.html") + ) + ).toString("utf-8"); const codiconsUri = webviewPanel.webview.asWebviewUri( vscode.Uri.file( extension.asAbsolutePath( diff --git a/src/toolchain/SelectedXcodeWatcher.ts b/src/toolchain/SelectedXcodeWatcher.ts index 04ccafcbe..178195929 100644 --- a/src/toolchain/SelectedXcodeWatcher.ts +++ b/src/toolchain/SelectedXcodeWatcher.ts @@ -12,7 +12,9 @@ // //===----------------------------------------------------------------------===// -import * as fs from "fs/promises"; +// TODO Is no way to read symlink target with the vscode.workspace.fs APIs +// eslint-disable-next-line no-restricted-imports +import { readlink } from "fs/promises"; import * as vscode from "vscode"; import { showReloadExtensionNotification } from "../ui/ReloadExtension"; import configuration from "../configuration"; @@ -42,7 +44,7 @@ export class SelectedXcodeWatcher implements vscode.Disposable { testDependencies?.xcodeSymlink || (async () => { try { - return await fs.readlink(SelectedXcodeWatcher.XCODE_SYMLINK_LOCATION); + return await readlink(SelectedXcodeWatcher.XCODE_SYMLINK_LOCATION); } catch (e) { return undefined; } diff --git a/src/toolchain/swiftly.ts b/src/toolchain/swiftly.ts index 554811ab8..d8debfbdb 100644 --- a/src/toolchain/swiftly.ts +++ b/src/toolchain/swiftly.ts @@ -14,7 +14,6 @@ import * as path from "path"; import { SwiftlyConfig } from "./ToolchainVersion"; -import * as fs from "fs/promises"; import { execFile, ExecFileError } from "../utilities/utilities"; import * as vscode from "vscode"; import { Version } from "../utilities/version"; @@ -229,10 +228,11 @@ export class Swiftly { if (!swiftlyHomeDir) { return; } - const swiftlyConfigRaw = await fs.readFile( - path.join(swiftlyHomeDir, "config.json"), - "utf-8" + const swiftlyConfigRaw = Buffer.from( + await vscode.workspace.fs.readFile( + vscode.Uri.file(path.join(swiftlyHomeDir, "config.json")) + ) ); - return JSON.parse(swiftlyConfigRaw); + return JSON.parse(swiftlyConfigRaw.toString("utf-8")); } } diff --git a/src/toolchain/toolchain.ts b/src/toolchain/toolchain.ts index e96dc2ca1..729dcb616 100644 --- a/src/toolchain/toolchain.ts +++ b/src/toolchain/toolchain.ts @@ -12,7 +12,9 @@ // //===----------------------------------------------------------------------===// -import * as fs from "fs/promises"; +// TODO vscode.workspace.fs APIs cannot resolve symlinks +// eslint-disable-next-line no-restricted-imports +import { realpath } from "fs/promises"; import * as path from "path"; import * as os from "os"; import * as plist from "plist"; @@ -280,10 +282,10 @@ export class SwiftToolchain { public static async findToolchainsIn(directory: string): Promise { try { const toolchains = await Promise.all( - (await fs.readdir(directory, { withFileTypes: true })) - .filter(dirent => dirent.name.startsWith("swift-")) + (await vscode.workspace.fs.readDirectory(vscode.Uri.file(directory))) + .filter(dirent => dirent[0].startsWith("swift-")) .map(async dirent => { - const toolchainPath = path.join(dirent.path, dirent.name); + const toolchainPath = path.join(directory, dirent[0]); const toolchainSwiftPath = path.join(toolchainPath, "usr", "bin", "swift"); if (!(await pathExists(toolchainSwiftPath))) { return null; @@ -567,7 +569,7 @@ export class SwiftToolchain { } } // swift may be a symbolic link - let realSwift = await fs.realpath(swift); + let realSwift = await realpath(swift); let isSwiftlyManaged = false; if (path.basename(realSwift) === "swiftly") { @@ -729,7 +731,7 @@ export class SwiftToolchain { // a unified binary and we need to use this utility to run the swift-testing tests // on macOS. XCTests are still run with the xctest utility on macOS. The test binaries // can be invoked directly on Linux/Windows. - if (await this.fileExists(toolchainSwiftPMHelperPath)) { + if (await fileExists(toolchainSwiftPMHelperPath)) { return toolchainSwiftPMHelperPath; } } @@ -831,7 +833,9 @@ export class SwiftToolchain { ); return undefined; } - const data = await fs.readFile(platformManifest, "utf8"); + const data = Buffer.from( + await vscode.workspace.fs.readFile(vscode.Uri.file(platformManifest)) + ).toString("utf-8"); let infoPlist; try { infoPlist = plist.parse(data) as unknown as InfoPlist; @@ -919,17 +923,4 @@ export class SwiftToolchain { } return version ?? new Version(0, 0, 0); } - - /** - * Check if a file exists. - * @returns true if the file exists at the supplied path - */ - private static async fileExists(path: string): Promise { - try { - await fs.access(path, fs.constants.F_OK); - return true; - } catch { - return false; - } - } } diff --git a/src/ui/ProjectPanelProvider.ts b/src/ui/ProjectPanelProvider.ts index bc087eca8..d359a75a8 100644 --- a/src/ui/ProjectPanelProvider.ts +++ b/src/ui/ProjectPanelProvider.ts @@ -13,7 +13,6 @@ //===----------------------------------------------------------------------===// import * as vscode from "vscode"; -import * as fs from "fs/promises"; import * as path from "path"; import configuration from "../configuration"; import { WorkspaceContext } from "../WorkspaceContext"; @@ -25,6 +24,8 @@ import { getPlatformConfig, resolveTaskCwd } from "../utilities/tasks"; import { SwiftTask, TaskPlatformSpecificConfig } from "../tasks/SwiftTaskProvider"; import { convertPathToPattern, glob } from "fast-glob"; import { Version } from "../utilities/version"; +// No synchronous APIs in vscode.workspace.fs +// eslint-disable-next-line no-restricted-imports import { existsSync } from "fs"; const LOADING_ICON = "loading~spin"; @@ -76,9 +77,15 @@ async function getChildren( }); const results: FileNode[] = []; for (const filePath of contents) { - const stats = await fs.stat(filePath); + const stats = await vscode.workspace.fs.stat(vscode.Uri.file(filePath)); results.push( - new FileNode(path.basename(filePath), filePath, stats.isDirectory(), parentId, mockFs) + new FileNode( + path.basename(filePath), + filePath, + stats.type === vscode.FileType.Directory, + parentId, + mockFs + ) ); } return results.sort((first, second) => { diff --git a/src/ui/ReadOnlyDocumentProvider.ts b/src/ui/ReadOnlyDocumentProvider.ts index 9c4cfcc90..5db02409f 100644 --- a/src/ui/ReadOnlyDocumentProvider.ts +++ b/src/ui/ReadOnlyDocumentProvider.ts @@ -13,7 +13,6 @@ //===----------------------------------------------------------------------===// import * as vscode from "vscode"; -import * as fs from "fs/promises"; /** * Registers a {@link vscode.TextDocumentContentProvider TextDocumentContentProvider} that will display @@ -23,8 +22,8 @@ export function getReadOnlyDocumentProvider(): vscode.Disposable { const provider = vscode.workspace.registerTextDocumentContentProvider("readonly", { provideTextDocumentContent: async uri => { try { - const contents = await fs.readFile(uri.fsPath, "utf8"); - return contents; + const contents = Buffer.from(await vscode.workspace.fs.readFile(uri)); + return contents.toString("utf-8"); } catch (error) { return `Failed to load swiftinterface ${uri.path}`; } diff --git a/src/ui/win32.ts b/src/ui/win32.ts index a93a451ee..73e53b1f9 100644 --- a/src/ui/win32.ts +++ b/src/ui/win32.ts @@ -12,7 +12,9 @@ // //===----------------------------------------------------------------------===// -import * as fs from "fs/promises"; +// TODO cannot work with symlinks using vscode.workspace.fs APIs +// eslint-disable-next-line no-restricted-imports +import { symlink, unlink } from "fs/promises"; import { TemporaryFolder } from "../utilities/tempFolder"; import configuration from "../configuration"; import * as vscode from "vscode"; @@ -58,8 +60,8 @@ export async function isSymlinkAllowed(logger?: SwiftLogger): Promise { return await temporaryFolder.withTemporaryFile("", async testFilePath => { const testSymlinkPath = temporaryFolder.filename("symlink-"); try { - await fs.symlink(testFilePath, testSymlinkPath, "file"); - await fs.unlink(testSymlinkPath); + await symlink(testFilePath, testSymlinkPath, "file"); + await unlink(testSymlinkPath); return true; } catch (error) { logger?.error(error); diff --git a/src/utilities/filesystem.ts b/src/utilities/filesystem.ts index 2dce7fe89..bcf199329 100644 --- a/src/utilities/filesystem.ts +++ b/src/utilities/filesystem.ts @@ -13,7 +13,6 @@ //===----------------------------------------------------------------------===// import { contains } from "micromatch"; -import * as fs from "fs/promises"; import * as path from "path"; import * as vscode from "vscode"; import { convertPathToPattern, glob as fastGlob, Options } from "fast-glob"; @@ -28,7 +27,7 @@ export const validFileTypes = ["swift", "c", "cpp", "h", "hpp", "m", "mm"]; */ export async function pathExists(...pathComponents: string[]): Promise { try { - await fs.access(path.join(...pathComponents)); + await vscode.workspace.fs.stat(vscode.Uri.file(path.join(...pathComponents))); return true; } catch { return false; @@ -42,7 +41,10 @@ export async function pathExists(...pathComponents: string[]): Promise */ export async function fileExists(...pathComponents: string[]): Promise { try { - return (await fs.stat(path.join(...pathComponents))).isFile(); + return ( + (await vscode.workspace.fs.stat(vscode.Uri.file(path.join(...pathComponents)))).type === + vscode.FileType.File + ); } catch (e) { return false; } diff --git a/src/utilities/tempFolder.ts b/src/utilities/tempFolder.ts index ee5fa7ac1..d32f58481 100644 --- a/src/utilities/tempFolder.ts +++ b/src/utilities/tempFolder.ts @@ -14,9 +14,8 @@ import { tmpdir } from "os"; import * as path from "path"; -import * as fs from "fs/promises"; import { randomString } from "./utilities"; -import { Disposable } from "vscode"; +import { Disposable, Uri, workspace } from "vscode"; export class TemporaryFolder { private constructor(public path: string) {} @@ -64,7 +63,7 @@ export class TemporaryFolder { static async create(): Promise { const tmpPath = path.join(tmpdir(), "vscode-swift"); try { - await fs.mkdir(tmpPath); + await workspace.fs.createDirectory(Uri.file(tmpPath)); } catch { // ignore error. It is most likely directory exists already } @@ -101,12 +100,12 @@ export class TemporaryFolder { try { const rt = await process(); for (const path of paths) { - await fs.rm(path, { force: true }); + await workspace.fs.delete(Uri.file(path), { recursive: true }); } return rt; } catch (error) { for (const path of paths) { - await fs.rm(path, { force: true }); + await workspace.fs.delete(Uri.file(path), { recursive: true }); } throw error; } @@ -126,7 +125,7 @@ export class DisposableFileCollection implements Disposable { async dispose() { for (const file of this.files) { - await fs.rm(file, { force: true }); + await workspace.fs.delete(Uri.file(file), { recursive: true }); } this.files = []; } diff --git a/test/integration-tests/commands/build.test.ts b/test/integration-tests/commands/build.test.ts index 6698aa797..b0b52d332 100644 --- a/test/integration-tests/commands/build.test.ts +++ b/test/integration-tests/commands/build.test.ts @@ -13,8 +13,6 @@ //===----------------------------------------------------------------------===// import * as vscode from "vscode"; -import * as fs from "fs/promises"; -import * as path from "path"; import { expect } from "chai"; import { testAssetUri } from "../../fixtures"; import { FolderContext } from "../../../src/FolderContext"; @@ -93,13 +91,13 @@ suite("Build Commands @slow", function () { let result = await vscode.commands.executeCommand(Commands.RUN, "PackageExe"); expect(result).to.be.true; - const buildPath = path.join(folderContext.folder.fsPath, ".build"); - const beforeItemCount = (await fs.readdir(buildPath)).length; + const buildPath = vscode.Uri.joinPath(folderContext.folder, ".build"); + const beforeItemCount = (await vscode.workspace.fs.readDirectory(buildPath)).length; result = await vscode.commands.executeCommand(Commands.CLEAN_BUILD); expect(result).to.be.true; - const afterItemCount = (await fs.readdir(buildPath)).length; + const afterItemCount = (await vscode.workspace.fs.readDirectory(buildPath)).length; // .build folder is going to be filled with built artifacts after Commands.RUN command // After executing the clean command the build directory is guranteed to have less entry. expect(afterItemCount).to.be.lessThan(beforeItemCount); diff --git a/test/integration-tests/commands/captureDiagnostics.test.ts b/test/integration-tests/commands/captureDiagnostics.test.ts index 5e5cca2e4..b02656aad 100644 --- a/test/integration-tests/commands/captureDiagnostics.test.ts +++ b/test/integration-tests/commands/captureDiagnostics.test.ts @@ -15,7 +15,6 @@ import * as vscode from "vscode"; import * as path from "path"; import * as os from "os"; -import { mkdir, rm } from "fs/promises"; import * as decompress from "decompress"; import { expect } from "chai"; import { captureDiagnostics } from "../../../src/commands/captureDiagnostics"; @@ -27,6 +26,7 @@ import { updateSettings, } from "../utilities/testutilities"; import { Version } from "../../../src/utilities/version"; +import * as assert from "assert"; suite("captureDiagnostics Test Suite", () => { let workspaceContext: WorkspaceContext; @@ -45,22 +45,22 @@ suite("captureDiagnostics Test Suite", () => { }); test("Should capture dianostics to a zip file", async () => { - const zipPath = await captureDiagnostics(workspaceContext); - expect(zipPath).to.not.be.undefined; + const zipFile = await captureDiagnostics(workspaceContext); + expect(zipFile).to.not.be.undefined; }); test("Should validate a single folder project zip file has contents", async () => { - const zipPath = await captureDiagnostics(workspaceContext); - expect(zipPath).to.not.be.undefined; + const zipFile = await captureDiagnostics(workspaceContext); + assert(zipFile); - const { files, folder } = await decompressZip(zipPath as string); + const { files, folder } = await decompressZip(zipFile); validate( files.map(file => file.path), ["swift-vscode-extension.log", "defaultPackage-[a-z0-9]+-settings.txt"] ); - await rm(folder, { recursive: true, force: true }); + await vscode.workspace.fs.delete(folder, { recursive: true }); }); suite("Multiple folder project", () => { @@ -69,10 +69,10 @@ suite("captureDiagnostics Test Suite", () => { }); test("Should validate a multiple folder project zip file has contents", async () => { - const zipPath = await captureDiagnostics(workspaceContext); - expect(zipPath).to.not.be.undefined; + const zipFile = await captureDiagnostics(workspaceContext); + assert(zipFile); - const { files, folder } = await decompressZip(zipPath as string); + const { files, folder } = await decompressZip(zipFile); validate( files.map(file => file.path), [ @@ -83,7 +83,7 @@ suite("captureDiagnostics Test Suite", () => { "dependencies/dependencies-[a-z0-9]+-settings.txt", ] ); - await rm(folder, { recursive: true, force: true }); + await vscode.workspace.fs.delete(folder, { recursive: true }); }); }); }); @@ -113,10 +113,10 @@ suite("captureDiagnostics Test Suite", () => { }); test("Should validate a single folder project zip file has contents", async () => { - const zipPath = await captureDiagnostics(workspaceContext, false); - expect(zipPath).to.not.be.undefined; + const zipFile = await captureDiagnostics(workspaceContext, false); + assert(zipFile); - const { files, folder } = await decompressZip(zipPath as string); + const { files, folder } = await decompressZip(zipFile); const post60Logs = workspaceContext.globalToolchainSwiftVersion.isGreaterThanOrEqual( new Version(6, 0, 0) @@ -134,7 +134,7 @@ suite("captureDiagnostics Test Suite", () => { false // Sometime are diagnostics, sometimes not but not point of this test ); - await rm(folder, { recursive: true, force: true }); + await vscode.workspace.fs.delete(folder, { recursive: true }); }); suite("Multiple folder project", () => { @@ -143,10 +143,8 @@ suite("captureDiagnostics Test Suite", () => { }); test("Should validate a multiple folder project zip file has contents", async () => { - const zipPath = await captureDiagnostics(workspaceContext, false); - expect(zipPath).to.not.be.undefined; - - const { files, folder } = await decompressZip(zipPath as string); + const zipFile = await captureDiagnostics(workspaceContext, false); + assert(zipFile); const post60Logs = workspaceContext.globalToolchainSwiftVersion.isGreaterThanOrEqual( @@ -160,6 +158,7 @@ suite("captureDiagnostics Test Suite", () => { ] : []; + const { files, folder } = await decompressZip(zipFile); validate( files.map(file => file.path), [ @@ -172,20 +171,19 @@ suite("captureDiagnostics Test Suite", () => { ], false // Sometime are diagnostics, sometimes not but not point of this test ); - await rm(folder, { recursive: true, force: true }); + await vscode.workspace.fs.delete(folder, { recursive: true }); }); }); }); async function decompressZip( - zipPath: string - ): Promise<{ folder: string; files: decompress.File[] }> { - const tempDir = path.join( - os.tmpdir(), - `vscode-swift-test-${Math.random().toString(36).substring(7)}` + zipFile: vscode.Uri + ): Promise<{ folder: vscode.Uri; files: decompress.File[] }> { + const tempDir = vscode.Uri.file( + path.join(os.tmpdir(), `vscode-swift-test-${Math.random().toString(36).substring(7)}`) ); - await mkdir(tempDir, { recursive: true }); - return { folder: tempDir, files: await decompress(zipPath as string, tempDir) }; + await vscode.workspace.fs.createDirectory(tempDir); + return { folder: tempDir, files: await decompress(zipFile.fsPath, tempDir.fsPath) }; } function validate(paths: string[], patterns: string[], matchCount: boolean = true): void { diff --git a/test/unit-tests/MockUtils.test.ts b/test/unit-tests/MockUtils.test.ts index f922a5bdc..3fbd4e73d 100644 --- a/test/unit-tests/MockUtils.test.ts +++ b/test/unit-tests/MockUtils.test.ts @@ -15,7 +15,6 @@ import { expect } from "chai"; import { stub } from "sinon"; import * as vscode from "vscode"; -import * as fs from "fs/promises"; import { AsyncEventEmitter, mockFn, @@ -206,17 +205,9 @@ suite("MockUtils Test Suite", () => { }); suite("mockGlobalModule()", () => { - const mockedFS = mockGlobalModule(fs); const mockedContextKeys = mockGlobalModule(contextKeys); const mockedConfiguration = mockGlobalModule(configuration); - test("can mock the fs/promises module", async () => { - mockedFS.readFile.resolves("file contents"); - - await expect(fs.readFile("some_file")).to.eventually.equal("file contents"); - expect(mockedFS.readFile).to.have.been.calledOnceWithExactly("some_file"); - }); - test("can mock the contextKeys module", () => { // Initial value should match default value in context keys expect(contextKeys.isActivated).to.be.false; diff --git a/test/unit-tests/debugger/lldb.test.ts b/test/unit-tests/debugger/lldb.test.ts index c3bd582df..8fac2a8a1 100644 --- a/test/unit-tests/debugger/lldb.test.ts +++ b/test/unit-tests/debugger/lldb.test.ts @@ -14,8 +14,7 @@ import * as util from "../../../src/utilities/utilities"; import * as lldb from "../../../src/debugger/lldb"; -import * as fs from "fs/promises"; -import * as sinon from "sinon"; +import * as vscode from "vscode"; import { expect } from "chai"; import { instance, @@ -23,20 +22,24 @@ import { mockFn, mockGlobalModule, mockObject, - MockedFunction, mockGlobalValue, } from "../../MockUtils"; import { SwiftToolchain } from "../../../src/toolchain/toolchain"; suite("debugger.lldb Tests", () => { + const wsMock = mockGlobalValue(vscode.workspace, "fs"); + const fsMock = mockObject({ readDirectory: mockFn(), stat: mockFn() }); + + setup(() => { + wsMock.setValue(fsMock); + }); + suite("getLLDBLibPath Tests", () => { let mockToolchain: MockedObject; - let mockFindLibLLDB: MockedFunction<(typeof lldb)["findLibLLDB"]>; const mockedPlatform = mockGlobalValue(process, "platform"); const mockUtil = mockGlobalModule(util); setup(() => { - mockFindLibLLDB = sinon.stub(); mockToolchain = mockObject({ getLLDB: mockFn(), swiftFolderPath: "", @@ -62,25 +65,28 @@ suite("debugger.lldb Tests", () => { test("should return failure if findLibLLDB returns falsy values", async () => { mockToolchain.getLLDB.resolves("/path/to/lldb"); mockUtil.execFile.resolves({ stdout: "", stderr: "" }); - mockFindLibLLDB.onFirstCall().resolves(undefined); - - let result = await lldb.getLLDBLibPath(instance(mockToolchain)); - expect(result.failure).to.not.equal(undefined); - - mockFindLibLLDB.onSecondCall().resolves(""); + fsMock.stat.resolves({ type: vscode.FileType.Directory } as any); - result = await lldb.getLLDBLibPath(instance(mockToolchain)); + const result = await lldb.getLLDBLibPath(instance(mockToolchain)); expect(result.failure).to.not.equal(undefined); }); // NB(separate itest): contract test with toolchains of various platforms }); suite("findLibLLDB Tests", () => { - const fsMock = mockGlobalModule(fs); + const wsMock = mockGlobalValue(vscode.workspace, "fs"); + const fsMock = mockObject({ readDirectory: mockFn(), stat: mockFn() }); + + setup(() => { + wsMock.setValue(fsMock); + }); test("should return undefined if no file matches the pattern", async () => { - fsMock.readdir.resolves(["file1", "file2"] as any); - fsMock.stat.resolves({ isFile: () => false } as any); + fsMock.readDirectory.resolves([ + ["file1", vscode.FileType.Directory], + ["file2", vscode.FileType.Directory], + ] as any); + fsMock.stat.resolves({ type: vscode.FileType.Directory } as any); const result = await lldb.findLibLLDB("/path/hint"); @@ -88,7 +94,7 @@ suite("debugger.lldb Tests", () => { }); test("should return path if file exists", async () => { - fsMock.stat.resolves({ isFile: () => true } as any); + fsMock.stat.resolves({ type: vscode.FileType.File } as any); const result = await lldb.findLibLLDB("/path/hint"); @@ -98,10 +104,11 @@ suite("debugger.lldb Tests", () => { }); suite("findFileByPattern Tests", () => { - const fsMock = mockGlobalModule(fs); - test("should return null if no file matches the pattern", async () => { - fsMock.readdir.resolves(["file1", "file2"] as any); + fsMock.readDirectory.resolves([ + ["file1", vscode.FileType.File], + ["file2", vscode.FileType.File], + ] as any); const result = await lldb.findFileByPattern("/some/path", /pattern/); @@ -109,7 +116,10 @@ suite("debugger.lldb Tests", () => { }); test("should return the first match if one file matches the pattern", async () => { - fsMock.readdir.resolves(["match1", "nomatch"] as any); + fsMock.readDirectory.resolves([ + ["match1", vscode.FileType.File], + ["nomatch", vscode.FileType.File], + ] as any); const result = await lldb.findFileByPattern("/some/path", /match1/); @@ -117,7 +127,10 @@ suite("debugger.lldb Tests", () => { }); test("should return the first match if multiple files match the pattern", async () => { - fsMock.readdir.resolves(["match1", "match2"] as any); + fsMock.readDirectory.resolves([ + ["match1", vscode.FileType.File], + ["match2", vscode.FileType.File], + ] as any); const result = await lldb.findFileByPattern("/some/path", /match/); @@ -125,7 +138,7 @@ suite("debugger.lldb Tests", () => { }); test("should return null if directory reading fails", async () => { - fsMock.readdir.rejects(new Error("Some error")); + fsMock.readDirectory.rejects(new Error("Some error")); const result = await lldb.findFileByPattern("/some/path", /pattern/); diff --git a/test/unit-tests/mock-fs.test.ts b/test/unit-tests/mock-fs.test.ts new file mode 100644 index 000000000..be0a64008 --- /dev/null +++ b/test/unit-tests/mock-fs.test.ts @@ -0,0 +1,49 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the VS Code Swift open source project +// +// Copyright (c) 2025 the VS Code Swift project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of VS Code Swift project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import { expect } from "chai"; +import * as mockFS from "mock-fs"; +import * as vscode from "vscode"; + +suite("mock-fs example", () => { + // This teardown step is also important to make sure your tests clean up the + // mocked file system when they complete! + teardown(() => { + mockFS.restore(); + }); + + test("mock out a file on disk", async () => { + // A single function call can be used to configure the file system + mockFS({ + "/path/to/some/file": "Some really cool file contents", + }); + expect( + Buffer.from( + await vscode.workspace.fs.readFile(vscode.Uri.file("/path/to/some/file")) + ).toString("utf-8") + ).to.equal("Some really cool file contents"); + }); + + test("file is not readable by the current user", async () => { + mockFS({ "/path/to/file": mockFS.file({ mode: 0o000 }) }); + if (process.platform === "linux") { + await expect( + vscode.workspace.fs.readFile(vscode.Uri.file("/path/to/file")) + ).to.eventually.deep.equal(Buffer.from([])); + } else { + await expect(vscode.workspace.fs.readFile(vscode.Uri.file("/path/to/file"))).to + .eventually.be.rejected; + } + }); +}); diff --git a/test/unit-tests/ui/PackageDependencyProvider.test.ts b/test/unit-tests/ui/PackageDependencyProvider.test.ts index 3d8758a22..d773b616e 100644 --- a/test/unit-tests/ui/PackageDependencyProvider.test.ts +++ b/test/unit-tests/ui/PackageDependencyProvider.test.ts @@ -15,9 +15,8 @@ import { expect } from "chai"; import * as path from "path"; import * as vscode from "vscode"; -import * as fs from "fs/promises"; import { FileNode, PackageNode } from "../../../src/ui/ProjectPanelProvider"; -import { mockGlobalModule } from "../../MockUtils"; +import { mockFn, mockGlobalValue, mockObject } from "../../MockUtils"; suite("PackageDependencyProvider Unit Test Suite", function () { suite("FileNode", () => { @@ -66,7 +65,12 @@ suite("PackageDependencyProvider Unit Test Suite", function () { expect(item.command).to.be.undefined; }); - const fsMock = mockGlobalModule(fs); + const wsMock = mockGlobalValue(vscode.workspace, "fs"); + const fsMock = mockObject({ readDirectory: mockFn(), stat: mockFn() }); + + setup(() => { + wsMock.setValue(fsMock); + }); test("enumerates child dependencies and files", async () => { fsMock.stat.resolves({ isFile: () => true, isDirectory: () => false } as any);