From 25cb1a28deb3cb2f5771927fa07a9e04eb7c4bca Mon Sep 17 00:00:00 2001 From: Frederik Claus Date: Sat, 23 Sep 2023 16:16:45 +0200 Subject: [PATCH 1/7] Fixes java.getPackageData for modules with long name Fixes #788 Signed-off-by: Frederik Claus --- .vscode/launch.json | 22 +- .../jdtls/ext/core/PackageCommand.java | 32 +- package-lock.json | 156 ++++---- package.json | 6 +- src/java/jdtls.ts | 8 +- .../BuildArtifactTaskProvider.ts | 4 +- src/views/nodeFactory.ts | 2 +- test/index.ts | 12 + test/maven-suite/projectView.test.ts | 18 +- test/maven/.vscode/settings.json | 13 + test/multi-module-suite/index.ts | 41 +++ test/multi-module-suite/projectView.test.ts | 25 ++ test/multi-module/.vscode/settings.json | 4 + test/multi-module/level1/pom.xml | 21 ++ test/multi-module/pom.xml | 12 + .../src/main/java/fvclaus/App.java | 13 + .../src/test/java/fvclaus/AppTest.java | 38 ++ test/shared.ts | 15 +- test/ui/command.test.ts | 336 +++++++++++------- webpack.config.js | 1 + 20 files changed, 539 insertions(+), 240 deletions(-) create mode 100644 test/multi-module-suite/index.ts create mode 100644 test/multi-module-suite/projectView.test.ts create mode 100644 test/multi-module/.vscode/settings.json create mode 100644 test/multi-module/level1/pom.xml create mode 100644 test/multi-module/pom.xml create mode 100644 test/multi-module/src/main/java/fvclaus/App.java create mode 100644 test/multi-module/src/test/java/fvclaus/AppTest.java diff --git a/.vscode/launch.json b/.vscode/launch.json index 2f331a2d..98391691 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -92,6 +92,20 @@ "outFiles": [ "${workspaceFolder}/dist/**/*.js" ], "preLaunchTask": "npm: compile" }, + { + "name": "Extension Tests - Multi Module Project", + "type": "extensionHost", + "request": "launch", + "runtimeExecutable": "${execPath}", + "args": [ + "${workspaceFolder}/test/multi-module/", + "--extensionDevelopmentPath=${workspaceFolder}", + "--extensionTestsPath=${workspaceFolder}/dist/test/multi-module-suite/index" + ], + "sourceMaps": true, + "outFiles": [ "${workspaceFolder}/dist/**/*.js" ], + "preLaunchTask": "npm: compile" + }, { "name": "Debug UI Command Tests", "type": "node", @@ -99,11 +113,15 @@ "program": "${workspaceFolder}/node_modules/vscode-extension-tester/out/cli.js", "args": [ "setup-and-run", + // If not set, will use the current version of vscode. Find a way not to hardcode this value. + "--code_version=1.77.0", "${workspaceFolder}/dist/test/ui/command.test.js", ], + // To debug the test code, you must set --mode=development inside the vscode:prepublish task. Find a better way to do this. + "sourceMaps": true, + "outFiles": [ "${workspaceFolder}/dist/**/*.js" ], "console": "integratedTerminal", - "internalConsoleOptions": "neverOpen", - "preLaunchTask": "npm: compile" + // No need to compile the code, vscode:prepublish task that compiles the code is run by vscode-extension-tester }, ] } diff --git a/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/PackageCommand.java b/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/PackageCommand.java index 68088c90..ef183ab4 100644 --- a/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/PackageCommand.java +++ b/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/PackageCommand.java @@ -19,11 +19,13 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.function.BiFunction; import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; +import org.eclipse.core.internal.utils.FileUtil; import org.eclipse.core.resources.IContainer; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IFolder; @@ -577,26 +579,20 @@ private static Object[] findJarDirectoryChildren(JarEntryDirectory directory, St public static IProject getProject(String projectUri) { IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot(); - IContainer[] containers = root.findContainersForLocationURI(JDTUtils.toURI(projectUri)); - - if (containers.length == 0) { - return null; - } - - // For multi-module scenario, findContainersForLocationURI API may return a container array, - // put the result from the nearest project in front. - Arrays.sort(containers, (Comparator) (IContainer a, IContainer b) -> { - return a.getFullPath().toPortableString().length() - b.getFullPath().toPortableString().length(); - }); - - for (IContainer container : containers) { - IProject project = container.getProject(); - if (!project.exists()) { - return null; + URI uri = JDTUtils.toURI(projectUri); + IContainer[] containers = root.findContainersForLocationURI(uri); + + Optional maybeProject = Arrays.stream(containers).filter(container -> container instanceof IProject).findFirst(); + if (maybeProject.isPresent()) { + return (IProject) maybeProject.get(); + } else { + String invisibleProjectName = ProjectUtils.getWorkspaceInvisibleProjectName(FileUtil.toPath(uri).removeTrailingSeparator()); + IProject invisibleProject = root.getProject(invisibleProjectName); + if (!invisibleProject.exists()) { + throw new IllegalArgumentException(projectUri + " is neither a Java nor an invisible project."); } - return project; + return invisibleProject; } - return null; } public static IJavaProject getJavaProject(String projectUri) { diff --git a/package-lock.json b/package-lock.json index f6d6c3c8..0d1a48fa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -34,7 +34,7 @@ "ts-loader": "^9.4.2", "tslint": "^6.1.3", "typescript": "^4.9.4", - "vscode-extension-tester": "^5.5.2", + "vscode-extension-tester": "^5.9.1", "webpack": "^5.76.0", "webpack-cli": "^4.10.0" }, @@ -467,15 +467,15 @@ } }, "node_modules/@vscode/vsce": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/@vscode/vsce/-/vsce-2.19.0.tgz", - "integrity": "sha512-dAlILxC5ggOutcvJY24jxz913wimGiUrHaPkk16Gm9/PGFbz1YezWtrXsTKUtJws4fIlpX2UIlVlVESWq8lkfQ==", + "version": "2.21.0", + "resolved": "https://registry.npmjs.org/@vscode/vsce/-/vsce-2.21.0.tgz", + "integrity": "sha512-KuxYqScqUY/duJbkj9eE2tN2X/WJoGAy54hHtxT3ZBkM6IzrOg7H7CXGUPBxNlmqku2w/cAjOUSrgIHlzz0mbA==", "dev": true, "dependencies": { "azure-devops-node-api": "^11.0.1", "chalk": "^2.4.2", "cheerio": "^1.0.0-rc.9", - "commander": "^6.1.0", + "commander": "^6.2.1", "glob": "^7.0.6", "hosted-git-info": "^4.0.2", "jsonc-parser": "^3.2.0", @@ -485,7 +485,7 @@ "minimatch": "^3.0.3", "parse-semver": "^1.1.1", "read": "^1.0.7", - "semver": "^5.1.0", + "semver": "^7.5.2", "tmp": "^0.2.1", "typed-rest-client": "^1.8.4", "url-join": "^4.0.1", @@ -593,15 +593,6 @@ "node": "*" } }, - "node_modules/@vscode/vsce/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, "node_modules/@vscode/vsce/node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -1500,9 +1491,9 @@ "dev": true }, "node_modules/compare-versions": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-5.0.3.tgz", - "integrity": "sha512-4UZlZP8Z99MGEY+Ovg/uJxJuvoXuN4M6B3hKaiackiHrgzQFEe3diJi1mf1PNHbFujM7FvLrK2bpgIaImbtZ1A==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-6.1.0.tgz", + "integrity": "sha512-LNZQXhqUvqUTotpZ00qLSaify3b4VFD588aRr8MKFw4CMUr98ytzCW5wDH5qx/DEY5kCDXcbcRuCqL0szEf2tg==", "dev": true }, "node_modules/concat-map": { @@ -3179,14 +3170,14 @@ } }, "node_modules/monaco-page-objects": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/monaco-page-objects/-/monaco-page-objects-3.8.0.tgz", - "integrity": "sha512-/bsrTHul7KsZ1SmdQFHIdVZTU2Nr+odGpxZAJddWxanX1uEAreqE4H758wI9Ie3mZLFO798lPjv2zaUAi+kDFw==", + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/monaco-page-objects/-/monaco-page-objects-3.9.1.tgz", + "integrity": "sha512-Y/haEkl0U5MPH2krk6hyOpSqUdzqY9VIW4NPz48i6QHP1hSCInW2ffXl1zisStv0vBWBrDr8hu5n6Q1i7z99PQ==", "dev": true, "dependencies": { "clipboardy": "^3.0.0", "clone-deep": "^4.0.1", - "compare-versions": "^5.0.3", + "compare-versions": "^6.1.0", "fs-extra": "^11.1.1", "ts-essentials": "^9.3.2" }, @@ -3247,9 +3238,9 @@ "dev": true }, "node_modules/node-abi": { - "version": "3.45.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.45.0.tgz", - "integrity": "sha512-iwXuFrMAcFVi/ZoZiqq8BzAdsLw9kxDfTC0HMyjXfSL/6CSDAGD5UmR7azrAgWV1zKYq7dUUMj4owusBWKLsiQ==", + "version": "3.47.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.47.0.tgz", + "integrity": "sha512-2s6B2CWZM//kPgwnuI0KrYwNjfdByE25zvAaEpq9IH4zcNsarH8Ihu/UuX6XMPEogDAxkuUFeZn60pXNHAqn3A==", "dev": true, "optional": true, "dependencies": { @@ -4455,12 +4446,17 @@ } }, "node_modules/ts-essentials": { - "version": "9.3.2", - "resolved": "https://registry.npmjs.org/ts-essentials/-/ts-essentials-9.3.2.tgz", - "integrity": "sha512-JxKJzuWqH1MmH4ZFHtJzGEhkfN3QvVR3C3w+4BIoWeoY68UVVoA2Np/Bca9z0IPSErVCWhv439aT0We4Dks8kQ==", + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/ts-essentials/-/ts-essentials-9.4.0.tgz", + "integrity": "sha512-s4BzWZmTh926caZO7XF7MMbwCn1BioT3s3r9hT8ARnwW//30OD0XioEsMyq3ORAHP/deN4Zkst2ZvxXmL+tG6g==", "dev": true, "peerDependencies": { "typescript": ">=4.1.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, "node_modules/ts-loader": { @@ -4825,26 +4821,26 @@ } }, "node_modules/vscode-extension-tester": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/vscode-extension-tester/-/vscode-extension-tester-5.8.0.tgz", - "integrity": "sha512-DMOSmxXnrREWqLXYYq+Au53n6/BAVbKbOPxAm7bmpuvMCyF6lKJk9olEsbbVMTugkIToLaafsh4LSq47bTgeHQ==", + "version": "5.9.1", + "resolved": "https://registry.npmjs.org/vscode-extension-tester/-/vscode-extension-tester-5.9.1.tgz", + "integrity": "sha512-dWUUZWiLbjch9yXrdd1GH/LdjZodn4FbNDraffFyON1qEIVio0Z3hwUunKmLBNFQWRseCJfFs5peBqoF5bGo0g==", "dev": true, "dependencies": { - "@types/selenium-webdriver": "^4.1.12", - "@vscode/vsce": "^2.19.0", + "@types/selenium-webdriver": "^4.1.15", + "@vscode/vsce": "^2.20.1", "commander": "^11.0.0", - "compare-versions": "^5.0.1", + "compare-versions": "^6.1.0", "fs-extra": "^11.1.0", "glob": "^8.1.0", "got": "^13.0.0", "hpagent": "^1.2.0", "js-yaml": "^4.1.0", - "monaco-page-objects": "^3.8.0", + "monaco-page-objects": "^3.9.1", "sanitize-filename": "^1.6.3", - "selenium-webdriver": "^4.10.0", + "selenium-webdriver": "4.10.0", "targz": "^1.0.1", "unzipper": "^0.10.14", - "vscode-extension-tester-locators": "^3.6.0" + "vscode-extension-tester-locators": "^3.7.1" }, "bin": { "extest": "out/cli.js" @@ -4855,12 +4851,12 @@ } }, "node_modules/vscode-extension-tester-locators": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/vscode-extension-tester-locators/-/vscode-extension-tester-locators-3.6.0.tgz", - "integrity": "sha512-RjQf5XL33dJORl9ck5X0vhAHf93TOONPE4nb9nai/pDM15nYcD1TqMyzRGl/XxHpvBu/bP8MV/bHfCT6b8w2cQ==", + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/vscode-extension-tester-locators/-/vscode-extension-tester-locators-3.7.1.tgz", + "integrity": "sha512-My+ZTH2PM0g9JwWw49E+SfBMFLSjBdv6YpjekUFpzhCWK0mH+BjRC0gtINr+x2+KuVZVhsO82oSivBa1d7ZJvg==", "dev": true, "peerDependencies": { - "monaco-page-objects": "^3.8.0", + "monaco-page-objects": "^3.9.1", "selenium-webdriver": "^4.6.1" } }, @@ -5155,9 +5151,9 @@ "dev": true }, "node_modules/ws": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", - "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", + "version": "8.14.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz", + "integrity": "sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==", "dev": true, "engines": { "node": ">=10.0.0" @@ -5656,15 +5652,15 @@ } }, "@vscode/vsce": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/@vscode/vsce/-/vsce-2.19.0.tgz", - "integrity": "sha512-dAlILxC5ggOutcvJY24jxz913wimGiUrHaPkk16Gm9/PGFbz1YezWtrXsTKUtJws4fIlpX2UIlVlVESWq8lkfQ==", + "version": "2.21.0", + "resolved": "https://registry.npmjs.org/@vscode/vsce/-/vsce-2.21.0.tgz", + "integrity": "sha512-KuxYqScqUY/duJbkj9eE2tN2X/WJoGAy54hHtxT3ZBkM6IzrOg7H7CXGUPBxNlmqku2w/cAjOUSrgIHlzz0mbA==", "dev": true, "requires": { "azure-devops-node-api": "^11.0.1", "chalk": "^2.4.2", "cheerio": "^1.0.0-rc.9", - "commander": "^6.1.0", + "commander": "^6.2.1", "glob": "^7.0.6", "hosted-git-info": "^4.0.2", "jsonc-parser": "^3.2.0", @@ -5675,7 +5671,7 @@ "minimatch": "^3.0.3", "parse-semver": "^1.1.1", "read": "^1.0.7", - "semver": "^5.1.0", + "semver": "^7.5.2", "tmp": "^0.2.1", "typed-rest-client": "^1.8.4", "url-join": "^4.0.1", @@ -5756,12 +5752,6 @@ "brace-expansion": "^1.1.7" } }, - "semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true - }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -6443,9 +6433,9 @@ "dev": true }, "compare-versions": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-5.0.3.tgz", - "integrity": "sha512-4UZlZP8Z99MGEY+Ovg/uJxJuvoXuN4M6B3hKaiackiHrgzQFEe3diJi1mf1PNHbFujM7FvLrK2bpgIaImbtZ1A==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-6.1.0.tgz", + "integrity": "sha512-LNZQXhqUvqUTotpZ00qLSaify3b4VFD588aRr8MKFw4CMUr98ytzCW5wDH5qx/DEY5kCDXcbcRuCqL0szEf2tg==", "dev": true }, "concat-map": { @@ -7673,14 +7663,14 @@ } }, "monaco-page-objects": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/monaco-page-objects/-/monaco-page-objects-3.8.0.tgz", - "integrity": "sha512-/bsrTHul7KsZ1SmdQFHIdVZTU2Nr+odGpxZAJddWxanX1uEAreqE4H758wI9Ie3mZLFO798lPjv2zaUAi+kDFw==", + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/monaco-page-objects/-/monaco-page-objects-3.9.1.tgz", + "integrity": "sha512-Y/haEkl0U5MPH2krk6hyOpSqUdzqY9VIW4NPz48i6QHP1hSCInW2ffXl1zisStv0vBWBrDr8hu5n6Q1i7z99PQ==", "dev": true, "requires": { "clipboardy": "^3.0.0", "clone-deep": "^4.0.1", - "compare-versions": "^5.0.3", + "compare-versions": "^6.1.0", "fs-extra": "^11.1.1", "ts-essentials": "^9.3.2" }, @@ -7730,9 +7720,9 @@ "dev": true }, "node-abi": { - "version": "3.45.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.45.0.tgz", - "integrity": "sha512-iwXuFrMAcFVi/ZoZiqq8BzAdsLw9kxDfTC0HMyjXfSL/6CSDAGD5UmR7azrAgWV1zKYq7dUUMj4owusBWKLsiQ==", + "version": "3.47.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.47.0.tgz", + "integrity": "sha512-2s6B2CWZM//kPgwnuI0KrYwNjfdByE25zvAaEpq9IH4zcNsarH8Ihu/UuX6XMPEogDAxkuUFeZn60pXNHAqn3A==", "dev": true, "optional": true, "requires": { @@ -8609,9 +8599,9 @@ } }, "ts-essentials": { - "version": "9.3.2", - "resolved": "https://registry.npmjs.org/ts-essentials/-/ts-essentials-9.3.2.tgz", - "integrity": "sha512-JxKJzuWqH1MmH4ZFHtJzGEhkfN3QvVR3C3w+4BIoWeoY68UVVoA2Np/Bca9z0IPSErVCWhv439aT0We4Dks8kQ==", + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/ts-essentials/-/ts-essentials-9.4.0.tgz", + "integrity": "sha512-s4BzWZmTh926caZO7XF7MMbwCn1BioT3s3r9hT8ARnwW//30OD0XioEsMyq3ORAHP/deN4Zkst2ZvxXmL+tG6g==", "dev": true, "requires": {} }, @@ -8890,26 +8880,26 @@ } }, "vscode-extension-tester": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/vscode-extension-tester/-/vscode-extension-tester-5.8.0.tgz", - "integrity": "sha512-DMOSmxXnrREWqLXYYq+Au53n6/BAVbKbOPxAm7bmpuvMCyF6lKJk9olEsbbVMTugkIToLaafsh4LSq47bTgeHQ==", + "version": "5.9.1", + "resolved": "https://registry.npmjs.org/vscode-extension-tester/-/vscode-extension-tester-5.9.1.tgz", + "integrity": "sha512-dWUUZWiLbjch9yXrdd1GH/LdjZodn4FbNDraffFyON1qEIVio0Z3hwUunKmLBNFQWRseCJfFs5peBqoF5bGo0g==", "dev": true, "requires": { - "@types/selenium-webdriver": "^4.1.12", - "@vscode/vsce": "^2.19.0", + "@types/selenium-webdriver": "^4.1.15", + "@vscode/vsce": "^2.20.1", "commander": "^11.0.0", - "compare-versions": "^5.0.1", + "compare-versions": "^6.1.0", "fs-extra": "^11.1.0", "glob": "^8.1.0", "got": "^13.0.0", "hpagent": "^1.2.0", "js-yaml": "^4.1.0", - "monaco-page-objects": "^3.8.0", + "monaco-page-objects": "^3.9.1", "sanitize-filename": "^1.6.3", - "selenium-webdriver": "^4.10.0", + "selenium-webdriver": "4.10.0", "targz": "^1.0.1", "unzipper": "^0.10.14", - "vscode-extension-tester-locators": "^3.6.0" + "vscode-extension-tester-locators": "^3.7.1" }, "dependencies": { "commander": { @@ -8945,9 +8935,9 @@ } }, "vscode-extension-tester-locators": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/vscode-extension-tester-locators/-/vscode-extension-tester-locators-3.6.0.tgz", - "integrity": "sha512-RjQf5XL33dJORl9ck5X0vhAHf93TOONPE4nb9nai/pDM15nYcD1TqMyzRGl/XxHpvBu/bP8MV/bHfCT6b8w2cQ==", + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/vscode-extension-tester-locators/-/vscode-extension-tester-locators-3.7.1.tgz", + "integrity": "sha512-My+ZTH2PM0g9JwWw49E+SfBMFLSjBdv6YpjekUFpzhCWK0mH+BjRC0gtINr+x2+KuVZVhsO82oSivBa1d7ZJvg==", "dev": true, "requires": {} }, @@ -9122,9 +9112,9 @@ "dev": true }, "ws": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", - "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", + "version": "8.14.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz", + "integrity": "sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==", "dev": true, "requires": {} }, diff --git a/package.json b/package.json index 7e1aaf66..65a5d054 100644 --- a/package.json +++ b/package.json @@ -867,8 +867,8 @@ "scripts": { "compile": "tsc -p . && webpack --config webpack.config.js --mode development", "watch": "webpack --mode development --watch", - "test": "tsc -p . && node ./dist/test/index.js", - "test-ui": "tsc -p . && node ./dist/test/ui/index.js", + "test": "tsc -p . && webpack --config webpack.config.js --mode development && node ./dist/test/index.js", + "test-ui": "tsc -p . && webpack --config webpack.config.js --mode development && node ./dist/test/ui/index.js", "build-server": "node scripts/buildJdtlsExt.js", "vscode:prepublish": "tsc -p ./ && webpack --mode production", "tslint": "tslint -t verbose --project tsconfig.json" @@ -889,7 +889,7 @@ "ts-loader": "^9.4.2", "tslint": "^6.1.3", "typescript": "^4.9.4", - "vscode-extension-tester": "^5.5.2", + "vscode-extension-tester": "^5.9.1", "webpack": "^5.76.0", "webpack-cli": "^4.10.0" }, diff --git a/src/java/jdtls.ts b/src/java/jdtls.ts index 1c07f194..c1388253 100644 --- a/src/java/jdtls.ts +++ b/src/java/jdtls.ts @@ -48,7 +48,7 @@ export namespace Jdtls { for (const pattern in excludePatterns) { if (excludePatterns[pattern]) { const toExclude: string[] = minimatch.match(uriOfChildren, pattern); - toExclude.forEach((uri: string) => urisToExclude.add(uri)); + toExclude.forEach((uriToExclude: string) => urisToExclude.add(uriToExclude)); } } @@ -58,7 +58,7 @@ export namespace Jdtls { return true; } return !urisToExclude.has(node.uri); - }) + }); } } return nodeData; @@ -95,6 +95,6 @@ export namespace Jdtls { } interface IPackageDataParam { - projectUri: string | undefined, - [key: string]: any, + projectUri: string | undefined; + [key: string]: any; } \ No newline at end of file diff --git a/src/tasks/buildArtifact/BuildArtifactTaskProvider.ts b/src/tasks/buildArtifact/BuildArtifactTaskProvider.ts index d5971de2..3fc0cb47 100644 --- a/src/tasks/buildArtifact/BuildArtifactTaskProvider.ts +++ b/src/tasks/buildArtifact/BuildArtifactTaskProvider.ts @@ -112,7 +112,7 @@ export class BuildArtifactTaskProvider implements TaskProvider { entry: undefined, taskLabel: resolvedDefinition.label || folder.name, workspaceFolder: folder, - projectList: projectList, + projectList, steps: [], elements: [], classpaths: [], @@ -166,7 +166,7 @@ export class BuildArtifactTaskProvider implements TaskProvider { entry: undefined, taskLabel: resolvedDefinition.label || folder.name, workspaceFolder: folder, - projectList: projectList, + projectList, steps: [], elements: [], classpaths: [], diff --git a/src/views/nodeFactory.ts b/src/views/nodeFactory.ts index 7706477e..460241db 100644 --- a/src/views/nodeFactory.ts +++ b/src/views/nodeFactory.ts @@ -88,6 +88,6 @@ export class NodeFactory { sendError(new Error(`Unsupported node kind: ${nodeData.kind}`)); return undefined; } - + } } diff --git a/test/index.ts b/test/index.ts index 69934a3f..66d0b20a 100644 --- a/test/index.ts +++ b/test/index.ts @@ -82,6 +82,18 @@ async function main(): Promise { ], }); + + // Run multi module test + await runTests({ + vscodeExecutablePath, + extensionDevelopmentPath, + extensionTestsPath: path.resolve(__dirname, "./multi-module-suite"), + launchArgs: [ + path.join(__dirname, "..", "..", "test", "multi-module"), + `--user-data-dir=${userDir}`, + ], + }); + process.exit(0); } catch (err) { diff --git a/test/maven-suite/projectView.test.ts b/test/maven-suite/projectView.test.ts index 91c5db09..f6863758 100644 --- a/test/maven-suite/projectView.test.ts +++ b/test/maven-suite/projectView.test.ts @@ -5,7 +5,7 @@ import * as assert from "assert"; import * as vscode from "vscode"; import { Commands, ContainerNode, contextManager, DataNode, DependencyExplorer, FileNode, INodeData, Jdtls, NodeKind, PackageNode, PackageRootNode, PrimaryTypeNode, ProjectNode } from "../../extension.bundle"; -import { fsPath, setupTestEnv, Uris } from "../shared"; +import { fsPath, printNodes, setupTestEnv, Uris } from "../shared"; // tslint:disable: only-arrow-functions suite("Maven Project View Tests", () => { @@ -26,7 +26,7 @@ suite("Maven Project View Tests", () => { const projectChildren = await projectNode.getChildren(); assert.ok(!!projectChildren.find((c: DataNode) => c.name === "pom.xml")); assert.ok(!!projectChildren.find((c: DataNode) => c.name === ".vscode")); - assert.equal(projectChildren.length, 8, "Number of children should be 8"); + assert.equal(projectChildren.length, 8, `Number of children should be 8, but was ${projectChildren.length}.\n${printNodes(projectChildren)}`); const mainPackage = projectChildren[0] as PackageRootNode; assert.equal(mainPackage.name, "src/main/java", "Package name should be \"src/main/java\""); @@ -101,8 +101,8 @@ suite("Maven Project View Tests", () => { const testSourceSetChildren = await testPackage.getChildren(); assert.equal(testSourceSetChildren.length, 1, "Number of test sub packages should be 1"); const testSubPackage = testSourceSetChildren[0] as PackageNode; - - + + assert.equal(testSubPackage.name, "com.mycompany.app", "Name of test subpackage should be \"com.mycompany.app\""); // validate innermost layer nodes @@ -252,4 +252,14 @@ suite("Maven Project View Tests", () => { const projectChildren = await projectNode.getChildren(); assert.equal(projectChildren.length, 4); }); + + teardown(async () => { + // Restore default settings. Some tests might alter them and others depend on a specific setting. + // Not resetting to the default settings will also show the file as changed in the source control view. + await vscode.workspace.getConfiguration("java.project.explorer").update( + "showNonJavaResources", + true + ); + await vscode.workspace.getConfiguration("java.dependency").update("packagePresentation", "flat"); + }); }); diff --git a/test/maven/.vscode/settings.json b/test/maven/.vscode/settings.json index ee7bd904..d70f7c04 100644 --- a/test/maven/.vscode/settings.json +++ b/test/maven/.vscode/settings.json @@ -1,6 +1,19 @@ { "java.server.launchMode": "Standard", + "java.dependency.packagePresentation": "flat", + "java.dependency.autoRefresh": true, + "java.dependency.syncWithFolderExplorer": true, + "java.project.explorer.showNonJavaResources": true, + // The test code does not expect a .classpath, .settings and .project file to be created. + // If the test is run on a developer machine, the user settings will be inherited and it might be set there. + "java.import.generatesMetadataFilesAtProjectRoot": false, "files.exclude": { "**/.hidden": true, + // Overwrite user settings, if they exist + "**/.classpath": false, + "**/.factorypath": false, + "**/.project": false, + "**/.settings": false, + "**/target": false } } diff --git a/test/multi-module-suite/index.ts b/test/multi-module-suite/index.ts new file mode 100644 index 00000000..3e503ad8 --- /dev/null +++ b/test/multi-module-suite/index.ts @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import * as glob from "glob"; +import * as Mocha from "mocha"; +import * as path from "path"; + +export function run(): Promise { + // Create the mocha test + const mocha = new Mocha({ + ui: "tdd", + color: true, + timeout: 1 * 60 * 1000, + }); + + const testsRoot = __dirname; + + return new Promise((c, e) => { + glob("**/**.test.js", { cwd: testsRoot }, (err, files) => { + if (err) { + return e(err); + } + + // Add files to the test suite + files.forEach((f) => mocha.addFile(path.resolve(testsRoot, f))); + + try { + // Run the mocha test + mocha.run((failures) => { + if (failures > 0) { + e(new Error(`${failures} tests failed.`)); + } else { + c(); + } + }); + } catch (err) { + e(err); + } + }); + }); +} diff --git a/test/multi-module-suite/projectView.test.ts b/test/multi-module-suite/projectView.test.ts new file mode 100644 index 00000000..e54e9336 --- /dev/null +++ b/test/multi-module-suite/projectView.test.ts @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import * as assert from "assert"; +import { contextManager, DependencyExplorer, + FileNode, + ProjectNode } from "../../extension.bundle"; +import { printNodes, setupTestEnv } from "../shared"; + +// tslint:disable: only-arrow-functions +suite("Multi Module Tests", () => { + + suiteSetup(setupTestEnv); + + test("Can open module with name equal or longer than folder name correctly", async function() { + const explorer = DependencyExplorer.getInstance(contextManager.context); + + const roots = await explorer.dataProvider.getChildren(); + const nestedProjectNode = roots?.find(project => + project instanceof ProjectNode && project.name === 'de.myorg.myservice.level1') as ProjectNode; + + const projectChildren = await nestedProjectNode.getChildren(); + assert.ok(!!projectChildren.find(child => child instanceof FileNode && child.path?.endsWith('level1/pom.xml'), `Expected to find FileNode with level1 pom.xml in:\n${printNodes(projectChildren)}`)); + }); +}); diff --git a/test/multi-module/.vscode/settings.json b/test/multi-module/.vscode/settings.json new file mode 100644 index 00000000..1759e992 --- /dev/null +++ b/test/multi-module/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "java.dependency.packagePresentation": "flat", + "java.import.generatesMetadataFilesAtProjectRoot": false +} \ No newline at end of file diff --git a/test/multi-module/level1/pom.xml b/test/multi-module/level1/pom.xml new file mode 100644 index 00000000..a70a0c09 --- /dev/null +++ b/test/multi-module/level1/pom.xml @@ -0,0 +1,21 @@ + + 4.0.0 + + fvclaus + de.myorg.myservice + 1.0-SNAPSHOT + + de.myorg.myservice.level1 + jar + 1.0-SNAPSHOT + http://maven.apache.org + + + junit + junit + 3.8.1 + test + + + \ No newline at end of file diff --git a/test/multi-module/pom.xml b/test/multi-module/pom.xml new file mode 100644 index 00000000..731fc1ea --- /dev/null +++ b/test/multi-module/pom.xml @@ -0,0 +1,12 @@ + + 4.0.0 + fvclaus + de.myorg.myservice + pom + 1.0-SNAPSHOT + http://maven.apache.org + + level1 + + diff --git a/test/multi-module/src/main/java/fvclaus/App.java b/test/multi-module/src/main/java/fvclaus/App.java new file mode 100644 index 00000000..9225dba0 --- /dev/null +++ b/test/multi-module/src/main/java/fvclaus/App.java @@ -0,0 +1,13 @@ +package fvclaus; + +/** + * Hello world! + * + */ +public class App +{ + public static void main( String[] args ) + { + System.out.println( "Hello World!" ); + } +} diff --git a/test/multi-module/src/test/java/fvclaus/AppTest.java b/test/multi-module/src/test/java/fvclaus/AppTest.java new file mode 100644 index 00000000..1b28eaa3 --- /dev/null +++ b/test/multi-module/src/test/java/fvclaus/AppTest.java @@ -0,0 +1,38 @@ +package fvclaus; + +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; + +/** + * Unit test for simple App. + */ +public class AppTest + extends TestCase +{ + /** + * Create the test case + * + * @param testName name of the test case + */ + public AppTest( String testName ) + { + super( testName ); + } + + /** + * @return the suite of tests being tested + */ + public static Test suite() + { + return new TestSuite( AppTest.class ); + } + + /** + * Rigourous Test :-) + */ + public void testApp() + { + assertTrue( true ); + } +} diff --git a/test/shared.ts b/test/shared.ts index a984c392..92c01e3b 100644 --- a/test/shared.ts +++ b/test/shared.ts @@ -3,7 +3,8 @@ import * as path from "path"; import { extensions, Uri } from "vscode"; -import { DataNode } from "../extension.bundle"; +import { DataNode, PackageNode } from "../extension.bundle"; +import { ExplorerNode } from "../src/views/explorerNode"; export namespace Uris { // Simple Project @@ -42,3 +43,15 @@ export async function setupTestEnv() { await extensions.getExtension("redhat.java")!.activate(); await extensions.getExtension("vscjava.vscode-java-dependency")!.activate(); } + +export function printNodes(nodes: ExplorerNode[]) { + return nodes.map(node => { + if (node instanceof DataNode) { + return `DataNode: ${node.name}`; + } + if (node instanceof PackageNode) { + return `PackageNode: ${node.name}`; + } + return `UnknownNode: ${node.constructor.name}`; + }).join('\n'); +} diff --git a/test/ui/command.test.ts b/test/ui/command.test.ts index a7e72b76..083fd8ab 100644 --- a/test/ui/command.test.ts +++ b/test/ui/command.test.ts @@ -3,10 +3,10 @@ import * as assert from "assert"; import * as fse from "fs-extra"; -import { platform } from "os"; +import { platform, tmpdir } from "os"; import * as path from "path"; import * as seleniumWebdriver from "selenium-webdriver"; -import { EditorView, InputBox, ModalDialog, SideBarView, TextEditor, TreeItem, VSBrowser, Workbench } from "vscode-extension-tester"; +import { ActivityBar, By, InputBox, ModalDialog, SideBarView, StatusBar, TextEditor, TreeItem, VSBrowser, ViewSection, Workbench } from "vscode-extension-tester"; import { sleep } from "../util"; // tslint:disable: only-arrow-functions @@ -14,47 +14,72 @@ const newProjectName = "helloworld"; const testFolder = path.join(__dirname, "..", "..", "..", "test"); const mavenProjectPath = path.join(testFolder, "maven"); const invisibleProjectPath = path.join(testFolder, "invisible"); -const targetPath = path.join(testFolder, "newProject"); + +// async function pauseInPipeline(timeInMs: number): Promise { +// if (process.env.GITHUB_ACTIONS) { +// return sleep(timeInMs); +// } else { +// return Promise.resolve(); +// } +// } describe("Command Tests", function() { - let browser: VSBrowser; this.timeout(2 * 60 * 1000 /*ms*/); + const mavenProjectTmpFolders: string[] = []; + let currentProjectPath: string | undefined; - before(async function() { - browser = VSBrowser.instance; - sleep(5000); - }); + function createTmpProjectFolder(projectName: string) { + const tmpFolder = fse.mkdtempSync(path.join(tmpdir(), 'vscode-java-dependency-ui-test')); + // Keep the folder name. + const projectFolder = path.join(tmpFolder, projectName); + fse.mkdirSync(projectFolder); + mavenProjectTmpFolders.push(tmpFolder); + return projectFolder; + } - beforeEach(async function() { - await sleep(5000); - }); + async function openProject(projectPath: string) { + const projectFolder = createTmpProjectFolder(path.basename(projectPath)); + // Copy to avoid restoring after each test run to revert changes done during the test. + fse.copySync(projectPath, projectFolder); + await VSBrowser.instance.openResources(projectFolder); + currentProjectPath = projectFolder; + await ensureExplorerIsOpen(); + } - it("Test open maven project", async function() { - await browser.openResources(mavenProjectPath); - // Close welcome editors - let editorView = new EditorView(); - let editorGroups = await editorView.getEditorGroups(); - for (const editorGroup of editorGroups) { - await editorGroup.closeAllEditors(); + async function waitForLanguageServerReady() { + const statusBar = new StatusBar(); + while (true) { + const language = await statusBar.getCurrentLanguage(); + if (language === 'Java') { + break; + } } - const settingsEditor = await new Workbench().openSettings(); - const refreshSetting = await settingsEditor.findSetting("Auto Refresh", "Java", "Dependency"); - await refreshSetting.setValue(true); - const viewSetting = await settingsEditor.findSetting("Package Presentation", "Java", "Dependency"); - await viewSetting.setValue("flat"); - // Close setting editor - editorView = new EditorView(); - editorGroups = await editorView.getEditorGroups(); - for (const editorGroup of editorGroups) { - await editorGroup.closeAllEditors(); + while (true) { + try { + const languageStatus = await statusBar.findElement(By.xpath('//*[@id="status.languageStatus"]')); + await languageStatus.click(); + await languageStatus.findElement(By.xpath(`//div[contains(@class, 'context-view')]//div[contains(@class, 'hover-language-status')]//span[contains(@class, 'codicon-thumbsup')]`)); + break; + } catch (e) { + await sleep(100); + } } - await sleep(1000); - const fileSections = await new SideBarView().getContent().getSections(); - await fileSections[0].collapse(); - await sleep(60 * 1000 /*ms*/); + } + + before(async function() { + await openProject(mavenProjectPath); + await openFile('App.java'); + await waitForLanguageServerReady(); }); + after(async function() { + mavenProjectTmpFolders.forEach(mavenProjectTmpFolder => { + fse.rmSync(mavenProjectTmpFolder, {force: true, recursive: true}); + }); + }); + + it("Test javaProjectExplorer.focus", async function() { await new Workbench().executeCommand("javaProjectExplorer.focus"); const section = await new SideBarView().getContent().getSection("Java Projects"); @@ -62,20 +87,9 @@ describe("Command Tests", function() { }); (platform() === "darwin" ? it.skip : it)("Test java.view.package.linkWithFolderExplorer", async function() { - const fileSections = await new SideBarView().getContent().getSections(); - await fileSections[0].expand(); - const srcNode = await fileSections[0].findItem("src") as TreeItem; - await srcNode.expand(); - const folderNode = await fileSections[0].findItem("main") as TreeItem; - await folderNode.expand(); - const subFolderNode = await fileSections[0].findItem("app") as TreeItem; - await subFolderNode.expand(); - const fileNode = await fileSections[0].findItem("App.java") as TreeItem; - await fileNode.click(); + await openFile('App.java'); await sleep(1000); - await fileSections[0].collapse(); - const section = await new SideBarView().getContent().getSection("Java Projects"); - await section.expand(); + const [, section] = await expandInJavaProjects('my-app'); const packageNode = await section.findItem("com.mycompany.app") as TreeItem; assert.ok(await packageNode.isExpanded(), `Package node "com.mycompany.app" should be expanded`); const classNode = await section.findItem("App") as TreeItem; @@ -84,30 +98,24 @@ describe("Command Tests", function() { }); (platform() === "darwin" ? it.skip : it)("Test java.view.package.unLinkWithFolderExplorer", async function() { - const section = await new SideBarView().getContent().getSection("Java Projects"); - const moreActions = await section.moreActions(); - const desynchronize = await moreActions?.getItem("Desynchronize with Editor"); - await desynchronize?.click(); - const fileSections = await new SideBarView().getContent().getSections(); - await fileSections[0].expand(); - const fileNode = await fileSections[0].findItem("App.java") as TreeItem; - await fileNode.click(); + const [, section] = await expandInJavaProjects('my-app'); + await section.click(); + let moreActions = await section.moreActions(); + const desynchronize = await moreActions!.getItem("Unlink with Editor"); + await desynchronize!.click(); + await openFile('App.java'); await sleep(1000); - await fileSections[0].collapse(); - await section.expand(); const packageNode = await section.findItem("com.mycompany.app") as TreeItem; assert.ok(!await packageNode.isExpanded(), `Package "com.mycompany.app" should not be expanded`); + moreActions = await section.moreActions(); + const link = await moreActions!.getItem("Link with Editor"); + await link!.click(); }); it("Test java.view.package.newJavaClass", async function() { - const section = await new SideBarView().getContent().getSection("Java Projects"); - const item = await section.findItem("my-app") as TreeItem; - assert.ok(item, `Project "my-app" should be found`); - await item.click(); - const button = await item.getActionButton("New Java Class"); - assert.ok(button, `Button "New Java Class" should be found`); - await button!.click(); - let inputBox = await InputBox.create(); + let inputBox = await createJavaResource(); + const javaClassQuickPick = await inputBox.findQuickPick("Java Class"); + await javaClassQuickPick!.click(); assert.ok(await inputBox.getPlaceHolder() === "Choose a source folder", `InputBox "Choose a source folder" should appear`); const quickPick = await inputBox.findQuickPick("src/main/java"); assert.ok(quickPick, `Quickpick item "src/main/java" should be found`); @@ -120,22 +128,16 @@ describe("Command Tests", function() { const editor = new TextEditor(); await editor.save(); assert.ok(await editor.getTitle() === "App2.java", `Editor's title should be "App2.java"`); - assert.ok(await fse.pathExists(path.join(mavenProjectPath, "src", "main", "java", "App2.java")), `"App2.java" should be created in correct path`); - await fse.remove(path.join(mavenProjectPath, "src", "main", "java", "App2.java")); + assert.ok(await fse.pathExists(path.join(currentProjectPath!, "src", "main", "java", "App2.java")), `"App2.java" should be created in correct path`); }); (platform() === "darwin" ? it.skip : it)("Test java.view.package.newPackage", async function() { // The current UI test framework doesn't support mac title bar and context menus. // See: https://github.com/redhat-developer/vscode-extension-tester#requirements // So we dismiss some UI tests on mac. - const section = await new SideBarView().getContent().getSection("Java Projects"); - const item = await section.findItem("my-app") as TreeItem; - await item.click(); - const contextMenu = await item.openContextMenu(); - const newPackageItem = await contextMenu.getItem("New Package"); - assert.ok(newPackageItem, `"New Package" should be found in context menu`); - await newPackageItem!.click(); - let inputBox = await InputBox.create(); + let inputBox = await createJavaResource(); + const packageQuickPick = await inputBox.findQuickPick('Package'); + await packageQuickPick!.click(); const quickPick = await inputBox.findQuickPick("src/main/java"); assert.ok(quickPick, `"src/main/java" should be found in quickpick items`); await quickPick!.click(); @@ -143,35 +145,34 @@ describe("Command Tests", function() { await inputBox.setText("com.mycompany.app2"); await inputBox.confirm(); await sleep(1000); - assert.ok(await fse.pathExists(path.join(mavenProjectPath, "src", "main", "java", "com", "mycompany", "app2")), `New package should be created in correct path`); - await fse.remove(path.join(mavenProjectPath, "src", "main", "java", "com", "mycompany", "app2")); + assert.ok(await fse.pathExists(path.join(currentProjectPath!, "src", "main", "java", "com", "mycompany", "app2")), `New package should be created in correct path`); }); (platform() === "darwin" ? it.skip : it)("Test java.view.package.revealInProjectExplorer", async function() { - const fileExplorerSections = await new SideBarView().getContent().getSections(); - await fileExplorerSections[0].expand(); + // Make sure App.java is not currently revealed in Java Projects const section = await new SideBarView().getContent().getSection("Java Projects"); - const packageNode = await section.findItem("com.mycompany.app") as TreeItem; - await packageNode.click(); - await packageNode.collapse(); - const srcNode = await fileExplorerSections[0].findItem("src") as TreeItem; - await srcNode.expand(); - const folderNode = await fileExplorerSections[0].findItem("main") as TreeItem; - await folderNode.expand(); - const fileNode = await fileExplorerSections[0].findItem("App.java") as TreeItem; - const menu = await fileNode.openContextMenu(); - const revealItem = await menu.getItem("Reveal in Java Project Explorer"); + const item = await section.findItem("my-app") as TreeItem; + await item.collapse(); + const [fileSection, fileNode] = await openAppJavaSourceCode(); + await fileNode.openContextMenu(); + // menu.getItem(label) does not work. I did not investigate this further. + // This is a global selector on purpose. The context-menu is located near the root node. + const revealItem = await fileNode.findElement(By.xpath(`//div[contains(@class, 'context-view')]//a[@role='menuitem' and span[contains(text(), 'Reveal in Java Project Explorer')]]`)); + // const revealItem = await menu.getItem("Reveal in Java Project Explorer"); assert.ok(revealItem, `Item "Reveal in Java Project Explorer" should be found in context menu`); await revealItem!.click(); const classNode = await section.findItem("App") as TreeItem; assert.ok(await classNode.isDisplayed(), `Class Node "App" should be revealed`); - await fileExplorerSections[0].collapse(); + await fileSection.collapse(); }); (platform() === "darwin" ? it.skip : it)("Test java.view.package.renameFile", async function() { - const section = await new SideBarView().getContent().getSection("Java Projects"); - await section.click(); + // Collapse file section to make sure that the AppToRename tree item fits in the current viewport. + // .findItem will only find tree items in the current viewport. + await collapseFileSection(); + const section = await expandMainCodeInJavaProjects(); const classNode = await section.findItem("AppToRename") as TreeItem; + assert.ok(classNode, `AppToRename.java should be found`); await classNode.click(); const menu = await classNode.openContextMenu(); const renameItem = await menu.getItem("Rename"); @@ -197,11 +198,18 @@ describe("Command Tests", function() { }); (platform() === "darwin" ? it.skip : it)("Test java.view.package.moveFileToTrash", async function() { - const section = await new SideBarView().getContent().getSection("Java Projects"); + // Collapse file section to make sure that the AppToRename tree item fits in the current viewport. + // .findItem will only find tree items in the current viewport. + await collapseFileSection(); + const section = await expandMainCodeInJavaProjects(); const classNode = await section.findItem("AppToDelete") as TreeItem; await classNode.click(); const menu = await classNode.openContextMenu(); - const deleteItem = await menu.getItem("Delete"); + let deleteItem = await menu.getItem("Delete"); + // Not sure why sometimes one is visible and other times the other. + if (deleteItem === undefined) { + deleteItem = await menu.getItem("Delete Permanently"); + } assert.ok(deleteItem, `"Delete" item should be found`); await deleteItem!.click(); const dialog = new ModalDialog(); @@ -213,58 +221,48 @@ describe("Command Tests", function() { } } await sleep(1000); - assert.ok(!await fse.pathExists(path.join(mavenProjectPath, "src", "main", "java", "AppToDelete.java")), `The source file "AppToDelete.java" should be deleted`); + assert.ok(!await fse.pathExists(path.join(currentProjectPath!, "src", "main", "java", "AppToDelete.java")), `The source file "AppToDelete.java" should be deleted`); }); it("Test change to invisible project", async function() { - await browser.openResources(invisibleProjectPath); + await openProject(invisibleProjectPath); await sleep(1000); - const fileExplorerSections = await new SideBarView().getContent().getSections(); - const folderNode = await fileExplorerSections[0].findItem("src") as TreeItem; - await folderNode.expand(); - const fileNode = await fileExplorerSections[0].findItem("App.java") as TreeItem; - await fileNode.click(); - await sleep(60 * 1000 /*ms*/); + await openFile('App.java'); + await waitForLanguageServerReady(); const fileSections = await new SideBarView().getContent().getSections(); await fileSections[0].collapse(); await new Workbench().executeCommand("javaProjectExplorer.focus"); }); it("Test java.project.addLibraries", async function() { - const section = await new SideBarView().getContent().getSection("Java Projects"); - const projectItem = await section.findItem("invisible") as TreeItem; - await projectItem.expand(); - await sleep(1000); - let referencedItem = await section.findItem("Referenced Libraries") as TreeItem; + // tslint:disable-next-line:prefer-const + let [referencedItem, section] = await expandInJavaProjects('invisible', 'Referenced Libraries'); await referencedItem.click(); - const buttons = await referencedItem.getActionButtons(); - await buttons[0].click(); + await clickActionButton(referencedItem, `Add Jar Libraries to Project Classpath...`); const input = await InputBox.create(); await input.setText(path.join(invisibleProjectPath, "libSource", "simple.jar")); await input.confirm(); await sleep(5000); referencedItem = await section.findItem("Referenced Libraries") as TreeItem; await referencedItem.expand(); - const simpleItem = await section.findItem("simple.jar") as TreeItem; + let simpleItem = await section.findItem("simple.jar") as TreeItem; assert.ok(simpleItem, `Library "simple.jar" should be found`); await simpleItem.click(); - const libraryButtons = await simpleItem.getActionButtons(); - await libraryButtons[0].click(); + await clickActionButton(simpleItem, 'Remove from Project Classpath'); await sleep(5000); + simpleItem = await section.findItem("simple.jar") as TreeItem; + assert.ok(!simpleItem, `Library "simple.jar" should not be found`); }); it("Test java.project.addLibraryFolders", async function() { - const section = await new SideBarView().getContent().getSection("Java Projects"); - const projectItem = await section.findItem("invisible") as TreeItem; - await projectItem.expand(); - await sleep(1000); - let referencedItem = await section.findItem("Referenced Libraries") as TreeItem; + // tslint:disable-next-line:prefer-const + let [referencedItem, section] = await expandInJavaProjects('invisible', 'Referenced Libraries'); await referencedItem.click(); - const buttons = await referencedItem.getActionButtons(); - await buttons[0].getDriver().actions() + const button = await getActionButton(referencedItem, `Add Jar Libraries to Project Classpath...`); + await button.getDriver().actions() // .mouseMove(buttons[0]) .keyDown(seleniumWebdriver.Key.ALT) - .click(buttons[0]) + .click(button) .keyUp(seleniumWebdriver.Key.ALT) .perform(); await sleep(5000); @@ -278,7 +276,8 @@ describe("Command Tests", function() { }); it("Test java.project.create", async function() { - await fse.ensureDir(targetPath); + const projectFolder = createTmpProjectFolder("newProject"); + await fse.ensureDir(projectFolder); await new Workbench().executeCommand("java.project.create"); let inputBox = await InputBox.create(); const picks = await inputBox.getQuickPicks(); @@ -286,14 +285,107 @@ describe("Command Tests", function() { await picks[0].select(); await sleep(3000); inputBox = await InputBox.create(); - await inputBox.setText(targetPath); + await inputBox.setText(projectFolder); await inputBox.confirm(); await sleep(3000); inputBox = await InputBox.create(); await inputBox.setText(newProjectName); await inputBox.confirm(); await sleep(5000); - assert.ok(await fse.pathExists(path.join(targetPath, newProjectName, "src", "App.java")), `The template source file should be created`); - assert.ok(await fse.pathExists(path.join(targetPath, newProjectName, "README.md")), `The template README file should be created`); + assert.ok(await fse.pathExists(path.join(projectFolder, newProjectName, "src", "App.java")), `The template source file should be created`); + assert.ok(await fse.pathExists(path.join(projectFolder, newProjectName, "README.md")), `The template README file should be created`); }); + + }); + +async function collapseFileSection() { + const fileSections = await new SideBarView().getContent().getSections(); + await fileSections[0].collapse(); +} + +async function expandMainCodeInJavaProjects() { + const section = await new SideBarView().getContent().getSection("Java Projects"); + await section.click(); + const appNode = await section.findItem("my-app") as TreeItem; + await appNode.expand(); + const srcFolderNode = await section.findItem('src/main/java') as TreeItem; + await srcFolderNode.expand(); + const packageNode = await section.findItem("com.mycompany.app") as TreeItem; + await packageNode.expand(); + return section; +} + +async function expandInJavaProjects(label: string, ...otherLabels: string[]): Promise<[TreeItem, ViewSection]> { + // Collapse file section to make sure that the AppToRename tree item fits in the current viewport. + // .findItem will only find tree items in the current viewport. + await collapseFileSection(); + const section = await new SideBarView().getContent().getSection("Java Projects"); + await section.click(); + let lastNode = await section.findItem(label) as TreeItem; + await lastNode.expand(); + for (const otherLabel of otherLabels) { + lastNode = await section.findItem(otherLabel) as TreeItem; + await lastNode.expand(); + } + return [lastNode, section]; +} + +async function openFile(filename: string) { + await new Workbench().executeCommand('workbench.action.quickOpen'); + const fileInputBox = await InputBox.create(); + await fileInputBox.setText(filename); + await fileInputBox.confirm(); +} + +async function openAppJavaSourceCode(): Promise<[ViewSection, TreeItem]> { + const fileSections = await new SideBarView().getContent().getSections(); + await fileSections[0].expand(); + const srcNode = await fileSections[0].findItem("src") as TreeItem; + await srcNode.expand(); + const folderNode = await fileSections[0].findItem("main") as TreeItem; + await folderNode.expand(); + const subFolderNode = await fileSections[0].findItem("com") as TreeItem; + await subFolderNode.expand(); + const appFolderNode = await fileSections[0].findItem("app") as TreeItem; + await appFolderNode.expand(); + const fileNode = await fileSections[0].findItem("App.java") as TreeItem; + await fileNode.click(); + return [fileSections[0], fileNode]; +} + +async function createJavaResource() { + await collapseFileSection(); + const section = await new SideBarView().getContent().getSection("Java Projects"); + const item = await section.findItem("my-app") as TreeItem; + assert.ok(item, `Project "my-app" should be found`); + await item.click(); + await clickActionButton(item, 'New...'); + const inputBox = await InputBox.create(); + assert.ok(await inputBox.getPlaceHolder() === "Select resource type to create.", + `InputBox "Select resource type to create" should appear.`); + return inputBox; +} + +async function clickActionButton(item: TreeItem, label: string) { + const button = await getActionButton(item, label); + await button.click(); +} + +async function getActionButton(item: TreeItem, label: string) { + // Using item.getActionButton('New...') throws an error: + // tslint:disable-next-line:max-line-length + // "no such element: Unable to locate element: {\"method\":\"xpath\",\"selector\":\".//a[contains(@class, 'action-label') and @role='button' and @title='New...']\"} + // This should be filled as an issue (I haven't find one). + // The problem is the @title='New...' which should be @aria-label='New...' for vscode 1.77.0 (and probably above). + return item.findElement(By.xpath(`.//a[contains(@class, 'action-label') and @role='button' and contains(@aria-label, '${label}')]`)); +} + +async function ensureExplorerIsOpen() { + const control = await new ActivityBar().getViewControl('Explorer'); + if (control === undefined) { + throw new Error(`Explorer control should not be null.`); + } + await control.openView(); +} + diff --git a/webpack.config.js b/webpack.config.js index 8c079ea0..ead14fd8 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -37,6 +37,7 @@ const config = { ], externals: { vscode: "commonjs vscode", // the vscode-module is created on-the-fly and must be excluded. Add other modules that cannot be webpack'ed, 📖 -> https://webpack.js.org/configuration/externals/ + 'applicationinsights-native-metrics': 'commonjs applicationinsights-native-metrics' // https://github.com/microsoft/vscode-extension-telemetry/issues/41#issuecomment-598852991 }, devtool: 'source-map', resolve: { // support reading TypeScript and JavaScript files, 📖 -> https://github.com/TypeStrong/ts-loader From 8575732416fd227e0a215941b18384aa0b658598 Mon Sep 17 00:00:00 2001 From: Frederik Claus Date: Sun, 1 Oct 2023 13:48:26 +0200 Subject: [PATCH 2/7] Removes unused import and corrects whitespace --- .../src/com/microsoft/jdtls/ext/core/PackageCommand.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/PackageCommand.java b/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/PackageCommand.java index ef183ab4..39c612d1 100644 --- a/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/PackageCommand.java +++ b/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/PackageCommand.java @@ -14,7 +14,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; -import java.util.Comparator; import java.util.HashMap; import java.util.LinkedList; import java.util.List; @@ -584,7 +583,7 @@ public static IProject getProject(String projectUri) { Optional maybeProject = Arrays.stream(containers).filter(container -> container instanceof IProject).findFirst(); if (maybeProject.isPresent()) { - return (IProject) maybeProject.get(); + return (IProject) maybeProject.get(); } else { String invisibleProjectName = ProjectUtils.getWorkspaceInvisibleProjectName(FileUtil.toPath(uri).removeTrailingSeparator()); IProject invisibleProject = root.getProject(invisibleProjectName); From b4b3d4316e2d1bb3d6475b78ed7f26064a2a23fa Mon Sep 17 00:00:00 2001 From: Frederik Claus Date: Sun, 1 Oct 2023 13:50:04 +0200 Subject: [PATCH 3/7] Removes whitespaces in empty line --- .../src/com/microsoft/jdtls/ext/core/PackageCommand.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/PackageCommand.java b/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/PackageCommand.java index 39c612d1..9c195d6b 100644 --- a/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/PackageCommand.java +++ b/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/PackageCommand.java @@ -580,7 +580,7 @@ public static IProject getProject(String projectUri) { IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot(); URI uri = JDTUtils.toURI(projectUri); IContainer[] containers = root.findContainersForLocationURI(uri); - + Optional maybeProject = Arrays.stream(containers).filter(container -> container instanceof IProject).findFirst(); if (maybeProject.isPresent()) { return (IProject) maybeProject.get(); From c2f1f4916a1d5ea7f3d40be70c89d6b02796e7db Mon Sep 17 00:00:00 2001 From: Frederik Claus Date: Thu, 5 Oct 2023 22:35:33 +0200 Subject: [PATCH 4/7] Handle any folder that links to a project --- .../com/microsoft/jdtls/ext/core/PackageCommand.java | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/PackageCommand.java b/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/PackageCommand.java index 9c195d6b..4fbf1a26 100644 --- a/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/PackageCommand.java +++ b/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/PackageCommand.java @@ -24,7 +24,6 @@ import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; -import org.eclipse.core.internal.utils.FileUtil; import org.eclipse.core.resources.IContainer; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IFolder; @@ -585,12 +584,9 @@ public static IProject getProject(String projectUri) { if (maybeProject.isPresent()) { return (IProject) maybeProject.get(); } else { - String invisibleProjectName = ProjectUtils.getWorkspaceInvisibleProjectName(FileUtil.toPath(uri).removeTrailingSeparator()); - IProject invisibleProject = root.getProject(invisibleProjectName); - if (!invisibleProject.exists()) { - throw new IllegalArgumentException(projectUri + " is neither a Java nor an invisible project."); - } - return invisibleProject; + // This must be a folder that has been linked to project outside the workspace + // for example an invisible project or a project created by a third party extension. + return containers[0].getProject(); } } From 48a684f3f27c3ea9ae6f92f8c5ce56fd43fa97c5 Mon Sep 17 00:00:00 2001 From: Frederik Claus Date: Fri, 6 Oct 2023 11:01:16 +0200 Subject: [PATCH 5/7] Adds error handling for loading invisible projects --- .../microsoft/jdtls/ext/core/PackageCommand.java | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/PackageCommand.java b/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/PackageCommand.java index 4fbf1a26..7a8b50ac 100644 --- a/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/PackageCommand.java +++ b/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/PackageCommand.java @@ -14,6 +14,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; import java.util.LinkedList; import java.util.List; @@ -584,9 +585,17 @@ public static IProject getProject(String projectUri) { if (maybeProject.isPresent()) { return (IProject) maybeProject.get(); } else { - // This must be a folder that has been linked to project outside the workspace - // for example an invisible project or a project created by a third party extension. - return containers[0].getProject(); + if (containers.length == 0) { + throw new IllegalArgumentException(String.format("Did not find container for URI %s", projectUri)); + } + + // This must be an invisible project. Third party extension could theoretically create invisible projects anywhere + // in the project hierarchy. We therefore pick the most specific path. + Arrays.sort(containers, (Comparator) (IContainer a, IContainer b) -> { + return b.getFullPath().toPortableString().length() - a.getFullPath().toPortableString().length(); + }); + + return containers[containers.length - 1].getProject(); } } From 5315e53983802458814db3a230f560580e9aa2f3 Mon Sep 17 00:00:00 2001 From: Sheng Chen Date: Sat, 7 Oct 2023 10:05:52 +0800 Subject: [PATCH 6/7] Add more comments Signed-off-by: Sheng Chen --- .../src/com/microsoft/jdtls/ext/core/PackageCommand.java | 1 + 1 file changed, 1 insertion(+) diff --git a/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/PackageCommand.java b/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/PackageCommand.java index 7a8b50ac..3918ece0 100644 --- a/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/PackageCommand.java +++ b/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/PackageCommand.java @@ -591,6 +591,7 @@ public static IProject getProject(String projectUri) { // This must be an invisible project. Third party extension could theoretically create invisible projects anywhere // in the project hierarchy. We therefore pick the most specific path. + // See: https://github.com/microsoft/vscode-java-dependency/pull/791#discussion_r1349435426 Arrays.sort(containers, (Comparator) (IContainer a, IContainer b) -> { return b.getFullPath().toPortableString().length() - a.getFullPath().toPortableString().length(); }); From 3ec60d126f5d257006e41e4d800d381daeeaef92 Mon Sep 17 00:00:00 2001 From: Frederik Claus Date: Sat, 7 Oct 2023 13:31:36 +0200 Subject: [PATCH 7/7] Removes not well understood sort logic --- .../com/microsoft/jdtls/ext/core/PackageCommand.java | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/PackageCommand.java b/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/PackageCommand.java index 3918ece0..e2f35f3b 100644 --- a/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/PackageCommand.java +++ b/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/PackageCommand.java @@ -14,7 +14,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; -import java.util.Comparator; import java.util.HashMap; import java.util.LinkedList; import java.util.List; @@ -589,14 +588,9 @@ public static IProject getProject(String projectUri) { throw new IllegalArgumentException(String.format("Did not find container for URI %s", projectUri)); } - // This must be an invisible project. Third party extension could theoretically create invisible projects anywhere - // in the project hierarchy. We therefore pick the most specific path. - // See: https://github.com/microsoft/vscode-java-dependency/pull/791#discussion_r1349435426 - Arrays.sort(containers, (Comparator) (IContainer a, IContainer b) -> { - return b.getFullPath().toPortableString().length() - a.getFullPath().toPortableString().length(); - }); - - return containers[containers.length - 1].getProject(); + // This must be an invisible project. + // There might be more than one way to access it, but all containers should link to the same project. + return containers[0].getProject(); } }