Skip to content

Commit 306d130

Browse files
Fix Search Subfolders For Packages Not Working in Swift Package Workspaces (#1425)
Enabling the searchSubfoldersForPackages flag didn't work when the workspace folder was a valid Swift Package. This issue occurred due to an early return statement that prevented further search. This PR resolves the issue by removing the premature return and refactoring the related logic into a utility. Additionally, unit tests have been added to ensure correct behavior.
1 parent db7a431 commit 306d130

File tree

12 files changed

+250
-36
lines changed

12 files changed

+250
-36
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// swift-tools-version:6.0
2+
3+
import PackageDescription
4+
5+
internal let package = Package(
6+
name: "Module1",
7+
products: [
8+
.executable(name: "Module1Demo", targets: ["Module1Demo"]),
9+
],
10+
targets: [
11+
.testTarget(
12+
name: "Module1Tests",
13+
dependencies: ["Module1"]
14+
),
15+
.executableTarget(
16+
name: "Module1Demo",
17+
dependencies: ["Module1"]
18+
),
19+
.target(
20+
name: "Module1"
21+
),
22+
]
23+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
public struct Module1 {
2+
public init() {}
3+
4+
public func add(_ x: Int, _ y: Int) -> Int {
5+
x + y
6+
}
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import Module1
2+
3+
private let module = Module1()
4+
5+
@MainActor
6+
func check(_ x: Int, _ y: Int) {
7+
print(module.add(x, y))
8+
}
9+
10+
check(1, 2)
11+
check(2, 3)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import XCTest
2+
@testable import Module1
3+
4+
internal final class Module1Tests: XCTestCase {
5+
private var sut: Module1!
6+
7+
override internal func setUp() {
8+
super.setUp()
9+
sut = .init()
10+
}
11+
12+
override internal func tearDown() {
13+
sut = nil
14+
super.tearDown()
15+
}
16+
17+
internal func test_add_with1And2_shouldReturn3() {
18+
XCTAssertEqual(sut.add(1, 2), 3)
19+
}
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// swift-tools-version:6.0
2+
3+
import PackageDescription
4+
5+
internal let package = Package(
6+
name: "Module2",
7+
products: [
8+
.executable(name: "Module2Demo", targets: ["Module2Demo"]),
9+
],
10+
targets: [
11+
.testTarget(
12+
name: "Module2Tests",
13+
dependencies: ["Module2"]
14+
),
15+
.executableTarget(
16+
name: "Module2Demo",
17+
dependencies: ["Module2"]
18+
),
19+
.target(
20+
name: "Module2"
21+
),
22+
]
23+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
public struct Module2 {
2+
public init() {}
3+
4+
public func subtract(_ x: Int, _ y: Int) -> Int {
5+
x - y
6+
}
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import Module2
2+
3+
private let module = Module2()
4+
5+
@MainActor
6+
func check(_ x: Int, _ y: Int) {
7+
print(module.subtract(x, y))
8+
}
9+
10+
check(1, 2)
11+
check(2, 3)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import XCTest
2+
@testable import Module2
3+
4+
internal final class Module2Tests: XCTestCase {
5+
private var sut: Module2!
6+
7+
override internal func setUp() {
8+
super.setUp()
9+
sut = .init()
10+
}
11+
12+
override internal func tearDown() {
13+
sut = nil
14+
super.tearDown()
15+
}
16+
17+
internal func test_add_with5And2_shouldReturn3() {
18+
XCTAssertEqual(sut.subtract(5, 2), 3)
19+
}
20+
}
+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// swift-tools-version:6.0
2+
3+
import PackageDescription
4+
5+
internal let package = Package(
6+
name: "ModularPackage",
7+
dependencies: [
8+
.package(path: "Module1"),
9+
.package(path: "Module2"),
10+
]
11+
)

src/WorkspaceContext.ts

+12-36
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { FolderContext } from "./FolderContext";
1818
import { StatusItem } from "./ui/StatusItem";
1919
import { SwiftOutputChannel } from "./ui/SwiftOutputChannel";
2020
import { swiftLibraryPathKey } from "./utilities/utilities";
21-
import { pathExists, isPathInsidePath } from "./utilities/filesystem";
21+
import { isPathInsidePath } from "./utilities/filesystem";
2222
import { LanguageClientManager } from "./sourcekit-lsp/LanguageClientManager";
2323
import { TemporaryFolder } from "./utilities/tempFolder";
2424
import { TaskManager } from "./tasks/TaskManager";
@@ -34,6 +34,7 @@ import { DiagnosticsManager } from "./DiagnosticsManager";
3434
import { DocumentationManager } from "./documentation/DocumentationManager";
3535
import { DocCDocumentationRequest, ReIndexProjectRequest } from "./sourcekit-lsp/extensions";
3636
import { TestKind } from "./TestExplorer/TestKind";
37+
import { isValidWorkspaceFolder, searchForPackages } from "./utilities/workspace";
3738

3839
/**
3940
* Context for whole workspace. Holds array of contexts for each workspace folder
@@ -391,38 +392,19 @@ export class WorkspaceContext implements vscode.Disposable {
391392
* @param folder folder being added
392393
*/
393394
async addWorkspaceFolder(workspaceFolder: vscode.WorkspaceFolder) {
394-
await this.searchForPackages(workspaceFolder.uri, workspaceFolder);
395-
396-
if (this.getActiveWorkspaceFolder(vscode.window.activeTextEditor) === workspaceFolder) {
397-
await this.focusTextEditor(vscode.window.activeTextEditor);
398-
}
399-
}
395+
const folders = await searchForPackages(
396+
workspaceFolder.uri,
397+
configuration.disableSwiftPMIntegration,
398+
configuration.folder(workspaceFolder).searchSubfoldersForPackages
399+
);
400400

401-
async searchForPackages(folder: vscode.Uri, workspaceFolder: vscode.WorkspaceFolder) {
402-
// add folder if Package.swift/compile_commands.json/compile_flags.txt/buildServer.json exists
403-
if (await this.isValidWorkspaceFolder(folder.fsPath)) {
401+
for (const folder of folders) {
404402
await this.addPackageFolder(folder, workspaceFolder);
405-
return;
406-
}
407-
// should I search sub-folders for more Swift Packages
408-
if (!configuration.folder(workspaceFolder).searchSubfoldersForPackages) {
409-
return;
410403
}
411404

412-
await vscode.workspace.fs.readDirectory(folder).then(async entries => {
413-
for (const entry of entries) {
414-
if (
415-
entry[1] === vscode.FileType.Directory &&
416-
entry[0][0] !== "." &&
417-
entry[0] !== "Packages"
418-
) {
419-
await this.searchForPackages(
420-
vscode.Uri.joinPath(folder, entry[0]),
421-
workspaceFolder
422-
);
423-
}
424-
}
425-
});
405+
if (this.getActiveWorkspaceFolder(vscode.window.activeTextEditor) === workspaceFolder) {
406+
await this.focusTextEditor(vscode.window.activeTextEditor);
407+
}
426408
}
427409

428410
public async addPackageFolder(
@@ -597,13 +579,7 @@ export class WorkspaceContext implements vscode.Disposable {
597579
* Package.swift or a CMake compile_commands.json, compile_flags.txt, or a BSP buildServer.json.
598580
*/
599581
async isValidWorkspaceFolder(folder: string): Promise<boolean> {
600-
return (
601-
((await pathExists(folder, "Package.swift")) &&
602-
!configuration.disableSwiftPMIntegration) ||
603-
(await pathExists(folder, "compile_commands.json")) ||
604-
(await pathExists(folder, "compile_flags.txt")) ||
605-
(await pathExists(folder, "buildServer.json"))
606-
);
582+
return await isValidWorkspaceFolder(folder, configuration.disableSwiftPMIntegration);
607583
}
608584

609585
/** send unfocus event to current focussed folder and clear current folder */

src/utilities/workspace.ts

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the VS Code Swift open source project
4+
//
5+
// Copyright (c) 2022 the VS Code Swift project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of VS Code Swift project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
import * as vscode from "vscode";
16+
import { pathExists } from "./filesystem";
17+
18+
export async function searchForPackages(
19+
folder: vscode.Uri,
20+
disableSwiftPMIntegration: boolean,
21+
searchSubfoldersForPackages: boolean
22+
): Promise<Array<vscode.Uri>> {
23+
const folders: Array<vscode.Uri> = [];
24+
25+
async function search(folder: vscode.Uri) {
26+
// add folder if Package.swift/compile_commands.json/compile_flags.txt/buildServer.json exists
27+
if (await isValidWorkspaceFolder(folder.fsPath, disableSwiftPMIntegration)) {
28+
folders.push(folder);
29+
}
30+
// should I search sub-folders for more Swift Packages
31+
if (!searchSubfoldersForPackages) {
32+
return;
33+
}
34+
35+
await vscode.workspace.fs.readDirectory(folder).then(async entries => {
36+
for (const entry of entries) {
37+
if (
38+
entry[1] === vscode.FileType.Directory &&
39+
entry[0][0] !== "." &&
40+
entry[0] !== "Packages"
41+
) {
42+
await search(vscode.Uri.joinPath(folder, entry[0]));
43+
}
44+
}
45+
});
46+
}
47+
48+
await search(folder);
49+
50+
return folders;
51+
}
52+
53+
export async function isValidWorkspaceFolder(
54+
folder: string,
55+
disableSwiftPMIntegration: boolean
56+
): Promise<boolean> {
57+
return (
58+
(!disableSwiftPMIntegration && (await pathExists(folder, "Package.swift"))) ||
59+
(await pathExists(folder, "compile_commands.json")) ||
60+
(await pathExists(folder, "compile_flags.txt")) ||
61+
(await pathExists(folder, "buildServer.json"))
62+
);
63+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the VS Code Swift open source project
4+
//
5+
// Copyright (c) 2024 the VS Code Swift project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of VS Code Swift project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
import * as vscode from "vscode";
16+
import { searchForPackages } from "../../../src/utilities/workspace";
17+
import { testAssetUri } from "../../fixtures";
18+
import { expect } from "chai";
19+
20+
suite("Workspace Utilities Unit Test Suite", () => {
21+
suite("searchForPackages", () => {
22+
const packageFolder = testAssetUri("ModularPackage");
23+
const firstModuleFolder = vscode.Uri.joinPath(packageFolder, "Module1");
24+
const secondModuleFolder = vscode.Uri.joinPath(packageFolder, "Module2");
25+
26+
test("returns only root package when search for subpackages disabled", async () => {
27+
const folders = await searchForPackages(packageFolder, false, false);
28+
29+
expect(folders.map(folder => folder.fsPath)).eql([packageFolder.fsPath]);
30+
});
31+
32+
test("returns subpackages when search for subpackages enabled", async () => {
33+
const folders = await searchForPackages(packageFolder, false, true);
34+
35+
expect(folders.map(folder => folder.fsPath).sort()).deep.equal([
36+
packageFolder.fsPath,
37+
firstModuleFolder.fsPath,
38+
secondModuleFolder.fsPath,
39+
]);
40+
});
41+
});
42+
});

0 commit comments

Comments
 (0)