diff --git a/assets/test/ModularPackage/Module1/Package.swift b/assets/test/ModularPackage/Module1/Package.swift new file mode 100644 index 000000000..871dbc947 --- /dev/null +++ b/assets/test/ModularPackage/Module1/Package.swift @@ -0,0 +1,23 @@ +// swift-tools-version:6.0 + +import PackageDescription + +internal let package = Package( + name: "Module1", + products: [ + .executable(name: "Module1Demo", targets: ["Module1Demo"]), + ], + targets: [ + .testTarget( + name: "Module1Tests", + dependencies: ["Module1"] + ), + .executableTarget( + name: "Module1Demo", + dependencies: ["Module1"] + ), + .target( + name: "Module1" + ), + ] +) diff --git a/assets/test/ModularPackage/Module1/Sources/Module1/Module1.swift b/assets/test/ModularPackage/Module1/Sources/Module1/Module1.swift new file mode 100644 index 000000000..32c3e62fa --- /dev/null +++ b/assets/test/ModularPackage/Module1/Sources/Module1/Module1.swift @@ -0,0 +1,7 @@ +public struct Module1 { + public init() {} + + public func add(_ x: Int, _ y: Int) -> Int { + x + y + } +} \ No newline at end of file diff --git a/assets/test/ModularPackage/Module1/Sources/Module1Demo/Module1Demo.swift b/assets/test/ModularPackage/Module1/Sources/Module1Demo/Module1Demo.swift new file mode 100644 index 000000000..941beed71 --- /dev/null +++ b/assets/test/ModularPackage/Module1/Sources/Module1Demo/Module1Demo.swift @@ -0,0 +1,11 @@ +import Module1 + +private let module = Module1() + +@MainActor +func check(_ x: Int, _ y: Int) { + print(module.add(x, y)) +} + +check(1, 2) +check(2, 3) diff --git a/assets/test/ModularPackage/Module1/Tests/Module1Tests.swift b/assets/test/ModularPackage/Module1/Tests/Module1Tests.swift new file mode 100644 index 000000000..e7df7b33c --- /dev/null +++ b/assets/test/ModularPackage/Module1/Tests/Module1Tests.swift @@ -0,0 +1,20 @@ +import XCTest +@testable import Module1 + +internal final class Module1Tests: XCTestCase { + private var sut: Module1! + + override internal func setUp() { + super.setUp() + sut = .init() + } + + override internal func tearDown() { + sut = nil + super.tearDown() + } + + internal func test_add_with1And2_shouldReturn3() { + XCTAssertEqual(sut.add(1, 2), 3) + } +} \ No newline at end of file diff --git a/assets/test/ModularPackage/Module2/Package.swift b/assets/test/ModularPackage/Module2/Package.swift new file mode 100644 index 000000000..9ccde0e86 --- /dev/null +++ b/assets/test/ModularPackage/Module2/Package.swift @@ -0,0 +1,23 @@ +// swift-tools-version:6.0 + +import PackageDescription + +internal let package = Package( + name: "Module2", + products: [ + .executable(name: "Module2Demo", targets: ["Module2Demo"]), + ], + targets: [ + .testTarget( + name: "Module2Tests", + dependencies: ["Module2"] + ), + .executableTarget( + name: "Module2Demo", + dependencies: ["Module2"] + ), + .target( + name: "Module2" + ), + ] +) diff --git a/assets/test/ModularPackage/Module2/Sources/Module2/Module2.swift b/assets/test/ModularPackage/Module2/Sources/Module2/Module2.swift new file mode 100644 index 000000000..07541013e --- /dev/null +++ b/assets/test/ModularPackage/Module2/Sources/Module2/Module2.swift @@ -0,0 +1,7 @@ +public struct Module2 { + public init() {} + + public func subtract(_ x: Int, _ y: Int) -> Int { + x - y + } +} \ No newline at end of file diff --git a/assets/test/ModularPackage/Module2/Sources/Module2Demo/main.swift b/assets/test/ModularPackage/Module2/Sources/Module2Demo/main.swift new file mode 100644 index 000000000..6d33bf22a --- /dev/null +++ b/assets/test/ModularPackage/Module2/Sources/Module2Demo/main.swift @@ -0,0 +1,11 @@ +import Module2 + +private let module = Module2() + +@MainActor +func check(_ x: Int, _ y: Int) { + print(module.subtract(x, y)) +} + +check(1, 2) +check(2, 3) diff --git a/assets/test/ModularPackage/Module2/Tests/Module2Tests.swift b/assets/test/ModularPackage/Module2/Tests/Module2Tests.swift new file mode 100644 index 000000000..795376105 --- /dev/null +++ b/assets/test/ModularPackage/Module2/Tests/Module2Tests.swift @@ -0,0 +1,20 @@ +import XCTest +@testable import Module2 + +internal final class Module2Tests: XCTestCase { + private var sut: Module2! + + override internal func setUp() { + super.setUp() + sut = .init() + } + + override internal func tearDown() { + sut = nil + super.tearDown() + } + + internal func test_add_with5And2_shouldReturn3() { + XCTAssertEqual(sut.subtract(5, 2), 3) + } +} \ No newline at end of file diff --git a/assets/test/ModularPackage/Package.swift b/assets/test/ModularPackage/Package.swift new file mode 100644 index 000000000..c8af6a378 --- /dev/null +++ b/assets/test/ModularPackage/Package.swift @@ -0,0 +1,11 @@ +// swift-tools-version:6.0 + +import PackageDescription + +internal let package = Package( + name: "ModularPackage", + dependencies: [ + .package(path: "Module1"), + .package(path: "Module2"), + ] +) diff --git a/src/WorkspaceContext.ts b/src/WorkspaceContext.ts index 648b6776d..4095593d7 100644 --- a/src/WorkspaceContext.ts +++ b/src/WorkspaceContext.ts @@ -18,7 +18,7 @@ import { FolderContext } from "./FolderContext"; import { StatusItem } from "./ui/StatusItem"; import { SwiftOutputChannel } from "./ui/SwiftOutputChannel"; import { swiftLibraryPathKey } from "./utilities/utilities"; -import { pathExists, isPathInsidePath } from "./utilities/filesystem"; +import { isPathInsidePath } from "./utilities/filesystem"; import { LanguageClientManager } from "./sourcekit-lsp/LanguageClientManager"; import { TemporaryFolder } from "./utilities/tempFolder"; import { TaskManager } from "./tasks/TaskManager"; @@ -34,6 +34,7 @@ import { DiagnosticsManager } from "./DiagnosticsManager"; import { DocumentationManager } from "./documentation/DocumentationManager"; import { DocCDocumentationRequest, ReIndexProjectRequest } from "./sourcekit-lsp/extensions"; import { TestKind } from "./TestExplorer/TestKind"; +import { isValidWorkspaceFolder, searchForPackages } from "./utilities/workspace"; /** * Context for whole workspace. Holds array of contexts for each workspace folder @@ -391,38 +392,19 @@ export class WorkspaceContext implements vscode.Disposable { * @param folder folder being added */ async addWorkspaceFolder(workspaceFolder: vscode.WorkspaceFolder) { - await this.searchForPackages(workspaceFolder.uri, workspaceFolder); - - if (this.getActiveWorkspaceFolder(vscode.window.activeTextEditor) === workspaceFolder) { - await this.focusTextEditor(vscode.window.activeTextEditor); - } - } + const folders = await searchForPackages( + workspaceFolder.uri, + configuration.disableSwiftPMIntegration, + configuration.folder(workspaceFolder).searchSubfoldersForPackages + ); - async searchForPackages(folder: vscode.Uri, workspaceFolder: vscode.WorkspaceFolder) { - // add folder if Package.swift/compile_commands.json/compile_flags.txt/buildServer.json exists - if (await this.isValidWorkspaceFolder(folder.fsPath)) { + for (const folder of folders) { await this.addPackageFolder(folder, workspaceFolder); - return; - } - // should I search sub-folders for more Swift Packages - if (!configuration.folder(workspaceFolder).searchSubfoldersForPackages) { - return; } - await vscode.workspace.fs.readDirectory(folder).then(async entries => { - for (const entry of entries) { - if ( - entry[1] === vscode.FileType.Directory && - entry[0][0] !== "." && - entry[0] !== "Packages" - ) { - await this.searchForPackages( - vscode.Uri.joinPath(folder, entry[0]), - workspaceFolder - ); - } - } - }); + if (this.getActiveWorkspaceFolder(vscode.window.activeTextEditor) === workspaceFolder) { + await this.focusTextEditor(vscode.window.activeTextEditor); + } } public async addPackageFolder( @@ -597,13 +579,7 @@ export class WorkspaceContext implements vscode.Disposable { * Package.swift or a CMake compile_commands.json, compile_flags.txt, or a BSP buildServer.json. */ async isValidWorkspaceFolder(folder: string): Promise { - return ( - ((await pathExists(folder, "Package.swift")) && - !configuration.disableSwiftPMIntegration) || - (await pathExists(folder, "compile_commands.json")) || - (await pathExists(folder, "compile_flags.txt")) || - (await pathExists(folder, "buildServer.json")) - ); + return await isValidWorkspaceFolder(folder, configuration.disableSwiftPMIntegration); } /** send unfocus event to current focussed folder and clear current folder */ diff --git a/src/utilities/workspace.ts b/src/utilities/workspace.ts new file mode 100644 index 000000000..cc0a6fa69 --- /dev/null +++ b/src/utilities/workspace.ts @@ -0,0 +1,63 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the VS Code Swift open source project +// +// Copyright (c) 2022 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 * as vscode from "vscode"; +import { pathExists } from "./filesystem"; + +export async function searchForPackages( + folder: vscode.Uri, + disableSwiftPMIntegration: boolean, + searchSubfoldersForPackages: boolean +): Promise> { + const folders: Array = []; + + async function search(folder: vscode.Uri) { + // add folder if Package.swift/compile_commands.json/compile_flags.txt/buildServer.json exists + if (await isValidWorkspaceFolder(folder.fsPath, disableSwiftPMIntegration)) { + folders.push(folder); + } + // should I search sub-folders for more Swift Packages + if (!searchSubfoldersForPackages) { + return; + } + + await vscode.workspace.fs.readDirectory(folder).then(async entries => { + for (const entry of entries) { + if ( + entry[1] === vscode.FileType.Directory && + entry[0][0] !== "." && + entry[0] !== "Packages" + ) { + await search(vscode.Uri.joinPath(folder, entry[0])); + } + } + }); + } + + await search(folder); + + return folders; +} + +export async function isValidWorkspaceFolder( + folder: string, + disableSwiftPMIntegration: boolean +): Promise { + return ( + (!disableSwiftPMIntegration && (await pathExists(folder, "Package.swift"))) || + (await pathExists(folder, "compile_commands.json")) || + (await pathExists(folder, "compile_flags.txt")) || + (await pathExists(folder, "buildServer.json")) + ); +} diff --git a/test/unit-tests/utilities/workspace.test.ts b/test/unit-tests/utilities/workspace.test.ts new file mode 100644 index 000000000..7ca500fc0 --- /dev/null +++ b/test/unit-tests/utilities/workspace.test.ts @@ -0,0 +1,42 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the VS Code Swift open source project +// +// Copyright (c) 2024 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 * as vscode from "vscode"; +import { searchForPackages } from "../../../src/utilities/workspace"; +import { testAssetUri } from "../../fixtures"; +import { expect } from "chai"; + +suite("Workspace Utilities Unit Test Suite", () => { + suite("searchForPackages", () => { + const packageFolder = testAssetUri("ModularPackage"); + const firstModuleFolder = vscode.Uri.joinPath(packageFolder, "Module1"); + const secondModuleFolder = vscode.Uri.joinPath(packageFolder, "Module2"); + + test("returns only root package when search for subpackages disabled", async () => { + const folders = await searchForPackages(packageFolder, false, false); + + expect(folders.map(folder => folder.fsPath)).eql([packageFolder.fsPath]); + }); + + test("returns subpackages when search for subpackages enabled", async () => { + const folders = await searchForPackages(packageFolder, false, true); + + expect(folders.map(folder => folder.fsPath).sort()).deep.equal([ + packageFolder.fsPath, + firstModuleFolder.fsPath, + secondModuleFolder.fsPath, + ]); + }); + }); +});