From f9dffe13ea2f2b4d7f18f9fb0d9c6a652c19c7e6 Mon Sep 17 00:00:00 2001 From: wenytang-ms Date: Thu, 21 May 2026 11:24:04 +0800 Subject: [PATCH 1/4] fix: absolute path --- src/controllers/libraryController.ts | 26 +++++++++++++++++++++----- src/views/DragAndDropController.ts | 6 ++++-- test/suite/libraryController.test.ts | 24 ++++++++++++++++++++++++ 3 files changed, 49 insertions(+), 7 deletions(-) create mode 100644 test/suite/libraryController.test.ts diff --git a/src/controllers/libraryController.ts b/src/controllers/libraryController.ts index 237d2afc..5556d1c1 100644 --- a/src/controllers/libraryController.ts +++ b/src/controllers/libraryController.ts @@ -14,6 +14,8 @@ import { Settings } from "../settings"; import { Utility } from "../utility"; import { DataNode } from "../views/dataNode"; +export const WORKSPACE_FOLDER_VARIABLE = "$" + "{workspaceFolder}"; + export class LibraryController implements Disposable { private disposable: Disposable; @@ -48,26 +50,27 @@ export class LibraryController implements Disposable { return; } addLibraryGlobs(await Promise.all(results.map(async (uri: Uri) => { - // keep the param: `includeWorkspaceFolder` to false here - // since the multi-root is not supported well for invisible projects - const uriPath = workspace.asRelativePath(uri, false); + const uriPath = toReferencedLibraryPath(uri, workspaceFolder); const isLibraryFolder = canSelectFolders || isMac && (await fse.stat(uri.fsPath)).isDirectory(); return isLibraryFolder ? uriPath + "/**/*.jar" : uriPath; }))); } public async removeLibrary(removalFsPath: string) { + const workspaceFolder: WorkspaceFolder | undefined = Utility.getDefaultWorkspaceFolder(); + const removalUri = Uri.file(removalFsPath); const setting = Settings.referencedLibraries(); const removedPaths = _.remove(setting.include, (include) => { if (path.isAbsolute(include)) { return Uri.file(include).fsPath === removalFsPath; } else { - return include === workspace.asRelativePath(removalFsPath, false); + return include === workspace.asRelativePath(removalFsPath, false) + || include === toReferencedLibraryPath(removalUri, workspaceFolder); } }); if (removedPaths.length === 0) { // No duplicated item in include array, add it into the exclude field - setting.exclude = updatePatternArray(setting.exclude, workspace.asRelativePath(removalFsPath, false)); + setting.exclude = updatePatternArray(setting.exclude, toReferencedLibraryPath(removalUri, workspaceFolder)); } Settings.updateReferencedLibraries(setting); } @@ -87,6 +90,19 @@ export function addLibraryGlobs(libraryGlobs: string[]) { Settings.updateReferencedLibraries(setting); } +export function toReferencedLibraryPath(uri: Uri, workspaceFolder: WorkspaceFolder | undefined): string { + if (!workspaceFolder) { + return uri.fsPath; + } + + const relativePath = path.relative(workspaceFolder.uri.fsPath, uri.fsPath); + if (relativePath === ".." || relativePath.startsWith(".." + path.sep) || path.isAbsolute(relativePath)) { + return uri.fsPath; + } + + return [WORKSPACE_FOLDER_VARIABLE, relativePath.replace(/\\/g, "/")].filter(Boolean).join("/"); +} + /** * Check if the `update` patterns are already covered by `origin` patterns and return those uncovered */ diff --git a/src/views/DragAndDropController.ts b/src/views/DragAndDropController.ts index 3ef2c0bf..66073cf7 100644 --- a/src/views/DragAndDropController.ts +++ b/src/views/DragAndDropController.ts @@ -16,9 +16,10 @@ import { PackageRootNode } from "./packageRootNode"; import { PrimaryTypeNode } from "./PrimaryTypeNode"; import { ProjectNode } from "./projectNode"; import { WorkspaceNode } from "./workspaceNode"; -import { addLibraryGlobs } from "../controllers/libraryController"; +import { addLibraryGlobs, toReferencedLibraryPath } from "../controllers/libraryController"; import { sendError, sendInfo } from "vscode-extension-telemetry-wrapper"; import { DocumentSymbolNode } from "./documentSymbolNode"; +import { Utility } from "../utility"; export class DragAndDropController implements TreeDragAndDropController { @@ -389,6 +390,7 @@ export class DragAndDropController implements TreeDragAndDropController { + const workspaceFolder = Utility.getDefaultWorkspaceFolder(); const pattern = (await Promise.all(uriStrings.map(async uriString => { try { const uri = Uri.parse(uriString, true /* strict */); @@ -400,7 +402,7 @@ export class DragAndDropController implements TreeDragAndDropController { + + test("Should use workspace folder variable for workspace-local libraries", () => { + const workspaceFolder = workspace.workspaceFolders![0]; + const libraryUri = Uri.file(path.join(workspaceFolder.uri.fsPath, "lib", "foo.jar")); + + assert.strictEqual(toReferencedLibraryPath(libraryUri, workspaceFolder), `${WORKSPACE_FOLDER_VARIABLE}/lib/foo.jar`); + }); + + test("Should keep absolute paths for external libraries", () => { + const workspaceFolder = workspace.workspaceFolders![0]; + const libraryUri = Uri.file(path.resolve(workspaceFolder.uri.fsPath, "..", "foo.jar")); + + assert.strictEqual(toReferencedLibraryPath(libraryUri, workspaceFolder), libraryUri.fsPath); + }); +}); \ No newline at end of file From 84776dc23c3520ccb61d4b03fdcf8f62b80b5a95 Mon Sep 17 00:00:00 2001 From: wenytang-ms Date: Thu, 21 May 2026 11:27:48 +0800 Subject: [PATCH 2/4] fix: update --- src/controllers/libraryController.ts | 21 +++++++++++++++++++-- test/suite/libraryController.test.ts | 19 +++++++++++++++++-- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/src/controllers/libraryController.ts b/src/controllers/libraryController.ts index 5556d1c1..0dd56ed0 100644 --- a/src/controllers/libraryController.ts +++ b/src/controllers/libraryController.ts @@ -3,7 +3,7 @@ import * as fse from "fs-extra"; import * as _ from "lodash"; -import * as minimatch from "minimatch"; +import minimatch = require("minimatch"); import { platform } from "os"; import * as path from "path"; import { Disposable, ExtensionContext, Uri, window, workspace, WorkspaceFolder } from "vscode"; @@ -70,7 +70,7 @@ export class LibraryController implements Disposable { }); if (removedPaths.length === 0) { // No duplicated item in include array, add it into the exclude field - setting.exclude = updatePatternArray(setting.exclude, toReferencedLibraryPath(removalUri, workspaceFolder)); + setting.exclude = updatePatternArray(setting.exclude, toReferencedLibraryExcludePath(removalUri, workspaceFolder, setting.include)); } Settings.updateReferencedLibraries(setting); } @@ -103,6 +103,23 @@ export function toReferencedLibraryPath(uri: Uri, workspaceFolder: WorkspaceFold return [WORKSPACE_FOLDER_VARIABLE, relativePath.replace(/\\/g, "/")].filter(Boolean).join("/"); } +export function toReferencedLibraryExcludePath(uri: Uri, workspaceFolder: WorkspaceFolder | undefined, includes: string[]): string { + const relativePath = workspace.asRelativePath(uri, false); + const workspaceVariablePath = toReferencedLibraryPath(uri, workspaceFolder); + + for (const include of includes) { + if (include.startsWith(WORKSPACE_FOLDER_VARIABLE) && minimatch(workspaceVariablePath, include)) { + return workspaceVariablePath; + } + + if (!path.isAbsolute(include) && !include.startsWith(WORKSPACE_FOLDER_VARIABLE) && minimatch(relativePath, include)) { + return relativePath; + } + } + + return workspaceVariablePath; +} + /** * Check if the `update` patterns are already covered by `origin` patterns and return those uncovered */ diff --git a/test/suite/libraryController.test.ts b/test/suite/libraryController.test.ts index 12ce7408..26978433 100644 --- a/test/suite/libraryController.test.ts +++ b/test/suite/libraryController.test.ts @@ -4,7 +4,7 @@ import * as assert from "assert"; import * as path from "path"; import { Uri, workspace } from "vscode"; -import { toReferencedLibraryPath, WORKSPACE_FOLDER_VARIABLE } from "../../src/controllers/libraryController"; +import { toReferencedLibraryExcludePath, toReferencedLibraryPath, WORKSPACE_FOLDER_VARIABLE } from "../../src/controllers/libraryController"; suite("Library Controller Tests", () => { @@ -21,4 +21,19 @@ suite("Library Controller Tests", () => { assert.strictEqual(toReferencedLibraryPath(libraryUri, workspaceFolder), libraryUri.fsPath); }); -}); \ No newline at end of file + + test("Should use relative exclude path for relative include patterns", () => { + const workspaceFolder = workspace.workspaceFolders![0]; + const libraryUri = Uri.file(path.join(workspaceFolder.uri.fsPath, "lib", "foo.jar")); + + assert.strictEqual(toReferencedLibraryExcludePath(libraryUri, workspaceFolder, ["lib/**/*.jar"]), "lib/foo.jar"); + }); + + test("Should use workspace folder variable exclude path for workspace folder variable include patterns", () => { + const workspaceFolder = workspace.workspaceFolders![0]; + const libraryUri = Uri.file(path.join(workspaceFolder.uri.fsPath, "lib", "foo.jar")); + const include = `${WORKSPACE_FOLDER_VARIABLE}/lib/**/*.jar`; + + assert.strictEqual(toReferencedLibraryExcludePath(libraryUri, workspaceFolder, [include]), `${WORKSPACE_FOLDER_VARIABLE}/lib/foo.jar`); + }); +}); From c4e46cff118f2ba9231ad8cbba2320de946b72ab Mon Sep 17 00:00:00 2001 From: wenytang-ms Date: Thu, 21 May 2026 13:07:23 +0800 Subject: [PATCH 3/4] fix: copilot comments --- extension.bundle.ts | 1 + src/controllers/libraryController.ts | 7 ++++--- test/suite/libraryController.test.ts | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/extension.bundle.ts b/extension.bundle.ts index e07cf41e..7a79e970 100644 --- a/extension.bundle.ts +++ b/extension.bundle.ts @@ -19,6 +19,7 @@ export { IMainClassInfo } from "./src/tasks/buildArtifact/ResolveMainClassExecut // project view test export { contextManager } from "./src/contextManager"; +export { toReferencedLibraryExcludePath, toReferencedLibraryPath, WORKSPACE_FOLDER_VARIABLE } from "./src/controllers/libraryController"; export { DependencyExplorer } from "./src/views/dependencyExplorer"; export { Commands } from "./src/commands"; export { LanguageServerMode } from "./src/languageServerApi/LanguageServerMode"; diff --git a/src/controllers/libraryController.ts b/src/controllers/libraryController.ts index 0dd56ed0..0b957af4 100644 --- a/src/controllers/libraryController.ts +++ b/src/controllers/libraryController.ts @@ -15,6 +15,7 @@ import { Utility } from "../utility"; import { DataNode } from "../views/dataNode"; export const WORKSPACE_FOLDER_VARIABLE = "$" + "{workspaceFolder}"; +const MINIMATCH_OPTIONS: minimatch.IOptions = { nobrace: true }; export class LibraryController implements Disposable { @@ -108,11 +109,11 @@ export function toReferencedLibraryExcludePath(uri: Uri, workspaceFolder: Worksp const workspaceVariablePath = toReferencedLibraryPath(uri, workspaceFolder); for (const include of includes) { - if (include.startsWith(WORKSPACE_FOLDER_VARIABLE) && minimatch(workspaceVariablePath, include)) { + if (include.startsWith(WORKSPACE_FOLDER_VARIABLE) && minimatch(workspaceVariablePath, include, MINIMATCH_OPTIONS)) { return workspaceVariablePath; } - if (!path.isAbsolute(include) && !include.startsWith(WORKSPACE_FOLDER_VARIABLE) && minimatch(relativePath, include)) { + if (!path.isAbsolute(include) && !include.startsWith(WORKSPACE_FOLDER_VARIABLE) && minimatch(relativePath, include, MINIMATCH_OPTIONS)) { return relativePath; } } @@ -126,7 +127,7 @@ export function toReferencedLibraryExcludePath(uri: Uri, workspaceFolder: Worksp function dedupAlreadyCoveredPattern(origin: string[], ...update: string[]): string[] { return update.filter((newPattern) => { return !origin.some((originPattern) => { - return minimatch(newPattern, originPattern); + return minimatch(newPattern, originPattern, MINIMATCH_OPTIONS); }); }); } diff --git a/test/suite/libraryController.test.ts b/test/suite/libraryController.test.ts index 26978433..a754cf86 100644 --- a/test/suite/libraryController.test.ts +++ b/test/suite/libraryController.test.ts @@ -4,7 +4,7 @@ import * as assert from "assert"; import * as path from "path"; import { Uri, workspace } from "vscode"; -import { toReferencedLibraryExcludePath, toReferencedLibraryPath, WORKSPACE_FOLDER_VARIABLE } from "../../src/controllers/libraryController"; +import { toReferencedLibraryExcludePath, toReferencedLibraryPath, WORKSPACE_FOLDER_VARIABLE } from "../../extension.bundle"; suite("Library Controller Tests", () => { From 6fef0d44a0b47bc329796181744fdd71dc2c22df Mon Sep 17 00:00:00 2001 From: wenytang-ms Date: Thu, 21 May 2026 13:53:46 +0800 Subject: [PATCH 4/4] fix: update --- extension.bundle.ts | 1 - src/controllers/libraryController.ts | 48 ++++------------------------ src/views/DragAndDropController.ts | 6 ++-- test/suite/libraryController.test.ts | 39 ---------------------- 4 files changed, 9 insertions(+), 85 deletions(-) delete mode 100644 test/suite/libraryController.test.ts diff --git a/extension.bundle.ts b/extension.bundle.ts index 7a79e970..e07cf41e 100644 --- a/extension.bundle.ts +++ b/extension.bundle.ts @@ -19,7 +19,6 @@ export { IMainClassInfo } from "./src/tasks/buildArtifact/ResolveMainClassExecut // project view test export { contextManager } from "./src/contextManager"; -export { toReferencedLibraryExcludePath, toReferencedLibraryPath, WORKSPACE_FOLDER_VARIABLE } from "./src/controllers/libraryController"; export { DependencyExplorer } from "./src/views/dependencyExplorer"; export { Commands } from "./src/commands"; export { LanguageServerMode } from "./src/languageServerApi/LanguageServerMode"; diff --git a/src/controllers/libraryController.ts b/src/controllers/libraryController.ts index 0b957af4..237d2afc 100644 --- a/src/controllers/libraryController.ts +++ b/src/controllers/libraryController.ts @@ -3,7 +3,7 @@ import * as fse from "fs-extra"; import * as _ from "lodash"; -import minimatch = require("minimatch"); +import * as minimatch from "minimatch"; import { platform } from "os"; import * as path from "path"; import { Disposable, ExtensionContext, Uri, window, workspace, WorkspaceFolder } from "vscode"; @@ -14,9 +14,6 @@ import { Settings } from "../settings"; import { Utility } from "../utility"; import { DataNode } from "../views/dataNode"; -export const WORKSPACE_FOLDER_VARIABLE = "$" + "{workspaceFolder}"; -const MINIMATCH_OPTIONS: minimatch.IOptions = { nobrace: true }; - export class LibraryController implements Disposable { private disposable: Disposable; @@ -51,27 +48,26 @@ export class LibraryController implements Disposable { return; } addLibraryGlobs(await Promise.all(results.map(async (uri: Uri) => { - const uriPath = toReferencedLibraryPath(uri, workspaceFolder); + // keep the param: `includeWorkspaceFolder` to false here + // since the multi-root is not supported well for invisible projects + const uriPath = workspace.asRelativePath(uri, false); const isLibraryFolder = canSelectFolders || isMac && (await fse.stat(uri.fsPath)).isDirectory(); return isLibraryFolder ? uriPath + "/**/*.jar" : uriPath; }))); } public async removeLibrary(removalFsPath: string) { - const workspaceFolder: WorkspaceFolder | undefined = Utility.getDefaultWorkspaceFolder(); - const removalUri = Uri.file(removalFsPath); const setting = Settings.referencedLibraries(); const removedPaths = _.remove(setting.include, (include) => { if (path.isAbsolute(include)) { return Uri.file(include).fsPath === removalFsPath; } else { - return include === workspace.asRelativePath(removalFsPath, false) - || include === toReferencedLibraryPath(removalUri, workspaceFolder); + return include === workspace.asRelativePath(removalFsPath, false); } }); if (removedPaths.length === 0) { // No duplicated item in include array, add it into the exclude field - setting.exclude = updatePatternArray(setting.exclude, toReferencedLibraryExcludePath(removalUri, workspaceFolder, setting.include)); + setting.exclude = updatePatternArray(setting.exclude, workspace.asRelativePath(removalFsPath, false)); } Settings.updateReferencedLibraries(setting); } @@ -91,43 +87,13 @@ export function addLibraryGlobs(libraryGlobs: string[]) { Settings.updateReferencedLibraries(setting); } -export function toReferencedLibraryPath(uri: Uri, workspaceFolder: WorkspaceFolder | undefined): string { - if (!workspaceFolder) { - return uri.fsPath; - } - - const relativePath = path.relative(workspaceFolder.uri.fsPath, uri.fsPath); - if (relativePath === ".." || relativePath.startsWith(".." + path.sep) || path.isAbsolute(relativePath)) { - return uri.fsPath; - } - - return [WORKSPACE_FOLDER_VARIABLE, relativePath.replace(/\\/g, "/")].filter(Boolean).join("/"); -} - -export function toReferencedLibraryExcludePath(uri: Uri, workspaceFolder: WorkspaceFolder | undefined, includes: string[]): string { - const relativePath = workspace.asRelativePath(uri, false); - const workspaceVariablePath = toReferencedLibraryPath(uri, workspaceFolder); - - for (const include of includes) { - if (include.startsWith(WORKSPACE_FOLDER_VARIABLE) && minimatch(workspaceVariablePath, include, MINIMATCH_OPTIONS)) { - return workspaceVariablePath; - } - - if (!path.isAbsolute(include) && !include.startsWith(WORKSPACE_FOLDER_VARIABLE) && minimatch(relativePath, include, MINIMATCH_OPTIONS)) { - return relativePath; - } - } - - return workspaceVariablePath; -} - /** * Check if the `update` patterns are already covered by `origin` patterns and return those uncovered */ function dedupAlreadyCoveredPattern(origin: string[], ...update: string[]): string[] { return update.filter((newPattern) => { return !origin.some((originPattern) => { - return minimatch(newPattern, originPattern, MINIMATCH_OPTIONS); + return minimatch(newPattern, originPattern); }); }); } diff --git a/src/views/DragAndDropController.ts b/src/views/DragAndDropController.ts index 66073cf7..3ef2c0bf 100644 --- a/src/views/DragAndDropController.ts +++ b/src/views/DragAndDropController.ts @@ -16,10 +16,9 @@ import { PackageRootNode } from "./packageRootNode"; import { PrimaryTypeNode } from "./PrimaryTypeNode"; import { ProjectNode } from "./projectNode"; import { WorkspaceNode } from "./workspaceNode"; -import { addLibraryGlobs, toReferencedLibraryPath } from "../controllers/libraryController"; +import { addLibraryGlobs } from "../controllers/libraryController"; import { sendError, sendInfo } from "vscode-extension-telemetry-wrapper"; import { DocumentSymbolNode } from "./documentSymbolNode"; -import { Utility } from "../utility"; export class DragAndDropController implements TreeDragAndDropController { @@ -390,7 +389,6 @@ export class DragAndDropController implements TreeDragAndDropController { - const workspaceFolder = Utility.getDefaultWorkspaceFolder(); const pattern = (await Promise.all(uriStrings.map(async uriString => { try { const uri = Uri.parse(uriString, true /* strict */); @@ -402,7 +400,7 @@ export class DragAndDropController implements TreeDragAndDropController { - - test("Should use workspace folder variable for workspace-local libraries", () => { - const workspaceFolder = workspace.workspaceFolders![0]; - const libraryUri = Uri.file(path.join(workspaceFolder.uri.fsPath, "lib", "foo.jar")); - - assert.strictEqual(toReferencedLibraryPath(libraryUri, workspaceFolder), `${WORKSPACE_FOLDER_VARIABLE}/lib/foo.jar`); - }); - - test("Should keep absolute paths for external libraries", () => { - const workspaceFolder = workspace.workspaceFolders![0]; - const libraryUri = Uri.file(path.resolve(workspaceFolder.uri.fsPath, "..", "foo.jar")); - - assert.strictEqual(toReferencedLibraryPath(libraryUri, workspaceFolder), libraryUri.fsPath); - }); - - test("Should use relative exclude path for relative include patterns", () => { - const workspaceFolder = workspace.workspaceFolders![0]; - const libraryUri = Uri.file(path.join(workspaceFolder.uri.fsPath, "lib", "foo.jar")); - - assert.strictEqual(toReferencedLibraryExcludePath(libraryUri, workspaceFolder, ["lib/**/*.jar"]), "lib/foo.jar"); - }); - - test("Should use workspace folder variable exclude path for workspace folder variable include patterns", () => { - const workspaceFolder = workspace.workspaceFolders![0]; - const libraryUri = Uri.file(path.join(workspaceFolder.uri.fsPath, "lib", "foo.jar")); - const include = `${WORKSPACE_FOLDER_VARIABLE}/lib/**/*.jar`; - - assert.strictEqual(toReferencedLibraryExcludePath(libraryUri, workspaceFolder, [include]), `${WORKSPACE_FOLDER_VARIABLE}/lib/foo.jar`); - }); -});