From 45f173ac7e34239397bcdeccfefc84db8798b9bb Mon Sep 17 00:00:00 2001 From: Paul LeMarquand Date: Tue, 11 Feb 2025 11:03:35 -0500 Subject: [PATCH 01/86] Make CommandLineTools toolchain selectable if installed (#1376) * Make CommandLineTools toolchain selectable if installed If the CLT are installed add it to the list of available toolchains. Also, if there are no Xcodes installed, omit the top level item from the list of quick pick options since it would render as an empty row. --- src/toolchain/toolchain.ts | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/toolchain/toolchain.ts b/src/toolchain/toolchain.ts index 9ee7b3938..615f85392 100644 --- a/src/toolchain/toolchain.ts +++ b/src/toolchain/toolchain.ts @@ -221,6 +221,10 @@ export class SwiftToolchain { const { stdout: xcodes } = await execFile("mdfind", [ `kMDItemCFBundleIdentifier == 'com.apple.dt.Xcode'`, ]); + // An empty string means no Xcodes are installed. + if (xcodes.length === 0) { + return []; + } return xcodes.trimEnd().split("\n"); } @@ -271,6 +275,7 @@ export class SwiftToolchain { return Promise.all([ this.findToolchainsIn("/Library/Developer/Toolchains/"), this.findToolchainsIn(path.join(os.homedir(), "Library/Developer/Toolchains/")), + this.findCommandLineTools(), ]).then(results => results.flatMap(a => a)); } @@ -349,6 +354,22 @@ export class SwiftToolchain { return result; } + /** + * Returns the path to the CommandLineTools toolchain if its installed. + */ + public static async findCommandLineTools(): Promise { + const commandLineToolsPath = "/Library/Developer/CommandLineTools"; + if (!(await pathExists(commandLineToolsPath))) { + return []; + } + + const toolchainSwiftPath = path.join(commandLineToolsPath, "usr", "bin", "swift"); + if (!(await pathExists(toolchainSwiftPath))) { + return []; + } + return [commandLineToolsPath]; + } + /** * Return fullpath for toolchain executable */ From 776eb06a13a1357f4d0c388f102265fe2c23d9ca Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 12 Feb 2025 09:10:43 -0500 Subject: [PATCH 02/86] Bump esbuild from 0.24.2 to 0.25.0 (#1379) Bumps [esbuild](https://github.com/evanw/esbuild) from 0.24.2 to 0.25.0. - [Release notes](https://github.com/evanw/esbuild/releases) - [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG-2024.md) - [Commits](https://github.com/evanw/esbuild/compare/v0.24.2...v0.25.0) --- updated-dependencies: - dependency-name: esbuild dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 444 ++++++++++++++++++++++++---------------------- package.json | 2 +- 2 files changed, 236 insertions(+), 210 deletions(-) diff --git a/package-lock.json b/package-lock.json index a30c01e0d..e7e6af057 100644 --- a/package-lock.json +++ b/package-lock.json @@ -40,7 +40,7 @@ "chai-as-promised": "^7.1.2", "chai-subset": "^1.6.0", "del-cli": "^6.0.0", - "esbuild": "^0.24.2", + "esbuild": "^0.25.0", "eslint": "^8.57.0", "eslint-config-prettier": "^10.0.1", "mocha": "^10.8.2", @@ -293,13 +293,14 @@ "dev": true }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.2.tgz", - "integrity": "sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.0.tgz", + "integrity": "sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==", "cpu": [ "ppc64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "aix" @@ -309,13 +310,14 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.24.2.tgz", - "integrity": "sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.0.tgz", + "integrity": "sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" @@ -325,13 +327,14 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.24.2.tgz", - "integrity": "sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.0.tgz", + "integrity": "sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" @@ -341,13 +344,14 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.24.2.tgz", - "integrity": "sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.0.tgz", + "integrity": "sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" @@ -357,13 +361,14 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.2.tgz", - "integrity": "sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.0.tgz", + "integrity": "sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -373,13 +378,14 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.24.2.tgz", - "integrity": "sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.0.tgz", + "integrity": "sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -389,13 +395,14 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.2.tgz", - "integrity": "sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.0.tgz", + "integrity": "sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "freebsd" @@ -405,13 +412,14 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.24.2.tgz", - "integrity": "sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.0.tgz", + "integrity": "sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "freebsd" @@ -421,13 +429,14 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.24.2.tgz", - "integrity": "sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.0.tgz", + "integrity": "sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -437,13 +446,14 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.24.2.tgz", - "integrity": "sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.0.tgz", + "integrity": "sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -453,13 +463,14 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.24.2.tgz", - "integrity": "sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.0.tgz", + "integrity": "sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==", "cpu": [ "ia32" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -469,13 +480,14 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.24.2.tgz", - "integrity": "sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.0.tgz", + "integrity": "sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==", "cpu": [ "loong64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -485,13 +497,14 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.24.2.tgz", - "integrity": "sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.0.tgz", + "integrity": "sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==", "cpu": [ "mips64el" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -501,13 +514,14 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.24.2.tgz", - "integrity": "sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.0.tgz", + "integrity": "sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==", "cpu": [ "ppc64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -517,13 +531,14 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.24.2.tgz", - "integrity": "sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.0.tgz", + "integrity": "sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==", "cpu": [ "riscv64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -533,13 +548,14 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.24.2.tgz", - "integrity": "sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.0.tgz", + "integrity": "sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==", "cpu": [ "s390x" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -549,13 +565,14 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.24.2.tgz", - "integrity": "sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.0.tgz", + "integrity": "sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -565,13 +582,14 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.24.2.tgz", - "integrity": "sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.0.tgz", + "integrity": "sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "netbsd" @@ -581,13 +599,14 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.24.2.tgz", - "integrity": "sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.0.tgz", + "integrity": "sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "netbsd" @@ -597,13 +616,14 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.2.tgz", - "integrity": "sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.0.tgz", + "integrity": "sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "openbsd" @@ -613,13 +633,14 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.24.2.tgz", - "integrity": "sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.0.tgz", + "integrity": "sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "openbsd" @@ -629,13 +650,14 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.24.2.tgz", - "integrity": "sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.0.tgz", + "integrity": "sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "sunos" @@ -645,13 +667,14 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.24.2.tgz", - "integrity": "sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.0.tgz", + "integrity": "sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -661,13 +684,14 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.24.2.tgz", - "integrity": "sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.0.tgz", + "integrity": "sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==", "cpu": [ "ia32" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -677,13 +701,14 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.24.2.tgz", - "integrity": "sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.0.tgz", + "integrity": "sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -2828,11 +2853,12 @@ } }, "node_modules/esbuild": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.2.tgz", - "integrity": "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.0.tgz", + "integrity": "sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==", "dev": true, "hasInstallScript": true, + "license": "MIT", "bin": { "esbuild": "bin/esbuild" }, @@ -2840,31 +2866,31 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.24.2", - "@esbuild/android-arm": "0.24.2", - "@esbuild/android-arm64": "0.24.2", - "@esbuild/android-x64": "0.24.2", - "@esbuild/darwin-arm64": "0.24.2", - "@esbuild/darwin-x64": "0.24.2", - "@esbuild/freebsd-arm64": "0.24.2", - "@esbuild/freebsd-x64": "0.24.2", - "@esbuild/linux-arm": "0.24.2", - "@esbuild/linux-arm64": "0.24.2", - "@esbuild/linux-ia32": "0.24.2", - "@esbuild/linux-loong64": "0.24.2", - "@esbuild/linux-mips64el": "0.24.2", - "@esbuild/linux-ppc64": "0.24.2", - "@esbuild/linux-riscv64": "0.24.2", - "@esbuild/linux-s390x": "0.24.2", - "@esbuild/linux-x64": "0.24.2", - "@esbuild/netbsd-arm64": "0.24.2", - "@esbuild/netbsd-x64": "0.24.2", - "@esbuild/openbsd-arm64": "0.24.2", - "@esbuild/openbsd-x64": "0.24.2", - "@esbuild/sunos-x64": "0.24.2", - "@esbuild/win32-arm64": "0.24.2", - "@esbuild/win32-ia32": "0.24.2", - "@esbuild/win32-x64": "0.24.2" + "@esbuild/aix-ppc64": "0.25.0", + "@esbuild/android-arm": "0.25.0", + "@esbuild/android-arm64": "0.25.0", + "@esbuild/android-x64": "0.25.0", + "@esbuild/darwin-arm64": "0.25.0", + "@esbuild/darwin-x64": "0.25.0", + "@esbuild/freebsd-arm64": "0.25.0", + "@esbuild/freebsd-x64": "0.25.0", + "@esbuild/linux-arm": "0.25.0", + "@esbuild/linux-arm64": "0.25.0", + "@esbuild/linux-ia32": "0.25.0", + "@esbuild/linux-loong64": "0.25.0", + "@esbuild/linux-mips64el": "0.25.0", + "@esbuild/linux-ppc64": "0.25.0", + "@esbuild/linux-riscv64": "0.25.0", + "@esbuild/linux-s390x": "0.25.0", + "@esbuild/linux-x64": "0.25.0", + "@esbuild/netbsd-arm64": "0.25.0", + "@esbuild/netbsd-x64": "0.25.0", + "@esbuild/openbsd-arm64": "0.25.0", + "@esbuild/openbsd-x64": "0.25.0", + "@esbuild/sunos-x64": "0.25.0", + "@esbuild/win32-arm64": "0.25.0", + "@esbuild/win32-ia32": "0.25.0", + "@esbuild/win32-x64": "0.25.0" } }, "node_modules/escalade": { @@ -6700,177 +6726,177 @@ "dev": true }, "@esbuild/aix-ppc64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.2.tgz", - "integrity": "sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.0.tgz", + "integrity": "sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==", "dev": true, "optional": true }, "@esbuild/android-arm": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.24.2.tgz", - "integrity": "sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.0.tgz", + "integrity": "sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==", "dev": true, "optional": true }, "@esbuild/android-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.24.2.tgz", - "integrity": "sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.0.tgz", + "integrity": "sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==", "dev": true, "optional": true }, "@esbuild/android-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.24.2.tgz", - "integrity": "sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.0.tgz", + "integrity": "sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==", "dev": true, "optional": true }, "@esbuild/darwin-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.2.tgz", - "integrity": "sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.0.tgz", + "integrity": "sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==", "dev": true, "optional": true }, "@esbuild/darwin-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.24.2.tgz", - "integrity": "sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.0.tgz", + "integrity": "sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==", "dev": true, "optional": true }, "@esbuild/freebsd-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.2.tgz", - "integrity": "sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.0.tgz", + "integrity": "sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==", "dev": true, "optional": true }, "@esbuild/freebsd-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.24.2.tgz", - "integrity": "sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.0.tgz", + "integrity": "sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==", "dev": true, "optional": true }, "@esbuild/linux-arm": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.24.2.tgz", - "integrity": "sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.0.tgz", + "integrity": "sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==", "dev": true, "optional": true }, "@esbuild/linux-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.24.2.tgz", - "integrity": "sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.0.tgz", + "integrity": "sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==", "dev": true, "optional": true }, "@esbuild/linux-ia32": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.24.2.tgz", - "integrity": "sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.0.tgz", + "integrity": "sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==", "dev": true, "optional": true }, "@esbuild/linux-loong64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.24.2.tgz", - "integrity": "sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.0.tgz", + "integrity": "sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==", "dev": true, "optional": true }, "@esbuild/linux-mips64el": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.24.2.tgz", - "integrity": "sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.0.tgz", + "integrity": "sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==", "dev": true, "optional": true }, "@esbuild/linux-ppc64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.24.2.tgz", - "integrity": "sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.0.tgz", + "integrity": "sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==", "dev": true, "optional": true }, "@esbuild/linux-riscv64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.24.2.tgz", - "integrity": "sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.0.tgz", + "integrity": "sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==", "dev": true, "optional": true }, "@esbuild/linux-s390x": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.24.2.tgz", - "integrity": "sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.0.tgz", + "integrity": "sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==", "dev": true, "optional": true }, "@esbuild/linux-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.24.2.tgz", - "integrity": "sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.0.tgz", + "integrity": "sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==", "dev": true, "optional": true }, "@esbuild/netbsd-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.24.2.tgz", - "integrity": "sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.0.tgz", + "integrity": "sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==", "dev": true, "optional": true }, "@esbuild/netbsd-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.24.2.tgz", - "integrity": "sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.0.tgz", + "integrity": "sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==", "dev": true, "optional": true }, "@esbuild/openbsd-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.2.tgz", - "integrity": "sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.0.tgz", + "integrity": "sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==", "dev": true, "optional": true }, "@esbuild/openbsd-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.24.2.tgz", - "integrity": "sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.0.tgz", + "integrity": "sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==", "dev": true, "optional": true }, "@esbuild/sunos-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.24.2.tgz", - "integrity": "sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.0.tgz", + "integrity": "sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==", "dev": true, "optional": true }, "@esbuild/win32-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.24.2.tgz", - "integrity": "sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.0.tgz", + "integrity": "sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==", "dev": true, "optional": true }, "@esbuild/win32-ia32": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.24.2.tgz", - "integrity": "sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.0.tgz", + "integrity": "sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==", "dev": true, "optional": true }, "@esbuild/win32-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.24.2.tgz", - "integrity": "sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.0.tgz", + "integrity": "sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==", "dev": true, "optional": true }, @@ -8416,36 +8442,36 @@ "dev": true }, "esbuild": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.2.tgz", - "integrity": "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==", - "dev": true, - "requires": { - "@esbuild/aix-ppc64": "0.24.2", - "@esbuild/android-arm": "0.24.2", - "@esbuild/android-arm64": "0.24.2", - "@esbuild/android-x64": "0.24.2", - "@esbuild/darwin-arm64": "0.24.2", - "@esbuild/darwin-x64": "0.24.2", - "@esbuild/freebsd-arm64": "0.24.2", - "@esbuild/freebsd-x64": "0.24.2", - "@esbuild/linux-arm": "0.24.2", - "@esbuild/linux-arm64": "0.24.2", - "@esbuild/linux-ia32": "0.24.2", - "@esbuild/linux-loong64": "0.24.2", - "@esbuild/linux-mips64el": "0.24.2", - "@esbuild/linux-ppc64": "0.24.2", - "@esbuild/linux-riscv64": "0.24.2", - "@esbuild/linux-s390x": "0.24.2", - "@esbuild/linux-x64": "0.24.2", - "@esbuild/netbsd-arm64": "0.24.2", - "@esbuild/netbsd-x64": "0.24.2", - "@esbuild/openbsd-arm64": "0.24.2", - "@esbuild/openbsd-x64": "0.24.2", - "@esbuild/sunos-x64": "0.24.2", - "@esbuild/win32-arm64": "0.24.2", - "@esbuild/win32-ia32": "0.24.2", - "@esbuild/win32-x64": "0.24.2" + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.0.tgz", + "integrity": "sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==", + "dev": true, + "requires": { + "@esbuild/aix-ppc64": "0.25.0", + "@esbuild/android-arm": "0.25.0", + "@esbuild/android-arm64": "0.25.0", + "@esbuild/android-x64": "0.25.0", + "@esbuild/darwin-arm64": "0.25.0", + "@esbuild/darwin-x64": "0.25.0", + "@esbuild/freebsd-arm64": "0.25.0", + "@esbuild/freebsd-x64": "0.25.0", + "@esbuild/linux-arm": "0.25.0", + "@esbuild/linux-arm64": "0.25.0", + "@esbuild/linux-ia32": "0.25.0", + "@esbuild/linux-loong64": "0.25.0", + "@esbuild/linux-mips64el": "0.25.0", + "@esbuild/linux-ppc64": "0.25.0", + "@esbuild/linux-riscv64": "0.25.0", + "@esbuild/linux-s390x": "0.25.0", + "@esbuild/linux-x64": "0.25.0", + "@esbuild/netbsd-arm64": "0.25.0", + "@esbuild/netbsd-x64": "0.25.0", + "@esbuild/openbsd-arm64": "0.25.0", + "@esbuild/openbsd-x64": "0.25.0", + "@esbuild/sunos-x64": "0.25.0", + "@esbuild/win32-arm64": "0.25.0", + "@esbuild/win32-ia32": "0.25.0", + "@esbuild/win32-x64": "0.25.0" } }, "escalade": { diff --git a/package.json b/package.json index 2518ea3f8..14c653778 100644 --- a/package.json +++ b/package.json @@ -1424,7 +1424,7 @@ "chai-as-promised": "^7.1.2", "chai-subset": "^1.6.0", "del-cli": "^6.0.0", - "esbuild": "^0.24.2", + "esbuild": "^0.25.0", "eslint": "^8.57.0", "eslint-config-prettier": "^10.0.1", "mocha": "^10.8.2", From e17311da50e38b468cf65c78a89d590ec3a2687b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 12 Feb 2025 10:24:59 -0500 Subject: [PATCH 03/86] Bump the all-dependencies group across 1 directory with 4 updates (#1380) Bumps the all-dependencies group with 4 updates in the / directory: [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin), [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser), [mock-fs](https://github.com/tschaub/mock-fs) and [prettier](https://github.com/prettier/prettier). Updates `@typescript-eslint/eslint-plugin` from 8.23.0 to 8.24.0 - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.24.0/packages/eslint-plugin) Updates `@typescript-eslint/parser` from 8.23.0 to 8.24.0 - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.24.0/packages/parser) Updates `mock-fs` from 5.4.1 to 5.5.0 - [Release notes](https://github.com/tschaub/mock-fs/releases) - [Changelog](https://github.com/tschaub/mock-fs/blob/main/changelog.md) - [Commits](https://github.com/tschaub/mock-fs/compare/v5.4.1...v5.5.0) Updates `prettier` from 3.4.2 to 3.5.0 - [Release notes](https://github.com/prettier/prettier/releases) - [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md) - [Commits](https://github.com/prettier/prettier/compare/3.4.2...3.5.0) --- updated-dependencies: - dependency-name: "@typescript-eslint/eslint-plugin" dependency-type: direct:development update-type: version-update:semver-minor dependency-group: all-dependencies - dependency-name: "@typescript-eslint/parser" dependency-type: direct:development update-type: version-update:semver-minor dependency-group: all-dependencies - dependency-name: mock-fs dependency-type: direct:development update-type: version-update:semver-minor dependency-group: all-dependencies - dependency-name: prettier dependency-type: direct:development update-type: version-update:semver-minor dependency-group: all-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 202 +++++++++++++++++++++++----------------------- package.json | 8 +- 2 files changed, 106 insertions(+), 104 deletions(-) diff --git a/package-lock.json b/package-lock.json index e7e6af057..dc5d21858 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,8 +30,8 @@ "@types/sinon-chai": "^3.2.12", "@types/vscode": "^1.88.0", "@types/xml2js": "^0.4.14", - "@typescript-eslint/eslint-plugin": "^8.23.0", - "@typescript-eslint/parser": "^8.23.0", + "@typescript-eslint/eslint-plugin": "^8.24.0", + "@typescript-eslint/parser": "^8.24.0", "@vscode/debugprotocol": "^1.68.0", "@vscode/test-cli": "^0.0.10", "@vscode/test-electron": "^2.4.1", @@ -44,9 +44,9 @@ "eslint": "^8.57.0", "eslint-config-prettier": "^10.0.1", "mocha": "^10.8.2", - "mock-fs": "^5.4.1", + "mock-fs": "^5.5.0", "node-pty": "^1.0.0", - "prettier": "^3.4.2", + "prettier": "^3.5.0", "semver": "^7.7.1", "simple-git": "^3.27.0", "sinon": "^19.0.2", @@ -1198,17 +1198,17 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.23.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.23.0.tgz", - "integrity": "sha512-vBz65tJgRrA1Q5gWlRfvoH+w943dq9K1p1yDBY2pc+a1nbBLZp7fB9+Hk8DaALUbzjqlMfgaqlVPT1REJdkt/w==", + "version": "8.24.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.24.0.tgz", + "integrity": "sha512-aFcXEJJCI4gUdXgoo/j9udUYIHgF23MFkg09LFz2dzEmU0+1Plk4rQWv/IYKvPHAtlkkGoB3m5e6oUp+JPsNaQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.23.0", - "@typescript-eslint/type-utils": "8.23.0", - "@typescript-eslint/utils": "8.23.0", - "@typescript-eslint/visitor-keys": "8.23.0", + "@typescript-eslint/scope-manager": "8.24.0", + "@typescript-eslint/type-utils": "8.24.0", + "@typescript-eslint/utils": "8.24.0", + "@typescript-eslint/visitor-keys": "8.24.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -1228,16 +1228,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.23.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.23.0.tgz", - "integrity": "sha512-h2lUByouOXFAlMec2mILeELUbME5SZRN/7R9Cw2RD2lRQQY08MWMM+PmVVKKJNK1aIwqTo9t/0CvOxwPbRIE2Q==", + "version": "8.24.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.24.0.tgz", + "integrity": "sha512-MFDaO9CYiard9j9VepMNa9MTcqVvSny2N4hkY6roquzj8pdCBRENhErrteaQuu7Yjn1ppk0v1/ZF9CG3KIlrTA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.23.0", - "@typescript-eslint/types": "8.23.0", - "@typescript-eslint/typescript-estree": "8.23.0", - "@typescript-eslint/visitor-keys": "8.23.0", + "@typescript-eslint/scope-manager": "8.24.0", + "@typescript-eslint/types": "8.24.0", + "@typescript-eslint/typescript-estree": "8.24.0", + "@typescript-eslint/visitor-keys": "8.24.0", "debug": "^4.3.4" }, "engines": { @@ -1253,14 +1253,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.23.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.23.0.tgz", - "integrity": "sha512-OGqo7+dXHqI7Hfm+WqkZjKjsiRtFUQHPdGMXzk5mYXhJUedO7e/Y7i8AK3MyLMgZR93TX4bIzYrfyVjLC+0VSw==", + "version": "8.24.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.24.0.tgz", + "integrity": "sha512-HZIX0UByphEtdVBKaQBgTDdn9z16l4aTUz8e8zPQnyxwHBtf5vtl1L+OhH+m1FGV9DrRmoDuYKqzVrvWDcDozw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.23.0", - "@typescript-eslint/visitor-keys": "8.23.0" + "@typescript-eslint/types": "8.24.0", + "@typescript-eslint/visitor-keys": "8.24.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1271,14 +1271,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.23.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.23.0.tgz", - "integrity": "sha512-iIuLdYpQWZKbiH+RkCGc6iu+VwscP5rCtQ1lyQ7TYuKLrcZoeJVpcLiG8DliXVkUxirW/PWlmS+d6yD51L9jvA==", + "version": "8.24.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.24.0.tgz", + "integrity": "sha512-8fitJudrnY8aq0F1wMiPM1UUgiXQRJ5i8tFjq9kGfRajU+dbPyOuHbl0qRopLEidy0MwqgTHDt6CnSeXanNIwA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.23.0", - "@typescript-eslint/utils": "8.23.0", + "@typescript-eslint/typescript-estree": "8.24.0", + "@typescript-eslint/utils": "8.24.0", "debug": "^4.3.4", "ts-api-utils": "^2.0.1" }, @@ -1295,9 +1295,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.23.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.23.0.tgz", - "integrity": "sha512-1sK4ILJbCmZOTt9k4vkoulT6/y5CHJ1qUYxqpF1K/DBAd8+ZUL4LlSCxOssuH5m4rUaaN0uS0HlVPvd45zjduQ==", + "version": "8.24.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.24.0.tgz", + "integrity": "sha512-VacJCBTyje7HGAw7xp11q439A+zeGG0p0/p2zsZwpnMzjPB5WteaWqt4g2iysgGFafrqvyLWqq6ZPZAOCoefCw==", "dev": true, "license": "MIT", "engines": { @@ -1309,14 +1309,14 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.23.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.23.0.tgz", - "integrity": "sha512-LcqzfipsB8RTvH8FX24W4UUFk1bl+0yTOf9ZA08XngFwMg4Kj8A+9hwz8Cr/ZS4KwHrmo9PJiLZkOt49vPnuvQ==", + "version": "8.24.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.24.0.tgz", + "integrity": "sha512-ITjYcP0+8kbsvT9bysygfIfb+hBj6koDsu37JZG7xrCiy3fPJyNmfVtaGsgTUSEuTzcvME5YI5uyL5LD1EV5ZQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.23.0", - "@typescript-eslint/visitor-keys": "8.23.0", + "@typescript-eslint/types": "8.24.0", + "@typescript-eslint/visitor-keys": "8.24.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -1362,16 +1362,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.23.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.23.0.tgz", - "integrity": "sha512-uB/+PSo6Exu02b5ZEiVtmY6RVYO7YU5xqgzTIVZwTHvvK3HsL8tZZHFaTLFtRG3CsV4A5mhOv+NZx5BlhXPyIA==", + "version": "8.24.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.24.0.tgz", + "integrity": "sha512-07rLuUBElvvEb1ICnafYWr4hk8/U7X9RDCOqd9JcAMtjh/9oRmcfN4yGzbPVirgMR0+HLVHehmu19CWeh7fsmQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.23.0", - "@typescript-eslint/types": "8.23.0", - "@typescript-eslint/typescript-estree": "8.23.0" + "@typescript-eslint/scope-manager": "8.24.0", + "@typescript-eslint/types": "8.24.0", + "@typescript-eslint/typescript-estree": "8.24.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1386,13 +1386,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.23.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.23.0.tgz", - "integrity": "sha512-oWWhcWDLwDfu++BGTZcmXWqpwtkwb5o7fxUIGksMQQDSdPW9prsSnfIOZMlsj4vBOSrcnjIUZMiIjODgGosFhQ==", + "version": "8.24.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.24.0.tgz", + "integrity": "sha512-kArLq83QxGLbuHrTMoOEWO+l2MwsNS2TGISEdx8xgqpkbytB07XmlQyQdNDrCc1ecSqx0cnmhGvpX+VBwqqSkg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.23.0", + "@typescript-eslint/types": "8.24.0", "eslint-visitor-keys": "^4.2.0" }, "engines": { @@ -4404,10 +4404,11 @@ } }, "node_modules/mock-fs": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/mock-fs/-/mock-fs-5.4.1.tgz", - "integrity": "sha512-sz/Q8K1gXXXHR+qr0GZg2ysxCRr323kuN10O7CtQjraJsFDJ4SJ+0I5MzALz7aRp9lHk8Cc/YdsT95h9Ka1aFw==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/mock-fs/-/mock-fs-5.5.0.tgz", + "integrity": "sha512-d/P1M/RacgM3dB0sJ8rjeRNXxtapkPCUnMGmIN0ixJ16F/E4GUZCvWcSGfWGz8eaXYvn1s9baUwNjI4LOPEjiA==", "dev": true, + "license": "MIT", "engines": { "node": ">=12.0.0" } @@ -4947,10 +4948,11 @@ } }, "node_modules/prettier": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz", - "integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.0.tgz", + "integrity": "sha512-quyMrVt6svPS7CjQ9gKb3GLEX/rl3BCL2oa/QkNcXv4YNVBC9olt3s+H7ukto06q7B1Qz46PbrKLO34PR6vXcA==", "dev": true, + "license": "MIT", "bin": { "prettier": "bin/prettier.cjs" }, @@ -7284,16 +7286,16 @@ } }, "@typescript-eslint/eslint-plugin": { - "version": "8.23.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.23.0.tgz", - "integrity": "sha512-vBz65tJgRrA1Q5gWlRfvoH+w943dq9K1p1yDBY2pc+a1nbBLZp7fB9+Hk8DaALUbzjqlMfgaqlVPT1REJdkt/w==", + "version": "8.24.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.24.0.tgz", + "integrity": "sha512-aFcXEJJCI4gUdXgoo/j9udUYIHgF23MFkg09LFz2dzEmU0+1Plk4rQWv/IYKvPHAtlkkGoB3m5e6oUp+JPsNaQ==", "dev": true, "requires": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.23.0", - "@typescript-eslint/type-utils": "8.23.0", - "@typescript-eslint/utils": "8.23.0", - "@typescript-eslint/visitor-keys": "8.23.0", + "@typescript-eslint/scope-manager": "8.24.0", + "@typescript-eslint/type-utils": "8.24.0", + "@typescript-eslint/utils": "8.24.0", + "@typescript-eslint/visitor-keys": "8.24.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -7301,54 +7303,54 @@ } }, "@typescript-eslint/parser": { - "version": "8.23.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.23.0.tgz", - "integrity": "sha512-h2lUByouOXFAlMec2mILeELUbME5SZRN/7R9Cw2RD2lRQQY08MWMM+PmVVKKJNK1aIwqTo9t/0CvOxwPbRIE2Q==", + "version": "8.24.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.24.0.tgz", + "integrity": "sha512-MFDaO9CYiard9j9VepMNa9MTcqVvSny2N4hkY6roquzj8pdCBRENhErrteaQuu7Yjn1ppk0v1/ZF9CG3KIlrTA==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "8.23.0", - "@typescript-eslint/types": "8.23.0", - "@typescript-eslint/typescript-estree": "8.23.0", - "@typescript-eslint/visitor-keys": "8.23.0", + "@typescript-eslint/scope-manager": "8.24.0", + "@typescript-eslint/types": "8.24.0", + "@typescript-eslint/typescript-estree": "8.24.0", + "@typescript-eslint/visitor-keys": "8.24.0", "debug": "^4.3.4" } }, "@typescript-eslint/scope-manager": { - "version": "8.23.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.23.0.tgz", - "integrity": "sha512-OGqo7+dXHqI7Hfm+WqkZjKjsiRtFUQHPdGMXzk5mYXhJUedO7e/Y7i8AK3MyLMgZR93TX4bIzYrfyVjLC+0VSw==", + "version": "8.24.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.24.0.tgz", + "integrity": "sha512-HZIX0UByphEtdVBKaQBgTDdn9z16l4aTUz8e8zPQnyxwHBtf5vtl1L+OhH+m1FGV9DrRmoDuYKqzVrvWDcDozw==", "dev": true, "requires": { - "@typescript-eslint/types": "8.23.0", - "@typescript-eslint/visitor-keys": "8.23.0" + "@typescript-eslint/types": "8.24.0", + "@typescript-eslint/visitor-keys": "8.24.0" } }, "@typescript-eslint/type-utils": { - "version": "8.23.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.23.0.tgz", - "integrity": "sha512-iIuLdYpQWZKbiH+RkCGc6iu+VwscP5rCtQ1lyQ7TYuKLrcZoeJVpcLiG8DliXVkUxirW/PWlmS+d6yD51L9jvA==", + "version": "8.24.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.24.0.tgz", + "integrity": "sha512-8fitJudrnY8aq0F1wMiPM1UUgiXQRJ5i8tFjq9kGfRajU+dbPyOuHbl0qRopLEidy0MwqgTHDt6CnSeXanNIwA==", "dev": true, "requires": { - "@typescript-eslint/typescript-estree": "8.23.0", - "@typescript-eslint/utils": "8.23.0", + "@typescript-eslint/typescript-estree": "8.24.0", + "@typescript-eslint/utils": "8.24.0", "debug": "^4.3.4", "ts-api-utils": "^2.0.1" } }, "@typescript-eslint/types": { - "version": "8.23.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.23.0.tgz", - "integrity": "sha512-1sK4ILJbCmZOTt9k4vkoulT6/y5CHJ1qUYxqpF1K/DBAd8+ZUL4LlSCxOssuH5m4rUaaN0uS0HlVPvd45zjduQ==", + "version": "8.24.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.24.0.tgz", + "integrity": "sha512-VacJCBTyje7HGAw7xp11q439A+zeGG0p0/p2zsZwpnMzjPB5WteaWqt4g2iysgGFafrqvyLWqq6ZPZAOCoefCw==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "8.23.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.23.0.tgz", - "integrity": "sha512-LcqzfipsB8RTvH8FX24W4UUFk1bl+0yTOf9ZA08XngFwMg4Kj8A+9hwz8Cr/ZS4KwHrmo9PJiLZkOt49vPnuvQ==", + "version": "8.24.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.24.0.tgz", + "integrity": "sha512-ITjYcP0+8kbsvT9bysygfIfb+hBj6koDsu37JZG7xrCiy3fPJyNmfVtaGsgTUSEuTzcvME5YI5uyL5LD1EV5ZQ==", "dev": true, "requires": { - "@typescript-eslint/types": "8.23.0", - "@typescript-eslint/visitor-keys": "8.23.0", + "@typescript-eslint/types": "8.24.0", + "@typescript-eslint/visitor-keys": "8.24.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -7378,24 +7380,24 @@ } }, "@typescript-eslint/utils": { - "version": "8.23.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.23.0.tgz", - "integrity": "sha512-uB/+PSo6Exu02b5ZEiVtmY6RVYO7YU5xqgzTIVZwTHvvK3HsL8tZZHFaTLFtRG3CsV4A5mhOv+NZx5BlhXPyIA==", + "version": "8.24.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.24.0.tgz", + "integrity": "sha512-07rLuUBElvvEb1ICnafYWr4hk8/U7X9RDCOqd9JcAMtjh/9oRmcfN4yGzbPVirgMR0+HLVHehmu19CWeh7fsmQ==", "dev": true, "requires": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.23.0", - "@typescript-eslint/types": "8.23.0", - "@typescript-eslint/typescript-estree": "8.23.0" + "@typescript-eslint/scope-manager": "8.24.0", + "@typescript-eslint/types": "8.24.0", + "@typescript-eslint/typescript-estree": "8.24.0" } }, "@typescript-eslint/visitor-keys": { - "version": "8.23.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.23.0.tgz", - "integrity": "sha512-oWWhcWDLwDfu++BGTZcmXWqpwtkwb5o7fxUIGksMQQDSdPW9prsSnfIOZMlsj4vBOSrcnjIUZMiIjODgGosFhQ==", + "version": "8.24.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.24.0.tgz", + "integrity": "sha512-kArLq83QxGLbuHrTMoOEWO+l2MwsNS2TGISEdx8xgqpkbytB07XmlQyQdNDrCc1ecSqx0cnmhGvpX+VBwqqSkg==", "dev": true, "requires": { - "@typescript-eslint/types": "8.23.0", + "@typescript-eslint/types": "8.24.0", "eslint-visitor-keys": "^4.2.0" }, "dependencies": { @@ -9604,9 +9606,9 @@ } }, "mock-fs": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/mock-fs/-/mock-fs-5.4.1.tgz", - "integrity": "sha512-sz/Q8K1gXXXHR+qr0GZg2ysxCRr323kuN10O7CtQjraJsFDJ4SJ+0I5MzALz7aRp9lHk8Cc/YdsT95h9Ka1aFw==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/mock-fs/-/mock-fs-5.5.0.tgz", + "integrity": "sha512-d/P1M/RacgM3dB0sJ8rjeRNXxtapkPCUnMGmIN0ixJ16F/E4GUZCvWcSGfWGz8eaXYvn1s9baUwNjI4LOPEjiA==", "dev": true }, "ms": { @@ -10004,9 +10006,9 @@ "dev": true }, "prettier": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz", - "integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.0.tgz", + "integrity": "sha512-quyMrVt6svPS7CjQ9gKb3GLEX/rl3BCL2oa/QkNcXv4YNVBC9olt3s+H7ukto06q7B1Qz46PbrKLO34PR6vXcA==", "dev": true }, "process-nextick-args": { diff --git a/package.json b/package.json index 14c653778..48a4c9eb4 100644 --- a/package.json +++ b/package.json @@ -1414,8 +1414,8 @@ "@types/sinon-chai": "^3.2.12", "@types/vscode": "^1.88.0", "@types/xml2js": "^0.4.14", - "@typescript-eslint/eslint-plugin": "^8.23.0", - "@typescript-eslint/parser": "^8.23.0", + "@typescript-eslint/eslint-plugin": "^8.24.0", + "@typescript-eslint/parser": "^8.24.0", "@vscode/debugprotocol": "^1.68.0", "@vscode/test-cli": "^0.0.10", "@vscode/test-electron": "^2.4.1", @@ -1428,9 +1428,9 @@ "eslint": "^8.57.0", "eslint-config-prettier": "^10.0.1", "mocha": "^10.8.2", - "mock-fs": "^5.4.1", + "mock-fs": "^5.5.0", "node-pty": "^1.0.0", - "prettier": "^3.4.2", + "prettier": "^3.5.0", "semver": "^7.7.1", "simple-git": "^3.27.0", "sinon": "^19.0.2", From 4fa67aca4acf054590ebee3e7d1396485ab7a8bd Mon Sep 17 00:00:00 2001 From: Paul LeMarquand Date: Wed, 12 Feb 2025 10:25:31 -0500 Subject: [PATCH 04/86] Show project dependencies hierarchically (#1339) * Show project dependencies hierarchically Currently the Package Dependencies view lists all dependencies in a flat list. This list not only includes those explicitly defined in the project's Package.swift, but also all the dependencies of dependencies. As such its difficult to tell at a glance how a dependency is included. It can also be confusing to see dependencies in the list that you did not explicitly add. Instead, show a top level list that is only the dependencies explicitly defined in Package.swift. Expanding one shows any child dependencies of the dependency. The files in the dependency's folder are still shown as well, just after any child deps. --- assets/test/dependencies/Package.swift | 2 +- assets/test/dependencies/Sources/main.swift | 2 +- .../test/identity-different/Package.resolved | 10 +- assets/test/identity-different/Package.swift | 4 +- package.json | 36 +- src/FolderContext.ts | 11 +- src/PackageWatcher.ts | 28 ++ src/SwiftPackage.ts | 161 +++++++- src/WorkspaceContext.ts | 4 + src/commands.ts | 9 + .../dependencies/updateDepViewList.ts | 23 ++ src/contextKeys.ts | 15 + src/ui/PackageDependencyProvider.ts | 369 ++++++------------ test/integration-tests/SwiftPackage.test.ts | 2 +- .../commands/dependency.test.ts | 30 +- .../ui/PackageDependencyProvider.test.ts | 22 ++ .../ui/PackageDependencyProvider.test.ts | 64 ++- 17 files changed, 495 insertions(+), 297 deletions(-) create mode 100644 src/commands/dependencies/updateDepViewList.ts diff --git a/assets/test/dependencies/Package.swift b/assets/test/dependencies/Package.swift index 51f61f4ca..ba5acdb59 100644 --- a/assets/test/dependencies/Package.swift +++ b/assets/test/dependencies/Package.swift @@ -12,7 +12,7 @@ let package = Package( targets: [ .executableTarget( name: "dependencies", - dependencies: [.product(name: "MarkdownLib", package: "swift-markdown")], + dependencies: [.product(name: "Markdown", package: "swift-markdown")], path: "Sources"), ] ) diff --git a/assets/test/dependencies/Sources/main.swift b/assets/test/dependencies/Sources/main.swift index 8bf0d5043..1581c5f07 100644 --- a/assets/test/dependencies/Sources/main.swift +++ b/assets/test/dependencies/Sources/main.swift @@ -1,4 +1,4 @@ -import MarkdownLib +import Markdown print("Test Asset:(dependencies)") print(a) \ No newline at end of file diff --git a/assets/test/identity-different/Package.resolved b/assets/test/identity-different/Package.resolved index fe4f52231..0b7133f7d 100644 --- a/assets/test/identity-different/Package.resolved +++ b/assets/test/identity-different/Package.resolved @@ -2,12 +2,12 @@ "object": { "pins": [ { - "package": "cmark-gfm", - "repositoryURL": "https://github.com/apple/swift-cmark.git", + "package": "swift-log", + "repositoryURL": "https://github.com/apple/swift-log.git", "state": { - "branch": "gfm", - "revision": "bfdc057b5a02fc65af20771a7ba08f9c944eb117", - "version": null + "branch": null, + "revision": "96a2f8a0fa41e9e09af4585e2724c4e825410b91", + "version": "1.6.2" } } ] diff --git a/assets/test/identity-different/Package.swift b/assets/test/identity-different/Package.swift index 2d9818878..5bec37a19 100644 --- a/assets/test/identity-different/Package.swift +++ b/assets/test/identity-different/Package.swift @@ -13,13 +13,13 @@ let package = Package( ], dependencies: [ // Dependencies declare other packages that this package depends on. - .package(name: "cmark", url: "https://github.com/apple/swift-cmark.git", .branch("gfm")), + .package(url: "https://github.com/apple/swift-log.git", from: "1.5.2"), ], targets: [ // Targets are the basic building blocks of a package. A target can define a module or a test suite. // Targets can depend on other targets in this package, and on products in packages this package depends on. .target( name: "identity-different", - dependencies: ["cmark"]), + dependencies: [.product(name: "Logging", package: "swift-log")]), ] ) diff --git a/package.json b/package.json index 48a4c9eb4..2e76a780c 100644 --- a/package.json +++ b/package.json @@ -129,6 +129,18 @@ "icon": "$(refresh)", "category": "Swift" }, + { + "command": "swift.flatDependenciesList", + "title": "Flat Dependencies List View", + "icon": "$(list-flat)", + "category": "Swift" + }, + { + "command": "swift.nestedDependenciesList", + "title": "Nested Dependencies List View", + "icon": "$(list-tree)", + "category": "Swift" + }, { "command": "swift.cleanBuild", "title": "Clean Build Folder", @@ -793,6 +805,14 @@ "command": "swift.openPackage", "when": "swift.hasPackage" }, + { + "command": "swift.flatDependenciesList", + "when": "false" + }, + { + "command": "swift.nestedDependenciesList", + "when": "false" + }, { "command": "swift.useLocalDependency", "when": "false" @@ -898,17 +918,27 @@ { "command": "swift.updateDependencies", "when": "view == packageDependencies", - "group": "navigation" + "group": "navigation@1" }, { "command": "swift.resolveDependencies", "when": "view == packageDependencies", - "group": "navigation" + "group": "navigation@2" }, { "command": "swift.resetPackage", "when": "view == packageDependencies", - "group": "navigation" + "group": "navigation@3" + }, + { + "command": "swift.flatDependenciesList", + "when": "view == packageDependencies && !swift.flatDependenciesList", + "group": "navigation@4" + }, + { + "command": "swift.nestedDependenciesList", + "when": "view == packageDependencies && swift.flatDependenciesList", + "group": "navigation@5" } ], "view/item/context": [ diff --git a/src/FolderContext.ts b/src/FolderContext.ts index f95fd8a66..422cb182c 100644 --- a/src/FolderContext.ts +++ b/src/FolderContext.ts @@ -128,13 +128,14 @@ export class FolderContext implements vscode.Disposable { await this.swiftPackage.reloadPackageResolved(); } + /** reload workspace-state.json for this folder */ + async reloadWorkspaceState() { + await this.swiftPackage.reloadWorkspaceState(); + } + /** Load Swift Plugins and store in Package */ async loadSwiftPlugins() { - const plugins = await SwiftPackage.loadPlugins( - this.folder, - this.workspaceContext.toolchain - ); - this.swiftPackage.plugins = plugins; + await this.swiftPackage.loadSwiftPlugins(this.workspaceContext.toolchain); } /** diff --git a/src/PackageWatcher.ts b/src/PackageWatcher.ts index 518811d55..946fe3b5e 100644 --- a/src/PackageWatcher.ts +++ b/src/PackageWatcher.ts @@ -15,6 +15,7 @@ import * as vscode from "vscode"; import { FolderContext } from "./FolderContext"; import { FolderOperation, WorkspaceContext } from "./WorkspaceContext"; +import { BuildFlags } from "./toolchain/BuildFlags"; /** * Watches for changes to **Package.swift** and **Package.resolved**. @@ -25,6 +26,7 @@ import { FolderOperation, WorkspaceContext } from "./WorkspaceContext"; export class PackageWatcher { private packageFileWatcher?: vscode.FileSystemWatcher; private resolvedFileWatcher?: vscode.FileSystemWatcher; + private workspaceStateFileWatcher?: vscode.FileSystemWatcher; constructor( private folderContext: FolderContext, @@ -38,6 +40,7 @@ export class PackageWatcher { install() { this.packageFileWatcher = this.createPackageFileWatcher(); this.resolvedFileWatcher = this.createResolvedFileWatcher(); + this.workspaceStateFileWatcher = this.createWorkspaceStateFileWatcher(); } /** @@ -47,6 +50,7 @@ export class PackageWatcher { dispose() { this.packageFileWatcher?.dispose(); this.resolvedFileWatcher?.dispose(); + this.workspaceStateFileWatcher?.dispose(); } private createPackageFileWatcher(): vscode.FileSystemWatcher { @@ -69,6 +73,20 @@ export class PackageWatcher { return watcher; } + private createWorkspaceStateFileWatcher(): vscode.FileSystemWatcher { + const uri = vscode.Uri.joinPath( + vscode.Uri.file( + BuildFlags.buildDirectoryFromWorkspacePath(this.folderContext.folder.fsPath, true) + ), + "workspace-state.json" + ); + const watcher = vscode.workspace.createFileSystemWatcher(uri.fsPath); + watcher.onDidCreate(async () => await this.handleWorkspaceStateChange()); + watcher.onDidChange(async () => await this.handleWorkspaceStateChange()); + watcher.onDidDelete(async () => await this.handleWorkspaceStateChange()); + return watcher; + } + /** * Handles a create or change event for **Package.swift**. * @@ -95,4 +113,14 @@ export class PackageWatcher { this.workspaceContext.fireEvent(this.folderContext, FolderOperation.resolvedUpdated); } } + + /** + * Handles a create or change event for **.build/workspace-state.json**. + * + * This will resolve any changes in the workspace-state. + */ + private async handleWorkspaceStateChange() { + await this.folderContext.reloadWorkspaceState(); + this.workspaceContext.fireEvent(this.folderContext, FolderOperation.workspaceStateUpdated); + } } diff --git a/src/SwiftPackage.ts b/src/SwiftPackage.ts index d645fbebc..ed8b23c53 100644 --- a/src/SwiftPackage.ts +++ b/src/SwiftPackage.ts @@ -47,10 +47,18 @@ export interface Target { /** Swift Package Manager dependency */ export interface Dependency { identity: string; - type?: string; // fileSystem, sourceControl or registry + type?: string; requirement?: object; url?: string; path?: string; + dependencies: Dependency[]; +} + +export interface ResolvedDependency extends Dependency { + version: string; + type: string; + path: string; + location: string; } /** Swift Package.resolved file */ @@ -187,7 +195,8 @@ export class SwiftPackage implements PackageContents { private constructor( readonly folder: vscode.Uri, private contents: SwiftPackageState, - public resolved: PackageResolved | undefined + public resolved: PackageResolved | undefined, + private workspaceState: WorkspaceState | undefined ) {} /** @@ -195,10 +204,14 @@ export class SwiftPackage implements PackageContents { * @param folder folder package is in * @returns new SwiftPackage */ - static async create(folder: vscode.Uri, toolchain: SwiftToolchain): Promise { + public static async create( + folder: vscode.Uri, + toolchain: SwiftToolchain + ): Promise { const contents = await SwiftPackage.loadPackage(folder, toolchain); const resolved = await SwiftPackage.loadPackageResolved(folder); - return new SwiftPackage(folder, contents, resolved); + const workspaceState = await SwiftPackage.loadWorkspaceState(folder); + return new SwiftPackage(folder, contents, resolved, workspaceState); } /** @@ -211,15 +224,28 @@ export class SwiftPackage implements PackageContents { toolchain: SwiftToolchain ): Promise { try { - let { stdout } = await execSwift(["package", "describe", "--type", "json"], toolchain, { + // Use swift package describe to describe the package targets, products, and platforms + const describe = await execSwift(["package", "describe", "--type", "json"], toolchain, { cwd: folder.fsPath, }); - // remove lines from `swift package describe` until we find a "{" - while (!stdout.startsWith("{")) { - const firstNewLine = stdout.indexOf("\n"); - stdout = stdout.slice(firstNewLine + 1); - } - return JSON.parse(stdout); + const packageState = JSON.parse( + SwiftPackage.trimStdout(describe.stdout) + ) as PackageContents; + + // Use swift package show-dependencies to get the dependencies in a tree format + const dependencies = await execSwift( + ["package", "show-dependencies", "--format", "json"], + toolchain, + { + cwd: folder.fsPath, + } + ); + + packageState.dependencies = JSON.parse( + SwiftPackage.trimStdout(dependencies.stdout) + ).dependencies; + + return packageState; } catch (error) { const execError = error as { stderr: string }; // if caught error and it begins with "error: root manifest" then there is no Package.swift @@ -237,7 +263,9 @@ export class SwiftPackage implements PackageContents { } } - static async loadPackageResolved(folder: vscode.Uri): Promise { + private static async loadPackageResolved( + folder: vscode.Uri + ): Promise { try { const uri = vscode.Uri.joinPath(folder, "Package.resolved"); const contents = await fs.readFile(uri.fsPath, "utf8"); @@ -248,7 +276,7 @@ export class SwiftPackage implements PackageContents { } } - static async loadPlugins( + private static async loadPlugins( folder: vscode.Uri, toolchain: SwiftToolchain ): Promise { @@ -280,12 +308,12 @@ export class SwiftPackage implements PackageContents { * Load workspace-state.json file for swift package * @returns Workspace state */ - public async loadWorkspaceState(): Promise { + private static async loadWorkspaceState( + folder: vscode.Uri + ): Promise { try { const uri = vscode.Uri.joinPath( - vscode.Uri.file( - BuildFlags.buildDirectoryFromWorkspacePath(this.folder.fsPath, true) - ), + vscode.Uri.file(BuildFlags.buildDirectoryFromWorkspacePath(folder.fsPath, true)), "workspace-state.json" ); const contents = await fs.readFile(uri.fsPath, "utf8"); @@ -306,6 +334,14 @@ export class SwiftPackage implements PackageContents { this.resolved = await SwiftPackage.loadPackageResolved(this.folder); } + public async reloadWorkspaceState() { + this.workspaceState = await SwiftPackage.loadWorkspaceState(this.folder); + } + + public async loadSwiftPlugins(toolchain: SwiftToolchain) { + this.plugins = await SwiftPackage.loadPlugins(this.folder, toolchain); + } + /** Return if has valid contents */ public get isValid(): boolean { return isPackage(this.contents); @@ -325,6 +361,88 @@ export class SwiftPackage implements PackageContents { return this.contents !== undefined; } + public rootDependencies(): ResolvedDependency[] { + // Correlate the root dependencies found in the Package.swift with their + // checked out versions in the workspace-state.json. + const result = this.dependencies.map(dependency => + this.resolveDependencyAgainstWorkspaceState(dependency) + ); + return result; + } + + private resolveDependencyAgainstWorkspaceState(dependency: Dependency): ResolvedDependency { + const workspaceStateDep = this.workspaceState?.object.dependencies.find( + dep => dep.packageRef.identity === dependency.identity + ); + return { + ...dependency, + version: workspaceStateDep?.state.checkoutState?.version ?? "", + path: workspaceStateDep + ? this.dependencyPackagePath(workspaceStateDep, this.folder.fsPath) + : "", + type: workspaceStateDep ? this.dependencyType(workspaceStateDep) : "", + location: workspaceStateDep ? workspaceStateDep.packageRef.location : "", + }; + } + + public childDependencies(dependency: Dependency): ResolvedDependency[] { + return dependency.dependencies.map(dep => this.resolveDependencyAgainstWorkspaceState(dep)); + } + + /** + * * Get package source path of dependency + * `editing`: dependency.state.path ?? workspacePath + Packages/ + dependency.subpath + * `local`: dependency.packageRef.location + * `remote`: buildDirectory + checkouts + dependency.packageRef.location + * @param dependency + * @param workspaceFolder + * @return the package path based on the type + */ + private dependencyPackagePath( + dependency: WorkspaceStateDependency, + workspaceFolder: string + ): string { + const type = this.dependencyType(dependency); + if (type === "editing") { + return ( + dependency.state.path ?? path.join(workspaceFolder, "Packages", dependency.subpath) + ); + } else if (type === "local") { + return dependency.state.path ?? dependency.packageRef.location; + } else { + // remote + const buildDirectory = BuildFlags.buildDirectoryFromWorkspacePath( + workspaceFolder, + true + ); + if (dependency.packageRef.kind === "registry") { + return path.join(buildDirectory, "registry", "downloads", dependency.subpath); + } else { + return path.join(buildDirectory, "checkouts", dependency.subpath); + } + } + } + + /** + * Get type of WorkspaceStateDependency for displaying in the tree: real version | edited | local + * @param dependency + * @return "local" | "remote" | "editing" + */ + private dependencyType(dependency: WorkspaceStateDependency): "local" | "remote" | "editing" { + if (dependency.state.name === "edited") { + return "editing"; + } else if ( + dependency.packageRef.kind === "local" || + dependency.packageRef.kind === "fileSystem" + ) { + // need to check for both "local" and "fileSystem" as swift 5.5 and earlier + // use "local" while 5.6 and later use "fileSystem" + return "local"; + } else { + return "remote"; + } + } + /** name of Swift Package */ get name(): string { return (this.contents as PackageContents)?.name ?? ""; @@ -375,6 +493,15 @@ export class SwiftPackage implements PackageContents { const filePath = path.relative(this.folder.fsPath, file); return this.targets.find(target => isPathInsidePath(filePath, target.path)); } + + private static trimStdout(stdout: string): string { + // remove lines from `swift package describe` until we find a "{" + while (!stdout.startsWith("{")) { + const firstNewLine = stdout.indexOf("\n"); + stdout = stdout.slice(firstNewLine + 1); + } + return stdout; + } } export enum TargetType { diff --git a/src/WorkspaceContext.ts b/src/WorkspaceContext.ts index 474412f5a..5e59509be 100644 --- a/src/WorkspaceContext.ts +++ b/src/WorkspaceContext.ts @@ -680,6 +680,10 @@ export enum FolderOperation { packageUpdated = "packageUpdated", // Package.resolved has been updated resolvedUpdated = "resolvedUpdated", + // .build/workspace-state.json has been updated + workspaceStateUpdated = "workspaceStateUpdated", + // .build/workspace-state.json has been updated + packageViewUpdated = "packageViewUpdated", } /** Workspace Folder Event */ diff --git a/src/commands.ts b/src/commands.ts index 12c38cdd9..b88e6cecb 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -39,6 +39,7 @@ import { runPluginTask } from "./commands/runPluginTask"; import { runTestMultipleTimes } from "./commands/testMultipleTimes"; import { newSwiftFile } from "./commands/newFile"; import { runAllTestsParallel } from "./commands/runParallelTests"; +import { updateDependenciesViewList } from "./commands/dependencies/updateDepViewList"; /** * References: @@ -69,6 +70,8 @@ export enum Commands { DEBUG = "swift.debug", CLEAN_BUILD = "swift.cleanBuild", RESOLVE_DEPENDENCIES = "swift.resolveDependencies", + SHOW_FLAT_DEPENDENCIES_LIST = "swift.flatDependenciesList", + SHOW_NESTED_DEPENDENCIES_LIST = "swift.nestedDependenciesList", UPDATE_DEPENDENCIES = "swift.updateDependencies", RUN_TESTS_MULTIPLE_TIMES = "swift.runTestsMultipleTimes", RESET_PACKAGE = "swift.resetPackage", @@ -160,5 +163,11 @@ export function register(ctx: WorkspaceContext): vscode.Disposable[] { Commands.PREVIEW_DOCUMENTATION, async () => await ctx.documentation.launchDocumentationPreview() ), + vscode.commands.registerCommand(Commands.SHOW_FLAT_DEPENDENCIES_LIST, () => + updateDependenciesViewList(ctx, true) + ), + vscode.commands.registerCommand(Commands.SHOW_NESTED_DEPENDENCIES_LIST, () => + updateDependenciesViewList(ctx, false) + ), ]; } diff --git a/src/commands/dependencies/updateDepViewList.ts b/src/commands/dependencies/updateDepViewList.ts new file mode 100644 index 000000000..04cbb4400 --- /dev/null +++ b/src/commands/dependencies/updateDepViewList.ts @@ -0,0 +1,23 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the VS Code Swift open source project +// +// Copyright (c) 2021-2025 the VS Code Swift project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of VS Code Swift project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import contextKeys from "../../contextKeys"; +import { FolderOperation, WorkspaceContext } from "../../WorkspaceContext"; + +export function updateDependenciesViewList(ctx: WorkspaceContext, flatList: boolean) { + if (ctx.currentFolder) { + contextKeys.flatDependenciesList = flatList; + ctx.fireEvent(ctx.currentFolder, FolderOperation.packageViewUpdated); + } +} diff --git a/src/contextKeys.ts b/src/contextKeys.ts index e17c15ba9..e550c7707 100644 --- a/src/contextKeys.ts +++ b/src/contextKeys.ts @@ -38,6 +38,11 @@ interface ContextKeys { */ packageHasDependencies: boolean; + /** + * Whether the dependencies list is displayed in a nested or flat view. + */ + flatDependenciesList: boolean; + /** * Whether the Swift package has any plugins. */ @@ -78,6 +83,7 @@ interface ContextKeys { function createContextKeys(): ContextKeys { let isActivated: boolean = false; let hasPackage: boolean = false; + let flatDependenciesList: boolean = false; let packageHasDependencies: boolean = false; let packageHasPlugins: boolean = false; let currentTargetType: string | undefined = undefined; @@ -115,6 +121,15 @@ function createContextKeys(): ContextKeys { vscode.commands.executeCommand("setContext", "swift.packageHasDependencies", value); }, + get flatDependenciesList() { + return flatDependenciesList; + }, + + set flatDependenciesList(value: boolean) { + flatDependenciesList = value; + vscode.commands.executeCommand("setContext", "swift.flatDependenciesList", value); + }, + get packageHasPlugins() { return packageHasPlugins; }, diff --git a/src/ui/PackageDependencyProvider.ts b/src/ui/PackageDependencyProvider.ts index feab34bac..6cd038935 100644 --- a/src/ui/PackageDependencyProvider.ts +++ b/src/ui/PackageDependencyProvider.ts @@ -18,16 +18,8 @@ import * as path from "path"; import configuration from "../configuration"; import { WorkspaceContext } from "../WorkspaceContext"; import { FolderOperation } from "../WorkspaceContext"; -import { FolderContext } from "../FolderContext"; import contextKeys from "../contextKeys"; -import { - Dependency, - PackageContents, - SwiftPackage, - WorkspaceState, - WorkspaceStateDependency, -} from "../SwiftPackage"; -import { BuildFlags } from "../toolchain/BuildFlags"; +import { Dependency, ResolvedDependency } from "../SwiftPackage"; /** * References: @@ -40,41 +32,107 @@ import { BuildFlags } from "../toolchain/BuildFlags"; * https://code.visualstudio.com/api/extension-guides/tree-view */ +/** + * Returns a {@link FileNode} for every file or subdirectory + * in the given directory. + */ +async function getChildren(directoryPath: string, parentId?: string): Promise { + const contents = await fs.readdir(directoryPath); + const results: FileNode[] = []; + const excludes = configuration.excludePathsFromPackageDependencies; + for (const fileName of contents) { + if (excludes.includes(fileName)) { + continue; + } + const filePath = path.join(directoryPath, fileName); + const stats = await fs.stat(filePath); + results.push(new FileNode(fileName, filePath, stats.isDirectory(), parentId)); + } + return results.sort((first, second) => { + if (first.isDirectory === second.isDirectory) { + // If both nodes are of the same type, sort them by name. + return first.name.localeCompare(second.name); + } else { + // Otherwise, sort directories first. + return first.isDirectory ? -1 : 1; + } + }); +} + /** * A package in the Package Dependencies {@link vscode.TreeView TreeView}. */ export class PackageNode { + private id: string; + constructor( - public name: string, - public path: string, - public location: string, - public version: string, - public type: "local" | "remote" | "editing" - ) {} + private dependency: ResolvedDependency, + private childDependencies: (dependency: Dependency) => ResolvedDependency[], + private parentId?: string + ) { + this.id = + (this.parentId ? `${this.parentId}->` : "") + + `${this.name}-${this.dependency.version ?? ""}`; + } + + get name(): string { + return this.dependency.identity; + } + + get location(): string { + return this.dependency.location; + } + + get type(): string { + return this.dependency.type; + } + + get path(): string { + return this.dependency.path ?? ""; + } toTreeItem(): vscode.TreeItem { const item = new vscode.TreeItem(this.name, vscode.TreeItemCollapsibleState.Collapsed); - item.id = this.path; - item.description = this.version; + item.id = this.id; + item.description = this.dependency.version; item.iconPath = - this.type === "editing" + this.dependency.type === "editing" ? new vscode.ThemeIcon("edit") : new vscode.ThemeIcon("package"); - item.contextValue = this.type; + item.contextValue = this.dependency.type; item.accessibilityInformation = { label: `Package ${this.name}` }; + item.tooltip = this.path; return item; } + + async getChildren(): Promise { + const [childDeps, files] = await Promise.all([ + this.childDependencies(this.dependency), + getChildren(this.dependency.path, this.id), + ]); + const childNodes = childDeps.map( + dep => new PackageNode(dep, this.childDependencies, this.id) + ); + + // Show dependencies first, then files. + return [...childNodes, ...files]; + } } /** * A file or directory in the Package Dependencies {@link vscode.TreeView TreeView}. */ export class FileNode { + private id: string; + constructor( public name: string, public path: string, - public isDirectory: boolean - ) {} + public isDirectory: boolean, + private parentId?: string + ) { + this.id = (this.parentId ? `${this.parentId}->` : "") + `${this.path}`; + } toTreeItem(): vscode.TreeItem { const item = new vscode.TreeItem( @@ -83,8 +141,9 @@ export class FileNode { ? vscode.TreeItemCollapsibleState.Collapsed : vscode.TreeItemCollapsibleState.None ); - item.id = this.path; + item.id = this.id; item.resourceUri = vscode.Uri.file(this.path); + item.tooltip = this.path; if (!this.isDirectory) { item.command = { command: "vscode.open", @@ -97,6 +156,10 @@ export class FileNode { } return item; } + + async getChildren(): Promise { + return await getChildren(this.path, this.id); + } } /** @@ -142,7 +205,9 @@ export class PackageDependenciesProvider implements vscode.TreeDataProvider { - if (!workspaceState) { - return []; - } - const inUseDependencies = await this.getInUseDependencies(workspaceState, folderContext); - return ( - workspaceState?.object.dependencies - .filter(dependency => - inUseDependencies.has(dependency.packageRef.identity.toLowerCase()) - ) - .map(dependency => { - const type = this.dependencyType(dependency); - const version = this.dependencyDisplayVersion(dependency); - const packagePath = this.dependencyPackagePath( - dependency, - folderContext.folder.fsPath - ); - const location = dependency.packageRef.location; - return new PackageNode( - dependency.packageRef.identity, - packagePath, - location, - version, - type - ); - }) ?? [] - ); - } - - /** - * * Returns a set of all dependencies that are in use in the workspace. - * Why tranverse is necessary here? - * * If we have an implicit local dependency of a dependency, you may not be able to see it in either `Package.swift` or `Package.resolved` unless tranversing from root Package.swift. - * Why not using `swift package show-dependencies`? - * * it costs more time and it triggers the file change of `workspace-state.json` which is not necessary - * Why not using `workspace-state.json` directly? - * * `workspace-state.json` contains all necessary dependencies but it also contains dependencies that are not in use. - * Here is the implementation details: - * 1. local/remote/edited dependency has remote/edited dependencies, Package.resolved covers them - * 2. remote/edited dependency has a local dependency, the local dependency must have been declared in root Package.swift - * 3. local dependency has a local dependency, traverse it and find the local dependencies only recursively - * 4. pins include all remote and edited packages for 1, 2 - */ - private async getInUseDependencies( - workspaceState: WorkspaceState, - folderContext: FolderContext - ): Promise> { - const localDependencies = await this.getLocalDependencySet(workspaceState, folderContext); - const remoteDependencies = this.getRemoteDependencySet(folderContext); - const editedDependencies = this.getEditedDependencySet(workspaceState); - return new Set([ - ...localDependencies, - ...remoteDependencies, - ...editedDependencies, - ]); - } - - private getRemoteDependencySet(folderContext: FolderContext | undefined): Set { - return new Set(folderContext?.swiftPackage.resolved?.pins.map(pin => pin.identity)); - } - - private getEditedDependencySet(workspaceState: WorkspaceState): Set { - return new Set( - workspaceState.object.dependencies - .filter(dependency => this.dependencyType(dependency) === "editing") - .map(dependency => dependency.packageRef.identity) - ); - } - - /** - * @param workspaceState the workspace state read from `Workspace-state.json` - * @param folderContext the folder context of the current folder - * @returns all local in-use dependencies - */ - private async getLocalDependencySet( - workspaceState: WorkspaceState, - folderContext: FolderContext - ): Promise> { - const rootDependencies = folderContext.swiftPackage.dependencies ?? []; - const workspaceStateDependencies = workspaceState.object.dependencies ?? []; - const workspacePath = folderContext.folder.fsPath; - - const showingDependencies: Set = new Set(); - const stack: Dependency[] = rootDependencies.slice(); - - while (stack.length > 0) { - const top = stack.pop(); - if (!top) { - continue; - } - - if (showingDependencies.has(top.identity)) { - continue; - } - - if (top.type !== "local" && top.type !== "fileSystem") { - continue; - } - - showingDependencies.add(top.identity); - const workspaceStateDependency = workspaceStateDependencies.find( - workspaceStateDependency => - workspaceStateDependency.packageRef.identity === top.identity - ); - if (!workspaceStateDependency) { - continue; - } - - const packagePath = this.dependencyPackagePath(workspaceStateDependency, workspacePath); - const childDependencyContents = (await SwiftPackage.loadPackage( - vscode.Uri.file(packagePath), - folderContext.workspaceContext.toolchain - )) as PackageContents; - - stack.push(...childDependencyContents.dependencies); - } - return showingDependencies; - } - - /** - * Returns a {@link FileNode} for every file or subdirectory - * in the given directory. - */ - private async getNodesInDirectory(directoryPath: string): Promise { - const contents = await fs.readdir(directoryPath); - const results: FileNode[] = []; - const excludes = configuration.excludePathsFromPackageDependencies; - for (const fileName of contents) { - if (excludes.includes(fileName)) { - continue; - } - const filePath = path.join(directoryPath, fileName); - const stats = await fs.stat(filePath); - results.push(new FileNode(fileName, filePath, stats.isDirectory())); - } - return results.sort((first, second) => { - if (first.isDirectory === second.isDirectory) { - // If both nodes are of the same type, sort them by name. - return first.name.localeCompare(second.name); + if (contextKeys.flatDependenciesList) { + const existenceMap = new Map(); + const gatherChildren = ( + dependencies: ResolvedDependency[] + ): ResolvedDependency[] => { + const result: ResolvedDependency[] = []; + for (const dep of dependencies) { + if (!existenceMap.has(dep.identity)) { + result.push(dep); + existenceMap.set(dep.identity, true); + } + const childDeps = folderContext.swiftPackage.childDependencies(dep); + result.push(...gatherChildren(childDeps)); + } + return result; + }; + + const rootDeps = folderContext.swiftPackage.rootDependencies(); + const allDeps = gatherChildren(rootDeps); + return allDeps.map(dependency => new PackageNode(dependency, () => [])); } else { - // Otherwise, sort directories first. - return first.isDirectory ? -1 : 1; + return folderContext.swiftPackage + .rootDependencies() + .map( + dependency => + new PackageNode( + dependency, + folderContext.swiftPackage.childDependencies.bind( + folderContext.swiftPackage + ) + ) + ); } - }); - } - - /// - Dependency display helpers - - /** - * Get type of WorkspaceStateDependency for displaying in the tree: real version | edited | local - * @param dependency - * @return "local" | "remote" | "editing" - */ - private dependencyType(dependency: WorkspaceStateDependency): "local" | "remote" | "editing" { - if (dependency.state.name === "edited") { - return "editing"; - } else if ( - dependency.packageRef.kind === "local" || - dependency.packageRef.kind === "fileSystem" - ) { - // need to check for both "local" and "fileSystem" as swift 5.5 and earlier - // use "local" while 5.6 and later use "fileSystem" - return "local"; } else { - return "remote"; - } - } - - /** - * Get version of WorkspaceStateDependency for displaying in the tree - * @param dependency - * @return real version | editing | local - */ - private dependencyDisplayVersion(dependency: WorkspaceStateDependency): string { - const type = this.dependencyType(dependency); - if (type === "editing") { - return "editing"; - } else if (type === "local") { - return "local"; - } else { - return ( - dependency.state.checkoutState?.version ?? - dependency.state.checkoutState?.branch ?? - dependency.state.checkoutState?.revision.substring(0, 7) ?? - dependency.state.version ?? - "unknown" - ); - } - } - - /** - * * Get package source path of dependency - * `editing`: dependency.state.path ?? workspacePath + Packages/ + dependency.subpath - * `local`: dependency.packageRef.location - * `remote`: buildDirectory + checkouts + dependency.packageRef.location - * @param dependency - * @param workspaceFolder - * @return the package path based on the type - */ - private dependencyPackagePath( - dependency: WorkspaceStateDependency, - workspaceFolder: string - ): string { - const type = this.dependencyType(dependency); - if (type === "editing") { - return ( - dependency.state.path ?? path.join(workspaceFolder, "Packages", dependency.subpath) - ); - } else if (type === "local") { - return dependency.state.path ?? dependency.packageRef.location; - } else { - // remote - const buildDirectory = BuildFlags.buildDirectoryFromWorkspacePath( - workspaceFolder, - true - ); - if (dependency.packageRef.kind === "registry") { - return path.join(buildDirectory, "registry", "downloads", dependency.subpath); - } else { - return path.join(buildDirectory, "checkouts", dependency.subpath); - } + return await element.getChildren(); } } } diff --git a/test/integration-tests/SwiftPackage.test.ts b/test/integration-tests/SwiftPackage.test.ts index 72f9db0f9..421f1adb3 100644 --- a/test/integration-tests/SwiftPackage.test.ts +++ b/test/integration-tests/SwiftPackage.test.ts @@ -69,6 +69,6 @@ suite("SwiftPackage Test Suite", () => { assert.strictEqual(spmPackage.dependencies.length, 1); assert(spmPackage.resolved !== undefined); assert.strictEqual(spmPackage.resolved.pins.length, 1); - assert.strictEqual(spmPackage.resolved.pins[0].identity, "swift-cmark"); + assert.strictEqual(spmPackage.resolved.pins[0].identity, "swift-log"); }).timeout(10000); }); diff --git a/test/integration-tests/commands/dependency.test.ts b/test/integration-tests/commands/dependency.test.ts index d393ac5e8..27c609e54 100644 --- a/test/integration-tests/commands/dependency.test.ts +++ b/test/integration-tests/commands/dependency.test.ts @@ -74,6 +74,20 @@ suite("Dependency Commmands Test Suite", function () { return items.find(n => n.name === "swift-markdown") as PackageNode; } + // Wait for the dependency to switch to the expected state. + // This doesn't happen immediately after the USE_LOCAL_DEPENDENCY + // and RESET_PACKAGE commands because the file watcher on + // workspace-state.json needs to trigger. + async function getDependencyInState(state: "remote" | "editing") { + for (let i = 0; i < 10; i++) { + const dep = await getDependency(); + if (dep.type === state) { + return dep; + } + await new Promise(resolve => setTimeout(resolve, 1000)); + } + } + async function useLocalDependencyTest() { // spm edit with user supplied local version of dependency const item = await getDependency(); @@ -85,12 +99,10 @@ suite("Dependency Commmands Test Suite", function () { ); expect(result).to.be.true; + const dep = await getDependencyInState("editing"); + expect(dep).to.not.be.undefined; // Make sure using local - expect((await getDependency()).type).to.equal("editing"); - } - - async function assertUsingRemote() { - expect((await getDependency()).type).to.equal("remote"); + expect(dep?.type).to.equal("editing"); } test("Swift: Reset Package Dependencies", async function () { @@ -104,7 +116,9 @@ suite("Dependency Commmands Test Suite", function () { const result = await vscode.commands.executeCommand(Commands.RESET_PACKAGE); expect(result).to.be.true; - await assertUsingRemote(); + const dep = await getDependencyInState("remote"); + expect(dep).to.not.be.undefined; + expect(dep?.type).to.equal("remote"); }); test("Swift: Revert To Original Version", async () => { @@ -116,7 +130,9 @@ suite("Dependency Commmands Test Suite", function () { ); expect(result).to.be.true; - await assertUsingRemote(); + const dep = await getDependencyInState("remote"); + expect(dep).to.not.be.undefined; + expect(dep?.type).to.equal("remote"); }); }); }); diff --git a/test/integration-tests/ui/PackageDependencyProvider.test.ts b/test/integration-tests/ui/PackageDependencyProvider.test.ts index 75f6ea6a7..fdced668f 100644 --- a/test/integration-tests/ui/PackageDependencyProvider.test.ts +++ b/test/integration-tests/ui/PackageDependencyProvider.test.ts @@ -23,6 +23,7 @@ import { executeTaskAndWaitForResult, waitForNoRunningTasks } from "../../utilit import { getBuildAllTask, SwiftTask } from "../../../src/tasks/SwiftTaskProvider"; import { testAssetPath } from "../../fixtures"; import { activateExtensionForSuite, folderInRootWorkspace } from "../utilities/testutilities"; +import contextKeys from "../../../src/contextKeys"; suite("PackageDependencyProvider Test Suite", function () { let treeProvider: PackageDependenciesProvider; @@ -39,12 +40,14 @@ suite("PackageDependencyProvider Test Suite", function () { await workspaceContext.focusFolder(folderContext); }, async teardown() { + contextKeys.flatDependenciesList = false; treeProvider.dispose(); }, testAssets: ["dependencies"], }); test("Includes remote dependency", async () => { + contextKeys.flatDependenciesList = false; const items = await treeProvider.getChildren(); const dep = items.find(n => n.name === "swift-markdown") as PackageNode; @@ -69,6 +72,7 @@ suite("PackageDependencyProvider Test Suite", function () { }); test("Lists local dependency file structure", async () => { + contextKeys.flatDependenciesList = false; const items = await treeProvider.getChildren(); const dep = items.find(n => n.name === "defaultpackage") as PackageNode; @@ -103,6 +107,7 @@ suite("PackageDependencyProvider Test Suite", function () { }); test("Lists remote dependency file structure", async () => { + contextKeys.flatDependenciesList = false; const items = await treeProvider.getChildren(); const dep = items.find(n => n.name === "swift-markdown") as PackageNode; @@ -128,6 +133,23 @@ suite("PackageDependencyProvider Test Suite", function () { assertPathsEqual(file?.path, path.join(depPath, "Sources/CAtomic/CAtomic.c")); }); + test("Shows a flat dependency list", async () => { + contextKeys.flatDependenciesList = true; + const items = await treeProvider.getChildren(); + expect(items.length).to.equal(3); + expect(items.find(n => n.name === "swift-markdown")).to.not.be.undefined; + expect(items.find(n => n.name === "swift-cmark")).to.not.be.undefined; + expect(items.find(n => n.name === "defaultpackage")).to.not.be.undefined; + }); + + test("Shows a nested dependency list", async () => { + contextKeys.flatDependenciesList = false; + const items = await treeProvider.getChildren(); + expect(items.length).to.equal(2); + expect(items.find(n => n.name === "swift-markdown")).to.not.be.undefined; + expect(items.find(n => n.name === "defaultpackage")).to.not.be.undefined; + }); + function assertPathsEqual(path1: string | undefined, path2: string | undefined) { expect(path1).to.not.be.undefined; expect(path2).to.not.be.undefined; diff --git a/test/unit-tests/ui/PackageDependencyProvider.test.ts b/test/unit-tests/ui/PackageDependencyProvider.test.ts index aa5871abc..d7d14b994 100644 --- a/test/unit-tests/ui/PackageDependencyProvider.test.ts +++ b/test/unit-tests/ui/PackageDependencyProvider.test.ts @@ -14,7 +14,9 @@ import { expect } from "chai"; import * as vscode from "vscode"; +import * as fs from "fs/promises"; import { FileNode, PackageNode } from "../../../src/ui/PackageDependencyProvider"; +import { mockGlobalModule } from "../../MockUtils"; suite("PackageDependencyProvider Unit Test Suite", function () { suite("FileNode", () => { @@ -46,11 +48,15 @@ suite("PackageDependencyProvider Unit Test Suite", function () { suite("PackageNode", () => { test("can create a VSCode TreeItem that represents a Swift package", () => { const node = new PackageNode( - "SwiftMarkdown", - "/path/to/.build/swift-markdown", - "https://github.com/swiftlang/swift-markdown.git", - "1.2.3", - "remote" + { + identity: "SwiftMarkdown", + path: "/path/to/.build/swift-markdown", + location: "https://github.com/swiftlang/swift-markdown.git", + dependencies: [], + version: "1.2.3", + type: "remote", + }, + () => [] ); const item = node.toTreeItem(); @@ -58,5 +64,53 @@ suite("PackageDependencyProvider Unit Test Suite", function () { expect(item.description).to.deep.equal("1.2.3"); expect(item.command).to.be.undefined; }); + + const fsMock = mockGlobalModule(fs); + + test("enumerates child dependencies and files", async () => { + fsMock.readdir.resolves(["file1", "file2"] as any); + fsMock.stat.resolves({ isFile: () => true, isDirectory: () => false } as any); + + const node = new PackageNode( + { + identity: "SwiftMarkdown", + path: "/path/to/.build/swift-markdown", + location: "https://github.com/swiftlang/swift-markdown.git", + dependencies: [], + version: "1.2.3", + type: "remote", + }, + () => [ + { + identity: "SomeChildDependency", + path: "/path/to/.build/child-dependency", + location: "https://github.com/swiftlang/some-child-dependency.git", + dependencies: [], + version: "1.2.4", + type: "remote", + }, + ] + ); + + const children = await node.getChildren(); + + expect(children).to.have.lengthOf(3); + const [childDep, ...childFiles] = children; + expect(childDep.name).to.equal("SomeChildDependency"); + expect(childFiles).to.deep.equal([ + new FileNode( + "file1", + "/path/to/.build/swift-markdown/file1", + false, + "SwiftMarkdown-1.2.3" + ), + new FileNode( + "file2", + "/path/to/.build/swift-markdown/file2", + false, + "SwiftMarkdown-1.2.3" + ), + ]); + }); }); }); From 3725797dd7f1b473be24baeee1817f03354bb9f8 Mon Sep 17 00:00:00 2001 From: Matthew Bastien Date: Wed, 12 Feb 2025 11:26:30 -0500 Subject: [PATCH 05/86] Use one launch configuration type for all debug adapters (#1362) --- package.json | 20 +- src/commands/attachDebugger.ts | 4 +- src/configuration.ts | 43 ++- src/debugger/buildConfig.ts | 6 +- src/debugger/debugAdapter.ts | 23 +- src/debugger/debugAdapterFactory.ts | 60 ++-- src/debugger/launch.ts | 6 +- src/debugger/logTracker.ts | 30 +- .../debugger/attachDebugger.test.ts | 2 + test/unit-tests/debugger/debugAdapter.test.ts | 39 ++- .../debugger/debugAdapterFactory.test.ts | 318 +++++++++--------- 11 files changed, 293 insertions(+), 258 deletions(-) diff --git a/package.json b/package.json index 2e76a780c..95e4bb879 100644 --- a/package.json +++ b/package.json @@ -658,9 +658,25 @@ { "title": "Debugger", "properties": { + "swift.debugger.debugAdapter": { + "type": "string", + "default": "auto", + "enum": [ + "auto", + "lldb-dap", + "CodeLLDB" + ], + "enumDescriptions": [ + "Automatically select which debug adapter to use based on your Swift toolchain version.", + "Use the `lldb-dap` executable from the toolchain. Requires Swift 6 or later.", + "Use the CodeLLDB extension's debug adapter." + ], + "order": 1 + }, "swift.debugger.useDebugAdapterFromToolchain": { "type": "boolean", "default": false, + "markdownDeprecationMessage": "**Deprecated**: Use the `swift.debugger.debugAdapter` setting instead. This will be removed in future versions of the Swift extension.", "markdownDescription": "Use the LLDB debug adapter packaged with the Swift toolchain as your debug adapter. Note: this is only available starting with Swift 6. The CodeLLDB extension will be used if your Swift toolchain does not contain lldb-dap.", "order": 1 }, @@ -1196,8 +1212,8 @@ ], "debuggers": [ { - "type": "swift-lldb", - "label": "Swift LLDB Debugger", + "type": "swift", + "label": "Swift Debugger", "configurationAttributes": { "launch": { "required": [ diff --git a/src/commands/attachDebugger.ts b/src/commands/attachDebugger.ts index a25ac9763..85486fcef 100644 --- a/src/commands/attachDebugger.ts +++ b/src/commands/attachDebugger.ts @@ -15,7 +15,7 @@ import * as vscode from "vscode"; import { WorkspaceContext } from "../WorkspaceContext"; import { getLldbProcess } from "../debugger/lldb"; -import { LaunchConfigType } from "../debugger/debugAdapter"; +import { SWIFT_LAUNCH_CONFIG_TYPE } from "../debugger/debugAdapter"; /** * Attaches the LLDB debugger to a running process selected by the user. @@ -37,7 +37,7 @@ export async function attachDebugger(ctx: WorkspaceContext) { }); if (picked) { const debugConfig: vscode.DebugConfiguration = { - type: LaunchConfigType.SWIFT_EXTENSION, + type: SWIFT_LAUNCH_CONFIG_TYPE, request: "attach", name: "Attach", pid: picked.pid, diff --git a/src/configuration.ts b/src/configuration.ts index d282378d1..45bbba2f9 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -14,9 +14,10 @@ import * as vscode from "vscode"; -type CFamilySupportOptions = "enable" | "disable" | "cpptools-inactive"; -type ActionAfterBuildError = "Focus Problems" | "Focus Terminal" | "Do Nothing"; -type OpenAfterCreateNewProjectOptions = +export type DebugAdapters = "auto" | "lldb-dap" | "CodeLLDB"; +export type CFamilySupportOptions = "enable" | "disable" | "cpptools-inactive"; +export type ActionAfterBuildError = "Focus Problems" | "Focus Terminal" | "Do Nothing"; +export type OpenAfterCreateNewProjectOptions = | "always" | "alwaysNewWindow" | "whenNoFolderOpen" @@ -47,8 +48,8 @@ export interface LSPConfiguration { /** debugger configuration */ export interface DebuggerConfiguration { - /** Whether or not to use CodeLLDB for debugging instead of lldb-dap */ - readonly useDebugAdapterFromToolchain: boolean; + /** Get the underlying debug adapter type requested by the user. */ + readonly debugAdapter: DebugAdapters; /** Return path to debug adapter */ readonly customDebugAdapterPath: string; } @@ -182,19 +183,31 @@ const configuration = { /** debugger configuration */ get debugger(): DebuggerConfiguration { return { - get useDebugAdapterFromToolchain(): boolean { - // Enabled by default only when we're on Windows arm64 since CodeLLDB does not support - // this platform and gives an awful error message. + get debugAdapter(): DebugAdapters { + // Use inspect to determine if the user has explicitly set swift.debugger.useDebugAdapterFromToolchain + const inspectUseDebugAdapterFromToolchain = vscode.workspace + .getConfiguration("swift.debugger") + .inspect("useDebugAdapterFromToolchain"); + let useDebugAdapterFromToolchain = + inspectUseDebugAdapterFromToolchain?.workspaceValue ?? + inspectUseDebugAdapterFromToolchain?.globalValue; + // On Windows arm64 we enable swift.debugger.useDebugAdapterFromToolchain by default since CodeLLDB does + // not support this platform and gives an awful error message. if (process.platform === "win32" && process.arch === "arm64") { - // We need to use inspect to find out if the value is explicitly set. - const inspect = vscode.workspace - .getConfiguration("swift.debugger") - .inspect("useDebugAdapterFromToolchain"); - return inspect?.workspaceValue ?? inspect?.globalValue ?? true; + useDebugAdapterFromToolchain = useDebugAdapterFromToolchain ?? true; } - return vscode.workspace + const selectedAdapter = vscode.workspace .getConfiguration("swift.debugger") - .get("useDebugAdapterFromToolchain", false); + .get("debugAdapter", "auto"); + switch (selectedAdapter) { + case "auto": + if (useDebugAdapterFromToolchain !== undefined) { + return useDebugAdapterFromToolchain ? "lldb-dap" : "CodeLLDB"; + } + return "auto"; + default: + return selectedAdapter; + } }, get customDebugAdapterPath(): string { return vscode.workspace.getConfiguration("swift.debugger").get("path", ""); diff --git a/src/debugger/buildConfig.ts b/src/debugger/buildConfig.ts index f31ea8a2e..956d2a356 100644 --- a/src/debugger/buildConfig.ts +++ b/src/debugger/buildConfig.ts @@ -20,7 +20,7 @@ import configuration from "../configuration"; import { FolderContext } from "../FolderContext"; import { BuildFlags } from "../toolchain/BuildFlags"; import { regexEscapedString, swiftRuntimeEnv } from "../utilities/utilities"; -import { DebugAdapter } from "./debugAdapter"; +import { SWIFT_LAUNCH_CONFIG_TYPE } from "./debugAdapter"; import { TargetType } from "../SwiftPackage"; import { Version } from "../utilities/version"; import { TestLibrary } from "../TestExplorer/TestRunner"; @@ -515,7 +515,7 @@ export class TestingConfigurationFactory { }).map(([key, value]) => `settings set target.env-vars ${key}="${value}"`); return { - type: DebugAdapter.getLaunchConfigType(this.ctx.workspaceContext.swiftVersion), + type: SWIFT_LAUNCH_CONFIG_TYPE, request: "custom", name: `Test ${this.ctx.swiftPackage.name}`, targetCreateCommands: [`file -a ${arch} ${xctestPath}/xctest`], @@ -738,7 +738,7 @@ export class TestingConfigurationFactory { function getBaseConfig(ctx: FolderContext, expandEnvVariables: boolean) { const { folder, nameSuffix } = getFolderAndNameSuffix(ctx, expandEnvVariables); return { - type: DebugAdapter.getLaunchConfigType(ctx.workspaceContext.swiftVersion), + type: SWIFT_LAUNCH_CONFIG_TYPE, request: "launch", sourceLanguages: ["swift"], name: `Test ${ctx.swiftPackage.name}`, diff --git a/src/debugger/debugAdapter.ts b/src/debugger/debugAdapter.ts index fa7713c99..2b5d60256 100644 --- a/src/debugger/debugAdapter.ts +++ b/src/debugger/debugAdapter.ts @@ -21,10 +21,17 @@ import { SwiftToolchain } from "../toolchain/toolchain"; import { SwiftOutputChannel } from "../ui/SwiftOutputChannel"; /** - * The supported {@link vscode.DebugConfiguration.type Debug Configuration Type} for auto-generation of launch configurations + * The launch configuration type added by the Swift extension that will delegate to the appropriate + * LLDB debug adapter when launched. + */ +export const SWIFT_LAUNCH_CONFIG_TYPE = "swift"; + +/** + * The supported {@link vscode.DebugConfiguration.type Debug Configuration Types} that can handle + * LLDB launch requests. */ export const enum LaunchConfigType { - SWIFT_EXTENSION = "swift-lldb", + LLDB_DAP = "swift", CODE_LLDB = "lldb", } @@ -40,10 +47,12 @@ export class DebugAdapter { * @returns the type of launch configuration used by the given Swift toolchain version */ public static getLaunchConfigType(swiftVersion: Version): LaunchConfigType { - return swiftVersion.isGreaterThanOrEqual(new Version(6, 0, 0)) && - configuration.debugger.useDebugAdapterFromToolchain - ? LaunchConfigType.SWIFT_EXTENSION - : LaunchConfigType.CODE_LLDB; + const lldbDapIsAvailable = swiftVersion.isGreaterThanOrEqual(new Version(6, 0, 0)); + if (lldbDapIsAvailable && configuration.debugger.debugAdapter === "lldb-dap") { + return LaunchConfigType.LLDB_DAP; + } else { + return LaunchConfigType.CODE_LLDB; + } } /** @@ -60,7 +69,7 @@ export class DebugAdapter { const debugAdapter = this.getLaunchConfigType(toolchain.swiftVersion); switch (debugAdapter) { - case LaunchConfigType.SWIFT_EXTENSION: + case LaunchConfigType.LLDB_DAP: return toolchain.getLLDBDebugAdapter(); case LaunchConfigType.CODE_LLDB: return toolchain.getLLDB(); diff --git a/src/debugger/debugAdapterFactory.ts b/src/debugger/debugAdapterFactory.ts index 8a264aa39..5a0fd17e8 100644 --- a/src/debugger/debugAdapterFactory.ts +++ b/src/debugger/debugAdapterFactory.ts @@ -15,8 +15,7 @@ import * as vscode from "vscode"; import * as path from "path"; import { WorkspaceContext } from "../WorkspaceContext"; -import { DebugAdapter, LaunchConfigType } from "./debugAdapter"; -import { Version } from "../utilities/version"; +import { DebugAdapter, LaunchConfigType, SWIFT_LAUNCH_CONFIG_TYPE } from "./debugAdapter"; import { registerLoggingDebugAdapterTracker } from "./logTracker"; import { SwiftToolchain } from "../toolchain/toolchain"; import { SwiftOutputChannel } from "../ui/SwiftOutputChannel"; @@ -28,14 +27,7 @@ import { SwiftOutputChannel } from "../ui/SwiftOutputChannel"; * @returns A disposable to be disposed when the extension is deactivated */ export function registerDebugger(workspaceContext: WorkspaceContext): vscode.Disposable { - let subscriptions: vscode.Disposable[] = []; - const register = async () => { - subscriptions.map(sub => sub.dispose()); - subscriptions = [ - registerLoggingDebugAdapterTracker(workspaceContext.toolchain.swiftVersion), - registerLLDBDebugAdapter(workspaceContext.toolchain, workspaceContext.outputChannel), - ]; - + async function updateDebugAdapter() { await workspaceContext.setLLDBVersion(); // Verify that the adapter exists, but only after registration. This async method @@ -49,20 +41,23 @@ export function registerDebugger(workspaceContext: WorkspaceContext): vscode.Dis ).catch(error => { workspaceContext.outputChannel.log(error); }); - }; + } - const changeMonitor = vscode.workspace.onDidChangeConfiguration(event => { - if (event.affectsConfiguration("swift.debugger.useDebugAdapterFromToolchain")) { - register(); - } - }); + const subscriptions: vscode.Disposable[] = [ + registerLoggingDebugAdapterTracker(), + registerLLDBDebugAdapter(workspaceContext.toolchain, workspaceContext.outputChannel), + vscode.workspace.onDidChangeConfiguration(event => { + if (event.affectsConfiguration("swift.debugger.useDebugAdapterFromToolchain")) { + updateDebugAdapter(); + } + }), + ]; // Perform the initial registration, then reregister every time the settings change. - register(); + updateDebugAdapter(); return { dispose: () => { - changeMonitor.dispose(); subscriptions.map(sub => sub.dispose()); }, }; @@ -78,13 +73,13 @@ function registerLLDBDebugAdapter( outputChannel: SwiftOutputChannel ): vscode.Disposable { const debugAdpaterFactory = vscode.debug.registerDebugAdapterDescriptorFactory( - LaunchConfigType.SWIFT_EXTENSION, + SWIFT_LAUNCH_CONFIG_TYPE, new LLDBDebugAdapterExecutableFactory(toolchain, outputChannel) ); const debugConfigProvider = vscode.debug.registerDebugConfigurationProvider( - LaunchConfigType.SWIFT_EXTENSION, - new LLDBDebugConfigurationProvider(process.platform, toolchain.swiftVersion) + SWIFT_LAUNCH_CONFIG_TYPE, + new LLDBDebugConfigurationProvider(process.platform, toolchain) ); return { @@ -140,14 +135,13 @@ export class LLDBDebugAdapterExecutableFactory implements vscode.DebugAdapterDes export class LLDBDebugConfigurationProvider implements vscode.DebugConfigurationProvider { constructor( private platform: NodeJS.Platform, - private swiftVersion: Version + private toolchain: SwiftToolchain ) {} async resolveDebugConfiguration( _folder: vscode.WorkspaceFolder | undefined, launchConfig: vscode.DebugConfiguration - ): Promise { - launchConfig.env = this.convertEnvironmentVariables(launchConfig.env); + ): Promise { // Fix the program path on Windows to include the ".exe" extension if ( this.platform === "win32" && @@ -157,20 +151,20 @@ export class LLDBDebugConfigurationProvider implements vscode.DebugConfiguration launchConfig.program += ".exe"; } - // Delegate to CodeLLDB if that's the debug adapter we have selected - if (DebugAdapter.getLaunchConfigType(this.swiftVersion) === LaunchConfigType.CODE_LLDB) { - launchConfig.type = LaunchConfigType.CODE_LLDB; + // Delegate to the appropriate debug adapter extension + launchConfig.type = DebugAdapter.getLaunchConfigType(this.toolchain.swiftVersion); + if (launchConfig.type === LaunchConfigType.CODE_LLDB) { launchConfig.sourceLanguages = ["swift"]; + } else if (launchConfig.type === LaunchConfigType.LLDB_DAP) { + if (launchConfig.env) { + launchConfig.env = this.convertEnvironmentVariables(launchConfig.env); + } } + return launchConfig; } - convertEnvironmentVariables( - map: { [key: string]: string } | undefined - ): { [key: string]: string } | string[] | undefined { - if (map === undefined) { - return undefined; - } + private convertEnvironmentVariables(map: { [key: string]: string }): string[] { return Object.entries(map).map(([key, value]) => `${key}=${value}`); } } diff --git a/src/debugger/launch.ts b/src/debugger/launch.ts index 549d8d13a..2b0d2a08b 100644 --- a/src/debugger/launch.ts +++ b/src/debugger/launch.ts @@ -17,7 +17,7 @@ import * as vscode from "vscode"; import { FolderContext } from "../FolderContext"; import { BuildFlags } from "../toolchain/BuildFlags"; import { stringArrayInEnglish, swiftLibraryPathKey, swiftRuntimeEnv } from "../utilities/utilities"; -import { DebugAdapter } from "./debugAdapter"; +import { SWIFT_LAUNCH_CONFIG_TYPE } from "./debugAdapter"; import { getFolderAndNameSuffix } from "./buildConfig"; import configuration from "../configuration"; import { CI_DISABLE_ASLR } from "./lldb"; @@ -136,7 +136,7 @@ function createExecutableConfigurations(ctx: FolderContext): vscode.DebugConfigu return executableProducts.flatMap(product => { const baseConfig = { - type: DebugAdapter.getLaunchConfigType(ctx.workspaceContext.swiftVersion), + type: SWIFT_LAUNCH_CONFIG_TYPE, request: "launch", args: [], cwd: folder, @@ -174,7 +174,7 @@ export function createSnippetConfiguration( const buildDirectory = BuildFlags.buildDirectoryFromWorkspacePath(folder, true); return { - type: DebugAdapter.getLaunchConfigType(ctx.workspaceContext.swiftVersion), + type: SWIFT_LAUNCH_CONFIG_TYPE, request: "launch", name: `Run ${snippetName}`, program: path.posix.join(buildDirectory, "debug", snippetName), diff --git a/src/debugger/logTracker.ts b/src/debugger/logTracker.ts index b5dde65e1..459f909e1 100644 --- a/src/debugger/logTracker.ts +++ b/src/debugger/logTracker.ts @@ -13,8 +13,7 @@ //===----------------------------------------------------------------------===// import * as vscode from "vscode"; -import { DebugAdapter } from "./debugAdapter"; -import { Version } from "../utilities/version"; +import { LaunchConfigType } from "./debugAdapter"; import { SwiftOutputChannel } from "../ui/SwiftOutputChannel"; /** @@ -44,28 +43,19 @@ interface DebugMessage { * Register the LoggingDebugAdapterTrackerFactory with the VS Code debug adapter tracker * @returns A disposable to be disposed when the extension is deactivated */ -export function registerLoggingDebugAdapterTracker(swiftVersion: Version): vscode.Disposable { - const register = () => - vscode.debug.registerDebugAdapterTrackerFactory( - DebugAdapter.getLaunchConfigType(swiftVersion), - new LoggingDebugAdapterTrackerFactory() - ); - - // Maintains the disposable for the last registered debug adapter. - let debugAdapterDisposable = register(); - const changeMonitor = vscode.workspace.onDidChangeConfiguration(event => { - if (event.affectsConfiguration("swift.debugger.useDebugAdapterFromToolchain")) { - // Dispose the old adapter and reconfigure with the new settings. - debugAdapterDisposable.dispose(); - debugAdapterDisposable = register(); - } - }); +export function registerLoggingDebugAdapterTracker(): vscode.Disposable { + // Register the factory for both lldb-dap and CodeLLDB since either could be used when + // resolving a Swift launch configuration. + const trackerFactory = new LoggingDebugAdapterTrackerFactory(); + const subscriptions: vscode.Disposable[] = [ + vscode.debug.registerDebugAdapterTrackerFactory(LaunchConfigType.CODE_LLDB, trackerFactory), + vscode.debug.registerDebugAdapterTrackerFactory(LaunchConfigType.LLDB_DAP, trackerFactory), + ]; // Return a disposable that cleans everything up. return { dispose() { - changeMonitor.dispose(); - debugAdapterDisposable.dispose(); + subscriptions.forEach(sub => sub.dispose()); }, }; } diff --git a/test/unit-tests/debugger/attachDebugger.test.ts b/test/unit-tests/debugger/attachDebugger.test.ts index 937132a0d..baaba6dc7 100644 --- a/test/unit-tests/debugger/attachDebugger.test.ts +++ b/test/unit-tests/debugger/attachDebugger.test.ts @@ -27,6 +27,7 @@ import { SwiftToolchain } from "../../../src/toolchain/toolchain"; import { WorkspaceContext } from "../../../src/WorkspaceContext"; import { registerDebugger } from "../../../src/debugger/debugAdapterFactory"; import { Version } from "../../../src/utilities/version"; +import { SwiftOutputChannel } from "../../../src/ui/SwiftOutputChannel"; suite("attachDebugger Unit Test Suite", () => { const lldbMock = mockGlobalModule(lldb); @@ -42,6 +43,7 @@ suite("attachDebugger Unit Test Suite", () => { }); mockContext = mockObject({ toolchain: instance(mockToolchain), + outputChannel: instance(mockObject({})), }); }); diff --git a/test/unit-tests/debugger/debugAdapter.test.ts b/test/unit-tests/debugger/debugAdapter.test.ts index 56971c14d..17008309d 100644 --- a/test/unit-tests/debugger/debugAdapter.test.ts +++ b/test/unit-tests/debugger/debugAdapter.test.ts @@ -42,7 +42,7 @@ suite("DebugAdapter Unit Test Suite", () => { setup(() => { // Mock VS Code settings mockDebugConfig = mockObject<(typeof configuration)["debugger"]>({ - useDebugAdapterFromToolchain: false, + debugAdapter: "auto", customDebugAdapterPath: "", }); mockConfiguration.debugger = instance(mockDebugConfig); @@ -65,28 +65,39 @@ suite("DebugAdapter Unit Test Suite", () => { }); suite("getLaunchConfigType()", () => { - test("returns SWIFT_EXTENSION when Swift version >=6.0.0 and swift.debugger.useDebugAdapterFromToolchain is true", () => { - mockDebugConfig.useDebugAdapterFromToolchain = true; + test("returns SWIFT_EXTENSION when Swift version >=6.0.0 and swift.debugger.debugAdapter is set to lldb-dap", () => { + mockDebugConfig.debugAdapter = "lldb-dap"; expect(DebugAdapter.getLaunchConfigType(new Version(6, 0, 1))).to.equal( - LaunchConfigType.SWIFT_EXTENSION + LaunchConfigType.LLDB_DAP ); }); - test("returns CODE_LLDB when Swift version >=6.0.0 and swift.debugger.useDebugAdapterFromToolchain is false", () => { - mockDebugConfig.useDebugAdapterFromToolchain = false; - expect(DebugAdapter.getLaunchConfigType(new Version(6, 0, 1))).to.equal( + test("returns CODE_LLDB when Swift version >=6.0.0 and swift.debugger.debugAdapter is set to auto or CodeLLDB", () => { + // Try with the setting set to auto + mockDebugConfig.debugAdapter = "auto"; + expect(DebugAdapter.getLaunchConfigType(new Version(5, 10, 0))).to.equal( + LaunchConfigType.CODE_LLDB + ); + // Try with the setting set to CodeLLDB + mockDebugConfig.debugAdapter = "CodeLLDB"; + expect(DebugAdapter.getLaunchConfigType(new Version(5, 10, 0))).to.equal( LaunchConfigType.CODE_LLDB ); }); test("returns CODE_LLDB when Swift version is older than 6.0.0 regardless of setting", () => { - // Try with the setting false - mockDebugConfig.useDebugAdapterFromToolchain = false; + // Try with the setting set to auto + mockDebugConfig.debugAdapter = "auto"; + expect(DebugAdapter.getLaunchConfigType(new Version(5, 10, 0))).to.equal( + LaunchConfigType.CODE_LLDB + ); + // Try with the setting set to CodeLLDB + mockDebugConfig.debugAdapter = "CodeLLDB"; expect(DebugAdapter.getLaunchConfigType(new Version(5, 10, 0))).to.equal( LaunchConfigType.CODE_LLDB ); - // Try with the setting true - mockDebugConfig.useDebugAdapterFromToolchain = true; + // Try with the setting set to lldb-dap + mockDebugConfig.debugAdapter = "lldb-dap"; expect(DebugAdapter.getLaunchConfigType(new Version(5, 10, 0))).to.equal( LaunchConfigType.CODE_LLDB ); @@ -97,7 +108,7 @@ suite("DebugAdapter Unit Test Suite", () => { suite("Using lldb-dap", () => { setup(() => { mockToolchain.swiftVersion = new Version(6, 0, 0); - mockDebugConfig.useDebugAdapterFromToolchain = true; + mockDebugConfig.debugAdapter = "lldb-dap"; // Should be using lldb-dap in this case mockFS({ "/toolchains/swift/lldb-dap": mockFS.file({ content: "", mode: 0o770 }), @@ -144,7 +155,7 @@ suite("DebugAdapter Unit Test Suite", () => { suite("Using lldb-dap with custom debug adapter path", () => { setup(() => { mockToolchain.swiftVersion = new Version(6, 0, 0); - mockDebugConfig.useDebugAdapterFromToolchain = true; + mockDebugConfig.debugAdapter = "lldb-dap"; mockDebugConfig.customDebugAdapterPath = "/path/to/custom/lldb-dap"; // Should be using a custom lldb-dap in this case mockFS({ @@ -158,7 +169,7 @@ suite("DebugAdapter Unit Test Suite", () => { suite("Using CodeLLDB", () => { setup(() => { mockToolchain.swiftVersion = new Version(6, 0, 0); - mockDebugConfig.useDebugAdapterFromToolchain = false; + mockDebugConfig.debugAdapter = "CodeLLDB"; // Should be using CodeLLDB in this case mockFS({ "/toolchains/swift/lldb": mockFS.file({ content: "", mode: 0o770 }), diff --git a/test/unit-tests/debugger/debugAdapterFactory.test.ts b/test/unit-tests/debugger/debugAdapterFactory.test.ts index 852d4e10c..8abf72bb0 100644 --- a/test/unit-tests/debugger/debugAdapterFactory.test.ts +++ b/test/unit-tests/debugger/debugAdapterFactory.test.ts @@ -28,115 +28,15 @@ import { mockFn, } from "../../MockUtils"; import configuration from "../../../src/configuration"; -import { DebugAdapter, LaunchConfigType } from "../../../src/debugger/debugAdapter"; +import { + DebugAdapter, + LaunchConfigType, + SWIFT_LAUNCH_CONFIG_TYPE, +} from "../../../src/debugger/debugAdapter"; import { SwiftToolchain } from "../../../src/toolchain/toolchain"; import { SwiftOutputChannel } from "../../../src/ui/SwiftOutputChannel"; -suite("Debug Adapter Factory Test Suite", () => { - const swift6 = new Version(6, 0, 0); - const swift510 = new Version(5, 10, 1); - const mockDebugConfig = mockGlobalObject(configuration, "debugger"); - - suite("LLDBDebugConfigurationProvider Test Suite", () => { - setup(() => { - mockDebugConfig.useDebugAdapterFromToolchain = true; - }); - - test("uses lldb-dap for swift versions >=6.0.0", async () => { - const configProvider = new LLDBDebugConfigurationProvider("darwin", swift6); - const launchConfig = await configProvider.resolveDebugConfiguration(undefined, { - name: "Test Launch Config", - type: LaunchConfigType.SWIFT_EXTENSION, - request: "launch", - program: "${workspaceFolder}/.build/debug/executable", - }); - expect(launchConfig).to.containSubset({ type: LaunchConfigType.SWIFT_EXTENSION }); - }); - - test("delegates to CodeLLDB for swift versions <6.0.0", async () => { - const configProvider = new LLDBDebugConfigurationProvider("darwin", swift510); - const launchConfig = await configProvider.resolveDebugConfiguration(undefined, { - name: "Test Launch Config", - type: LaunchConfigType.SWIFT_EXTENSION, - request: "launch", - program: "${workspaceFolder}/.build/debug/executable", - }); - expect(launchConfig).to.containSubset({ - type: LaunchConfigType.CODE_LLDB, - sourceLanguages: ["swift"], - }); - }); - - test("delegates to CodeLLDB on Swift 6.0.0 if setting swift.debugger.useDebugAdapterFromToolchain is explicitly disabled", async () => { - mockDebugConfig.useDebugAdapterFromToolchain = false; - const configProvider = new LLDBDebugConfigurationProvider("darwin", swift6); - const launchConfig = await configProvider.resolveDebugConfiguration(undefined, { - name: "Test Launch Config", - type: LaunchConfigType.SWIFT_EXTENSION, - request: "launch", - program: "${workspaceFolder}/.build/debug/executable", - }); - expect(launchConfig).to.containSubset({ - type: LaunchConfigType.CODE_LLDB, - sourceLanguages: ["swift"], - }); - }); - - test("modifies program to add file extension on Windows", async () => { - const configProvider = new LLDBDebugConfigurationProvider("win32", swift6); - const launchConfig = await configProvider.resolveDebugConfiguration(undefined, { - name: "Test Launch Config", - type: LaunchConfigType.SWIFT_EXTENSION, - request: "launch", - program: "${workspaceFolder}/.build/debug/executable", - }); - expect(launchConfig).to.containSubset({ - program: "${workspaceFolder}/.build/debug/executable.exe", - }); - }); - - test("does not modify program on Windows if file extension is already present", async () => { - const configProvider = new LLDBDebugConfigurationProvider("win32", swift6); - const launchConfig = await configProvider.resolveDebugConfiguration(undefined, { - name: "Test Launch Config", - type: LaunchConfigType.SWIFT_EXTENSION, - request: "launch", - program: "${workspaceFolder}/.build/debug/executable.exe", - }); - expect(launchConfig).to.containSubset({ - program: "${workspaceFolder}/.build/debug/executable.exe", - }); - }); - - test("does not modify program on macOS", async () => { - const configProvider = new LLDBDebugConfigurationProvider("darwin", swift6); - const launchConfig = await configProvider.resolveDebugConfiguration(undefined, { - name: "Test Launch Config", - type: LaunchConfigType.SWIFT_EXTENSION, - request: "launch", - program: "${workspaceFolder}/.build/debug/executable", - }); - expect(launchConfig).to.containSubset({ - program: "${workspaceFolder}/.build/debug/executable", - }); - }); - - test("does not modify program on Linux", async () => { - const configProvider = new LLDBDebugConfigurationProvider("linux", swift6); - const launchConfig = await configProvider.resolveDebugConfiguration(undefined, { - name: "Test Launch Config", - type: LaunchConfigType.SWIFT_EXTENSION, - request: "launch", - program: "${workspaceFolder}/.build/debug/executable", - }); - expect(launchConfig).to.containSubset({ - program: "${workspaceFolder}/.build/debug/executable", - }); - }); - }); -}); - -suite("debugAdapterFactory Tests", () => { +suite("LLDBDebugAdapterExecutableFactory Tests", () => { const mockAdapter = mockGlobalModule(DebugAdapter); let mockToolchain: MockedObject; let mockOutputChannel: MockedObject; @@ -206,78 +106,178 @@ suite("debugAdapterFactory Tests", () => { expect(mockAdapter.debugAdapterPath).to.have.been.calledOnce; expect(mockAdapter.verifyDebugAdapterExists).to.have.been.calledOnce; }); +}); - suite("LLDBDebugConfigurationProvider Tests", () => { - let provider: LLDBDebugConfigurationProvider; - const swift6 = new Version(6, 0, 0); +suite("LLDBDebugConfigurationProvider Tests", () => { + let swift6Toolchain: SwiftToolchain; + let swift5Toolchain: SwiftToolchain; + const mockDebugConfig = mockGlobalObject(configuration, "debugger"); - setup(() => { - provider = new LLDBDebugConfigurationProvider("darwin", swift6); - }); + setup(() => { + mockDebugConfig.debugAdapter = "auto"; + swift6Toolchain = instance( + mockObject({ + swiftVersion: new Version(6, 0, 0), + }) + ); + swift5Toolchain = instance( + mockObject({ + swiftVersion: new Version(5, 10, 0), + }) + ); + }); - test("should convert environment variables to string[] format", () => { - const env = { - VAR1: "value1", - VAR2: "value2", - }; + test("delegates to CodeLLDB when debugAdapter is set to auto", async () => { + mockDebugConfig.debugAdapter = "auto"; + const configProvider = new LLDBDebugConfigurationProvider("darwin", swift6Toolchain); + const launchConfig = await configProvider.resolveDebugConfiguration(undefined, { + name: "Test Launch Config", + type: SWIFT_LAUNCH_CONFIG_TYPE, + request: "launch", + program: "${workspaceFolder}/.build/debug/executable", + }); + expect(launchConfig).to.containSubset({ type: LaunchConfigType.CODE_LLDB }); + }); - const result = provider.convertEnvironmentVariables(env); + test("delegates to lldb-dap when debugAdapter is set to lldb-dap and swift version >=6.0.0", async () => { + mockDebugConfig.debugAdapter = "lldb-dap"; + const configProvider = new LLDBDebugConfigurationProvider("darwin", swift6Toolchain); + const launchConfig = await configProvider.resolveDebugConfiguration(undefined, { + name: "Test Launch Config", + type: SWIFT_LAUNCH_CONFIG_TYPE, + request: "launch", + program: "${workspaceFolder}/.build/debug/executable", + }); + expect(launchConfig).to.containSubset({ type: LaunchConfigType.LLDB_DAP }); + }); - expect(result).to.deep.equal(["VAR1=value1", "VAR2=value2"]); + test("delegates to CodeLLDB even though debugAdapter is set to lldb-dap and swift version >=6.0.0", async () => { + mockDebugConfig.debugAdapter = "lldb-dap"; + const configProvider = new LLDBDebugConfigurationProvider("darwin", swift5Toolchain); + const launchConfig = await configProvider.resolveDebugConfiguration(undefined, { + name: "Test Launch Config", + type: SWIFT_LAUNCH_CONFIG_TYPE, + request: "launch", + program: "${workspaceFolder}/.build/debug/executable", }); + expect(launchConfig).to.containSubset({ + type: LaunchConfigType.CODE_LLDB, + sourceLanguages: ["swift"], + }); + }); - test("should return undefined when environment variables are undefined", () => { - const result = provider.convertEnvironmentVariables(undefined); - expect(result).to.deep.equal(undefined); + test("modifies program to add file extension on Windows", async () => { + const configProvider = new LLDBDebugConfigurationProvider("win32", swift6Toolchain); + const launchConfig = await configProvider.resolveDebugConfiguration(undefined, { + name: "Test Launch Config", + type: SWIFT_LAUNCH_CONFIG_TYPE, + request: "launch", + program: "${workspaceFolder}/.build/debug/executable", }); + expect(launchConfig).to.containSubset({ + program: "${workspaceFolder}/.build/debug/executable.exe", + }); + }); - test("should resolve debug configuration with converted environment variables", async () => { - const launchConfig: vscode.DebugConfiguration = { - type: LaunchConfigType.SWIFT_EXTENSION, - request: "launch", - name: "Test Launch", - env: { - VAR1: "value1", - VAR2: "value2", - }, - }; - - const resolvedConfig = await provider.resolveDebugConfiguration( - undefined, - launchConfig - ); - - expect(resolvedConfig.env).to.deep.equal(["VAR1=value1", "VAR2=value2"]); + test("does not modify program on Windows if file extension is already present", async () => { + const configProvider = new LLDBDebugConfigurationProvider("win32", swift6Toolchain); + const launchConfig = await configProvider.resolveDebugConfiguration(undefined, { + name: "Test Launch Config", + type: SWIFT_LAUNCH_CONFIG_TYPE, + request: "launch", + program: "${workspaceFolder}/.build/debug/executable.exe", }); + expect(launchConfig).to.containSubset({ + program: "${workspaceFolder}/.build/debug/executable.exe", + }); + }); - test("should handle one environment variable", () => { - const env = { - VAR1: "value1", - }; - const result = provider.convertEnvironmentVariables(env); + test("does not modify program on macOS", async () => { + const configProvider = new LLDBDebugConfigurationProvider("darwin", swift6Toolchain); + const launchConfig = await configProvider.resolveDebugConfiguration(undefined, { + name: "Test Launch Config", + type: SWIFT_LAUNCH_CONFIG_TYPE, + request: "launch", + program: "${workspaceFolder}/.build/debug/executable", + }); + expect(launchConfig).to.containSubset({ + program: "${workspaceFolder}/.build/debug/executable", + }); + }); - expect(result).to.deep.equal(["VAR1=value1"]); + test("does not modify program on Linux", async () => { + const configProvider = new LLDBDebugConfigurationProvider("linux", swift6Toolchain); + const launchConfig = await configProvider.resolveDebugConfiguration(undefined, { + name: "Test Launch Config", + type: SWIFT_LAUNCH_CONFIG_TYPE, + request: "launch", + program: "${workspaceFolder}/.build/debug/executable", + }); + expect(launchConfig).to.containSubset({ + program: "${workspaceFolder}/.build/debug/executable", }); + }); - test("should handle empty environment variables", () => { - const env = {}; - const result = provider.convertEnvironmentVariables(env); + test("should convert environment variables to string[] format when using lldb-dap", async () => { + mockDebugConfig.debugAdapter = "lldb-dap"; + const configProvider = new LLDBDebugConfigurationProvider("darwin", swift6Toolchain); + const launchConfig = await configProvider.resolveDebugConfiguration(undefined, { + name: "Test Launch Config", + type: SWIFT_LAUNCH_CONFIG_TYPE, + request: "launch", + program: "${workspaceFolder}/.build/debug/executable", + env: { + VAR1: "value1", + VAR2: "value2", + }, + }); + expect(launchConfig) + .to.have.property("env") + .that.deep.equals(["VAR1=value1", "VAR2=value2"]); + }); - expect(result).to.deep.equal([]); + test("should leave env undefined when environment variables are undefined and using lldb-dap", async () => { + mockDebugConfig.debugAdapter = "lldb-dap"; + const configProvider = new LLDBDebugConfigurationProvider("darwin", swift6Toolchain); + const launchConfig = await configProvider.resolveDebugConfiguration(undefined, { + name: "Test Launch Config", + type: SWIFT_LAUNCH_CONFIG_TYPE, + request: "launch", + program: "${workspaceFolder}/.build/debug/executable", }); + expect(launchConfig).to.not.have.property("env"); + }); - test("should handle a large number of environment variables", () => { - // Create 1000 environment variables - const env: { [key: string]: string } = {}; - for (let i = 0; i < 1000; i++) { - env[`VAR${i}`] = `value${i}`; - } + test("should convert empty environment variables when using lldb-dap", async () => { + mockDebugConfig.debugAdapter = "lldb-dap"; + const configProvider = new LLDBDebugConfigurationProvider("darwin", swift6Toolchain); + const launchConfig = await configProvider.resolveDebugConfiguration(undefined, { + type: SWIFT_LAUNCH_CONFIG_TYPE, + request: "launch", + name: "Test Launch", + env: {}, + }); - const result = provider.convertEnvironmentVariables(env); + expect(launchConfig).to.have.property("env").that.deep.equals([]); + }); - // Verify that all 1000 environment variables are properly converted - const expected = Array.from({ length: 1000 }, (_, i) => `VAR${i}=value${i}`); - expect(result).to.deep.equal(expected); + test("should handle a large number of environment variables when using lldb-dap", async () => { + mockDebugConfig.debugAdapter = "lldb-dap"; + // Create 1000 environment variables + const env: { [key: string]: string } = {}; + for (let i = 0; i < 1000; i++) { + env[`VAR${i}`] = `value${i}`; + } + const configProvider = new LLDBDebugConfigurationProvider("darwin", swift6Toolchain); + const launchConfig = await configProvider.resolveDebugConfiguration(undefined, { + type: SWIFT_LAUNCH_CONFIG_TYPE, + request: "launch", + name: "Test Launch", + env, }); + + // Verify that all 1000 environment variables are properly converted + const expectedEnv = Array.from({ length: 1000 }, (_, i) => `VAR${i}=value${i}`); + expect(launchConfig).to.have.property("env").that.deep.equals(expectedEnv); }); }); From 7fa7cb1c5dfb906dc535b82006304e89847a2a59 Mon Sep 17 00:00:00 2001 From: Matthew Bastien Date: Wed, 12 Feb 2025 13:27:46 -0500 Subject: [PATCH 06/86] prompt for CodeLLDB settings only when launching a debug session (#1381) --- src/WorkspaceContext.ts | 74 +-- src/debugger/debugAdapter.ts | 56 +-- src/debugger/debugAdapterFactory.ts | 99 ++-- src/debugger/lldb.ts | 4 +- test/unit-tests/debugger/debugAdapter.test.ts | 234 +--------- .../debugger/debugAdapterFactory.test.ts | 424 ++++++++++-------- 6 files changed, 312 insertions(+), 579 deletions(-) diff --git a/src/WorkspaceContext.ts b/src/WorkspaceContext.ts index 5e59509be..b3a635815 100644 --- a/src/WorkspaceContext.ts +++ b/src/WorkspaceContext.ts @@ -17,9 +17,8 @@ import * as path from "path"; import { FolderContext } from "./FolderContext"; import { StatusItem } from "./ui/StatusItem"; import { SwiftOutputChannel } from "./ui/SwiftOutputChannel"; -import { swiftLibraryPathKey, getErrorDescription } from "./utilities/utilities"; +import { swiftLibraryPathKey } from "./utilities/utilities"; import { pathExists, isPathInsidePath } from "./utilities/filesystem"; -import { getLLDBLibPath } from "./debugger/lldb"; import { LanguageClientManager } from "./sourcekit-lsp/LanguageClientManager"; import { TemporaryFolder } from "./utilities/tempFolder"; import { TaskManager } from "./tasks/TaskManager"; @@ -29,7 +28,6 @@ import configuration from "./configuration"; import contextKeys from "./contextKeys"; import { setSnippetContextKey } from "./SwiftSnippets"; import { CommentCompletionProviders } from "./editor/CommentCompletion"; -import { DebugAdapter, LaunchConfigType } from "./debugger/debugAdapter"; import { SwiftBuildStatus } from "./ui/SwiftBuildStatus"; import { SwiftToolchain } from "./toolchain/toolchain"; import { DiagnosticsManager } from "./DiagnosticsManager"; @@ -444,76 +442,6 @@ export class WorkspaceContext implements vscode.Disposable { return { dispose: () => this.swiftFileObservers.delete(listener) }; } - /** find LLDB version and setup path in CodeLLDB */ - async setLLDBVersion() { - // check we are using CodeLLDB - if (DebugAdapter.getLaunchConfigType(this.swiftVersion) !== LaunchConfigType.CODE_LLDB) { - return; - } - const libPathResult = await getLLDBLibPath(this.toolchain); - if (!libPathResult.success) { - // if failure message is undefined then fail silently - if (!libPathResult.failure) { - return; - } - const errorMessage = `Error: ${getErrorDescription(libPathResult.failure)}`; - vscode.window.showErrorMessage( - `Failed to setup CodeLLDB for debugging of Swift code. Debugging may produce unexpected results. ${errorMessage}` - ); - this.outputChannel.log(`Failed to setup CodeLLDB: ${errorMessage}`); - return; - } - - const libPath = libPathResult.success; - const lldbConfig = vscode.workspace.getConfiguration("lldb"); - const configLLDBPath = lldbConfig.get("library"); - const expressions = lldbConfig.get("launch.expressions"); - if (configLLDBPath === libPath && expressions === "native") { - return; - } - - // show dialog for setting up LLDB - vscode.window - .showInformationMessage( - "The Swift extension needs to update some CodeLLDB settings to enable debugging features. Do you want to set this up in your global settings or the workspace settings?", - "Global", - "Workspace", - "Cancel" - ) - .then(result => { - switch (result) { - case "Global": - lldbConfig.update("library", libPath, vscode.ConfigurationTarget.Global); - lldbConfig.update( - "launch.expressions", - "native", - vscode.ConfigurationTarget.Global - ); - // clear workspace setting - lldbConfig.update( - "library", - undefined, - vscode.ConfigurationTarget.Workspace - ); - // clear workspace setting - lldbConfig.update( - "launch.expressions", - undefined, - vscode.ConfigurationTarget.Workspace - ); - break; - case "Workspace": - lldbConfig.update("library", libPath, vscode.ConfigurationTarget.Workspace); - lldbConfig.update( - "launch.expressions", - "native", - vscode.ConfigurationTarget.Workspace - ); - break; - } - }); - } - /** set focus based on the file a TextEditor is editing */ async focusTextEditor(editor?: vscode.TextEditor) { await this.focusUri(editor?.document.uri); diff --git a/src/debugger/debugAdapter.ts b/src/debugger/debugAdapter.ts index 2b5d60256..3547b1850 100644 --- a/src/debugger/debugAdapter.ts +++ b/src/debugger/debugAdapter.ts @@ -12,13 +12,9 @@ // //===----------------------------------------------------------------------===// -import * as vscode from "vscode"; import configuration from "../configuration"; -import contextKeys from "../contextKeys"; -import { fileExists } from "../utilities/filesystem"; import { Version } from "../utilities/version"; import { SwiftToolchain } from "../toolchain/toolchain"; -import { SwiftOutputChannel } from "../ui/SwiftOutputChannel"; /** * The launch configuration type added by the Swift extension that will delegate to the appropriate @@ -61,59 +57,11 @@ export class DebugAdapter { * @param toolchain The Swift toolchain to use * @returns A path to the debug adapter for the user's toolchain and configuration **/ - public static async debugAdapterPath(toolchain: SwiftToolchain): Promise { + public static async getLLDBDebugAdapterPath(toolchain: SwiftToolchain): Promise { const customDebugAdapterPath = configuration.debugger.customDebugAdapterPath; if (customDebugAdapterPath.length > 0) { return customDebugAdapterPath; } - - const debugAdapter = this.getLaunchConfigType(toolchain.swiftVersion); - switch (debugAdapter) { - case LaunchConfigType.LLDB_DAP: - return toolchain.getLLDBDebugAdapter(); - case LaunchConfigType.CODE_LLDB: - return toolchain.getLLDB(); - } - } - - /** - * Verify that the toolchain debug adapter exists and display an error message to the user - * if it doesn't. - * - * Has the side effect of setting the `swift.lldbVSCodeAvailable` context key depending - * on the result. - * - * @param workspace WorkspaceContext - * @param quiet Whether or not the dialog should be displayed if the adapter does not exist - * @returns Whether or not the debug adapter exists - */ - public static async verifyDebugAdapterExists( - toolchain: SwiftToolchain, - outputChannel: SwiftOutputChannel, - quiet = false - ): Promise { - const lldbDebugAdapterPath = await this.debugAdapterPath(toolchain).catch(error => { - outputChannel.log(error); - return undefined; - }); - - if (!lldbDebugAdapterPath || !(await fileExists(lldbDebugAdapterPath))) { - if (!quiet) { - const debugAdapterName = this.getLaunchConfigType(toolchain.swiftVersion); - vscode.window.showErrorMessage( - configuration.debugger.customDebugAdapterPath.length > 0 - ? `Cannot find ${debugAdapterName} debug adapter specified in setting Swift.Debugger.Path.` - : `Cannot find ${debugAdapterName} debug adapter in your Swift toolchain.` - ); - } - if (lldbDebugAdapterPath) { - outputChannel.log(`Failed to find ${lldbDebugAdapterPath}`); - } - contextKeys.lldbVSCodeAvailable = false; - return false; - } - - contextKeys.lldbVSCodeAvailable = true; - return true; + return toolchain.getLLDBDebugAdapter(); } } diff --git a/src/debugger/debugAdapterFactory.ts b/src/debugger/debugAdapterFactory.ts index 5a0fd17e8..125c4e7f6 100644 --- a/src/debugger/debugAdapterFactory.ts +++ b/src/debugger/debugAdapterFactory.ts @@ -19,6 +19,9 @@ import { DebugAdapter, LaunchConfigType, SWIFT_LAUNCH_CONFIG_TYPE } from "./debu import { registerLoggingDebugAdapterTracker } from "./logTracker"; import { SwiftToolchain } from "../toolchain/toolchain"; import { SwiftOutputChannel } from "../ui/SwiftOutputChannel"; +import { fileExists } from "../utilities/filesystem"; +import { getLLDBLibPath } from "./lldb"; +import { getErrorDescription } from "../utilities/utilities"; /** * Registers the active debugger with the extension, and reregisters it @@ -27,35 +30,11 @@ import { SwiftOutputChannel } from "../ui/SwiftOutputChannel"; * @returns A disposable to be disposed when the extension is deactivated */ export function registerDebugger(workspaceContext: WorkspaceContext): vscode.Disposable { - async function updateDebugAdapter() { - await workspaceContext.setLLDBVersion(); - - // Verify that the adapter exists, but only after registration. This async method - // is basically an unstructured task so we don't want to run it before the adapter - // registration above as it could cause code executing immediately after register() - // to use the incorrect adapter. - DebugAdapter.verifyDebugAdapterExists( - workspaceContext.toolchain, - workspaceContext.outputChannel, - true - ).catch(error => { - workspaceContext.outputChannel.log(error); - }); - } - const subscriptions: vscode.Disposable[] = [ registerLoggingDebugAdapterTracker(), registerLLDBDebugAdapter(workspaceContext.toolchain, workspaceContext.outputChannel), - vscode.workspace.onDidChangeConfiguration(event => { - if (event.affectsConfiguration("swift.debugger.useDebugAdapterFromToolchain")) { - updateDebugAdapter(); - } - }), ]; - // Perform the initial registration, then reregister every time the settings change. - updateDebugAdapter(); - return { dispose: () => { subscriptions.map(sub => sub.dispose()); @@ -79,7 +58,7 @@ function registerLLDBDebugAdapter( const debugConfigProvider = vscode.debug.registerDebugConfigurationProvider( SWIFT_LAUNCH_CONFIG_TYPE, - new LLDBDebugConfigurationProvider(process.platform, toolchain) + new LLDBDebugConfigurationProvider(process.platform, toolchain, outputChannel) ); return { @@ -117,8 +96,7 @@ export class LLDBDebugAdapterExecutableFactory implements vscode.DebugAdapterDes } async createDebugAdapterDescriptor(): Promise { - const path = await DebugAdapter.debugAdapterPath(this.toolchain); - await DebugAdapter.verifyDebugAdapterExists(this.toolchain, this.outputChannel); + const path = await DebugAdapter.getLLDBDebugAdapterPath(this.toolchain); return new vscode.DebugAdapterExecutable(path, [], {}); } } @@ -135,7 +113,8 @@ export class LLDBDebugAdapterExecutableFactory implements vscode.DebugAdapterDes export class LLDBDebugConfigurationProvider implements vscode.DebugConfigurationProvider { constructor( private platform: NodeJS.Platform, - private toolchain: SwiftToolchain + private toolchain: SwiftToolchain, + private outputChannel: SwiftOutputChannel ) {} async resolveDebugConfiguration( @@ -155,15 +134,79 @@ export class LLDBDebugConfigurationProvider implements vscode.DebugConfiguration launchConfig.type = DebugAdapter.getLaunchConfigType(this.toolchain.swiftVersion); if (launchConfig.type === LaunchConfigType.CODE_LLDB) { launchConfig.sourceLanguages = ["swift"]; + // Prompt the user to update CodeLLDB settings if necessary + await this.promptForCodeLldbSettings(); } else if (launchConfig.type === LaunchConfigType.LLDB_DAP) { if (launchConfig.env) { launchConfig.env = this.convertEnvironmentVariables(launchConfig.env); } + const lldbDapPath = await DebugAdapter.getLLDBDebugAdapterPath(this.toolchain); + // Verify that the debug adapter exists or bail otherwise + if (!(await fileExists(lldbDapPath))) { + vscode.window.showErrorMessage( + `Cannot find the LLDB debug adapter in your Swift toolchain: No such file or directory "${lldbDapPath}"` + ); + return undefined; + } } return launchConfig; } + private async promptForCodeLldbSettings(): Promise { + const libLldbPathResult = await getLLDBLibPath(this.toolchain); + if (!libLldbPathResult.success) { + const errorMessage = `Error: ${getErrorDescription(libLldbPathResult.failure)}`; + vscode.window.showWarningMessage( + `Failed to setup CodeLLDB for debugging of Swift code. Debugging may produce unexpected results. ${errorMessage}` + ); + this.outputChannel.log(`Failed to setup CodeLLDB: ${errorMessage}`); + return; + } + const libLldbPath = libLldbPathResult.success; + const lldbConfig = vscode.workspace.getConfiguration("lldb"); + if ( + lldbConfig.get("library") === libLldbPath && + lldbConfig.get("launch.expressions") === "native" + ) { + return; + } + const userSelection = await vscode.window.showInformationMessage( + "The Swift extension needs to update some CodeLLDB settings to enable debugging features. Do you want to set this up in your global settings or workspace settings?", + { modal: true }, + "Global", + "Workspace", + "Run Anyway" + ); + switch (userSelection) { + case "Global": + lldbConfig.update("library", libLldbPath, vscode.ConfigurationTarget.Global); + lldbConfig.update( + "launch.expressions", + "native", + vscode.ConfigurationTarget.Global + ); + // clear workspace setting + lldbConfig.update("library", undefined, vscode.ConfigurationTarget.Workspace); + // clear workspace setting + lldbConfig.update( + "launch.expressions", + undefined, + vscode.ConfigurationTarget.Workspace + ); + break; + case "Workspace": + lldbConfig.update("library", libLldbPath, vscode.ConfigurationTarget.Workspace); + lldbConfig.update( + "launch.expressions", + "native", + vscode.ConfigurationTarget.Workspace + ); + break; + } + return; + } + private convertEnvironmentVariables(map: { [key: string]: string }): string[] { return Object.entries(map).map(([key, value]) => `${key}=${value}`); } diff --git a/src/debugger/lldb.ts b/src/debugger/lldb.ts index 803913c27..cf0e0d2fe 100644 --- a/src/debugger/lldb.ts +++ b/src/debugger/lldb.ts @@ -33,8 +33,8 @@ export const CI_DISABLE_ASLR = : {}; /** - * Get LLDB library for given LLDB executable - * @param executable LLDB executable + * Get the path to the LLDB library. + * * @returns Library path for LLDB */ export async function getLLDBLibPath(toolchain: SwiftToolchain): Promise> { diff --git a/test/unit-tests/debugger/debugAdapter.test.ts b/test/unit-tests/debugger/debugAdapter.test.ts index 17008309d..e0c877ba2 100644 --- a/test/unit-tests/debugger/debugAdapter.test.ts +++ b/test/unit-tests/debugger/debugAdapter.test.ts @@ -12,32 +12,17 @@ // //===----------------------------------------------------------------------===// -import * as vscode from "vscode"; import { expect } from "chai"; import * as mockFS from "mock-fs"; -import { - mockGlobalObject, - MockedObject, - mockObject, - instance, - mockGlobalModule, - mockFn, -} from "../../MockUtils"; +import { MockedObject, mockObject, instance, mockGlobalModule } from "../../MockUtils"; import configuration from "../../../src/configuration"; import { DebugAdapter, LaunchConfigType } from "../../../src/debugger/debugAdapter"; -import { SwiftToolchain } from "../../../src/toolchain/toolchain"; -import { SwiftOutputChannel } from "../../../src/ui/SwiftOutputChannel"; import { Version } from "../../../src/utilities/version"; -import contextKeys from "../../../src/contextKeys"; suite("DebugAdapter Unit Test Suite", () => { const mockConfiguration = mockGlobalModule(configuration); - const mockedContextKeys = mockGlobalModule(contextKeys); - const mockedWindow = mockGlobalObject(vscode, "window"); let mockDebugConfig: MockedObject<(typeof configuration)["debugger"]>; - let mockToolchain: MockedObject; - let mockOutputChannel: MockedObject; setup(() => { // Mock VS Code settings @@ -48,16 +33,6 @@ suite("DebugAdapter Unit Test Suite", () => { mockConfiguration.debugger = instance(mockDebugConfig); // Mock the file system mockFS({}); - // Mock the WorkspaceContext and related dependencies - const toolchainPath = "/toolchains/swift"; - mockToolchain = mockObject({ - swiftVersion: new Version(6, 0, 0), - getLLDBDebugAdapter: mockFn(s => s.callsFake(() => toolchainPath + "/lldb-dap")), - getLLDB: mockFn(s => s.callsFake(() => toolchainPath + "/lldb")), - }); - mockOutputChannel = mockObject({ - log: mockFn(), - }); }); teardown(() => { @@ -103,211 +78,4 @@ suite("DebugAdapter Unit Test Suite", () => { ); }); }); - - suite("verifyDebugAdapterExists()", () => { - suite("Using lldb-dap", () => { - setup(() => { - mockToolchain.swiftVersion = new Version(6, 0, 0); - mockDebugConfig.debugAdapter = "lldb-dap"; - // Should be using lldb-dap in this case - mockFS({ - "/toolchains/swift/lldb-dap": mockFS.file({ content: "", mode: 0o770 }), - }); - }); - - createCommonTests(); - - test("returns false when the toolchain throws an error trying to find lldb-dap", async () => { - mockToolchain.getLLDBDebugAdapter.rejects(new Error("Uh oh!")); - - await expect( - DebugAdapter.verifyDebugAdapterExists( - instance(mockToolchain), - instance(mockOutputChannel), - false - ) - ).to.eventually.be.false; - }); - - test("shows an error message to the user when the toolchain throws an error trying to find lldb-dap", async () => { - mockToolchain.getLLDBDebugAdapter.rejects(new Error("Uh oh!")); - - await DebugAdapter.verifyDebugAdapterExists( - instance(mockToolchain), - instance(mockOutputChannel), - false - ); - expect(mockedWindow.showErrorMessage).to.have.been.calledOnce; - }); - - test("disables the swift.lldbVSCodeAvailable context key if the toolchain throws an error trying to find lldb-dap", async () => { - mockToolchain.getLLDBDebugAdapter.rejects(new Error("Uh oh!")); - - await DebugAdapter.verifyDebugAdapterExists( - instance(mockToolchain), - instance(mockOutputChannel), - false - ); - expect(mockedContextKeys.lldbVSCodeAvailable).to.be.false; - }); - }); - - suite("Using lldb-dap with custom debug adapter path", () => { - setup(() => { - mockToolchain.swiftVersion = new Version(6, 0, 0); - mockDebugConfig.debugAdapter = "lldb-dap"; - mockDebugConfig.customDebugAdapterPath = "/path/to/custom/lldb-dap"; - // Should be using a custom lldb-dap in this case - mockFS({ - "/path/to/custom/lldb-dap": mockFS.file({ content: "", mode: 0o770 }), - }); - }); - - createCommonTests(); - }); - - suite("Using CodeLLDB", () => { - setup(() => { - mockToolchain.swiftVersion = new Version(6, 0, 0); - mockDebugConfig.debugAdapter = "CodeLLDB"; - // Should be using CodeLLDB in this case - mockFS({ - "/toolchains/swift/lldb": mockFS.file({ content: "", mode: 0o770 }), - }); - }); - - createCommonTests(); - - test("returns false when the toolchain throws an error trying to find lldb", async () => { - mockToolchain.getLLDB.rejects(new Error("Uh oh!")); - - await expect( - DebugAdapter.verifyDebugAdapterExists( - instance(mockToolchain), - instance(mockOutputChannel), - false - ) - ).to.eventually.be.false; - }); - - test("shows an error message to the user when the toolchain throws an error trying to find lldb", async () => { - mockToolchain.getLLDB.rejects(new Error("Uh oh!")); - - await DebugAdapter.verifyDebugAdapterExists( - instance(mockToolchain), - instance(mockOutputChannel), - false - ); - expect(mockedWindow.showErrorMessage).to.have.been.calledOnce; - }); - - test("disables the swift.lldbVSCodeAvailable context key if the toolchain throws an error trying to find lldb", async () => { - mockToolchain.getLLDB.rejects(new Error("Uh oh!")); - - await DebugAdapter.verifyDebugAdapterExists( - instance(mockToolchain), - instance(mockOutputChannel), - false - ); - expect(mockedContextKeys.lldbVSCodeAvailable).to.be.false; - }); - }); - - function createCommonTests() { - test("returns true when debug adapter exists regardless of quiet setting", async () => { - // Test with quiet = true - await expect( - DebugAdapter.verifyDebugAdapterExists( - instance(mockToolchain), - instance(mockOutputChannel), - true - ) - ).to.eventually.be.true; - - // Test with quiet = false - await expect( - DebugAdapter.verifyDebugAdapterExists( - instance(mockToolchain), - instance(mockOutputChannel), - false - ) - ).to.eventually.be.true; - }); - - test("returns false when debug adapter doesn't exist regardless of quiet setting", async () => { - // Reset the file system to empty - mockFS({}); - - // Test with quiet = true - await expect( - DebugAdapter.verifyDebugAdapterExists( - instance(mockToolchain), - instance(mockOutputChannel), - true - ) - ).to.eventually.be.false; - - // Test with quiet = false - await expect( - DebugAdapter.verifyDebugAdapterExists( - instance(mockToolchain), - instance(mockOutputChannel), - false - ) - ).to.eventually.be.false; - }); - - test("shows an error message to the user when the debug adapter doesn't exist and quiet is false", async () => { - // Reset the file system to empty - mockFS({}); - - await DebugAdapter.verifyDebugAdapterExists( - instance(mockToolchain), - instance(mockOutputChannel), - false - ); - expect(mockedWindow.showErrorMessage).to.have.been.called; - }); - - test("doesn't show an error message to the user when the debug adapter doesn't exist and quiet is false", async () => { - // Reset the file system to empty - mockFS({}); - - await DebugAdapter.verifyDebugAdapterExists( - instance(mockToolchain), - instance(mockOutputChannel), - true - ); - expect(mockedWindow.showErrorMessage).to.not.have.been.called; - }); - - test("doesn't show an error message to the user when the debug adapter exists", async () => { - await DebugAdapter.verifyDebugAdapterExists( - instance(mockToolchain), - instance(mockOutputChannel), - true - ); - expect(mockedWindow.showErrorMessage).to.not.have.been.called; - }); - - test("enables the swift.lldbVSCodeAvailable context key if the debugger exists", async () => { - await DebugAdapter.verifyDebugAdapterExists( - instance(mockToolchain), - instance(mockOutputChannel) - ); - expect(mockedContextKeys.lldbVSCodeAvailable).to.be.true; - }); - - test("disables the swift.lldbVSCodeAvailable context key if the debugger doesn't exist", async () => { - // Reset the file system to empty - mockFS({}); - - await DebugAdapter.verifyDebugAdapterExists( - instance(mockToolchain), - instance(mockOutputChannel) - ); - expect(mockedContextKeys.lldbVSCodeAvailable).to.be.false; - }); - } - }); }); diff --git a/test/unit-tests/debugger/debugAdapterFactory.test.ts b/test/unit-tests/debugger/debugAdapterFactory.test.ts index 8abf72bb0..f32bcde68 100644 --- a/test/unit-tests/debugger/debugAdapterFactory.test.ts +++ b/test/unit-tests/debugger/debugAdapterFactory.test.ts @@ -27,17 +27,20 @@ import { mockGlobalModule, mockFn, } from "../../MockUtils"; -import configuration from "../../../src/configuration"; +import * as mockFS from "mock-fs"; import { DebugAdapter, LaunchConfigType, SWIFT_LAUNCH_CONFIG_TYPE, } from "../../../src/debugger/debugAdapter"; +import * as lldb from "../../../src/debugger/lldb"; import { SwiftToolchain } from "../../../src/toolchain/toolchain"; import { SwiftOutputChannel } from "../../../src/ui/SwiftOutputChannel"; +import * as debugAdapter from "../../../src/debugger/debugAdapter"; +import { Result } from "../../../src/utilities/result"; suite("LLDBDebugAdapterExecutableFactory Tests", () => { - const mockAdapter = mockGlobalModule(DebugAdapter); + const mockDebugAdapter = mockGlobalModule(DebugAdapter); let mockToolchain: MockedObject; let mockOutputChannel: MockedObject; @@ -48,11 +51,10 @@ suite("LLDBDebugAdapterExecutableFactory Tests", () => { }); }); - test("should return DebugAdapterExecutable when path and verification succeed", async () => { + test("should return a DebugAdapterExecutable with the path to lldb-dap", async () => { const toolchainPath = "/path/to/debug/adapter"; - mockAdapter.debugAdapterPath.resolves(toolchainPath); - mockAdapter.verifyDebugAdapterExists.resolves(true); + mockDebugAdapter.getLLDBDebugAdapterPath.resolves(toolchainPath); const factory = new LLDBDebugAdapterExecutableFactory( instance(mockToolchain), @@ -61,223 +63,267 @@ suite("LLDBDebugAdapterExecutableFactory Tests", () => { const result = await factory.createDebugAdapterDescriptor(); expect(result).to.be.instanceOf(vscode.DebugAdapterExecutable); - expect((result as vscode.DebugAdapterExecutable).command).to.equal(toolchainPath); - - expect(mockAdapter.debugAdapterPath).to.have.been.calledOnce; - expect(mockAdapter.verifyDebugAdapterExists).to.have.been.calledOnce; - }); - - test("should throw error if debugAdapterPath fails", async () => { - const errorMessage = "Failed to get debug adapter path"; - - mockAdapter.debugAdapterPath.rejects(new Error(errorMessage)); - - const factory = new LLDBDebugAdapterExecutableFactory( - instance(mockToolchain), - instance(mockOutputChannel) - ); - - await expect(factory.createDebugAdapterDescriptor()).to.eventually.be.rejectedWith( - Error, - errorMessage - ); - - expect(mockAdapter.debugAdapterPath).to.have.been.calledOnce; - expect(mockAdapter.verifyDebugAdapterExists).to.not.have.been.called; - }); - - test("should throw error if verifyDebugAdapterExists fails", async () => { - const toolchainPath = "/path/to/debug/adapter"; - const errorMessage = "Failed to verify debug adapter exists"; - - mockAdapter.debugAdapterPath.resolves(toolchainPath); - mockAdapter.verifyDebugAdapterExists.rejects(new Error(errorMessage)); - - const factory = new LLDBDebugAdapterExecutableFactory( - instance(mockToolchain), - instance(mockOutputChannel) - ); - - await expect(factory.createDebugAdapterDescriptor()).to.eventually.be.rejectedWith( - Error, - errorMessage - ); - - expect(mockAdapter.debugAdapterPath).to.have.been.calledOnce; - expect(mockAdapter.verifyDebugAdapterExists).to.have.been.calledOnce; + expect(result).to.have.property("command").that.equals(toolchainPath); }); }); suite("LLDBDebugConfigurationProvider Tests", () => { - let swift6Toolchain: SwiftToolchain; - let swift5Toolchain: SwiftToolchain; - const mockDebugConfig = mockGlobalObject(configuration, "debugger"); + let mockToolchain: MockedObject; + let mockOutputChannel: MockedObject; + const mockDebugAdapter = mockGlobalObject(debugAdapter, "DebugAdapter"); + const mockWindow = mockGlobalObject(vscode, "window"); setup(() => { - mockDebugConfig.debugAdapter = "auto"; - swift6Toolchain = instance( - mockObject({ - swiftVersion: new Version(6, 0, 0), - }) - ); - swift5Toolchain = instance( - mockObject({ - swiftVersion: new Version(5, 10, 0), - }) - ); - }); - - test("delegates to CodeLLDB when debugAdapter is set to auto", async () => { - mockDebugConfig.debugAdapter = "auto"; - const configProvider = new LLDBDebugConfigurationProvider("darwin", swift6Toolchain); - const launchConfig = await configProvider.resolveDebugConfiguration(undefined, { - name: "Test Launch Config", - type: SWIFT_LAUNCH_CONFIG_TYPE, - request: "launch", - program: "${workspaceFolder}/.build/debug/executable", + mockToolchain = mockObject({ swiftVersion: new Version(6, 0, 0) }); + mockOutputChannel = mockObject({ + log: mockFn(), }); - expect(launchConfig).to.containSubset({ type: LaunchConfigType.CODE_LLDB }); }); - test("delegates to lldb-dap when debugAdapter is set to lldb-dap and swift version >=6.0.0", async () => { - mockDebugConfig.debugAdapter = "lldb-dap"; - const configProvider = new LLDBDebugConfigurationProvider("darwin", swift6Toolchain); - const launchConfig = await configProvider.resolveDebugConfiguration(undefined, { - name: "Test Launch Config", - type: SWIFT_LAUNCH_CONFIG_TYPE, - request: "launch", - program: "${workspaceFolder}/.build/debug/executable", + suite("CodeLLDB selected in settings", () => { + let mockLldbConfiguration: MockedObject; + const mockLLDB = mockGlobalModule(lldb); + const mockWorkspace = mockGlobalObject(vscode, "workspace"); + + setup(() => { + mockLldbConfiguration = mockObject({ + get: mockFn(s => { + s.withArgs("library").returns("/path/to/liblldb.dyLib"); + s.withArgs("launch.expressions").returns("native"); + }), + update: mockFn(), + }); + mockWorkspace.getConfiguration.returns(instance(mockLldbConfiguration)); + mockLLDB.getLLDBLibPath.resolves(Result.makeSuccess("/path/to/liblldb.dyLib")); + mockDebugAdapter.getLaunchConfigType.returns(LaunchConfigType.CODE_LLDB); }); - expect(launchConfig).to.containSubset({ type: LaunchConfigType.LLDB_DAP }); - }); - test("delegates to CodeLLDB even though debugAdapter is set to lldb-dap and swift version >=6.0.0", async () => { - mockDebugConfig.debugAdapter = "lldb-dap"; - const configProvider = new LLDBDebugConfigurationProvider("darwin", swift5Toolchain); - const launchConfig = await configProvider.resolveDebugConfiguration(undefined, { - name: "Test Launch Config", - type: SWIFT_LAUNCH_CONFIG_TYPE, - request: "launch", - program: "${workspaceFolder}/.build/debug/executable", + test("returns a launch configuration that uses CodeLLDB as the debug adapter", async () => { + const configProvider = new LLDBDebugConfigurationProvider( + "darwin", + instance(mockToolchain), + instance(mockOutputChannel) + ); + const launchConfig = await configProvider.resolveDebugConfiguration(undefined, { + name: "Test Launch Config", + type: SWIFT_LAUNCH_CONFIG_TYPE, + request: "launch", + program: "${workspaceFolder}/.build/debug/executable", + }); + expect(launchConfig).to.containSubset({ type: LaunchConfigType.CODE_LLDB }); }); - expect(launchConfig).to.containSubset({ - type: LaunchConfigType.CODE_LLDB, - sourceLanguages: ["swift"], - }); - }); - test("modifies program to add file extension on Windows", async () => { - const configProvider = new LLDBDebugConfigurationProvider("win32", swift6Toolchain); - const launchConfig = await configProvider.resolveDebugConfiguration(undefined, { - name: "Test Launch Config", - type: SWIFT_LAUNCH_CONFIG_TYPE, - request: "launch", - program: "${workspaceFolder}/.build/debug/executable", - }); - expect(launchConfig).to.containSubset({ - program: "${workspaceFolder}/.build/debug/executable.exe", + test("prompts the user to update CodeLLDB settings if they aren't configured yet", async () => { + mockLldbConfiguration.get.withArgs("library").returns(undefined); + mockWindow.showInformationMessage.resolves("Global" as any); + const configProvider = new LLDBDebugConfigurationProvider( + "darwin", + instance(mockToolchain), + instance(mockOutputChannel) + ); + await expect( + configProvider.resolveDebugConfiguration(undefined, { + name: "Test Launch Config", + type: SWIFT_LAUNCH_CONFIG_TYPE, + request: "launch", + program: "${workspaceFolder}/.build/debug/executable", + }) + ).to.eventually.be.an("object"); + expect(mockWindow.showInformationMessage).to.have.been.calledOnce; + expect(mockLldbConfiguration.update).to.have.been.calledWith( + "library", + "/path/to/liblldb.dyLib" + ); }); }); - test("does not modify program on Windows if file extension is already present", async () => { - const configProvider = new LLDBDebugConfigurationProvider("win32", swift6Toolchain); - const launchConfig = await configProvider.resolveDebugConfiguration(undefined, { - name: "Test Launch Config", - type: SWIFT_LAUNCH_CONFIG_TYPE, - request: "launch", - program: "${workspaceFolder}/.build/debug/executable.exe", + suite("lldb-dap selected in settings", () => { + setup(() => { + mockDebugAdapter.getLaunchConfigType.returns(LaunchConfigType.LLDB_DAP); + mockDebugAdapter.getLLDBDebugAdapterPath.resolves("/path/to/lldb-dap"); + mockFS({ + "/path/to/lldb-dap": mockFS.file({ content: "", mode: 0o770 }), + }); }); - expect(launchConfig).to.containSubset({ - program: "${workspaceFolder}/.build/debug/executable.exe", + + teardown(() => { + mockFS.restore(); }); - }); - test("does not modify program on macOS", async () => { - const configProvider = new LLDBDebugConfigurationProvider("darwin", swift6Toolchain); - const launchConfig = await configProvider.resolveDebugConfiguration(undefined, { - name: "Test Launch Config", - type: SWIFT_LAUNCH_CONFIG_TYPE, - request: "launch", - program: "${workspaceFolder}/.build/debug/executable", + test("returns a launch configuration that uses lldb-dap as the debug adapter", async () => { + const configProvider = new LLDBDebugConfigurationProvider( + "darwin", + instance(mockToolchain), + instance(mockOutputChannel) + ); + const launchConfig = await configProvider.resolveDebugConfiguration(undefined, { + name: "Test Launch Config", + type: SWIFT_LAUNCH_CONFIG_TYPE, + request: "launch", + program: "${workspaceFolder}/.build/debug/executable", + }); + expect(launchConfig).to.containSubset({ type: LaunchConfigType.LLDB_DAP }); }); - expect(launchConfig).to.containSubset({ - program: "${workspaceFolder}/.build/debug/executable", + + test("fails if the path to lldb-dap could not be found", async () => { + mockFS({}); // Reset mockFS so that no files exist + const configProvider = new LLDBDebugConfigurationProvider( + "darwin", + instance(mockToolchain), + instance(mockOutputChannel) + ); + await expect( + configProvider.resolveDebugConfiguration(undefined, { + name: "Test Launch Config", + type: SWIFT_LAUNCH_CONFIG_TYPE, + request: "launch", + program: "${workspaceFolder}/.build/debug/executable", + }) + ).to.eventually.be.undefined; + expect(mockWindow.showErrorMessage).to.have.been.calledOnce; }); - }); - test("does not modify program on Linux", async () => { - const configProvider = new LLDBDebugConfigurationProvider("linux", swift6Toolchain); - const launchConfig = await configProvider.resolveDebugConfiguration(undefined, { - name: "Test Launch Config", - type: SWIFT_LAUNCH_CONFIG_TYPE, - request: "launch", - program: "${workspaceFolder}/.build/debug/executable", + test("modifies program to add file extension on Windows", async () => { + const configProvider = new LLDBDebugConfigurationProvider( + "win32", + instance(mockToolchain), + instance(mockOutputChannel) + ); + const launchConfig = await configProvider.resolveDebugConfiguration(undefined, { + name: "Test Launch Config", + type: SWIFT_LAUNCH_CONFIG_TYPE, + request: "launch", + program: "${workspaceFolder}/.build/debug/executable", + }); + expect(launchConfig).to.containSubset({ + program: "${workspaceFolder}/.build/debug/executable.exe", + }); }); - expect(launchConfig).to.containSubset({ - program: "${workspaceFolder}/.build/debug/executable", + + test("does not modify program on Windows if file extension is already present", async () => { + const configProvider = new LLDBDebugConfigurationProvider( + "win32", + instance(mockToolchain), + instance(mockOutputChannel) + ); + const launchConfig = await configProvider.resolveDebugConfiguration(undefined, { + name: "Test Launch Config", + type: SWIFT_LAUNCH_CONFIG_TYPE, + request: "launch", + program: "${workspaceFolder}/.build/debug/executable.exe", + }); + expect(launchConfig).to.containSubset({ + program: "${workspaceFolder}/.build/debug/executable.exe", + }); }); - }); - test("should convert environment variables to string[] format when using lldb-dap", async () => { - mockDebugConfig.debugAdapter = "lldb-dap"; - const configProvider = new LLDBDebugConfigurationProvider("darwin", swift6Toolchain); - const launchConfig = await configProvider.resolveDebugConfiguration(undefined, { - name: "Test Launch Config", - type: SWIFT_LAUNCH_CONFIG_TYPE, - request: "launch", - program: "${workspaceFolder}/.build/debug/executable", - env: { - VAR1: "value1", - VAR2: "value2", - }, + test("does not modify program on macOS", async () => { + const configProvider = new LLDBDebugConfigurationProvider( + "darwin", + instance(mockToolchain), + instance(mockOutputChannel) + ); + const launchConfig = await configProvider.resolveDebugConfiguration(undefined, { + name: "Test Launch Config", + type: SWIFT_LAUNCH_CONFIG_TYPE, + request: "launch", + program: "${workspaceFolder}/.build/debug/executable", + }); + expect(launchConfig).to.containSubset({ + program: "${workspaceFolder}/.build/debug/executable", + }); }); - expect(launchConfig) - .to.have.property("env") - .that.deep.equals(["VAR1=value1", "VAR2=value2"]); - }); - test("should leave env undefined when environment variables are undefined and using lldb-dap", async () => { - mockDebugConfig.debugAdapter = "lldb-dap"; - const configProvider = new LLDBDebugConfigurationProvider("darwin", swift6Toolchain); - const launchConfig = await configProvider.resolveDebugConfiguration(undefined, { - name: "Test Launch Config", - type: SWIFT_LAUNCH_CONFIG_TYPE, - request: "launch", - program: "${workspaceFolder}/.build/debug/executable", + test("does not modify program on Linux", async () => { + const configProvider = new LLDBDebugConfigurationProvider( + "linux", + instance(mockToolchain), + instance(mockOutputChannel) + ); + const launchConfig = await configProvider.resolveDebugConfiguration(undefined, { + name: "Test Launch Config", + type: SWIFT_LAUNCH_CONFIG_TYPE, + request: "launch", + program: "${workspaceFolder}/.build/debug/executable", + }); + expect(launchConfig).to.containSubset({ + program: "${workspaceFolder}/.build/debug/executable", + }); }); - expect(launchConfig).to.not.have.property("env"); - }); - test("should convert empty environment variables when using lldb-dap", async () => { - mockDebugConfig.debugAdapter = "lldb-dap"; - const configProvider = new LLDBDebugConfigurationProvider("darwin", swift6Toolchain); - const launchConfig = await configProvider.resolveDebugConfiguration(undefined, { - type: SWIFT_LAUNCH_CONFIG_TYPE, - request: "launch", - name: "Test Launch", - env: {}, + test("should convert environment variables to string[] format when using lldb-dap", async () => { + const configProvider = new LLDBDebugConfigurationProvider( + "darwin", + instance(mockToolchain), + instance(mockOutputChannel) + ); + const launchConfig = await configProvider.resolveDebugConfiguration(undefined, { + name: "Test Launch Config", + type: SWIFT_LAUNCH_CONFIG_TYPE, + request: "launch", + program: "${workspaceFolder}/.build/debug/executable", + env: { + VAR1: "value1", + VAR2: "value2", + }, + }); + expect(launchConfig) + .to.have.property("env") + .that.deep.equals(["VAR1=value1", "VAR2=value2"]); }); - expect(launchConfig).to.have.property("env").that.deep.equals([]); - }); + test("should leave env undefined when environment variables are undefined and using lldb-dap", async () => { + const configProvider = new LLDBDebugConfigurationProvider( + "darwin", + instance(mockToolchain), + instance(mockOutputChannel) + ); + const launchConfig = await configProvider.resolveDebugConfiguration(undefined, { + name: "Test Launch Config", + type: SWIFT_LAUNCH_CONFIG_TYPE, + request: "launch", + program: "${workspaceFolder}/.build/debug/executable", + }); + expect(launchConfig).to.not.have.property("env"); + }); - test("should handle a large number of environment variables when using lldb-dap", async () => { - mockDebugConfig.debugAdapter = "lldb-dap"; - // Create 1000 environment variables - const env: { [key: string]: string } = {}; - for (let i = 0; i < 1000; i++) { - env[`VAR${i}`] = `value${i}`; - } - const configProvider = new LLDBDebugConfigurationProvider("darwin", swift6Toolchain); - const launchConfig = await configProvider.resolveDebugConfiguration(undefined, { - type: SWIFT_LAUNCH_CONFIG_TYPE, - request: "launch", - name: "Test Launch", - env, + test("should convert empty environment variables when using lldb-dap", async () => { + const configProvider = new LLDBDebugConfigurationProvider( + "darwin", + instance(mockToolchain), + instance(mockOutputChannel) + ); + const launchConfig = await configProvider.resolveDebugConfiguration(undefined, { + type: SWIFT_LAUNCH_CONFIG_TYPE, + request: "launch", + name: "Test Launch", + env: {}, + }); + + expect(launchConfig).to.have.property("env").that.deep.equals([]); }); - // Verify that all 1000 environment variables are properly converted - const expectedEnv = Array.from({ length: 1000 }, (_, i) => `VAR${i}=value${i}`); - expect(launchConfig).to.have.property("env").that.deep.equals(expectedEnv); + test("should handle a large number of environment variables when using lldb-dap", async () => { + // Create 1000 environment variables + const env: { [key: string]: string } = {}; + for (let i = 0; i < 1000; i++) { + env[`VAR${i}`] = `value${i}`; + } + const configProvider = new LLDBDebugConfigurationProvider( + "darwin", + instance(mockToolchain), + instance(mockOutputChannel) + ); + const launchConfig = await configProvider.resolveDebugConfiguration(undefined, { + type: SWIFT_LAUNCH_CONFIG_TYPE, + request: "launch", + name: "Test Launch", + env, + }); + + // Verify that all 1000 environment variables are properly converted + const expectedEnv = Array.from({ length: 1000 }, (_, i) => `VAR${i}=value${i}`); + expect(launchConfig).to.have.property("env").that.deep.equals(expectedEnv); + }); }); }); From ce0b454b66c96218fc86c8d2cf7a6a00413477e7 Mon Sep 17 00:00:00 2001 From: Paul LeMarquand Date: Wed, 12 Feb 2025 13:39:53 -0500 Subject: [PATCH 07/86] Fix extra parentheses in soundness.sh (#1383) The soundness script was failing to run with the error: `find: you have too many ')'` --- scripts/soundness.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/soundness.sh b/scripts/soundness.sh index 894b7e875..ab4f0a54f 100755 --- a/scripts/soundness.sh +++ b/scripts/soundness.sh @@ -100,7 +100,7 @@ EOF \( \! -path './assets/*' -a \ \( \! -path './coverage/*' -a \ \( "${matching_files[@]}" \) \ - \) \) \) \) \) \) \) \) + \) \) \) \) \) \) \) } | while read -r line; do if [[ "$(replace_acceptable_years < "$line" | head -n "$expected_lines" | shasum)" != "$expected_sha" ]]; then printf "\033[0;31mmissing headers in file '%s'!\033[0m\n" "$line" From c0eba38d740cca5c62d168f2458d9dcae4383bdd Mon Sep 17 00:00:00 2001 From: Matthew Bastien Date: Wed, 12 Feb 2025 13:53:12 -0500 Subject: [PATCH 08/86] add editor run actions for Swift executables (#1378) --- package.json | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 95e4bb879..3adadae24 100644 --- a/package.json +++ b/package.json @@ -148,13 +148,15 @@ }, { "command": "swift.run", - "title": "Run Build", - "category": "Swift" + "title": "Run Swift executable", + "category": "Swift", + "icon": "$(play)" }, { "command": "swift.debug", - "title": "Debug Build", - "category": "Swift" + "title": "Debug Swift executable", + "category": "Swift", + "icon": "$(debug)" }, { "command": "swift.resetPackage", @@ -898,6 +900,18 @@ "group": "navigation" } ], + "editor/title/run": [ + { + "command": "swift.run", + "group": "navigation@0", + "when": "resourceLangId == swift && swift.currentTargetType == 'executable'" + }, + { + "command": "swift.debug", + "group": "navigation@0", + "when": "resourceLangId == swift && swift.currentTargetType == 'executable'" + } + ], "swift.editor": [ { "command": "swift.run", From 39fb6862a90b39154d9afdd9d30474454b3c5a62 Mon Sep 17 00:00:00 2001 From: Paul LeMarquand Date: Wed, 12 Feb 2025 15:16:43 -0500 Subject: [PATCH 09/86] Don't append .exe to binaries ending in .xctest (#1377) * Don't append .exe to binaries ending in .xctest Certain non standard workflows (CMake) could produce binaries that end in .xctest. Only append .exe if it doesn't already have a valid extension. Also declare `testType` in the debug configuration schema allowing users to declare what type of test framework they're using in the launch config if they're using a non standard flow. Issue: #1364 --- package.json | 4 ++++ src/debugger/debugAdapterFactory.ts | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 3adadae24..e61380dca 100644 --- a/package.json +++ b/package.json @@ -1276,6 +1276,10 @@ "description": "Don't retrieve STDIN, STDOUT and STDERR as the program is running.", "default": false }, + "testType": { + "type": "string", + "description": "If the program is a test, set this to the type of test (`XCTest` or `swift-testing`). This is typically set automatically and is only required when your launch program uses a non standard filename." + }, "shellExpandArguments": { "type": "boolean", "description": "Expand program arguments as a shell would without actually launching the program in a shell.", diff --git a/src/debugger/debugAdapterFactory.ts b/src/debugger/debugAdapterFactory.ts index 125c4e7f6..f150e884d 100644 --- a/src/debugger/debugAdapterFactory.ts +++ b/src/debugger/debugAdapterFactory.ts @@ -125,7 +125,8 @@ export class LLDBDebugConfigurationProvider implements vscode.DebugConfiguration if ( this.platform === "win32" && launchConfig.testType === undefined && - path.extname(launchConfig.program) !== ".exe" + path.extname(launchConfig.program) !== ".exe" && + path.extname(launchConfig.program) !== ".xctest" ) { launchConfig.program += ".exe"; } From bd64d772859b8650f24abbd6e685504dee1bd7e1 Mon Sep 17 00:00:00 2001 From: Paul LeMarquand Date: Thu, 13 Feb 2025 13:53:31 -0500 Subject: [PATCH 10/86] Debug failing CodeLLDB swift-testing test (#1387) Started failing with the merge of #1381 --- .../testexplorer/TestExplorerIntegration.test.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/integration-tests/testexplorer/TestExplorerIntegration.test.ts b/test/integration-tests/testexplorer/TestExplorerIntegration.test.ts index 374044db0..55eb84dc2 100644 --- a/test/integration-tests/testexplorer/TestExplorerIntegration.test.ts +++ b/test/integration-tests/testexplorer/TestExplorerIntegration.test.ts @@ -147,7 +147,10 @@ suite("Test Explorer Suite", function () { const lldbPath = process.env["CI"] === "1" - ? { "lldb.library": await getLLDBDebugAdapterPath() } + ? { + "lldb.library": await getLLDBDebugAdapterPath(), + "lldb.launch.expressions": "native", + } : {}; resetSettings = await updateSettings({ From 0296cbe56404c0efe2ee16d2a01a230ebd79f86e Mon Sep 17 00:00:00 2001 From: Paul LeMarquand Date: Thu, 13 Feb 2025 15:06:49 -0500 Subject: [PATCH 11/86] Fix flakey XCTest parallel test failure reporting (#1343) The `ParallelXCTestRunStateProxy` in `XCTestOutputParser.ts` wraps the test run state and only allows issues to be recorded using the terminal output of the test run. The remaining test run state is filled in with the xml xUnit output. This is because SwiftPM only dumps test output to the terminal if there is a failure. Using these two sources we can (almost) fully reconstruct a regular test run. When parsing XCTest output from the terminal failure messages might be broken up over multiple lines. In order to capture all these lines and group them in to a single failure message even if the message is broken up across terminal buffer chunks we maintain an `excess` variable on the TestRunState, which we check at the beginning of chunk parsing to know if we need to continue an error message. However, the `ParallelXCTestRunStateProxy` that wraps the test run state was not forwarding along the setting of `excess`. Instead `excess` was set on the proxy, which is recreated for every chunk of output parsed. This manifested as XCTests _sometimes_ not correctly reporting their error message when run in the Parallel test profile, instead reporting the message "Failed". Issue: #1334 --- .../TestParsers/XCTestOutputParser.ts | 39 ++++++++++++++++++- src/TestExplorer/TestXUnitParser.ts | 16 ++++---- .../TestExplorerIntegration.test.ts | 16 ++++---- .../testexplorer/utilities.ts | 9 ++++- 4 files changed, 62 insertions(+), 18 deletions(-) diff --git a/src/TestExplorer/TestParsers/XCTestOutputParser.ts b/src/TestExplorer/TestParsers/XCTestOutputParser.ts index e373e8517..4089e792f 100644 --- a/src/TestExplorer/TestParsers/XCTestOutputParser.ts +++ b/src/TestExplorer/TestParsers/XCTestOutputParser.ts @@ -92,8 +92,9 @@ export class ParallelXCTestOutputParser implements IXCTestOutputParser { public parseResult(output: string, runState: ITestRunState) { // From 5.7 to 5.10 running with the --parallel option dumps the test results out // to the console with no newlines, so it isn't possible to distinguish where errors - // begin and end. Consequently we can't record them, and so we manually mark them - // as passed or failed here with a manufactured issue. + // begin and end. Consequently we can't record them. For these versions we rely on the + // generated xunit XML, which we can parse and mark tests as passed or failed here with + // manufactured issues. // Don't attempt to parse the console output of parallel tests between 5.7 and 5.10 // as it doesn't have newlines. You might get lucky and find the output is split // in the right spot, but more often than not we wont be able to parse it. @@ -112,8 +113,42 @@ export class ParallelXCTestOutputParser implements IXCTestOutputParser { /* eslint-disable @typescript-eslint/no-unused-vars */ class ParallelXCTestRunStateProxy implements ITestRunState { + // Note this must remain stateless as its recreated on + // every `parseResult` call in `ParallelXCTestOutputParser` constructor(private runState: ITestRunState) {} + get excess(): typeof this.runState.excess { + return this.runState.excess; + } + + set excess(value: typeof this.runState.excess) { + this.runState.excess = value; + } + + get activeSuite(): typeof this.runState.activeSuite { + return this.runState.activeSuite; + } + + set activeSuite(value: typeof this.runState.activeSuite) { + this.runState.activeSuite = value; + } + + get pendingSuiteOutput(): typeof this.runState.pendingSuiteOutput { + return this.runState.pendingSuiteOutput; + } + + set pendingSuiteOutput(value: typeof this.runState.pendingSuiteOutput) { + this.runState.pendingSuiteOutput = value; + } + + get failedTest(): typeof this.runState.failedTest { + return this.runState.failedTest; + } + + set failedTest(value: typeof this.runState.failedTest) { + this.runState.failedTest = value; + } + getTestItemIndex(id: string, filename: string | undefined): number { return this.runState.getTestItemIndex(id, filename); } diff --git a/src/TestExplorer/TestXUnitParser.ts b/src/TestExplorer/TestXUnitParser.ts index 8ef064d92..8a4e19062 100644 --- a/src/TestExplorer/TestXUnitParser.ts +++ b/src/TestExplorer/TestXUnitParser.ts @@ -13,8 +13,8 @@ //===----------------------------------------------------------------------===// import * as xml2js from "xml2js"; -import { TestRunnerTestRunState } from "./TestRunner"; -import { OutputChannel } from "vscode"; +import { ITestRunState } from "./TestParsers/TestRunState"; +import { SwiftOutputChannel } from "../ui/SwiftOutputChannel"; export interface TestResults { tests: number; @@ -49,8 +49,8 @@ export class TestXUnitParser { async parse( buffer: string, - runState: TestRunnerTestRunState, - outputChannel: OutputChannel + runState: ITestRunState, + outputChannel: SwiftOutputChannel ): Promise { const xml = await xml2js.parseStringPromise(buffer); try { @@ -63,7 +63,8 @@ export class TestXUnitParser { } // eslint-disable-next-line @typescript-eslint/no-unused-vars - async parseXUnit(xUnit: XUnit, runState: TestRunnerTestRunState): Promise { + async parseXUnit(xUnit: XUnit, runState: ITestRunState): Promise { + // eslint-disable-next-line no-console let tests = 0; let failures = 0; let errors = 0; @@ -77,7 +78,7 @@ export class TestXUnitParser { testsuite.testcase.forEach(testcase => { className = testcase.$.classname; const id = `${className}/${testcase.$.name}`; - const index = runState.getTestItemIndex(id); + const index = runState.getTestItemIndex(id, undefined); // From 5.7 to 5.10 running with the --parallel option dumps the test results out // to the console with no newlines, so it isn't possible to distinguish where errors @@ -86,7 +87,8 @@ export class TestXUnitParser { if (!!testcase.failure && !this.hasMultiLineParallelTestOutput) { runState.recordIssue( index, - testcase.failure.shift()?.$.message ?? "Test Failed" + testcase.failure.shift()?.$.message ?? "Test Failed", + false ); } runState.completed(index, { duration: testcase.$.time }); diff --git a/test/integration-tests/testexplorer/TestExplorerIntegration.test.ts b/test/integration-tests/testexplorer/TestExplorerIntegration.test.ts index 55eb84dc2..c48d1e847 100644 --- a/test/integration-tests/testexplorer/TestExplorerIntegration.test.ts +++ b/test/integration-tests/testexplorer/TestExplorerIntegration.test.ts @@ -563,7 +563,7 @@ suite("Test Explorer Suite", function () { }); }); - test(`Runs failing test (${runProfile})`, async function () { + test(`swift-testing Runs failing test (${runProfile})`, async function () { const testRun = await runTest( testExplorer, runProfile, @@ -585,7 +585,7 @@ suite("Test Explorer Suite", function () { }); }); - test(`Runs Suite (${runProfile})`, async function () { + test(`swift-testing Runs Suite (${runProfile})`, async function () { const testRun = await runTest( testExplorer, runProfile, @@ -610,7 +610,7 @@ suite("Test Explorer Suite", function () { }); }); - test(`Runs parameterized test (${runProfile})`, async function () { + test(`swift-testing Runs parameterized test (${runProfile})`, async function () { const testId = "PackageTests.parameterizedTest(_:)"; const testRun = await runTest(testExplorer, runProfile, testId); @@ -660,7 +660,7 @@ suite("Test Explorer Suite", function () { assert.deepEqual(unrunnableChildren, [true, true, true]); }); - test(`Runs Suite (${runProfile})`, async function () { + test(`swift-testing Runs Suite (${runProfile})`, async function () { const testRun = await runTest( testExplorer, runProfile, @@ -685,7 +685,7 @@ suite("Test Explorer Suite", function () { }); }); - test(`Runs All (${runProfile})`, async function () { + test(`swift-testing Runs All (${runProfile})`, async function () { const testRun = await runTest( testExplorer, runProfile, @@ -724,7 +724,7 @@ suite("Test Explorer Suite", function () { }); suite(`XCTests (${runProfile})`, () => { - test("Runs passing test", async function () { + test(`XCTest Runs passing test (${runProfile})`, async function () { const testRun = await runTest( testExplorer, runProfile, @@ -739,7 +739,7 @@ suite("Test Explorer Suite", function () { }); }); - test("Runs failing test", async function () { + test(`XCTest Runs failing test (${runProfile})`, async function () { const testRun = await runTest( testExplorer, runProfile, @@ -760,7 +760,7 @@ suite("Test Explorer Suite", function () { }); }); - test("Runs Suite", async function () { + test(`XCTest Runs Suite (${runProfile})`, async function () { const testRun = await runTest( testExplorer, runProfile, diff --git a/test/integration-tests/testexplorer/utilities.ts b/test/integration-tests/testexplorer/utilities.ts index 20662258c..00460a88e 100644 --- a/test/integration-tests/testexplorer/utilities.ts +++ b/test/integration-tests/testexplorer/utilities.ts @@ -160,7 +160,11 @@ export function assertTestResults( skipped: (state.skipped ?? []).sort(), errored: (state.errored ?? []).sort(), unknown: 0, - } + }, + ` + Build Output: + ${testRun.runState.output.join("\n")} + ` ); } @@ -280,6 +284,9 @@ export async function runTest( const testItems = await gatherTests(testExplorer.controller, ...tests); const request = new vscode.TestRunRequest(testItems); + // The first promise is the return value, the second promise builds and runs + // the tests, populating the TestRunProxy with results and blocking the return + // of that TestRunProxy until the test run is complete. return ( await Promise.all([ eventPromise(testExplorer.onCreateTestRun), From 2c43d6fa8c12e1bd6bd6f2ebd140e1501e0a2e1d Mon Sep 17 00:00:00 2001 From: Paul LeMarquand Date: Fri, 14 Feb 2025 14:49:32 -0500 Subject: [PATCH 12/86] Terminate process under test on test run cancellation (#1391) During a standard test run (not using the debugger) a cancellation request from VS Code did not terminate the process under test, which lead to it being orphaned. Issue: #1389 --- src/TestExplorer/TestRunner.ts | 9 ++++++++- src/tasks/SwiftExecution.ts | 9 ++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/TestExplorer/TestRunner.ts b/src/TestExplorer/TestRunner.ts index 1e13e3486..d9fae8a14 100644 --- a/src/TestExplorer/TestRunner.ts +++ b/src/TestExplorer/TestRunner.ts @@ -729,7 +729,14 @@ export class TestRunner { outputStream.write(replaced); }); + // If the test run is iterrupted by a cancellation request from VS Code, ensure the task is terminated. + const cancellationDisposable = this.testRun.token.onCancellationRequested(() => { + task.execution.terminate("SIGINT"); + }); + task.execution.onDidClose(code => { + cancellationDisposable.dispose(); + // undefined or 0 are viewed as success if (!code) { resolve(); @@ -925,7 +932,6 @@ export class TestRunner { return; } - // add cancelation const startSession = vscode.debug.onDidStartDebugSession(session => { if (config.testType === TestLibrary.xctest) { this.testRun.testRunStarted(); @@ -945,6 +951,7 @@ export class TestRunner { } ); + // add cancellation const cancellation = this.testRun.token.onCancellationRequested(() => { this.workspaceContext.outputChannel.logDiagnostic( "Test Debugging Cancelled", diff --git a/src/tasks/SwiftExecution.ts b/src/tasks/SwiftExecution.ts index d7b23e515..f9c6c5502 100644 --- a/src/tasks/SwiftExecution.ts +++ b/src/tasks/SwiftExecution.ts @@ -30,7 +30,7 @@ export class SwiftExecution extends vscode.CustomExecution { public readonly command: string, public readonly args: string[], public readonly options: SwiftExecutionOptions, - swiftProcess: SwiftProcess = new SwiftPtyProcess(command, args, options) + private readonly swiftProcess: SwiftProcess = new SwiftPtyProcess(command, args, options) ) { super(async () => { return new SwiftPseudoterminal(swiftProcess, options.presentation || {}); @@ -54,4 +54,11 @@ export class SwiftExecution extends vscode.CustomExecution { * @see {@link SwiftProcess.onDidClose} */ onDidClose: vscode.Event; + + /** + * Terminate the underlying executable. + */ + terminate(signal?: NodeJS.Signals) { + this.swiftProcess.terminate(signal); + } } From 2c1233780e66aceb1d291e115e2b8a13ce653f1f Mon Sep 17 00:00:00 2001 From: award999 Date: Wed, 19 Feb 2025 11:04:22 -0500 Subject: [PATCH 13/86] Allow disabling SwiftPM sandboxing (#1386) * Allow disabling SwiftPM sandboxing To get our tests to run in a sandbox, we need to disable sandboxing because you cannot create a new sandbox when you're already running under a sandbox - Add new `swift.disableSandbox` setting - Disable sandboxing for tasks and commands run by the extension - Disable hardware acceleration since ci.swift.org will run on x64 - Fix failing xcode watcher unit test - Increase some timeouts as build times seem slower on these nodes - Skip any LSP dependent tests for 6.0 or earlier. The LSP will only allow disabling sandboxing in 6.1+ - Disable debugging tests since need shareport permission * Fix review comment * Disable test for Windows --- .gitignore | 1 + .prettierignore | 3 + .vscode-test.js | 31 +++--- assets/test/.vscode/tasks.json | 1 + package.json | 7 +- src/commands/dependencies/unedit.ts | 7 +- src/commands/dependencies/useLocal.ts | 8 +- src/commands/resetPackage.ts | 5 +- src/configuration.ts | 11 ++ src/debugger/buildConfig.ts | 2 +- src/extension.ts | 4 +- src/tasks/SwiftPluginTaskProvider.ts | 4 +- src/tasks/SwiftTaskProvider.ts | 5 +- src/toolchain/BuildFlags.ts | 34 +++++- src/utilities/utilities.ts | 8 +- .../BackgroundCompilation.test.ts | 2 +- .../DiagnosticsManager.test.ts | 6 +- test/integration-tests/SwiftPackage.test.ts | 11 +- test/integration-tests/SwiftSnippet.test.ts | 3 +- test/integration-tests/commands/build.test.ts | 3 +- test/integration-tests/debugger/lldb.test.ts | 1 + test/integration-tests/extension.test.ts | 15 +-- .../LanguageClientIntegration.test.ts | 2 +- .../tasks/SwiftPluginTaskProvider.test.ts | 51 +++++---- .../TestExplorerIntegration.test.ts | 3 + .../ui/PackageDependencyProvider.test.ts | 17 ++- .../utilities/testutilities.ts | 30 +++++- .../LanguageClientManager.test.ts | 2 +- .../tasks/SwiftPluginTaskProvider.test.ts | 4 +- .../tasks/SwiftTaskProvider.test.ts | 8 +- test/unit-tests/toolchain/BuildFlags.test.ts | 100 +++++++++++++++--- .../toolchain/SelectedXcodeWatcher.test.ts | 21 +++- 32 files changed, 312 insertions(+), 98 deletions(-) diff --git a/.gitignore b/.gitignore index a09ca1cb4..da0219e89 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ default.profraw assets/documentation-webview assets/test/**/Package.resolved assets/swift-docc-render +ud diff --git a/.prettierignore b/.prettierignore index 42ce3550c..a7aad951d 100644 --- a/.prettierignore +++ b/.prettierignore @@ -18,3 +18,6 @@ node_modules/ /coverage/ /dist/ /snippets/ + +# macOS CI +/ud/ \ No newline at end of file diff --git a/.vscode-test.js b/.vscode-test.js index 5fa7ce6ba..e0302dd36 100644 --- a/.vscode-test.js +++ b/.vscode-test.js @@ -18,12 +18,28 @@ const path = require("path"); const isCIBuild = process.env["CI"] === "1"; const isFastTestRun = process.env["FAST_TEST_RUN"] === "1"; +const dataDir = process.env["VSCODE_DATA_DIR"]; + // "env" in launch.json doesn't seem to work with vscode-test const isDebugRun = !(process.env["_"] ?? "").endsWith("node_modules/.bin/vscode-test"); // so tests don't timeout when a breakpoint is hit const timeout = isDebugRun ? Number.MAX_SAFE_INTEGER : 3000; +const launchArgs = [ + "--disable-updates", + "--disable-crash-reporter", + "--disable-workspace-trust", + "--disable-telemetry", +]; +if (dataDir) { + launchArgs.push("--user-data-dir", dataDir); +} +// GPU hardware acceleration not working on Darwin for intel +if (process.platform === "darwin" && process.arch === "x64") { + launchArgs.push("--disable-gpu"); +} + module.exports = defineConfig({ tests: [ { @@ -31,12 +47,7 @@ module.exports = defineConfig({ files: ["dist/test/common.js", "dist/test/integration-tests/**/*.test.js"], version: process.env["VSCODE_VERSION"] ?? "stable", workspaceFolder: "./assets/test", - launchArgs: [ - "--disable-updates", - "--disable-crash-reporter", - "--disable-workspace-trust", - "--disable-telemetry", - ], + launchArgs, mocha: { ui: "tdd", color: true, @@ -59,13 +70,7 @@ module.exports = defineConfig({ label: "unitTests", files: ["dist/test/common.js", "dist/test/unit-tests/**/*.test.js"], version: process.env["VSCODE_VERSION"] ?? "stable", - launchArgs: [ - "--disable-extensions", - "--disable-updates", - "--disable-crash-reporter", - "--disable-workspace-trust", - "--disable-telemetry", - ], + launchArgs: launchArgs.concat("--disable-extensions"), mocha: { ui: "tdd", color: true, diff --git a/assets/test/.vscode/tasks.json b/assets/test/.vscode/tasks.json index d91e6ff96..406dec54f 100644 --- a/assets/test/.vscode/tasks.json +++ b/assets/test/.vscode/tasks.json @@ -35,6 +35,7 @@ "command": "command_plugin", "args": ["--foo"], "cwd": "command-plugin", + "disableSandbox": true, "problemMatcher": [ "$swiftc" ], diff --git a/package.json b/package.json index e61380dca..91477c8e5 100644 --- a/package.json +++ b/package.json @@ -719,7 +719,12 @@ "swift.swiftSDK": { "type": "string", "default": "", - "markdownDescription": "The [Swift SDK](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0387-cross-compilation-destinations.md) to compile against (`--swift-sdk` parameter).", + "markdownDescription": "The [Swift SDK](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0387-cross-compilation-destinations.md) to compile against (`--swift-sdk` parameter)." + }, + "swift.disableSandox": { + "type": "boolean", + "default": false, + "markdownDescription": "Disable sandboxing when running SwiftPM commands. In most cases you should keep the sandbox enabled and leave this setting set to `false`", "order": 4 }, "swift.diagnostics": { diff --git a/src/commands/dependencies/unedit.ts b/src/commands/dependencies/unedit.ts index 521d71afd..720709edd 100644 --- a/src/commands/dependencies/unedit.ts +++ b/src/commands/dependencies/unedit.ts @@ -44,7 +44,12 @@ async function uneditFolderDependency( ) { try { const uneditOperation = new SwiftExecOperation( - ["package", "unedit", ...args, identifier], + ctx.toolchain.buildFlags.withAdditionalFlags([ + "package", + "unedit", + ...args, + identifier, + ]), folder, `Finish editing ${identifier}`, { showStatusItem: true, checkAlreadyRunning: false, log: "Unedit" }, diff --git a/src/commands/dependencies/useLocal.ts b/src/commands/dependencies/useLocal.ts index e108cee71..c75a3c6c2 100644 --- a/src/commands/dependencies/useLocal.ts +++ b/src/commands/dependencies/useLocal.ts @@ -50,7 +50,13 @@ export async function useLocalDependency( folder = folders[0]; } const task = createSwiftTask( - ["package", "edit", "--path", folder.fsPath, identifier], + ctx.toolchain.buildFlags.withAdditionalFlags([ + "package", + "edit", + "--path", + folder.fsPath, + identifier, + ]), "Edit Package Dependency", { scope: currentFolder.workspaceFolder, diff --git a/src/commands/resetPackage.ts b/src/commands/resetPackage.ts index 6d621dee2..84c0c9141 100644 --- a/src/commands/resetPackage.ts +++ b/src/commands/resetPackage.ts @@ -35,7 +35,10 @@ export async function resetPackage(ctx: WorkspaceContext) { */ export async function folderResetPackage(folderContext: FolderContext) { const task = createSwiftTask( - ["package", "reset"], + folderContext.workspaceContext.toolchain.buildFlags.withAdditionalFlags([ + "package", + "reset", + ]), "Reset Package Dependencies", { cwd: folderContext.folder, diff --git a/src/configuration.ts b/src/configuration.ts index 45bbba2f9..5f7abdf29 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -52,6 +52,8 @@ export interface DebuggerConfiguration { readonly debugAdapter: DebugAdapters; /** Return path to debug adapter */ readonly customDebugAdapterPath: string; + /** Whether or not to disable setting up the debugger */ + readonly disable: boolean; } /** workspace folder configuration */ @@ -212,6 +214,11 @@ const configuration = { get customDebugAdapterPath(): string { return vscode.workspace.getConfiguration("swift.debugger").get("path", ""); }, + get disable(): boolean { + return vscode.workspace + .getConfiguration("swift.debugger") + .get("disable", false); + }, }; }, /** Files and directories to exclude from the code coverage. */ @@ -375,6 +382,10 @@ const configuration = { .getConfiguration("swift") .get("enableTerminalEnvironment", true); }, + /** Whether or not to disable SwiftPM sandboxing */ + get disableSandbox(): boolean { + return vscode.workspace.getConfiguration("swift").get("disableSandbox", false); + }, }; export default configuration; diff --git a/src/debugger/buildConfig.ts b/src/debugger/buildConfig.ts index 956d2a356..8722336e0 100644 --- a/src/debugger/buildConfig.ts +++ b/src/debugger/buildConfig.ts @@ -535,7 +535,7 @@ export class TestingConfigurationFactory { } const swiftTestingArgs = [ - ...args, + ...this.ctx.workspaceContext.toolchain.buildFlags.withAdditionalFlags(args), "--enable-swift-testing", "--event-stream-version", "0", diff --git a/src/extension.ts b/src/extension.ts index 612ea9e3b..b87f950fc 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -128,7 +128,9 @@ export async function activate(context: vscode.ExtensionContext): Promise { const workspaceContext = await WorkspaceContext.create(context, outputChannel, toolchain); context.subscriptions.push(...commands.register(workspaceContext)); context.subscriptions.push(workspaceContext); - context.subscriptions.push(registerDebugger(workspaceContext)); + if (!configuration.debugger.disable) { + context.subscriptions.push(registerDebugger(workspaceContext)); + } context.subscriptions.push(new SelectedXcodeWatcher(outputChannel)); // listen for workspace folder changes and active text editor changes diff --git a/src/tasks/SwiftPluginTaskProvider.ts b/src/tasks/SwiftPluginTaskProvider.ts index f0339b2de..1de21d238 100644 --- a/src/tasks/SwiftPluginTaskProvider.ts +++ b/src/tasks/SwiftPluginTaskProvider.ts @@ -82,7 +82,7 @@ export class SwiftPluginTaskProvider implements vscode.TaskProvider { task.definition.command, ...task.definition.args, ]; - swiftArgs = this.workspaceContext.toolchain.buildFlags.withSwiftSDKFlags(swiftArgs); + swiftArgs = this.workspaceContext.toolchain.buildFlags.withAdditionalFlags(swiftArgs); const cwd = resolveTaskCwd(task, task.definition.cwd); const newTask = new vscode.Task( @@ -122,7 +122,7 @@ export class SwiftPluginTaskProvider implements vscode.TaskProvider { plugin.command, ...definition.args, ]; - swiftArgs = this.workspaceContext.toolchain.buildFlags.withSwiftSDKFlags(swiftArgs); + swiftArgs = this.workspaceContext.toolchain.buildFlags.withAdditionalFlags(swiftArgs); const presentation = config?.presentationOptions ?? {}; const task = new vscode.Task( diff --git a/src/tasks/SwiftTaskProvider.ts b/src/tasks/SwiftTaskProvider.ts index fbf5ba946..77bd93689 100644 --- a/src/tasks/SwiftTaskProvider.ts +++ b/src/tasks/SwiftTaskProvider.ts @@ -271,7 +271,7 @@ export function createSwiftTask( cmdEnv: { [key: string]: string } = {} ): SwiftTask { const swift = toolchain.getToolchainExecutable("swift"); - args = toolchain.buildFlags.withSwiftPackageFlags(toolchain.buildFlags.withSwiftSDKFlags(args)); + args = toolchain.buildFlags.withAdditionalFlags(args); // Add relative path current working directory const cwd = config.cwd.fsPath; @@ -422,7 +422,8 @@ export class SwiftTaskProvider implements vscode.TaskProvider { resolveTask(task: vscode.Task, token: vscode.CancellationToken): vscode.Task { // We need to create a new Task object here. // Reusing the task parameter doesn't seem to work. - const swift = this.workspaceContext.toolchain.getToolchainExecutable("swift"); + const toolchain = this.workspaceContext.toolchain; + const swift = toolchain.getToolchainExecutable("swift"); // platform specific let platform: TaskPlatformSpecificConfig | undefined; if (process.platform === "win32") { diff --git a/src/toolchain/BuildFlags.ts b/src/toolchain/BuildFlags.ts index 95f376dab..01d1164e9 100644 --- a/src/toolchain/BuildFlags.ts +++ b/src/toolchain/BuildFlags.ts @@ -37,7 +37,7 @@ export class BuildFlags { * * @param args original commandline arguments */ - withSwiftSDKFlags(args: string[]): string[] { + private withSwiftSDKFlags(args: string[]): string[] { switch (args[0]) { case "package": { const subcommand = args.splice(0, 2).concat(this.buildPathFlags()); @@ -192,6 +192,38 @@ export class BuildFlags { return indirect ? args.flatMap(arg => ["-Xswiftc", arg]) : args; } + /** + * Get modified swift arguments with new arguments for disabling + * sandboxing if the `swift.disableSandbox` setting is enabled. + * + * @param args original commandline arguments + */ + private withDisableSandboxFlags(args: string[]): string[] { + if (!configuration.disableSandbox) { + return args; + } + const disableSandboxFlags = ["--disable-sandbox", "-Xswiftc", "-disable-sandbox"]; + switch (args[0]) { + case "package": { + return [args[0], ...disableSandboxFlags, ...args.slice(1)]; + } + case "build": + case "run": + case "test": { + return [...args, ...disableSandboxFlags]; + } + default: + // Do nothing for other commands + return args; + } + } + + withAdditionalFlags(args: string[]): string[] { + return this.withSwiftPackageFlags( + this.withDisableSandboxFlags(this.withSwiftSDKFlags(args)) + ); + } + /** * Filter argument list * @param args argument list diff --git a/src/utilities/utilities.ts b/src/utilities/utilities.ts index 31122bea1..030c91fa3 100644 --- a/src/utilities/utilities.ts +++ b/src/utilities/utilities.ts @@ -109,14 +109,14 @@ export async function execFile( options.env = { ...(options.env ?? process.env), ...runtimeEnv }; } } - return new Promise<{ stdout: string; stderr: string }>((resolve, reject) => + return new Promise<{ stdout: string; stderr: string }>((resolve, reject) => { cp.execFile(executable, args, options, (error, stdout, stderr) => { if (error) { reject(new ExecFileError(error, stdout, stderr)); } resolve({ stdout, stderr }); - }) - ); + }); + }); } export async function execFileStreamOutput( @@ -187,7 +187,7 @@ export async function execSwift( swift = toolchain.getToolchainExecutable("swift"); } if (toolchain !== "default") { - args = toolchain.buildFlags.withSwiftSDKFlags(args); + args = toolchain.buildFlags.withAdditionalFlags(args); } if (Object.keys(configuration.swiftEnvironmentVariables).length > 0) { // when adding environment vars we either combine with vars passed diff --git a/test/integration-tests/BackgroundCompilation.test.ts b/test/integration-tests/BackgroundCompilation.test.ts index 90c8660a4..13df067aa 100644 --- a/test/integration-tests/BackgroundCompilation.test.ts +++ b/test/integration-tests/BackgroundCompilation.test.ts @@ -58,5 +58,5 @@ suite("BackgroundCompilation Test Suite", () => { await vscode.workspace.save(uri); await taskPromise; - }).timeout(120000); + }).timeout(180000); }); diff --git a/test/integration-tests/DiagnosticsManager.test.ts b/test/integration-tests/DiagnosticsManager.test.ts index 5855a5d99..b0a783090 100644 --- a/test/integration-tests/DiagnosticsManager.test.ts +++ b/test/integration-tests/DiagnosticsManager.test.ts @@ -216,7 +216,7 @@ suite("DiagnosticsManager Test Suite", async function () { // after first build and can cause intermittent // failure if `swiftc` diagnostic is fixed suiteSetup(async function () { - this.timeout(2 * 60 * 1000); // Allow 2 minutes to build + this.timeout(3 * 60 * 1000); // Allow 3 minutes to build const task = createBuildAllTask(folderContext); // This return exit code and output for the task but we will omit it here // because the failures are expected and we just want the task to build @@ -1062,7 +1062,7 @@ suite("DiagnosticsManager Test Suite", async function () { assertHasDiagnostic(mainUri, expectedDiagnostic1); assertHasDiagnostic(mainUri, expectedDiagnostic2); - }).timeout(2 * 60 * 1000); // Allow 2 minutes to build + }).timeout(3 * 60 * 1000); // Allow 3 minutes to build test("Provides clang diagnostics", async () => { // Build for indexing @@ -1099,6 +1099,6 @@ suite("DiagnosticsManager Test Suite", async function () { assertHasDiagnostic(cUri, expectedDiagnostic1); assertHasDiagnostic(cUri, expectedDiagnostic2); - }).timeout(2 * 60 * 1000); // Allow 2 minutes to build + }).timeout(3 * 60 * 1000); // Allow 3 minutes to build }); }); diff --git a/test/integration-tests/SwiftPackage.test.ts b/test/integration-tests/SwiftPackage.test.ts index 421f1adb3..5c796db90 100644 --- a/test/integration-tests/SwiftPackage.test.ts +++ b/test/integration-tests/SwiftPackage.test.ts @@ -45,10 +45,17 @@ suite("SwiftPackage Test Suite", () => { assert.strictEqual(spmPackage.targets.length, 2); }).timeout(10000); - test("Package resolve v2", async () => { - if (toolchain && toolchain.swiftVersion.isLessThan(new Version(5, 6, 0))) { + test("Package resolve v2", async function () { + if (!toolchain) { return; } + if ( + (process.platform === "win32" && + toolchain.swiftVersion.isLessThan(new Version(6, 0, 0))) || + toolchain.swiftVersion.isLessThan(new Version(5, 6, 0)) + ) { + this.skip(); + } const spmPackage = await SwiftPackage.create(testAssetUri("package5.6"), toolchain); assert.strictEqual(spmPackage.isValid, true); assert(spmPackage.resolved !== undefined); diff --git a/test/integration-tests/SwiftSnippet.test.ts b/test/integration-tests/SwiftSnippet.test.ts index 72c594b7d..82b0342f4 100644 --- a/test/integration-tests/SwiftSnippet.test.ts +++ b/test/integration-tests/SwiftSnippet.test.ts @@ -37,7 +37,7 @@ function normalizePath(...segments: string[]): string { } suite("SwiftSnippet Test Suite @slow", function () { - this.timeout(120000); + this.timeout(180000); const uri = testAssetUri("defaultPackage/Snippets/hello.swift"); const breakpoints = [ @@ -62,6 +62,7 @@ suite("SwiftSnippet Test Suite @slow", function () { // Set a breakpoint vscode.debug.addBreakpoints(breakpoints); }, + requiresDebugger: true, }); suiteTeardown(async () => { diff --git a/test/integration-tests/commands/build.test.ts b/test/integration-tests/commands/build.test.ts index 59d6b1201..e3109e0d3 100644 --- a/test/integration-tests/commands/build.test.ts +++ b/test/integration-tests/commands/build.test.ts @@ -28,7 +28,7 @@ import { Version } from "../../../src/utilities/version"; suite("Build Commands @slow", function () { // Default timeout is a bit too short, give it a little bit more time - this.timeout(2 * 60 * 1000); + this.timeout(3 * 60 * 1000); let folderContext: FolderContext; let workspaceContext: WorkspaceContext; @@ -56,6 +56,7 @@ suite("Build Commands @slow", function () { async teardown() { await vscode.commands.executeCommand(Workbench.ACTION_CLOSEALLEDITORS); }, + requiresDebugger: true, }); test("Swift: Run Build", async () => { diff --git a/test/integration-tests/debugger/lldb.test.ts b/test/integration-tests/debugger/lldb.test.ts index aa070721c..2a66f8b2e 100644 --- a/test/integration-tests/debugger/lldb.test.ts +++ b/test/integration-tests/debugger/lldb.test.ts @@ -34,6 +34,7 @@ suite("lldb contract test suite", () => { } workspaceContext = ctx; }, + requiresDebugger: true, }); test("getLldbProcess Contract Test, make sure the command returns", async () => { diff --git a/test/integration-tests/extension.test.ts b/test/integration-tests/extension.test.ts index b9dad4080..346b34510 100644 --- a/test/integration-tests/extension.test.ts +++ b/test/integration-tests/extension.test.ts @@ -17,8 +17,10 @@ import { WorkspaceContext } from "../../src/WorkspaceContext"; import { getBuildAllTask } from "../../src/tasks/SwiftTaskProvider"; import { SwiftExecution } from "../../src/tasks/SwiftExecution"; import { activateExtensionForTest } from "./utilities/testutilities"; +import { expect } from "chai"; -suite("Extension Test Suite", () => { +suite("Extension Test Suite", function () { + this.timeout(60000); let workspaceContext: WorkspaceContext; activateExtensionForTest({ @@ -41,18 +43,19 @@ suite("Extension Test Suite", () => { }).timeout(5000);*/ }); - suite("Workspace", () => { + suite("Workspace", function () { + this.timeout(60000); /** Verify tasks.json is being loaded */ test("Tasks.json", async () => { const folder = workspaceContext.folders.find(f => f.name === "test/defaultPackage"); assert(folder); const buildAllTask = await getBuildAllTask(folder); const execution = buildAllTask.execution as SwiftExecution; - assert.strictEqual(buildAllTask.definition.type, "swift"); - assert.strictEqual(buildAllTask.name, "swift: Build All (defaultPackage)"); + expect(buildAllTask.definition.type).to.equal("swift"); + expect(buildAllTask.name).to.include("Build All (defaultPackage)"); for (const arg of ["build", "--build-tests", "--verbose"]) { assert(execution?.args.find(item => item === arg)); } - }); + }).timeout(60000); }); -}).timeout(15000); +}); diff --git a/test/integration-tests/language/LanguageClientIntegration.test.ts b/test/integration-tests/language/LanguageClientIntegration.test.ts index 3caab8c8d..883cc49a1 100644 --- a/test/integration-tests/language/LanguageClientIntegration.test.ts +++ b/test/integration-tests/language/LanguageClientIntegration.test.ts @@ -33,7 +33,7 @@ async function buildProject(ctx: WorkspaceContext, name: string) { } suite("Language Client Integration Suite @slow", function () { - this.timeout(2 * 60 * 1000); + this.timeout(3 * 60 * 1000); let clientManager: LanguageClientManager; let workspaceContext: WorkspaceContext; diff --git a/test/integration-tests/tasks/SwiftPluginTaskProvider.test.ts b/test/integration-tests/tasks/SwiftPluginTaskProvider.test.ts index 47e1bc5e9..85b17112a 100644 --- a/test/integration-tests/tasks/SwiftPluginTaskProvider.test.ts +++ b/test/integration-tests/tasks/SwiftPluginTaskProvider.test.ts @@ -30,11 +30,14 @@ import { } from "../../utilities/tasks"; import { mutable } from "../../utilities/types"; import { SwiftExecution } from "../../../src/tasks/SwiftExecution"; +import { SwiftTask } from "../../../src/tasks/SwiftTaskProvider"; -suite("SwiftPluginTaskProvider Test Suite", () => { +suite("SwiftPluginTaskProvider Test Suite", function () { let workspaceContext: WorkspaceContext; let folderContext: FolderContext; + this.timeout(60000); // Mostly only when running suite with .only + suite("settings plugin arguments", () => { activateExtensionForSuite({ async setup(ctx) { @@ -59,17 +62,20 @@ suite("SwiftPluginTaskProvider Test Suite", () => { const tasks = await vscode.tasks.fetchTasks({ type: "swift-plugin" }); const task = tasks.find(t => t.name === "command-plugin"); const swiftExecution = task?.execution as SwiftExecution; - assert.deepEqual(swiftExecution.args, [ - "package", - "--disable-sandbox", - "--allow-writing-to-package-directory", - "--allow-writing-to-directory", - "/foo", - "/bar", - "--allow-network-connections", - "all", - "command_plugin", - ]); + assert.deepEqual( + swiftExecution.args, + workspaceContext.toolchain.buildFlags.withAdditionalFlags([ + "package", + "--disable-sandbox", + "--allow-writing-to-package-directory", + "--allow-writing-to-directory", + "/foo", + "/bar", + "--allow-network-connections", + "all", + "command_plugin", + ]) + ); }); }); @@ -101,7 +107,7 @@ suite("SwiftPluginTaskProvider Test Suite", () => { const { exitCode, output } = await executeTaskAndWaitForResult(task); expect(exitCode).to.equal(0); expect(cleanOutput(output)).to.include("Hello, World!"); - }).timeout(60000); + }); test("Exit code on failure", async () => { const task = taskProvider.createSwiftPluginTask( @@ -118,20 +124,25 @@ suite("SwiftPluginTaskProvider Test Suite", () => { mutable(task.execution).command = "/definitely/not/swift"; const { exitCode, output } = await executeTaskAndWaitForResult(task); expect(exitCode, `${output}`).to.not.equal(0); - }).timeout(10000); + }); }); suite("provideTasks", () => { suite("includes command plugin provided by the extension", async () => { - let task: vscode.Task | undefined; + let task: SwiftTask | undefined; setup(async () => { const tasks = await vscode.tasks.fetchTasks({ type: "swift-plugin" }); - task = tasks.find(t => t.name === "command-plugin"); + task = tasks.find(t => t.name === "command-plugin") as SwiftTask; }); test("provides", () => { - expect(task?.detail).to.equal("swift package command_plugin"); + expect(task?.execution.args).to.deep.equal( + workspaceContext.toolchain.buildFlags.withAdditionalFlags([ + "package", + "command_plugin", + ]) + ); }); test("executes", async () => { @@ -140,7 +151,7 @@ suite("SwiftPluginTaskProvider Test Suite", () => { await vscode.tasks.executeTask(task); const exitCode = await exitPromise; expect(exitCode).to.equal(0); - }).timeout(30000); // 30 seconds to run + }); }); suite("includes command plugin provided by tasks.json", async () => { @@ -152,7 +163,7 @@ suite("SwiftPluginTaskProvider Test Suite", () => { }); test("provides", () => { - expect(task?.detail).to.equal("swift package command_plugin --foo"); + expect(task?.detail).to.include("swift package command_plugin --foo"); }); test("executes", async () => { @@ -161,7 +172,7 @@ suite("SwiftPluginTaskProvider Test Suite", () => { await vscode.tasks.executeTask(task); const exitCode = await exitPromise; expect(exitCode).to.equal(0); - }).timeout(30000); // 30 seconds to run + }); }); }); }); diff --git a/test/integration-tests/testexplorer/TestExplorerIntegration.test.ts b/test/integration-tests/testexplorer/TestExplorerIntegration.test.ts index c48d1e847..823895d13 100644 --- a/test/integration-tests/testexplorer/TestExplorerIntegration.test.ts +++ b/test/integration-tests/testexplorer/TestExplorerIntegration.test.ts @@ -71,6 +71,8 @@ suite("Test Explorer Suite", function () { // which starts searching the workspace for tests. await waitForTestExplorerReady(testExplorer); }, + requiresLSP: true, + requiresDebugger: true, }); suite("Debugging", function () { @@ -131,6 +133,7 @@ suite("Test Explorer Suite", function () { switch (process.platform) { case "linux": return "/usr/lib/liblldb.so"; + case "darwin": case "win32": return await (await SwiftToolchain.create()).getLLDBDebugAdapter(); default: diff --git a/test/integration-tests/ui/PackageDependencyProvider.test.ts b/test/integration-tests/ui/PackageDependencyProvider.test.ts index fdced668f..72b7ddaa3 100644 --- a/test/integration-tests/ui/PackageDependencyProvider.test.ts +++ b/test/integration-tests/ui/PackageDependencyProvider.test.ts @@ -24,18 +24,22 @@ import { getBuildAllTask, SwiftTask } from "../../../src/tasks/SwiftTaskProvider import { testAssetPath } from "../../fixtures"; import { activateExtensionForSuite, folderInRootWorkspace } from "../utilities/testutilities"; import contextKeys from "../../../src/contextKeys"; +import { FolderContext } from "../../../src/FolderContext"; +import { WorkspaceContext } from "../../../src/WorkspaceContext"; suite("PackageDependencyProvider Test Suite", function () { + let workspaceContext: WorkspaceContext; + let folderContext: FolderContext; let treeProvider: PackageDependenciesProvider; - this.timeout(2 * 60 * 1000); // Allow up to 2 minutes to build + this.timeout(3 * 60 * 1000); // Allow up to 3 minutes to build activateExtensionForSuite({ async setup(ctx) { - const workspaceContext = ctx; + workspaceContext = ctx; await waitForNoRunningTasks(); - await folderInRootWorkspace("defaultPackage", workspaceContext); - const folderContext = await folderInRootWorkspace("dependencies", workspaceContext); + folderContext = await folderInRootWorkspace("dependencies", workspaceContext); await executeTaskAndWaitForResult((await getBuildAllTask(folderContext)) as SwiftTask); + await folderContext.reload(); treeProvider = new PackageDependenciesProvider(workspaceContext); await workspaceContext.focusFolder(folderContext); }, @@ -43,7 +47,10 @@ suite("PackageDependencyProvider Test Suite", function () { contextKeys.flatDependenciesList = false; treeProvider.dispose(); }, - testAssets: ["dependencies"], + }); + + setup(async () => { + await workspaceContext.focusFolder(folderContext); }); test("Includes remote dependency", async () => { diff --git a/test/integration-tests/utilities/testutilities.ts b/test/integration-tests/utilities/testutilities.ts index 806d30bb6..3bac12678 100644 --- a/test/integration-tests/utilities/testutilities.ts +++ b/test/integration-tests/utilities/testutilities.ts @@ -22,6 +22,8 @@ import { FolderContext } from "../../../src/FolderContext"; import { waitForNoRunningTasks } from "../../utilities/tasks"; import { closeAllEditors } from "../../utilities/commands"; import { isDeepStrictEqual } from "util"; +import { Version } from "../../../src/utilities/version"; +import configuration from "../../../src/configuration"; function getRootWorkspaceFolder(): vscode.WorkspaceFolder { const result = vscode.workspace.workspaceFolders?.at(0); @@ -65,7 +67,9 @@ const extensionBootstrapper = (() => { | undefined, after: Mocha.HookFunction, teardown: ((this: Mocha.Context) => Promise) | undefined, - testAssets?: string[] + testAssets?: string[], + requiresLSP: boolean = false, + requiresDebugger: boolean = false ) { let workspaceContext: WorkspaceContext | undefined; let autoTeardown: void | (() => Promise); @@ -76,6 +80,18 @@ const extensionBootstrapper = (() => { this.currentTest, testAssets ?? ["defaultPackage"] ); + // Need the `disableSandbox` configuration which is only in 6.1 + // https://github.com/swiftlang/sourcekit-lsp/commit/7e2d12a7a0d184cc820ae6af5ddbb8aa18b1501c + if ( + process.platform === "darwin" && + workspaceContext.toolchain.swiftVersion.isLessThan(new Version(6, 1, 0)) && + requiresLSP + ) { + this.skip(); + } + if (requiresDebugger && configuration.debugger.disable) { + this.skip(); + } if (!setup) { return; } @@ -192,13 +208,17 @@ const extensionBootstrapper = (() => { ) => Promise<(() => Promise) | void>; teardown?: (this: Mocha.Context) => Promise; testAssets?: string[]; + requiresLSP?: boolean; + requiresDebugger?: boolean; }) { testRunnerSetup( mocha.before, config?.setup, mocha.after, config?.teardown, - config?.testAssets + config?.testAssets, + config?.requiresLSP, + config?.requiresDebugger ); }, @@ -209,13 +229,17 @@ const extensionBootstrapper = (() => { ) => Promise<(() => Promise) | void>; teardown?: (this: Mocha.Context) => Promise; testAssets?: string[]; + requiresLSP?: boolean; + requiresDebugger?: boolean; }) { testRunnerSetup( mocha.beforeEach, config?.setup, mocha.afterEach, config?.teardown, - config?.testAssets + config?.testAssets, + config?.requiresLSP, + config?.requiresDebugger ); }, }; diff --git a/test/unit-tests/sourcekit-lsp/LanguageClientManager.test.ts b/test/unit-tests/sourcekit-lsp/LanguageClientManager.test.ts index fd9009c02..a50da8e44 100644 --- a/test/unit-tests/sourcekit-lsp/LanguageClientManager.test.ts +++ b/test/unit-tests/sourcekit-lsp/LanguageClientManager.test.ts @@ -92,7 +92,7 @@ suite("LanguageClientManager Suite", () => { }); mockedToolchain = mockObject({ swiftVersion: new Version(6, 0, 0), - buildFlags: mockedBuildFlags, + buildFlags: mockedBuildFlags as unknown as BuildFlags, getToolchainExecutable: mockFn(s => s.withArgs("sourcekit-lsp").returns("/path/to/toolchain/bin/sourcekit-lsp") ), diff --git a/test/unit-tests/tasks/SwiftPluginTaskProvider.test.ts b/test/unit-tests/tasks/SwiftPluginTaskProvider.test.ts index 28be1b94a..762d24f3b 100644 --- a/test/unit-tests/tasks/SwiftPluginTaskProvider.test.ts +++ b/test/unit-tests/tasks/SwiftPluginTaskProvider.test.ts @@ -31,7 +31,7 @@ suite("SwiftPluginTaskProvider Unit Test Suite", () => { setup(async () => { buildFlags = mockObject({ - withSwiftSDKFlags: mockFn(s => s.callsFake(args => args)), + withAdditionalFlags: mockFn(s => s.callsFake(args => args)), }); toolchain = mockObject({ swiftVersion: new Version(6, 0, 0), @@ -192,7 +192,7 @@ suite("SwiftPluginTaskProvider Unit Test Suite", () => { }); test("includes sdk flags", async () => { - buildFlags.withSwiftSDKFlags + buildFlags.withAdditionalFlags .withArgs(match(["package", "my-plugin"])) .returns(["package", "my-plugin", "--sdk", "/path/to/sdk"]); const taskProvider = new SwiftPluginTaskProvider(instance(workspaceContext)); diff --git a/test/unit-tests/tasks/SwiftTaskProvider.test.ts b/test/unit-tests/tasks/SwiftTaskProvider.test.ts index f5a4198a3..f5523e79f 100644 --- a/test/unit-tests/tasks/SwiftTaskProvider.test.ts +++ b/test/unit-tests/tasks/SwiftTaskProvider.test.ts @@ -49,8 +49,7 @@ suite("SwiftTaskProvider Unit Test Suite", () => { setup(async () => { buildFlags = mockObject({ - withSwiftSDKFlags: mockFn(s => s.returns([])), - withSwiftPackageFlags: mockFn(s => s.returns(s.args)), + withAdditionalFlags: mockFn(s => s.callsFake(arr => arr)), }); toolchain = mockObject({ swiftVersion: new Version(6, 0, 0), @@ -184,11 +183,8 @@ suite("SwiftTaskProvider Unit Test Suite", () => { }); test("include sdk flags", () => { - buildFlags.withSwiftSDKFlags + buildFlags.withAdditionalFlags .withArgs(match(["build"])) - .returns(["build", "--sdk", "/path/to/sdk"]); - buildFlags.withSwiftPackageFlags - .withArgs(match(["build", "--sdk", "/path/to/sdk"])) .returns(["build", "--sdk", "/path/to/sdk", "--replace-scm-with-registry"]); const task = createSwiftTask( ["build"], diff --git a/test/unit-tests/toolchain/BuildFlags.test.ts b/test/unit-tests/toolchain/BuildFlags.test.ts index aba7b7f40..0d57cade7 100644 --- a/test/unit-tests/toolchain/BuildFlags.test.ts +++ b/test/unit-tests/toolchain/BuildFlags.test.ts @@ -24,6 +24,8 @@ suite("BuildFlags Test Suite", () => { let mockedToolchain: MockedObject; let buildFlags: BuildFlags; + const sandboxConfig = mockGlobalValue(configuration, "disableSandbox"); + suiteSetup(async () => { mockedToolchain = mockObject({ swiftVersion: new Version(6, 0, 0), @@ -33,6 +35,7 @@ suite("BuildFlags Test Suite", () => { setup(() => { mockedPlatform.setValue("darwin"); + sandboxConfig.setValue(false); }); suite("getDarwinTarget", () => { @@ -233,19 +236,19 @@ suite("BuildFlags Test Suite", () => { }); }); - suite("withSwiftSDKFlags", () => { + suite("withAdditionalFlags", () => { const sdkConfig = mockGlobalValue(configuration, "sdk"); test("package", () => { for (const sub of ["dump-symbol-graph", "diagnose-api-breaking-changes", "resolve"]) { sdkConfig.setValue(""); expect( - buildFlags.withSwiftSDKFlags(["package", sub, "--disable-sandbox"]) + buildFlags.withAdditionalFlags(["package", sub, "--disable-sandbox"]) ).to.deep.equal(["package", sub, "--disable-sandbox"]); sdkConfig.setValue("/some/full/path/to/sdk"); expect( - buildFlags.withSwiftSDKFlags(["package", sub, "--disable-sandbox"]) + buildFlags.withAdditionalFlags(["package", sub, "--disable-sandbox"]) ).to.deep.equal([ "package", sub, @@ -256,76 +259,139 @@ suite("BuildFlags Test Suite", () => { } sdkConfig.setValue(""); - expect( - buildFlags.withSwiftSDKFlags(["package", "init", "--disable-sandbox"]) - ).to.deep.equal(["package", "init", "--disable-sandbox"]); + expect(buildFlags.withAdditionalFlags(["package", "init"])).to.deep.equal([ + "package", + "init", + ]); sdkConfig.setValue("/some/full/path/to/sdk"); - expect( - buildFlags.withSwiftSDKFlags(["package", "init", "--disable-sandbox"]) - ).to.deep.equal(["package", "init", "--disable-sandbox"]); + expect(buildFlags.withAdditionalFlags(["package", "init"])).to.deep.equal([ + "package", + "init", + ]); + + sandboxConfig.setValue(true); + expect(buildFlags.withAdditionalFlags(["package", "init"])).to.deep.equal([ + "package", + "--disable-sandbox", + "-Xswiftc", + "-disable-sandbox", + "init", + ]); }); test("build", () => { sdkConfig.setValue(""); expect( - buildFlags.withSwiftSDKFlags(["build", "--target", "MyExecutable"]) + buildFlags.withAdditionalFlags(["build", "--target", "MyExecutable"]) ).to.deep.equal(["build", "--target", "MyExecutable"]); sdkConfig.setValue("/some/full/path/to/sdk"); expect( - buildFlags.withSwiftSDKFlags(["build", "--target", "MyExecutable"]) + buildFlags.withAdditionalFlags(["build", "--target", "MyExecutable"]) + ).to.deep.equal([ + "build", + "--sdk", + "/some/full/path/to/sdk", + "--target", + "MyExecutable", + ]); + + sandboxConfig.setValue(true); + expect( + buildFlags.withAdditionalFlags(["build", "--target", "MyExecutable"]) ).to.deep.equal([ "build", "--sdk", "/some/full/path/to/sdk", "--target", "MyExecutable", + "--disable-sandbox", + "-Xswiftc", + "-disable-sandbox", ]); }); test("run", () => { sdkConfig.setValue(""); expect( - buildFlags.withSwiftSDKFlags(["run", "--product", "MyExecutable"]) + buildFlags.withAdditionalFlags(["run", "--product", "MyExecutable"]) ).to.deep.equal(["run", "--product", "MyExecutable"]); sdkConfig.setValue("/some/full/path/to/sdk"); expect( - buildFlags.withSwiftSDKFlags(["run", "--product", "MyExecutable"]) + buildFlags.withAdditionalFlags(["run", "--product", "MyExecutable"]) + ).to.deep.equal([ + "run", + "--sdk", + "/some/full/path/to/sdk", + "--product", + "MyExecutable", + ]); + + sandboxConfig.setValue(true); + expect( + buildFlags.withAdditionalFlags(["run", "--product", "MyExecutable"]) ).to.deep.equal([ "run", "--sdk", "/some/full/path/to/sdk", "--product", "MyExecutable", + "--disable-sandbox", + "-Xswiftc", + "-disable-sandbox", ]); }); test("test", () => { sdkConfig.setValue(""); - expect(buildFlags.withSwiftSDKFlags(["test", "--filter", "MyTests"])).to.deep.equal([ + expect(buildFlags.withAdditionalFlags(["test", "--filter", "MyTests"])).to.deep.equal([ "test", "--filter", "MyTests", ]); sdkConfig.setValue("/some/full/path/to/sdk"); - expect(buildFlags.withSwiftSDKFlags(["test", "--filter", "MyTests"])).to.deep.equal([ + expect(buildFlags.withAdditionalFlags(["test", "--filter", "MyTests"])).to.deep.equal([ + "test", + "--sdk", + "/some/full/path/to/sdk", + "--filter", + "MyTests", + ]); + + sandboxConfig.setValue(true); + expect(buildFlags.withAdditionalFlags(["test", "--filter", "MyTests"])).to.deep.equal([ "test", "--sdk", "/some/full/path/to/sdk", "--filter", "MyTests", + "--disable-sandbox", + "-Xswiftc", + "-disable-sandbox", ]); }); test("other commands", () => { sdkConfig.setValue(""); - expect(buildFlags.withSwiftSDKFlags(["help", "repl"])).to.deep.equal(["help", "repl"]); + expect(buildFlags.withAdditionalFlags(["help", "repl"])).to.deep.equal([ + "help", + "repl", + ]); sdkConfig.setValue("/some/full/path/to/sdk"); - expect(buildFlags.withSwiftSDKFlags(["help", "repl"])).to.deep.equal(["help", "repl"]); + expect(buildFlags.withAdditionalFlags(["help", "repl"])).to.deep.equal([ + "help", + "repl", + ]); + + sandboxConfig.setValue(true); + expect(buildFlags.withAdditionalFlags(["help", "repl"])).to.deep.equal([ + "help", + "repl", + ]); }); }); diff --git a/test/unit-tests/toolchain/SelectedXcodeWatcher.test.ts b/test/unit-tests/toolchain/SelectedXcodeWatcher.test.ts index c87d77a4c..490bca449 100644 --- a/test/unit-tests/toolchain/SelectedXcodeWatcher.test.ts +++ b/test/unit-tests/toolchain/SelectedXcodeWatcher.test.ts @@ -16,11 +16,20 @@ import * as vscode from "vscode"; import { expect } from "chai"; import { SelectedXcodeWatcher } from "../../../src/toolchain/SelectedXcodeWatcher"; import { SwiftOutputChannel } from "../../../src/ui/SwiftOutputChannel"; -import { instance, MockedObject, mockFn, mockGlobalObject, mockObject } from "../../MockUtils"; +import { + instance, + MockedObject, + mockFn, + mockGlobalObject, + mockGlobalValue, + mockObject, +} from "../../MockUtils"; +import configuration from "../../../src/configuration"; suite("Selected Xcode Watcher", () => { const mockedVSCodeWindow = mockGlobalObject(vscode, "window"); let mockOutputChannel: MockedObject; + const pathConfig = mockGlobalValue(configuration, "path"); setup(function () { // Xcode only exists on macOS, so the SelectedXcodeWatcher is macOS-only. @@ -31,6 +40,8 @@ suite("Selected Xcode Watcher", () => { mockOutputChannel = mockObject({ appendLine: mockFn(), }); + + pathConfig.setValue(""); }); async function run(symLinksOnCallback: (string | undefined)[]) { @@ -72,4 +83,12 @@ suite("Selected Xcode Watcher", () => { "Reload Extensions" ); }); + + test("Ignores when path is explicitly set", async () => { + pathConfig.setValue("/path/to/swift/bin"); + + await run(["/foo", "/bar"]); + + expect(mockedVSCodeWindow.showWarningMessage).to.have.not.been.called; + }); }); From 55504137c17a004af8299429bda74b2c19498e65 Mon Sep 17 00:00:00 2001 From: Paul LeMarquand Date: Thu, 20 Feb 2025 08:58:36 -0500 Subject: [PATCH 14/86] Add PrivateFrameworks as additional Swift Testing framework path (#1396) This is essentially the same change made in: https://github.com/swiftlang/swift-package-manager/pull/8199 VS Code would be able to leverage it for regular runs with `swift test` but because when debugging we launch the xctest executable directly to run tests, we must include the same framework search paths as SPM. --- src/toolchain/toolchain.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/toolchain/toolchain.ts b/src/toolchain/toolchain.ts index 615f85392..dc2ca194f 100644 --- a/src/toolchain/toolchain.ts +++ b/src/toolchain/toolchain.ts @@ -483,7 +483,9 @@ export class SwiftToolchain { if (!base) { return undefined; } - return path.join(base, "Library/Frameworks"); + const frameworks = path.join(base, "Library/Frameworks"); + const privateFrameworks = path.join(base, "Library/PrivateFrameworks"); + return `${frameworks}:${privateFrameworks}`; } get diagnostics(): string { From acf80847caa889b01c69913c2fc686286cefb0a3 Mon Sep 17 00:00:00 2001 From: Matthew Bastien Date: Thu, 20 Feb 2025 16:15:12 +0000 Subject: [PATCH 15/86] Fix nightly debugging test failures (#1388) * change "swift-lldb" debug configs to "swift" in tests * add option to disable CodeLLDB settings prompt * don't use liblldb on Swift versions <5.10 * disable flaky dependency tests until they can be fixed * bump timeout for package resolve v2 test --- assets/test/.vscode/launch.json | 4 +-- assets/test/.vscode/settings.json | 3 +- .../test/defaultPackage/.vscode/launch.json | 4 +-- assets/test/diagnostics/.vscode/launch.json | 8 ++--- package.json | 33 +++++++++++++++---- src/configuration.ts | 12 +++++++ src/debugger/debugAdapterFactory.ts | 29 ++++++++++++---- test/integration-tests/SwiftPackage.test.ts | 2 +- .../commands/dependency.test.ts | 4 ++- .../TestExplorerIntegration.test.ts | 24 +------------- .../utilities/testutilities.ts | 10 ++++++ .../debugger/debugAdapterFactory.test.ts | 26 +++++++++++++++ 12 files changed, 111 insertions(+), 48 deletions(-) diff --git a/assets/test/.vscode/launch.json b/assets/test/.vscode/launch.json index 152e39f4a..90b9c8da4 100644 --- a/assets/test/.vscode/launch.json +++ b/assets/test/.vscode/launch.json @@ -1,7 +1,7 @@ { "configurations": [ { - "type": "swift-lldb", + "type": "swift", "request": "launch", "name": "Debug PackageExe (defaultPackage)", "program": "${workspaceFolder:test}/defaultPackage/.build/debug/PackageExe", @@ -12,7 +12,7 @@ "initCommands": ["settings set target.disable-aslr false"], }, { - "type": "swift-lldb", + "type": "swift", "request": "launch", "name": "Release PackageExe (defaultPackage)", "program": "${workspaceFolder:test}/defaultPackage/.build/release/PackageExe", diff --git a/assets/test/.vscode/settings.json b/assets/test/.vscode/settings.json index 4a09afde5..db1acbde4 100644 --- a/assets/test/.vscode/settings.json +++ b/assets/test/.vscode/settings.json @@ -1,7 +1,8 @@ { "swift.disableAutoResolve": true, "swift.autoGenerateLaunchConfigurations": false, - "swift.debugger.useDebugAdapterFromToolchain": true, + "swift.debugger.debugAdapter": "lldb-dap", + "swift.debugger.setupCodeLLDB": "alwaysUpdateGlobal", "swift.additionalTestArguments": [ "-Xswiftc", "-DTEST_ARGUMENT_SET_VIA_TEST_BUILD_ARGUMENTS_SETTING" diff --git a/assets/test/defaultPackage/.vscode/launch.json b/assets/test/defaultPackage/.vscode/launch.json index c0eb8e90d..a2ecfe301 100644 --- a/assets/test/defaultPackage/.vscode/launch.json +++ b/assets/test/defaultPackage/.vscode/launch.json @@ -1,7 +1,7 @@ { "configurations": [ { - "type": "swift-lldb", + "type": "swift", "request": "launch", "name": "Debug package1", "program": "${workspaceFolder:defaultPackage}/.build/debug/package1", @@ -10,7 +10,7 @@ "preLaunchTask": "swift: Build Debug package1" }, { - "type": "swift-lldb", + "type": "swift", "request": "launch", "name": "Release package1", "program": "${workspaceFolder:defaultPackage}/.build/release/package1", diff --git a/assets/test/diagnostics/.vscode/launch.json b/assets/test/diagnostics/.vscode/launch.json index aca14e524..e86c37d2e 100644 --- a/assets/test/diagnostics/.vscode/launch.json +++ b/assets/test/diagnostics/.vscode/launch.json @@ -1,7 +1,7 @@ { "configurations": [ { - "type": "swift-lldb", + "type": "swift", "request": "launch", "args": [], "cwd": "${workspaceFolder:diagnostics}", @@ -10,7 +10,7 @@ "preLaunchTask": "swift: Build Debug diagnostics" }, { - "type": "swift-lldb", + "type": "swift", "request": "launch", "args": [], "cwd": "${workspaceFolder:diagnostics}", @@ -19,7 +19,7 @@ "preLaunchTask": "swift: Build Release diagnostics" }, { - "type": "swift-lldb", + "type": "swift", "request": "launch", "args": [], "cwd": "${workspaceFolder:diagnostics}", @@ -28,7 +28,7 @@ "preLaunchTask": "swift: Build Debug diagnostics" }, { - "type": "swift-lldb", + "type": "swift", "request": "launch", "args": [], "cwd": "${workspaceFolder:diagnostics}", diff --git a/package.json b/package.json index 91477c8e5..af7d020b5 100644 --- a/package.json +++ b/package.json @@ -673,13 +673,7 @@ "Use the `lldb-dap` executable from the toolchain. Requires Swift 6 or later.", "Use the CodeLLDB extension's debug adapter." ], - "order": 1 - }, - "swift.debugger.useDebugAdapterFromToolchain": { - "type": "boolean", - "default": false, - "markdownDeprecationMessage": "**Deprecated**: Use the `swift.debugger.debugAdapter` setting instead. This will be removed in future versions of the Swift extension.", - "markdownDescription": "Use the LLDB debug adapter packaged with the Swift toolchain as your debug adapter. Note: this is only available starting with Swift 6. The CodeLLDB extension will be used if your Swift toolchain does not contain lldb-dap.", + "markdownDescription": "Select which debug adapter to use to debus Swift executables.", "order": 1 }, "swift.debugger.path": { @@ -687,6 +681,31 @@ "default": "", "markdownDescription": "Path to lldb debug adapter.", "order": 2 + }, + "swift.debugger.setupCodeLLDB": { + "type": "string", + "default": "prompt", + "enum": [ + "prompt", + "alwaysUpdateGlobal", + "alwaysUpdateWorkspace", + "never" + ], + "enumDescriptions": [ + "Prompt to update CodeLLDB settings when they are incorrect.", + "Always automatically update CodeLLDB settings globally when they are incorrect.", + "Always automatically update CodeLLDB settings in the workspace when they are incorrect.", + "Never automatically update CodeLLDB settings when they are incorrect." + ], + "markdownDescription": "Choose how CodeLLDB settings are updated when debugging Swift executables.", + "order": 3 + }, + "swift.debugger.useDebugAdapterFromToolchain": { + "type": "boolean", + "default": false, + "markdownDeprecationMessage": "**Deprecated**: Use the `swift.debugger.debugAdapter` setting instead. This will be removed in future versions of the Swift extension.", + "markdownDescription": "Use the LLDB debug adapter packaged with the Swift toolchain as your debug adapter. Note: this is only available starting with Swift 6. The CodeLLDB extension will be used if your Swift toolchain does not contain lldb-dap.", + "order": 4 } } }, diff --git a/src/configuration.ts b/src/configuration.ts index 5f7abdf29..79e95fa0e 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -15,6 +15,11 @@ import * as vscode from "vscode"; export type DebugAdapters = "auto" | "lldb-dap" | "CodeLLDB"; +export type SetupCodeLLDBOptions = + | "prompt" + | "alwaysUpdateGlobal" + | "alwaysUpdateWorkspace" + | "never"; export type CFamilySupportOptions = "enable" | "disable" | "cpptools-inactive"; export type ActionAfterBuildError = "Focus Problems" | "Focus Terminal" | "Do Nothing"; export type OpenAfterCreateNewProjectOptions = @@ -54,6 +59,8 @@ export interface DebuggerConfiguration { readonly customDebugAdapterPath: string; /** Whether or not to disable setting up the debugger */ readonly disable: boolean; + /** User choices for updating CodeLLDB settings */ + readonly setupCodeLLDB: SetupCodeLLDBOptions; } /** workspace folder configuration */ @@ -219,6 +226,11 @@ const configuration = { .getConfiguration("swift.debugger") .get("disable", false); }, + get setupCodeLLDB(): SetupCodeLLDBOptions { + return vscode.workspace + .getConfiguration("swift.debugger") + .get("setupCodeLLDB", "prompt"); + }, }; }, /** Files and directories to exclude from the code coverage. */ diff --git a/src/debugger/debugAdapterFactory.ts b/src/debugger/debugAdapterFactory.ts index f150e884d..01845b464 100644 --- a/src/debugger/debugAdapterFactory.ts +++ b/src/debugger/debugAdapterFactory.ts @@ -22,6 +22,7 @@ import { SwiftOutputChannel } from "../ui/SwiftOutputChannel"; import { fileExists } from "../utilities/filesystem"; import { getLLDBLibPath } from "./lldb"; import { getErrorDescription } from "../utilities/utilities"; +import configuration from "../configuration"; /** * Registers the active debugger with the extension, and reregisters it @@ -172,13 +173,27 @@ export class LLDBDebugConfigurationProvider implements vscode.DebugConfiguration ) { return; } - const userSelection = await vscode.window.showInformationMessage( - "The Swift extension needs to update some CodeLLDB settings to enable debugging features. Do you want to set this up in your global settings or workspace settings?", - { modal: true }, - "Global", - "Workspace", - "Run Anyway" - ); + let userSelection: "Global" | "Workspace" | "Run Anyway" | undefined = undefined; + switch (configuration.debugger.setupCodeLLDB) { + case "prompt": + userSelection = await vscode.window.showInformationMessage( + "The Swift extension needs to update some CodeLLDB settings to enable debugging features. Do you want to set this up in your global settings or workspace settings?", + { modal: true }, + "Global", + "Workspace", + "Run Anyway" + ); + break; + case "alwaysUpdateGlobal": + userSelection = "Global"; + break; + case "alwaysUpdateWorkspace": + userSelection = "Workspace"; + break; + case "never": + userSelection = "Run Anyway"; + break; + } switch (userSelection) { case "Global": lldbConfig.update("library", libLldbPath, vscode.ConfigurationTarget.Global); diff --git a/test/integration-tests/SwiftPackage.test.ts b/test/integration-tests/SwiftPackage.test.ts index 5c796db90..7e29cf209 100644 --- a/test/integration-tests/SwiftPackage.test.ts +++ b/test/integration-tests/SwiftPackage.test.ts @@ -59,7 +59,7 @@ suite("SwiftPackage Test Suite", () => { const spmPackage = await SwiftPackage.create(testAssetUri("package5.6"), toolchain); assert.strictEqual(spmPackage.isValid, true); assert(spmPackage.resolved !== undefined); - }).timeout(15000); + }).timeout(20000); test("Identity case-insensitivity", async () => { const spmPackage = await SwiftPackage.create(testAssetUri("identity-case"), toolchain); diff --git a/test/integration-tests/commands/dependency.test.ts b/test/integration-tests/commands/dependency.test.ts index 27c609e54..afa7e40e4 100644 --- a/test/integration-tests/commands/dependency.test.ts +++ b/test/integration-tests/commands/dependency.test.ts @@ -106,6 +106,7 @@ suite("Dependency Commmands Test Suite", function () { } test("Swift: Reset Package Dependencies", async function () { + this.skip(); // https://github.com/swiftlang/vscode-swift/issues/1316 // spm reset after using local dependency is broken on windows if (process.platform === "win32") { this.skip(); @@ -121,7 +122,8 @@ suite("Dependency Commmands Test Suite", function () { expect(dep?.type).to.equal("remote"); }); - test("Swift: Revert To Original Version", async () => { + test("Swift: Revert To Original Version", async function () { + this.skip(); // https://github.com/swiftlang/vscode-swift/issues/1316 await useLocalDependencyTest(); const result = await vscode.commands.executeCommand( diff --git a/test/integration-tests/testexplorer/TestExplorerIntegration.test.ts b/test/integration-tests/testexplorer/TestExplorerIntegration.test.ts index 823895d13..aa5c34d09 100644 --- a/test/integration-tests/testexplorer/TestExplorerIntegration.test.ts +++ b/test/integration-tests/testexplorer/TestExplorerIntegration.test.ts @@ -46,7 +46,6 @@ import { updateSettings, } from "../utilities/testutilities"; import { Commands } from "../../../src/commands"; -import { SwiftToolchain } from "../../../src/toolchain/toolchain"; suite("Test Explorer Suite", function () { const MAX_TEST_RUN_TIME_MINUTES = 5; @@ -129,18 +128,6 @@ suite("Test Explorer Suite", function () { }); suite("CodeLLDB", () => { - async function getLLDBDebugAdapterPath() { - switch (process.platform) { - case "linux": - return "/usr/lib/liblldb.so"; - case "darwin": - case "win32": - return await (await SwiftToolchain.create()).getLLDBDebugAdapter(); - default: - throw new Error("Please provide the path to lldb for this platform"); - } - } - let resetSettings: (() => Promise) | undefined; beforeEach(async function () { // CodeLLDB on windows doesn't print output and so cannot be parsed @@ -148,17 +135,8 @@ suite("Test Explorer Suite", function () { this.skip(); } - const lldbPath = - process.env["CI"] === "1" - ? { - "lldb.library": await getLLDBDebugAdapterPath(), - "lldb.launch.expressions": "native", - } - : {}; - resetSettings = await updateSettings({ - "swift.debugger.useDebugAdapterFromToolchain": false, - ...lldbPath, + "swift.debugger.debugAdapter": "CodeLLDB", }); }); diff --git a/test/integration-tests/utilities/testutilities.ts b/test/integration-tests/utilities/testutilities.ts index 3bac12678..e77b53ea8 100644 --- a/test/integration-tests/utilities/testutilities.ts +++ b/test/integration-tests/utilities/testutilities.ts @@ -73,6 +73,7 @@ const extensionBootstrapper = (() => { ) { let workspaceContext: WorkspaceContext | undefined; let autoTeardown: void | (() => Promise); + let restoreSettings: (() => Promise) | undefined; before(async function () { // Always activate the extension. If no test assets are provided, // default to adding `defaultPackage` to the workspace. @@ -92,6 +93,12 @@ const extensionBootstrapper = (() => { if (requiresDebugger && configuration.debugger.disable) { this.skip(); } + // CodeLLDB does not work with libllbd in Swift toolchains prior to 5.10 + if (workspaceContext.swiftVersion.isLessThan(new Version(5, 10, 0))) { + restoreSettings = await updateSettings({ + "swift.debugger.setupCodeLLDB": "never", + }); + } if (!setup) { return; } @@ -121,6 +128,9 @@ const extensionBootstrapper = (() => { if (autoTeardown) { await autoTeardown(); } + if (restoreSettings) { + await restoreSettings(); + } } catch (error) { if (workspaceContext) { console.error(`Error during test/suite teardown, captured logs are:`); diff --git a/test/unit-tests/debugger/debugAdapterFactory.test.ts b/test/unit-tests/debugger/debugAdapterFactory.test.ts index f32bcde68..b410e30a5 100644 --- a/test/unit-tests/debugger/debugAdapterFactory.test.ts +++ b/test/unit-tests/debugger/debugAdapterFactory.test.ts @@ -38,6 +38,7 @@ import { SwiftToolchain } from "../../../src/toolchain/toolchain"; import { SwiftOutputChannel } from "../../../src/ui/SwiftOutputChannel"; import * as debugAdapter from "../../../src/debugger/debugAdapter"; import { Result } from "../../../src/utilities/result"; +import configuration from "../../../src/configuration"; suite("LLDBDebugAdapterExecutableFactory Tests", () => { const mockDebugAdapter = mockGlobalModule(DebugAdapter); @@ -83,6 +84,7 @@ suite("LLDBDebugConfigurationProvider Tests", () => { suite("CodeLLDB selected in settings", () => { let mockLldbConfiguration: MockedObject; const mockLLDB = mockGlobalModule(lldb); + const mockDebuggerConfig = mockGlobalObject(configuration, "debugger"); const mockWorkspace = mockGlobalObject(vscode, "workspace"); setup(() => { @@ -95,6 +97,7 @@ suite("LLDBDebugConfigurationProvider Tests", () => { }); mockWorkspace.getConfiguration.returns(instance(mockLldbConfiguration)); mockLLDB.getLLDBLibPath.resolves(Result.makeSuccess("/path/to/liblldb.dyLib")); + mockDebuggerConfig.setupCodeLLDB = "prompt"; mockDebugAdapter.getLaunchConfigType.returns(LaunchConfigType.CODE_LLDB); }); @@ -135,6 +138,29 @@ suite("LLDBDebugConfigurationProvider Tests", () => { "/path/to/liblldb.dyLib" ); }); + + test("avoids prompting the user about CodeLLDB if requested in settings", async () => { + mockDebuggerConfig.setupCodeLLDB = "alwaysUpdateGlobal"; + mockLldbConfiguration.get.withArgs("library").returns(undefined); + const configProvider = new LLDBDebugConfigurationProvider( + "darwin", + instance(mockToolchain), + instance(mockOutputChannel) + ); + await expect( + configProvider.resolveDebugConfiguration(undefined, { + name: "Test Launch Config", + type: SWIFT_LAUNCH_CONFIG_TYPE, + request: "launch", + program: "${workspaceFolder}/.build/debug/executable", + }) + ).to.eventually.be.an("object"); + expect(mockWindow.showInformationMessage).to.not.have.been.called; + expect(mockLldbConfiguration.update).to.have.been.calledWith( + "library", + "/path/to/liblldb.dyLib" + ); + }); }); suite("lldb-dap selected in settings", () => { From 8b57d89eb97ef47257bc9faf1e08fb2a19e4a619 Mon Sep 17 00:00:00 2001 From: Matthew Bastien Date: Fri, 21 Feb 2025 14:45:36 +0000 Subject: [PATCH 16/86] fix output logs on integration test failure (#1400) --- src/extension.ts | 2 +- src/sourcekit-lsp/LanguageClientManager.ts | 2 +- src/toolchain/toolchain.ts | 2 +- src/ui/SwiftOutputChannel.ts | 12 ------ .../ui/SwiftOutputChannel.test.ts | 2 +- .../utilities/testutilities.ts | 40 +++++++++---------- test/unit-tests/debugger/lldb.test.ts | 2 +- 7 files changed, 24 insertions(+), 38 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index b87f950fc..661696230 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -56,7 +56,7 @@ export interface Api { */ export async function activate(context: vscode.ExtensionContext): Promise { try { - const outputChannel = new SwiftOutputChannel("Swift", !process.env["VSCODE_TEST"]); + const outputChannel = new SwiftOutputChannel("Swift"); outputChannel.log("Activating Swift for Visual Studio Code..."); checkAndWarnAboutWindowsSymlinks(outputChannel); diff --git a/src/sourcekit-lsp/LanguageClientManager.ts b/src/sourcekit-lsp/LanguageClientManager.ts index 8379b9027..a71b55754 100644 --- a/src/sourcekit-lsp/LanguageClientManager.ts +++ b/src/sourcekit-lsp/LanguageClientManager.ts @@ -532,7 +532,7 @@ export class LanguageClientManager implements vscode.Disposable { documentSelector: LanguageClientManager.documentSelector, revealOutputChannelOn: RevealOutputChannelOn.Never, workspaceFolder: workspaceFolder, - outputChannel: new SwiftOutputChannel("SourceKit Language Server", false), + outputChannel: new SwiftOutputChannel("SourceKit Language Server"), middleware: { provideCodeLenses: async (document, token, next) => { const result = await next(document, token); diff --git a/src/toolchain/toolchain.ts b/src/toolchain/toolchain.ts index dc2ca194f..1e6646f1f 100644 --- a/src/toolchain/toolchain.ts +++ b/src/toolchain/toolchain.ts @@ -790,7 +790,7 @@ export class SwiftToolchain { const plistKey = type === "XCTest" ? "XCTEST_VERSION" : "SWIFT_TESTING_VERSION"; const version = infoPlist.DefaultProperties[plistKey]; if (!version) { - new SwiftOutputChannel("swift", true).appendLine( + new SwiftOutputChannel("swift").appendLine( `Warning: ${platformManifest} is missing the ${plistKey} key.` ); return undefined; diff --git a/src/ui/SwiftOutputChannel.ts b/src/ui/SwiftOutputChannel.ts index 4dfe482ab..ecad10f33 100644 --- a/src/ui/SwiftOutputChannel.ts +++ b/src/ui/SwiftOutputChannel.ts @@ -25,11 +25,9 @@ export class SwiftOutputChannel implements vscode.OutputChannel { */ constructor( public name: string, - private logToConsole: boolean = true, logStoreLinesSize: number = 250_000 // default to capturing 250k log lines ) { this.name = name; - this.logToConsole = process.env["CI"] !== "1" && logToConsole; this.channel = vscode.window.createOutputChannel(name, "Swift"); this.logStore = new RollingLog(logStoreLinesSize); } @@ -37,21 +35,11 @@ export class SwiftOutputChannel implements vscode.OutputChannel { append(value: string): void { this.channel.append(value); this.logStore.append(value); - - if (this.logToConsole) { - // eslint-disable-next-line no-console - console.log(value); - } } appendLine(value: string): void { this.channel.appendLine(value); this.logStore.appendLine(value); - - if (this.logToConsole) { - // eslint-disable-next-line no-console - console.log(value); - } } replace(value: string): void { diff --git a/test/integration-tests/ui/SwiftOutputChannel.test.ts b/test/integration-tests/ui/SwiftOutputChannel.test.ts index 57b113965..8321b9319 100644 --- a/test/integration-tests/ui/SwiftOutputChannel.test.ts +++ b/test/integration-tests/ui/SwiftOutputChannel.test.ts @@ -20,7 +20,7 @@ suite("SwiftOutputChannel", function () { const channels: SwiftOutputChannel[] = []; setup(function () { const channelName = `SwiftOutputChannel Tests ${this.currentTest?.id ?? ""}`; - channel = new SwiftOutputChannel(channelName, false, 3); + channel = new SwiftOutputChannel(channelName, 3); channels.push(channel); }); diff --git a/test/integration-tests/utilities/testutilities.ts b/test/integration-tests/utilities/testutilities.ts index e77b53ea8..6393e39a5 100644 --- a/test/integration-tests/utilities/testutilities.ts +++ b/test/integration-tests/utilities/testutilities.ts @@ -35,28 +35,8 @@ const extensionBootstrapper = (() => { let activator: (() => Promise) | undefined = undefined; let activatedAPI: Api | undefined = undefined; let lastTestName: string | undefined = undefined; - let lastTestLogs: string[] = []; const testTitle = (currentTest: Mocha.Test) => currentTest.titlePath().join(" → "); - mocha.afterEach(function () { - if (this.currentTest && this.currentTest.isFailed()) { - console.log(`Captured logs during ${testTitle(this.currentTest)}:`); - if (lastTestLogs.length === 0) { - console.log("No logs captured."); - } - for (const log of lastTestLogs) { - console.log(log); - } - } - }); - - mocha.beforeEach(function () { - if (this.currentTest && activatedAPI && process.env["VSCODE_TEST"]) { - activatedAPI.outputChannel.clear(); - activatedAPI.outputChannel.appendLine(`Starting test: ${testTitle(this.currentTest)}`); - } - }); - function testRunnerSetup( before: Mocha.HookFunction, setup: @@ -119,6 +99,25 @@ const extensionBootstrapper = (() => { } }); + mocha.beforeEach(function () { + if (this.currentTest && activatedAPI) { + activatedAPI.outputChannel.clear(); + activatedAPI.outputChannel.appendLine( + `Starting test: ${testTitle(this.currentTest)}` + ); + } + }); + + mocha.afterEach(function () { + if (this.currentTest && activatedAPI && this.currentTest.isFailed()) { + console.log(`Captured logs during ${testTitle(this.currentTest)}:`); + for (const log of activatedAPI.outputChannel.logs) { + console.log(log); + } + console.log("======== END OF LOGS ========\n\n"); + } + }); + after(async function () { try { // First run the users supplied teardown, then await the autoTeardown if it exists. @@ -196,7 +195,6 @@ const extensionBootstrapper = (() => { if (!activatedAPI) { throw new Error("Extension is not activated. Call activateExtension() first."); } - lastTestLogs = activatedAPI.outputChannel.logs; // Wait for up to 10 seconds for all tasks to complete before deactivating. // Long running tasks should be avoided in tests, but this is a safety net. diff --git a/test/unit-tests/debugger/lldb.test.ts b/test/unit-tests/debugger/lldb.test.ts index 0bc5c1d6d..f7034e460 100644 --- a/test/unit-tests/debugger/lldb.test.ts +++ b/test/unit-tests/debugger/lldb.test.ts @@ -154,7 +154,7 @@ suite("debugger.lldb Tests", () => { }); mockContext = mockObject({ toolchain: instance(mockToolchain), - outputChannel: new SwiftOutputChannel("mockChannel", false), + outputChannel: new SwiftOutputChannel("mockChannel"), }); }); From a67d04939633eed0ccea781b159dd2d4a98cadfa Mon Sep 17 00:00:00 2001 From: award999 Date: Mon, 24 Feb 2025 09:35:06 -0500 Subject: [PATCH 17/86] Increase timeout for TaskQueue test (#1403) Although I cannot reproduce locally it is taking 7600ms which is very close to the timeout so going to try increasing the timeout to fix. --- test/integration-tests/tasks/TaskQueue.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration-tests/tasks/TaskQueue.test.ts b/test/integration-tests/tasks/TaskQueue.test.ts index 113d3ce7a..a72330f16 100644 --- a/test/integration-tests/tasks/TaskQueue.test.ts +++ b/test/integration-tests/tasks/TaskQueue.test.ts @@ -156,7 +156,7 @@ suite("TaskQueue Test Suite", () => { taskQueue.queueOperation(new TaskOperation(task2)).then(rt => results.push(rt)), ]); assert.notStrictEqual(results, [1, 2]); - }).timeout(8000); + }).timeout(15000); // check queuing task will return expected value test("swift exec", async () => { From baecac7f83f05a420d81bcd0e40c67bcdc6b8968 Mon Sep 17 00:00:00 2001 From: award999 Date: Mon, 24 Feb 2025 10:59:30 -0500 Subject: [PATCH 18/86] Disable nightly-6.1 run against insiders (#1402) Now that nightly-6.1 matrix option is available for windows, we do not want to run this against vscode insiders --- .github/workflows/nightly.yml | 2 +- .github/workflows/pull_request.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 42220e111..9f22338bd 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -67,7 +67,7 @@ jobs: linux_pre_build_command: . .github/workflows/scripts/setup-linux.sh linux_build_command: ./scripts/test.sh # Windows - windows_exclude_swift_versions: '[{"swift_version": "5.9"}, {"swift_version": "nightly-6.0"}, {"swift_version": "nightly"}]' + windows_exclude_swift_versions: '[{"swift_version": "5.9"}, {"swift_version": "nightly-6.1"}, {"swift_version": "nightly"}]' windows_env_vars: | CI=1 VSCODE_TEST=1 diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 9e37c43e8..f9e673fee 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -21,7 +21,7 @@ jobs: linux_pre_build_command: . .github/workflows/scripts/setup-linux.sh linux_build_command: ./scripts/test.sh # Windows - windows_exclude_swift_versions: '[{"swift_version": "nightly-6.0"},{"swift_version": "nightly"}]' + windows_exclude_swift_versions: '[{"swift_version": "nightly-6.1"},{"swift_version": "nightly"}]' windows_env_vars: | CI=1 VSCODE_TEST=1 From dbc96f9fa887b114b7eb0f40d77c40306f870d6c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 25 Feb 2025 08:10:38 -0500 Subject: [PATCH 19/86] Bump the all-dependencies group across 1 directory with 6 updates (#1405) Bumps the all-dependencies group with 6 updates in the / directory: | Package | From | To | | --- | --- | --- | | [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `18.19.75` | `18.19.76` | | [@types/sinon](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/sinon) | `17.0.3` | `17.0.4` | | [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) | `8.24.0` | `8.25.0` | | [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) | `8.24.0` | `8.25.0` | | [prettier](https://github.com/prettier/prettier) | `3.5.0` | `3.5.2` | | [tsx](https://github.com/privatenumber/tsx) | `4.19.2` | `4.19.3` | Updates `@types/node` from 18.19.75 to 18.19.76 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) Updates `@types/sinon` from 17.0.3 to 17.0.4 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/sinon) Updates `@typescript-eslint/eslint-plugin` from 8.24.0 to 8.25.0 - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.25.0/packages/eslint-plugin) Updates `@typescript-eslint/parser` from 8.24.0 to 8.25.0 - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.25.0/packages/parser) Updates `prettier` from 3.5.0 to 3.5.2 - [Release notes](https://github.com/prettier/prettier/releases) - [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md) - [Commits](https://github.com/prettier/prettier/compare/3.5.0...3.5.2) Updates `tsx` from 4.19.2 to 4.19.3 - [Release notes](https://github.com/privatenumber/tsx/releases) - [Changelog](https://github.com/privatenumber/tsx/blob/master/release.config.cjs) - [Commits](https://github.com/privatenumber/tsx/compare/v4.19.2...v4.19.3) --- updated-dependencies: - dependency-name: "@types/node" dependency-type: direct:development update-type: version-update:semver-patch dependency-group: all-dependencies - dependency-name: "@types/sinon" dependency-type: direct:development update-type: version-update:semver-patch dependency-group: all-dependencies - dependency-name: "@typescript-eslint/eslint-plugin" dependency-type: direct:development update-type: version-update:semver-minor dependency-group: all-dependencies - dependency-name: "@typescript-eslint/parser" dependency-type: direct:development update-type: version-update:semver-minor dependency-group: all-dependencies - dependency-name: prettier dependency-type: direct:development update-type: version-update:semver-patch dependency-group: all-dependencies - dependency-name: tsx dependency-type: direct:development update-type: version-update:semver-patch dependency-group: all-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 858 +++++++--------------------------------------- package.json | 12 +- 2 files changed, 123 insertions(+), 747 deletions(-) diff --git a/package-lock.json b/package-lock.json index dc5d21858..3a0e543a0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,15 +23,15 @@ "@types/lcov-parse": "^1.0.2", "@types/mocha": "^10.0.10", "@types/mock-fs": "^4.13.4", - "@types/node": "^18.19.75", + "@types/node": "^18.19.76", "@types/plist": "^3.0.5", "@types/semver": "^7.5.8", - "@types/sinon": "^17.0.3", + "@types/sinon": "^17.0.4", "@types/sinon-chai": "^3.2.12", "@types/vscode": "^1.88.0", "@types/xml2js": "^0.4.14", - "@typescript-eslint/eslint-plugin": "^8.24.0", - "@typescript-eslint/parser": "^8.24.0", + "@typescript-eslint/eslint-plugin": "^8.25.0", + "@typescript-eslint/parser": "^8.25.0", "@vscode/debugprotocol": "^1.68.0", "@vscode/test-cli": "^0.0.10", "@vscode/test-electron": "^2.4.1", @@ -46,14 +46,14 @@ "mocha": "^10.8.2", "mock-fs": "^5.5.0", "node-pty": "^1.0.0", - "prettier": "^3.5.0", + "prettier": "^3.5.2", "semver": "^7.7.1", "simple-git": "^3.27.0", "sinon": "^19.0.2", "sinon-chai": "^3.7.0", "source-map-support": "^0.5.21", "strip-ansi": "^6.0.1", - "tsx": "^4.19.2", + "tsx": "^4.19.3", "typescript": "^5.7.3" }, "engines": { @@ -1129,9 +1129,9 @@ } }, "node_modules/@types/node": { - "version": "18.19.75", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.75.tgz", - "integrity": "sha512-UIksWtThob6ZVSyxcOqCLOUNg/dyO1Qvx4McgeuhrEtHTLFTf7BBhEazaE4K806FGTPtzd/2sE90qn4fVr7cyw==", + "version": "18.19.76", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.76.tgz", + "integrity": "sha512-yvR7Q9LdPz2vGpmpJX5LolrgRdWvB67MJKDPSgIIzpFbaf9a1j/f5DnLp5VDyHGMR0QZHlTr1afsD87QCXFHKw==", "dev": true, "license": "MIT", "dependencies": { @@ -1155,9 +1155,9 @@ "dev": true }, "node_modules/@types/sinon": { - "version": "17.0.3", - "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-17.0.3.tgz", - "integrity": "sha512-j3uovdn8ewky9kRBG19bOwaZbexJu/XjtkHyjvUgt4xfPFz18dcORIMqnYh66Fx3Powhcr85NT5+er3+oViapw==", + "version": "17.0.4", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-17.0.4.tgz", + "integrity": "sha512-RHnIrhfPO3+tJT0s7cFaXGZvsL4bbR3/k7z3P312qMS4JaS2Tk+KiwiLx1S0rQ56ERj00u1/BtdyVd0FY+Pdew==", "dev": true, "license": "MIT", "dependencies": { @@ -1198,17 +1198,17 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.24.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.24.0.tgz", - "integrity": "sha512-aFcXEJJCI4gUdXgoo/j9udUYIHgF23MFkg09LFz2dzEmU0+1Plk4rQWv/IYKvPHAtlkkGoB3m5e6oUp+JPsNaQ==", + "version": "8.25.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.25.0.tgz", + "integrity": "sha512-VM7bpzAe7JO/BFf40pIT1lJqS/z1F8OaSsUB3rpFJucQA4cOSuH2RVVVkFULN+En0Djgr29/jb4EQnedUo95KA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.24.0", - "@typescript-eslint/type-utils": "8.24.0", - "@typescript-eslint/utils": "8.24.0", - "@typescript-eslint/visitor-keys": "8.24.0", + "@typescript-eslint/scope-manager": "8.25.0", + "@typescript-eslint/type-utils": "8.25.0", + "@typescript-eslint/utils": "8.25.0", + "@typescript-eslint/visitor-keys": "8.25.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -1228,16 +1228,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.24.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.24.0.tgz", - "integrity": "sha512-MFDaO9CYiard9j9VepMNa9MTcqVvSny2N4hkY6roquzj8pdCBRENhErrteaQuu7Yjn1ppk0v1/ZF9CG3KIlrTA==", + "version": "8.25.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.25.0.tgz", + "integrity": "sha512-4gbs64bnbSzu4FpgMiQ1A+D+urxkoJk/kqlDJ2W//5SygaEiAP2B4GoS7TEdxgwol2el03gckFV9lJ4QOMiiHg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.24.0", - "@typescript-eslint/types": "8.24.0", - "@typescript-eslint/typescript-estree": "8.24.0", - "@typescript-eslint/visitor-keys": "8.24.0", + "@typescript-eslint/scope-manager": "8.25.0", + "@typescript-eslint/types": "8.25.0", + "@typescript-eslint/typescript-estree": "8.25.0", + "@typescript-eslint/visitor-keys": "8.25.0", "debug": "^4.3.4" }, "engines": { @@ -1253,14 +1253,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.24.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.24.0.tgz", - "integrity": "sha512-HZIX0UByphEtdVBKaQBgTDdn9z16l4aTUz8e8zPQnyxwHBtf5vtl1L+OhH+m1FGV9DrRmoDuYKqzVrvWDcDozw==", + "version": "8.25.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.25.0.tgz", + "integrity": "sha512-6PPeiKIGbgStEyt4NNXa2ru5pMzQ8OYKO1hX1z53HMomrmiSB+R5FmChgQAP1ro8jMtNawz+TRQo/cSXrauTpg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.24.0", - "@typescript-eslint/visitor-keys": "8.24.0" + "@typescript-eslint/types": "8.25.0", + "@typescript-eslint/visitor-keys": "8.25.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1271,14 +1271,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.24.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.24.0.tgz", - "integrity": "sha512-8fitJudrnY8aq0F1wMiPM1UUgiXQRJ5i8tFjq9kGfRajU+dbPyOuHbl0qRopLEidy0MwqgTHDt6CnSeXanNIwA==", + "version": "8.25.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.25.0.tgz", + "integrity": "sha512-d77dHgHWnxmXOPJuDWO4FDWADmGQkN5+tt6SFRZz/RtCWl4pHgFl3+WdYCn16+3teG09DY6XtEpf3gGD0a186g==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.24.0", - "@typescript-eslint/utils": "8.24.0", + "@typescript-eslint/typescript-estree": "8.25.0", + "@typescript-eslint/utils": "8.25.0", "debug": "^4.3.4", "ts-api-utils": "^2.0.1" }, @@ -1295,9 +1295,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.24.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.24.0.tgz", - "integrity": "sha512-VacJCBTyje7HGAw7xp11q439A+zeGG0p0/p2zsZwpnMzjPB5WteaWqt4g2iysgGFafrqvyLWqq6ZPZAOCoefCw==", + "version": "8.25.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.25.0.tgz", + "integrity": "sha512-+vUe0Zb4tkNgznQwicsvLUJgZIRs6ITeWSCclX1q85pR1iOiaj+4uZJIUp//Z27QWu5Cseiw3O3AR8hVpax7Aw==", "dev": true, "license": "MIT", "engines": { @@ -1309,14 +1309,14 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.24.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.24.0.tgz", - "integrity": "sha512-ITjYcP0+8kbsvT9bysygfIfb+hBj6koDsu37JZG7xrCiy3fPJyNmfVtaGsgTUSEuTzcvME5YI5uyL5LD1EV5ZQ==", + "version": "8.25.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.25.0.tgz", + "integrity": "sha512-ZPaiAKEZ6Blt/TPAx5Ot0EIB/yGtLI2EsGoY6F7XKklfMxYQyvtL+gT/UCqkMzO0BVFHLDlzvFqQzurYahxv9Q==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.24.0", - "@typescript-eslint/visitor-keys": "8.24.0", + "@typescript-eslint/types": "8.25.0", + "@typescript-eslint/visitor-keys": "8.25.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -1362,16 +1362,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.24.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.24.0.tgz", - "integrity": "sha512-07rLuUBElvvEb1ICnafYWr4hk8/U7X9RDCOqd9JcAMtjh/9oRmcfN4yGzbPVirgMR0+HLVHehmu19CWeh7fsmQ==", + "version": "8.25.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.25.0.tgz", + "integrity": "sha512-syqRbrEv0J1wywiLsK60XzHnQe/kRViI3zwFALrNEgnntn1l24Ra2KvOAWwWbWZ1lBZxZljPDGOq967dsl6fkA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.24.0", - "@typescript-eslint/types": "8.24.0", - "@typescript-eslint/typescript-estree": "8.24.0" + "@typescript-eslint/scope-manager": "8.25.0", + "@typescript-eslint/types": "8.25.0", + "@typescript-eslint/typescript-estree": "8.25.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1386,13 +1386,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.24.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.24.0.tgz", - "integrity": "sha512-kArLq83QxGLbuHrTMoOEWO+l2MwsNS2TGISEdx8xgqpkbytB07XmlQyQdNDrCc1ecSqx0cnmhGvpX+VBwqqSkg==", + "version": "8.25.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.25.0.tgz", + "integrity": "sha512-kCYXKAum9CecGVHGij7muybDfTS2sD3t0L4bJsEZLkyrXUImiCTq1M3LG2SRtOhiHFwMR9wAFplpT6XHYjTkwQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.24.0", + "@typescript-eslint/types": "8.25.0", "eslint-visitor-keys": "^4.2.0" }, "engines": { @@ -4948,9 +4948,9 @@ } }, "node_modules/prettier": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.0.tgz", - "integrity": "sha512-quyMrVt6svPS7CjQ9gKb3GLEX/rl3BCL2oa/QkNcXv4YNVBC9olt3s+H7ukto06q7B1Qz46PbrKLO34PR6vXcA==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.2.tgz", + "integrity": "sha512-lc6npv5PH7hVqozBR7lkBNOGXV9vMwROAPlumdBkX0wTbbzPu/U1hk5yL8p2pt4Xoc+2mkT8t/sow2YrV/M5qg==", "dev": true, "license": "MIT", "bin": { @@ -5715,12 +5715,13 @@ "dev": true }, "node_modules/tsx": { - "version": "4.19.2", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.2.tgz", - "integrity": "sha512-pOUl6Vo2LUq/bSa8S5q7b91cgNSjctn9ugq/+Mvow99qW6x/UZYwzxy/3NmqoT66eHYfCVvFvACC58UBPFf28g==", + "version": "4.19.3", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.3.tgz", + "integrity": "sha512-4H8vUNGNjQ4V2EOoGw005+c+dGuPSnhpPBPHBtsZdGZBk/iJb4kguGlPWaZTZ3q5nMtFOEsY0nRDlh9PJyd6SQ==", "dev": true, + "license": "MIT", "dependencies": { - "esbuild": "~0.23.0", + "esbuild": "~0.25.0", "get-tsconfig": "^4.7.5" }, "bin": { @@ -5733,429 +5734,6 @@ "fsevents": "~2.3.3" } }, - "node_modules/tsx/node_modules/@esbuild/aix-ppc64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.1.tgz", - "integrity": "sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/android-arm": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.23.1.tgz", - "integrity": "sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/android-arm64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.23.1.tgz", - "integrity": "sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/android-x64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.23.1.tgz", - "integrity": "sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/darwin-arm64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.1.tgz", - "integrity": "sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/darwin-x64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.1.tgz", - "integrity": "sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/freebsd-arm64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.1.tgz", - "integrity": "sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/freebsd-x64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.23.1.tgz", - "integrity": "sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-arm": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.23.1.tgz", - "integrity": "sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-arm64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.23.1.tgz", - "integrity": "sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-ia32": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.23.1.tgz", - "integrity": "sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-loong64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.23.1.tgz", - "integrity": "sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==", - "cpu": [ - "loong64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-mips64el": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.23.1.tgz", - "integrity": "sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==", - "cpu": [ - "mips64el" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-ppc64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.23.1.tgz", - "integrity": "sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-riscv64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.23.1.tgz", - "integrity": "sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-s390x": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.23.1.tgz", - "integrity": "sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==", - "cpu": [ - "s390x" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-x64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.1.tgz", - "integrity": "sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/netbsd-x64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.1.tgz", - "integrity": "sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/openbsd-arm64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.1.tgz", - "integrity": "sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/openbsd-x64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.23.1.tgz", - "integrity": "sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/sunos-x64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.1.tgz", - "integrity": "sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/win32-arm64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.23.1.tgz", - "integrity": "sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/win32-ia32": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.23.1.tgz", - "integrity": "sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/win32-x64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.23.1.tgz", - "integrity": "sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/esbuild": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.1.tgz", - "integrity": "sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==", - "dev": true, - "hasInstallScript": true, - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.23.1", - "@esbuild/android-arm": "0.23.1", - "@esbuild/android-arm64": "0.23.1", - "@esbuild/android-x64": "0.23.1", - "@esbuild/darwin-arm64": "0.23.1", - "@esbuild/darwin-x64": "0.23.1", - "@esbuild/freebsd-arm64": "0.23.1", - "@esbuild/freebsd-x64": "0.23.1", - "@esbuild/linux-arm": "0.23.1", - "@esbuild/linux-arm64": "0.23.1", - "@esbuild/linux-ia32": "0.23.1", - "@esbuild/linux-loong64": "0.23.1", - "@esbuild/linux-mips64el": "0.23.1", - "@esbuild/linux-ppc64": "0.23.1", - "@esbuild/linux-riscv64": "0.23.1", - "@esbuild/linux-s390x": "0.23.1", - "@esbuild/linux-x64": "0.23.1", - "@esbuild/netbsd-x64": "0.23.1", - "@esbuild/openbsd-arm64": "0.23.1", - "@esbuild/openbsd-x64": "0.23.1", - "@esbuild/sunos-x64": "0.23.1", - "@esbuild/win32-arm64": "0.23.1", - "@esbuild/win32-ia32": "0.23.1", - "@esbuild/win32-x64": "0.23.1" - } - }, "node_modules/tunnel": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", @@ -7221,9 +6799,9 @@ } }, "@types/node": { - "version": "18.19.75", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.75.tgz", - "integrity": "sha512-UIksWtThob6ZVSyxcOqCLOUNg/dyO1Qvx4McgeuhrEtHTLFTf7BBhEazaE4K806FGTPtzd/2sE90qn4fVr7cyw==", + "version": "18.19.76", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.76.tgz", + "integrity": "sha512-yvR7Q9LdPz2vGpmpJX5LolrgRdWvB67MJKDPSgIIzpFbaf9a1j/f5DnLp5VDyHGMR0QZHlTr1afsD87QCXFHKw==", "dev": true, "requires": { "undici-types": "~5.26.4" @@ -7246,9 +6824,9 @@ "dev": true }, "@types/sinon": { - "version": "17.0.3", - "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-17.0.3.tgz", - "integrity": "sha512-j3uovdn8ewky9kRBG19bOwaZbexJu/XjtkHyjvUgt4xfPFz18dcORIMqnYh66Fx3Powhcr85NT5+er3+oViapw==", + "version": "17.0.4", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-17.0.4.tgz", + "integrity": "sha512-RHnIrhfPO3+tJT0s7cFaXGZvsL4bbR3/k7z3P312qMS4JaS2Tk+KiwiLx1S0rQ56ERj00u1/BtdyVd0FY+Pdew==", "dev": true, "requires": { "@types/sinonjs__fake-timers": "*" @@ -7286,16 +6864,16 @@ } }, "@typescript-eslint/eslint-plugin": { - "version": "8.24.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.24.0.tgz", - "integrity": "sha512-aFcXEJJCI4gUdXgoo/j9udUYIHgF23MFkg09LFz2dzEmU0+1Plk4rQWv/IYKvPHAtlkkGoB3m5e6oUp+JPsNaQ==", + "version": "8.25.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.25.0.tgz", + "integrity": "sha512-VM7bpzAe7JO/BFf40pIT1lJqS/z1F8OaSsUB3rpFJucQA4cOSuH2RVVVkFULN+En0Djgr29/jb4EQnedUo95KA==", "dev": true, "requires": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.24.0", - "@typescript-eslint/type-utils": "8.24.0", - "@typescript-eslint/utils": "8.24.0", - "@typescript-eslint/visitor-keys": "8.24.0", + "@typescript-eslint/scope-manager": "8.25.0", + "@typescript-eslint/type-utils": "8.25.0", + "@typescript-eslint/utils": "8.25.0", + "@typescript-eslint/visitor-keys": "8.25.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -7303,54 +6881,54 @@ } }, "@typescript-eslint/parser": { - "version": "8.24.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.24.0.tgz", - "integrity": "sha512-MFDaO9CYiard9j9VepMNa9MTcqVvSny2N4hkY6roquzj8pdCBRENhErrteaQuu7Yjn1ppk0v1/ZF9CG3KIlrTA==", + "version": "8.25.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.25.0.tgz", + "integrity": "sha512-4gbs64bnbSzu4FpgMiQ1A+D+urxkoJk/kqlDJ2W//5SygaEiAP2B4GoS7TEdxgwol2el03gckFV9lJ4QOMiiHg==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "8.24.0", - "@typescript-eslint/types": "8.24.0", - "@typescript-eslint/typescript-estree": "8.24.0", - "@typescript-eslint/visitor-keys": "8.24.0", + "@typescript-eslint/scope-manager": "8.25.0", + "@typescript-eslint/types": "8.25.0", + "@typescript-eslint/typescript-estree": "8.25.0", + "@typescript-eslint/visitor-keys": "8.25.0", "debug": "^4.3.4" } }, "@typescript-eslint/scope-manager": { - "version": "8.24.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.24.0.tgz", - "integrity": "sha512-HZIX0UByphEtdVBKaQBgTDdn9z16l4aTUz8e8zPQnyxwHBtf5vtl1L+OhH+m1FGV9DrRmoDuYKqzVrvWDcDozw==", + "version": "8.25.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.25.0.tgz", + "integrity": "sha512-6PPeiKIGbgStEyt4NNXa2ru5pMzQ8OYKO1hX1z53HMomrmiSB+R5FmChgQAP1ro8jMtNawz+TRQo/cSXrauTpg==", "dev": true, "requires": { - "@typescript-eslint/types": "8.24.0", - "@typescript-eslint/visitor-keys": "8.24.0" + "@typescript-eslint/types": "8.25.0", + "@typescript-eslint/visitor-keys": "8.25.0" } }, "@typescript-eslint/type-utils": { - "version": "8.24.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.24.0.tgz", - "integrity": "sha512-8fitJudrnY8aq0F1wMiPM1UUgiXQRJ5i8tFjq9kGfRajU+dbPyOuHbl0qRopLEidy0MwqgTHDt6CnSeXanNIwA==", + "version": "8.25.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.25.0.tgz", + "integrity": "sha512-d77dHgHWnxmXOPJuDWO4FDWADmGQkN5+tt6SFRZz/RtCWl4pHgFl3+WdYCn16+3teG09DY6XtEpf3gGD0a186g==", "dev": true, "requires": { - "@typescript-eslint/typescript-estree": "8.24.0", - "@typescript-eslint/utils": "8.24.0", + "@typescript-eslint/typescript-estree": "8.25.0", + "@typescript-eslint/utils": "8.25.0", "debug": "^4.3.4", "ts-api-utils": "^2.0.1" } }, "@typescript-eslint/types": { - "version": "8.24.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.24.0.tgz", - "integrity": "sha512-VacJCBTyje7HGAw7xp11q439A+zeGG0p0/p2zsZwpnMzjPB5WteaWqt4g2iysgGFafrqvyLWqq6ZPZAOCoefCw==", + "version": "8.25.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.25.0.tgz", + "integrity": "sha512-+vUe0Zb4tkNgznQwicsvLUJgZIRs6ITeWSCclX1q85pR1iOiaj+4uZJIUp//Z27QWu5Cseiw3O3AR8hVpax7Aw==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "8.24.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.24.0.tgz", - "integrity": "sha512-ITjYcP0+8kbsvT9bysygfIfb+hBj6koDsu37JZG7xrCiy3fPJyNmfVtaGsgTUSEuTzcvME5YI5uyL5LD1EV5ZQ==", + "version": "8.25.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.25.0.tgz", + "integrity": "sha512-ZPaiAKEZ6Blt/TPAx5Ot0EIB/yGtLI2EsGoY6F7XKklfMxYQyvtL+gT/UCqkMzO0BVFHLDlzvFqQzurYahxv9Q==", "dev": true, "requires": { - "@typescript-eslint/types": "8.24.0", - "@typescript-eslint/visitor-keys": "8.24.0", + "@typescript-eslint/types": "8.25.0", + "@typescript-eslint/visitor-keys": "8.25.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -7380,24 +6958,24 @@ } }, "@typescript-eslint/utils": { - "version": "8.24.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.24.0.tgz", - "integrity": "sha512-07rLuUBElvvEb1ICnafYWr4hk8/U7X9RDCOqd9JcAMtjh/9oRmcfN4yGzbPVirgMR0+HLVHehmu19CWeh7fsmQ==", + "version": "8.25.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.25.0.tgz", + "integrity": "sha512-syqRbrEv0J1wywiLsK60XzHnQe/kRViI3zwFALrNEgnntn1l24Ra2KvOAWwWbWZ1lBZxZljPDGOq967dsl6fkA==", "dev": true, "requires": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.24.0", - "@typescript-eslint/types": "8.24.0", - "@typescript-eslint/typescript-estree": "8.24.0" + "@typescript-eslint/scope-manager": "8.25.0", + "@typescript-eslint/types": "8.25.0", + "@typescript-eslint/typescript-estree": "8.25.0" } }, "@typescript-eslint/visitor-keys": { - "version": "8.24.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.24.0.tgz", - "integrity": "sha512-kArLq83QxGLbuHrTMoOEWO+l2MwsNS2TGISEdx8xgqpkbytB07XmlQyQdNDrCc1ecSqx0cnmhGvpX+VBwqqSkg==", + "version": "8.25.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.25.0.tgz", + "integrity": "sha512-kCYXKAum9CecGVHGij7muybDfTS2sD3t0L4bJsEZLkyrXUImiCTq1M3LG2SRtOhiHFwMR9wAFplpT6XHYjTkwQ==", "dev": true, "requires": { - "@typescript-eslint/types": "8.24.0", + "@typescript-eslint/types": "8.25.0", "eslint-visitor-keys": "^4.2.0" }, "dependencies": { @@ -10006,9 +9584,9 @@ "dev": true }, "prettier": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.0.tgz", - "integrity": "sha512-quyMrVt6svPS7CjQ9gKb3GLEX/rl3BCL2oa/QkNcXv4YNVBC9olt3s+H7ukto06q7B1Qz46PbrKLO34PR6vXcA==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.2.tgz", + "integrity": "sha512-lc6npv5PH7hVqozBR7lkBNOGXV9vMwROAPlumdBkX0wTbbzPu/U1hk5yL8p2pt4Xoc+2mkT8t/sow2YrV/M5qg==", "dev": true }, "process-nextick-args": { @@ -10538,216 +10116,14 @@ "dev": true }, "tsx": { - "version": "4.19.2", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.2.tgz", - "integrity": "sha512-pOUl6Vo2LUq/bSa8S5q7b91cgNSjctn9ugq/+Mvow99qW6x/UZYwzxy/3NmqoT66eHYfCVvFvACC58UBPFf28g==", + "version": "4.19.3", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.3.tgz", + "integrity": "sha512-4H8vUNGNjQ4V2EOoGw005+c+dGuPSnhpPBPHBtsZdGZBk/iJb4kguGlPWaZTZ3q5nMtFOEsY0nRDlh9PJyd6SQ==", "dev": true, "requires": { - "esbuild": "~0.23.0", + "esbuild": "~0.25.0", "fsevents": "~2.3.3", "get-tsconfig": "^4.7.5" - }, - "dependencies": { - "@esbuild/aix-ppc64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.1.tgz", - "integrity": "sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==", - "dev": true, - "optional": true - }, - "@esbuild/android-arm": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.23.1.tgz", - "integrity": "sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==", - "dev": true, - "optional": true - }, - "@esbuild/android-arm64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.23.1.tgz", - "integrity": "sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==", - "dev": true, - "optional": true - }, - "@esbuild/android-x64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.23.1.tgz", - "integrity": "sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==", - "dev": true, - "optional": true - }, - "@esbuild/darwin-arm64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.1.tgz", - "integrity": "sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==", - "dev": true, - "optional": true - }, - "@esbuild/darwin-x64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.1.tgz", - "integrity": "sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==", - "dev": true, - "optional": true - }, - "@esbuild/freebsd-arm64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.1.tgz", - "integrity": "sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==", - "dev": true, - "optional": true - }, - "@esbuild/freebsd-x64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.23.1.tgz", - "integrity": "sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==", - "dev": true, - "optional": true - }, - "@esbuild/linux-arm": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.23.1.tgz", - "integrity": "sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==", - "dev": true, - "optional": true - }, - "@esbuild/linux-arm64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.23.1.tgz", - "integrity": "sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==", - "dev": true, - "optional": true - }, - "@esbuild/linux-ia32": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.23.1.tgz", - "integrity": "sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==", - "dev": true, - "optional": true - }, - "@esbuild/linux-loong64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.23.1.tgz", - "integrity": "sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==", - "dev": true, - "optional": true - }, - "@esbuild/linux-mips64el": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.23.1.tgz", - "integrity": "sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==", - "dev": true, - "optional": true - }, - "@esbuild/linux-ppc64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.23.1.tgz", - "integrity": "sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==", - "dev": true, - "optional": true - }, - "@esbuild/linux-riscv64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.23.1.tgz", - "integrity": "sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==", - "dev": true, - "optional": true - }, - "@esbuild/linux-s390x": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.23.1.tgz", - "integrity": "sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==", - "dev": true, - "optional": true - }, - "@esbuild/linux-x64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.1.tgz", - "integrity": "sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==", - "dev": true, - "optional": true - }, - "@esbuild/netbsd-x64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.1.tgz", - "integrity": "sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==", - "dev": true, - "optional": true - }, - "@esbuild/openbsd-arm64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.1.tgz", - "integrity": "sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==", - "dev": true, - "optional": true - }, - "@esbuild/openbsd-x64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.23.1.tgz", - "integrity": "sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==", - "dev": true, - "optional": true - }, - "@esbuild/sunos-x64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.1.tgz", - "integrity": "sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==", - "dev": true, - "optional": true - }, - "@esbuild/win32-arm64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.23.1.tgz", - "integrity": "sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==", - "dev": true, - "optional": true - }, - "@esbuild/win32-ia32": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.23.1.tgz", - "integrity": "sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==", - "dev": true, - "optional": true - }, - "@esbuild/win32-x64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.23.1.tgz", - "integrity": "sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==", - "dev": true, - "optional": true - }, - "esbuild": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.1.tgz", - "integrity": "sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==", - "dev": true, - "requires": { - "@esbuild/aix-ppc64": "0.23.1", - "@esbuild/android-arm": "0.23.1", - "@esbuild/android-arm64": "0.23.1", - "@esbuild/android-x64": "0.23.1", - "@esbuild/darwin-arm64": "0.23.1", - "@esbuild/darwin-x64": "0.23.1", - "@esbuild/freebsd-arm64": "0.23.1", - "@esbuild/freebsd-x64": "0.23.1", - "@esbuild/linux-arm": "0.23.1", - "@esbuild/linux-arm64": "0.23.1", - "@esbuild/linux-ia32": "0.23.1", - "@esbuild/linux-loong64": "0.23.1", - "@esbuild/linux-mips64el": "0.23.1", - "@esbuild/linux-ppc64": "0.23.1", - "@esbuild/linux-riscv64": "0.23.1", - "@esbuild/linux-s390x": "0.23.1", - "@esbuild/linux-x64": "0.23.1", - "@esbuild/netbsd-x64": "0.23.1", - "@esbuild/openbsd-arm64": "0.23.1", - "@esbuild/openbsd-x64": "0.23.1", - "@esbuild/sunos-x64": "0.23.1", - "@esbuild/win32-arm64": "0.23.1", - "@esbuild/win32-ia32": "0.23.1", - "@esbuild/win32-x64": "0.23.1" - } - } } }, "tunnel": { diff --git a/package.json b/package.json index af7d020b5..a568e407b 100644 --- a/package.json +++ b/package.json @@ -1495,15 +1495,15 @@ "@types/lcov-parse": "^1.0.2", "@types/mocha": "^10.0.10", "@types/mock-fs": "^4.13.4", - "@types/node": "^18.19.75", + "@types/node": "^18.19.76", "@types/plist": "^3.0.5", "@types/semver": "^7.5.8", - "@types/sinon": "^17.0.3", + "@types/sinon": "^17.0.4", "@types/sinon-chai": "^3.2.12", "@types/vscode": "^1.88.0", "@types/xml2js": "^0.4.14", - "@typescript-eslint/eslint-plugin": "^8.24.0", - "@typescript-eslint/parser": "^8.24.0", + "@typescript-eslint/eslint-plugin": "^8.25.0", + "@typescript-eslint/parser": "^8.25.0", "@vscode/debugprotocol": "^1.68.0", "@vscode/test-cli": "^0.0.10", "@vscode/test-electron": "^2.4.1", @@ -1518,14 +1518,14 @@ "mocha": "^10.8.2", "mock-fs": "^5.5.0", "node-pty": "^1.0.0", - "prettier": "^3.5.0", + "prettier": "^3.5.2", "semver": "^7.7.1", "simple-git": "^3.27.0", "sinon": "^19.0.2", "sinon-chai": "^3.7.0", "source-map-support": "^0.5.21", "strip-ansi": "^6.0.1", - "tsx": "^4.19.2", + "tsx": "^4.19.3", "typescript": "^5.7.3" }, "dependencies": { From c6dfadbf104613cf7c2d81e6671ba70fc4b80ef5 Mon Sep 17 00:00:00 2001 From: Matthew Bastien Date: Tue, 25 Feb 2025 16:07:07 +0000 Subject: [PATCH 20/86] use LLDB DAP extension to launch debug sessions (#1384) --- package.json | 3 +- src/debugger/debugAdapter.ts | 2 +- src/debugger/debugAdapterFactory.ts | 47 +------------------ .../debugger/debugAdapterFactory.test.ts | 44 +++-------------- 4 files changed, 11 insertions(+), 85 deletions(-) diff --git a/package.json b/package.json index a568e407b..d843f31ce 100644 --- a/package.json +++ b/package.json @@ -1459,7 +1459,8 @@ ] }, "extensionDependencies": [ - "vadimcn.vscode-lldb" + "vadimcn.vscode-lldb", + "llvm-vs-code-extensions.lldb-dap" ], "scripts": { "vscode:prepublish": "npm run bundle", diff --git a/src/debugger/debugAdapter.ts b/src/debugger/debugAdapter.ts index 3547b1850..9ae96b0c1 100644 --- a/src/debugger/debugAdapter.ts +++ b/src/debugger/debugAdapter.ts @@ -27,7 +27,7 @@ export const SWIFT_LAUNCH_CONFIG_TYPE = "swift"; * LLDB launch requests. */ export const enum LaunchConfigType { - LLDB_DAP = "swift", + LLDB_DAP = "lldb-dap", CODE_LLDB = "lldb", } diff --git a/src/debugger/debugAdapterFactory.ts b/src/debugger/debugAdapterFactory.ts index 01845b464..be4ffa6d6 100644 --- a/src/debugger/debugAdapterFactory.ts +++ b/src/debugger/debugAdapterFactory.ts @@ -52,54 +52,10 @@ function registerLLDBDebugAdapter( toolchain: SwiftToolchain, outputChannel: SwiftOutputChannel ): vscode.Disposable { - const debugAdpaterFactory = vscode.debug.registerDebugAdapterDescriptorFactory( - SWIFT_LAUNCH_CONFIG_TYPE, - new LLDBDebugAdapterExecutableFactory(toolchain, outputChannel) - ); - - const debugConfigProvider = vscode.debug.registerDebugConfigurationProvider( + return vscode.debug.registerDebugConfigurationProvider( SWIFT_LAUNCH_CONFIG_TYPE, new LLDBDebugConfigurationProvider(process.platform, toolchain, outputChannel) ); - - return { - dispose: () => { - debugConfigProvider.dispose(); - debugAdpaterFactory.dispose(); - }, - }; -} - -/** - * A factory class for creating and providing the executable descriptor for the LLDB Debug Adapter. - * This class implements the vscode.DebugAdapterDescriptorFactory interface and is responsible for - * determining the path to the LLDB Debug Adapter executable and ensuring it exists before launching - * a debug session. - * - * This class uses the workspace context to: - * - Resolve the path to the debug adapter executable. - * - Verify that the debug adapter exists in the toolchain. - * - * The main method of this class, `createDebugAdapterDescriptor`, is invoked by VS Code to supply - * the debug adapter executable when a debug session is started. The executable parameter by default - * will be provided in package.json > contributes > debuggers > program if defined, but since we will - * determine the executable via the toolchain anyway, this is now redundant and will be ignored. - * - * @implements {vscode.DebugAdapterDescriptorFactory} - */ -export class LLDBDebugAdapterExecutableFactory implements vscode.DebugAdapterDescriptorFactory { - private toolchain: SwiftToolchain; - private outputChannel: SwiftOutputChannel; - - constructor(toolchain: SwiftToolchain, outputChannel: SwiftOutputChannel) { - this.toolchain = toolchain; - this.outputChannel = outputChannel; - } - - async createDebugAdapterDescriptor(): Promise { - const path = await DebugAdapter.getLLDBDebugAdapterPath(this.toolchain); - return new vscode.DebugAdapterExecutable(path, [], {}); - } } /** Provide configurations for lldb-vscode/lldb-dap @@ -150,6 +106,7 @@ export class LLDBDebugConfigurationProvider implements vscode.DebugConfiguration ); return undefined; } + launchConfig.debugAdapterExecutable = lldbDapPath; } return launchConfig; diff --git a/test/unit-tests/debugger/debugAdapterFactory.test.ts b/test/unit-tests/debugger/debugAdapterFactory.test.ts index b410e30a5..ed50195ca 100644 --- a/test/unit-tests/debugger/debugAdapterFactory.test.ts +++ b/test/unit-tests/debugger/debugAdapterFactory.test.ts @@ -14,10 +14,7 @@ import * as vscode from "vscode"; import { expect } from "chai"; -import { - LLDBDebugAdapterExecutableFactory, - LLDBDebugConfigurationProvider, -} from "../../../src/debugger/debugAdapterFactory"; +import { LLDBDebugConfigurationProvider } from "../../../src/debugger/debugAdapterFactory"; import { Version } from "../../../src/utilities/version"; import { mockGlobalObject, @@ -28,11 +25,7 @@ import { mockFn, } from "../../MockUtils"; import * as mockFS from "mock-fs"; -import { - DebugAdapter, - LaunchConfigType, - SWIFT_LAUNCH_CONFIG_TYPE, -} from "../../../src/debugger/debugAdapter"; +import { LaunchConfigType, SWIFT_LAUNCH_CONFIG_TYPE } from "../../../src/debugger/debugAdapter"; import * as lldb from "../../../src/debugger/lldb"; import { SwiftToolchain } from "../../../src/toolchain/toolchain"; import { SwiftOutputChannel } from "../../../src/ui/SwiftOutputChannel"; @@ -40,34 +33,6 @@ import * as debugAdapter from "../../../src/debugger/debugAdapter"; import { Result } from "../../../src/utilities/result"; import configuration from "../../../src/configuration"; -suite("LLDBDebugAdapterExecutableFactory Tests", () => { - const mockDebugAdapter = mockGlobalModule(DebugAdapter); - let mockToolchain: MockedObject; - let mockOutputChannel: MockedObject; - - setup(() => { - mockToolchain = mockObject({}); - mockOutputChannel = mockObject({ - log: mockFn(), - }); - }); - - test("should return a DebugAdapterExecutable with the path to lldb-dap", async () => { - const toolchainPath = "/path/to/debug/adapter"; - - mockDebugAdapter.getLLDBDebugAdapterPath.resolves(toolchainPath); - - const factory = new LLDBDebugAdapterExecutableFactory( - instance(mockToolchain), - instance(mockOutputChannel) - ); - const result = await factory.createDebugAdapterDescriptor(); - - expect(result).to.be.instanceOf(vscode.DebugAdapterExecutable); - expect(result).to.have.property("command").that.equals(toolchainPath); - }); -}); - suite("LLDBDebugConfigurationProvider Tests", () => { let mockToolchain: MockedObject; let mockOutputChannel: MockedObject; @@ -188,7 +153,10 @@ suite("LLDBDebugConfigurationProvider Tests", () => { request: "launch", program: "${workspaceFolder}/.build/debug/executable", }); - expect(launchConfig).to.containSubset({ type: LaunchConfigType.LLDB_DAP }); + expect(launchConfig).to.containSubset({ + type: LaunchConfigType.LLDB_DAP, + debugAdapterExecutable: "/path/to/lldb-dap", + }); }); test("fails if the path to lldb-dap could not be found", async () => { From da1d5fbda6f663a86e309b357f6a2648ecc21123 Mon Sep 17 00:00:00 2001 From: Matthew Bastien Date: Tue, 25 Feb 2025 16:47:14 +0000 Subject: [PATCH 21/86] Remove dependency on CodeLLDB (#1385) * remove dependency on CodeLLDB * install CodeLLDB during integration tests * change wording in installation dialog Co-authored-by: Paul LeMarquand --------- Co-authored-by: Paul LeMarquand --- package.json | 1 - src/debugger/debugAdapterFactory.ts | 47 ++++++++++++++++--- .../utilities/testutilities.ts | 7 +++ .../debugger/debugAdapterFactory.test.ts | 27 ++++++++++- 4 files changed, 74 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index d843f31ce..112ae5d89 100644 --- a/package.json +++ b/package.json @@ -1459,7 +1459,6 @@ ] }, "extensionDependencies": [ - "vadimcn.vscode-lldb", "llvm-vs-code-extensions.lldb-dap" ], "scripts": { diff --git a/src/debugger/debugAdapterFactory.ts b/src/debugger/debugAdapterFactory.ts index be4ffa6d6..10ce7458a 100644 --- a/src/debugger/debugAdapterFactory.ts +++ b/src/debugger/debugAdapterFactory.ts @@ -92,8 +92,14 @@ export class LLDBDebugConfigurationProvider implements vscode.DebugConfiguration launchConfig.type = DebugAdapter.getLaunchConfigType(this.toolchain.swiftVersion); if (launchConfig.type === LaunchConfigType.CODE_LLDB) { launchConfig.sourceLanguages = ["swift"]; - // Prompt the user to update CodeLLDB settings if necessary - await this.promptForCodeLldbSettings(); + if (!vscode.extensions.getExtension("vadimcn.vscode-lldb")) { + if (!(await this.promptToInstallCodeLLDB())) { + return undefined; + } + } + if (!(await this.promptForCodeLldbSettings())) { + return undefined; + } } else if (launchConfig.type === LaunchConfigType.LLDB_DAP) { if (launchConfig.env) { launchConfig.env = this.convertEnvironmentVariables(launchConfig.env); @@ -112,7 +118,36 @@ export class LLDBDebugConfigurationProvider implements vscode.DebugConfiguration return launchConfig; } - private async promptForCodeLldbSettings(): Promise { + private async promptToInstallCodeLLDB(): Promise { + const selection = await vscode.window.showErrorMessage( + "The CodeLLDB extension is required to debug with Swift toolchains prior to Swift 6.0. Please install the extension to continue.", + { modal: true }, + "Install CodeLLDB", + "View Extension" + ); + switch (selection) { + case "Install CodeLLDB": + await vscode.commands.executeCommand( + "workbench.extensions.installExtension", + "vadimcn.vscode-lldb" + ); + return true; + case "View Extension": + await vscode.commands.executeCommand( + "workbench.extensions.search", + "@id:vadimcn.vscode-lldb" + ); + await vscode.commands.executeCommand( + "workbench.extensions.action.showReleasedVersion", + "vadimcn.vscode-lldb" + ); + return false; + case undefined: + return false; + } + } + + private async promptForCodeLldbSettings(): Promise { const libLldbPathResult = await getLLDBLibPath(this.toolchain); if (!libLldbPathResult.success) { const errorMessage = `Error: ${getErrorDescription(libLldbPathResult.failure)}`; @@ -120,7 +155,7 @@ export class LLDBDebugConfigurationProvider implements vscode.DebugConfiguration `Failed to setup CodeLLDB for debugging of Swift code. Debugging may produce unexpected results. ${errorMessage}` ); this.outputChannel.log(`Failed to setup CodeLLDB: ${errorMessage}`); - return; + return true; } const libLldbPath = libLldbPathResult.success; const lldbConfig = vscode.workspace.getConfiguration("lldb"); @@ -128,7 +163,7 @@ export class LLDBDebugConfigurationProvider implements vscode.DebugConfiguration lldbConfig.get("library") === libLldbPath && lldbConfig.get("launch.expressions") === "native" ) { - return; + return true; } let userSelection: "Global" | "Workspace" | "Run Anyway" | undefined = undefined; switch (configuration.debugger.setupCodeLLDB) { @@ -177,7 +212,7 @@ export class LLDBDebugConfigurationProvider implements vscode.DebugConfiguration ); break; } - return; + return true; } private convertEnvironmentVariables(map: { [key: string]: string }): string[] { diff --git a/test/integration-tests/utilities/testutilities.ts b/test/integration-tests/utilities/testutilities.ts index 6393e39a5..3414b1954 100644 --- a/test/integration-tests/utilities/testutilities.ts +++ b/test/integration-tests/utilities/testutilities.ts @@ -55,6 +55,13 @@ const extensionBootstrapper = (() => { let autoTeardown: void | (() => Promise); let restoreSettings: (() => Promise) | undefined; before(async function () { + // Make sure that CodeLLDB is installed for debugging related tests + if (!vscode.extensions.getExtension("vadimcn.vscode-lldb")) { + await vscode.commands.executeCommand( + "workbench.extensions.installExtension", + "vadimcn.vscode-lldb" + ); + } // Always activate the extension. If no test assets are provided, // default to adding `defaultPackage` to the workspace. workspaceContext = await extensionBootstrapper.activateExtension( diff --git a/test/unit-tests/debugger/debugAdapterFactory.test.ts b/test/unit-tests/debugger/debugAdapterFactory.test.ts index ed50195ca..81292548c 100644 --- a/test/unit-tests/debugger/debugAdapterFactory.test.ts +++ b/test/unit-tests/debugger/debugAdapterFactory.test.ts @@ -51,8 +51,11 @@ suite("LLDBDebugConfigurationProvider Tests", () => { const mockLLDB = mockGlobalModule(lldb); const mockDebuggerConfig = mockGlobalObject(configuration, "debugger"); const mockWorkspace = mockGlobalObject(vscode, "workspace"); + const mockExtensions = mockGlobalObject(vscode, "extensions"); + const mockCommands = mockGlobalObject(vscode, "commands"); setup(() => { + mockExtensions.getExtension.returns(mockObject>({})); mockLldbConfiguration = mockObject({ get: mockFn(s => { s.withArgs("library").returns("/path/to/liblldb.dyLib"); @@ -81,6 +84,28 @@ suite("LLDBDebugConfigurationProvider Tests", () => { expect(launchConfig).to.containSubset({ type: LaunchConfigType.CODE_LLDB }); }); + test("prompts the user to install CodeLLDB if it isn't found", async () => { + mockExtensions.getExtension.returns(undefined); + mockWindow.showErrorMessage.resolves("Install CodeLLDB" as any); + const configProvider = new LLDBDebugConfigurationProvider( + "darwin", + instance(mockToolchain), + instance(mockOutputChannel) + ); + await expect( + configProvider.resolveDebugConfiguration(undefined, { + name: "Test Launch Config", + type: SWIFT_LAUNCH_CONFIG_TYPE, + request: "launch", + program: "${workspaceFolder}/.build/debug/executable", + }) + ).to.eventually.not.be.undefined; + expect(mockCommands.executeCommand).to.have.been.calledWith( + "workbench.extensions.installExtension", + "vadimcn.vscode-lldb" + ); + }); + test("prompts the user to update CodeLLDB settings if they aren't configured yet", async () => { mockLldbConfiguration.get.withArgs("library").returns(undefined); mockWindow.showInformationMessage.resolves("Global" as any); @@ -96,7 +121,7 @@ suite("LLDBDebugConfigurationProvider Tests", () => { request: "launch", program: "${workspaceFolder}/.build/debug/executable", }) - ).to.eventually.be.an("object"); + ).to.eventually.not.be.undefined; expect(mockWindow.showInformationMessage).to.have.been.calledOnce; expect(mockLldbConfiguration.update).to.have.been.calledWith( "library", From eaabdf121b93b0de113d4d62816fb9ae569cbe15 Mon Sep 17 00:00:00 2001 From: Paul LeMarquand Date: Tue, 25 Feb 2025 13:45:15 -0500 Subject: [PATCH 22/86] Fix missing Test Result output on Linux when using `print` (#1401) node-pty on Linux suffers from a long standing issue where the last chunk of output before a program exits is sometimes dropped, especially if that program produces a lot of output immediately before exiting. See https://github.com/microsoft/node-pty/issues/72 For swift processes that don't require input and may produce a lot of output that could potentially be truncated we can add a new `ReadOnlySwiftProcess` that spawns a child process using node's built in `child_process`. These spawned child processes emit their full output before exiting without the need to resort to flakey workarounds on the node-pty process. When creating new `SwiftExecution`s in future `readOnlyTerminal` should be set to `true` unless the process may require user input. Issue: #1393 --- .../Tests/PackageTests/PackageTests.swift | 8 ++ src/TestExplorer/TestRunArguments.ts | 3 +- src/TestExplorer/TestRunner.ts | 3 +- src/tasks/SwiftExecution.ts | 9 +- src/tasks/SwiftProcess.ts | 89 +++++++++++++++++++ src/tasks/SwiftTaskProvider.ts | 4 +- .../TestExplorerIntegration.test.ts | 31 ++++++- .../testexplorer/TestRunArguments.test.ts | 2 +- .../testexplorer/utilities.ts | 18 +++- 9 files changed, 157 insertions(+), 10 deletions(-) diff --git a/assets/test/defaultPackage/Tests/PackageTests/PackageTests.swift b/assets/test/defaultPackage/Tests/PackageTests/PackageTests.swift index 835e7f127..dc856f6ce 100644 --- a/assets/test/defaultPackage/Tests/PackageTests/PackageTests.swift +++ b/assets/test/defaultPackage/Tests/PackageTests/PackageTests.swift @@ -102,6 +102,14 @@ struct MixedSwiftTestingSuite { } #expect(2 == 3) } + +@Test func testLotsOfOutput() { + var string = "" + for i in 1...100_000 { + string += "\(i)\n" + } + print(string) +} #endif #if swift(>=6.1) diff --git a/src/TestExplorer/TestRunArguments.ts b/src/TestExplorer/TestRunArguments.ts index 027e3c2ec..d6d4a5fa6 100644 --- a/src/TestExplorer/TestRunArguments.ts +++ b/src/TestExplorer/TestRunArguments.ts @@ -93,11 +93,12 @@ export class TestRunArguments { const terminator = hasChildren ? "/" : "$"; // Debugging XCTests requires exact matches, so we don't need a trailing terminator. return isDebug ? arg.id : `${arg.id}${terminator}`; - } else { + } else if (hasChildren) { // Append a trailing slash to match a suite name exactly. // This prevents TestTarget.MySuite matching TestTarget.MySuite2. return `${arg.id}/`; } + return arg.id; }); } diff --git a/src/TestExplorer/TestRunner.ts b/src/TestExplorer/TestRunner.ts index d9fae8a14..abf54b60d 100644 --- a/src/TestExplorer/TestRunner.ts +++ b/src/TestExplorer/TestRunner.ts @@ -715,7 +715,8 @@ export class TestRunner { presentationOptions: { reveal: vscode.TaskRevealKind.Never }, }, this.folderContext.workspaceContext.toolchain, - { ...process.env, ...testBuildConfig.env } + { ...process.env, ...testBuildConfig.env }, + { readOnlyTerminal: process.platform !== "win32" } ); task.execution.onDidWrite(str => { diff --git a/src/tasks/SwiftExecution.ts b/src/tasks/SwiftExecution.ts index f9c6c5502..3cb73495f 100644 --- a/src/tasks/SwiftExecution.ts +++ b/src/tasks/SwiftExecution.ts @@ -13,11 +13,12 @@ //===----------------------------------------------------------------------===// import * as vscode from "vscode"; -import { SwiftProcess, SwiftPtyProcess } from "./SwiftProcess"; +import { ReadOnlySwiftProcess, SwiftProcess, SwiftPtyProcess } from "./SwiftProcess"; import { SwiftPseudoterminal } from "./SwiftPseudoterminal"; export interface SwiftExecutionOptions extends vscode.ProcessExecutionOptions { presentation?: vscode.TaskPresentationOptions; + readOnlyTerminal?: boolean; } /** @@ -30,11 +31,15 @@ export class SwiftExecution extends vscode.CustomExecution { public readonly command: string, public readonly args: string[], public readonly options: SwiftExecutionOptions, - private readonly swiftProcess: SwiftProcess = new SwiftPtyProcess(command, args, options) + private readonly swiftProcess: SwiftProcess = options.readOnlyTerminal + ? new ReadOnlySwiftProcess(command, args, options) + : new SwiftPtyProcess(command, args, options) ) { super(async () => { return new SwiftPseudoterminal(swiftProcess, options.presentation || {}); }); + + this.swiftProcess = swiftProcess; this.onDidWrite = swiftProcess.onDidWrite; this.onDidClose = swiftProcess.onDidClose; } diff --git a/src/tasks/SwiftProcess.ts b/src/tasks/SwiftProcess.ts index 1c0ce05e8..0a41726c9 100644 --- a/src/tasks/SwiftProcess.ts +++ b/src/tasks/SwiftProcess.ts @@ -13,6 +13,7 @@ //===----------------------------------------------------------------------===// import type * as nodePty from "node-pty"; +import * as child_process from "child_process"; import * as vscode from "vscode"; import { requireNativeModule } from "../utilities/native"; @@ -153,3 +154,91 @@ export class SwiftPtyProcess implements SwiftProcess { onDidClose: vscode.Event = this.closeEmitter.event; } + +/** + * A {@link SwiftProcess} that spawns a child process and does not bind to stdio. + * + * Use this for Swift tasks that do not need to accept input, as its lighter weight and + * less error prone than using a spawned node-pty process. + * + * Specifically node-pty on Linux suffers from a long standing issue where the last chunk + * of output before a program exits is sometimes dropped, especially if that program produces + * a lot of output immediately before exiting. See https://github.com/microsoft/node-pty/issues/72 + */ +export class ReadOnlySwiftProcess implements SwiftProcess { + private readonly spawnEmitter: vscode.EventEmitter = new vscode.EventEmitter(); + private readonly writeEmitter: vscode.EventEmitter = new vscode.EventEmitter(); + private readonly errorEmitter: vscode.EventEmitter = new vscode.EventEmitter(); + private readonly closeEmitter: vscode.EventEmitter = new vscode.EventEmitter< + number | void + >(); + + private spawnedProcess: child_process.ChildProcessWithoutNullStreams | undefined; + + constructor( + public readonly command: string, + public readonly args: string[], + private readonly options: vscode.ProcessExecutionOptions = {} + ) {} + + spawn(): void { + try { + this.spawnedProcess = child_process.spawn(this.command, this.args, { + cwd: this.options.cwd, + env: { ...process.env, ...this.options.env }, + }); + + this.spawnedProcess.stdout.on("data", data => { + this.writeEmitter.fire(data.toString()); + }); + + this.spawnedProcess.stderr.on("data", data => { + this.writeEmitter.fire(data.toString()); + }); + + this.spawnedProcess.on("error", error => { + this.errorEmitter.fire(new Error(`${error}`)); + this.closeEmitter.fire(); + }); + + this.spawnedProcess.once("exit", code => { + this.closeEmitter.fire(code ?? undefined); + this.dispose(); + }); + } catch (error) { + this.errorEmitter.fire(new Error(`${error}`)); + this.closeEmitter.fire(); + this.dispose(); + } + } + + handleInput(_s: string): void { + // Do nothing + } + + terminate(signal?: NodeJS.Signals): void { + if (!this.spawnedProcess) { + return; + } + this.spawnedProcess.kill(signal); + this.dispose(); + } + + setDimensions(_dimensions: vscode.TerminalDimensions): void { + // Do nothing + } + + dispose(): void { + this.spawnedProcess?.stdout.removeAllListeners(); + this.spawnedProcess?.stderr.removeAllListeners(); + this.spawnedProcess?.removeAllListeners(); + } + + onDidSpawn: vscode.Event = this.spawnEmitter.event; + + onDidWrite: vscode.Event = this.writeEmitter.event; + + onDidThrowError: vscode.Event = this.errorEmitter.event; + + onDidClose: vscode.Event = this.closeEmitter.event; +} diff --git a/src/tasks/SwiftTaskProvider.ts b/src/tasks/SwiftTaskProvider.ts index 77bd93689..40ac5179f 100644 --- a/src/tasks/SwiftTaskProvider.ts +++ b/src/tasks/SwiftTaskProvider.ts @@ -268,7 +268,8 @@ export function createSwiftTask( name: string, config: TaskConfig, toolchain: SwiftToolchain, - cmdEnv: { [key: string]: string } = {} + cmdEnv: { [key: string]: string } = {}, + options: { readOnlyTerminal: boolean } = { readOnlyTerminal: false } ): SwiftTask { const swift = toolchain.getToolchainExecutable("swift"); args = toolchain.buildFlags.withAdditionalFlags(args); @@ -313,6 +314,7 @@ export function createSwiftTask( cwd: fullCwd, env: env, presentation, + readOnlyTerminal: options.readOnlyTerminal, }) ); // This doesn't include any quotes added by VS Code. diff --git a/test/integration-tests/testexplorer/TestExplorerIntegration.test.ts b/test/integration-tests/testexplorer/TestExplorerIntegration.test.ts index aa5c34d09..b613bdc82 100644 --- a/test/integration-tests/testexplorer/TestExplorerIntegration.test.ts +++ b/test/integration-tests/testexplorer/TestExplorerIntegration.test.ts @@ -20,6 +20,7 @@ import { beforeEach, afterEach } from "mocha"; import { TestExplorer } from "../../../src/TestExplorer/TestExplorer"; import { assertContains, + assertContainsTrimmed, assertTestControllerHierarchy, assertTestResults, eventPromise, @@ -190,6 +191,7 @@ suite("Test Explorer Suite", function () { ["testPassing()", "testFailing()", "testDisabled()"], "testWithKnownIssue()", "testWithKnownIssueAndUnknownIssue()", + "testLotsOfOutput()", "testAttachment()", "DuplicateSuffixTests", ["testPassing()", "testPassingSuffix()"], @@ -230,6 +232,29 @@ suite("Test Explorer Suite", function () { } }); + test("captures lots of output", async () => { + const testRun = await runTest( + testExplorer, + TestKind.standard, + "PackageTests.testLotsOfOutput()" + ); + + assertTestResults(testRun, { + passed: ["PackageTests.testLotsOfOutput()"], + }); + + // Right now the swift-testing "test run complete" text is being emitted + // in the middle of the print, so the last line is actually end end of our + // huge string. If they fix this in future this `find` ensures the test wont break. + const needle = "100000"; + const lastTenLines = testRun.runState.output.slice(-10).join("\n"); + assertContainsTrimmed( + testRun.runState.output, + needle, + `Expected all test output to be captured, but it was truncated. Last 10 lines of output were: ${lastTenLines}` + ); + }); + test("attachments", async function () { // Attachments were introduced in 6.1 if (workspaceContext.swiftVersion.isLessThan(new Version(6, 1, 0))) { @@ -535,9 +560,11 @@ suite("Test Explorer Suite", function () { "PackageTests.topLevelTestPassing()" ); - assertContains( + // Use assertContainsTrimmed to ignore the line ending differences + // across platforms (windows vs linux/darwin) + assertContainsTrimmed( testRun.runState.output, - "A print statement in a test.\r\r\n" + "A print statement in a test." ); assertTestResults(testRun, { passed: ["PackageTests.topLevelTestPassing()"], diff --git a/test/integration-tests/testexplorer/TestRunArguments.test.ts b/test/integration-tests/testexplorer/TestRunArguments.test.ts index 6fc75aa8f..f435d2774 100644 --- a/test/integration-tests/testexplorer/TestRunArguments.test.ts +++ b/test/integration-tests/testexplorer/TestRunArguments.test.ts @@ -240,7 +240,7 @@ suite("TestRunArguments Suite", () => { const testArgs = new TestRunArguments(runRequestByIds([anotherSwiftTestId]), false); assertRunArguments(testArgs, { xcTestArgs: [], - swiftTestArgs: [`${anotherSwiftTestId}/`], + swiftTestArgs: [anotherSwiftTestId], testItems: [swiftTestSuiteId, testTargetId, anotherSwiftTestId], }); }); diff --git a/test/integration-tests/testexplorer/utilities.ts b/test/integration-tests/testexplorer/utilities.ts index 00460a88e..b049f74c7 100644 --- a/test/integration-tests/testexplorer/utilities.ts +++ b/test/integration-tests/testexplorer/utilities.ts @@ -110,9 +110,23 @@ export function assertTestControllerHierarchy( * * @param array The array to check. * @param value The value to check for. + * @param message An optional message to display if the assertion fails. */ -export function assertContains(array: T[], value: T) { - assert.ok(array.includes(value), `${value} is not in ${array}`); +export function assertContains(array: T[], value: T, message?: string) { + assert.ok(array.includes(value), message ?? `${value} is not in ${array}`); +} + +/** + * Asserts that an array of strings contains the value ignoring + * leading/trailing whitespace. + * + * @param array The array to check. + * @param value The value to check for. + * @param message An optional message to display if the assertion fails. + */ +export function assertContainsTrimmed(array: string[], value: string, message?: string) { + const found = array.find(row => row.trim() === value); + assert.ok(found, message ?? `${value} is not in ${array}`); } /** From 32cb840bdc980466cf381804ba7eb453af59fcba Mon Sep 17 00:00:00 2001 From: Paul LeMarquand Date: Tue, 25 Feb 2025 14:06:39 -0500 Subject: [PATCH 23/86] Always use unified testing binary (#1049) The .swift-testing binary is no longer output, it has been rolled in to a single binary for both XCTest and Swift Testing we no longer need to test for this distinction now that the nightly toolchains have been producing this unified binary for enough time. Issue: #994 --- src/TestExplorer/TestRunner.ts | 8 ++-- src/debugger/buildConfig.ts | 86 +++++++++------------------------- 2 files changed, 25 insertions(+), 69 deletions(-) diff --git a/src/TestExplorer/TestRunner.ts b/src/TestExplorer/TestRunner.ts index abf54b60d..c42294239 100644 --- a/src/TestExplorer/TestRunner.ts +++ b/src/TestExplorer/TestRunner.ts @@ -577,7 +577,7 @@ export class TestRunner { fifoPipePath, attachmentFolder ); - const testBuildConfig = await TestingConfigurationFactory.swiftTestingConfig( + const testBuildConfig = TestingConfigurationFactory.swiftTestingConfig( this.folderContext, swiftTestingArgs, this.testKind, @@ -612,7 +612,7 @@ export class TestRunner { } if (this.testArgs.hasXCTests) { - const testBuildConfig = await TestingConfigurationFactory.xcTestConfig( + const testBuildConfig = TestingConfigurationFactory.xcTestConfig( this.folderContext, this.testKind, this.testArgs.xcTestArgs, @@ -862,7 +862,7 @@ export class TestRunner { attachmentFolder ); - const swiftTestBuildConfig = await TestingConfigurationFactory.swiftTestingConfig( + const swiftTestBuildConfig = TestingConfigurationFactory.swiftTestingConfig( this.folderContext, swiftTestingArgs, this.testKind, @@ -896,7 +896,7 @@ export class TestRunner { // create launch config for testing if (this.testArgs.hasXCTests) { - const xcTestBuildConfig = await TestingConfigurationFactory.xcTestConfig( + const xcTestBuildConfig = TestingConfigurationFactory.xcTestConfig( this.folderContext, this.testKind, this.testArgs.xcTestArgs, diff --git a/src/debugger/buildConfig.ts b/src/debugger/buildConfig.ts index 8722336e0..a066a4bf7 100644 --- a/src/debugger/buildConfig.ts +++ b/src/debugger/buildConfig.ts @@ -186,13 +186,13 @@ export class SwiftTestingConfigurationSetup { * and `xcTestConfig` functions to create */ export class TestingConfigurationFactory { - public static async swiftTestingConfig( + public static swiftTestingConfig( ctx: FolderContext, buildArguments: SwiftTestingBuildAguments, testKind: TestKind, testList: string[], expandEnvVariables = false - ): Promise { + ): vscode.DebugConfiguration | null { return new TestingConfigurationFactory( ctx, testKind, @@ -203,12 +203,12 @@ export class TestingConfigurationFactory { ).build(); } - public static async xcTestConfig( + public static xcTestConfig( ctx: FolderContext, testKind: TestKind, testList: string[], expandEnvVariables = false - ): Promise { + ): vscode.DebugConfiguration | null { return new TestingConfigurationFactory( ctx, testKind, @@ -219,11 +219,11 @@ export class TestingConfigurationFactory { ).build(); } - public static async testExecutableOutputPath( + public static testExecutableOutputPath( ctx: FolderContext, testKind: TestKind, testLibrary: TestLibrary - ): Promise { + ): string { return new TestingConfigurationFactory( ctx, testKind, @@ -251,7 +251,7 @@ export class TestingConfigurationFactory { * - Test Kind (coverage, debugging) * - Test Library (XCTest, swift-testing) */ - private async build(): Promise { + private build(): vscode.DebugConfiguration | null { if (!this.hasTestTarget) { return null; } @@ -267,7 +267,7 @@ export class TestingConfigurationFactory { } /* eslint-disable no-case-declarations */ - private async buildWindowsConfig(): Promise { + private buildWindowsConfig(): vscode.DebugConfiguration | null { if (isDebugging(this.testKind)) { const testEnv = { ...swiftRuntimeEnv(), @@ -288,8 +288,8 @@ export class TestingConfigurationFactory { return { ...this.baseConfig, - program: await this.testExecutableOutputPath(), - args: await this.debuggingTestExecutableArgs(), + program: this.testExecutableOutputPath(), + args: this.debuggingTestExecutableArgs(), env: testEnv, }; } else { @@ -298,12 +298,12 @@ export class TestingConfigurationFactory { } /* eslint-disable no-case-declarations */ - private async buildLinuxConfig(): Promise { + private buildLinuxConfig(): vscode.DebugConfiguration | null { if (isDebugging(this.testKind) && this.testLibrary === TestLibrary.xctest) { return { ...this.baseConfig, - program: await this.testExecutableOutputPath(), - args: await this.debuggingTestExecutableArgs(), + program: this.testExecutableOutputPath(), + args: this.debuggingTestExecutableArgs(), env: { ...swiftRuntimeEnv( process.env, @@ -313,11 +313,11 @@ export class TestingConfigurationFactory { }, }; } else { - return await this.buildDarwinConfig(); + return this.buildDarwinConfig(); } } - private async buildDarwinConfig(): Promise { + private buildDarwinConfig(): vscode.DebugConfiguration | null { switch (this.testLibrary) { case TestLibrary.swiftTesting: switch (this.testKind) { @@ -362,8 +362,8 @@ export class TestingConfigurationFactory { const result = { ...this.baseConfig, - program: await this.testExecutableOutputPath(), - args: await this.debuggingTestExecutableArgs(), + program: this.testExecutableOutputPath(), + args: this.debuggingTestExecutableArgs(), env: { ...this.testEnv, ...this.sanitizerRuntimeEnvironment, @@ -631,45 +631,6 @@ export class TestingConfigurationFactory { ); } - private swiftTestingOutputPath(): string { - return path.join( - this.buildDirectory, - this.artifactFolderForTestKind, - `${this.ctx.swiftPackage.name}PackageTests.swift-testing` - ); - } - - private buildDescriptionPath(): string { - return path.join(this.buildDirectory, this.artifactFolderForTestKind, "description.json"); - } - - private async isUnifiedTestingBinary(): Promise { - // Toolchains that contain https://github.com/swiftlang/swift-package-manager/commit/844bd137070dcd18d0f46dd95885ef7907ea0697 - // no longer produce a .swift-testing binary, instead we want to use `unifiedTestingOutputPath`. - // In order to determine if we're working with a unified binary we need to check if the .swift-testing - // binary was produced by the latest build. If it was then we are not using a unified binary. - - // TODO: When Swift 6 is released and enough time has passed that we're sure no one is building the .swift-testing - // binary anymore this workaround can be removed and `swiftTestingPath` can be returned, and the build config - // generation can be made synchronous again. - - try { - const buildDescriptionStr = await fs.readFile(this.buildDescriptionPath(), "utf-8"); - const buildDescription = JSON.parse(buildDescriptionStr); - const testProducts = buildDescription.builtTestProducts as { binaryPath: string }[]; - if (!testProducts) { - return false; - } - const testBinaryPaths = testProducts.map(({ binaryPath }) => binaryPath); - const swiftTestingBinaryRealPath = await fs.realpath(this.swiftTestingOutputPath()); - return !testBinaryPaths.includes(swiftTestingBinaryRealPath); - } catch { - // If the .swift-testing binary wasn't produced by the latest build then we assume the - // swift-testing tests are in the unified binary. - return true; - } - } - private unifiedTestingOutputPath(): string { // The unified binary that contains both swift-testing and XCTests // is named the same as the old style .xctest binary. The swiftpm-testing-helper @@ -686,24 +647,19 @@ export class TestingConfigurationFactory { } } - private async testExecutableOutputPath(): Promise { + private testExecutableOutputPath(): string { switch (this.testLibrary) { case TestLibrary.swiftTesting: - return (await this.isUnifiedTestingBinary()) - ? this.unifiedTestingOutputPath() - : this.swiftTestingOutputPath(); + return this.unifiedTestingOutputPath(); case TestLibrary.xctest: return this.xcTestOutputPath(); } } - private async debuggingTestExecutableArgs(): Promise { + private debuggingTestExecutableArgs(): string[] { switch (this.testLibrary) { case TestLibrary.swiftTesting: { - const isUnifiedBinary = await this.isUnifiedTestingBinary(); - const swiftTestingArgs = isUnifiedBinary - ? ["--testing-library", "swift-testing"] - : []; + const swiftTestingArgs = ["--testing-library", "swift-testing"]; return this.addBuildOptionsToArgs( this.addTestsToArgs(this.addSwiftTestingFlagsArgs(swiftTestingArgs)) From 9314392517f1f63fe4b01b36db6f3da909b89b0a Mon Sep 17 00:00:00 2001 From: Matthew Bastien Date: Tue, 25 Feb 2025 19:18:32 +0000 Subject: [PATCH 24/86] use LLDB DAP extension by default (#1406) --- src/debugger/debugAdapter.ts | 2 +- test/unit-tests/debugger/debugAdapter.test.ts | 15 ++++++++------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/debugger/debugAdapter.ts b/src/debugger/debugAdapter.ts index 9ae96b0c1..fdaa7b03d 100644 --- a/src/debugger/debugAdapter.ts +++ b/src/debugger/debugAdapter.ts @@ -44,7 +44,7 @@ export class DebugAdapter { */ public static getLaunchConfigType(swiftVersion: Version): LaunchConfigType { const lldbDapIsAvailable = swiftVersion.isGreaterThanOrEqual(new Version(6, 0, 0)); - if (lldbDapIsAvailable && configuration.debugger.debugAdapter === "lldb-dap") { + if (lldbDapIsAvailable && configuration.debugger.debugAdapter !== "CodeLLDB") { return LaunchConfigType.LLDB_DAP; } else { return LaunchConfigType.CODE_LLDB; diff --git a/test/unit-tests/debugger/debugAdapter.test.ts b/test/unit-tests/debugger/debugAdapter.test.ts index e0c877ba2..a136db764 100644 --- a/test/unit-tests/debugger/debugAdapter.test.ts +++ b/test/unit-tests/debugger/debugAdapter.test.ts @@ -40,22 +40,23 @@ suite("DebugAdapter Unit Test Suite", () => { }); suite("getLaunchConfigType()", () => { - test("returns SWIFT_EXTENSION when Swift version >=6.0.0 and swift.debugger.debugAdapter is set to lldb-dap", () => { + test("returns LLDB_DAP when Swift version >=6.0.0 and swift.debugger.debugAdapter is set to lldb-dap", () => { mockDebugConfig.debugAdapter = "lldb-dap"; expect(DebugAdapter.getLaunchConfigType(new Version(6, 0, 1))).to.equal( LaunchConfigType.LLDB_DAP ); }); - test("returns CODE_LLDB when Swift version >=6.0.0 and swift.debugger.debugAdapter is set to auto or CodeLLDB", () => { - // Try with the setting set to auto + test("returns LLDB_DAP when Swift version >=6.0.0 and swift.debugger.debugAdapter is set to auto", () => { mockDebugConfig.debugAdapter = "auto"; - expect(DebugAdapter.getLaunchConfigType(new Version(5, 10, 0))).to.equal( - LaunchConfigType.CODE_LLDB + expect(DebugAdapter.getLaunchConfigType(new Version(6, 0, 1))).to.equal( + LaunchConfigType.LLDB_DAP ); - // Try with the setting set to CodeLLDB + }); + + test("returns CODE_LLDB when Swift version >=6.0.0 and swift.debugger.debugAdapter is set to CODE_LLDB", () => { mockDebugConfig.debugAdapter = "CodeLLDB"; - expect(DebugAdapter.getLaunchConfigType(new Version(5, 10, 0))).to.equal( + expect(DebugAdapter.getLaunchConfigType(new Version(6, 0, 1))).to.equal( LaunchConfigType.CODE_LLDB ); }); From 48da2c7eed4003e419055f61cb90105294fab64f Mon Sep 17 00:00:00 2001 From: Paul LeMarquand Date: Wed, 26 Feb 2025 11:20:07 -0500 Subject: [PATCH 25/86] Switch dev containers to use lldb-dap extension over CodeLLDB (#1407) --- .devcontainer/devcontainer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 6b96f71bb..fba366480 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -27,7 +27,7 @@ "extensions": [ "dbaeumer.vscode-eslint", "esbenp.prettier-vscode", - "vadimcn.vscode-lldb" + "llvm-vs-code-extensions.lldb-dap" ] } }, From 85c9a3234a0b8f2352cfb87600d10dda09f86729 Mon Sep 17 00:00:00 2001 From: Paul LeMarquand Date: Wed, 26 Feb 2025 14:17:30 -0500 Subject: [PATCH 26/86] Remove more references to CodeLLDB (#1408) - Switch from CodeLLDB to LLDB DAP in the testing-debug.code-profile - If you're running the tests from within VS Code at desk you'll want to delete the existing Testing profile and reload with the new one. - Fixup the README - Fixup the vscode-test.js required extensions --- .vscode-test.js | 2 +- .vscode/testing-debug.code-profile | 2 +- README.md | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.vscode-test.js b/.vscode-test.js index e0302dd36..f244bd1c2 100644 --- a/.vscode-test.js +++ b/.vscode-test.js @@ -64,7 +64,7 @@ module.exports = defineConfig({ }, }, reuseMachineInstall: !isCIBuild, - installExtensions: ["vadimcn.vscode-lldb"], + installExtensions: ["vadimcn.vscode-lldb", "llvm-vs-code-extensions.lldb-dap"], }, { label: "unitTests", diff --git a/.vscode/testing-debug.code-profile b/.vscode/testing-debug.code-profile index 37035a549..d9d74b288 100644 --- a/.vscode/testing-debug.code-profile +++ b/.vscode/testing-debug.code-profile @@ -1,4 +1,4 @@ { "name": "testing-debug", - "extensions": "[{\"identifier\":{\"id\":\"ms-vscode-remote.remote-containers\",\"uuid\":\"93ce222b-5f6f-49b7-9ab1-a0463c6238df\"},\"displayName\":\"Dev Containers\"},{\"identifier\":{\"id\":\"vadimcn.vscode-lldb\",\"uuid\":\"bee31e34-a44b-4a76-9ec2-e9fd1439a0f6\"},\"displayName\":\"CodeLLDB\"}]" + "extensions": "[{\"identifier\":{\"id\":\"ms-vscode-remote.remote-containers\",\"uuid\":\"93ce222b-5f6f-49b7-9ab1-a0463c6238df\"},\"displayName\":\"Dev Containers\"},{\"identifier\":{\"id\":\"llvm-vs-code-extensions.lldb-dap\",\"uuid\":\"8f0e51b3-cc69-4cf9-abab-97289d29d6de\"},\"displayName\":\"LLDB DAP\"}]" } \ No newline at end of file diff --git a/README.md b/README.md index 7f8069c26..4638e905e 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ This extension adds language support for Swift to Visual Studio Code, providing * Package dependency view * Test Explorer view -This extension uses [SourceKit LSP](https://github.com/apple/sourcekit-lsp) for the [language server](https://microsoft.github.io/language-server-protocol/overviews/lsp/overview/), which powers code completion. It also has a dependency on [CodeLLDB](https://github.com/vadimcn/vscode-lldb) for debugging. +This extension uses [SourceKit LSP](https://github.com/apple/sourcekit-lsp) for the [language server](https://microsoft.github.io/language-server-protocol/overviews/lsp/overview/), which powers code completion. It also has a dependency on [LLDB DAP](https://marketplace.visualstudio.com/items?itemName=llvm-vs-code-extensions.lldb-dap) for debugging. To propose new features, you can post on the [swift.org forums](https://forums.swift.org) in the [VS Code Swift Extension category](https://forums.swift.org/c/related-projects/vscode-swift-extension/). If you run into something that doesn't work the way you'd expect, you can [file an issue in the GitHub repository](https://github.com/swiftlang/vscode-swift/issues/new). @@ -22,7 +22,7 @@ The Swift extension is supported on macOS, Linux, and Windows. To install, first ### Language features -The extension provides language features such as code completion and jump to definition via [SourceKit-LSP](https://github.com/apple/sourcekit-lsp). To ensure the extension functions correctly, it’s important to first build the project so that SourceKit-LSP has access to all the symbol data. Whenever you add a new dependency to your project, make sure to rebuild it so that SourceKit-LSP can update its information. +The extension provides language features such as code completion and jump to definition via [SourceKit-LSP](https://github.com/apple/sourcekit-lsp). To ensure the extension functions correctly, it’s important to first build the project so that SourceKit-LSP has access to all the symbol data. Whenever you add a new dependency to your project, make sure to rebuild it so that SourceKit-LSP can update its information. ### Automatic task creation @@ -99,13 +99,13 @@ Additionally, the extension will monitor `Package.swift` and `Package.resolved` ### Debugging -The Swift extension uses the [CodeLLDB](https://marketplace.visualstudio.com/items?itemName=vadimcn.vscode-lldb) extension for debugging. +The Swift extension uses the [LLDB DAP](https://marketplace.visualstudio.com/items?itemName=llvm-vs-code-extensions.lldb-dap) extension for debugging. When you open a Swift package (a directory containing a `Package.swift` file), the extension automatically generates build tasks and launch configurations for each executable within the package. Additionally, if the package includes tests, the extension creates a configuration specifically designed to run those tests. These configurations all leverage the CodeLLDB extension as the debugger of choice. Use the **Run > Start Debugging** menu item to run an executable and start debugging. If you have multiple launch configurations you can choose which launch configuration to use in the debugger view. -CodeLLDB includes a version of `lldb` that it uses by default for debugging, but this version of `lldb` doesn’t support Swift. The Swift extension will automatically identify the required version and offer to update the CodeLLDB configuration as necessary so that debugging is supported. +CodeLLDB includes a version of `lldb` that it uses by default for debugging, but this version of `lldb` doesn’t support Swift. The Swift extension will automatically identify the required version and offer to update the CodeLLDB configuration as necessary so that debugging is supported. ### Test Explorer From 679093790b588f4855f5fddb60d3dbc0e5c59775 Mon Sep 17 00:00:00 2001 From: Paul LeMarquand Date: Wed, 26 Feb 2025 15:23:51 -0500 Subject: [PATCH 27/86] Add missing spawn event on ReadOnlySwiftProcess (#1410) --- src/tasks/SwiftProcess.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tasks/SwiftProcess.ts b/src/tasks/SwiftProcess.ts index 0a41726c9..012587834 100644 --- a/src/tasks/SwiftProcess.ts +++ b/src/tasks/SwiftProcess.ts @@ -187,6 +187,7 @@ export class ReadOnlySwiftProcess implements SwiftProcess { cwd: this.options.cwd, env: { ...process.env, ...this.options.env }, }); + this.spawnEmitter.fire(); this.spawnedProcess.stdout.on("data", data => { this.writeEmitter.fire(data.toString()); From 5753b6b1a4c7f54028fb91e63ab63efc1dc70248 Mon Sep 17 00:00:00 2001 From: Paul LeMarquand Date: Thu, 27 Feb 2025 15:24:27 -0500 Subject: [PATCH 28/86] Project Panel (#1382) * Project Panel Convert the dependencies panel in to the new project panel. This rolls the dependencies up in to its own top level item in the tree, and places it along side Targets, Tasks, Commands and Snippets. Tasks, Commands and Snippets can be run directly from the panel, and update their icon to show their running status. Clicking a test target will run all the tests in the target. --- assets/test/targets/Package.swift | 48 ++ .../targets/Plugins/PluginTarget/main.swift | 9 + .../targets/Snippets/AnotherSnippet.swift | 1 + assets/test/targets/Snippets/Snippet.swift | 1 + .../CommandPluginTarget.swift | 0 .../Sources/ExecutableTarget/main.swift | 1 + .../Sources/LibraryTarget/Targets.swift | 9 + .../Tests/AnotherTests/AnotherTests.swift | 8 + .../Tests/TargetsTests/TargetsTests.swift | 8 + package.json | 105 +++- src/PackageWatcher.ts | 12 + src/SwiftPackage.ts | 2 +- src/SwiftSnippets.ts | 50 +- src/TestExplorer/TestRunner.ts | 22 +- src/WorkspaceContext.ts | 52 ++ src/commands.ts | 59 +- src/commands/build.ts | 33 +- src/commands/openInExternalEditor.ts | 2 +- src/commands/openInWorkspace.ts | 2 +- .../{runParallelTests.ts => runAllTests.ts} | 16 +- src/commands/runTask.ts | 43 ++ src/debugger/launch.ts | 1 + src/extension.ts | 16 +- src/ui/PackageDependencyProvider.ts | 269 --------- src/ui/ProjectPanelProvider.ts | 558 ++++++++++++++++++ src/ui/StatusItem.ts | 4 +- .../commands/dependency.test.ts | 20 +- .../ui/PackageDependencyProvider.test.ts | 166 ------ .../ui/ProjectPanelProvider.test.ts | 300 ++++++++++ .../utilities/testutilities.ts | 4 +- .../ui/PackageDependencyProvider.test.ts | 2 +- 31 files changed, 1302 insertions(+), 521 deletions(-) create mode 100644 assets/test/targets/Package.swift create mode 100644 assets/test/targets/Plugins/PluginTarget/main.swift create mode 100644 assets/test/targets/Snippets/AnotherSnippet.swift create mode 100644 assets/test/targets/Snippets/Snippet.swift create mode 100644 assets/test/targets/Sources/CommandPluginTarget/CommandPluginTarget.swift create mode 100644 assets/test/targets/Sources/ExecutableTarget/main.swift create mode 100644 assets/test/targets/Sources/LibraryTarget/Targets.swift create mode 100644 assets/test/targets/Tests/AnotherTests/AnotherTests.swift create mode 100644 assets/test/targets/Tests/TargetsTests/TargetsTests.swift rename src/commands/{runParallelTests.ts => runAllTests.ts} (64%) create mode 100644 src/commands/runTask.ts delete mode 100644 src/ui/PackageDependencyProvider.ts create mode 100644 src/ui/ProjectPanelProvider.ts delete mode 100644 test/integration-tests/ui/PackageDependencyProvider.test.ts create mode 100644 test/integration-tests/ui/ProjectPanelProvider.test.ts diff --git a/assets/test/targets/Package.swift b/assets/test/targets/Package.swift new file mode 100644 index 000000000..35cda10ab --- /dev/null +++ b/assets/test/targets/Package.swift @@ -0,0 +1,48 @@ +// swift-tools-version: 5.6 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "targets", + products: [ + .library( + name: "LibraryTarget", + targets: ["LibraryTarget"] + ), + .executable( + name: "ExecutableTarget", + targets: ["ExecutableTarget"] + ), + .plugin( + name: "PluginTarget", + targets: ["PluginTarget"] + ), + ], + dependencies: [ + .package(url: "https://github.com/swiftlang/swift-markdown.git", branch: "main"), + .package(path: "../defaultPackage"), + ], + targets: [ + .target( + name: "LibraryTarget" + ), + .executableTarget( + name: "ExecutableTarget" + ), + .plugin( + name: "PluginTarget", + capability: .command( + intent: .custom(verb: "testing", description: "A plugin for testing plugins") + ) + ), + .testTarget( + name: "TargetsTests", + dependencies: ["LibraryTarget"] + ), + .testTarget( + name: "AnotherTests", + dependencies: ["LibraryTarget"] + ), + ] +) diff --git a/assets/test/targets/Plugins/PluginTarget/main.swift b/assets/test/targets/Plugins/PluginTarget/main.swift new file mode 100644 index 000000000..8a2a8680f --- /dev/null +++ b/assets/test/targets/Plugins/PluginTarget/main.swift @@ -0,0 +1,9 @@ +import PackagePlugin +import Foundation + +@main +struct MyCommandPlugin: CommandPlugin { + func performCommand(context: PluginContext, arguments: [String]) throws { + print("Plugin Target Hello World") + } +} \ No newline at end of file diff --git a/assets/test/targets/Snippets/AnotherSnippet.swift b/assets/test/targets/Snippets/AnotherSnippet.swift new file mode 100644 index 000000000..25f53dfa6 --- /dev/null +++ b/assets/test/targets/Snippets/AnotherSnippet.swift @@ -0,0 +1 @@ +print("Another Snippet Hello World") \ No newline at end of file diff --git a/assets/test/targets/Snippets/Snippet.swift b/assets/test/targets/Snippets/Snippet.swift new file mode 100644 index 000000000..cdd7d267c --- /dev/null +++ b/assets/test/targets/Snippets/Snippet.swift @@ -0,0 +1 @@ +print("Snippet Hello World") \ No newline at end of file diff --git a/assets/test/targets/Sources/CommandPluginTarget/CommandPluginTarget.swift b/assets/test/targets/Sources/CommandPluginTarget/CommandPluginTarget.swift new file mode 100644 index 000000000..e69de29bb diff --git a/assets/test/targets/Sources/ExecutableTarget/main.swift b/assets/test/targets/Sources/ExecutableTarget/main.swift new file mode 100644 index 000000000..2fcea7ab3 --- /dev/null +++ b/assets/test/targets/Sources/ExecutableTarget/main.swift @@ -0,0 +1 @@ +print("Executable Target Hello World!") \ No newline at end of file diff --git a/assets/test/targets/Sources/LibraryTarget/Targets.swift b/assets/test/targets/Sources/LibraryTarget/Targets.swift new file mode 100644 index 000000000..37c4f8832 --- /dev/null +++ b/assets/test/targets/Sources/LibraryTarget/Targets.swift @@ -0,0 +1,9 @@ +// The Swift Programming Language +// https://docs.swift.org/swift-book + +public func foo() { + print("foo") +} +public func bar() { + print("bar") +} \ No newline at end of file diff --git a/assets/test/targets/Tests/AnotherTests/AnotherTests.swift b/assets/test/targets/Tests/AnotherTests/AnotherTests.swift new file mode 100644 index 000000000..8aa96db8b --- /dev/null +++ b/assets/test/targets/Tests/AnotherTests/AnotherTests.swift @@ -0,0 +1,8 @@ +import LibraryTarget +import XCTest + +class AnotherTests: XCTestCase { + func testExample() { + bar() + } +} \ No newline at end of file diff --git a/assets/test/targets/Tests/TargetsTests/TargetsTests.swift b/assets/test/targets/Tests/TargetsTests/TargetsTests.swift new file mode 100644 index 000000000..089304193 --- /dev/null +++ b/assets/test/targets/Tests/TargetsTests/TargetsTests.swift @@ -0,0 +1,8 @@ +import LibraryTarget +import XCTest + +class TargetsTests: XCTestCase { + func testExample() { + foo() + } +} \ No newline at end of file diff --git a/package.json b/package.json index 112ae5d89..9101e61f0 100644 --- a/package.json +++ b/package.json @@ -222,12 +222,14 @@ { "command": "swift.runSnippet", "title": "Run Swift Snippet", - "category": "Swift" + "category": "Swift", + "icon": "$(play)" }, { "command": "swift.debugSnippet", "title": "Debug Swift Snippet", - "category": "Swift" + "category": "Swift", + "icon": "$(debug)" }, { "command": "swift.runPluginTask", @@ -266,8 +268,27 @@ }, { "command": "swift.runAllTestsParallel", - "title": "Run All Tests in Parallel", - "category": "Test" + "title": "Run Tests in Parallel", + "category": "Test", + "icon": "$(testing-run-all-icon)" + }, + { + "command": "swift.runAllTests", + "title": "Run Tests", + "category": "Test", + "icon": "$(testing-run-icon)" + }, + { + "command": "swift.debugAllTests", + "title": "Debug Tests", + "category": "Test", + "icon": "$(testing-debug-icon)" + }, + { + "command": "swift.coverAllTests", + "title": "Run Tests with Coverage", + "category": "Test", + "icon": "$(debug-coverage)" } ], "configuration": [ @@ -910,6 +931,18 @@ { "command": "swift.runAllTestsParallel", "when": "swift.isActivated" + }, + { + "command": "swift.runAllTests", + "when": "swift.isActivated" + }, + { + "command": "swift.debugAllTests", + "when": "swift.isActivated" + }, + { + "command": "swift.coverAllTests", + "when": "swift.isActivated" } ], "editor/context": [ @@ -971,50 +1004,90 @@ "view/title": [ { "command": "swift.updateDependencies", - "when": "view == packageDependencies", + "when": "view == projectPanel", "group": "navigation@1" }, { "command": "swift.resolveDependencies", - "when": "view == packageDependencies", + "when": "view == projectPanel", "group": "navigation@2" }, { "command": "swift.resetPackage", - "when": "view == packageDependencies", + "when": "view == projectPanel", "group": "navigation@3" }, { "command": "swift.flatDependenciesList", - "when": "view == packageDependencies && !swift.flatDependenciesList", + "when": "view == projectPanel && !swift.flatDependenciesList", "group": "navigation@4" }, { "command": "swift.nestedDependenciesList", - "when": "view == packageDependencies && swift.flatDependenciesList", + "when": "view == projectPanel && swift.flatDependenciesList", "group": "navigation@5" } ], "view/item/context": [ { "command": "swift.useLocalDependency", - "when": "view == packageDependencies && viewItem == remote" + "when": "view == projectPanel && viewItem == remote" }, { "command": "swift.uneditDependency", - "when": "view == packageDependencies && viewItem == editing" + "when": "view == projectPanel && viewItem == editing" }, { "command": "swift.openInWorkspace", - "when": "view == packageDependencies && viewItem == editing" + "when": "view == projectPanel && viewItem == editing" }, { "command": "swift.openInWorkspace", - "when": "view == packageDependencies && viewItem == local" + "when": "view == projectPanel && viewItem == local" }, { "command": "swift.openExternal", - "when": "view == packageDependencies && viewItem != local" + "when": "view == projectPanel && (viewItem == 'editing' || viewItem == 'remote')" + }, + { + "command": "swift.run", + "when": "view == projectPanel && viewItem == 'runnable'", + "group": "inline@0" + }, + { + "command": "swift.debug", + "when": "view == projectPanel && viewItem == 'runnable'", + "group": "inline@1" + }, + { + "command": "swift.runSnippet", + "when": "view == projectPanel && viewItem == 'snippet_runnable'", + "group": "inline@0" + }, + { + "command": "swift.debugSnippet", + "when": "view == projectPanel && viewItem == 'snippet_runnable'", + "group": "inline@1" + }, + { + "command": "swift.runAllTests", + "when": "view == projectPanel && viewItem == 'test_runnable'", + "group": "inline@0" + }, + { + "command": "swift.debugAllTests", + "when": "view == projectPanel && viewItem == 'test_runnable'", + "group": "inline@1" + }, + { + "command": "swift.runAllTestsParallel", + "when": "view == projectPanel && viewItem == 'test_runnable'", + "group": "inline@2" + }, + { + "command": "swift.coverAllTests", + "when": "view == projectPanel && viewItem == 'test_runnable'", + "group": "inline@3" } ] }, @@ -1211,8 +1284,8 @@ "views": { "explorer": [ { - "id": "packageDependencies", - "name": "Package Dependencies", + "id": "projectPanel", + "name": "Swift Project", "icon": "$(archive)", "when": "swift.hasPackage" } diff --git a/src/PackageWatcher.ts b/src/PackageWatcher.ts index 946fe3b5e..86b4840e2 100644 --- a/src/PackageWatcher.ts +++ b/src/PackageWatcher.ts @@ -27,6 +27,7 @@ export class PackageWatcher { private packageFileWatcher?: vscode.FileSystemWatcher; private resolvedFileWatcher?: vscode.FileSystemWatcher; private workspaceStateFileWatcher?: vscode.FileSystemWatcher; + private snippetWatcher?: vscode.FileSystemWatcher; constructor( private folderContext: FolderContext, @@ -41,6 +42,7 @@ export class PackageWatcher { this.packageFileWatcher = this.createPackageFileWatcher(); this.resolvedFileWatcher = this.createResolvedFileWatcher(); this.workspaceStateFileWatcher = this.createWorkspaceStateFileWatcher(); + this.snippetWatcher = this.createSnippetFileWatcher(); } /** @@ -51,6 +53,7 @@ export class PackageWatcher { this.packageFileWatcher?.dispose(); this.resolvedFileWatcher?.dispose(); this.workspaceStateFileWatcher?.dispose(); + this.snippetWatcher?.dispose(); } private createPackageFileWatcher(): vscode.FileSystemWatcher { @@ -87,6 +90,15 @@ export class PackageWatcher { return watcher; } + private createSnippetFileWatcher(): vscode.FileSystemWatcher { + const watcher = vscode.workspace.createFileSystemWatcher( + new vscode.RelativePattern(this.folderContext.folder, "Snippets/*.swift") + ); + watcher.onDidCreate(async () => await this.handlePackageSwiftChange()); + watcher.onDidDelete(async () => await this.handlePackageSwiftChange()); + return watcher; + } + /** * Handles a create or change event for **Package.swift**. * diff --git a/src/SwiftPackage.ts b/src/SwiftPackage.ts index ed8b23c53..8df08f560 100644 --- a/src/SwiftPackage.ts +++ b/src/SwiftPackage.ts @@ -41,7 +41,7 @@ export interface Target { c99name: string; path: string; sources: string[]; - type: "executable" | "test" | "library" | "snippet"; + type: "executable" | "test" | "library" | "snippet" | "plugin"; } /** Swift Package Manager dependency */ diff --git a/src/SwiftSnippets.ts b/src/SwiftSnippets.ts index f7fa4cccb..22898b94a 100644 --- a/src/SwiftSnippets.ts +++ b/src/SwiftSnippets.ts @@ -48,29 +48,44 @@ export function setSnippetContextKey(ctx: WorkspaceContext) { * If current file is a Swift Snippet run it * @param ctx Workspace Context */ -export async function runSnippet(ctx: WorkspaceContext): Promise { - return await debugSnippetWithOptions(ctx, { noDebug: true }); +export async function runSnippet( + ctx: WorkspaceContext, + snippet?: string +): Promise { + return await debugSnippetWithOptions(ctx, { noDebug: true }, snippet); } /** * If current file is a Swift Snippet run it in the debugger * @param ctx Workspace Context */ -export async function debugSnippet(ctx: WorkspaceContext): Promise { - return await debugSnippetWithOptions(ctx, {}); +export async function debugSnippet( + ctx: WorkspaceContext, + snippet?: string +): Promise { + return await debugSnippetWithOptions(ctx, {}, snippet); } export async function debugSnippetWithOptions( ctx: WorkspaceContext, - options: vscode.DebugSessionOptions + options: vscode.DebugSessionOptions, + snippet?: string ): Promise { + // create build task + let snippetName: string; + if (snippet) { + snippetName = snippet; + } else if (ctx.currentDocument) { + snippetName = path.basename(ctx.currentDocument.fsPath, ".swift"); + } else { + return false; + } + const folderContext = ctx.currentFolder; - if (!ctx.currentDocument || !folderContext) { - return; + if (!folderContext) { + return false; } - // create build task - const snippetName = path.basename(ctx.currentDocument.fsPath, ".swift"); const snippetBuildTask = createSwiftTask( ["build", "--product", snippetName], `Build ${snippetName}`, @@ -84,26 +99,29 @@ export async function debugSnippetWithOptions( }, ctx.toolchain ); - + const snippetDebugConfig = createSnippetConfiguration(snippetName, folderContext); try { + ctx.buildStarted(snippetName, snippetDebugConfig, options); + // queue build task and when it is complete run executable in the debugger return await folderContext.taskQueue .queueOperation(new TaskOperation(snippetBuildTask)) .then(result => { if (result === 0) { - const snippetDebugConfig = createSnippetConfiguration( - snippetName, - folderContext - ); return debugLaunchConfig( folderContext.workspaceFolder, snippetDebugConfig, options ); } + }) + .then(result => { + ctx.buildFinished(snippetName, snippetDebugConfig, options); + return result; }); - } catch { + } catch (error) { + ctx.outputChannel.appendLine(`Failed to debug snippet: ${error}`); // ignore error if task failed to run - return; + return false; } } diff --git a/src/TestExplorer/TestRunner.ts b/src/TestExplorer/TestRunner.ts index c42294239..ecd742c61 100644 --- a/src/TestExplorer/TestRunner.ts +++ b/src/TestExplorer/TestRunner.ts @@ -520,6 +520,18 @@ export class TestRunner { ]; } + /** + * Extracts a list of unique test Targets from the list of test items. + */ + private testTargets(items: vscode.TestItem[]): string[] { + const targets = new Set(); + for (const item of items) { + const target = item.id.split(".")[0]; + targets.add(target); + } + return Array.from(targets); + } + /** * Test run handler. Run a series of tests and extracts the results from the output * @param shouldDebug Should we run the debugger @@ -527,6 +539,9 @@ export class TestRunner { * @returns When complete */ async runHandler() { + const testTargets = this.testTargets(this.testArgs.testItems); + this.workspaceContext.testsStarted(this.folderContext, this.testKind, testTargets); + const runState = new TestRunnerTestRunState(this.testRun); const cancellationDisposable = this.testRun.token.onCancellationRequested(() => { @@ -551,6 +566,8 @@ export class TestRunner { cancellationDisposable.dispose(); await this.testRun.end(); + + this.workspaceContext.testsFinished(this.folderContext, this.testKind, testTargets); } /** Run test session without attaching to a debugger */ @@ -979,11 +996,6 @@ export class TestRunner { ); } - // show test results pane - vscode.commands.executeCommand( - "testing.showMostRecentOutput" - ); - const terminateSession = vscode.debug.onDidTerminateDebugSession(() => { this.workspaceContext.outputChannel.logDiagnostic( diff --git a/src/WorkspaceContext.ts b/src/WorkspaceContext.ts index b3a635815..648b6776d 100644 --- a/src/WorkspaceContext.ts +++ b/src/WorkspaceContext.ts @@ -33,6 +33,7 @@ import { SwiftToolchain } from "./toolchain/toolchain"; import { DiagnosticsManager } from "./DiagnosticsManager"; import { DocumentationManager } from "./documentation/DocumentationManager"; import { DocCDocumentationRequest, ReIndexProjectRequest } from "./sourcekit-lsp/extensions"; +import { TestKind } from "./TestExplorer/TestKind"; /** * Context for whole workspace. Holds array of contexts for each workspace folder @@ -53,6 +54,17 @@ export class WorkspaceContext implements vscode.Disposable { private lastFocusUri: vscode.Uri | undefined; private initialisationFinished = false; + private readonly testStartEmitter = new vscode.EventEmitter(); + private readonly testFinishEmitter = new vscode.EventEmitter(); + + public onDidStartTests = this.testStartEmitter.event; + public onDidFinishTests = this.testFinishEmitter.event; + + private readonly buildStartEmitter = new vscode.EventEmitter(); + private readonly buildFinishEmitter = new vscode.EventEmitter(); + public onDidStartBuild = this.buildStartEmitter.event; + public onDidFinishBuild = this.buildFinishEmitter.event; + private constructor( extensionContext: vscode.ExtensionContext, public tempFolder: TemporaryFolder, @@ -336,6 +348,30 @@ export class WorkspaceContext implements vscode.Disposable { await this.fireEvent(folderContext, FolderOperation.focus); } + public testsFinished(folder: FolderContext, kind: TestKind, targets: string[]) { + this.testFinishEmitter.fire({ kind, folder, targets }); + } + + public testsStarted(folder: FolderContext, kind: TestKind, targets: string[]) { + this.testStartEmitter.fire({ kind, folder, targets }); + } + + public buildStarted( + targetName: string, + launchConfig: vscode.DebugConfiguration, + options: vscode.DebugSessionOptions + ) { + this.buildStartEmitter.fire({ targetName, launchConfig, options }); + } + + public buildFinished( + targetName: string, + launchConfig: vscode.DebugConfiguration, + options: vscode.DebugSessionOptions + ) { + this.buildFinishEmitter.fire({ targetName, launchConfig, options }); + } + /** * catch workspace folder changes and add or remove folders based on those changes * @param event workspace folder event @@ -594,6 +630,20 @@ export class WorkspaceContext implements vscode.Disposable { private swiftFileObservers = new Set<(listener: SwiftFileEvent) => unknown>(); } +/** Test events for test run begin/end */ +interface TestEvent { + kind: TestKind; + folder: FolderContext; + targets: string[]; +} + +/** Build events for build + run start/stop */ +interface BuildEvent { + targetName: string; + launchConfig: vscode.DebugConfiguration; + options: vscode.DebugSessionOptions; +} + /** Workspace Folder Operation types */ export enum FolderOperation { // Package folder has been added @@ -612,6 +662,8 @@ export enum FolderOperation { workspaceStateUpdated = "workspaceStateUpdated", // .build/workspace-state.json has been updated packageViewUpdated = "packageViewUpdated", + // Package plugins list has been updated + pluginsUpdated = "pluginsUpdated", } /** Workspace Folder Event */ diff --git a/src/commands.ts b/src/commands.ts index b88e6cecb..a28979167 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -14,7 +14,7 @@ import * as vscode from "vscode"; import { WorkspaceContext } from "./WorkspaceContext"; -import { PackageNode } from "./ui/PackageDependencyProvider"; +import { PackageNode } from "./ui/ProjectPanelProvider"; import { SwiftToolchain } from "./toolchain/toolchain"; import { debugSnippet, runSnippet } from "./SwiftSnippets"; import { showToolchainSelectionQuickPick } from "./ui/ToolchainSelection"; @@ -38,8 +38,10 @@ import { updateDependencies } from "./commands/dependencies/update"; import { runPluginTask } from "./commands/runPluginTask"; import { runTestMultipleTimes } from "./commands/testMultipleTimes"; import { newSwiftFile } from "./commands/newFile"; -import { runAllTestsParallel } from "./commands/runParallelTests"; +import { runAllTests } from "./commands/runAllTests"; import { updateDependenciesViewList } from "./commands/dependencies/updateDepViewList"; +import { runTask } from "./commands/runTask"; +import { TestKind } from "./TestExplorer/TestKind"; /** * References: @@ -77,8 +79,15 @@ export enum Commands { RESET_PACKAGE = "swift.resetPackage", USE_LOCAL_DEPENDENCY = "swift.useLocalDependency", UNEDIT_DEPENDENCY = "swift.uneditDependency", + RUN_TASK = "swift.runTask", RUN_PLUGIN_TASK = "swift.runPluginTask", + RUN_SNIPPET = "swift.runSnippet", + DEBUG_SNIPPET = "swift.debugSnippet", PREVIEW_DOCUMENTATION = "swift.previewDocumentation", + RUN_ALL_TESTS = "swift.runAllTests", + RUN_ALL_TESTS_PARALLEL = "swift.runAllTestsParallel", + DEBUG_ALL_TESTS = "swift.debugAllTests", + COVER_ALL_TESTS = "swift.coverAllTests", } /** @@ -93,8 +102,12 @@ export function register(ctx: WorkspaceContext): vscode.Disposable[] { vscode.commands.registerCommand(Commands.UPDATE_DEPENDENCIES, () => updateDependencies(ctx) ), - vscode.commands.registerCommand(Commands.RUN, () => runBuild(ctx)), - vscode.commands.registerCommand(Commands.DEBUG, () => debugBuild(ctx)), + vscode.commands.registerCommand(Commands.RUN, target => + runBuild(ctx, ...unwrapTreeItem(target)) + ), + vscode.commands.registerCommand(Commands.DEBUG, target => + debugBuild(ctx, ...unwrapTreeItem(target)) + ), vscode.commands.registerCommand(Commands.CLEAN_BUILD, () => cleanBuild(ctx)), vscode.commands.registerCommand(Commands.RUN_TESTS_MULTIPLE_TIMES, item => { if (ctx.currentFolder) { @@ -115,9 +128,14 @@ export function register(ctx: WorkspaceContext): vscode.Disposable[] { return openPackage(ctx.toolchain.swiftVersion, ctx.currentFolder.folder); } }), - vscode.commands.registerCommand("swift.runSnippet", () => runSnippet(ctx)), - vscode.commands.registerCommand("swift.debugSnippet", () => debugSnippet(ctx)), + vscode.commands.registerCommand(Commands.RUN_SNIPPET, target => + runSnippet(ctx, ...unwrapTreeItem(target)) + ), + vscode.commands.registerCommand(Commands.DEBUG_SNIPPET, target => + debugSnippet(ctx, ...unwrapTreeItem(target)) + ), vscode.commands.registerCommand(Commands.RUN_PLUGIN_TASK, () => runPluginTask()), + vscode.commands.registerCommand(Commands.RUN_TASK, name => runTask(ctx, name)), vscode.commands.registerCommand("swift.restartLSPServer", () => ctx.languageClientManager.restart() ), @@ -156,8 +174,20 @@ export function register(ctx: WorkspaceContext): vscode.Disposable[] { ), vscode.commands.registerCommand("swift.captureDiagnostics", () => captureDiagnostics(ctx)), vscode.commands.registerCommand( - "swift.runAllTestsParallel", - async () => await runAllTestsParallel(ctx) + Commands.RUN_ALL_TESTS_PARALLEL, + async item => await runAllTests(ctx, TestKind.parallel, ...unwrapTreeItem(item)) + ), + vscode.commands.registerCommand( + Commands.RUN_ALL_TESTS, + async item => await runAllTests(ctx, TestKind.standard, ...unwrapTreeItem(item)) + ), + vscode.commands.registerCommand( + Commands.DEBUG_ALL_TESTS, + async item => await runAllTests(ctx, TestKind.debug, ...unwrapTreeItem(item)) + ), + vscode.commands.registerCommand( + Commands.COVER_ALL_TESTS, + async item => await runAllTests(ctx, TestKind.coverage, ...unwrapTreeItem(item)) ), vscode.commands.registerCommand( Commands.PREVIEW_DOCUMENTATION, @@ -171,3 +201,16 @@ export function register(ctx: WorkspaceContext): vscode.Disposable[] { ), ]; } + +/** + * Certain commands can be called via a vscode TreeView, which will pass a {@link CommandNode} object. + * If the command is called via a command palette or other means, the target will be a string. + */ +function unwrapTreeItem(target?: string | { args: string[] }): string[] { + if (typeof target === "object" && target !== null && "args" in target) { + return target.args ?? []; + } else if (typeof target === "string") { + return [target]; + } + return []; +} diff --git a/src/commands/build.ts b/src/commands/build.ts index 03e66489a..1ce2744d3 100644 --- a/src/commands/build.ts +++ b/src/commands/build.ts @@ -18,19 +18,20 @@ import { createSwiftTask, SwiftTaskProvider } from "../tasks/SwiftTaskProvider"; import { debugLaunchConfig, getLaunchConfiguration } from "../debugger/launch"; import { executeTaskWithUI } from "./utilities"; import { FolderContext } from "../FolderContext"; +import { Target } from "../SwiftPackage"; /** * Executes a {@link vscode.Task task} to run swift target. */ -export async function runBuild(ctx: WorkspaceContext) { - return await debugBuildWithOptions(ctx, { noDebug: true }); +export async function runBuild(ctx: WorkspaceContext, target?: string) { + return await debugBuildWithOptions(ctx, { noDebug: true }, target); } /** * Executes a {@link vscode.Task task} to debug swift target. */ -export async function debugBuild(ctx: WorkspaceContext) { - return await debugBuildWithOptions(ctx, {}); +export async function debugBuild(ctx: WorkspaceContext, target?: string) { + return await debugBuildWithOptions(ctx, {}, target); } /** @@ -70,7 +71,8 @@ export async function folderCleanBuild(folderContext: FolderContext) { */ export async function debugBuildWithOptions( ctx: WorkspaceContext, - options: vscode.DebugSessionOptions + options: vscode.DebugSessionOptions, + targetName?: string ) { const current = ctx.currentFolder; if (!current) { @@ -80,13 +82,19 @@ export async function debugBuildWithOptions( return; } - const file = vscode.window.activeTextEditor?.document.fileName; - if (!file) { - ctx.outputChannel.appendLine("debugBuildWithOptions: No active text editor"); - return; + let target: Target | undefined; + if (targetName) { + target = current.swiftPackage.targets.find(target => target.name === targetName); + } else { + const file = vscode.window.activeTextEditor?.document.fileName; + if (!file) { + ctx.outputChannel.appendLine("debugBuildWithOptions: No active text editor"); + return; + } + + target = current.swiftPackage.getTarget(file); } - const target = current.swiftPackage.getTarget(file); if (!target) { ctx.outputChannel.appendLine("debugBuildWithOptions: No active target"); return; @@ -101,6 +109,9 @@ export async function debugBuildWithOptions( const launchConfig = getLaunchConfiguration(target.name, current); if (launchConfig) { - return debugLaunchConfig(current.workspaceFolder, launchConfig, options); + ctx.buildStarted(target.name, launchConfig, options); + const result = await debugLaunchConfig(current.workspaceFolder, launchConfig, options); + ctx.buildFinished(target.name, launchConfig, options); + return result; } } diff --git a/src/commands/openInExternalEditor.ts b/src/commands/openInExternalEditor.ts index 29f4114ab..6dc621765 100644 --- a/src/commands/openInExternalEditor.ts +++ b/src/commands/openInExternalEditor.ts @@ -13,7 +13,7 @@ //===----------------------------------------------------------------------===// import * as vscode from "vscode"; -import { PackageNode } from "../ui/PackageDependencyProvider"; +import { PackageNode } from "../ui/ProjectPanelProvider"; /** * Opens the supplied `PackageNode` externally using the default application. diff --git a/src/commands/openInWorkspace.ts b/src/commands/openInWorkspace.ts index 7b4b9601d..dda3903c0 100644 --- a/src/commands/openInWorkspace.ts +++ b/src/commands/openInWorkspace.ts @@ -13,7 +13,7 @@ //===----------------------------------------------------------------------===// import * as vscode from "vscode"; -import { PackageNode } from "../ui/PackageDependencyProvider"; +import { PackageNode } from "../ui/ProjectPanelProvider"; /** * Open a local package in workspace diff --git a/src/commands/runParallelTests.ts b/src/commands/runAllTests.ts similarity index 64% rename from src/commands/runParallelTests.ts rename to src/commands/runAllTests.ts index 11970b803..f629417ad 100644 --- a/src/commands/runParallelTests.ts +++ b/src/commands/runAllTests.ts @@ -17,23 +17,29 @@ import { TestKind } from "../TestExplorer/TestKind"; import { WorkspaceContext } from "../WorkspaceContext"; import { flattenTestItemCollection } from "../TestExplorer/TestUtils"; -export async function runAllTestsParallel(ctx: WorkspaceContext) { +export async function runAllTests(ctx: WorkspaceContext, testKind: TestKind, target?: string) { const testExplorer = ctx.currentFolder?.testExplorer; if (testExplorer === undefined) { return; } - const profile = testExplorer.testRunProfiles.find( - profile => profile.label === TestKind.parallel - ); + const profile = testExplorer.testRunProfiles.find(profile => profile.label === testKind); if (profile === undefined) { return; } - const tests = flattenTestItemCollection(testExplorer.controller.items); + let tests = flattenTestItemCollection(testExplorer.controller.items); + + // If a target is specified, filter the tests to only run those that match the target. + if (target) { + const targetRegex = new RegExp(`^${target}(\\.|$)`); + tests = tests.filter(test => targetRegex.test(test.id)); + } const tokenSource = new vscode.CancellationTokenSource(); await profile.runHandler( new vscode.TestRunRequest(tests, undefined, profile), tokenSource.token ); + + await vscode.commands.executeCommand("testing.showMostRecentOutput"); } diff --git a/src/commands/runTask.ts b/src/commands/runTask.ts new file mode 100644 index 000000000..3bca73535 --- /dev/null +++ b/src/commands/runTask.ts @@ -0,0 +1,43 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the VS Code Swift open source project +// +// Copyright (c) 2021-2024 the VS Code Swift project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of VS Code Swift project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import * as vscode from "vscode"; +import { WorkspaceContext } from "../WorkspaceContext"; +import { TaskOperation } from "../tasks/TaskQueue"; +import { SwiftPluginTaskProvider } from "../tasks/SwiftPluginTaskProvider"; + +export const runTask = async (ctx: WorkspaceContext, name: string) => { + if (!ctx.currentFolder) { + return; + } + + const tasks = await vscode.tasks.fetchTasks(); + let task = tasks.find(task => task.name === name); + if (!task) { + const pluginTaskProvider = new SwiftPluginTaskProvider(ctx); + const pluginTasks = await pluginTaskProvider.provideTasks( + new vscode.CancellationTokenSource().token + ); + task = pluginTasks.find(task => task.name === name); + } + + if (!task) { + vscode.window.showErrorMessage(`Task "${name}" not found`); + return; + } + + return ctx.currentFolder.taskQueue + .queueOperation(new TaskOperation(task)) + .then(result => result === 0); +}; diff --git a/src/debugger/launch.ts b/src/debugger/launch.ts index 2b0d2a08b..b91836828 100644 --- a/src/debugger/launch.ts +++ b/src/debugger/launch.ts @@ -181,6 +181,7 @@ export function createSnippetConfiguration( args: [], cwd: folder, env: swiftRuntimeEnv(true), + runType: "snippet", ...CI_DISABLE_ASLR, }; } diff --git a/src/extension.ts b/src/extension.ts index 661696230..15e45be46 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -18,7 +18,7 @@ import "source-map-support/register"; import * as vscode from "vscode"; import * as commands from "./commands"; import * as debug from "./debugger/launch"; -import { PackageDependenciesProvider } from "./ui/PackageDependencyProvider"; +import { ProjectPanelProvider } from "./ui/ProjectPanelProvider"; import { SwiftTaskProvider } from "./tasks/SwiftTaskProvider"; import { FolderOperation, WorkspaceContext } from "./WorkspaceContext"; import { FolderContext } from "./FolderContext"; @@ -160,13 +160,13 @@ export async function activate(context: vscode.ExtensionContext): Promise { ); }); - // dependency view - const dependenciesProvider = new PackageDependenciesProvider(workspaceContext); - const dependenciesView = vscode.window.createTreeView("packageDependencies", { - treeDataProvider: dependenciesProvider, + // project panel provider + const projectPanelProvider = new ProjectPanelProvider(workspaceContext); + const dependenciesView = vscode.window.createTreeView("projectPanel", { + treeDataProvider: projectPanelProvider, showCollapseAll: true, }); - dependenciesProvider.observeFolders(dependenciesView); + projectPanelProvider.observeFolders(dependenciesView); // observer that will resolve package and build launch configurations const resolvePackageObserver = workspaceContext.onDidChangeFolders( @@ -189,6 +189,7 @@ export async function activate(context: vscode.ExtensionContext): Promise { } else { await resolveFolderDependencies(folder, true); } + if ( workspace.toolchain.swiftVersion.isGreaterThanOrEqual( new Version(5, 6, 0) @@ -201,6 +202,7 @@ export async function activate(context: vscode.ExtensionContext): Promise { async () => { await folder.loadSwiftPlugins(); workspace.updatePluginContextKey(); + folder.fireEvent(FolderOperation.pluginsUpdated); } ); } @@ -252,7 +254,7 @@ export async function activate(context: vscode.ExtensionContext): Promise { testExplorerObserver, swiftModuleDocumentProvider, dependenciesView, - dependenciesProvider, + projectPanelProvider, logObserver, languageStatusItem, pluginTaskProvider, diff --git a/src/ui/PackageDependencyProvider.ts b/src/ui/PackageDependencyProvider.ts deleted file mode 100644 index 6cd038935..000000000 --- a/src/ui/PackageDependencyProvider.ts +++ /dev/null @@ -1,269 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the VS Code Swift open source project -// -// Copyright (c) 2021 the VS Code Swift project authors -// Licensed under Apache License v2.0 -// -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of VS Code Swift project authors -// -// SPDX-License-Identifier: Apache-2.0 -// -//===----------------------------------------------------------------------===// - -import * as vscode from "vscode"; -import * as fs from "fs/promises"; -import * as path from "path"; -import configuration from "../configuration"; -import { WorkspaceContext } from "../WorkspaceContext"; -import { FolderOperation } from "../WorkspaceContext"; -import contextKeys from "../contextKeys"; -import { Dependency, ResolvedDependency } from "../SwiftPackage"; - -/** - * References: - * - * - Contributing views: - * https://code.visualstudio.com/api/references/contribution-points#contributes.views - * - Contributing welcome views: - * https://code.visualstudio.com/api/references/contribution-points#contributes.viewsWelcome - * - Implementing a TreeView: - * https://code.visualstudio.com/api/extension-guides/tree-view - */ - -/** - * Returns a {@link FileNode} for every file or subdirectory - * in the given directory. - */ -async function getChildren(directoryPath: string, parentId?: string): Promise { - const contents = await fs.readdir(directoryPath); - const results: FileNode[] = []; - const excludes = configuration.excludePathsFromPackageDependencies; - for (const fileName of contents) { - if (excludes.includes(fileName)) { - continue; - } - const filePath = path.join(directoryPath, fileName); - const stats = await fs.stat(filePath); - results.push(new FileNode(fileName, filePath, stats.isDirectory(), parentId)); - } - return results.sort((first, second) => { - if (first.isDirectory === second.isDirectory) { - // If both nodes are of the same type, sort them by name. - return first.name.localeCompare(second.name); - } else { - // Otherwise, sort directories first. - return first.isDirectory ? -1 : 1; - } - }); -} - -/** - * A package in the Package Dependencies {@link vscode.TreeView TreeView}. - */ -export class PackageNode { - private id: string; - - constructor( - private dependency: ResolvedDependency, - private childDependencies: (dependency: Dependency) => ResolvedDependency[], - private parentId?: string - ) { - this.id = - (this.parentId ? `${this.parentId}->` : "") + - `${this.name}-${this.dependency.version ?? ""}`; - } - - get name(): string { - return this.dependency.identity; - } - - get location(): string { - return this.dependency.location; - } - - get type(): string { - return this.dependency.type; - } - - get path(): string { - return this.dependency.path ?? ""; - } - - toTreeItem(): vscode.TreeItem { - const item = new vscode.TreeItem(this.name, vscode.TreeItemCollapsibleState.Collapsed); - item.id = this.id; - item.description = this.dependency.version; - item.iconPath = - this.dependency.type === "editing" - ? new vscode.ThemeIcon("edit") - : new vscode.ThemeIcon("package"); - item.contextValue = this.dependency.type; - item.accessibilityInformation = { label: `Package ${this.name}` }; - item.tooltip = this.path; - return item; - } - - async getChildren(): Promise { - const [childDeps, files] = await Promise.all([ - this.childDependencies(this.dependency), - getChildren(this.dependency.path, this.id), - ]); - const childNodes = childDeps.map( - dep => new PackageNode(dep, this.childDependencies, this.id) - ); - - // Show dependencies first, then files. - return [...childNodes, ...files]; - } -} - -/** - * A file or directory in the Package Dependencies {@link vscode.TreeView TreeView}. - */ -export class FileNode { - private id: string; - - constructor( - public name: string, - public path: string, - public isDirectory: boolean, - private parentId?: string - ) { - this.id = (this.parentId ? `${this.parentId}->` : "") + `${this.path}`; - } - - toTreeItem(): vscode.TreeItem { - const item = new vscode.TreeItem( - this.name, - this.isDirectory - ? vscode.TreeItemCollapsibleState.Collapsed - : vscode.TreeItemCollapsibleState.None - ); - item.id = this.id; - item.resourceUri = vscode.Uri.file(this.path); - item.tooltip = this.path; - if (!this.isDirectory) { - item.command = { - command: "vscode.open", - arguments: [item.resourceUri], - title: "Open File", - }; - item.accessibilityInformation = { label: `File ${this.name}` }; - } else { - item.accessibilityInformation = { label: `Folder ${this.name}` }; - } - return item; - } - - async getChildren(): Promise { - return await getChildren(this.path, this.id); - } -} - -/** - * A node in the Package Dependencies {@link vscode.TreeView TreeView}. - * - * Can be either a {@link PackageNode} or a {@link FileNode}. - */ -type TreeNode = PackageNode | FileNode; - -/** - * A {@link vscode.TreeDataProvider TreeDataProvider} for the Package Dependencies {@link vscode.TreeView TreeView}. - */ -export class PackageDependenciesProvider implements vscode.TreeDataProvider { - private didChangeTreeDataEmitter = new vscode.EventEmitter< - TreeNode | undefined | null | void - >(); - private workspaceObserver?: vscode.Disposable; - - onDidChangeTreeData = this.didChangeTreeDataEmitter.event; - - constructor(private workspaceContext: WorkspaceContext) { - // default context key to false. These will be updated as folders are given focus - contextKeys.hasPackage = false; - contextKeys.packageHasDependencies = false; - } - - dispose() { - this.workspaceObserver?.dispose(); - } - - observeFolders(treeView: vscode.TreeView) { - this.workspaceObserver = this.workspaceContext.onDidChangeFolders( - ({ folder, operation }) => { - switch (operation) { - case FolderOperation.focus: - if (!folder) { - return; - } - treeView.title = `Package Dependencies (${folder.name})`; - this.didChangeTreeDataEmitter.fire(); - break; - case FolderOperation.unfocus: - treeView.title = `Package Dependencies`; - this.didChangeTreeDataEmitter.fire(); - break; - case FolderOperation.workspaceStateUpdated: - case FolderOperation.resolvedUpdated: - case FolderOperation.packageViewUpdated: - if (!folder) { - return; - } - if (folder === this.workspaceContext.currentFolder) { - this.didChangeTreeDataEmitter.fire(); - } - } - } - ); - } - - getTreeItem(element: TreeNode): vscode.TreeItem { - return element.toTreeItem(); - } - - async getChildren(element?: TreeNode): Promise { - const folderContext = this.workspaceContext.currentFolder; - if (!folderContext) { - return []; - } - if (!element) { - if (contextKeys.flatDependenciesList) { - const existenceMap = new Map(); - const gatherChildren = ( - dependencies: ResolvedDependency[] - ): ResolvedDependency[] => { - const result: ResolvedDependency[] = []; - for (const dep of dependencies) { - if (!existenceMap.has(dep.identity)) { - result.push(dep); - existenceMap.set(dep.identity, true); - } - const childDeps = folderContext.swiftPackage.childDependencies(dep); - result.push(...gatherChildren(childDeps)); - } - return result; - }; - - const rootDeps = folderContext.swiftPackage.rootDependencies(); - const allDeps = gatherChildren(rootDeps); - return allDeps.map(dependency => new PackageNode(dependency, () => [])); - } else { - return folderContext.swiftPackage - .rootDependencies() - .map( - dependency => - new PackageNode( - dependency, - folderContext.swiftPackage.childDependencies.bind( - folderContext.swiftPackage - ) - ) - ); - } - } else { - return await element.getChildren(); - } - } -} diff --git a/src/ui/ProjectPanelProvider.ts b/src/ui/ProjectPanelProvider.ts new file mode 100644 index 000000000..793bebe1f --- /dev/null +++ b/src/ui/ProjectPanelProvider.ts @@ -0,0 +1,558 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the VS Code Swift open source project +// +// Copyright (c) 2021 the VS Code Swift project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of VS Code Swift project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import * as vscode from "vscode"; +import * as fs from "fs/promises"; +import * as path from "path"; +import configuration from "../configuration"; +import { WorkspaceContext } from "../WorkspaceContext"; +import { FolderOperation } from "../WorkspaceContext"; +import contextKeys from "../contextKeys"; +import { Dependency, ResolvedDependency, Target } from "../SwiftPackage"; +import { SwiftPluginTaskProvider } from "../tasks/SwiftPluginTaskProvider"; + +const LOADING_ICON = "loading~spin"; +/** + * References: + * + * - Contributing views: + * https://code.visualstudio.com/api/references/contribution-points#contributes.views + * - Contributing welcome views: + * https://code.visualstudio.com/api/references/contribution-points#contributes.viewsWelcome + * - Implementing a TreeView: + * https://code.visualstudio.com/api/extension-guides/tree-view + */ + +/** + * Returns a {@link FileNode} for every file or subdirectory + * in the given directory. + */ +async function getChildren(directoryPath: string, parentId?: string): Promise { + const contents = await fs.readdir(directoryPath); + const results: FileNode[] = []; + const excludes = configuration.excludePathsFromPackageDependencies; + for (const fileName of contents) { + if (excludes.includes(fileName)) { + continue; + } + const filePath = path.join(directoryPath, fileName); + const stats = await fs.stat(filePath); + results.push(new FileNode(fileName, filePath, stats.isDirectory(), parentId)); + } + return results.sort((first, second) => { + if (first.isDirectory === second.isDirectory) { + // If both nodes are of the same type, sort them by name. + return first.name.localeCompare(second.name); + } else { + // Otherwise, sort directories first. + return first.isDirectory ? -1 : 1; + } + }); +} + +/** + * A package in the Package Dependencies {@link vscode.TreeView TreeView}. + */ +export class PackageNode { + private id: string; + + constructor( + private dependency: ResolvedDependency, + private childDependencies: (dependency: Dependency) => ResolvedDependency[], + private parentId?: string + ) { + this.id = + (this.parentId ? `${this.parentId}->` : "") + + `${this.name}-${this.dependency.version ?? ""}`; + } + + get name(): string { + return this.dependency.identity; + } + + get location(): string { + return this.dependency.location; + } + + get type(): string { + return this.dependency.type; + } + + get path(): string { + return this.dependency.path ?? ""; + } + + toTreeItem(): vscode.TreeItem { + const item = new vscode.TreeItem(this.name, vscode.TreeItemCollapsibleState.Collapsed); + item.id = this.id; + item.description = this.dependency.version; + item.iconPath = new vscode.ThemeIcon(this.icon()); + item.contextValue = this.dependency.type; + item.accessibilityInformation = { label: `Package ${this.name}` }; + item.tooltip = this.path; + return item; + } + + icon() { + if (this.dependency.type === "editing") { + return "edit"; + } + if (this.dependency.type === "local") { + return "notebook-render-output"; + } + return "package"; + } + + async getChildren(): Promise { + const [childDeps, files] = await Promise.all([ + this.childDependencies(this.dependency), + getChildren(this.dependency.path, this.id), + ]); + const childNodes = childDeps.map( + dep => new PackageNode(dep, this.childDependencies, this.id) + ); + + // Show dependencies first, then files. + return [...childNodes, ...files]; + } +} + +/** + * A file or directory in the Package Dependencies {@link vscode.TreeView TreeView}. + */ +export class FileNode { + private id: string; + + constructor( + public name: string, + public path: string, + public isDirectory: boolean, + private parentId?: string + ) { + this.id = (this.parentId ? `${this.parentId}->` : "") + `${this.path}`; + } + + toTreeItem(): vscode.TreeItem { + const item = new vscode.TreeItem( + this.name, + this.isDirectory + ? vscode.TreeItemCollapsibleState.Collapsed + : vscode.TreeItemCollapsibleState.None + ); + item.id = this.id; + item.resourceUri = vscode.Uri.file(this.path); + item.tooltip = this.path; + if (!this.isDirectory) { + item.command = { + command: "vscode.open", + arguments: [item.resourceUri], + title: "Open File", + }; + item.accessibilityInformation = { label: `File ${this.name}` }; + } else { + item.accessibilityInformation = { label: `Folder ${this.name}` }; + } + return item; + } + + async getChildren(): Promise { + return await getChildren(this.path, this.id); + } +} + +class TaskNode { + constructor( + public type: string, + public name: string, + private active: boolean + ) {} + + toTreeItem(): vscode.TreeItem { + const item = new vscode.TreeItem(this.name, vscode.TreeItemCollapsibleState.None); + item.id = `${this.type}-${this.name}`; + item.iconPath = new vscode.ThemeIcon(this.active ? LOADING_ICON : "play"); + item.contextValue = "task"; + item.accessibilityInformation = { label: this.name }; + item.command = { + command: "swift.runTask", + arguments: [this.name], + title: "Run Task", + }; + return item; + } + + getChildren(): TreeNode[] { + return []; + } +} + +/* + * Prefix a unique string on the test target name to avoid confusing it + * with another target that may share the same name. Targets can't start with % + * so this is guarenteed to be unique. + */ +function testTaskName(name: string): string { + return `%test-${name}`; +} + +function snippetTaskName(name: string): string { + return `%snippet-${name}`; +} + +class TargetNode { + constructor( + public target: Target, + private activeTasks: Set + ) {} + + get name(): string { + return this.target.name; + } + + get args(): string[] { + return [this.name]; + } + + toTreeItem(): vscode.TreeItem { + const name = this.target.name; + const hasChildren = this.getChildren().length > 0; + const item = new vscode.TreeItem( + name, + hasChildren + ? vscode.TreeItemCollapsibleState.Expanded + : vscode.TreeItemCollapsibleState.None + ); + item.id = `${this.target.type}:${name}`; + item.iconPath = new vscode.ThemeIcon(this.icon()); + item.contextValue = this.contextValue(); + item.accessibilityInformation = { label: name }; + return item; + } + + private icon(): string { + if (this.activeTasks.has(this.name)) { + return LOADING_ICON; + } + + switch (this.target.type) { + case "executable": + return "output"; + case "library": + return "library"; + case "test": + if (this.activeTasks.has(testTaskName(this.name))) { + return LOADING_ICON; + } + return "test-view-icon"; + case "snippet": + if (this.activeTasks.has(snippetTaskName(this.name))) { + return LOADING_ICON; + } + return "notebook"; + case "plugin": + return "plug"; + } + } + + private contextValue(): string | undefined { + switch (this.target.type) { + case "executable": + return "runnable"; + case "snippet": + return "snippet_runnable"; + case "test": + return "test_runnable"; + default: + return undefined; + } + } + + getChildren(): TreeNode[] { + return []; + } +} + +class HeaderNode { + constructor( + private id: string, + public name: string, + private icon: string, + private _getChildren: () => Promise + ) {} + + get path(): string { + return ""; + } + + toTreeItem(): vscode.TreeItem { + const item = new vscode.TreeItem(this.name, vscode.TreeItemCollapsibleState.Collapsed); + item.id = `${this.id}-${this.name}`; + item.iconPath = new vscode.ThemeIcon(this.icon); + item.contextValue = "header"; + item.accessibilityInformation = { label: this.name }; + return item; + } + + getChildren(): Promise { + return this._getChildren(); + } +} + +/** + * A node in the Package Dependencies {@link vscode.TreeView TreeView}. + * + * Can be either a {@link PackageNode}, {@link FileNode}, {@link TargetNode}, {@link TaskNode} or {@link HeaderNode}. + */ +type TreeNode = PackageNode | FileNode | HeaderNode | TaskNode | TargetNode; + +/** + * A {@link vscode.TreeDataProvider TreeDataProvider} for project dependencies, tasks and commands {@link vscode.TreeView TreeView}. + */ +export class ProjectPanelProvider implements vscode.TreeDataProvider { + private didChangeTreeDataEmitter = new vscode.EventEmitter< + TreeNode | undefined | null | void + >(); + private workspaceObserver?: vscode.Disposable; + private disposables: vscode.Disposable[] = []; + private activeTasks: Set = new Set(); + + onDidChangeTreeData = this.didChangeTreeDataEmitter.event; + + constructor(private workspaceContext: WorkspaceContext) { + // default context key to false. These will be updated as folders are given focus + contextKeys.hasPackage = false; + contextKeys.packageHasDependencies = false; + + this.observeTasks(workspaceContext); + } + + dispose() { + this.workspaceObserver?.dispose(); + } + + observeTasks(ctx: WorkspaceContext) { + this.disposables.push( + vscode.tasks.onDidStartTask(e => { + const taskId = e.execution.task.detail ?? e.execution.task.name; + this.activeTasks.add(taskId); + this.didChangeTreeDataEmitter.fire(); + }), + vscode.tasks.onDidEndTask(e => { + const taskId = e.execution.task.detail ?? e.execution.task.name; + this.activeTasks.delete(taskId); + this.didChangeTreeDataEmitter.fire(); + }), + ctx.onDidStartBuild(e => { + if (e.launchConfig.runType === "snippet") { + this.activeTasks.add(snippetTaskName(e.targetName)); + } else { + this.activeTasks.add(e.targetName); + } + this.didChangeTreeDataEmitter.fire(); + }), + ctx.onDidFinishBuild(e => { + if (e.launchConfig.runType === "snippet") { + this.activeTasks.delete(snippetTaskName(e.targetName)); + } else { + this.activeTasks.delete(e.targetName); + } + this.didChangeTreeDataEmitter.fire(); + }), + ctx.onDidStartTests(e => { + for (const target of e.targets) { + this.activeTasks.add(testTaskName(target)); + } + this.didChangeTreeDataEmitter.fire(); + }), + ctx.onDidFinishTests(e => { + for (const target of e.targets) { + this.activeTasks.delete(testTaskName(target)); + } + this.didChangeTreeDataEmitter.fire(); + }) + ); + } + + observeFolders(treeView: vscode.TreeView) { + this.workspaceObserver = this.workspaceContext.onDidChangeFolders( + ({ folder, operation }) => { + switch (operation) { + case FolderOperation.focus: + if (!folder) { + return; + } + treeView.title = `Swift Project (${folder.name})`; + this.didChangeTreeDataEmitter.fire(); + break; + case FolderOperation.unfocus: + treeView.title = `Swift Project`; + this.didChangeTreeDataEmitter.fire(); + break; + case FolderOperation.workspaceStateUpdated: + case FolderOperation.resolvedUpdated: + case FolderOperation.packageViewUpdated: + case FolderOperation.pluginsUpdated: + if (!folder) { + return; + } + if (folder === this.workspaceContext.currentFolder) { + this.didChangeTreeDataEmitter.fire(); + } + } + } + ); + } + + getTreeItem(element: TreeNode): vscode.TreeItem { + return element.toTreeItem(); + } + + async getChildren(element?: TreeNode): Promise { + const folderContext = this.workspaceContext.currentFolder; + if (!folderContext) { + return []; + } + + if (element) { + return element.getChildren(); + } + + const dependencies = this.dependencies(); + const snippets = this.snippets(); + const commands = await this.commands(); + + // TODO: Control ordering + return [ + ...(dependencies.length > 0 + ? [ + new HeaderNode( + "dependencies", + "Dependencies", + "circuit-board", + this.wrapInAsync(this.dependencies.bind(this)) + ), + ] + : []), + new HeaderNode("targets", "Targets", "book", this.wrapInAsync(this.targets.bind(this))), + new HeaderNode("tasks", "Tasks", "debug-continue-small", this.tasks.bind(this)), + ...(snippets.length > 0 + ? [ + new HeaderNode("snippets", "Snippets", "notebook", () => + Promise.resolve(snippets) + ), + ] + : []), + ...(commands.length > 0 + ? [ + new HeaderNode("commands", "Commands", "debug-line-by-line", () => + Promise.resolve(commands) + ), + ] + : []), + ]; + } + + private dependencies(): TreeNode[] { + const folderContext = this.workspaceContext.currentFolder; + if (!folderContext) { + return []; + } + const pkg = folderContext.swiftPackage; + if (contextKeys.flatDependenciesList) { + const existenceMap = new Map(); + const gatherChildren = (dependencies: ResolvedDependency[]): ResolvedDependency[] => { + const result: ResolvedDependency[] = []; + for (const dep of dependencies) { + if (!existenceMap.has(dep.identity)) { + result.push(dep); + existenceMap.set(dep.identity, true); + } + const childDeps = pkg.childDependencies(dep); + result.push(...gatherChildren(childDeps)); + } + return result; + }; + + const rootDeps = pkg.rootDependencies(); + const allDeps = gatherChildren(rootDeps); + return allDeps.map(dependency => new PackageNode(dependency, () => [])); + } else { + const childDeps = pkg.childDependencies.bind(pkg); + return pkg.rootDependencies().map(dep => new PackageNode(dep, childDeps)); + } + } + + private targets(): TreeNode[] { + const folderContext = this.workspaceContext.currentFolder; + if (!folderContext) { + return []; + } + const targetSort = (node: TargetNode) => `${node.target.type}-${node.name}`; + return ( + folderContext.swiftPackage.targets + // Snipepts are shown under the Snippets header + .filter(target => target.type !== "snippet") + .map(target => new TargetNode(target, this.activeTasks)) + .sort((a, b) => targetSort(a).localeCompare(targetSort(b))) + ); + } + + private async tasks(): Promise { + const tasks = await vscode.tasks.fetchTasks(); + return ( + tasks + // Plugin tasks are shown under the Commands header + .filter(task => task.source !== "swift-plugin") + .map( + task => + new TaskNode( + "task", + task.name, + this.activeTasks.has(task.detail ?? task.name) + ) + ) + .sort((a, b) => a.name.localeCompare(b.name)) + ); + } + + private async commands(): Promise { + const provider = new SwiftPluginTaskProvider(this.workspaceContext); + const tasks = await provider.provideTasks(new vscode.CancellationTokenSource().token); + return tasks + .map( + task => + new TaskNode( + "command", + task.name, + this.activeTasks.has(task.detail ?? task.name) + ) + ) + .sort((a, b) => a.name.localeCompare(b.name)); + } + + private snippets(): TreeNode[] { + const folderContext = this.workspaceContext.currentFolder; + if (!folderContext) { + return []; + } + return folderContext.swiftPackage.targets + .filter(target => target.type === "snippet") + .flatMap(target => new TargetNode(target, this.activeTasks)) + .sort((a, b) => a.name.localeCompare(b.name)); + } + + private wrapInAsync(fn: () => T): () => Promise { + return async () => fn(); + } +} diff --git a/src/ui/StatusItem.ts b/src/ui/StatusItem.ts index 107429845..3d78ec29a 100644 --- a/src/ui/StatusItem.ts +++ b/src/ui/StatusItem.ts @@ -116,9 +116,9 @@ export class StatusItem { private showTask(task: RunningTask, message?: string) { message = message ?? task.name; if (typeof task.task !== "string") { - this.show(`$(sync~spin) ${message}`, message, "workbench.action.tasks.showTasks"); + this.show(`$(loading~spin) ${message}`, message, "workbench.action.tasks.showTasks"); } else { - this.show(`$(sync~spin) ${message}`, message); + this.show(`$(loading~spin) ${message}`, message); } } diff --git a/test/integration-tests/commands/dependency.test.ts b/test/integration-tests/commands/dependency.test.ts index afa7e40e4..771d3c8a4 100644 --- a/test/integration-tests/commands/dependency.test.ts +++ b/test/integration-tests/commands/dependency.test.ts @@ -14,10 +14,7 @@ import { expect } from "chai"; import * as vscode from "vscode"; -import { - PackageDependenciesProvider, - PackageNode, -} from "../../../src/ui/PackageDependencyProvider"; +import { PackageNode, ProjectPanelProvider } from "../../../src/ui/ProjectPanelProvider"; import { testAssetUri } from "../../fixtures"; import { FolderContext } from "../../../src/FolderContext"; import { WorkspaceContext } from "../../../src/WorkspaceContext"; @@ -28,8 +25,8 @@ import { getBuildAllTask, SwiftTask } from "../../../src/tasks/SwiftTaskProvider suite("Dependency Commmands Test Suite", function () { // full workflow's interaction with spm is longer than the default timeout - // 60 seconds for each test should be more than enough - this.timeout(60 * 1000); + // 120 seconds for each test should be more than enough + this.timeout(120 * 1000); let defaultContext: FolderContext; let depsContext: FolderContext; @@ -57,12 +54,12 @@ suite("Dependency Commmands Test Suite", function () { }); suite("Swift: Use Local Dependency", function () { - let treeProvider: PackageDependenciesProvider; + let treeProvider: ProjectPanelProvider; setup(async () => { await workspaceContext.focusFolder(depsContext); await executeTaskAndWaitForResult((await getBuildAllTask(depsContext)) as SwiftTask); - treeProvider = new PackageDependenciesProvider(workspaceContext); + treeProvider = new ProjectPanelProvider(workspaceContext); }); teardown(() => { @@ -70,8 +67,11 @@ suite("Dependency Commmands Test Suite", function () { }); async function getDependency() { - const items = await treeProvider.getChildren(); - return items.find(n => n.name === "swift-markdown") as PackageNode; + const headers = await treeProvider.getChildren(); + const header = headers.find(n => n.name === "Dependencies") as PackageNode; + expect(header).to.not.be.undefined; + const children = await header.getChildren(); + return children.find(n => n.name === "swift-markdown") as PackageNode; } // Wait for the dependency to switch to the expected state. diff --git a/test/integration-tests/ui/PackageDependencyProvider.test.ts b/test/integration-tests/ui/PackageDependencyProvider.test.ts deleted file mode 100644 index 72b7ddaa3..000000000 --- a/test/integration-tests/ui/PackageDependencyProvider.test.ts +++ /dev/null @@ -1,166 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the VS Code Swift open source project -// -// Copyright (c) 2024 the VS Code Swift project authors -// Licensed under Apache License v2.0 -// -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of VS Code Swift project authors -// -// SPDX-License-Identifier: Apache-2.0 -// -//===----------------------------------------------------------------------===// - -import { expect } from "chai"; -import * as vscode from "vscode"; -import * as path from "path"; -import { - PackageDependenciesProvider, - PackageNode, -} from "../../../src/ui/PackageDependencyProvider"; -import { executeTaskAndWaitForResult, waitForNoRunningTasks } from "../../utilities/tasks"; -import { getBuildAllTask, SwiftTask } from "../../../src/tasks/SwiftTaskProvider"; -import { testAssetPath } from "../../fixtures"; -import { activateExtensionForSuite, folderInRootWorkspace } from "../utilities/testutilities"; -import contextKeys from "../../../src/contextKeys"; -import { FolderContext } from "../../../src/FolderContext"; -import { WorkspaceContext } from "../../../src/WorkspaceContext"; - -suite("PackageDependencyProvider Test Suite", function () { - let workspaceContext: WorkspaceContext; - let folderContext: FolderContext; - let treeProvider: PackageDependenciesProvider; - this.timeout(3 * 60 * 1000); // Allow up to 3 minutes to build - - activateExtensionForSuite({ - async setup(ctx) { - workspaceContext = ctx; - await waitForNoRunningTasks(); - folderContext = await folderInRootWorkspace("dependencies", workspaceContext); - await executeTaskAndWaitForResult((await getBuildAllTask(folderContext)) as SwiftTask); - await folderContext.reload(); - treeProvider = new PackageDependenciesProvider(workspaceContext); - await workspaceContext.focusFolder(folderContext); - }, - async teardown() { - contextKeys.flatDependenciesList = false; - treeProvider.dispose(); - }, - }); - - setup(async () => { - await workspaceContext.focusFolder(folderContext); - }); - - test("Includes remote dependency", async () => { - contextKeys.flatDependenciesList = false; - const items = await treeProvider.getChildren(); - - const dep = items.find(n => n.name === "swift-markdown") as PackageNode; - expect(dep, `${JSON.stringify(items, null, 2)}`).to.not.be.undefined; - expect(dep?.location).to.equal("https://github.com/swiftlang/swift-markdown.git"); - assertPathsEqual( - dep?.path, - path.join(testAssetPath("dependencies"), ".build/checkouts/swift-markdown") - ); - }); - - test("Includes local dependency", async () => { - const items = await treeProvider.getChildren(); - - const dep = items.find(n => n.name === "defaultpackage") as PackageNode; - expect( - dep, - `Expected to find defaultPackage, but instead items were ${items.map(n => n.name)}` - ).to.not.be.undefined; - assertPathsEqual(dep?.location, testAssetPath("defaultPackage")); - assertPathsEqual(dep?.path, testAssetPath("defaultPackage")); - }); - - test("Lists local dependency file structure", async () => { - contextKeys.flatDependenciesList = false; - const items = await treeProvider.getChildren(); - - const dep = items.find(n => n.name === "defaultpackage") as PackageNode; - expect( - dep, - `Expected to find defaultPackage, but instead items were ${items.map(n => n.name)}` - ).to.not.be.undefined; - - const folders = await treeProvider.getChildren(dep); - const folder = folders.find(n => n.name === "Sources"); - expect(folder).to.not.be.undefined; - - assertPathsEqual(folder?.path, path.join(testAssetPath("defaultPackage"), "Sources")); - - const childFolders = await treeProvider.getChildren(folder); - const childFolder = childFolders.find(n => n.name === "PackageExe"); - expect(childFolder).to.not.be.undefined; - - assertPathsEqual( - childFolder?.path, - path.join(testAssetPath("defaultPackage"), "Sources/PackageExe") - ); - - const files = await treeProvider.getChildren(childFolder); - const file = files.find(n => n.name === "main.swift"); - expect(file).to.not.be.undefined; - - assertPathsEqual( - file?.path, - path.join(testAssetPath("defaultPackage"), "Sources/PackageExe/main.swift") - ); - }); - - test("Lists remote dependency file structure", async () => { - contextKeys.flatDependenciesList = false; - const items = await treeProvider.getChildren(); - - const dep = items.find(n => n.name === "swift-markdown") as PackageNode; - expect(dep, `${JSON.stringify(items, null, 2)}`).to.not.be.undefined; - - const folders = await treeProvider.getChildren(dep); - const folder = folders.find(n => n.name === "Sources"); - expect(folder).to.not.be.undefined; - - const depPath = path.join(testAssetPath("dependencies"), ".build/checkouts/swift-markdown"); - assertPathsEqual(folder?.path, path.join(depPath, "Sources")); - - const childFolders = await treeProvider.getChildren(folder); - const childFolder = childFolders.find(n => n.name === "CAtomic"); - expect(childFolder).to.not.be.undefined; - - assertPathsEqual(childFolder?.path, path.join(depPath, "Sources/CAtomic")); - - const files = await treeProvider.getChildren(childFolder); - const file = files.find(n => n.name === "CAtomic.c"); - expect(file).to.not.be.undefined; - - assertPathsEqual(file?.path, path.join(depPath, "Sources/CAtomic/CAtomic.c")); - }); - - test("Shows a flat dependency list", async () => { - contextKeys.flatDependenciesList = true; - const items = await treeProvider.getChildren(); - expect(items.length).to.equal(3); - expect(items.find(n => n.name === "swift-markdown")).to.not.be.undefined; - expect(items.find(n => n.name === "swift-cmark")).to.not.be.undefined; - expect(items.find(n => n.name === "defaultpackage")).to.not.be.undefined; - }); - - test("Shows a nested dependency list", async () => { - contextKeys.flatDependenciesList = false; - const items = await treeProvider.getChildren(); - expect(items.length).to.equal(2); - expect(items.find(n => n.name === "swift-markdown")).to.not.be.undefined; - expect(items.find(n => n.name === "defaultpackage")).to.not.be.undefined; - }); - - function assertPathsEqual(path1: string | undefined, path2: string | undefined) { - expect(path1).to.not.be.undefined; - expect(path2).to.not.be.undefined; - // Convert to vscode.Uri to normalize paths, including drive letter capitalization on Windows. - expect(vscode.Uri.file(path1!).fsPath).to.equal(vscode.Uri.file(path2!).fsPath); - } -}); diff --git a/test/integration-tests/ui/ProjectPanelProvider.test.ts b/test/integration-tests/ui/ProjectPanelProvider.test.ts new file mode 100644 index 000000000..e1cb5c433 --- /dev/null +++ b/test/integration-tests/ui/ProjectPanelProvider.test.ts @@ -0,0 +1,300 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the VS Code Swift open source project +// +// Copyright (c) 2024 the VS Code Swift project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of VS Code Swift project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import { expect } from "chai"; +import { beforeEach, afterEach } from "mocha"; +import * as vscode from "vscode"; +import * as path from "path"; +import { ProjectPanelProvider, PackageNode, FileNode } from "../../../src/ui/ProjectPanelProvider"; +import { executeTaskAndWaitForResult, waitForNoRunningTasks } from "../../utilities/tasks"; +import { getBuildAllTask, SwiftTask } from "../../../src/tasks/SwiftTaskProvider"; +import { testAssetPath } from "../../fixtures"; +import { + activateExtensionForSuite, + folderInRootWorkspace, + updateSettings, +} from "../utilities/testutilities"; +import contextKeys from "../../../src/contextKeys"; +import { WorkspaceContext } from "../../../src/WorkspaceContext"; +import { Version } from "../../../src/utilities/version"; + +suite("ProjectPanelProvider Test Suite", function () { + let workspaceContext: WorkspaceContext; + let treeProvider: ProjectPanelProvider; + this.timeout(2 * 60 * 1000); // Allow up to 2 minutes to build + + activateExtensionForSuite({ + async setup(ctx) { + workspaceContext = ctx; + await waitForNoRunningTasks(); + const folderContext = await folderInRootWorkspace("targets", workspaceContext); + await vscode.workspace.openTextDocument( + path.join(folderContext.folder.fsPath, "Package.swift") + ); + const buildAllTask = await getBuildAllTask(folderContext); + buildAllTask.definition.dontTriggerTestDiscovery = true; + await executeTaskAndWaitForResult(buildAllTask as SwiftTask); + await folderContext.loadSwiftPlugins(); + treeProvider = new ProjectPanelProvider(workspaceContext); + await workspaceContext.focusFolder(folderContext); + }, + async teardown() { + contextKeys.flatDependenciesList = false; + treeProvider.dispose(); + }, + testAssets: ["targets"], + }); + + let resetSettings: (() => Promise) | undefined; + beforeEach(async function () { + resetSettings = await updateSettings({ + "swift.debugger.debugAdapter": "CodeLLDB", + }); + }); + + afterEach(async () => { + if (resetSettings) { + await resetSettings(); + } + }); + + test("Includes top level nodes", async () => { + const commands = await treeProvider.getChildren(); + const commandNames = commands.map(n => n.name); + expect(commandNames).to.deep.equal([ + "Dependencies", + "Targets", + "Tasks", + "Snippets", + "Commands", + ]); + }); + + suite("Targets", () => { + test("Includes targets", async () => { + const targets = await getHeaderChildren("Targets"); + const targetNames = targets.map(target => target.name); + expect( + targetNames, + `Expected to find dependencies target, but instead items were ${targetNames}` + ).to.deep.equal([ + "ExecutableTarget", + "LibraryTarget", + "PluginTarget", + "AnotherTests", + "TargetsTests", + ]); + }); + }); + + suite("Tasks", () => { + beforeEach(async () => { + await waitForNoRunningTasks(); + }); + + async function getBuildAllTask() { + // In Swift 5.10 and below the build tasks are disabled while other tasks that could modify .build are running. + // Typically because the extension has just started up in tests its `swift test list` that runs to gather tests + // for the test explorer. If we're running 5.10 or below, poll for the build all task for up to 60 seconds. + if (workspaceContext.toolchain.swiftVersion.isLessThan(new Version(6, 0, 0))) { + const startTime = Date.now(); + let task: PackageNode | undefined; + while (!task && Date.now() - startTime < 45 * 1000) { + const tasks = await getHeaderChildren("Tasks"); + task = tasks.find(n => n.name === "Build All (targets)") as PackageNode; + await new Promise(resolve => setTimeout(resolve, 1000)); + } + return task; + } else { + const tasks = await getHeaderChildren("Tasks"); + return tasks.find(n => n.name === "Build All (targets)") as PackageNode; + } + } + + test("Includes tasks", async () => { + const dep = await getBuildAllTask(); + expect(dep).to.not.be.undefined; + }); + + test("Executes a task", async () => { + const task = await getBuildAllTask(); + expect(task).to.not.be.undefined; + const treeItem = task?.toTreeItem(); + expect(treeItem?.command).to.not.be.undefined; + expect(treeItem?.command?.arguments).to.not.be.undefined; + if (treeItem && treeItem.command && treeItem.command.arguments) { + const command = treeItem.command.command; + const args = treeItem.command.arguments; + const result = await vscode.commands.executeCommand(command, ...args); + expect(result).to.be.true; + } + }); + }); + + suite("Snippets", () => { + test("Includes snippets", async () => { + const snippets = await getHeaderChildren("Snippets"); + const snippetNames = snippets.map(n => n.name); + expect(snippetNames).to.deep.equal(["AnotherSnippet", "Snippet"]); + }); + + test("Executes a snippet", async () => { + const snippets = await getHeaderChildren("Snippets"); + const snippet = snippets.find(n => n.name === "Snippet"); + expect(snippet).to.not.be.undefined; + + const result = await vscode.commands.executeCommand("swift.runSnippet", snippet?.name); + expect(result).to.be.true; + }); + }); + + suite("Commands", () => { + test("Includes commands", async () => { + const commands = await getHeaderChildren("Commands"); + const commandNames = commands.map(n => n.name); + expect(commandNames).to.deep.equal(["PluginTarget"]); + }); + + test("Executes a command", async () => { + const commands = await getHeaderChildren("Commands"); + const command = commands.find(n => n.name === "PluginTarget"); + expect(command).to.not.be.undefined; + const treeItem = command?.toTreeItem(); + expect(treeItem?.command).to.not.be.undefined; + expect(treeItem?.command?.arguments).to.not.be.undefined; + if (treeItem && treeItem.command && treeItem.command.arguments) { + const command = treeItem.command.command; + const args = treeItem.command.arguments; + const result = await vscode.commands.executeCommand(command, ...args); + expect(result).to.be.true; + } + }); + }); + + suite("Dependencies", () => { + test("Includes remote dependency", async () => { + contextKeys.flatDependenciesList = false; + const items = await getHeaderChildren("Dependencies"); + const dep = items.find(n => n.name === "swift-markdown") as PackageNode; + expect(dep, `${JSON.stringify(items, null, 2)}`).to.not.be.undefined; + expect(dep?.location).to.equal("https://github.com/swiftlang/swift-markdown.git"); + assertPathsEqual( + dep?.path, + path.join(testAssetPath("targets"), ".build/checkouts/swift-markdown") + ); + }); + + test("Includes local dependency", async () => { + const items = await getHeaderChildren("Dependencies"); + const dep = items.find(n => n.name === "defaultpackage") as PackageNode; + expect( + dep, + `Expected to find defaultPackage, but instead items were ${items.map(n => n.name)}` + ).to.not.be.undefined; + assertPathsEqual(dep?.location, testAssetPath("defaultPackage")); + assertPathsEqual(dep?.path, testAssetPath("defaultPackage")); + }); + + test("Lists local dependency file structure", async () => { + contextKeys.flatDependenciesList = false; + const children = await getHeaderChildren("Dependencies"); + const dep = children.find(n => n.name === "defaultpackage") as PackageNode; + expect( + dep, + `Expected to find defaultPackage, but instead items were ${children.map(n => n.name)}` + ).to.not.be.undefined; + + const folders = await treeProvider.getChildren(dep); + const folder = folders.find(n => n.name === "Sources") as FileNode; + expect(folder).to.not.be.undefined; + + assertPathsEqual(folder?.path, path.join(testAssetPath("defaultPackage"), "Sources")); + + const childFolders = await treeProvider.getChildren(folder); + const childFolder = childFolders.find(n => n.name === "PackageExe") as FileNode; + expect(childFolder).to.not.be.undefined; + + assertPathsEqual( + childFolder?.path, + path.join(testAssetPath("defaultPackage"), "Sources/PackageExe") + ); + + const files = await treeProvider.getChildren(childFolder); + const file = files.find(n => n.name === "main.swift") as FileNode; + expect(file).to.not.be.undefined; + + assertPathsEqual( + file?.path, + path.join(testAssetPath("defaultPackage"), "Sources/PackageExe/main.swift") + ); + }); + + test("Lists remote dependency file structure", async () => { + contextKeys.flatDependenciesList = false; + const children = await getHeaderChildren("Dependencies"); + const dep = children.find(n => n.name === "swift-markdown") as PackageNode; + expect(dep, `${JSON.stringify(children, null, 2)}`).to.not.be.undefined; + + const folders = await treeProvider.getChildren(dep); + const folder = folders.find(n => n.name === "Sources") as FileNode; + expect(folder).to.not.be.undefined; + + const depPath = path.join(testAssetPath("targets"), ".build/checkouts/swift-markdown"); + assertPathsEqual(folder?.path, path.join(depPath, "Sources")); + + const childFolders = await treeProvider.getChildren(folder); + const childFolder = childFolders.find(n => n.name === "CAtomic") as FileNode; + expect(childFolder).to.not.be.undefined; + + assertPathsEqual(childFolder?.path, path.join(depPath, "Sources/CAtomic")); + + const files = await treeProvider.getChildren(childFolder); + const file = files.find(n => n.name === "CAtomic.c") as FileNode; + expect(file).to.not.be.undefined; + + assertPathsEqual(file?.path, path.join(depPath, "Sources/CAtomic/CAtomic.c")); + }); + + test("Shows a flat dependency list", async () => { + contextKeys.flatDependenciesList = true; + const items = await getHeaderChildren("Dependencies"); + expect(items.length).to.equal(3); + expect(items.find(n => n.name === "swift-markdown")).to.not.be.undefined; + expect(items.find(n => n.name === "swift-cmark")).to.not.be.undefined; + expect(items.find(n => n.name === "defaultpackage")).to.not.be.undefined; + }); + + test("Shows a nested dependency list", async () => { + contextKeys.flatDependenciesList = false; + const items = await getHeaderChildren("Dependencies"); + expect(items.length).to.equal(2); + expect(items.find(n => n.name === "swift-markdown")).to.not.be.undefined; + expect(items.find(n => n.name === "defaultpackage")).to.not.be.undefined; + }); + }); + + async function getHeaderChildren(headerName: string) { + const headers = await treeProvider.getChildren(); + const header = headers.find(n => n.name === headerName) as PackageNode; + expect(header).to.not.be.undefined; + return await header.getChildren(); + } + + function assertPathsEqual(path1: string | undefined, path2: string | undefined) { + expect(path1).to.not.be.undefined; + expect(path2).to.not.be.undefined; + // Convert to vscode.Uri to normalize paths, including drive letter capitalization on Windows. + expect(vscode.Uri.file(path1!).fsPath).to.equal(vscode.Uri.file(path2!).fsPath); + } +}); diff --git a/test/integration-tests/utilities/testutilities.ts b/test/integration-tests/utilities/testutilities.ts index 3414b1954..23a7530cc 100644 --- a/test/integration-tests/utilities/testutilities.ts +++ b/test/integration-tests/utilities/testutilities.ts @@ -318,7 +318,7 @@ export type SettingsMap = { [key: string]: unknown }; export async function updateSettings(settings: SettingsMap): Promise<() => Promise> { const applySettings = async (settings: SettingsMap) => { const savedOriginalSettings: SettingsMap = {}; - Object.keys(settings).forEach(async setting => { + for (const setting of Object.keys(settings)) { const { section, name } = decomposeSettingName(setting); const config = vscode.workspace.getConfiguration(section, { languageId: "swift" }); savedOriginalSettings[setting] = config.get(name); @@ -327,7 +327,7 @@ export async function updateSettings(settings: SettingsMap): Promise<() => Promi settings[setting] === "" ? undefined : settings[setting], vscode.ConfigurationTarget.Workspace ); - }); + } // There is actually a delay between when the config.update promise resolves and when // the setting is actually written. If we exit this function right away the test might diff --git a/test/unit-tests/ui/PackageDependencyProvider.test.ts b/test/unit-tests/ui/PackageDependencyProvider.test.ts index d7d14b994..b7cca7b3a 100644 --- a/test/unit-tests/ui/PackageDependencyProvider.test.ts +++ b/test/unit-tests/ui/PackageDependencyProvider.test.ts @@ -15,7 +15,7 @@ import { expect } from "chai"; import * as vscode from "vscode"; import * as fs from "fs/promises"; -import { FileNode, PackageNode } from "../../../src/ui/PackageDependencyProvider"; +import { FileNode, PackageNode } from "../../../src/ui/ProjectPanelProvider"; import { mockGlobalModule } from "../../MockUtils"; suite("PackageDependencyProvider Unit Test Suite", function () { From 71f29321836658323d644796fbf81b1c04ce1da0 Mon Sep 17 00:00:00 2001 From: Paul LeMarquand Date: Fri, 28 Feb 2025 10:56:26 -0500 Subject: [PATCH 29/86] Disable attachment test until attachments are formally released (#1412) Attachments are still SPI, and SPI is available on linux and windows but not through Xcode on macOS. We can reenable this once attachments are formalized and no longer SPI. --- .../Tests/PackageTests/PackageTests.swift | 17 +++++++---------- .../TestExplorerIntegration.test.ts | 10 +++++----- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/assets/test/defaultPackage/Tests/PackageTests/PackageTests.swift b/assets/test/defaultPackage/Tests/PackageTests/PackageTests.swift index dc856f6ce..68d430378 100644 --- a/assets/test/defaultPackage/Tests/PackageTests/PackageTests.swift +++ b/assets/test/defaultPackage/Tests/PackageTests/PackageTests.swift @@ -42,13 +42,9 @@ final class DebugReleaseTestSuite: XCTestCase { } } -#if swift(>=6.1) -@_spi(Experimental) import Testing -#elseif swift(>=6.0) +#if swift(>=6.0) import Testing -#endif -#if swift(>=6.0) @Test func topLevelTestPassing() { print("A print statement in a test.") #if !TEST_ARGUMENT_SET_VIA_TEST_BUILD_ARGUMENTS_SETTING @@ -112,11 +108,12 @@ struct MixedSwiftTestingSuite { } #endif -#if swift(>=6.1) -@Test func testAttachment() throws { - Attachment("Hello, world!", named: "hello.txt").attach() -} -#endif +// Disabled until Attachments are formalized and released. +// #if swift(>=6.1) +// @Test func testAttachment() throws { +// Attachment("Hello, world!", named: "hello.txt").attach() +// } +// #endif final class DuplicateSuffixTests: XCTestCase { func testPassing() throws {} diff --git a/test/integration-tests/testexplorer/TestExplorerIntegration.test.ts b/test/integration-tests/testexplorer/TestExplorerIntegration.test.ts index b613bdc82..469a14fd5 100644 --- a/test/integration-tests/testexplorer/TestExplorerIntegration.test.ts +++ b/test/integration-tests/testexplorer/TestExplorerIntegration.test.ts @@ -192,7 +192,6 @@ suite("Test Explorer Suite", function () { "testWithKnownIssue()", "testWithKnownIssueAndUnknownIssue()", "testLotsOfOutput()", - "testAttachment()", "DuplicateSuffixTests", ["testPassing()", "testPassingSuffix()"], ], @@ -255,7 +254,8 @@ suite("Test Explorer Suite", function () { ); }); - test("attachments", async function () { + // Disabled until Attachments are formalized and released. + test.skip("attachments", async function () { // Attachments were introduced in 6.1 if (workspaceContext.swiftVersion.isLessThan(new Version(6, 1, 0))) { this.skip(); @@ -624,8 +624,8 @@ suite("Test Explorer Suite", function () { assertTestResults(testRun, { passed: [ - `${testId}/PackageTests.swift:63:2/argumentIDs: Optional([Testing.Test.Case.Argument.ID(bytes: [49])])`, - `${testId}/PackageTests.swift:63:2/argumentIDs: Optional([Testing.Test.Case.Argument.ID(bytes: [51])])`, + `${testId}/PackageTests.swift:59:2/argumentIDs: Optional([Testing.Test.Case.Argument.ID(bytes: [49])])`, + `${testId}/PackageTests.swift:59:2/argumentIDs: Optional([Testing.Test.Case.Argument.ID(bytes: [51])])`, ], failed: [ { @@ -635,7 +635,7 @@ suite("Test Explorer Suite", function () { text: "Expectation failed: (arg → 2) != 2", })}`, ], - test: `${testId}/PackageTests.swift:63:2/argumentIDs: Optional([Testing.Test.Case.Argument.ID(bytes: [50])])`, + test: `${testId}/PackageTests.swift:59:2/argumentIDs: Optional([Testing.Test.Case.Argument.ID(bytes: [50])])`, }, { issues: [], From ef0f5dee138a2a73fd5e0f0907881539788d0ecc Mon Sep 17 00:00:00 2001 From: Matthew Bastien Date: Fri, 28 Feb 2025 16:52:51 +0000 Subject: [PATCH 30/86] Add event handler for editor selection in Documentation Live Preview editor (#1413) * throttle convert requests to 10Hz * never steal focus when revealing the live preview editor * add selection even listener to detect cursor movement --- package-lock.json | 47 ++++++ package.json | 2 + .../DocumentationPreviewEditor.ts | 154 ++++++++++-------- 3 files changed, 136 insertions(+), 67 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3a0e543a0..9fd05d3c9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,6 +21,7 @@ "@types/chai-subset": "^1.3.5", "@types/glob": "^7.1.6", "@types/lcov-parse": "^1.0.2", + "@types/lodash.throttle": "^4.1.9", "@types/mocha": "^10.0.10", "@types/mock-fs": "^4.13.4", "@types/node": "^18.19.76", @@ -43,6 +44,7 @@ "esbuild": "^0.25.0", "eslint": "^8.57.0", "eslint-config-prettier": "^10.0.1", + "lodash.throttle": "^4.1.1", "mocha": "^10.8.2", "mock-fs": "^5.5.0", "node-pty": "^1.0.0", @@ -1106,6 +1108,23 @@ "integrity": "sha512-tdoxiYm04XdDEdR7UMwkWj78UAVo9U2IOcxI6tmX2/s9TK/ue/9T8gbpS/07yeWyVkVO0UumFQ5EUIBQbVejzQ==", "dev": true }, + "node_modules/@types/lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-w/P33JFeySuhN6JLkysYUK2gEmy9kHHFN7E8ro0tkfmlDOgxBDzWEZ/J8cWA+fHqFevpswDTFZnDx+R9lbL6xw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/lodash.throttle": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/lodash.throttle/-/lodash.throttle-4.1.9.tgz", + "integrity": "sha512-PCPVfpfueguWZQB7pJQK890F2scYKoDUL3iM522AptHWn7d5NQmeS/LTEHIcLr5PaTzl3dK2Z0xSUHHTHwaL5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/lodash": "*" + } + }, "node_modules/@types/minimatch": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", @@ -4099,6 +4118,13 @@ "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", "dev": true }, + "node_modules/lodash.throttle": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", + "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==", + "dev": true, + "license": "MIT" + }, "node_modules/log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", @@ -6777,6 +6803,21 @@ "integrity": "sha512-tdoxiYm04XdDEdR7UMwkWj78UAVo9U2IOcxI6tmX2/s9TK/ue/9T8gbpS/07yeWyVkVO0UumFQ5EUIBQbVejzQ==", "dev": true }, + "@types/lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-w/P33JFeySuhN6JLkysYUK2gEmy9kHHFN7E8ro0tkfmlDOgxBDzWEZ/J8cWA+fHqFevpswDTFZnDx+R9lbL6xw==", + "dev": true + }, + "@types/lodash.throttle": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/lodash.throttle/-/lodash.throttle-4.1.9.tgz", + "integrity": "sha512-PCPVfpfueguWZQB7pJQK890F2scYKoDUL3iM522AptHWn7d5NQmeS/LTEHIcLr5PaTzl3dK2Z0xSUHHTHwaL5g==", + "dev": true, + "requires": { + "@types/lodash": "*" + } + }, "@types/minimatch": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", @@ -8958,6 +8999,12 @@ "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", "dev": true }, + "lodash.throttle": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", + "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==", + "dev": true + }, "log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", diff --git a/package.json b/package.json index 9101e61f0..656b96250 100644 --- a/package.json +++ b/package.json @@ -1566,6 +1566,7 @@ "@types/chai-subset": "^1.3.5", "@types/glob": "^7.1.6", "@types/lcov-parse": "^1.0.2", + "@types/lodash.throttle": "^4.1.9", "@types/mocha": "^10.0.10", "@types/mock-fs": "^4.13.4", "@types/node": "^18.19.76", @@ -1588,6 +1589,7 @@ "esbuild": "^0.25.0", "eslint": "^8.57.0", "eslint-config-prettier": "^10.0.1", + "lodash.throttle": "^4.1.1", "mocha": "^10.8.2", "mock-fs": "^5.5.0", "node-pty": "^1.0.0", diff --git a/src/documentation/DocumentationPreviewEditor.ts b/src/documentation/DocumentationPreviewEditor.ts index d9cd177e8..2b2a8fb2f 100644 --- a/src/documentation/DocumentationPreviewEditor.ts +++ b/src/documentation/DocumentationPreviewEditor.ts @@ -19,6 +19,8 @@ import { RenderNode, WebviewContent, WebviewMessage } from "./webview/WebviewMes import { WorkspaceContext } from "../WorkspaceContext"; import { DocCDocumentationRequest, DocCDocumentationResponse } from "../sourcekit-lsp/extensions"; import { LSPErrorCodes, ResponseError } from "vscode-languageclient"; +// eslint-disable-next-line @typescript-eslint/no-require-imports +import throttle = require("lodash.throttle"); export enum PreviewEditorConstant { VIEW_TYPE = "swift.previewDocumentationEditor", @@ -94,6 +96,7 @@ export class DocumentationPreviewEditor implements vscode.Disposable { } private activeTextEditor?: vscode.TextEditor; + private activeTextEditorSelection?: vscode.Selection; private subscriptions: vscode.Disposable[] = []; private disposeEmitter = new vscode.EventEmitter(); @@ -108,11 +111,11 @@ export class DocumentationPreviewEditor implements vscode.Disposable { this.subscriptions.push( this.webviewPanel.webview.onDidReceiveMessage(this.receiveMessage, this), vscode.window.onDidChangeActiveTextEditor(this.handleActiveTextEditorChange, this), + vscode.window.onDidChangeTextEditorSelection(this.handleSelectionChange, this), vscode.workspace.onDidChangeTextDocument(this.handleDocumentChange, this), this.webviewPanel.onDidDispose(this.dispose, this) ); - // Reveal the editor, but don't change the focus of the active text editor - webviewPanel.reveal(undefined, true); + this.reveal(); } /** An event that is fired when the Documentation Preview Editor is disposed */ @@ -125,7 +128,8 @@ export class DocumentationPreviewEditor implements vscode.Disposable { onDidRenderContent = this.renderEmitter.event; reveal() { - this.webviewPanel.reveal(); + // Reveal the editor, but don't change the focus of the active text editor + this.webviewPanel.reveal(undefined, true); } dispose() { @@ -161,82 +165,98 @@ export class DocumentationPreviewEditor implements vscode.Disposable { return; } this.activeTextEditor = activeTextEditor; + this.activeTextEditorSelection = activeTextEditor.selection; this.convertDocumentation(activeTextEditor); } + private handleSelectionChange(event: vscode.TextEditorSelectionChangeEvent) { + if ( + this.activeTextEditor !== event.textEditor || + this.activeTextEditorSelection === event.textEditor.selection + ) { + return; + } + this.activeTextEditorSelection = event.textEditor.selection; + this.convertDocumentation(event.textEditor); + } + private handleDocumentChange(event: vscode.TextDocumentChangeEvent) { if (this.activeTextEditor?.document === event.document) { this.convertDocumentation(this.activeTextEditor); } } - private async convertDocumentation(textEditor: vscode.TextEditor): Promise { - const document = textEditor.document; - if ( - document.uri.scheme !== "file" || - !["markdown", "tutorial", "swift"].includes(document.languageId) - ) { - this.postMessage({ - type: "update-content", - content: { - type: "error", - errorMessage: PreviewEditorConstant.UNSUPPORTED_EDITOR_ERROR_MESSAGE, - }, - }); - return; - } + private convertDocumentation = throttle( + async (textEditor: vscode.TextEditor): Promise => { + const document = textEditor.document; + if ( + document.uri.scheme !== "file" || + !["markdown", "tutorial", "swift"].includes(document.languageId) + ) { + this.postMessage({ + type: "update-content", + content: { + type: "error", + errorMessage: PreviewEditorConstant.UNSUPPORTED_EDITOR_ERROR_MESSAGE, + }, + }); + return; + } - try { - const response = await this.context.languageClientManager.useLanguageClient( - async (client): Promise => { - return await client.sendRequest(DocCDocumentationRequest.type, { - textDocument: { - uri: document.uri.toString(), - }, - position: textEditor.selection.start, - }); + try { + const response = await this.context.languageClientManager.useLanguageClient( + async (client): Promise => { + return await client.sendRequest(DocCDocumentationRequest.type, { + textDocument: { + uri: document.uri.toString(), + }, + position: textEditor.selection.start, + }); + } + ); + this.postMessage({ + type: "update-content", + content: { + type: "render-node", + renderNode: this.parseRenderNode(response.renderNode), + }, + }); + } catch (error) { + // Update the preview editor to reflect what error occurred + let livePreviewErrorMessage = "An internal error occurred"; + const baseLogErrorMessage = `SourceKit-LSP request "${DocCDocumentationRequest.method}" failed: `; + if (error instanceof ResponseError) { + if (error.code === LSPErrorCodes.RequestCancelled) { + // We can safely ignore cancellations + return undefined; + } + switch (error.code) { + case LSPErrorCodes.RequestFailed: + // RequestFailed response errors can be shown to the user + livePreviewErrorMessage = error.message; + break; + default: + // We should log additional info for other response errors + this.context.outputChannel.log( + baseLogErrorMessage + JSON.stringify(error.toJson(), undefined, 2) + ); + break; + } + } else { + this.context.outputChannel.log(baseLogErrorMessage + `${error}`); } - ); - this.postMessage({ - type: "update-content", - content: { - type: "render-node", - renderNode: this.parseRenderNode(response.renderNode), - }, - }); - } catch (error) { - // Update the preview editor to reflect what error occurred - let livePreviewErrorMessage = "An internal error occurred"; - const baseLogErrorMessage = `SourceKit-LSP request "${DocCDocumentationRequest.method}" failed: `; - if (error instanceof ResponseError) { - if (error.code === LSPErrorCodes.RequestCancelled) { - // We can safely ignore cancellations - return undefined; - } - switch (error.code) { - case LSPErrorCodes.RequestFailed: - // RequestFailed response errors can be shown to the user - livePreviewErrorMessage = error.message; - break; - default: - // We should log additional info for other response errors - this.context.outputChannel.log( - baseLogErrorMessage + JSON.stringify(error.toJson(), undefined, 2) - ); - break; - } - } else { - this.context.outputChannel.log(baseLogErrorMessage + `${error}`); + this.postMessage({ + type: "update-content", + content: { + type: "error", + errorMessage: livePreviewErrorMessage, + }, + }); } - this.postMessage({ - type: "update-content", - content: { - type: "error", - errorMessage: livePreviewErrorMessage, - }, - }); - } - } + }, + 100 /* 10 times per second */, + { trailing: true } + ); private parseRenderNode(content: string): RenderNode { const renderNode: RenderNode = JSON.parse(content); From 7b60f35fa27a977fedd0d90304d3d96effe28fa8 Mon Sep 17 00:00:00 2001 From: Paul LeMarquand Date: Mon, 3 Mar 2025 09:21:00 -0500 Subject: [PATCH 31/86] Simplify XCTestOutputParser Tests (#1392) To write a new one of these tests you had to define all the participating test names in the TestRunState at the begining of the test. Remove this constraint by returning an enqueued mock test item every time one is requested that hasn't been seen. --- .../testexplorer/MockTestRunState.ts | 27 +- .../SwiftTestingOutputParser.test.ts | 10 +- .../testexplorer/XCTestOutputParser.test.ts | 403 +++++++++--------- 3 files changed, 212 insertions(+), 228 deletions(-) diff --git a/test/integration-tests/testexplorer/MockTestRunState.ts b/test/integration-tests/testexplorer/MockTestRunState.ts index d9f7478ed..c8ae407c6 100644 --- a/test/integration-tests/testexplorer/MockTestRunState.ts +++ b/test/integration-tests/testexplorer/MockTestRunState.ts @@ -44,16 +44,26 @@ interface ITestItemFinder { } export class DarwinTestItemFinder implements ITestItemFinder { - constructor(public tests: TestItem[]) {} + tests: TestItem[] = []; getIndex(id: string): number { - return this.tests.findIndex(item => item.name === id); + const index = this.tests.findIndex(item => item.name === id); + if (index === -1) { + this.tests.push({ name: id, status: TestStatus.enqueued, output: [] }); + return this.tests.length - 1; + } + return index; } } export class NonDarwinTestItemFinder implements ITestItemFinder { - constructor(public tests: TestItem[]) {} + tests: TestItem[] = []; getIndex(id: string): number { - return this.tests.findIndex(item => item.name.endsWith(id)); + const index = this.tests.findIndex(item => item.name.endsWith(id)); + if (index === -1) { + this.tests.push({ name: id, status: TestStatus.enqueued, output: [] }); + return this.tests.length - 1; + } + return index; } } @@ -75,14 +85,11 @@ export class TestRunState implements ITestRunState { return this.testItemFinder.tests; } - constructor(testNames: string[], darwin: boolean) { - const tests = testNames.map(name => { - return { name: name, status: TestStatus.enqueued, output: [] }; - }); + constructor(darwin: boolean) { if (darwin) { - this.testItemFinder = new DarwinTestItemFinder(tests); + this.testItemFinder = new DarwinTestItemFinder(); } else { - this.testItemFinder = new NonDarwinTestItemFinder(tests); + this.testItemFinder = new NonDarwinTestItemFinder(); } } diff --git a/test/integration-tests/testexplorer/SwiftTestingOutputParser.test.ts b/test/integration-tests/testexplorer/SwiftTestingOutputParser.test.ts index 7b84475f4..df56401e7 100644 --- a/test/integration-tests/testexplorer/SwiftTestingOutputParser.test.ts +++ b/test/integration-tests/testexplorer/SwiftTestingOutputParser.test.ts @@ -41,6 +41,7 @@ class TestEventStream { suite("SwiftTestingOutputParser Suite", () => { let outputParser: SwiftTestingOutputParser; + let testRunState: TestRunState; beforeEach(() => { outputParser = new SwiftTestingOutputParser( @@ -48,6 +49,7 @@ suite("SwiftTestingOutputParser Suite", () => { () => {}, () => {} ); + testRunState = new TestRunState(true); }); type ExtractPayload = T extends { payload: infer E } ? E : never; @@ -76,7 +78,6 @@ suite("SwiftTestingOutputParser Suite", () => { } test("Passed test", async () => { - const testRunState = new TestRunState(["MyTests.MyTests/testPass()"], true); const events = new TestEventStream([ testEvent("runStarted"), testEvent("testCaseStarted", "MyTests.MyTests/testPass()"), @@ -97,7 +98,6 @@ suite("SwiftTestingOutputParser Suite", () => { }); test("Skipped test", async () => { - const testRunState = new TestRunState(["MyTests.MyTests/testSkip()"], true); const events = new TestEventStream([ testEvent("runStarted"), testEvent("testSkipped", "MyTests.MyTests/testSkip()"), @@ -116,7 +116,6 @@ suite("SwiftTestingOutputParser Suite", () => { }); async function performTestFailure(messages: EventMessage[]) { - const testRunState = new TestRunState(["MyTests.MyTests/testFail()"], true); const issueLocation = { _filePath: "file:///some/file.swift", line: 1, @@ -174,7 +173,6 @@ suite("SwiftTestingOutputParser Suite", () => { }); test("Parameterized test", async () => { - const testRunState = new TestRunState(["MyTests.MyTests/testParameterized()"], true); const events = new TestEventStream([ { kind: "test", @@ -270,10 +268,6 @@ suite("SwiftTestingOutputParser Suite", () => { }); test("Output is captured", async () => { - const testRunState = new TestRunState( - ["MyTests.MyTests/testOutput()", "MyTests.MyTests/testOutput2()"], - true - ); const symbol = TestSymbol.pass; const makeEvent = (kind: ExtractPayload["kind"], testId?: string) => testEvent(kind, testId, [{ text: kind, symbol }]); diff --git a/test/integration-tests/testexplorer/XCTestOutputParser.test.ts b/test/integration-tests/testexplorer/XCTestOutputParser.test.ts index 6ad6bac66..7617f874e 100644 --- a/test/integration-tests/testexplorer/XCTestOutputParser.test.ts +++ b/test/integration-tests/testexplorer/XCTestOutputParser.test.ts @@ -13,6 +13,7 @@ //===----------------------------------------------------------------------===// import * as assert from "assert"; +import { beforeEach } from "mocha"; import { darwinTestRegex, nonDarwinTestRegex, @@ -29,10 +30,14 @@ suite("XCTestOutputParser Suite", () => { .map(line => `${line}\r\n`); suite("Darwin", () => { - const outputParser = new XCTestOutputParser(darwinTestRegex); + let outputParser: XCTestOutputParser; + let testRunState: TestRunState; + beforeEach(() => { + outputParser = new XCTestOutputParser(darwinTestRegex); + testRunState = new TestRunState(true); + }); test("Passed Test", () => { - const testRunState = new TestRunState(["MyTests.MyTests/testPass"], true); const input = `Test Case '-[MyTests.MyTests testPass]' started. Test Case '-[MyTests.MyTests testPass]' passed (0.001 seconds). `; @@ -49,10 +54,6 @@ Test Case '-[MyTests.MyTests testPass]' passed (0.001 seconds). }); test("Multiple Passed Tests", () => { - const testRunState = new TestRunState( - ["MyTests.MyTests/testPass", "MyTests.MyTests/testPass2"], - true - ); const test1Input = `Test Case '-[MyTests.MyTests testPass]' started. Test Case '-[MyTests.MyTests testPass]' passed (0.001 seconds). `; @@ -80,89 +81,88 @@ Test Case '-[MyTests.MyTests testPass2]' passed (0.001 seconds). }); test("Failed Test", () => { - const testRunState = new TestRunState(["MyTests.MyTests/testFail"], true); const input = `Test Case '-[MyTests.MyTests testFail]' started. /Users/user/Developer/MyTests/MyTests.swift:59: error: -[MyTests.MyTests testFail] : XCTAssertEqual failed: ("1") is not equal to ("2") Test Case '-[MyTests.MyTests testFail]' failed (0.106 seconds). `; - const runState = testRunState.tests[0]; outputParser.parseResult(input, testRunState); - assert.deepEqual(runState, { - name: "MyTests.MyTests/testFail", - status: TestStatus.failed, - timing: { duration: 0.106 }, - issues: [ - { - message: `XCTAssertEqual failed: ("1") is not equal to ("2")`, - location: sourceLocationToVSCodeLocation( - "/Users/user/Developer/MyTests/MyTests.swift", - 59, - 0 - ), - isKnown: false, - diff: { - expected: '"1"', - actual: '"2"', + assert.deepEqual(testRunState.tests, [ + { + name: "MyTests.MyTests/testFail", + status: TestStatus.failed, + timing: { duration: 0.106 }, + issues: [ + { + message: `XCTAssertEqual failed: ("1") is not equal to ("2")`, + location: sourceLocationToVSCodeLocation( + "/Users/user/Developer/MyTests/MyTests.swift", + 59, + 0 + ), + isKnown: false, + diff: { + expected: '"1"', + actual: '"2"', + }, }, - }, - ], - output: inputToTestOutput(input), - }); + ], + output: inputToTestOutput(input), + }, + ]); }); test("Skipped Test", () => { - const testRunState = new TestRunState(["MyTests.MyTests/testSkip"], true); const input = `Test Case '-[MyTests.MyTests testSkip]' started. /Users/user/Developer/MyTests/MyTests.swift:90: -[MyTests.MyTests testSkip] : Test skipped Test Case '-[MyTests.MyTests testSkip]' skipped (0.002 seconds). `; - const runState = testRunState.tests[0]; outputParser.parseResult(input, testRunState); - assert.deepEqual(runState, { - name: "MyTests.MyTests/testSkip", - status: TestStatus.skipped, - output: inputToTestOutput(input), - }); + assert.deepEqual(testRunState.tests, [ + { + name: "MyTests.MyTests/testSkip", + status: TestStatus.skipped, + output: inputToTestOutput(input), + }, + ]); }); test("Multi-line Fail", () => { - const testRunState = new TestRunState(["MyTests.MyTests/testFail"], true); const input = `Test Case '-[MyTests.MyTests testFail]' started. /Users/user/Developer/MyTests/MyTests.swift:59: error: -[MyTests.MyTests testFail] : failed - Multiline fail message Test Case '-[MyTests.MyTests testFail]' failed (0.571 seconds). `; - const runState = testRunState.tests[0]; outputParser.parseResult(input, testRunState); - assert.deepEqual(runState, { - name: "MyTests.MyTests/testFail", - status: TestStatus.failed, - timing: { duration: 0.571 }, - issues: [ - { - message: `failed - Multiline + assert.deepEqual(testRunState.tests, [ + { + name: "MyTests.MyTests/testFail", + status: TestStatus.failed, + timing: { duration: 0.571 }, + issues: [ + { + message: `failed - Multiline fail message`, - location: sourceLocationToVSCodeLocation( - "/Users/user/Developer/MyTests/MyTests.swift", - 59, - 0 - ), - isKnown: false, - diff: undefined, - }, - ], - output: inputToTestOutput(input), - }); + location: sourceLocationToVSCodeLocation( + "/Users/user/Developer/MyTests/MyTests.swift", + 59, + 0 + ), + isKnown: false, + diff: undefined, + }, + ], + output: inputToTestOutput(input), + }, + ]); }); test("Multi-line Fail followed by another error", () => { - const testRunState = new TestRunState(["MyTests.MyTests/testFail"], true); const input = `Test Case '-[MyTests.MyTests testFail]' started. /Users/user/Developer/MyTests/MyTests.swift:59: error: -[MyTests.MyTests testFail] : failed - Multiline fail @@ -170,104 +170,101 @@ message /Users/user/Developer/MyTests/MyTests.swift:61: error: -[MyTests.MyTests testFail] : failed - Again Test Case '-[MyTests.MyTests testFail]' failed (0.571 seconds). `; - const runState = testRunState.tests[0]; outputParser.parseResult(input, testRunState); - assert.deepEqual(runState, { - name: "MyTests.MyTests/testFail", - status: TestStatus.failed, - timing: { duration: 0.571 }, - issues: [ - { - message: `failed - Multiline + assert.deepEqual(testRunState.tests, [ + { + name: "MyTests.MyTests/testFail", + status: TestStatus.failed, + timing: { duration: 0.571 }, + issues: [ + { + message: `failed - Multiline fail message`, - location: sourceLocationToVSCodeLocation( - "/Users/user/Developer/MyTests/MyTests.swift", - 59, - 0 - ), - isKnown: false, - diff: undefined, - }, - { - message: `failed - Again`, - location: sourceLocationToVSCodeLocation( - "/Users/user/Developer/MyTests/MyTests.swift", - 61, - 0 - ), - isKnown: false, - diff: undefined, - }, - ], - output: inputToTestOutput(input), - }); + location: sourceLocationToVSCodeLocation( + "/Users/user/Developer/MyTests/MyTests.swift", + 59, + 0 + ), + isKnown: false, + diff: undefined, + }, + { + message: `failed - Again`, + location: sourceLocationToVSCodeLocation( + "/Users/user/Developer/MyTests/MyTests.swift", + 61, + 0 + ), + isKnown: false, + diff: undefined, + }, + ], + output: inputToTestOutput(input), + }, + ]); }); test("Single-line Fail followed by another error", () => { - const testRunState = new TestRunState(["MyTests.MyTests/testFail"], true); const input = `Test Case '-[MyTests.MyTests testFail]' started. /Users/user/Developer/MyTests/MyTests.swift:59: error: -[MyTests.MyTests testFail] : failed - Message /Users/user/Developer/MyTests/MyTests.swift:61: error: -[MyTests.MyTests testFail] : failed - Again Test Case '-[MyTests.MyTests testFail]' failed (0.571 seconds). `; - const runState = testRunState.tests[0]; outputParser.parseResult(input, testRunState); - assert.deepEqual(runState, { - name: "MyTests.MyTests/testFail", - status: TestStatus.failed, - timing: { duration: 0.571 }, - issues: [ - { - message: `failed - Message`, - location: sourceLocationToVSCodeLocation( - "/Users/user/Developer/MyTests/MyTests.swift", - 59, - 0 - ), - isKnown: false, - diff: undefined, - }, - { - message: `failed - Again`, - location: sourceLocationToVSCodeLocation( - "/Users/user/Developer/MyTests/MyTests.swift", - 61, - 0 - ), - isKnown: false, - diff: undefined, - }, - ], - output: inputToTestOutput(input), - }); + assert.deepEqual(testRunState.tests, [ + { + name: "MyTests.MyTests/testFail", + status: TestStatus.failed, + timing: { duration: 0.571 }, + issues: [ + { + message: `failed - Message`, + location: sourceLocationToVSCodeLocation( + "/Users/user/Developer/MyTests/MyTests.swift", + 59, + 0 + ), + isKnown: false, + diff: undefined, + }, + { + message: `failed - Again`, + location: sourceLocationToVSCodeLocation( + "/Users/user/Developer/MyTests/MyTests.swift", + 61, + 0 + ), + isKnown: false, + diff: undefined, + }, + ], + output: inputToTestOutput(input), + }, + ]); }); test("Split line", () => { - const testRunState = new TestRunState(["MyTests.MyTests/testPass"], true); const input1 = `Test Case '-[MyTests.MyTests testPass]' started. Test Case '-[MyTests.MyTests`; const input2 = ` testPass]' passed (0.006 seconds). `; - const runState = testRunState.tests[0]; outputParser.parseResult(input1, testRunState); outputParser.parseResult(input2, testRunState); - assert.deepEqual(runState, { - name: "MyTests.MyTests/testPass", - status: TestStatus.passed, - timing: { duration: 0.006 }, - output: inputToTestOutput(input1 + input2), - }); + assert.deepEqual(testRunState.tests, [ + { + name: "MyTests.MyTests/testPass", + status: TestStatus.passed, + timing: { duration: 0.006 }, + output: inputToTestOutput(input1 + input2), + }, + ]); }); test("Suite", () => { - const testRunState = new TestRunState( - ["MyTests.MyTests", "MyTests.MyTests/testPass"], - true - ); const input = `Test Suite 'MyTests' started at 2024-08-26 13:19:25.325. Test Case '-[MyTests.MyTests testPass]' started. Test Case '-[MyTests.MyTests testPass]' passed (0.001 seconds). @@ -294,7 +291,6 @@ Test Suite 'MyTests' passed at 2024-08-26 13:19:25.328. }); test("Empty Suite", () => { - const testRunState = new TestRunState([], true); const input = `Test Suite 'Selected tests' started at 2024-10-19 15:23:29.594. Test Suite 'EmptyAppPackageTests.xctest' started at 2024-10-19 15:23:29.595. Test Suite 'EmptyAppPackageTests.xctest' passed at 2024-10-19 15:23:29.595. @@ -310,15 +306,6 @@ warning: No matching test cases were run`; }); test("Multiple Suites", () => { - const testRunState = new TestRunState( - [ - "MyTests.TestSuite1", - "MyTests.TestSuite1/testFirst", - "MyTests.TestSuite2", - "MyTests.TestSuite2/testSecond", - ], - true - ); const input = `Test Suite 'All tests' started at 2024-10-20 21:54:32.568. Test Suite 'EmptyAppPackageTests.xctest' started at 2024-10-20 21:54:32.570. Test Suite 'TestSuite1' started at 2024-10-20 21:54:32.570. @@ -371,15 +358,6 @@ Test Suite 'All tests' passed at 2024-10-20 21:54:32.571. }); test("Multiple Suites with Failed Test", () => { - const testRunState = new TestRunState( - [ - "MyTests.TestSuite1", - "MyTests.TestSuite1/testFirst", - "MyTests.TestSuite2", - "MyTests.TestSuite2/testSecond", - ], - true - ); const input = `Test Suite 'Selected tests' started at 2024-10-20 22:01:46.206. Test Suite 'EmptyAppPackageTests.xctest' started at 2024-10-20 22:01:46.207. Test Suite 'TestSuite1' started at 2024-10-20 22:01:46.207. @@ -445,38 +423,38 @@ Test Suite 'Selected tests' failed at 2024-10-20 22:01:46.306. suite("Diffs", () => { const testRun = (message: string, expected?: string, actual?: string) => { - const testRunState = new TestRunState(["MyTests.MyTests/testFail"], true); const input = `Test Case '-[MyTests.MyTests testFail]' started. /Users/user/Developer/MyTests/MyTests.swift:59: error: -[MyTests.MyTests testFail] : ${message} Test Case '-[MyTests.MyTests testFail]' failed (0.106 seconds). `; - const runState = testRunState.tests[0]; outputParser.parseResult(input, testRunState); - assert.deepEqual(runState, { - name: "MyTests.MyTests/testFail", - status: TestStatus.failed, - timing: { duration: 0.106 }, - issues: [ - { - message, - location: sourceLocationToVSCodeLocation( - "/Users/user/Developer/MyTests/MyTests.swift", - 59, - 0 - ), - isKnown: false, - diff: - expected && actual - ? { - expected, - actual, - } - : undefined, - }, - ], - output: inputToTestOutput(input), - }); + assert.deepEqual(testRunState.tests, [ + { + name: "MyTests.MyTests/testFail", + status: TestStatus.failed, + timing: { duration: 0.106 }, + issues: [ + { + message, + location: sourceLocationToVSCodeLocation( + "/Users/user/Developer/MyTests/MyTests.swift", + 59, + 0 + ), + isKnown: false, + diff: + expected && actual + ? { + expected, + actual, + } + : undefined, + }, + ], + output: inputToTestOutput(input), + }, + ]); }; test("XCTAssertEqual", () => { @@ -503,70 +481,75 @@ Test Case '-[MyTests.MyTests testFail]' failed (0.106 seconds). }); suite("Linux", () => { - const outputParser = new XCTestOutputParser(nonDarwinTestRegex); + let outputParser: XCTestOutputParser; + let testRunState: TestRunState; + beforeEach(() => { + outputParser = new XCTestOutputParser(nonDarwinTestRegex); + testRunState = new TestRunState(false); + }); test("Passed Test", () => { - const testRunState = new TestRunState(["MyTests.MyTests/testPass"], false); const input = `Test Case 'MyTests.testPass' started. Test Case 'MyTests.testPass' passed (0.001 seconds). `; - const runState = testRunState.tests[0]; outputParser.parseResult(input, testRunState); - assert.deepEqual(runState, { - name: "MyTests.MyTests/testPass", - status: TestStatus.passed, - timing: { duration: 0.001 }, - output: inputToTestOutput(input), - }); + assert.deepEqual(testRunState.tests, [ + { + name: "MyTests/testPass", + status: TestStatus.passed, + timing: { duration: 0.001 }, + output: inputToTestOutput(input), + }, + ]); }); test("Failed Test", () => { - const testRunState = new TestRunState(["MyTests.MyTests/testFail"], false); const input = `Test Case 'MyTests.testFail' started. /Users/user/Developer/MyTests/MyTests.swift:59: error: MyTests.testFail : XCTAssertEqual failed: ("1") is not equal to ("2") Test Case 'MyTests.testFail' failed (0.106 seconds). `; - const runState = testRunState.tests[0]; outputParser.parseResult(input, testRunState); - assert.deepEqual(runState, { - name: "MyTests.MyTests/testFail", - status: TestStatus.failed, - timing: { duration: 0.106 }, - issues: [ - { - message: `XCTAssertEqual failed: ("1") is not equal to ("2")`, - location: sourceLocationToVSCodeLocation( - "/Users/user/Developer/MyTests/MyTests.swift", - 59, - 0 - ), - isKnown: false, - diff: { - expected: '"1"', - actual: '"2"', + assert.deepEqual(testRunState.tests, [ + { + name: "MyTests/testFail", + status: TestStatus.failed, + timing: { duration: 0.106 }, + issues: [ + { + message: `XCTAssertEqual failed: ("1") is not equal to ("2")`, + location: sourceLocationToVSCodeLocation( + "/Users/user/Developer/MyTests/MyTests.swift", + 59, + 0 + ), + isKnown: false, + diff: { + expected: '"1"', + actual: '"2"', + }, }, - }, - ], - output: inputToTestOutput(input), - }); + ], + output: inputToTestOutput(input), + }, + ]); }); test("Skipped Test", () => { - const testRunState = new TestRunState(["MyTests.MyTests/testSkip"], false); const input = `Test Case 'MyTests.testSkip' started. /Users/user/Developer/MyTests/MyTests.swift:90: MyTests.testSkip : Test skipped Test Case 'MyTests.testSkip' skipped (0.002 seconds). `; - const runState = testRunState.tests[0]; outputParser.parseResult(input, testRunState); - assert.deepEqual(runState, { - name: "MyTests.MyTests/testSkip", - status: TestStatus.skipped, - output: inputToTestOutput(input), - }); + assert.deepEqual(testRunState.tests, [ + { + name: "MyTests/testSkip", + status: TestStatus.skipped, + output: inputToTestOutput(input), + }, + ]); }); }); }); From d7ceea7f7439e8c6abf2bf02346c018369c17ee8 Mon Sep 17 00:00:00 2001 From: Paul LeMarquand Date: Mon, 3 Mar 2025 09:24:20 -0500 Subject: [PATCH 32/86] Print the command being run when debugging tests (#1417) Just like a regular test run, print the command being run when starting a debugging session whiel running tests. --- src/TestExplorer/TestRunner.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/TestExplorer/TestRunner.ts b/src/TestExplorer/TestRunner.ts index ecd742c61..c740d4e7f 100644 --- a/src/TestExplorer/TestRunner.ts +++ b/src/TestExplorer/TestRunner.ts @@ -961,6 +961,8 @@ export class TestRunner { ); const outputHandler = this.testOutputHandler(config.testType, runState); + outputHandler(`> ${config.program} ${config.args.join(" ")}\n\n\r`); + LoggingDebugAdapterTracker.setDebugSessionCallback( session, this.workspaceContext.outputChannel, From 09cca61aaf89752a4f566320e98eeb25feb145a3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Mar 2025 15:58:11 -0500 Subject: [PATCH 33/86] Bump the all-dependencies group with 6 updates (#1420) Bumps the all-dependencies group with 6 updates: | Package | From | To | | --- | --- | --- | | [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `18.19.76` | `18.19.79` | | [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) | `8.25.0` | `8.26.0` | | [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) | `8.25.0` | `8.26.0` | | [eslint-config-prettier](https://github.com/prettier/eslint-config-prettier) | `10.0.1` | `10.0.2` | | [prettier](https://github.com/prettier/prettier) | `3.5.2` | `3.5.3` | | [typescript](https://github.com/microsoft/TypeScript) | `5.7.3` | `5.8.2` | Updates `@types/node` from 18.19.76 to 18.19.79 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) Updates `@typescript-eslint/eslint-plugin` from 8.25.0 to 8.26.0 - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.26.0/packages/eslint-plugin) Updates `@typescript-eslint/parser` from 8.25.0 to 8.26.0 - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.26.0/packages/parser) Updates `eslint-config-prettier` from 10.0.1 to 10.0.2 - [Release notes](https://github.com/prettier/eslint-config-prettier/releases) - [Changelog](https://github.com/prettier/eslint-config-prettier/blob/main/CHANGELOG.md) - [Commits](https://github.com/prettier/eslint-config-prettier/compare/v10.0.1...v10.0.2) Updates `prettier` from 3.5.2 to 3.5.3 - [Release notes](https://github.com/prettier/prettier/releases) - [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md) - [Commits](https://github.com/prettier/prettier/compare/3.5.2...3.5.3) Updates `typescript` from 5.7.3 to 5.8.2 - [Release notes](https://github.com/microsoft/TypeScript/releases) - [Changelog](https://github.com/microsoft/TypeScript/blob/main/azure-pipelines.release.yml) - [Commits](https://github.com/microsoft/TypeScript/compare/v5.7.3...v5.8.2) --- updated-dependencies: - dependency-name: "@types/node" dependency-type: direct:development update-type: version-update:semver-patch dependency-group: all-dependencies - dependency-name: "@typescript-eslint/eslint-plugin" dependency-type: direct:development update-type: version-update:semver-minor dependency-group: all-dependencies - dependency-name: "@typescript-eslint/parser" dependency-type: direct:development update-type: version-update:semver-minor dependency-group: all-dependencies - dependency-name: eslint-config-prettier dependency-type: direct:development update-type: version-update:semver-patch dependency-group: all-dependencies - dependency-name: prettier dependency-type: direct:development update-type: version-update:semver-patch dependency-group: all-dependencies - dependency-name: typescript dependency-type: direct:development update-type: version-update:semver-minor dependency-group: all-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 240 +++++++++++++++++++++++----------------------- package.json | 12 +-- 2 files changed, 127 insertions(+), 125 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9fd05d3c9..8ffe603d8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,15 +24,15 @@ "@types/lodash.throttle": "^4.1.9", "@types/mocha": "^10.0.10", "@types/mock-fs": "^4.13.4", - "@types/node": "^18.19.76", + "@types/node": "^18.19.79", "@types/plist": "^3.0.5", "@types/semver": "^7.5.8", "@types/sinon": "^17.0.4", "@types/sinon-chai": "^3.2.12", "@types/vscode": "^1.88.0", "@types/xml2js": "^0.4.14", - "@typescript-eslint/eslint-plugin": "^8.25.0", - "@typescript-eslint/parser": "^8.25.0", + "@typescript-eslint/eslint-plugin": "^8.26.0", + "@typescript-eslint/parser": "^8.26.0", "@vscode/debugprotocol": "^1.68.0", "@vscode/test-cli": "^0.0.10", "@vscode/test-electron": "^2.4.1", @@ -43,12 +43,12 @@ "del-cli": "^6.0.0", "esbuild": "^0.25.0", "eslint": "^8.57.0", - "eslint-config-prettier": "^10.0.1", + "eslint-config-prettier": "^10.0.2", "lodash.throttle": "^4.1.1", "mocha": "^10.8.2", "mock-fs": "^5.5.0", "node-pty": "^1.0.0", - "prettier": "^3.5.2", + "prettier": "^3.5.3", "semver": "^7.7.1", "simple-git": "^3.27.0", "sinon": "^19.0.2", @@ -56,7 +56,7 @@ "source-map-support": "^0.5.21", "strip-ansi": "^6.0.1", "tsx": "^4.19.3", - "typescript": "^5.7.3" + "typescript": "^5.8.2" }, "engines": { "vscode": "^1.88.0" @@ -1148,9 +1148,9 @@ } }, "node_modules/@types/node": { - "version": "18.19.76", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.76.tgz", - "integrity": "sha512-yvR7Q9LdPz2vGpmpJX5LolrgRdWvB67MJKDPSgIIzpFbaf9a1j/f5DnLp5VDyHGMR0QZHlTr1afsD87QCXFHKw==", + "version": "18.19.79", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.79.tgz", + "integrity": "sha512-90K8Oayimbctc5zTPHPfZloc/lGVs7f3phUAAMcTgEPtg8kKquGZDERC8K4vkBYkQQh48msiYUslYtxTWvqcAg==", "dev": true, "license": "MIT", "dependencies": { @@ -1217,17 +1217,17 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.25.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.25.0.tgz", - "integrity": "sha512-VM7bpzAe7JO/BFf40pIT1lJqS/z1F8OaSsUB3rpFJucQA4cOSuH2RVVVkFULN+En0Djgr29/jb4EQnedUo95KA==", + "version": "8.26.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.26.0.tgz", + "integrity": "sha512-cLr1J6pe56zjKYajK6SSSre6nl1Gj6xDp1TY0trpgPzjVbgDwd09v2Ws37LABxzkicmUjhEeg/fAUjPJJB1v5Q==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.25.0", - "@typescript-eslint/type-utils": "8.25.0", - "@typescript-eslint/utils": "8.25.0", - "@typescript-eslint/visitor-keys": "8.25.0", + "@typescript-eslint/scope-manager": "8.26.0", + "@typescript-eslint/type-utils": "8.26.0", + "@typescript-eslint/utils": "8.26.0", + "@typescript-eslint/visitor-keys": "8.26.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -1243,20 +1243,20 @@ "peerDependencies": { "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.8.0" + "typescript": ">=4.8.4 <5.9.0" } }, "node_modules/@typescript-eslint/parser": { - "version": "8.25.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.25.0.tgz", - "integrity": "sha512-4gbs64bnbSzu4FpgMiQ1A+D+urxkoJk/kqlDJ2W//5SygaEiAP2B4GoS7TEdxgwol2el03gckFV9lJ4QOMiiHg==", + "version": "8.26.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.26.0.tgz", + "integrity": "sha512-mNtXP9LTVBy14ZF3o7JG69gRPBK/2QWtQd0j0oH26HcY/foyJJau6pNUez7QrM5UHnSvwlQcJXKsk0I99B9pOA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.25.0", - "@typescript-eslint/types": "8.25.0", - "@typescript-eslint/typescript-estree": "8.25.0", - "@typescript-eslint/visitor-keys": "8.25.0", + "@typescript-eslint/scope-manager": "8.26.0", + "@typescript-eslint/types": "8.26.0", + "@typescript-eslint/typescript-estree": "8.26.0", + "@typescript-eslint/visitor-keys": "8.26.0", "debug": "^4.3.4" }, "engines": { @@ -1268,18 +1268,18 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.8.0" + "typescript": ">=4.8.4 <5.9.0" } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.25.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.25.0.tgz", - "integrity": "sha512-6PPeiKIGbgStEyt4NNXa2ru5pMzQ8OYKO1hX1z53HMomrmiSB+R5FmChgQAP1ro8jMtNawz+TRQo/cSXrauTpg==", + "version": "8.26.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.26.0.tgz", + "integrity": "sha512-E0ntLvsfPqnPwng8b8y4OGuzh/iIOm2z8U3S9zic2TeMLW61u5IH2Q1wu0oSTkfrSzwbDJIB/Lm8O3//8BWMPA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.25.0", - "@typescript-eslint/visitor-keys": "8.25.0" + "@typescript-eslint/types": "8.26.0", + "@typescript-eslint/visitor-keys": "8.26.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1290,14 +1290,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.25.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.25.0.tgz", - "integrity": "sha512-d77dHgHWnxmXOPJuDWO4FDWADmGQkN5+tt6SFRZz/RtCWl4pHgFl3+WdYCn16+3teG09DY6XtEpf3gGD0a186g==", + "version": "8.26.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.26.0.tgz", + "integrity": "sha512-ruk0RNChLKz3zKGn2LwXuVoeBcUMh+jaqzN461uMMdxy5H9epZqIBtYj7UiPXRuOpaALXGbmRuZQhmwHhaS04Q==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.25.0", - "@typescript-eslint/utils": "8.25.0", + "@typescript-eslint/typescript-estree": "8.26.0", + "@typescript-eslint/utils": "8.26.0", "debug": "^4.3.4", "ts-api-utils": "^2.0.1" }, @@ -1310,13 +1310,13 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.8.0" + "typescript": ">=4.8.4 <5.9.0" } }, "node_modules/@typescript-eslint/types": { - "version": "8.25.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.25.0.tgz", - "integrity": "sha512-+vUe0Zb4tkNgznQwicsvLUJgZIRs6ITeWSCclX1q85pR1iOiaj+4uZJIUp//Z27QWu5Cseiw3O3AR8hVpax7Aw==", + "version": "8.26.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.26.0.tgz", + "integrity": "sha512-89B1eP3tnpr9A8L6PZlSjBvnJhWXtYfZhECqlBl1D9Lme9mHO6iWlsprBtVenQvY1HMhax1mWOjhtL3fh/u+pA==", "dev": true, "license": "MIT", "engines": { @@ -1328,14 +1328,14 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.25.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.25.0.tgz", - "integrity": "sha512-ZPaiAKEZ6Blt/TPAx5Ot0EIB/yGtLI2EsGoY6F7XKklfMxYQyvtL+gT/UCqkMzO0BVFHLDlzvFqQzurYahxv9Q==", + "version": "8.26.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.26.0.tgz", + "integrity": "sha512-tiJ1Hvy/V/oMVRTbEOIeemA2XoylimlDQ03CgPPNaHYZbpsc78Hmngnt+WXZfJX1pjQ711V7g0H7cSJThGYfPQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.25.0", - "@typescript-eslint/visitor-keys": "8.25.0", + "@typescript-eslint/types": "8.26.0", + "@typescript-eslint/visitor-keys": "8.26.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -1351,7 +1351,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <5.8.0" + "typescript": ">=4.8.4 <5.9.0" } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { @@ -1381,16 +1381,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.25.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.25.0.tgz", - "integrity": "sha512-syqRbrEv0J1wywiLsK60XzHnQe/kRViI3zwFALrNEgnntn1l24Ra2KvOAWwWbWZ1lBZxZljPDGOq967dsl6fkA==", + "version": "8.26.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.26.0.tgz", + "integrity": "sha512-2L2tU3FVwhvU14LndnQCA2frYC8JnPDVKyQtWFPf8IYFMt/ykEN1bPolNhNbCVgOmdzTlWdusCTKA/9nKrf8Ig==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.25.0", - "@typescript-eslint/types": "8.25.0", - "@typescript-eslint/typescript-estree": "8.25.0" + "@typescript-eslint/scope-manager": "8.26.0", + "@typescript-eslint/types": "8.26.0", + "@typescript-eslint/typescript-estree": "8.26.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1401,17 +1401,17 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.8.0" + "typescript": ">=4.8.4 <5.9.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.25.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.25.0.tgz", - "integrity": "sha512-kCYXKAum9CecGVHGij7muybDfTS2sD3t0L4bJsEZLkyrXUImiCTq1M3LG2SRtOhiHFwMR9wAFplpT6XHYjTkwQ==", + "version": "8.26.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.26.0.tgz", + "integrity": "sha512-2z8JQJWAzPdDd51dRQ/oqIJxe99/hoLIqmf8RMCAJQtYDc535W/Jt2+RTP4bP0aKeBG1F65yjIZuczOXCmbWwg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.25.0", + "@typescript-eslint/types": "8.26.0", "eslint-visitor-keys": "^4.2.0" }, "engines": { @@ -2989,10 +2989,11 @@ } }, "node_modules/eslint-config-prettier": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.0.1.tgz", - "integrity": "sha512-lZBts941cyJyeaooiKxAtzoPHTN+GbQTJFAIdQbRhA4/8whaAraEh47Whw/ZFfrjNSnlAxqfm9i0XVAEkULjCw==", + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.0.2.tgz", + "integrity": "sha512-1105/17ZIMjmCOJOPNfVdbXafLCLj3hPmkmB7dLgt7XsQ/zkxSuDerE/xgO3RxoHysR1N1whmquY0lSn2O0VLg==", "dev": true, + "license": "MIT", "bin": { "eslint-config-prettier": "build/bin/cli.js" }, @@ -4974,9 +4975,9 @@ } }, "node_modules/prettier": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.2.tgz", - "integrity": "sha512-lc6npv5PH7hVqozBR7lkBNOGXV9vMwROAPlumdBkX0wTbbzPu/U1hk5yL8p2pt4Xoc+2mkT8t/sow2YrV/M5qg==", + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz", + "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", "dev": true, "license": "MIT", "bin": { @@ -5828,10 +5829,11 @@ } }, "node_modules/typescript": { - "version": "5.7.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", - "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", + "version": "5.8.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz", + "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", "dev": true, + "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -6840,9 +6842,9 @@ } }, "@types/node": { - "version": "18.19.76", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.76.tgz", - "integrity": "sha512-yvR7Q9LdPz2vGpmpJX5LolrgRdWvB67MJKDPSgIIzpFbaf9a1j/f5DnLp5VDyHGMR0QZHlTr1afsD87QCXFHKw==", + "version": "18.19.79", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.79.tgz", + "integrity": "sha512-90K8Oayimbctc5zTPHPfZloc/lGVs7f3phUAAMcTgEPtg8kKquGZDERC8K4vkBYkQQh48msiYUslYtxTWvqcAg==", "dev": true, "requires": { "undici-types": "~5.26.4" @@ -6905,16 +6907,16 @@ } }, "@typescript-eslint/eslint-plugin": { - "version": "8.25.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.25.0.tgz", - "integrity": "sha512-VM7bpzAe7JO/BFf40pIT1lJqS/z1F8OaSsUB3rpFJucQA4cOSuH2RVVVkFULN+En0Djgr29/jb4EQnedUo95KA==", + "version": "8.26.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.26.0.tgz", + "integrity": "sha512-cLr1J6pe56zjKYajK6SSSre6nl1Gj6xDp1TY0trpgPzjVbgDwd09v2Ws37LABxzkicmUjhEeg/fAUjPJJB1v5Q==", "dev": true, "requires": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.25.0", - "@typescript-eslint/type-utils": "8.25.0", - "@typescript-eslint/utils": "8.25.0", - "@typescript-eslint/visitor-keys": "8.25.0", + "@typescript-eslint/scope-manager": "8.26.0", + "@typescript-eslint/type-utils": "8.26.0", + "@typescript-eslint/utils": "8.26.0", + "@typescript-eslint/visitor-keys": "8.26.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -6922,54 +6924,54 @@ } }, "@typescript-eslint/parser": { - "version": "8.25.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.25.0.tgz", - "integrity": "sha512-4gbs64bnbSzu4FpgMiQ1A+D+urxkoJk/kqlDJ2W//5SygaEiAP2B4GoS7TEdxgwol2el03gckFV9lJ4QOMiiHg==", + "version": "8.26.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.26.0.tgz", + "integrity": "sha512-mNtXP9LTVBy14ZF3o7JG69gRPBK/2QWtQd0j0oH26HcY/foyJJau6pNUez7QrM5UHnSvwlQcJXKsk0I99B9pOA==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "8.25.0", - "@typescript-eslint/types": "8.25.0", - "@typescript-eslint/typescript-estree": "8.25.0", - "@typescript-eslint/visitor-keys": "8.25.0", + "@typescript-eslint/scope-manager": "8.26.0", + "@typescript-eslint/types": "8.26.0", + "@typescript-eslint/typescript-estree": "8.26.0", + "@typescript-eslint/visitor-keys": "8.26.0", "debug": "^4.3.4" } }, "@typescript-eslint/scope-manager": { - "version": "8.25.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.25.0.tgz", - "integrity": "sha512-6PPeiKIGbgStEyt4NNXa2ru5pMzQ8OYKO1hX1z53HMomrmiSB+R5FmChgQAP1ro8jMtNawz+TRQo/cSXrauTpg==", + "version": "8.26.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.26.0.tgz", + "integrity": "sha512-E0ntLvsfPqnPwng8b8y4OGuzh/iIOm2z8U3S9zic2TeMLW61u5IH2Q1wu0oSTkfrSzwbDJIB/Lm8O3//8BWMPA==", "dev": true, "requires": { - "@typescript-eslint/types": "8.25.0", - "@typescript-eslint/visitor-keys": "8.25.0" + "@typescript-eslint/types": "8.26.0", + "@typescript-eslint/visitor-keys": "8.26.0" } }, "@typescript-eslint/type-utils": { - "version": "8.25.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.25.0.tgz", - "integrity": "sha512-d77dHgHWnxmXOPJuDWO4FDWADmGQkN5+tt6SFRZz/RtCWl4pHgFl3+WdYCn16+3teG09DY6XtEpf3gGD0a186g==", + "version": "8.26.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.26.0.tgz", + "integrity": "sha512-ruk0RNChLKz3zKGn2LwXuVoeBcUMh+jaqzN461uMMdxy5H9epZqIBtYj7UiPXRuOpaALXGbmRuZQhmwHhaS04Q==", "dev": true, "requires": { - "@typescript-eslint/typescript-estree": "8.25.0", - "@typescript-eslint/utils": "8.25.0", + "@typescript-eslint/typescript-estree": "8.26.0", + "@typescript-eslint/utils": "8.26.0", "debug": "^4.3.4", "ts-api-utils": "^2.0.1" } }, "@typescript-eslint/types": { - "version": "8.25.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.25.0.tgz", - "integrity": "sha512-+vUe0Zb4tkNgznQwicsvLUJgZIRs6ITeWSCclX1q85pR1iOiaj+4uZJIUp//Z27QWu5Cseiw3O3AR8hVpax7Aw==", + "version": "8.26.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.26.0.tgz", + "integrity": "sha512-89B1eP3tnpr9A8L6PZlSjBvnJhWXtYfZhECqlBl1D9Lme9mHO6iWlsprBtVenQvY1HMhax1mWOjhtL3fh/u+pA==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "8.25.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.25.0.tgz", - "integrity": "sha512-ZPaiAKEZ6Blt/TPAx5Ot0EIB/yGtLI2EsGoY6F7XKklfMxYQyvtL+gT/UCqkMzO0BVFHLDlzvFqQzurYahxv9Q==", + "version": "8.26.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.26.0.tgz", + "integrity": "sha512-tiJ1Hvy/V/oMVRTbEOIeemA2XoylimlDQ03CgPPNaHYZbpsc78Hmngnt+WXZfJX1pjQ711V7g0H7cSJThGYfPQ==", "dev": true, "requires": { - "@typescript-eslint/types": "8.25.0", - "@typescript-eslint/visitor-keys": "8.25.0", + "@typescript-eslint/types": "8.26.0", + "@typescript-eslint/visitor-keys": "8.26.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -6999,24 +7001,24 @@ } }, "@typescript-eslint/utils": { - "version": "8.25.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.25.0.tgz", - "integrity": "sha512-syqRbrEv0J1wywiLsK60XzHnQe/kRViI3zwFALrNEgnntn1l24Ra2KvOAWwWbWZ1lBZxZljPDGOq967dsl6fkA==", + "version": "8.26.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.26.0.tgz", + "integrity": "sha512-2L2tU3FVwhvU14LndnQCA2frYC8JnPDVKyQtWFPf8IYFMt/ykEN1bPolNhNbCVgOmdzTlWdusCTKA/9nKrf8Ig==", "dev": true, "requires": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.25.0", - "@typescript-eslint/types": "8.25.0", - "@typescript-eslint/typescript-estree": "8.25.0" + "@typescript-eslint/scope-manager": "8.26.0", + "@typescript-eslint/types": "8.26.0", + "@typescript-eslint/typescript-estree": "8.26.0" } }, "@typescript-eslint/visitor-keys": { - "version": "8.25.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.25.0.tgz", - "integrity": "sha512-kCYXKAum9CecGVHGij7muybDfTS2sD3t0L4bJsEZLkyrXUImiCTq1M3LG2SRtOhiHFwMR9wAFplpT6XHYjTkwQ==", + "version": "8.26.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.26.0.tgz", + "integrity": "sha512-2z8JQJWAzPdDd51dRQ/oqIJxe99/hoLIqmf8RMCAJQtYDc535W/Jt2+RTP4bP0aKeBG1F65yjIZuczOXCmbWwg==", "dev": true, "requires": { - "@typescript-eslint/types": "8.25.0", + "@typescript-eslint/types": "8.26.0", "eslint-visitor-keys": "^4.2.0" }, "dependencies": { @@ -8154,9 +8156,9 @@ } }, "eslint-config-prettier": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.0.1.tgz", - "integrity": "sha512-lZBts941cyJyeaooiKxAtzoPHTN+GbQTJFAIdQbRhA4/8whaAraEh47Whw/ZFfrjNSnlAxqfm9i0XVAEkULjCw==", + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.0.2.tgz", + "integrity": "sha512-1105/17ZIMjmCOJOPNfVdbXafLCLj3hPmkmB7dLgt7XsQ/zkxSuDerE/xgO3RxoHysR1N1whmquY0lSn2O0VLg==", "dev": true, "requires": {} }, @@ -9631,9 +9633,9 @@ "dev": true }, "prettier": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.2.tgz", - "integrity": "sha512-lc6npv5PH7hVqozBR7lkBNOGXV9vMwROAPlumdBkX0wTbbzPu/U1hk5yL8p2pt4Xoc+2mkT8t/sow2YrV/M5qg==", + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz", + "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", "dev": true }, "process-nextick-args": { @@ -10222,9 +10224,9 @@ } }, "typescript": { - "version": "5.7.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", - "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", + "version": "5.8.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz", + "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", "dev": true }, "uc.micro": { diff --git a/package.json b/package.json index 656b96250..9f216849a 100644 --- a/package.json +++ b/package.json @@ -1569,15 +1569,15 @@ "@types/lodash.throttle": "^4.1.9", "@types/mocha": "^10.0.10", "@types/mock-fs": "^4.13.4", - "@types/node": "^18.19.76", + "@types/node": "^18.19.79", "@types/plist": "^3.0.5", "@types/semver": "^7.5.8", "@types/sinon": "^17.0.4", "@types/sinon-chai": "^3.2.12", "@types/vscode": "^1.88.0", "@types/xml2js": "^0.4.14", - "@typescript-eslint/eslint-plugin": "^8.25.0", - "@typescript-eslint/parser": "^8.25.0", + "@typescript-eslint/eslint-plugin": "^8.26.0", + "@typescript-eslint/parser": "^8.26.0", "@vscode/debugprotocol": "^1.68.0", "@vscode/test-cli": "^0.0.10", "@vscode/test-electron": "^2.4.1", @@ -1588,12 +1588,12 @@ "del-cli": "^6.0.0", "esbuild": "^0.25.0", "eslint": "^8.57.0", - "eslint-config-prettier": "^10.0.1", + "eslint-config-prettier": "^10.0.2", "lodash.throttle": "^4.1.1", "mocha": "^10.8.2", "mock-fs": "^5.5.0", "node-pty": "^1.0.0", - "prettier": "^3.5.2", + "prettier": "^3.5.3", "semver": "^7.7.1", "simple-git": "^3.27.0", "sinon": "^19.0.2", @@ -1601,7 +1601,7 @@ "source-map-support": "^0.5.21", "strip-ansi": "^6.0.1", "tsx": "^4.19.3", - "typescript": "^5.7.3" + "typescript": "^5.8.2" }, "dependencies": { "@vscode/codicons": "^0.0.36", From d2990b3b2babb8ff411d5dbb6b5b0b006cd4d73f Mon Sep 17 00:00:00 2001 From: Paul LeMarquand Date: Tue, 4 Mar 2025 08:53:23 -0500 Subject: [PATCH 34/86] Document how to use a different Visual Studio (#1421) * Document how to use a different Visual Studio Add some documentation to the settings docs that outlines how to specify which Visual Studio installation Swift should pull its dependencies from on Windows. Also annotates existing setting names in the document with links that jump straight to the relevent setting in VS Code. Issue: #1419 Co-authored-by: Rishi --- docs/settings.md | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/docs/settings.md b/docs/settings.md index 60b67aa92..3f98eeb64 100644 --- a/docs/settings.md +++ b/docs/settings.md @@ -6,9 +6,9 @@ This document outlines useful configuration options not covered by the settings ## Command Plugins -Swift packages can define [command plugins](https://github.com/swiftlang/swift-package-manager/blob/main/Documentation/Plugins.md) that can perform arbitrary tasks. For example, the [swift-format](https://github.com/swiftlang/swift-format) package exposes a `format-source-code` command which will use swift-format to format source code in a folder. These plugin commands can be invoked from VS Code using `> Swift: Run Command Plugin`. +Swift packages can define [command plugins](https://github.com/swiftlang/swift-package-manager/blob/main/Documentation/Plugins.md) that can perform arbitrary tasks. For example, the [swift-format](https://github.com/swiftlang/swift-format) package exposes a `format-source-code` command which will use swift-format to format source code in a folder. These plugin commands can be invoked from VS Code using `> Swift: Run Command Plugin`. -A plugin may require permissions to perform tasks like writing to the file system or using the network. If a plugin command requires one of these permissions, you will be prompted in the integrated terminal to accept them. If you trust the command and wish to apply permissions on every command execution, you can configure a setting in your `settings.json`. +A plugin may require permissions to perform tasks like writing to the file system or using the network. If a plugin command requires one of these permissions, you will be prompted in the integrated terminal to accept them. If you trust the command and wish to apply permissions on every command execution, you can configure the [`swift.pluginPermissions`](vscode://settings/swift.pluginPermissions) setting in your `settings.json`. ```json { @@ -51,17 +51,25 @@ Alternatively, you can define a task in your tasks.json and define permissions d If you're using a nightly (`main`) or recent `6.0` toolchain you can enable support for background indexing in Sourcekit-LSP. This removes the need to do a build before getting code completion and diagnostics. -To enable support, set the `swift.sourcekit-lsp.backgroundIndexing` setting to `true`. +To enable support, set the [`swift.sourcekit-lsp.backgroundIndexing`](vscode://settings/swift.sourcekit-lsp.backgroundIndexing) setting to `true`. ### Support for 'Expand Macro' If you are using a nightly (`main`) toolchain you can enable support for the "Peek Macro" Quick Action, accessible through the light bulb icon when the cursor is on a macro. -To enable support, set the following Sourcekit-LSP server arguments in your settings.json, or add two new entries to the `Sourcekit-lsp: Server Arguments` entry in the extension settings page. +To enable support, set the following Sourcekit-LSP server arguments in your settings.json, or add two new entries to the [`swift.sourcekit-lsp.serverArguments`](vscode://settings/swift.sourcekit-lsp.serverArguments) setting. -``` +```json "swift.sourcekit-lsp.serverArguments": [ "--experimental-feature", "show-macro-expansions" ] ``` + +## Windows Development + +### Specifying a Visual Studio installation + +Swift depends on a number of developer tools when running on Windows, including the C++ toolchain and the Windows SDK. Typically these are installed with [Visual Studio](https://visualstudio.microsoft.com/). + +If you have multiple versions of Visual Studio installed you can specify the path to the desired version by setting a `VCToolsInstallDir` environment variable using the [`swift.swiftEnvironmentVariables`](vscode://settings/swift.swiftEnvironmentVariables) setting. From b9713fd1939b35374863ae7a359ea2d7361b0f05 Mon Sep 17 00:00:00 2001 From: Paul LeMarquand Date: Tue, 4 Mar 2025 15:30:18 -0500 Subject: [PATCH 35/86] Add tests for parsing parallel XCTest run output (#1418) When performing a parallel XCTest run we first parse the terminal output, then parse the xUnit XML output and merge the two data sets together to create the run results. This merging was not being tested at the unit test level, only at the integration test level, which lead to a subtle and occasional bug, #1334 (fixed in #1343). As a followup, update the existing XCTestOutputParser tests to test both regular and parallel test run output. xUnit XML is synthesized from the expected results and fed in to the `TestXUnitParser` to modify the test's `TestRunState` just as it is in the extension. --- .../testexplorer/MockTestRunState.ts | 12 +- .../testexplorer/XCTestOutputParser.test.ts | 893 ++++++++++-------- 2 files changed, 485 insertions(+), 420 deletions(-) diff --git a/test/integration-tests/testexplorer/MockTestRunState.ts b/test/integration-tests/testexplorer/MockTestRunState.ts index c8ae407c6..91335235e 100644 --- a/test/integration-tests/testexplorer/MockTestRunState.ts +++ b/test/integration-tests/testexplorer/MockTestRunState.ts @@ -24,8 +24,8 @@ export enum TestStatus { skipped = "skipped", } -/** TestItem */ -interface TestItem { +/** TestRunTestItem */ +export interface TestRunTestItem { name: string; status: TestStatus; issues?: { @@ -40,11 +40,11 @@ interface TestItem { interface ITestItemFinder { getIndex(id: string): number; - tests: TestItem[]; + tests: TestRunTestItem[]; } export class DarwinTestItemFinder implements ITestItemFinder { - tests: TestItem[] = []; + tests: TestRunTestItem[] = []; getIndex(id: string): number { const index = this.tests.findIndex(item => item.name === id); if (index === -1) { @@ -56,7 +56,7 @@ export class DarwinTestItemFinder implements ITestItemFinder { } export class NonDarwinTestItemFinder implements ITestItemFinder { - tests: TestItem[] = []; + tests: TestRunTestItem[] = []; getIndex(id: string): number { const index = this.tests.findIndex(item => item.name.endsWith(id)); if (index === -1) { @@ -81,7 +81,7 @@ export class TestRunState implements ITestRunState { public testItemFinder: ITestItemFinder; - get tests(): TestItem[] { + get tests(): TestRunTestItem[] { return this.testItemFinder.tests; } diff --git a/test/integration-tests/testexplorer/XCTestOutputParser.test.ts b/test/integration-tests/testexplorer/XCTestOutputParser.test.ts index 7617f874e..4f1fe0737 100644 --- a/test/integration-tests/testexplorer/XCTestOutputParser.test.ts +++ b/test/integration-tests/testexplorer/XCTestOutputParser.test.ts @@ -19,537 +19,602 @@ import { nonDarwinTestRegex, XCTestOutputParser, } from "../../../src/TestExplorer/TestParsers/XCTestOutputParser"; -import { TestRunState, TestStatus } from "./MockTestRunState"; +import { TestRunState, TestRunTestItem, TestStatus } from "./MockTestRunState"; import { sourceLocationToVSCodeLocation } from "../../../src/utilities/utilities"; +import { TestXUnitParser } from "../../../src/TestExplorer/TestXUnitParser"; +import { activateExtensionForSuite } from "../utilities/testutilities"; +import { SwiftOutputChannel } from "../../../src/ui/SwiftOutputChannel"; + +enum ParserTestKind { + Regular = "Regular Test Run", + Parallel = "Parallel Test Run", +} suite("XCTestOutputParser Suite", () => { - const inputToTestOutput = (input: string) => - input + function inputToTestOutput(input: string) { + return input .split("\n") .slice(0, -1) .map(line => `${line}\r\n`); + } + + function expectedStateToXML(tests: TestRunTestItem[]) { + const extractClassName = (name: string) => { + const parts = name.split("/"); + return parts[0]; + }; + const extractName = (name: string) => { + const parts = name.split("/"); + return parts[parts.length - 1]; + }; + const extractTiming = (test: TestRunTestItem) => { + if (test.timing) { + const time = + "duration" in test.timing ? test.timing.duration : test.timing.timestamp; + return `time="${time}"`; + } + return ""; + }; + const extractFailures = (test: TestRunTestItem[]) => { + return test.reduce((acc, t) => acc + ((t.issues?.length ?? 0) > 0 ? 1 : 0), 0); + }; + return ` + + +${tests.map( + t => + `${(t.issues ?? []).map(() => `\n`).join("\n")}` +)} + + +`; + } + + let hasMultiLineParallelTestOutput: boolean; + activateExtensionForSuite({ + async setup(ctx) { + hasMultiLineParallelTestOutput = ctx.toolchain.hasMultiLineParallelTestOutput; + }, + }); - suite("Darwin", () => { - let outputParser: XCTestOutputParser; - let testRunState: TestRunState; - beforeEach(() => { - outputParser = new XCTestOutputParser(darwinTestRegex); - testRunState = new TestRunState(true); - }); - - test("Passed Test", () => { - const input = `Test Case '-[MyTests.MyTests testPass]' started. + [ParserTestKind.Regular, ParserTestKind.Parallel].forEach(parserTestKind => { + function assertTestRunState(testRunState: TestRunState, expected: TestRunTestItem[]) { + // When parsing the results of a parallel test run the TestXUnitParser runs on the + // generated XML results after the test run is completed to fill out the state with + // anything that couldn't be captured off the output stream. + if (parserTestKind === ParserTestKind.Parallel) { + const xmlResults = expectedStateToXML(expected); + const xmlParser = new TestXUnitParser(hasMultiLineParallelTestOutput); + xmlParser.parse(xmlResults, testRunState, new SwiftOutputChannel("test")); + } + + assert.deepEqual(testRunState.tests, expected); + } + + suite(`${parserTestKind}`, () => { + suite("Darwin", () => { + let outputParser: XCTestOutputParser; + let testRunState: TestRunState; + beforeEach(() => { + outputParser = new XCTestOutputParser(darwinTestRegex); + testRunState = new TestRunState(true); + }); + + test("Passed Test", () => { + const input = `Test Case '-[MyTests.MyTests testPass]' started. Test Case '-[MyTests.MyTests testPass]' passed (0.001 seconds). `; - outputParser.parseResult(input, testRunState); - - assert.deepEqual(testRunState.tests, [ - { - name: "MyTests.MyTests/testPass", - status: TestStatus.passed, - timing: { duration: 0.001 }, - output: inputToTestOutput(input), - }, - ]); - }); + outputParser.parseResult(input, testRunState); - test("Multiple Passed Tests", () => { - const test1Input = `Test Case '-[MyTests.MyTests testPass]' started. + assertTestRunState(testRunState, [ + { + name: "MyTests.MyTests/testPass", + status: TestStatus.passed, + timing: { duration: 0.001 }, + output: inputToTestOutput(input), + }, + ]); + }); + + test("Multiple Passed Tests", () => { + const test1Input = `Test Case '-[MyTests.MyTests testPass]' started. Test Case '-[MyTests.MyTests testPass]' passed (0.001 seconds). `; - const test2Input = `Test Case '-[MyTests.MyTests testPass2]' started. + const test2Input = `Test Case '-[MyTests.MyTests testPass2]' started. Test Case '-[MyTests.MyTests testPass2]' passed (0.001 seconds). `; - const input = `${test1Input}${test2Input}`; - - outputParser.parseResult(input, testRunState); - - assert.deepEqual(testRunState.tests, [ - { - name: "MyTests.MyTests/testPass", - status: TestStatus.passed, - timing: { duration: 0.001 }, - output: inputToTestOutput(test1Input), - }, - { - name: "MyTests.MyTests/testPass2", - status: TestStatus.passed, - timing: { duration: 0.001 }, - output: inputToTestOutput(test2Input), - }, - ]); - }); + const input = `${test1Input}${test2Input}`; - test("Failed Test", () => { - const input = `Test Case '-[MyTests.MyTests testFail]' started. + outputParser.parseResult(input, testRunState); + + assertTestRunState(testRunState, [ + { + name: "MyTests.MyTests/testPass", + status: TestStatus.passed, + timing: { duration: 0.001 }, + output: inputToTestOutput(test1Input), + }, + { + name: "MyTests.MyTests/testPass2", + status: TestStatus.passed, + timing: { duration: 0.001 }, + output: inputToTestOutput(test2Input), + }, + ]); + }); + + test("Failed Test", () => { + const input = `Test Case '-[MyTests.MyTests testFail]' started. /Users/user/Developer/MyTests/MyTests.swift:59: error: -[MyTests.MyTests testFail] : XCTAssertEqual failed: ("1") is not equal to ("2") Test Case '-[MyTests.MyTests testFail]' failed (0.106 seconds). `; - outputParser.parseResult(input, testRunState); - - assert.deepEqual(testRunState.tests, [ - { - name: "MyTests.MyTests/testFail", - status: TestStatus.failed, - timing: { duration: 0.106 }, - issues: [ + outputParser.parseResult(input, testRunState); + + assertTestRunState(testRunState, [ { - message: `XCTAssertEqual failed: ("1") is not equal to ("2")`, - location: sourceLocationToVSCodeLocation( - "/Users/user/Developer/MyTests/MyTests.swift", - 59, - 0 - ), - isKnown: false, - diff: { - expected: '"1"', - actual: '"2"', - }, + name: "MyTests.MyTests/testFail", + status: TestStatus.failed, + timing: { duration: 0.106 }, + issues: [ + { + message: `XCTAssertEqual failed: ("1") is not equal to ("2")`, + location: sourceLocationToVSCodeLocation( + "/Users/user/Developer/MyTests/MyTests.swift", + 59, + 0 + ), + isKnown: false, + diff: { + expected: '"1"', + actual: '"2"', + }, + }, + ], + output: inputToTestOutput(input), }, - ], - output: inputToTestOutput(input), - }, - ]); - }); + ]); + }); - test("Skipped Test", () => { - const input = `Test Case '-[MyTests.MyTests testSkip]' started. + test("Skipped Test", () => { + const input = `Test Case '-[MyTests.MyTests testSkip]' started. /Users/user/Developer/MyTests/MyTests.swift:90: -[MyTests.MyTests testSkip] : Test skipped Test Case '-[MyTests.MyTests testSkip]' skipped (0.002 seconds). `; - outputParser.parseResult(input, testRunState); + outputParser.parseResult(input, testRunState); - assert.deepEqual(testRunState.tests, [ - { - name: "MyTests.MyTests/testSkip", - status: TestStatus.skipped, - output: inputToTestOutput(input), - }, - ]); - }); + assertTestRunState(testRunState, [ + { + name: "MyTests.MyTests/testSkip", + status: TestStatus.skipped, + output: inputToTestOutput(input), + }, + ]); + }); - test("Multi-line Fail", () => { - const input = `Test Case '-[MyTests.MyTests testFail]' started. + test("Multi-line Fail", () => { + const input = `Test Case '-[MyTests.MyTests testFail]' started. /Users/user/Developer/MyTests/MyTests.swift:59: error: -[MyTests.MyTests testFail] : failed - Multiline fail message Test Case '-[MyTests.MyTests testFail]' failed (0.571 seconds). `; - outputParser.parseResult(input, testRunState); - - assert.deepEqual(testRunState.tests, [ - { - name: "MyTests.MyTests/testFail", - status: TestStatus.failed, - timing: { duration: 0.571 }, - issues: [ + outputParser.parseResult(input, testRunState); + + assertTestRunState(testRunState, [ { - message: `failed - Multiline + name: "MyTests.MyTests/testFail", + status: TestStatus.failed, + timing: { duration: 0.571 }, + issues: [ + { + message: `failed - Multiline fail message`, - location: sourceLocationToVSCodeLocation( - "/Users/user/Developer/MyTests/MyTests.swift", - 59, - 0 - ), - isKnown: false, - diff: undefined, + location: sourceLocationToVSCodeLocation( + "/Users/user/Developer/MyTests/MyTests.swift", + 59, + 0 + ), + isKnown: false, + diff: undefined, + }, + ], + output: inputToTestOutput(input), }, - ], - output: inputToTestOutput(input), - }, - ]); - }); + ]); + }); - test("Multi-line Fail followed by another error", () => { - const input = `Test Case '-[MyTests.MyTests testFail]' started. + test("Multi-line Fail followed by another error", () => { + const input = `Test Case '-[MyTests.MyTests testFail]' started. /Users/user/Developer/MyTests/MyTests.swift:59: error: -[MyTests.MyTests testFail] : failed - Multiline fail message /Users/user/Developer/MyTests/MyTests.swift:61: error: -[MyTests.MyTests testFail] : failed - Again Test Case '-[MyTests.MyTests testFail]' failed (0.571 seconds). `; - outputParser.parseResult(input, testRunState); - - assert.deepEqual(testRunState.tests, [ - { - name: "MyTests.MyTests/testFail", - status: TestStatus.failed, - timing: { duration: 0.571 }, - issues: [ + outputParser.parseResult(input, testRunState); + + assertTestRunState(testRunState, [ { - message: `failed - Multiline + name: "MyTests.MyTests/testFail", + status: TestStatus.failed, + timing: { duration: 0.571 }, + issues: [ + { + message: `failed - Multiline fail message`, - location: sourceLocationToVSCodeLocation( - "/Users/user/Developer/MyTests/MyTests.swift", - 59, - 0 - ), - isKnown: false, - diff: undefined, + location: sourceLocationToVSCodeLocation( + "/Users/user/Developer/MyTests/MyTests.swift", + 59, + 0 + ), + isKnown: false, + diff: undefined, + }, + { + message: `failed - Again`, + location: sourceLocationToVSCodeLocation( + "/Users/user/Developer/MyTests/MyTests.swift", + 61, + 0 + ), + isKnown: false, + diff: undefined, + }, + ], + output: inputToTestOutput(input), }, - { - message: `failed - Again`, - location: sourceLocationToVSCodeLocation( - "/Users/user/Developer/MyTests/MyTests.swift", - 61, - 0 - ), - isKnown: false, - diff: undefined, - }, - ], - output: inputToTestOutput(input), - }, - ]); - }); + ]); + }); - test("Single-line Fail followed by another error", () => { - const input = `Test Case '-[MyTests.MyTests testFail]' started. + test("Single-line Fail followed by another error", () => { + const input = `Test Case '-[MyTests.MyTests testFail]' started. /Users/user/Developer/MyTests/MyTests.swift:59: error: -[MyTests.MyTests testFail] : failed - Message /Users/user/Developer/MyTests/MyTests.swift:61: error: -[MyTests.MyTests testFail] : failed - Again Test Case '-[MyTests.MyTests testFail]' failed (0.571 seconds). `; - outputParser.parseResult(input, testRunState); - - assert.deepEqual(testRunState.tests, [ - { - name: "MyTests.MyTests/testFail", - status: TestStatus.failed, - timing: { duration: 0.571 }, - issues: [ - { - message: `failed - Message`, - location: sourceLocationToVSCodeLocation( - "/Users/user/Developer/MyTests/MyTests.swift", - 59, - 0 - ), - isKnown: false, - diff: undefined, - }, + outputParser.parseResult(input, testRunState); + + assertTestRunState(testRunState, [ { - message: `failed - Again`, - location: sourceLocationToVSCodeLocation( - "/Users/user/Developer/MyTests/MyTests.swift", - 61, - 0 - ), - isKnown: false, - diff: undefined, + name: "MyTests.MyTests/testFail", + status: TestStatus.failed, + timing: { duration: 0.571 }, + issues: [ + { + message: `failed - Message`, + location: sourceLocationToVSCodeLocation( + "/Users/user/Developer/MyTests/MyTests.swift", + 59, + 0 + ), + isKnown: false, + diff: undefined, + }, + { + message: `failed - Again`, + location: sourceLocationToVSCodeLocation( + "/Users/user/Developer/MyTests/MyTests.swift", + 61, + 0 + ), + isKnown: false, + diff: undefined, + }, + ], + output: inputToTestOutput(input), }, - ], - output: inputToTestOutput(input), - }, - ]); - }); + ]); + }); - test("Split line", () => { - const input1 = `Test Case '-[MyTests.MyTests testPass]' started. + test("Split line", () => { + const input1 = `Test Case '-[MyTests.MyTests testPass]' started. Test Case '-[MyTests.MyTests`; - const input2 = ` testPass]' passed (0.006 seconds). + const input2 = ` testPass]' passed (0.006 seconds). `; - outputParser.parseResult(input1, testRunState); - outputParser.parseResult(input2, testRunState); - - assert.deepEqual(testRunState.tests, [ - { - name: "MyTests.MyTests/testPass", - status: TestStatus.passed, - timing: { duration: 0.006 }, - output: inputToTestOutput(input1 + input2), - }, - ]); - }); + outputParser.parseResult(input1, testRunState); + outputParser.parseResult(input2, testRunState); - test("Suite", () => { - const input = `Test Suite 'MyTests' started at 2024-08-26 13:19:25.325. + assertTestRunState(testRunState, [ + { + name: "MyTests.MyTests/testPass", + status: TestStatus.passed, + timing: { duration: 0.006 }, + output: inputToTestOutput(input1 + input2), + }, + ]); + }); + + test("Suite", () => { + const input = `Test Suite 'MyTests' started at 2024-08-26 13:19:25.325. Test Case '-[MyTests.MyTests testPass]' started. Test Case '-[MyTests.MyTests testPass]' passed (0.001 seconds). Test Suite 'MyTests' passed at 2024-08-26 13:19:25.328. - Executed 1 test, with 0 failures (0 unexpected) in 0.001 (0.001) seconds + Executed 1 test, with 0 failures (0 unexpected) in 0.001 (0.001) seconds `; - outputParser.parseResult(input, testRunState); - - const testOutput = inputToTestOutput(input); - assert.deepEqual(testRunState.tests, [ - { - name: "MyTests.MyTests", - output: [testOutput[0], testOutput[3]], - status: TestStatus.passed, - }, - { - name: "MyTests.MyTests/testPass", - status: TestStatus.passed, - timing: { duration: 0.001 }, - output: [testOutput[1], testOutput[2]], - }, - ]); - assert.deepEqual(inputToTestOutput(input), testRunState.allOutput); - }); + outputParser.parseResult(input, testRunState); + + const testOutput = inputToTestOutput(input); + assertTestRunState(testRunState, [ + { + name: "MyTests.MyTests", + output: [testOutput[0], testOutput[3]], + status: TestStatus.passed, + }, + { + name: "MyTests.MyTests/testPass", + status: TestStatus.passed, + timing: { duration: 0.001 }, + output: [testOutput[1], testOutput[2]], + }, + ]); + assert.deepEqual(inputToTestOutput(input), testRunState.allOutput); + }); - test("Empty Suite", () => { - const input = `Test Suite 'Selected tests' started at 2024-10-19 15:23:29.594. + test("Empty Suite", () => { + const input = `Test Suite 'Selected tests' started at 2024-10-19 15:23:29.594. Test Suite 'EmptyAppPackageTests.xctest' started at 2024-10-19 15:23:29.595. Test Suite 'EmptyAppPackageTests.xctest' passed at 2024-10-19 15:23:29.595. - Executed 0 tests, with 0 failures (0 unexpected) in 0.000 (0.000) seconds + Executed 0 tests, with 0 failures (0 unexpected) in 0.000 (0.000) seconds Test Suite 'Selected tests' passed at 2024-10-19 15:23:29.596. - Executed 0 tests, with 0 failures (0 unexpected) in 0.000 (0.001) seconds + Executed 0 tests, with 0 failures (0 unexpected) in 0.000 (0.001) seconds warning: No matching test cases were run`; - outputParser.parseResult(input, testRunState); + outputParser.parseResult(input, testRunState); - assert.deepEqual(testRunState.tests, []); - assert.deepEqual(inputToTestOutput(input), testRunState.allOutput); - }); + assertTestRunState(testRunState, []); + assert.deepEqual(inputToTestOutput(input), testRunState.allOutput); + }); - test("Multiple Suites", () => { - const input = `Test Suite 'All tests' started at 2024-10-20 21:54:32.568. + test("Multiple Suites", () => { + const input = `Test Suite 'All tests' started at 2024-10-20 21:54:32.568. Test Suite 'EmptyAppPackageTests.xctest' started at 2024-10-20 21:54:32.570. Test Suite 'TestSuite1' started at 2024-10-20 21:54:32.570. Test Case '-[MyTests.TestSuite1 testFirst]' started. Test Case '-[MyTests.TestSuite1 testFirst]' passed (0.000 seconds). Test Suite 'TestSuite1' passed at 2024-10-20 21:54:32.570. - Executed 1 test, with 0 failures (0 unexpected) in 0.000 (0.001) seconds + Executed 1 test, with 0 failures (0 unexpected) in 0.000 (0.001) seconds Test Suite 'TestSuite2' started at 2024-10-20 21:54:32.570. Test Case '-[MyTests.TestSuite2 testSecond]' started. Test Case '-[MyTests.TestSuite2 testSecond]' passed (0.000 seconds). Test Suite 'TestSuite2' passed at 2024-10-20 21:54:32.571. - Executed 1 test, with 0 failures (0 unexpected) in 0.000 (0.000) seconds + Executed 1 test, with 0 failures (0 unexpected) in 0.000 (0.000) seconds Test Suite 'EmptyAppPackageTests.xctest' passed at 2024-10-20 21:54:32.571. - Executed 2 tests, with 0 failures (0 unexpected) in 0.001 (0.001) seconds + Executed 2 tests, with 0 failures (0 unexpected) in 0.001 (0.001) seconds Test Suite 'All tests' passed at 2024-10-20 21:54:32.571. - Executed 2 tests, with 0 failures (0 unexpected) in 0.001 (0.002) seconds`; - - outputParser.parseResult(input, testRunState); - - const testOutput = inputToTestOutput(input); - assert.deepEqual(testRunState.tests, [ - { - name: "MyTests.TestSuite1", - output: [testOutput[2], testOutput[5]], - status: "passed", - }, - { - name: "MyTests.TestSuite1/testFirst", - output: [testOutput[3], testOutput[4]], - status: "passed", - timing: { - duration: 0, - }, - }, - { - name: "MyTests.TestSuite2", - output: [testOutput[7], testOutput[10]], - status: "passed", - }, - { - name: "MyTests.TestSuite2/testSecond", - output: [testOutput[8], testOutput[9]], - status: "passed", - timing: { - duration: 0, - }, - }, - ]); - assert.deepEqual(inputToTestOutput(input), testRunState.allOutput); - }); + Executed 2 tests, with 0 failures (0 unexpected) in 0.001 (0.002) seconds`; + + outputParser.parseResult(input, testRunState); - test("Multiple Suites with Failed Test", () => { - const input = `Test Suite 'Selected tests' started at 2024-10-20 22:01:46.206. + const testOutput = inputToTestOutput(input); + assertTestRunState(testRunState, [ + { + name: "MyTests.TestSuite1", + output: [testOutput[2], testOutput[5]], + status: TestStatus.passed, + }, + { + name: "MyTests.TestSuite1/testFirst", + output: [testOutput[3], testOutput[4]], + status: TestStatus.passed, + timing: { + duration: 0, + }, + }, + { + name: "MyTests.TestSuite2", + output: [testOutput[7], testOutput[10]], + status: TestStatus.passed, + }, + { + name: "MyTests.TestSuite2/testSecond", + output: [testOutput[8], testOutput[9]], + status: TestStatus.passed, + timing: { + duration: 0, + }, + }, + ]); + assert.deepEqual(inputToTestOutput(input), testRunState.allOutput); + }); + + test("Multiple Suites with Failed Test", () => { + const input = `Test Suite 'Selected tests' started at 2024-10-20 22:01:46.206. Test Suite 'EmptyAppPackageTests.xctest' started at 2024-10-20 22:01:46.207. Test Suite 'TestSuite1' started at 2024-10-20 22:01:46.207. Test Case '-[MyTests.TestSuite1 testFirst]' started. Test Case '-[MyTests.TestSuite1 testFirst]' passed (0.000 seconds). Test Suite 'TestSuite1' passed at 2024-10-20 22:01:46.208. - Executed 1 test, with 0 failures (0 unexpected) in 0.000 (0.000) seconds + Executed 1 test, with 0 failures (0 unexpected) in 0.000 (0.000) seconds Test Suite 'TestSuite2' started at 2024-10-20 22:01:46.208. Test Case '-[MyTests.TestSuite2 testSecond]' started. /Users/user/Developer/MyTests/MyTests.swift:13: error: -[MyTests.TestSuite2 testSecond] : failed Test Case '-[MyTests.TestSuite2 testSecond]' failed (0.000 seconds). Test Suite 'TestSuite2' failed at 2024-10-20 22:01:46.306. - Executed 1 test, with 1 failure (0 unexpected) in 0.000 (0.000) seconds + Executed 1 test, with 1 failure (0 unexpected) in 0.000 (0.000) seconds Test Suite 'EmptyAppPackageTests.xctest' failed at 2024-10-20 22:01:46.306. - Executed 2 tests, with 1 failure (0 unexpected) in 0.001 (0.001) seconds + Executed 2 tests, with 1 failure (0 unexpected) in 0.001 (0.001) seconds Test Suite 'Selected tests' failed at 2024-10-20 22:01:46.306. - Executed 2 tests, with 1 failure (0 unexpected) in 0.002 (0.002) seconds`; - outputParser.parseResult(input, testRunState); - - const testOutput = inputToTestOutput(input); - assert.deepEqual(testRunState.tests, [ - { - name: "MyTests.TestSuite1", - output: [testOutput[2], testOutput[5]], - status: "passed", - }, - { - name: "MyTests.TestSuite1/testFirst", - output: [testOutput[3], testOutput[4]], - status: "passed", - timing: { - duration: 0, - }, - }, - { - name: "MyTests.TestSuite2", - output: [testOutput[7], testOutput[11]], - status: "failed", - }, - { - name: "MyTests.TestSuite2/testSecond", - output: [testOutput[8], testOutput[9], testOutput[10]], - status: "failed", - timing: { - duration: 0, - }, - issues: [ + Executed 2 tests, with 1 failure (0 unexpected) in 0.002 (0.002) seconds`; + outputParser.parseResult(input, testRunState); + + const testOutput = inputToTestOutput(input); + assertTestRunState(testRunState, [ { - message: "failed", - location: sourceLocationToVSCodeLocation( - "/Users/user/Developer/MyTests/MyTests.swift", - 13, - 0 - ), - isKnown: false, - diff: undefined, + name: "MyTests.TestSuite1", + output: [testOutput[2], testOutput[5]], + status: TestStatus.passed, }, - ], - }, - ]); - assert.deepEqual(inputToTestOutput(input), testRunState.allOutput); - }); + { + name: "MyTests.TestSuite1/testFirst", + output: [testOutput[3], testOutput[4]], + status: TestStatus.passed, + timing: { + duration: 0, + }, + }, + { + name: "MyTests.TestSuite2", + output: [testOutput[7], testOutput[11]], + status: TestStatus.failed, + }, + { + name: "MyTests.TestSuite2/testSecond", + output: [testOutput[8], testOutput[9], testOutput[10]], + status: TestStatus.failed, + timing: { + duration: 0, + }, + issues: [ + { + message: "failed", + location: sourceLocationToVSCodeLocation( + "/Users/user/Developer/MyTests/MyTests.swift", + 13, + 0 + ), + isKnown: false, + diff: undefined, + }, + ], + }, + ]); + assert.deepEqual(inputToTestOutput(input), testRunState.allOutput); + }); - suite("Diffs", () => { - const testRun = (message: string, expected?: string, actual?: string) => { - const input = `Test Case '-[MyTests.MyTests testFail]' started. + suite("Diffs", () => { + const testRun = (message: string, expected?: string, actual?: string) => { + const input = `Test Case '-[MyTests.MyTests testFail]' started. /Users/user/Developer/MyTests/MyTests.swift:59: error: -[MyTests.MyTests testFail] : ${message} Test Case '-[MyTests.MyTests testFail]' failed (0.106 seconds). `; - outputParser.parseResult(input, testRunState); - - assert.deepEqual(testRunState.tests, [ - { - name: "MyTests.MyTests/testFail", - status: TestStatus.failed, - timing: { duration: 0.106 }, - issues: [ + outputParser.parseResult(input, testRunState); + + assertTestRunState(testRunState, [ { - message, - location: sourceLocationToVSCodeLocation( - "/Users/user/Developer/MyTests/MyTests.swift", - 59, - 0 - ), - isKnown: false, - diff: - expected && actual - ? { - expected, - actual, - } - : undefined, + name: "MyTests.MyTests/testFail", + status: TestStatus.failed, + timing: { duration: 0.106 }, + issues: [ + { + message, + location: sourceLocationToVSCodeLocation( + "/Users/user/Developer/MyTests/MyTests.swift", + 59, + 0 + ), + isKnown: false, + diff: + expected && actual + ? { + expected, + actual, + } + : undefined, + }, + ], + output: inputToTestOutput(input), }, - ], - output: inputToTestOutput(input), - }, - ]); - }; - - test("XCTAssertEqual", () => { - testRun(`XCTAssertEqual failed: ("1") is not equal to ("2")`, '"1"', '"2"'); - }); - test("XCTAssertEqualMultiline", () => { - testRun( - `XCTAssertEqual failed: ("foo\nbar") is not equal to ("foo\nbaz")`, - '"foo\nbar"', - '"foo\nbaz"' - ); - }); - test("XCTAssertIdentical", () => { - testRun( - `XCTAssertIdentical failed: ("V: 1") is not identical to ("V: 2")`, - '"V: 1"', - '"V: 2"' - ); + ]); + }; + + test("XCTAssertEqual", () => { + testRun(`XCTAssertEqual failed: ("1") is not equal to ("2")`, '"1"', '"2"'); + }); + test("XCTAssertEqualMultiline", () => { + testRun( + `XCTAssertEqual failed: ("foo\nbar") is not equal to ("foo\nbaz")`, + '"foo\nbar"', + '"foo\nbaz"' + ); + }); + test("XCTAssertIdentical", () => { + testRun( + `XCTAssertIdentical failed: ("V: 1") is not identical to ("V: 2")`, + '"V: 1"', + '"V: 2"' + ); + }); + test("XCTAssertIdentical with Identical Strings", () => { + testRun(`XCTAssertIdentical failed: ("V: 1") is not identical to ("V: 1")`); + }); + }); }); - test("XCTAssertIdentical with Identical Strings", () => { - testRun(`XCTAssertIdentical failed: ("V: 1") is not identical to ("V: 1")`); - }); - }); - }); - suite("Linux", () => { - let outputParser: XCTestOutputParser; - let testRunState: TestRunState; - beforeEach(() => { - outputParser = new XCTestOutputParser(nonDarwinTestRegex); - testRunState = new TestRunState(false); - }); + suite("Linux", () => { + let outputParser: XCTestOutputParser; + let testRunState: TestRunState; + beforeEach(() => { + outputParser = new XCTestOutputParser(nonDarwinTestRegex); + testRunState = new TestRunState(false); + }); - test("Passed Test", () => { - const input = `Test Case 'MyTests.testPass' started. + test("Passed Test", () => { + const input = `Test Case 'MyTests.testPass' started. Test Case 'MyTests.testPass' passed (0.001 seconds). `; - outputParser.parseResult(input, testRunState); - - assert.deepEqual(testRunState.tests, [ - { - name: "MyTests/testPass", - status: TestStatus.passed, - timing: { duration: 0.001 }, - output: inputToTestOutput(input), - }, - ]); - }); + outputParser.parseResult(input, testRunState); - test("Failed Test", () => { - const input = `Test Case 'MyTests.testFail' started. + assertTestRunState(testRunState, [ + { + name: "MyTests/testPass", + status: TestStatus.passed, + timing: { duration: 0.001 }, + output: inputToTestOutput(input), + }, + ]); + }); + + test("Failed Test", () => { + const input = `Test Case 'MyTests.testFail' started. /Users/user/Developer/MyTests/MyTests.swift:59: error: MyTests.testFail : XCTAssertEqual failed: ("1") is not equal to ("2") Test Case 'MyTests.testFail' failed (0.106 seconds). `; - outputParser.parseResult(input, testRunState); - - assert.deepEqual(testRunState.tests, [ - { - name: "MyTests/testFail", - status: TestStatus.failed, - timing: { duration: 0.106 }, - issues: [ + outputParser.parseResult(input, testRunState); + + assertTestRunState(testRunState, [ { - message: `XCTAssertEqual failed: ("1") is not equal to ("2")`, - location: sourceLocationToVSCodeLocation( - "/Users/user/Developer/MyTests/MyTests.swift", - 59, - 0 - ), - isKnown: false, - diff: { - expected: '"1"', - actual: '"2"', - }, + name: "MyTests/testFail", + status: TestStatus.failed, + timing: { duration: 0.106 }, + issues: [ + { + message: `XCTAssertEqual failed: ("1") is not equal to ("2")`, + location: sourceLocationToVSCodeLocation( + "/Users/user/Developer/MyTests/MyTests.swift", + 59, + 0 + ), + isKnown: false, + diff: { + expected: '"1"', + actual: '"2"', + }, + }, + ], + output: inputToTestOutput(input), }, - ], - output: inputToTestOutput(input), - }, - ]); - }); + ]); + }); - test("Skipped Test", () => { - const input = `Test Case 'MyTests.testSkip' started. + test("Skipped Test", () => { + const input = `Test Case 'MyTests.testSkip' started. /Users/user/Developer/MyTests/MyTests.swift:90: MyTests.testSkip : Test skipped Test Case 'MyTests.testSkip' skipped (0.002 seconds). `; - outputParser.parseResult(input, testRunState); - - assert.deepEqual(testRunState.tests, [ - { - name: "MyTests/testSkip", - status: TestStatus.skipped, - output: inputToTestOutput(input), - }, - ]); + outputParser.parseResult(input, testRunState); + + assertTestRunState(testRunState, [ + { + name: "MyTests/testSkip", + status: TestStatus.skipped, + output: inputToTestOutput(input), + }, + ]); + }); + }); }); }); }); From ff3ca281deddadd7a982de23ed9dc209a32b2f68 Mon Sep 17 00:00:00 2001 From: Paul LeMarquand Date: Tue, 4 Mar 2025 21:51:34 -0500 Subject: [PATCH 36/86] Align test result icons for terminal with swift-testing (#1416) Swift-testing updated their icons for test failure in https://github.com/swiftlang/swift-testing/pull/983 --- src/TestExplorer/TestParsers/SwiftTestingOutputParser.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/TestExplorer/TestParsers/SwiftTestingOutputParser.ts b/src/TestExplorer/TestParsers/SwiftTestingOutputParser.ts index 7a354a928..aa0fc973d 100644 --- a/src/TestExplorer/TestParsers/SwiftTestingOutputParser.ts +++ b/src/TestExplorer/TestParsers/SwiftTestingOutputParser.ts @@ -530,7 +530,7 @@ export class SymbolRenderer { case TestSymbol.skip: case TestSymbol.passWithKnownIssue: case TestSymbol.fail: - return "\u{00D7}"; // Unicode: MULTIPLICATION SIGN + return "\u{279C}"; // Unicode: HEAVY ROUND-TIPPED RIGHTWARDS ARROW case TestSymbol.pass: return "\u{221A}"; // Unicode: SQUARE ROOT case TestSymbol.difference: @@ -551,7 +551,7 @@ export class SymbolRenderer { case TestSymbol.skip: case TestSymbol.passWithKnownIssue: case TestSymbol.fail: - return "\u{2718}"; // Unicode: HEAVY BALLOT X + return "\u{279C}"; // Unicode: HEAVY ROUND-TIPPED RIGHTWARDS ARROW case TestSymbol.pass: return "\u{2714}"; // Unicode: HEAVY CHECK MARK case TestSymbol.difference: From 13b707dbd951c28f97a1e3833467019bdf002bf2 Mon Sep 17 00:00:00 2001 From: Paul LeMarquand Date: Fri, 7 Mar 2025 10:49:53 -0500 Subject: [PATCH 37/86] Add tag to skipped test (#1415) Add a `skipped` tag to tests in the test explorer if they were skipped in the last run. This lets users filter down just the skipped tests in a test run. --- src/TestExplorer/TestRunner.ts | 26 +++++++++++++++++++ .../TestExplorerIntegration.test.ts | 9 +++++++ 2 files changed, 35 insertions(+) diff --git a/src/TestExplorer/TestRunner.ts b/src/TestExplorer/TestRunner.ts index c740d4e7f..bf79fd20c 100644 --- a/src/TestExplorer/TestRunner.ts +++ b/src/TestExplorer/TestRunner.ts @@ -122,6 +122,8 @@ export class TestRunProxy { if (this.runStarted) { return; } + + this.resetTags(this.controller); this.runStarted = true; // When a test run starts we need to do several things: @@ -191,6 +193,14 @@ export class TestRunProxy { const attachments = this.attachments[testIndex] ?? []; attachments.push(attachment); this.attachments[testIndex] = attachments; + + const testItem = this.testItems[testIndex]; + if (testItem) { + testItem.tags = [ + ...testItem.tags, + new vscode.TestTag(TestRunProxy.Tags.HAS_ATTACHMENT), + ]; + } }; public getTestIndex(id: string, filename?: string): number { @@ -214,6 +224,8 @@ export class TestRunProxy { } public skipped(test: vscode.TestItem) { + test.tags = [...test.tags, new vscode.TestTag(TestRunProxy.Tags.SKIPPED)]; + this.runState.skipped.push(test); this.testRun?.skipped(test); } @@ -323,6 +335,20 @@ export class TestRunProxy { // Compute final coverage numbers if any coverage info has been captured during the run. await this.coverage.computeCoverage(this.testRun); } + + private static Tags = { + SKIPPED: "skipped", + HAS_ATTACHMENT: "hasAttachment", + }; + + // Remove any tags that were added due to test results + private resetTags(controller: vscode.TestController) { + function removeTestRunTags(_acc: void, test: vscode.TestItem) { + const tags = Object.values(TestRunProxy.Tags); + test.tags = test.tags.filter(tag => !tags.includes(tag.id)); + } + reduceTestItemChildren(controller.items, removeTestRunTags, void 0); + } } /** Class used to run tests */ diff --git a/test/integration-tests/testexplorer/TestExplorerIntegration.test.ts b/test/integration-tests/testexplorer/TestExplorerIntegration.test.ts index 469a14fd5..0dd40e008 100644 --- a/test/integration-tests/testexplorer/TestExplorerIntegration.test.ts +++ b/test/integration-tests/testexplorer/TestExplorerIntegration.test.ts @@ -303,6 +303,15 @@ suite("Test Explorer Suite", function () { assertTestResults(testRun, { skipped: ["PackageTests.testWithKnownIssue()"], }); + + const testItem = testRun.testItems.find( + ({ id }) => id === "PackageTests.testWithKnownIssue()" + ); + assert.ok(testItem, "Unable to find test item for testWithKnownIssue"); + assert.ok( + testItem.tags.find(tag => tag.id === "skipped"), + "skipped tag was not found on test item" + ); }); test("testWithKnownIssueAndUnknownIssue", async () => { From db7a431179bccea3b46acc4abef03f6140798fa7 Mon Sep 17 00:00:00 2001 From: David Cummings Date: Fri, 7 Mar 2025 13:34:05 -0500 Subject: [PATCH 38/86] Improve UX around educational notes (#1423) The Swift compiler contains educational notes to further describe diagnostics [1]. These educational notes are documented in markdown files that are contained within the toolchain. Sourcekit LSP includes a link to the local markdown file when returning diagnostics that have an associated educational note (as part of the diagnostic code). The default behaviour in VSCode is to present these as a link in the diagnostic hover, and open the editor to the markdown file when the link is clicked. This PR updates the behaviour for educational notes to instead open the link using the markdown preview, which shows nicely rendered content. It also updates the link in the hover to show "More Information" instead of the code. Issue: #1395 [1] https://github.com/swiftlang/swift/tree/main/userdocs/diagnostics --- package.json | 9 ++ src/DiagnosticsManager.ts | 18 +++ src/commands.ts | 4 + src/commands/openEducationalNote.ts | 24 ++++ .../DiagnosticsManager.test.ts | 111 ++++++++++++++++++ 5 files changed, 166 insertions(+) create mode 100644 src/commands/openEducationalNote.ts diff --git a/package.json b/package.json index 9f216849a..99d6dbea1 100644 --- a/package.json +++ b/package.json @@ -111,6 +111,11 @@ "title": "Create New Project...", "category": "Swift" }, + { + "command": "swift.openEducationalNote", + "title": "Open Educational Note...", + "category": "Swift" + }, { "command": "swift.newFile", "title": "Create New Swift File...", @@ -943,6 +948,10 @@ { "command": "swift.coverAllTests", "when": "swift.isActivated" + }, + { + "command": "swift.openEducationalNote", + "when": "false" } ], "editor/context": [ diff --git a/src/DiagnosticsManager.ts b/src/DiagnosticsManager.ts index 7f2ea1578..e2ab4570d 100644 --- a/src/DiagnosticsManager.ts +++ b/src/DiagnosticsManager.ts @@ -142,6 +142,24 @@ export class DiagnosticsManager implements vscode.Disposable { d1 => isSwiftc(d1) && !!removedDiagnostics.find(d2 => isEqual(d1, d2)) ); } + + for (const diagnostic of newDiagnostics) { + if ( + diagnostic.code && + typeof diagnostic.code !== "string" && + typeof diagnostic.code !== "number" + ) { + if (diagnostic.code.target.fsPath.endsWith(".md")) { + diagnostic.code = { + target: vscode.Uri.parse( + `command:swift.openEducationalNote?${encodeURIComponent(JSON.stringify(diagnostic.code.target))}` + ), + value: "More Information...", + }; + } + } + } + // Append the new diagnostics we just received allDiagnostics.push(...newDiagnostics); this.allDiagnostics.set(uri.fsPath, allDiagnostics); diff --git a/src/commands.ts b/src/commands.ts index a28979167..6a6b08a32 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -31,6 +31,7 @@ import { openInExternalEditor } from "./commands/openInExternalEditor"; import { switchPlatform } from "./commands/switchPlatform"; import { insertFunctionComment } from "./commands/insertFunctionComment"; import { createNewProject } from "./commands/createNewProject"; +import { openEducationalNote } from "./commands/openEducationalNote"; import { openPackage } from "./commands/openPackage"; import { resolveDependencies } from "./commands/dependencies/resolve"; import { resetPackage } from "./commands/resetPackage"; @@ -199,6 +200,9 @@ export function register(ctx: WorkspaceContext): vscode.Disposable[] { vscode.commands.registerCommand(Commands.SHOW_NESTED_DEPENDENCIES_LIST, () => updateDependenciesViewList(ctx, false) ), + vscode.commands.registerCommand("swift.openEducationalNote", uri => + openEducationalNote(uri) + ), ]; } diff --git a/src/commands/openEducationalNote.ts b/src/commands/openEducationalNote.ts new file mode 100644 index 000000000..ea3dbe677 --- /dev/null +++ b/src/commands/openEducationalNote.ts @@ -0,0 +1,24 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the VS Code Swift open source project +// +// Copyright (c) 2021-2025 the VS Code Swift project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of VS Code Swift project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import * as vscode from "vscode"; + +/** + * Handle the user requesting to show an educational note. + * + * The default behaviour is to open it in a markdown preview to the side. + */ +export async function openEducationalNote(markdownFile: vscode.Uri | undefined): Promise { + await vscode.commands.executeCommand("markdown.showPreviewToSide", markdownFile); +} diff --git a/test/integration-tests/DiagnosticsManager.test.ts b/test/integration-tests/DiagnosticsManager.test.ts index b0a783090..5ac97f374 100644 --- a/test/integration-tests/DiagnosticsManager.test.ts +++ b/test/integration-tests/DiagnosticsManager.test.ts @@ -24,6 +24,7 @@ import { FolderContext } from "../../src/FolderContext"; import { Version } from "../../src/utilities/version"; import { Workbench } from "../../src/utilities/commands"; import { activateExtensionForSuite, folderInRootWorkspace } from "./utilities/testutilities"; +import { expect } from "chai"; const isEqual = (d1: vscode.Diagnostic, d2: vscode.Diagnostic) => { return ( @@ -555,6 +556,116 @@ suite("DiagnosticsManager Test Suite", async function () { await swiftConfig.update("diagnosticsCollection", undefined); }); + suite("markdownLinks", () => { + let diagnostic: vscode.Diagnostic; + + setup(async () => { + workspaceContext.diagnostics.clear(); + diagnostic = new vscode.Diagnostic( + new vscode.Range(new vscode.Position(1, 8), new vscode.Position(1, 8)), // Note swiftc provides empty range + "Cannot assign to value: 'bar' is a 'let' constant", + vscode.DiagnosticSeverity.Error + ); + diagnostic.source = "SourceKit"; + }); + + test("ignore strings", async () => { + diagnostic.code = "string"; + + // Now provide identical SourceKit diagnostic + workspaceContext.diagnostics.handleDiagnostics( + mainUri, + DiagnosticsManager.isSourcekit, + [diagnostic] + ); + + // check diagnostic hasn't changed + assertHasDiagnostic(mainUri, diagnostic); + + const diagnostics = vscode.languages.getDiagnostics(mainUri); + const matchingDiagnostic = diagnostics.find(findDiagnostic(diagnostic)); + + expect(matchingDiagnostic).to.have.property("code", "string"); + }); + + test("ignore numbers", async () => { + diagnostic.code = 1; + + // Now provide identical SourceKit diagnostic + workspaceContext.diagnostics.handleDiagnostics( + mainUri, + DiagnosticsManager.isSourcekit, + [diagnostic] + ); + + // check diagnostic hasn't changed + assertHasDiagnostic(mainUri, diagnostic); + + const diagnostics = vscode.languages.getDiagnostics(mainUri); + const matchingDiagnostic = diagnostics.find(findDiagnostic(diagnostic)); + + expect(matchingDiagnostic).to.have.property("code", 1); + }); + + test("target without markdown link", async () => { + const diagnosticCode = { + value: "string", + target: vscode.Uri.file("/some/path/md/readme.txt"), + }; + diagnostic.code = diagnosticCode; + + // Now provide identical SourceKit diagnostic + workspaceContext.diagnostics.handleDiagnostics( + mainUri, + DiagnosticsManager.isSourcekit, + [diagnostic] + ); + + // check diagnostic hasn't changed + assertHasDiagnostic(mainUri, diagnostic); + + const diagnostics = vscode.languages.getDiagnostics(mainUri); + const matchingDiagnostic = diagnostics.find(findDiagnostic(diagnostic)); + + expect(matchingDiagnostic).to.have.property("code", diagnostic.code); + }); + + test("target with markdown link", async () => { + const pathToMd = "/some/path/md/readme.md"; + diagnostic.code = { + value: "string", + target: vscode.Uri.file(pathToMd), + }; + + workspaceContext.diagnostics.handleDiagnostics( + mainUri, + DiagnosticsManager.isSourcekit, + [diagnostic] + ); + + const diagnostics = vscode.languages.getDiagnostics(mainUri); + const matchingDiagnostic = diagnostics.find(findDiagnostic(diagnostic)); + + expect(matchingDiagnostic).to.have.property("code"); + expect(matchingDiagnostic?.code).to.have.property("value", "More Information..."); + + if ( + matchingDiagnostic && + matchingDiagnostic.code && + typeof matchingDiagnostic.code !== "string" && + typeof matchingDiagnostic.code !== "number" + ) { + expect(matchingDiagnostic.code.target.scheme).to.equal("command"); + expect(matchingDiagnostic.code.target.path).to.equal( + "swift.openEducationalNote" + ); + expect(matchingDiagnostic.code.target.query).to.contain(pathToMd); + } else { + assert.fail("Diagnostic target not replaced with markdown command"); + } + }); + }); + suite("keepAll", () => { setup(async () => { await swiftConfig.update("diagnosticsCollection", "keepAll"); From 306d130f3a6988bb7ddb690d06459b55120c19c7 Mon Sep 17 00:00:00 2001 From: Ahmed AbdelMagied Date: Mon, 10 Mar 2025 19:16:24 +0200 Subject: [PATCH 39/86] 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. --- .../test/ModularPackage/Module1/Package.swift | 23 +++++++ .../Module1/Sources/Module1/Module1.swift | 7 +++ .../Sources/Module1Demo/Module1Demo.swift | 11 ++++ .../Module1/Tests/Module1Tests.swift | 20 ++++++ .../test/ModularPackage/Module2/Package.swift | 23 +++++++ .../Module2/Sources/Module2/Module2.swift | 7 +++ .../Module2/Sources/Module2Demo/main.swift | 11 ++++ .../Module2/Tests/Module2Tests.swift | 20 ++++++ assets/test/ModularPackage/Package.swift | 11 ++++ src/WorkspaceContext.ts | 48 ++++---------- src/utilities/workspace.ts | 63 +++++++++++++++++++ test/unit-tests/utilities/workspace.test.ts | 42 +++++++++++++ 12 files changed, 250 insertions(+), 36 deletions(-) create mode 100644 assets/test/ModularPackage/Module1/Package.swift create mode 100644 assets/test/ModularPackage/Module1/Sources/Module1/Module1.swift create mode 100644 assets/test/ModularPackage/Module1/Sources/Module1Demo/Module1Demo.swift create mode 100644 assets/test/ModularPackage/Module1/Tests/Module1Tests.swift create mode 100644 assets/test/ModularPackage/Module2/Package.swift create mode 100644 assets/test/ModularPackage/Module2/Sources/Module2/Module2.swift create mode 100644 assets/test/ModularPackage/Module2/Sources/Module2Demo/main.swift create mode 100644 assets/test/ModularPackage/Module2/Tests/Module2Tests.swift create mode 100644 assets/test/ModularPackage/Package.swift create mode 100644 src/utilities/workspace.ts create mode 100644 test/unit-tests/utilities/workspace.test.ts diff --git a/assets/test/ModularPackage/Module1/Package.swift b/assets/test/ModularPackage/Module1/Package.swift new file mode 100644 index 000000000..871dbc947 --- /dev/null +++ b/assets/test/ModularPackage/Module1/Package.swift @@ -0,0 +1,23 @@ +// swift-tools-version:6.0 + +import PackageDescription + +internal let package = Package( + name: "Module1", + products: [ + .executable(name: "Module1Demo", targets: ["Module1Demo"]), + ], + targets: [ + .testTarget( + name: "Module1Tests", + dependencies: ["Module1"] + ), + .executableTarget( + name: "Module1Demo", + dependencies: ["Module1"] + ), + .target( + name: "Module1" + ), + ] +) diff --git a/assets/test/ModularPackage/Module1/Sources/Module1/Module1.swift b/assets/test/ModularPackage/Module1/Sources/Module1/Module1.swift new file mode 100644 index 000000000..32c3e62fa --- /dev/null +++ b/assets/test/ModularPackage/Module1/Sources/Module1/Module1.swift @@ -0,0 +1,7 @@ +public struct Module1 { + public init() {} + + public func add(_ x: Int, _ y: Int) -> Int { + x + y + } +} \ No newline at end of file diff --git a/assets/test/ModularPackage/Module1/Sources/Module1Demo/Module1Demo.swift b/assets/test/ModularPackage/Module1/Sources/Module1Demo/Module1Demo.swift new file mode 100644 index 000000000..941beed71 --- /dev/null +++ b/assets/test/ModularPackage/Module1/Sources/Module1Demo/Module1Demo.swift @@ -0,0 +1,11 @@ +import Module1 + +private let module = Module1() + +@MainActor +func check(_ x: Int, _ y: Int) { + print(module.add(x, y)) +} + +check(1, 2) +check(2, 3) diff --git a/assets/test/ModularPackage/Module1/Tests/Module1Tests.swift b/assets/test/ModularPackage/Module1/Tests/Module1Tests.swift new file mode 100644 index 000000000..e7df7b33c --- /dev/null +++ b/assets/test/ModularPackage/Module1/Tests/Module1Tests.swift @@ -0,0 +1,20 @@ +import XCTest +@testable import Module1 + +internal final class Module1Tests: XCTestCase { + private var sut: Module1! + + override internal func setUp() { + super.setUp() + sut = .init() + } + + override internal func tearDown() { + sut = nil + super.tearDown() + } + + internal func test_add_with1And2_shouldReturn3() { + XCTAssertEqual(sut.add(1, 2), 3) + } +} \ No newline at end of file diff --git a/assets/test/ModularPackage/Module2/Package.swift b/assets/test/ModularPackage/Module2/Package.swift new file mode 100644 index 000000000..9ccde0e86 --- /dev/null +++ b/assets/test/ModularPackage/Module2/Package.swift @@ -0,0 +1,23 @@ +// swift-tools-version:6.0 + +import PackageDescription + +internal let package = Package( + name: "Module2", + products: [ + .executable(name: "Module2Demo", targets: ["Module2Demo"]), + ], + targets: [ + .testTarget( + name: "Module2Tests", + dependencies: ["Module2"] + ), + .executableTarget( + name: "Module2Demo", + dependencies: ["Module2"] + ), + .target( + name: "Module2" + ), + ] +) diff --git a/assets/test/ModularPackage/Module2/Sources/Module2/Module2.swift b/assets/test/ModularPackage/Module2/Sources/Module2/Module2.swift new file mode 100644 index 000000000..07541013e --- /dev/null +++ b/assets/test/ModularPackage/Module2/Sources/Module2/Module2.swift @@ -0,0 +1,7 @@ +public struct Module2 { + public init() {} + + public func subtract(_ x: Int, _ y: Int) -> Int { + x - y + } +} \ No newline at end of file diff --git a/assets/test/ModularPackage/Module2/Sources/Module2Demo/main.swift b/assets/test/ModularPackage/Module2/Sources/Module2Demo/main.swift new file mode 100644 index 000000000..6d33bf22a --- /dev/null +++ b/assets/test/ModularPackage/Module2/Sources/Module2Demo/main.swift @@ -0,0 +1,11 @@ +import Module2 + +private let module = Module2() + +@MainActor +func check(_ x: Int, _ y: Int) { + print(module.subtract(x, y)) +} + +check(1, 2) +check(2, 3) diff --git a/assets/test/ModularPackage/Module2/Tests/Module2Tests.swift b/assets/test/ModularPackage/Module2/Tests/Module2Tests.swift new file mode 100644 index 000000000..795376105 --- /dev/null +++ b/assets/test/ModularPackage/Module2/Tests/Module2Tests.swift @@ -0,0 +1,20 @@ +import XCTest +@testable import Module2 + +internal final class Module2Tests: XCTestCase { + private var sut: Module2! + + override internal func setUp() { + super.setUp() + sut = .init() + } + + override internal func tearDown() { + sut = nil + super.tearDown() + } + + internal func test_add_with5And2_shouldReturn3() { + XCTAssertEqual(sut.subtract(5, 2), 3) + } +} \ No newline at end of file diff --git a/assets/test/ModularPackage/Package.swift b/assets/test/ModularPackage/Package.swift new file mode 100644 index 000000000..c8af6a378 --- /dev/null +++ b/assets/test/ModularPackage/Package.swift @@ -0,0 +1,11 @@ +// swift-tools-version:6.0 + +import PackageDescription + +internal let package = Package( + name: "ModularPackage", + dependencies: [ + .package(path: "Module1"), + .package(path: "Module2"), + ] +) diff --git a/src/WorkspaceContext.ts b/src/WorkspaceContext.ts index 648b6776d..4095593d7 100644 --- a/src/WorkspaceContext.ts +++ b/src/WorkspaceContext.ts @@ -18,7 +18,7 @@ import { FolderContext } from "./FolderContext"; import { StatusItem } from "./ui/StatusItem"; import { SwiftOutputChannel } from "./ui/SwiftOutputChannel"; import { swiftLibraryPathKey } from "./utilities/utilities"; -import { pathExists, isPathInsidePath } from "./utilities/filesystem"; +import { isPathInsidePath } from "./utilities/filesystem"; import { LanguageClientManager } from "./sourcekit-lsp/LanguageClientManager"; import { TemporaryFolder } from "./utilities/tempFolder"; import { TaskManager } from "./tasks/TaskManager"; @@ -34,6 +34,7 @@ import { DiagnosticsManager } from "./DiagnosticsManager"; import { DocumentationManager } from "./documentation/DocumentationManager"; import { DocCDocumentationRequest, ReIndexProjectRequest } from "./sourcekit-lsp/extensions"; import { TestKind } from "./TestExplorer/TestKind"; +import { isValidWorkspaceFolder, searchForPackages } from "./utilities/workspace"; /** * Context for whole workspace. Holds array of contexts for each workspace folder @@ -391,38 +392,19 @@ export class WorkspaceContext implements vscode.Disposable { * @param folder folder being added */ async addWorkspaceFolder(workspaceFolder: vscode.WorkspaceFolder) { - await this.searchForPackages(workspaceFolder.uri, workspaceFolder); - - if (this.getActiveWorkspaceFolder(vscode.window.activeTextEditor) === workspaceFolder) { - await this.focusTextEditor(vscode.window.activeTextEditor); - } - } + const folders = await searchForPackages( + workspaceFolder.uri, + configuration.disableSwiftPMIntegration, + configuration.folder(workspaceFolder).searchSubfoldersForPackages + ); - async searchForPackages(folder: vscode.Uri, workspaceFolder: vscode.WorkspaceFolder) { - // add folder if Package.swift/compile_commands.json/compile_flags.txt/buildServer.json exists - if (await this.isValidWorkspaceFolder(folder.fsPath)) { + for (const folder of folders) { await this.addPackageFolder(folder, workspaceFolder); - return; - } - // should I search sub-folders for more Swift Packages - if (!configuration.folder(workspaceFolder).searchSubfoldersForPackages) { - return; } - await vscode.workspace.fs.readDirectory(folder).then(async entries => { - for (const entry of entries) { - if ( - entry[1] === vscode.FileType.Directory && - entry[0][0] !== "." && - entry[0] !== "Packages" - ) { - await this.searchForPackages( - vscode.Uri.joinPath(folder, entry[0]), - workspaceFolder - ); - } - } - }); + if (this.getActiveWorkspaceFolder(vscode.window.activeTextEditor) === workspaceFolder) { + await this.focusTextEditor(vscode.window.activeTextEditor); + } } public async addPackageFolder( @@ -597,13 +579,7 @@ export class WorkspaceContext implements vscode.Disposable { * Package.swift or a CMake compile_commands.json, compile_flags.txt, or a BSP buildServer.json. */ async isValidWorkspaceFolder(folder: string): Promise { - return ( - ((await pathExists(folder, "Package.swift")) && - !configuration.disableSwiftPMIntegration) || - (await pathExists(folder, "compile_commands.json")) || - (await pathExists(folder, "compile_flags.txt")) || - (await pathExists(folder, "buildServer.json")) - ); + return await isValidWorkspaceFolder(folder, configuration.disableSwiftPMIntegration); } /** send unfocus event to current focussed folder and clear current folder */ diff --git a/src/utilities/workspace.ts b/src/utilities/workspace.ts new file mode 100644 index 000000000..cc0a6fa69 --- /dev/null +++ b/src/utilities/workspace.ts @@ -0,0 +1,63 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the VS Code Swift open source project +// +// Copyright (c) 2022 the VS Code Swift project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of VS Code Swift project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import * as vscode from "vscode"; +import { pathExists } from "./filesystem"; + +export async function searchForPackages( + folder: vscode.Uri, + disableSwiftPMIntegration: boolean, + searchSubfoldersForPackages: boolean +): Promise> { + const folders: Array = []; + + async function search(folder: vscode.Uri) { + // add folder if Package.swift/compile_commands.json/compile_flags.txt/buildServer.json exists + if (await isValidWorkspaceFolder(folder.fsPath, disableSwiftPMIntegration)) { + folders.push(folder); + } + // should I search sub-folders for more Swift Packages + if (!searchSubfoldersForPackages) { + return; + } + + await vscode.workspace.fs.readDirectory(folder).then(async entries => { + for (const entry of entries) { + if ( + entry[1] === vscode.FileType.Directory && + entry[0][0] !== "." && + entry[0] !== "Packages" + ) { + await search(vscode.Uri.joinPath(folder, entry[0])); + } + } + }); + } + + await search(folder); + + return folders; +} + +export async function isValidWorkspaceFolder( + folder: string, + disableSwiftPMIntegration: boolean +): Promise { + return ( + (!disableSwiftPMIntegration && (await pathExists(folder, "Package.swift"))) || + (await pathExists(folder, "compile_commands.json")) || + (await pathExists(folder, "compile_flags.txt")) || + (await pathExists(folder, "buildServer.json")) + ); +} diff --git a/test/unit-tests/utilities/workspace.test.ts b/test/unit-tests/utilities/workspace.test.ts new file mode 100644 index 000000000..7ca500fc0 --- /dev/null +++ b/test/unit-tests/utilities/workspace.test.ts @@ -0,0 +1,42 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the VS Code Swift open source project +// +// Copyright (c) 2024 the VS Code Swift project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of VS Code Swift project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import * as vscode from "vscode"; +import { searchForPackages } from "../../../src/utilities/workspace"; +import { testAssetUri } from "../../fixtures"; +import { expect } from "chai"; + +suite("Workspace Utilities Unit Test Suite", () => { + suite("searchForPackages", () => { + const packageFolder = testAssetUri("ModularPackage"); + const firstModuleFolder = vscode.Uri.joinPath(packageFolder, "Module1"); + const secondModuleFolder = vscode.Uri.joinPath(packageFolder, "Module2"); + + test("returns only root package when search for subpackages disabled", async () => { + const folders = await searchForPackages(packageFolder, false, false); + + expect(folders.map(folder => folder.fsPath)).eql([packageFolder.fsPath]); + }); + + test("returns subpackages when search for subpackages enabled", async () => { + const folders = await searchForPackages(packageFolder, false, true); + + expect(folders.map(folder => folder.fsPath).sort()).deep.equal([ + packageFolder.fsPath, + firstModuleFolder.fsPath, + secondModuleFolder.fsPath, + ]); + }); + }); +}); From 263a0e0869905f08ff3f5a3c23a91cd8dd287213 Mon Sep 17 00:00:00 2001 From: Paul LeMarquand Date: Tue, 11 Mar 2025 12:20:22 -0400 Subject: [PATCH 40/86] Configurable Command Plugin Build Configuration (#1409) * Configurable Command Plugin Build Configuration Packages can define their own plugins either directly or through their dependencies. These plugins define commands, and the extension exposes a list of these when you use `> Swift: Run Command Plugin`. It may be desirable to build and run these plugins with specific arguments. This patch introduces a new setting that can be specified globally or on a per workspace folder basis that allows users to configure arguments to pass to plugin command invocations. The setting is defined under `swift.pluginArguments`, and is specified as an object in the following form: ```json { "PluginCommandName:intent-name": ["--some", "--args"] } ``` - The top level string key is the command id in the form `PluginCommandName:intent-name`. For instance, swift-format's format-source-code command would be specified as `swift-format:format-source-code` - Specifying `PluginCommandName` will apply the arguments to all intents in the command plugin - Specifying `*` will apply the arguments to all commands. This patch also adds this wildcard functionality to the `swift.pluginPermissions` setting. Issue: #1365 Co-authored-by: Rishi --- docs/settings.md | 20 +- package.json | 24 +++ src/configuration.ts | 51 ++++- src/tasks/SwiftPluginTaskProvider.ts | 23 ++- .../tasks/SwiftPluginTaskProvider.test.ts | 187 +++++++++++++----- 5 files changed, 247 insertions(+), 58 deletions(-) diff --git a/docs/settings.md b/docs/settings.md index 3f98eeb64..b3e8c8527 100644 --- a/docs/settings.md +++ b/docs/settings.md @@ -23,7 +23,7 @@ A plugin may require permissions to perform tasks like writing to the file syste } ``` -A key of `PluginName:command` will set permissions for a specific command. A key of `PluginName` will set permissions for all commands in the plugin. +A key of `PluginName:command` will set permissions for a specific command. A key of `PluginName` will set permissions for all commands in the plugin. If you'd like the same permissions to be applied to all plugins use `*` as the plugin name. Precedence order is determined by specificity, where more specific names take priority. The name `*` is the least specific and `PluginName:command` is the most specific. Alternatively, you can define a task in your tasks.json and define permissions directly on the task. This will create a new entry in the list shown by `> Swift: Run Command Plugin`. @@ -43,6 +43,24 @@ Alternatively, you can define a task in your tasks.json and define permissions d } ``` +If you'd like to provide specific arguments to your plugin command invocation you can use the `swift.pluginArguments` setting. Defining an array for this setting applies the same arguments to all plugin command invocations. + +```json +{ + "swift.pluginArguments": ["-c", "release"] +} +``` + +Alternatively you can specfiy which specific command the arguments should apply to using `PluginName:command`. A key of `PluginName` will use the arguments for all commands in the plugin. If you'd like the same arguments to be used for all plugins use `*` as the plugin name. + +```json +{ + "swift.pluginArguments": { + "PluginName:command": ["-c", "release"] + } +} +``` + ## SourceKit-LSP [SourceKit-LSP](https://github.com/apple/sourcekit-lsp) is the language server used by the the Swift extension to provide symbol completion, jump to definition etc. It is developed by Apple to provide Swift and C language support for any editor that supports the Language Server Protocol. diff --git a/package.json b/package.json index 99d6dbea1..62b37c9ca 100644 --- a/package.json +++ b/package.json @@ -444,6 +444,30 @@ "default": true, "markdownDescription": "Controls whether or not the extension will contribute environment variables defined in `Swift: Environment Variables` to the integrated terminal. If this is set to `true` and a custom `Swift: Path` is also set then the swift path is appended to the terminal's `PATH`." }, + "swift.pluginArguments": { + "default": [], + "markdownDescription": "Configure a list of arguments to pass to command invocations. This can either be an array of arguments, which will apply to all command invocations, or an object with command names as the key where the value is an array of arguments.", + "scope": "machine-overridable", + "oneOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "object", + "patternProperties": { + "^([a-zA-Z0-9_-]+(:[a-zA-Z0-9_-]+)?)$": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + ] + }, "swift.pluginPermissions": { "type": "object", "default": {}, diff --git a/src/configuration.ts b/src/configuration.ts index 79e95fa0e..5397071b8 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -78,7 +78,9 @@ export interface FolderConfiguration { /** location to save swift-testing attachments */ readonly attachmentsPath: string; /** look up saved permissions for the supplied plugin */ - pluginPermissions(pluginId: string): PluginPermissionConfiguration; + pluginPermissions(pluginId?: string): PluginPermissionConfiguration; + /** look up saved arguments for the supplied plugin, or global plugin arguments if no plugin id is provided */ + pluginArguments(pluginId?: string): string[]; } export interface PluginPermissionConfiguration { @@ -143,6 +145,42 @@ const configuration = { }, folder(workspaceFolder: vscode.WorkspaceFolder): FolderConfiguration { + function pluginSetting( + setting: string, + pluginId?: string, + resultIsArray: boolean = false + ): T | undefined { + if (!pluginId) { + // Check for * as a wildcard plugin ID for configurations that want both + // global arguments as well as specific additional arguments for a plugin. + const wildcardSetting = pluginSetting(setting, "*", resultIsArray) as T | undefined; + if (wildcardSetting) { + return wildcardSetting; + } + + // Check if there is a global setting like `"swift.pluginArguments": ["-c", "release"]` + // that should apply to all plugins. + const args = vscode.workspace + .getConfiguration("swift", workspaceFolder) + .get(setting); + + if (resultIsArray && Array.isArray(args)) { + return args; + } else if ( + !resultIsArray && + args !== null && + typeof args === "object" && + Object.keys(args).length !== 0 + ) { + return args; + } + return undefined; + } + + return vscode.workspace.getConfiguration("swift", workspaceFolder).get<{ + [key: string]: T; + }>(setting, {})[pluginId]; + } return { /** Environment variables to set when running tests */ get testEnvironmentVariables(): { [key: string]: string } { @@ -179,12 +217,11 @@ const configuration = { .getConfiguration("swift", workspaceFolder) .get("attachmentsPath", "./.build/attachments"); }, - pluginPermissions(pluginId: string): PluginPermissionConfiguration { - return ( - vscode.workspace.getConfiguration("swift", workspaceFolder).get<{ - [key: string]: PluginPermissionConfiguration; - }>("pluginPermissions", {})[pluginId] ?? {} - ); + pluginPermissions(pluginId?: string): PluginPermissionConfiguration { + return pluginSetting("pluginPermissions", pluginId, false) ?? {}; + }, + pluginArguments(pluginId?: string): string[] { + return pluginSetting("pluginArguments", pluginId, true) ?? []; }, }; }, diff --git a/src/tasks/SwiftPluginTaskProvider.ts b/src/tasks/SwiftPluginTaskProvider.ts index 1de21d238..7aaca41f4 100644 --- a/src/tasks/SwiftPluginTaskProvider.ts +++ b/src/tasks/SwiftPluginTaskProvider.ts @@ -202,7 +202,7 @@ export class SwiftPluginTaskProvider implements vscode.TaskProvider { * are keyed by either plugin command name (package), or in the form `name:command`. * User-configured permissions take precedence over the hardcoded permissions, and the more * specific form of `name:command` takes precedence over the more general form of `name`. - * @param folderContext The folder context to search for the `swift.pluginPermissions` key. + * @param folderContext The folder context to search for the `swift.pluginPermissions` and `swift.pluginArguments` keys. * @param taskDefinition The task definition to search for the `disableSandbox` and `allowWritingToPackageDirectory` keys. * @param plugin The plugin to generate arguments for. * @returns A list of permission related arguments to pass when invoking the plugin. @@ -213,9 +213,14 @@ export class SwiftPluginTaskProvider implements vscode.TaskProvider { plugin: PackagePlugin ): string[] { const config = configuration.folder(folderContext); + const globalPackageConfig = config.pluginPermissions(); const packageConfig = config.pluginPermissions(plugin.package); const commandConfig = config.pluginPermissions(`${plugin.package}:${plugin.command}`); + const globalPackageArgs = config.pluginArguments(); + const packageArgs = config.pluginArguments(plugin.package); + const commandArgs = config.pluginArguments(`${plugin.package}:${plugin.command}`); + const taskDefinitionConfiguration: PluginPermissionConfiguration = {}; if (taskDefinition.disableSandbox) { taskDefinitionConfiguration.disableSandbox = true; @@ -232,11 +237,17 @@ export class SwiftPluginTaskProvider implements vscode.TaskProvider { taskDefinition.allowNetworkConnections; } - return this.pluginArguments({ - ...packageConfig, - ...commandConfig, - ...taskDefinitionConfiguration, - }); + return [ + ...globalPackageArgs, + ...packageArgs, + ...commandArgs, + ...this.pluginArguments({ + ...globalPackageConfig, + ...packageConfig, + ...commandConfig, + ...taskDefinitionConfiguration, + }), + ]; } private pluginArguments(config: PluginPermissionConfiguration): string[] { diff --git a/test/integration-tests/tasks/SwiftPluginTaskProvider.test.ts b/test/integration-tests/tasks/SwiftPluginTaskProvider.test.ts index 85b17112a..bf3715c49 100644 --- a/test/integration-tests/tasks/SwiftPluginTaskProvider.test.ts +++ b/test/integration-tests/tasks/SwiftPluginTaskProvider.test.ts @@ -14,6 +14,7 @@ import * as vscode from "vscode"; import * as assert from "assert"; +import { beforeEach, afterEach } from "mocha"; import { expect } from "chai"; import { WorkspaceContext } from "../../../src/WorkspaceContext"; import { SwiftPluginTaskProvider } from "../../../src/tasks/SwiftPluginTaskProvider"; @@ -38,57 +39,155 @@ suite("SwiftPluginTaskProvider Test Suite", function () { this.timeout(60000); // Mostly only when running suite with .only - suite("settings plugin arguments", () => { - activateExtensionForSuite({ - async setup(ctx) { - workspaceContext = ctx; - folderContext = await folderInRootWorkspace("command-plugin", workspaceContext); - await folderContext.loadSwiftPlugins(); - expect(workspaceContext.folders).to.not.have.lengthOf(0); - return await updateSettings({ - "swift.pluginPermissions": { - "command-plugin:command_plugin": { - disableSandbox: true, - allowWritingToPackageDirectory: true, - allowWritingToDirectory: ["/foo", "/bar"], - allowNetworkConnections: "all", - }, + activateExtensionForSuite({ + async setup(ctx) { + workspaceContext = ctx; + folderContext = await folderInRootWorkspace("command-plugin", workspaceContext); + await folderContext.loadSwiftPlugins(); + expect(workspaceContext.folders).to.not.have.lengthOf(0); + }, + }); + + const expectedPluginPermissions = [ + "--disable-sandbox", + "--allow-writing-to-package-directory", + "--allow-writing-to-directory", + "/foo", + "/bar", + "--allow-network-connections", + "all", + ]; + + [ + { + name: "global plugin permissions", + settings: { + "swift.pluginPermissions": { + disableSandbox: true, + allowWritingToPackageDirectory: true, + allowWritingToDirectory: ["/foo", "/bar"], + allowNetworkConnections: "all", + }, + }, + expected: expectedPluginPermissions, + }, + { + name: "plugin scoped plugin permissions", + settings: { + "swift.pluginPermissions": { + "command-plugin": { + disableSandbox: true, + allowWritingToPackageDirectory: true, + allowWritingToDirectory: ["/foo", "/bar"], + allowNetworkConnections: "all", }, - }); + }, }, - }); + expected: expectedPluginPermissions, + }, + { + name: "command scoped plugin permissions", + settings: { + "swift.pluginPermissions": { + "command-plugin:command_plugin": { + disableSandbox: true, + allowWritingToPackageDirectory: true, + allowWritingToDirectory: ["/foo", "/bar"], + allowNetworkConnections: "all", + }, + }, + }, + expected: expectedPluginPermissions, + }, + { + name: "wildcard scoped plugin permissions", + settings: { + "swift.pluginPermissions": { + "*": { + disableSandbox: true, + allowWritingToPackageDirectory: true, + allowWritingToDirectory: ["/foo", "/bar"], + allowNetworkConnections: "all", + }, + }, + }, + expected: expectedPluginPermissions, + }, + { + name: "global plugin arguments", + settings: { + "swift.pluginArguments": ["-c", "release"], + }, + expected: ["-c", "release"], + }, + { + name: "plugin scoped plugin arguments", + settings: { + "swift.pluginArguments": { + "command-plugin": ["-c", "release"], + }, + }, + expected: ["-c", "release"], + }, + { + name: "command scoped plugin arguments", + settings: { + "swift.pluginArguments": { + "command-plugin:command_plugin": ["-c", "release"], + }, + }, + expected: ["-c", "release"], + }, + { + name: "wildcard scoped plugin arguments", + settings: { + "swift.pluginArguments": { + "*": ["-c", "release"], + }, + }, + expected: ["-c", "release"], + }, + { + name: "overlays settings", + settings: { + "swift.pluginArguments": { + "*": ["-a"], + "command-plugin": ["-b"], + "command-plugin:command_plugin": ["-c"], + }, + }, + expected: ["-a", "-b", "-c"], + }, + ].forEach(({ name, settings, expected }) => { + suite(name, () => { + let resetSettings: (() => Promise) | undefined; + beforeEach(async function () { + resetSettings = await updateSettings(settings); + }); + + afterEach(async () => { + if (resetSettings) { + await resetSettings(); + } + }); - test("provides a task with permissions set via settings", async () => { - const tasks = await vscode.tasks.fetchTasks({ type: "swift-plugin" }); - const task = tasks.find(t => t.name === "command-plugin"); - const swiftExecution = task?.execution as SwiftExecution; - assert.deepEqual( - swiftExecution.args, - workspaceContext.toolchain.buildFlags.withAdditionalFlags([ - "package", - "--disable-sandbox", - "--allow-writing-to-package-directory", - "--allow-writing-to-directory", - "/foo", - "/bar", - "--allow-network-connections", - "all", - "command_plugin", - ]) - ); + test("sets arguments", async () => { + const tasks = await vscode.tasks.fetchTasks({ type: "swift-plugin" }); + const task = tasks.find(t => t.name === "command-plugin"); + const swiftExecution = task?.execution as SwiftExecution; + assert.deepEqual( + swiftExecution.args, + workspaceContext.toolchain.buildFlags.withAdditionalFlags([ + "package", + ...expected, + "command_plugin", + ]) + ); + }); }); }); suite("execution", () => { - activateExtensionForSuite({ - async setup(ctx) { - workspaceContext = ctx; - folderContext = await folderInRootWorkspace("command-plugin", workspaceContext); - await folderContext.loadSwiftPlugins(); - expect(workspaceContext.folders).to.not.have.lengthOf(0); - }, - }); - suite("createSwiftPluginTask", () => { let taskProvider: SwiftPluginTaskProvider; From fabf4b38db54830f7bdc2b44b6cdadb489eb1bbc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 11 Mar 2025 12:21:10 -0400 Subject: [PATCH 41/86] Bump the all-dependencies group with 6 updates (#1426) Bumps the all-dependencies group with 6 updates: | Package | From | To | | --- | --- | --- | | [@types/chai-subset](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/chai-subset) | `1.3.5` | `1.3.6` | | [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `18.19.79` | `18.19.80` | | [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) | `8.26.0` | `8.26.1` | | [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) | `8.26.0` | `8.26.1` | | [esbuild](https://github.com/evanw/esbuild) | `0.25.0` | `0.25.1` | | [eslint-config-prettier](https://github.com/prettier/eslint-config-prettier) | `10.0.2` | `10.1.1` | Updates `@types/chai-subset` from 1.3.5 to 1.3.6 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/chai-subset) Updates `@types/node` from 18.19.79 to 18.19.80 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) Updates `@typescript-eslint/eslint-plugin` from 8.26.0 to 8.26.1 - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.26.1/packages/eslint-plugin) Updates `@typescript-eslint/parser` from 8.26.0 to 8.26.1 - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.26.1/packages/parser) Updates `esbuild` from 0.25.0 to 0.25.1 - [Release notes](https://github.com/evanw/esbuild/releases) - [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG.md) - [Commits](https://github.com/evanw/esbuild/compare/v0.25.0...v0.25.1) Updates `eslint-config-prettier` from 10.0.2 to 10.1.1 - [Release notes](https://github.com/prettier/eslint-config-prettier/releases) - [Changelog](https://github.com/prettier/eslint-config-prettier/blob/main/CHANGELOG.md) - [Commits](https://github.com/prettier/eslint-config-prettier/compare/v10.0.2...v10.1.1) --- updated-dependencies: - dependency-name: "@types/chai-subset" dependency-type: direct:development update-type: version-update:semver-patch dependency-group: all-dependencies - dependency-name: "@types/node" dependency-type: direct:development update-type: version-update:semver-patch dependency-group: all-dependencies - dependency-name: "@typescript-eslint/eslint-plugin" dependency-type: direct:development update-type: version-update:semver-patch dependency-group: all-dependencies - dependency-name: "@typescript-eslint/parser" dependency-type: direct:development update-type: version-update:semver-patch dependency-group: all-dependencies - dependency-name: esbuild dependency-type: direct:development update-type: version-update:semver-patch dependency-group: all-dependencies - dependency-name: eslint-config-prettier dependency-type: direct:development update-type: version-update:semver-minor dependency-group: all-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 642 +++++++++++++++++++++++----------------------- package.json | 12 +- 2 files changed, 326 insertions(+), 328 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8ffe603d8..dd60b2bb7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,21 +18,21 @@ "devDependencies": { "@types/chai": "^4.3.19", "@types/chai-as-promised": "^7.1.8", - "@types/chai-subset": "^1.3.5", + "@types/chai-subset": "^1.3.6", "@types/glob": "^7.1.6", "@types/lcov-parse": "^1.0.2", "@types/lodash.throttle": "^4.1.9", "@types/mocha": "^10.0.10", "@types/mock-fs": "^4.13.4", - "@types/node": "^18.19.79", + "@types/node": "^18.19.80", "@types/plist": "^3.0.5", "@types/semver": "^7.5.8", "@types/sinon": "^17.0.4", "@types/sinon-chai": "^3.2.12", "@types/vscode": "^1.88.0", "@types/xml2js": "^0.4.14", - "@typescript-eslint/eslint-plugin": "^8.26.0", - "@typescript-eslint/parser": "^8.26.0", + "@typescript-eslint/eslint-plugin": "^8.26.1", + "@typescript-eslint/parser": "^8.26.1", "@vscode/debugprotocol": "^1.68.0", "@vscode/test-cli": "^0.0.10", "@vscode/test-electron": "^2.4.1", @@ -41,9 +41,9 @@ "chai-as-promised": "^7.1.2", "chai-subset": "^1.6.0", "del-cli": "^6.0.0", - "esbuild": "^0.25.0", + "esbuild": "^0.25.1", "eslint": "^8.57.0", - "eslint-config-prettier": "^10.0.2", + "eslint-config-prettier": "^10.1.1", "lodash.throttle": "^4.1.1", "mocha": "^10.8.2", "mock-fs": "^5.5.0", @@ -295,9 +295,9 @@ "dev": true }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.0.tgz", - "integrity": "sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.1.tgz", + "integrity": "sha512-kfYGy8IdzTGy+z0vFGvExZtxkFlA4zAxgKEahG9KE1ScBjpQnFsNOX8KTU5ojNru5ed5CVoJYXFtoxaq5nFbjQ==", "cpu": [ "ppc64" ], @@ -312,9 +312,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.0.tgz", - "integrity": "sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.1.tgz", + "integrity": "sha512-dp+MshLYux6j/JjdqVLnMglQlFu+MuVeNrmT5nk6q07wNhCdSnB7QZj+7G8VMUGh1q+vj2Bq8kRsuyA00I/k+Q==", "cpu": [ "arm" ], @@ -329,9 +329,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.0.tgz", - "integrity": "sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.1.tgz", + "integrity": "sha512-50tM0zCJW5kGqgG7fQ7IHvQOcAn9TKiVRuQ/lN0xR+T2lzEFvAi1ZcS8DiksFcEpf1t/GYOeOfCAgDHFpkiSmA==", "cpu": [ "arm64" ], @@ -346,9 +346,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.0.tgz", - "integrity": "sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.1.tgz", + "integrity": "sha512-GCj6WfUtNldqUzYkN/ITtlhwQqGWu9S45vUXs7EIYf+7rCiiqH9bCloatO9VhxsL0Pji+PF4Lz2XXCES+Q8hDw==", "cpu": [ "x64" ], @@ -363,9 +363,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.0.tgz", - "integrity": "sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.1.tgz", + "integrity": "sha512-5hEZKPf+nQjYoSr/elb62U19/l1mZDdqidGfmFutVUjjUZrOazAtwK+Kr+3y0C/oeJfLlxo9fXb1w7L+P7E4FQ==", "cpu": [ "arm64" ], @@ -380,9 +380,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.0.tgz", - "integrity": "sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.1.tgz", + "integrity": "sha512-hxVnwL2Dqs3fM1IWq8Iezh0cX7ZGdVhbTfnOy5uURtao5OIVCEyj9xIzemDi7sRvKsuSdtCAhMKarxqtlyVyfA==", "cpu": [ "x64" ], @@ -397,9 +397,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.0.tgz", - "integrity": "sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.1.tgz", + "integrity": "sha512-1MrCZs0fZa2g8E+FUo2ipw6jw5qqQiH+tERoS5fAfKnRx6NXH31tXBKI3VpmLijLH6yriMZsxJtaXUyFt/8Y4A==", "cpu": [ "arm64" ], @@ -414,9 +414,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.0.tgz", - "integrity": "sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.1.tgz", + "integrity": "sha512-0IZWLiTyz7nm0xuIs0q1Y3QWJC52R8aSXxe40VUxm6BB1RNmkODtW6LHvWRrGiICulcX7ZvyH6h5fqdLu4gkww==", "cpu": [ "x64" ], @@ -431,9 +431,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.0.tgz", - "integrity": "sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.1.tgz", + "integrity": "sha512-NdKOhS4u7JhDKw9G3cY6sWqFcnLITn6SqivVArbzIaf3cemShqfLGHYMx8Xlm/lBit3/5d7kXvriTUGa5YViuQ==", "cpu": [ "arm" ], @@ -448,9 +448,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.0.tgz", - "integrity": "sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.1.tgz", + "integrity": "sha512-jaN3dHi0/DDPelk0nLcXRm1q7DNJpjXy7yWaWvbfkPvI+7XNSc/lDOnCLN7gzsyzgu6qSAmgSvP9oXAhP973uQ==", "cpu": [ "arm64" ], @@ -465,9 +465,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.0.tgz", - "integrity": "sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.1.tgz", + "integrity": "sha512-OJykPaF4v8JidKNGz8c/q1lBO44sQNUQtq1KktJXdBLn1hPod5rE/Hko5ugKKZd+D2+o1a9MFGUEIUwO2YfgkQ==", "cpu": [ "ia32" ], @@ -482,9 +482,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.0.tgz", - "integrity": "sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.1.tgz", + "integrity": "sha512-nGfornQj4dzcq5Vp835oM/o21UMlXzn79KobKlcs3Wz9smwiifknLy4xDCLUU0BWp7b/houtdrgUz7nOGnfIYg==", "cpu": [ "loong64" ], @@ -499,9 +499,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.0.tgz", - "integrity": "sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.1.tgz", + "integrity": "sha512-1osBbPEFYwIE5IVB/0g2X6i1qInZa1aIoj1TdL4AaAb55xIIgbg8Doq6a5BzYWgr+tEcDzYH67XVnTmUzL+nXg==", "cpu": [ "mips64el" ], @@ -516,9 +516,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.0.tgz", - "integrity": "sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.1.tgz", + "integrity": "sha512-/6VBJOwUf3TdTvJZ82qF3tbLuWsscd7/1w+D9LH0W/SqUgM5/JJD0lrJ1fVIfZsqB6RFmLCe0Xz3fmZc3WtyVg==", "cpu": [ "ppc64" ], @@ -533,9 +533,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.0.tgz", - "integrity": "sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.1.tgz", + "integrity": "sha512-nSut/Mx5gnilhcq2yIMLMe3Wl4FK5wx/o0QuuCLMtmJn+WeWYoEGDN1ipcN72g1WHsnIbxGXd4i/MF0gTcuAjQ==", "cpu": [ "riscv64" ], @@ -550,9 +550,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.0.tgz", - "integrity": "sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.1.tgz", + "integrity": "sha512-cEECeLlJNfT8kZHqLarDBQso9a27o2Zd2AQ8USAEoGtejOrCYHNtKP8XQhMDJMtthdF4GBmjR2au3x1udADQQQ==", "cpu": [ "s390x" ], @@ -567,9 +567,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.0.tgz", - "integrity": "sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.1.tgz", + "integrity": "sha512-xbfUhu/gnvSEg+EGovRc+kjBAkrvtk38RlerAzQxvMzlB4fXpCFCeUAYzJvrnhFtdeyVCDANSjJvOvGYoeKzFA==", "cpu": [ "x64" ], @@ -584,9 +584,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.0.tgz", - "integrity": "sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.1.tgz", + "integrity": "sha512-O96poM2XGhLtpTh+s4+nP7YCCAfb4tJNRVZHfIE7dgmax+yMP2WgMd2OecBuaATHKTHsLWHQeuaxMRnCsH8+5g==", "cpu": [ "arm64" ], @@ -601,9 +601,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.0.tgz", - "integrity": "sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.1.tgz", + "integrity": "sha512-X53z6uXip6KFXBQ+Krbx25XHV/NCbzryM6ehOAeAil7X7oa4XIq+394PWGnwaSQ2WRA0KI6PUO6hTO5zeF5ijA==", "cpu": [ "x64" ], @@ -618,9 +618,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.0.tgz", - "integrity": "sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.1.tgz", + "integrity": "sha512-Na9T3szbXezdzM/Kfs3GcRQNjHzM6GzFBeU1/6IV/npKP5ORtp9zbQjvkDJ47s6BCgaAZnnnu/cY1x342+MvZg==", "cpu": [ "arm64" ], @@ -635,9 +635,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.0.tgz", - "integrity": "sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.1.tgz", + "integrity": "sha512-T3H78X2h1tszfRSf+txbt5aOp/e7TAz3ptVKu9Oyir3IAOFPGV6O9c2naym5TOriy1l0nNf6a4X5UXRZSGX/dw==", "cpu": [ "x64" ], @@ -652,9 +652,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.0.tgz", - "integrity": "sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.1.tgz", + "integrity": "sha512-2H3RUvcmULO7dIE5EWJH8eubZAI4xw54H1ilJnRNZdeo8dTADEZ21w6J22XBkXqGJbe0+wnNJtw3UXRoLJnFEg==", "cpu": [ "x64" ], @@ -669,9 +669,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.0.tgz", - "integrity": "sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.1.tgz", + "integrity": "sha512-GE7XvrdOzrb+yVKB9KsRMq+7a2U/K5Cf/8grVFRAGJmfADr/e/ODQ134RK2/eeHqYV5eQRFxb1hY7Nr15fv1NQ==", "cpu": [ "arm64" ], @@ -686,9 +686,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.0.tgz", - "integrity": "sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.1.tgz", + "integrity": "sha512-uOxSJCIcavSiT6UnBhBzE8wy3n0hOkJsBOzy7HDAuTDE++1DJMRRVCPGisULScHL+a/ZwdXPpXD3IyFKjA7K8A==", "cpu": [ "ia32" ], @@ -703,9 +703,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.0.tgz", - "integrity": "sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.1.tgz", + "integrity": "sha512-Y1EQdcfwMSeQN/ujR5VayLOJ1BHaK+ssyk0AEzPjC+t1lITgsnccPqFjb6V+LsTp/9Iov4ysfjxLaGJ9RPtkVg==", "cpu": [ "x64" ], @@ -1077,13 +1077,13 @@ } }, "node_modules/@types/chai-subset": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@types/chai-subset/-/chai-subset-1.3.5.tgz", - "integrity": "sha512-c2mPnw+xHtXDoHmdtcCXGwyLMiauiAyxWMzhGpqHC4nqI/Y5G2XhTampslK2rb59kpcuHon03UH8W6iYUzw88A==", + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/@types/chai-subset/-/chai-subset-1.3.6.tgz", + "integrity": "sha512-m8lERkkQj+uek18hXOZuec3W/fCRTrU4hrnXjH3qhHy96ytuPaPiWGgu7sJb7tZxZonO75vYAjCvpe/e4VUwRw==", "dev": true, "license": "MIT", - "dependencies": { - "@types/chai": "*" + "peerDependencies": { + "@types/chai": "<5.2.0" } }, "node_modules/@types/glob": { @@ -1148,9 +1148,9 @@ } }, "node_modules/@types/node": { - "version": "18.19.79", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.79.tgz", - "integrity": "sha512-90K8Oayimbctc5zTPHPfZloc/lGVs7f3phUAAMcTgEPtg8kKquGZDERC8K4vkBYkQQh48msiYUslYtxTWvqcAg==", + "version": "18.19.80", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.80.tgz", + "integrity": "sha512-kEWeMwMeIvxYkeg1gTc01awpwLbfMRZXdIhwRcakd/KlK53jmRC26LqcbIt7fnAQTu5GzlnWmzA3H6+l1u6xxQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1217,17 +1217,17 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.26.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.26.0.tgz", - "integrity": "sha512-cLr1J6pe56zjKYajK6SSSre6nl1Gj6xDp1TY0trpgPzjVbgDwd09v2Ws37LABxzkicmUjhEeg/fAUjPJJB1v5Q==", + "version": "8.26.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.26.1.tgz", + "integrity": "sha512-2X3mwqsj9Bd3Ciz508ZUtoQQYpOhU/kWoUqIf49H8Z0+Vbh6UF/y0OEYp0Q0axOGzaBGs7QxRwq0knSQ8khQNA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.26.0", - "@typescript-eslint/type-utils": "8.26.0", - "@typescript-eslint/utils": "8.26.0", - "@typescript-eslint/visitor-keys": "8.26.0", + "@typescript-eslint/scope-manager": "8.26.1", + "@typescript-eslint/type-utils": "8.26.1", + "@typescript-eslint/utils": "8.26.1", + "@typescript-eslint/visitor-keys": "8.26.1", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -1247,16 +1247,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.26.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.26.0.tgz", - "integrity": "sha512-mNtXP9LTVBy14ZF3o7JG69gRPBK/2QWtQd0j0oH26HcY/foyJJau6pNUez7QrM5UHnSvwlQcJXKsk0I99B9pOA==", + "version": "8.26.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.26.1.tgz", + "integrity": "sha512-w6HZUV4NWxqd8BdeFf81t07d7/YV9s7TCWrQQbG5uhuvGUAW+fq1usZ1Hmz9UPNLniFnD8GLSsDpjP0hm1S4lQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.26.0", - "@typescript-eslint/types": "8.26.0", - "@typescript-eslint/typescript-estree": "8.26.0", - "@typescript-eslint/visitor-keys": "8.26.0", + "@typescript-eslint/scope-manager": "8.26.1", + "@typescript-eslint/types": "8.26.1", + "@typescript-eslint/typescript-estree": "8.26.1", + "@typescript-eslint/visitor-keys": "8.26.1", "debug": "^4.3.4" }, "engines": { @@ -1272,14 +1272,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.26.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.26.0.tgz", - "integrity": "sha512-E0ntLvsfPqnPwng8b8y4OGuzh/iIOm2z8U3S9zic2TeMLW61u5IH2Q1wu0oSTkfrSzwbDJIB/Lm8O3//8BWMPA==", + "version": "8.26.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.26.1.tgz", + "integrity": "sha512-6EIvbE5cNER8sqBu6V7+KeMZIC1664d2Yjt+B9EWUXrsyWpxx4lEZrmvxgSKRC6gX+efDL/UY9OpPZ267io3mg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.26.0", - "@typescript-eslint/visitor-keys": "8.26.0" + "@typescript-eslint/types": "8.26.1", + "@typescript-eslint/visitor-keys": "8.26.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1290,14 +1290,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.26.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.26.0.tgz", - "integrity": "sha512-ruk0RNChLKz3zKGn2LwXuVoeBcUMh+jaqzN461uMMdxy5H9epZqIBtYj7UiPXRuOpaALXGbmRuZQhmwHhaS04Q==", + "version": "8.26.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.26.1.tgz", + "integrity": "sha512-Kcj/TagJLwoY/5w9JGEFV0dclQdyqw9+VMndxOJKtoFSjfZhLXhYjzsQEeyza03rwHx2vFEGvrJWJBXKleRvZg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.26.0", - "@typescript-eslint/utils": "8.26.0", + "@typescript-eslint/typescript-estree": "8.26.1", + "@typescript-eslint/utils": "8.26.1", "debug": "^4.3.4", "ts-api-utils": "^2.0.1" }, @@ -1314,9 +1314,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.26.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.26.0.tgz", - "integrity": "sha512-89B1eP3tnpr9A8L6PZlSjBvnJhWXtYfZhECqlBl1D9Lme9mHO6iWlsprBtVenQvY1HMhax1mWOjhtL3fh/u+pA==", + "version": "8.26.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.26.1.tgz", + "integrity": "sha512-n4THUQW27VmQMx+3P+B0Yptl7ydfceUj4ON/AQILAASwgYdZ/2dhfymRMh5egRUrvK5lSmaOm77Ry+lmXPOgBQ==", "dev": true, "license": "MIT", "engines": { @@ -1328,14 +1328,14 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.26.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.26.0.tgz", - "integrity": "sha512-tiJ1Hvy/V/oMVRTbEOIeemA2XoylimlDQ03CgPPNaHYZbpsc78Hmngnt+WXZfJX1pjQ711V7g0H7cSJThGYfPQ==", + "version": "8.26.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.26.1.tgz", + "integrity": "sha512-yUwPpUHDgdrv1QJ7YQal3cMVBGWfnuCdKbXw1yyjArax3353rEJP1ZA+4F8nOlQ3RfS2hUN/wze3nlY+ZOhvoA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.26.0", - "@typescript-eslint/visitor-keys": "8.26.0", + "@typescript-eslint/types": "8.26.1", + "@typescript-eslint/visitor-keys": "8.26.1", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -1381,16 +1381,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.26.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.26.0.tgz", - "integrity": "sha512-2L2tU3FVwhvU14LndnQCA2frYC8JnPDVKyQtWFPf8IYFMt/ykEN1bPolNhNbCVgOmdzTlWdusCTKA/9nKrf8Ig==", + "version": "8.26.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.26.1.tgz", + "integrity": "sha512-V4Urxa/XtSUroUrnI7q6yUTD3hDtfJ2jzVfeT3VK0ciizfK2q/zGC0iDh1lFMUZR8cImRrep6/q0xd/1ZGPQpg==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.26.0", - "@typescript-eslint/types": "8.26.0", - "@typescript-eslint/typescript-estree": "8.26.0" + "@typescript-eslint/scope-manager": "8.26.1", + "@typescript-eslint/types": "8.26.1", + "@typescript-eslint/typescript-estree": "8.26.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1405,13 +1405,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.26.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.26.0.tgz", - "integrity": "sha512-2z8JQJWAzPdDd51dRQ/oqIJxe99/hoLIqmf8RMCAJQtYDc535W/Jt2+RTP4bP0aKeBG1F65yjIZuczOXCmbWwg==", + "version": "8.26.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.26.1.tgz", + "integrity": "sha512-AjOC3zfnxd6S4Eiy3jwktJPclqhFHNyd8L6Gycf9WUPoKZpgM5PjkxY1X7uSy61xVpiJDhhk7XT2NVsN3ALTWg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.26.0", + "@typescript-eslint/types": "8.26.1", "eslint-visitor-keys": "^4.2.0" }, "engines": { @@ -2872,9 +2872,9 @@ } }, "node_modules/esbuild": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.0.tgz", - "integrity": "sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.1.tgz", + "integrity": "sha512-BGO5LtrGC7vxnqucAe/rmvKdJllfGaYWdyABvyMoXQlfYMb2bbRuReWR5tEGE//4LcNJj9XrkovTqNYRFZHAMQ==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -2885,31 +2885,31 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.0", - "@esbuild/android-arm": "0.25.0", - "@esbuild/android-arm64": "0.25.0", - "@esbuild/android-x64": "0.25.0", - "@esbuild/darwin-arm64": "0.25.0", - "@esbuild/darwin-x64": "0.25.0", - "@esbuild/freebsd-arm64": "0.25.0", - "@esbuild/freebsd-x64": "0.25.0", - "@esbuild/linux-arm": "0.25.0", - "@esbuild/linux-arm64": "0.25.0", - "@esbuild/linux-ia32": "0.25.0", - "@esbuild/linux-loong64": "0.25.0", - "@esbuild/linux-mips64el": "0.25.0", - "@esbuild/linux-ppc64": "0.25.0", - "@esbuild/linux-riscv64": "0.25.0", - "@esbuild/linux-s390x": "0.25.0", - "@esbuild/linux-x64": "0.25.0", - "@esbuild/netbsd-arm64": "0.25.0", - "@esbuild/netbsd-x64": "0.25.0", - "@esbuild/openbsd-arm64": "0.25.0", - "@esbuild/openbsd-x64": "0.25.0", - "@esbuild/sunos-x64": "0.25.0", - "@esbuild/win32-arm64": "0.25.0", - "@esbuild/win32-ia32": "0.25.0", - "@esbuild/win32-x64": "0.25.0" + "@esbuild/aix-ppc64": "0.25.1", + "@esbuild/android-arm": "0.25.1", + "@esbuild/android-arm64": "0.25.1", + "@esbuild/android-x64": "0.25.1", + "@esbuild/darwin-arm64": "0.25.1", + "@esbuild/darwin-x64": "0.25.1", + "@esbuild/freebsd-arm64": "0.25.1", + "@esbuild/freebsd-x64": "0.25.1", + "@esbuild/linux-arm": "0.25.1", + "@esbuild/linux-arm64": "0.25.1", + "@esbuild/linux-ia32": "0.25.1", + "@esbuild/linux-loong64": "0.25.1", + "@esbuild/linux-mips64el": "0.25.1", + "@esbuild/linux-ppc64": "0.25.1", + "@esbuild/linux-riscv64": "0.25.1", + "@esbuild/linux-s390x": "0.25.1", + "@esbuild/linux-x64": "0.25.1", + "@esbuild/netbsd-arm64": "0.25.1", + "@esbuild/netbsd-x64": "0.25.1", + "@esbuild/openbsd-arm64": "0.25.1", + "@esbuild/openbsd-x64": "0.25.1", + "@esbuild/sunos-x64": "0.25.1", + "@esbuild/win32-arm64": "0.25.1", + "@esbuild/win32-ia32": "0.25.1", + "@esbuild/win32-x64": "0.25.1" } }, "node_modules/escalade": { @@ -2989,13 +2989,13 @@ } }, "node_modules/eslint-config-prettier": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.0.2.tgz", - "integrity": "sha512-1105/17ZIMjmCOJOPNfVdbXafLCLj3hPmkmB7dLgt7XsQ/zkxSuDerE/xgO3RxoHysR1N1whmquY0lSn2O0VLg==", + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.1.tgz", + "integrity": "sha512-4EQQr6wXwS+ZJSzaR5ZCrYgLxqvUjdXctaEtBqHcbkW944B1NQyO4qpdHQbXBONfwxXdkAY81HH4+LUfrg+zPw==", "dev": true, "license": "MIT", "bin": { - "eslint-config-prettier": "build/bin/cli.js" + "eslint-config-prettier": "bin/cli.js" }, "peerDependencies": { "eslint": ">=7.0.0" @@ -6334,177 +6334,177 @@ "dev": true }, "@esbuild/aix-ppc64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.0.tgz", - "integrity": "sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.1.tgz", + "integrity": "sha512-kfYGy8IdzTGy+z0vFGvExZtxkFlA4zAxgKEahG9KE1ScBjpQnFsNOX8KTU5ojNru5ed5CVoJYXFtoxaq5nFbjQ==", "dev": true, "optional": true }, "@esbuild/android-arm": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.0.tgz", - "integrity": "sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.1.tgz", + "integrity": "sha512-dp+MshLYux6j/JjdqVLnMglQlFu+MuVeNrmT5nk6q07wNhCdSnB7QZj+7G8VMUGh1q+vj2Bq8kRsuyA00I/k+Q==", "dev": true, "optional": true }, "@esbuild/android-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.0.tgz", - "integrity": "sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.1.tgz", + "integrity": "sha512-50tM0zCJW5kGqgG7fQ7IHvQOcAn9TKiVRuQ/lN0xR+T2lzEFvAi1ZcS8DiksFcEpf1t/GYOeOfCAgDHFpkiSmA==", "dev": true, "optional": true }, "@esbuild/android-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.0.tgz", - "integrity": "sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.1.tgz", + "integrity": "sha512-GCj6WfUtNldqUzYkN/ITtlhwQqGWu9S45vUXs7EIYf+7rCiiqH9bCloatO9VhxsL0Pji+PF4Lz2XXCES+Q8hDw==", "dev": true, "optional": true }, "@esbuild/darwin-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.0.tgz", - "integrity": "sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.1.tgz", + "integrity": "sha512-5hEZKPf+nQjYoSr/elb62U19/l1mZDdqidGfmFutVUjjUZrOazAtwK+Kr+3y0C/oeJfLlxo9fXb1w7L+P7E4FQ==", "dev": true, "optional": true }, "@esbuild/darwin-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.0.tgz", - "integrity": "sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.1.tgz", + "integrity": "sha512-hxVnwL2Dqs3fM1IWq8Iezh0cX7ZGdVhbTfnOy5uURtao5OIVCEyj9xIzemDi7sRvKsuSdtCAhMKarxqtlyVyfA==", "dev": true, "optional": true }, "@esbuild/freebsd-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.0.tgz", - "integrity": "sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.1.tgz", + "integrity": "sha512-1MrCZs0fZa2g8E+FUo2ipw6jw5qqQiH+tERoS5fAfKnRx6NXH31tXBKI3VpmLijLH6yriMZsxJtaXUyFt/8Y4A==", "dev": true, "optional": true }, "@esbuild/freebsd-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.0.tgz", - "integrity": "sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.1.tgz", + "integrity": "sha512-0IZWLiTyz7nm0xuIs0q1Y3QWJC52R8aSXxe40VUxm6BB1RNmkODtW6LHvWRrGiICulcX7ZvyH6h5fqdLu4gkww==", "dev": true, "optional": true }, "@esbuild/linux-arm": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.0.tgz", - "integrity": "sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.1.tgz", + "integrity": "sha512-NdKOhS4u7JhDKw9G3cY6sWqFcnLITn6SqivVArbzIaf3cemShqfLGHYMx8Xlm/lBit3/5d7kXvriTUGa5YViuQ==", "dev": true, "optional": true }, "@esbuild/linux-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.0.tgz", - "integrity": "sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.1.tgz", + "integrity": "sha512-jaN3dHi0/DDPelk0nLcXRm1q7DNJpjXy7yWaWvbfkPvI+7XNSc/lDOnCLN7gzsyzgu6qSAmgSvP9oXAhP973uQ==", "dev": true, "optional": true }, "@esbuild/linux-ia32": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.0.tgz", - "integrity": "sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.1.tgz", + "integrity": "sha512-OJykPaF4v8JidKNGz8c/q1lBO44sQNUQtq1KktJXdBLn1hPod5rE/Hko5ugKKZd+D2+o1a9MFGUEIUwO2YfgkQ==", "dev": true, "optional": true }, "@esbuild/linux-loong64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.0.tgz", - "integrity": "sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.1.tgz", + "integrity": "sha512-nGfornQj4dzcq5Vp835oM/o21UMlXzn79KobKlcs3Wz9smwiifknLy4xDCLUU0BWp7b/houtdrgUz7nOGnfIYg==", "dev": true, "optional": true }, "@esbuild/linux-mips64el": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.0.tgz", - "integrity": "sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.1.tgz", + "integrity": "sha512-1osBbPEFYwIE5IVB/0g2X6i1qInZa1aIoj1TdL4AaAb55xIIgbg8Doq6a5BzYWgr+tEcDzYH67XVnTmUzL+nXg==", "dev": true, "optional": true }, "@esbuild/linux-ppc64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.0.tgz", - "integrity": "sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.1.tgz", + "integrity": "sha512-/6VBJOwUf3TdTvJZ82qF3tbLuWsscd7/1w+D9LH0W/SqUgM5/JJD0lrJ1fVIfZsqB6RFmLCe0Xz3fmZc3WtyVg==", "dev": true, "optional": true }, "@esbuild/linux-riscv64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.0.tgz", - "integrity": "sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.1.tgz", + "integrity": "sha512-nSut/Mx5gnilhcq2yIMLMe3Wl4FK5wx/o0QuuCLMtmJn+WeWYoEGDN1ipcN72g1WHsnIbxGXd4i/MF0gTcuAjQ==", "dev": true, "optional": true }, "@esbuild/linux-s390x": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.0.tgz", - "integrity": "sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.1.tgz", + "integrity": "sha512-cEECeLlJNfT8kZHqLarDBQso9a27o2Zd2AQ8USAEoGtejOrCYHNtKP8XQhMDJMtthdF4GBmjR2au3x1udADQQQ==", "dev": true, "optional": true }, "@esbuild/linux-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.0.tgz", - "integrity": "sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.1.tgz", + "integrity": "sha512-xbfUhu/gnvSEg+EGovRc+kjBAkrvtk38RlerAzQxvMzlB4fXpCFCeUAYzJvrnhFtdeyVCDANSjJvOvGYoeKzFA==", "dev": true, "optional": true }, "@esbuild/netbsd-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.0.tgz", - "integrity": "sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.1.tgz", + "integrity": "sha512-O96poM2XGhLtpTh+s4+nP7YCCAfb4tJNRVZHfIE7dgmax+yMP2WgMd2OecBuaATHKTHsLWHQeuaxMRnCsH8+5g==", "dev": true, "optional": true }, "@esbuild/netbsd-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.0.tgz", - "integrity": "sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.1.tgz", + "integrity": "sha512-X53z6uXip6KFXBQ+Krbx25XHV/NCbzryM6ehOAeAil7X7oa4XIq+394PWGnwaSQ2WRA0KI6PUO6hTO5zeF5ijA==", "dev": true, "optional": true }, "@esbuild/openbsd-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.0.tgz", - "integrity": "sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.1.tgz", + "integrity": "sha512-Na9T3szbXezdzM/Kfs3GcRQNjHzM6GzFBeU1/6IV/npKP5ORtp9zbQjvkDJ47s6BCgaAZnnnu/cY1x342+MvZg==", "dev": true, "optional": true }, "@esbuild/openbsd-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.0.tgz", - "integrity": "sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.1.tgz", + "integrity": "sha512-T3H78X2h1tszfRSf+txbt5aOp/e7TAz3ptVKu9Oyir3IAOFPGV6O9c2naym5TOriy1l0nNf6a4X5UXRZSGX/dw==", "dev": true, "optional": true }, "@esbuild/sunos-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.0.tgz", - "integrity": "sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.1.tgz", + "integrity": "sha512-2H3RUvcmULO7dIE5EWJH8eubZAI4xw54H1ilJnRNZdeo8dTADEZ21w6J22XBkXqGJbe0+wnNJtw3UXRoLJnFEg==", "dev": true, "optional": true }, "@esbuild/win32-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.0.tgz", - "integrity": "sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.1.tgz", + "integrity": "sha512-GE7XvrdOzrb+yVKB9KsRMq+7a2U/K5Cf/8grVFRAGJmfADr/e/ODQ134RK2/eeHqYV5eQRFxb1hY7Nr15fv1NQ==", "dev": true, "optional": true }, "@esbuild/win32-ia32": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.0.tgz", - "integrity": "sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.1.tgz", + "integrity": "sha512-uOxSJCIcavSiT6UnBhBzE8wy3n0hOkJsBOzy7HDAuTDE++1DJMRRVCPGisULScHL+a/ZwdXPpXD3IyFKjA7K8A==", "dev": true, "optional": true }, "@esbuild/win32-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.0.tgz", - "integrity": "sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.1.tgz", + "integrity": "sha512-Y1EQdcfwMSeQN/ujR5VayLOJ1BHaK+ssyk0AEzPjC+t1lITgsnccPqFjb6V+LsTp/9Iov4ysfjxLaGJ9RPtkVg==", "dev": true, "optional": true }, @@ -6775,13 +6775,11 @@ } }, "@types/chai-subset": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@types/chai-subset/-/chai-subset-1.3.5.tgz", - "integrity": "sha512-c2mPnw+xHtXDoHmdtcCXGwyLMiauiAyxWMzhGpqHC4nqI/Y5G2XhTampslK2rb59kpcuHon03UH8W6iYUzw88A==", + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/@types/chai-subset/-/chai-subset-1.3.6.tgz", + "integrity": "sha512-m8lERkkQj+uek18hXOZuec3W/fCRTrU4hrnXjH3qhHy96ytuPaPiWGgu7sJb7tZxZonO75vYAjCvpe/e4VUwRw==", "dev": true, - "requires": { - "@types/chai": "*" - } + "requires": {} }, "@types/glob": { "version": "7.2.0", @@ -6842,9 +6840,9 @@ } }, "@types/node": { - "version": "18.19.79", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.79.tgz", - "integrity": "sha512-90K8Oayimbctc5zTPHPfZloc/lGVs7f3phUAAMcTgEPtg8kKquGZDERC8K4vkBYkQQh48msiYUslYtxTWvqcAg==", + "version": "18.19.80", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.80.tgz", + "integrity": "sha512-kEWeMwMeIvxYkeg1gTc01awpwLbfMRZXdIhwRcakd/KlK53jmRC26LqcbIt7fnAQTu5GzlnWmzA3H6+l1u6xxQ==", "dev": true, "requires": { "undici-types": "~5.26.4" @@ -6907,16 +6905,16 @@ } }, "@typescript-eslint/eslint-plugin": { - "version": "8.26.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.26.0.tgz", - "integrity": "sha512-cLr1J6pe56zjKYajK6SSSre6nl1Gj6xDp1TY0trpgPzjVbgDwd09v2Ws37LABxzkicmUjhEeg/fAUjPJJB1v5Q==", + "version": "8.26.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.26.1.tgz", + "integrity": "sha512-2X3mwqsj9Bd3Ciz508ZUtoQQYpOhU/kWoUqIf49H8Z0+Vbh6UF/y0OEYp0Q0axOGzaBGs7QxRwq0knSQ8khQNA==", "dev": true, "requires": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.26.0", - "@typescript-eslint/type-utils": "8.26.0", - "@typescript-eslint/utils": "8.26.0", - "@typescript-eslint/visitor-keys": "8.26.0", + "@typescript-eslint/scope-manager": "8.26.1", + "@typescript-eslint/type-utils": "8.26.1", + "@typescript-eslint/utils": "8.26.1", + "@typescript-eslint/visitor-keys": "8.26.1", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -6924,54 +6922,54 @@ } }, "@typescript-eslint/parser": { - "version": "8.26.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.26.0.tgz", - "integrity": "sha512-mNtXP9LTVBy14ZF3o7JG69gRPBK/2QWtQd0j0oH26HcY/foyJJau6pNUez7QrM5UHnSvwlQcJXKsk0I99B9pOA==", + "version": "8.26.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.26.1.tgz", + "integrity": "sha512-w6HZUV4NWxqd8BdeFf81t07d7/YV9s7TCWrQQbG5uhuvGUAW+fq1usZ1Hmz9UPNLniFnD8GLSsDpjP0hm1S4lQ==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "8.26.0", - "@typescript-eslint/types": "8.26.0", - "@typescript-eslint/typescript-estree": "8.26.0", - "@typescript-eslint/visitor-keys": "8.26.0", + "@typescript-eslint/scope-manager": "8.26.1", + "@typescript-eslint/types": "8.26.1", + "@typescript-eslint/typescript-estree": "8.26.1", + "@typescript-eslint/visitor-keys": "8.26.1", "debug": "^4.3.4" } }, "@typescript-eslint/scope-manager": { - "version": "8.26.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.26.0.tgz", - "integrity": "sha512-E0ntLvsfPqnPwng8b8y4OGuzh/iIOm2z8U3S9zic2TeMLW61u5IH2Q1wu0oSTkfrSzwbDJIB/Lm8O3//8BWMPA==", + "version": "8.26.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.26.1.tgz", + "integrity": "sha512-6EIvbE5cNER8sqBu6V7+KeMZIC1664d2Yjt+B9EWUXrsyWpxx4lEZrmvxgSKRC6gX+efDL/UY9OpPZ267io3mg==", "dev": true, "requires": { - "@typescript-eslint/types": "8.26.0", - "@typescript-eslint/visitor-keys": "8.26.0" + "@typescript-eslint/types": "8.26.1", + "@typescript-eslint/visitor-keys": "8.26.1" } }, "@typescript-eslint/type-utils": { - "version": "8.26.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.26.0.tgz", - "integrity": "sha512-ruk0RNChLKz3zKGn2LwXuVoeBcUMh+jaqzN461uMMdxy5H9epZqIBtYj7UiPXRuOpaALXGbmRuZQhmwHhaS04Q==", + "version": "8.26.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.26.1.tgz", + "integrity": "sha512-Kcj/TagJLwoY/5w9JGEFV0dclQdyqw9+VMndxOJKtoFSjfZhLXhYjzsQEeyza03rwHx2vFEGvrJWJBXKleRvZg==", "dev": true, "requires": { - "@typescript-eslint/typescript-estree": "8.26.0", - "@typescript-eslint/utils": "8.26.0", + "@typescript-eslint/typescript-estree": "8.26.1", + "@typescript-eslint/utils": "8.26.1", "debug": "^4.3.4", "ts-api-utils": "^2.0.1" } }, "@typescript-eslint/types": { - "version": "8.26.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.26.0.tgz", - "integrity": "sha512-89B1eP3tnpr9A8L6PZlSjBvnJhWXtYfZhECqlBl1D9Lme9mHO6iWlsprBtVenQvY1HMhax1mWOjhtL3fh/u+pA==", + "version": "8.26.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.26.1.tgz", + "integrity": "sha512-n4THUQW27VmQMx+3P+B0Yptl7ydfceUj4ON/AQILAASwgYdZ/2dhfymRMh5egRUrvK5lSmaOm77Ry+lmXPOgBQ==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "8.26.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.26.0.tgz", - "integrity": "sha512-tiJ1Hvy/V/oMVRTbEOIeemA2XoylimlDQ03CgPPNaHYZbpsc78Hmngnt+WXZfJX1pjQ711V7g0H7cSJThGYfPQ==", + "version": "8.26.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.26.1.tgz", + "integrity": "sha512-yUwPpUHDgdrv1QJ7YQal3cMVBGWfnuCdKbXw1yyjArax3353rEJP1ZA+4F8nOlQ3RfS2hUN/wze3nlY+ZOhvoA==", "dev": true, "requires": { - "@typescript-eslint/types": "8.26.0", - "@typescript-eslint/visitor-keys": "8.26.0", + "@typescript-eslint/types": "8.26.1", + "@typescript-eslint/visitor-keys": "8.26.1", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -7001,24 +6999,24 @@ } }, "@typescript-eslint/utils": { - "version": "8.26.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.26.0.tgz", - "integrity": "sha512-2L2tU3FVwhvU14LndnQCA2frYC8JnPDVKyQtWFPf8IYFMt/ykEN1bPolNhNbCVgOmdzTlWdusCTKA/9nKrf8Ig==", + "version": "8.26.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.26.1.tgz", + "integrity": "sha512-V4Urxa/XtSUroUrnI7q6yUTD3hDtfJ2jzVfeT3VK0ciizfK2q/zGC0iDh1lFMUZR8cImRrep6/q0xd/1ZGPQpg==", "dev": true, "requires": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.26.0", - "@typescript-eslint/types": "8.26.0", - "@typescript-eslint/typescript-estree": "8.26.0" + "@typescript-eslint/scope-manager": "8.26.1", + "@typescript-eslint/types": "8.26.1", + "@typescript-eslint/typescript-estree": "8.26.1" } }, "@typescript-eslint/visitor-keys": { - "version": "8.26.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.26.0.tgz", - "integrity": "sha512-2z8JQJWAzPdDd51dRQ/oqIJxe99/hoLIqmf8RMCAJQtYDc535W/Jt2+RTP4bP0aKeBG1F65yjIZuczOXCmbWwg==", + "version": "8.26.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.26.1.tgz", + "integrity": "sha512-AjOC3zfnxd6S4Eiy3jwktJPclqhFHNyd8L6Gycf9WUPoKZpgM5PjkxY1X7uSy61xVpiJDhhk7XT2NVsN3ALTWg==", "dev": true, "requires": { - "@typescript-eslint/types": "8.26.0", + "@typescript-eslint/types": "8.26.1", "eslint-visitor-keys": "^4.2.0" }, "dependencies": { @@ -8065,36 +8063,36 @@ "dev": true }, "esbuild": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.0.tgz", - "integrity": "sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==", - "dev": true, - "requires": { - "@esbuild/aix-ppc64": "0.25.0", - "@esbuild/android-arm": "0.25.0", - "@esbuild/android-arm64": "0.25.0", - "@esbuild/android-x64": "0.25.0", - "@esbuild/darwin-arm64": "0.25.0", - "@esbuild/darwin-x64": "0.25.0", - "@esbuild/freebsd-arm64": "0.25.0", - "@esbuild/freebsd-x64": "0.25.0", - "@esbuild/linux-arm": "0.25.0", - "@esbuild/linux-arm64": "0.25.0", - "@esbuild/linux-ia32": "0.25.0", - "@esbuild/linux-loong64": "0.25.0", - "@esbuild/linux-mips64el": "0.25.0", - "@esbuild/linux-ppc64": "0.25.0", - "@esbuild/linux-riscv64": "0.25.0", - "@esbuild/linux-s390x": "0.25.0", - "@esbuild/linux-x64": "0.25.0", - "@esbuild/netbsd-arm64": "0.25.0", - "@esbuild/netbsd-x64": "0.25.0", - "@esbuild/openbsd-arm64": "0.25.0", - "@esbuild/openbsd-x64": "0.25.0", - "@esbuild/sunos-x64": "0.25.0", - "@esbuild/win32-arm64": "0.25.0", - "@esbuild/win32-ia32": "0.25.0", - "@esbuild/win32-x64": "0.25.0" + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.1.tgz", + "integrity": "sha512-BGO5LtrGC7vxnqucAe/rmvKdJllfGaYWdyABvyMoXQlfYMb2bbRuReWR5tEGE//4LcNJj9XrkovTqNYRFZHAMQ==", + "dev": true, + "requires": { + "@esbuild/aix-ppc64": "0.25.1", + "@esbuild/android-arm": "0.25.1", + "@esbuild/android-arm64": "0.25.1", + "@esbuild/android-x64": "0.25.1", + "@esbuild/darwin-arm64": "0.25.1", + "@esbuild/darwin-x64": "0.25.1", + "@esbuild/freebsd-arm64": "0.25.1", + "@esbuild/freebsd-x64": "0.25.1", + "@esbuild/linux-arm": "0.25.1", + "@esbuild/linux-arm64": "0.25.1", + "@esbuild/linux-ia32": "0.25.1", + "@esbuild/linux-loong64": "0.25.1", + "@esbuild/linux-mips64el": "0.25.1", + "@esbuild/linux-ppc64": "0.25.1", + "@esbuild/linux-riscv64": "0.25.1", + "@esbuild/linux-s390x": "0.25.1", + "@esbuild/linux-x64": "0.25.1", + "@esbuild/netbsd-arm64": "0.25.1", + "@esbuild/netbsd-x64": "0.25.1", + "@esbuild/openbsd-arm64": "0.25.1", + "@esbuild/openbsd-x64": "0.25.1", + "@esbuild/sunos-x64": "0.25.1", + "@esbuild/win32-arm64": "0.25.1", + "@esbuild/win32-ia32": "0.25.1", + "@esbuild/win32-x64": "0.25.1" } }, "escalade": { @@ -8156,9 +8154,9 @@ } }, "eslint-config-prettier": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.0.2.tgz", - "integrity": "sha512-1105/17ZIMjmCOJOPNfVdbXafLCLj3hPmkmB7dLgt7XsQ/zkxSuDerE/xgO3RxoHysR1N1whmquY0lSn2O0VLg==", + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.1.tgz", + "integrity": "sha512-4EQQr6wXwS+ZJSzaR5ZCrYgLxqvUjdXctaEtBqHcbkW944B1NQyO4qpdHQbXBONfwxXdkAY81HH4+LUfrg+zPw==", "dev": true, "requires": {} }, diff --git a/package.json b/package.json index 62b37c9ca..f3df2b895 100644 --- a/package.json +++ b/package.json @@ -1596,21 +1596,21 @@ "devDependencies": { "@types/chai": "^4.3.19", "@types/chai-as-promised": "^7.1.8", - "@types/chai-subset": "^1.3.5", + "@types/chai-subset": "^1.3.6", "@types/glob": "^7.1.6", "@types/lcov-parse": "^1.0.2", "@types/lodash.throttle": "^4.1.9", "@types/mocha": "^10.0.10", "@types/mock-fs": "^4.13.4", - "@types/node": "^18.19.79", + "@types/node": "^18.19.80", "@types/plist": "^3.0.5", "@types/semver": "^7.5.8", "@types/sinon": "^17.0.4", "@types/sinon-chai": "^3.2.12", "@types/vscode": "^1.88.0", "@types/xml2js": "^0.4.14", - "@typescript-eslint/eslint-plugin": "^8.26.0", - "@typescript-eslint/parser": "^8.26.0", + "@typescript-eslint/eslint-plugin": "^8.26.1", + "@typescript-eslint/parser": "^8.26.1", "@vscode/debugprotocol": "^1.68.0", "@vscode/test-cli": "^0.0.10", "@vscode/test-electron": "^2.4.1", @@ -1619,9 +1619,9 @@ "chai-as-promised": "^7.1.2", "chai-subset": "^1.6.0", "del-cli": "^6.0.0", - "esbuild": "^0.25.0", + "esbuild": "^0.25.1", "eslint": "^8.57.0", - "eslint-config-prettier": "^10.0.2", + "eslint-config-prettier": "^10.1.1", "lodash.throttle": "^4.1.1", "mocha": "^10.8.2", "mock-fs": "^5.5.0", From e0f95312363ad85c050d97cadffcd6835d52ed69 Mon Sep 17 00:00:00 2001 From: Matthew Bastien Date: Wed, 12 Mar 2025 13:08:28 -0500 Subject: [PATCH 42/86] Add snippets for debug configurations (#1411) --- package.json | 50 +++++- src/commands.ts | 6 +- src/commands/attachDebugger.ts | 25 +-- src/commands/pickProcess.ts | 79 ++++++++++ src/debugger/debugAdapterFactory.ts | 2 +- src/debugger/lldb.ts | 47 +----- src/process-list/BaseProcessList.ts | 55 +++++++ src/process-list/index.ts | 50 ++++++ .../platforms/DarwinProcessList.ts | 25 +++ .../platforms/LinuxProcessList.ts | 65 ++++++++ .../platforms/WindowsProcessList.ts | 61 ++++++++ test/integration-tests/debugger/lldb.test.ts | 15 +- .../process-list/processList.test.ts | 47 ++++++ .../debugger/attachDebugger.test.ts | 70 --------- .../debugger/debugAdapterFactory.test.ts | 146 ++++++++++-------- test/unit-tests/debugger/lldb.test.ts | 81 ---------- 16 files changed, 524 insertions(+), 300 deletions(-) create mode 100644 src/commands/pickProcess.ts create mode 100644 src/process-list/BaseProcessList.ts create mode 100644 src/process-list/index.ts create mode 100644 src/process-list/platforms/DarwinProcessList.ts create mode 100644 src/process-list/platforms/LinuxProcessList.ts create mode 100644 src/process-list/platforms/WindowsProcessList.ts create mode 100644 test/integration-tests/process-list/processList.test.ts delete mode 100644 test/unit-tests/debugger/attachDebugger.test.ts diff --git a/package.json b/package.json index f3df2b895..6e6def414 100644 --- a/package.json +++ b/package.json @@ -271,6 +271,11 @@ "title": "Run Until Failure...", "category": "Swift" }, + { + "command": "swift.pickProcess", + "title": "Pick Process...", + "category": "Swift" + }, { "command": "swift.runAllTestsParallel", "title": "Run Tests in Parallel", @@ -957,6 +962,10 @@ "command": "swift.reindexProject", "when": "swift.supportsReindexing" }, + { + "command": "swift.pickProcess", + "when": "false" + }, { "command": "swift.runAllTestsParallel", "when": "swift.isActivated" @@ -1358,6 +1367,9 @@ { "type": "swift", "label": "Swift Debugger", + "variables": { + "pickProcess": "swift.pickProcess" + }, "configurationAttributes": { "launch": { "required": [ @@ -1490,6 +1502,7 @@ }, "pid": { "type": [ + "string", "number" ], "description": "System process ID to attach to." @@ -1560,7 +1573,42 @@ } } } - } + }, + "configurationSnippets": [ + { + "label": "Swift: Launch", + "description": "", + "body": { + "type": "swift", + "request": "launch", + "name": "${2:Launch Swift Executable}", + "program": "^\"\\${workspaceRoot}/.build/debug/${1:}\"", + "args": [], + "env": {}, + "cwd": "^\"\\${workspaceRoot}\"" + } + }, + { + "label": "Swift: Attach to Process", + "description": "", + "body": { + "type": "swift", + "request": "attach", + "name": "${1:Attach to Swift Executable}", + "pid": "^\"\\${command:pickProcess}\"" + } + }, + { + "label": "Swift: Attach", + "description": "", + "body": { + "type": "swift", + "request": "attach", + "name": "${2:Attach to Swift Executable}", + "program": "^\"\\${workspaceRoot}/.build/debug/${1:}\"" + } + } + ] } ] }, diff --git a/src/commands.ts b/src/commands.ts index 6a6b08a32..82d28fe38 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -43,6 +43,7 @@ import { runAllTests } from "./commands/runAllTests"; import { updateDependenciesViewList } from "./commands/dependencies/updateDepViewList"; import { runTask } from "./commands/runTask"; import { TestKind } from "./TestExplorer/TestKind"; +import { pickProcess } from "./commands/pickProcess"; /** * References: @@ -65,6 +66,9 @@ export function registerToolchainCommands( vscode.commands.registerCommand("swift.selectToolchain", () => showToolchainSelectionQuickPick(toolchain) ), + vscode.commands.registerCommand("swift.pickProcess", configuration => + pickProcess(configuration) + ), ]; } @@ -169,7 +173,7 @@ export function register(ctx: WorkspaceContext): vscode.Disposable[] { return openInExternalEditor(item); } }), - vscode.commands.registerCommand("swift.attachDebugger", () => attachDebugger(ctx)), + vscode.commands.registerCommand("swift.attachDebugger", attachDebugger), vscode.commands.registerCommand("swift.clearDiagnosticsCollection", () => ctx.diagnostics.clear() ), diff --git a/src/commands/attachDebugger.ts b/src/commands/attachDebugger.ts index 85486fcef..558910015 100644 --- a/src/commands/attachDebugger.ts +++ b/src/commands/attachDebugger.ts @@ -13,8 +13,6 @@ //===----------------------------------------------------------------------===// import * as vscode from "vscode"; -import { WorkspaceContext } from "../WorkspaceContext"; -import { getLldbProcess } from "../debugger/lldb"; import { SWIFT_LAUNCH_CONFIG_TYPE } from "../debugger/debugAdapter"; /** @@ -29,20 +27,11 @@ import { SWIFT_LAUNCH_CONFIG_TYPE } from "../debugger/debugAdapter"; * * @throws Will display an error message if no processes are available, or if the debugger fails to attach to the selected process. */ -export async function attachDebugger(ctx: WorkspaceContext) { - const processPickItems = await getLldbProcess(ctx); - if (processPickItems !== undefined) { - const picked = await vscode.window.showQuickPick(processPickItems, { - placeHolder: "Select Process", - }); - if (picked) { - const debugConfig: vscode.DebugConfiguration = { - type: SWIFT_LAUNCH_CONFIG_TYPE, - request: "attach", - name: "Attach", - pid: picked.pid, - }; - await vscode.debug.startDebugging(undefined, debugConfig); - } - } +export async function attachDebugger() { + await vscode.debug.startDebugging(undefined, { + type: SWIFT_LAUNCH_CONFIG_TYPE, + request: "attach", + name: "Attach", + pid: "${command:pickProcess}", + }); } diff --git a/src/commands/pickProcess.ts b/src/commands/pickProcess.ts new file mode 100644 index 000000000..e18e8b791 --- /dev/null +++ b/src/commands/pickProcess.ts @@ -0,0 +1,79 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the VS Code Swift open source project +// +// Copyright (c) 2025 the VS Code Swift project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of VS Code Swift project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import * as path from "path"; +import * as vscode from "vscode"; +import { createProcessList } from "../process-list"; + +interface ProcessQuickPick extends vscode.QuickPickItem { + processId?: number; +} + +/** + * Prompts the user to select a running process. + * + * The return value must be a string so that it is compatible with VS Code's + * string substitution infrastructure. The value will eventually be converted + * to a number by the debug configuration provider. + * + * @param configuration The related debug configuration, if any + * @returns The pid of the process as a string or undefined if cancelled. + */ +export async function pickProcess( + configuration?: vscode.DebugConfiguration +): Promise { + const processList = createProcessList(); + const selectedProcess = await vscode.window.showQuickPick( + processList.listAllProcesses().then((processes): ProcessQuickPick[] => { + // Sort by start date in descending order + processes.sort((a, b) => b.start - a.start); + // Filter by program if requested + if (typeof configuration?.program === "string") { + const program = configuration.program; + const programBaseName = path.basename(program); + processes = processes + .filter(proc => path.basename(proc.command) === programBaseName) + .sort((a, b) => { + // Bring exact command matches to the top + const aIsExactMatch = a.command === program ? 1 : 0; + const bIsExactMatch = b.command === program ? 1 : 0; + return bIsExactMatch - aIsExactMatch; + }); + // Show a better message if all processes were filtered out + if (processes.length === 0) { + return [ + { + label: "No processes matched the debug configuration's program", + }, + ]; + } + } + // Convert to a QuickPickItem + return processes.map(proc => { + return { + processId: proc.id, + label: path.basename(proc.command), + description: proc.id.toString(), + detail: proc.arguments, + } satisfies ProcessQuickPick; + }); + }), + { + placeHolder: "Select a process to attach the debugger to", + matchOnDetail: true, + matchOnDescription: true, + } + ); + return selectedProcess?.processId?.toString(); +} diff --git a/src/debugger/debugAdapterFactory.ts b/src/debugger/debugAdapterFactory.ts index 10ce7458a..8bea3adde 100644 --- a/src/debugger/debugAdapterFactory.ts +++ b/src/debugger/debugAdapterFactory.ts @@ -74,7 +74,7 @@ export class LLDBDebugConfigurationProvider implements vscode.DebugConfiguration private outputChannel: SwiftOutputChannel ) {} - async resolveDebugConfiguration( + async resolveDebugConfigurationWithSubstitutedVariables( _folder: vscode.WorkspaceFolder | undefined, launchConfig: vscode.DebugConfiguration ): Promise { diff --git a/src/debugger/lldb.ts b/src/debugger/lldb.ts index cf0e0d2fe..91d3b5179 100644 --- a/src/debugger/lldb.ts +++ b/src/debugger/lldb.ts @@ -17,9 +17,7 @@ import * as path from "path"; import * as fs from "fs/promises"; -import * as vscode from "vscode"; -import { WorkspaceContext } from "../WorkspaceContext"; -import { execFile, getErrorDescription } from "../utilities/utilities"; +import { execFile } from "../utilities/utilities"; import { Result } from "../utilities/result"; import { SwiftToolchain } from "../toolchain/toolchain"; @@ -114,46 +112,3 @@ export async function findFileByPattern(path: string, pattern: RegExp): Promise< } return null; } - -/** - * Retrieves a list of LLDB processes from the system using LLDB. - * - * This function executes an LLDB command to list all processes on the system, - * including their arguments, and returns them in an array of objects where each - * object contains the `pid` and a `label` describing the process. - * - * @param {WorkspaceContext} ctx - The workspace context, which includes the toolchain needed to run LLDB. - * @returns {Promise | undefined>} - * A promise that resolves to an array of processes, where each process is represented by an object with a `pid` and a `label`. - * If an error occurs or no processes are found, it returns `undefined`. - * - * @throws Will display an error message in VS Code if the LLDB command fails. - */ -export async function getLldbProcess( - ctx: WorkspaceContext -): Promise | undefined> { - try { - // use LLDB to get list of processes - const lldb = await ctx.toolchain.getLLDB(); - const { stdout } = await execFile(lldb, [ - "--batch", - "--no-lldbinit", - "--one-line", - "platform process list --show-args --all-users", - ]); - const entries = stdout.split("\n"); - const processes = entries.flatMap(line => { - const match = /^(\d+)\s+\d+\s+\S+\s+\S+\s+(.+)$/.exec(line); - if (match) { - return [{ pid: parseInt(match[1]), label: `${match[1]}: ${match[2]}` }]; - } else { - return []; - } - }); - return processes; - } catch (error) { - const errorMessage = `Failed to run LLDB: ${getErrorDescription(error)}`; - ctx.outputChannel.log(errorMessage); - vscode.window.showErrorMessage(errorMessage); - } -} diff --git a/src/process-list/BaseProcessList.ts b/src/process-list/BaseProcessList.ts new file mode 100644 index 000000000..567150a81 --- /dev/null +++ b/src/process-list/BaseProcessList.ts @@ -0,0 +1,55 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the VS Code Swift open source project +// +// Copyright (c) 2025 the VS Code Swift project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of VS Code Swift project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import * as util from "util"; +import * as child_process from "child_process"; +import { Process, ProcessList } from "."; + +const exec = util.promisify(child_process.execFile); + +/** Parses process information from a given line of process output. */ +export type ProcessListParser = (line: string) => Process | undefined; + +/** + * Implements common behavior between the different {@link ProcessList} implementations. + */ +export abstract class BaseProcessList implements ProcessList { + /** + * Get the command responsible for collecting all processes on the system. + */ + protected abstract getCommand(): string; + + /** + * Get the list of arguments used to launch the command. + */ + protected abstract getCommandArguments(): string[]; + + /** + * Create a new parser that can read the process information from stdout of the process + * spawned by {@link spawnProcess spawnProcess()}. + */ + protected abstract createParser(): ProcessListParser; + + async listAllProcesses(): Promise { + const execCommand = exec(this.getCommand(), this.getCommandArguments()); + const parser = this.createParser(); + return (await execCommand).stdout.split("\n").flatMap(line => { + const process = parser(line.toString()); + if (!process || process.id === execCommand.child.pid) { + return []; + } + return [process]; + }); + } +} diff --git a/src/process-list/index.ts b/src/process-list/index.ts new file mode 100644 index 000000000..e1efa2cd6 --- /dev/null +++ b/src/process-list/index.ts @@ -0,0 +1,50 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the VS Code Swift open source project +// +// Copyright (c) 2025 the VS Code Swift project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of VS Code Swift project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import { DarwinProcessList } from "./platforms/DarwinProcessList"; +import { LinuxProcessList } from "./platforms/LinuxProcessList"; +import { WindowsProcessList } from "./platforms/WindowsProcessList"; + +/** + * Represents a single process running on the system. + */ +export interface Process { + /** Process ID */ + id: number; + + /** Command that was used to start the process */ + command: string; + + /** The full command including arguments that was used to start the process */ + arguments: string; + + /** The date when the process was started */ + start: number; +} + +export interface ProcessList { + listAllProcesses(): Promise; +} + +/** Returns a {@link ProcessList} based on the current platform. */ +export function createProcessList(): ProcessList { + switch (process.platform) { + case "darwin": + return new DarwinProcessList(); + case "win32": + return new WindowsProcessList(); + default: + return new LinuxProcessList(); + } +} diff --git a/src/process-list/platforms/DarwinProcessList.ts b/src/process-list/platforms/DarwinProcessList.ts new file mode 100644 index 000000000..2fbc60033 --- /dev/null +++ b/src/process-list/platforms/DarwinProcessList.ts @@ -0,0 +1,25 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the VS Code Swift open source project +// +// Copyright (c) 2025 the VS Code Swift project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of VS Code Swift project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import { LinuxProcessList } from "./LinuxProcessList"; + +export class DarwinProcessList extends LinuxProcessList { + protected override getCommandArguments(): string[] { + return [ + "-axo", + // The length of comm must be large enough or data will be truncated. + `pid=PID,state=STATE,lstart=START,comm=${"COMMAND".padEnd(256, "-")},args=ARGUMENTS`, + ]; + } +} diff --git a/src/process-list/platforms/LinuxProcessList.ts b/src/process-list/platforms/LinuxProcessList.ts new file mode 100644 index 000000000..9db94a8e7 --- /dev/null +++ b/src/process-list/platforms/LinuxProcessList.ts @@ -0,0 +1,65 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the VS Code Swift open source project +// +// Copyright (c) 2025 the VS Code Swift project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of VS Code Swift project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import { BaseProcessList, ProcessListParser } from "../BaseProcessList"; + +export class LinuxProcessList extends BaseProcessList { + protected override getCommand(): string { + return "ps"; + } + + protected override getCommandArguments(): string[] { + return [ + "-axo", + // The length of exe must be large enough or data will be truncated. + `pid=PID,state=STATE,lstart=START,exe:128=COMMAND,args=ARGUMENTS`, + ]; + } + + protected override createParser(): ProcessListParser { + let commandOffset: number | undefined; + let argumentsOffset: number | undefined; + return line => { + if (!commandOffset || !argumentsOffset) { + commandOffset = line.indexOf("COMMAND"); + argumentsOffset = line.indexOf("ARGUMENTS"); + return; + } + + const pidAndState = /^\s*([0-9]+)\s+([a-zA-Z<>+]+)\s+/.exec(line); + if (!pidAndState) { + return; + } + + // Make sure the process isn't in a trace/debug or zombie state as we cannot attach to them + const state = pidAndState[2]; + if (state.includes("X") || state.includes("Z")) { + return; + } + + // ps will list "-" as the command if it does not know where the executable is located + const command = line.slice(commandOffset, argumentsOffset).trim(); + if (command === "-") { + return; + } + + return { + id: Number(pidAndState[1]), + command, + arguments: line.slice(argumentsOffset).trim(), + start: Date.parse(line.slice(pidAndState[0].length, commandOffset).trim()), + }; + }; + } +} diff --git a/src/process-list/platforms/WindowsProcessList.ts b/src/process-list/platforms/WindowsProcessList.ts new file mode 100644 index 000000000..ce7d77698 --- /dev/null +++ b/src/process-list/platforms/WindowsProcessList.ts @@ -0,0 +1,61 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the VS Code Swift open source project +// +// Copyright (c) 2025 the VS Code Swift project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of VS Code Swift project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import { BaseProcessList, ProcessListParser } from "../BaseProcessList"; + +export class WindowsProcessList extends BaseProcessList { + protected override getCommand(): string { + return "PowerShell"; + } + + protected override getCommandArguments(): string[] { + return [ + "-Command", + 'Get-CimInstance -ClassName Win32_Process | Format-Table ProcessId, @{Label="CreationDate";Expression={"{0:yyyyMddHHmmss}" -f $_.CreationDate}}, CommandLine | Out-String -width 9999', + ]; + } + + protected override createParser(): ProcessListParser { + const lineRegex = /^([0-9]+)\s+([0-9]+)\s+(.*)$/; + + return line => { + const matches = lineRegex.exec(line.trim()); + if (!matches || matches.length !== 4) { + return; + } + + const id = Number(matches[1]); + const start = Number(matches[2]); + const fullCommandLine = matches[3].trim(); + if (isNaN(id) || !fullCommandLine) { + return; + } + // Extract the command from the full command line + let command = fullCommandLine; + if (fullCommandLine[0] === '"') { + const end = fullCommandLine.indexOf('"', 1); + if (end > 0) { + command = fullCommandLine.slice(1, end); + } + } else { + const end = fullCommandLine.indexOf(" "); + if (end > 0) { + command = fullCommandLine.slice(0, end); + } + } + + return { id, command, arguments: fullCommandLine, start }; + }; + } +} diff --git a/test/integration-tests/debugger/lldb.test.ts b/test/integration-tests/debugger/lldb.test.ts index 2a66f8b2e..0f747bf91 100644 --- a/test/integration-tests/debugger/lldb.test.ts +++ b/test/integration-tests/debugger/lldb.test.ts @@ -13,7 +13,7 @@ //===----------------------------------------------------------------------===// import { expect } from "chai"; -import { getLLDBLibPath, getLldbProcess } from "../../../src/debugger/lldb"; +import { getLLDBLibPath } from "../../../src/debugger/lldb"; import { WorkspaceContext } from "../../../src/WorkspaceContext"; import { activateExtensionForTest } from "../utilities/testutilities"; import { Version } from "../../../src/utilities/version"; @@ -37,19 +37,6 @@ suite("lldb contract test suite", () => { requiresDebugger: true, }); - test("getLldbProcess Contract Test, make sure the command returns", async () => { - const result = await getLldbProcess(workspaceContext); - - // Assumption: machine will always return some process - expect(result).to.be.an("array"); - - // If result is an array, assert that each element has a pid and label - result?.forEach(item => { - expect(item).to.have.property("pid").that.is.a("number"); - expect(item).to.have.property("label").that.is.a("string"); - }); - }); - test("getLLDBLibPath Contract Test, make sure we can find lib LLDB", async () => { const libPath = await getLLDBLibPath(workspaceContext.toolchain); diff --git a/test/integration-tests/process-list/processList.test.ts b/test/integration-tests/process-list/processList.test.ts new file mode 100644 index 000000000..65360d2f8 --- /dev/null +++ b/test/integration-tests/process-list/processList.test.ts @@ -0,0 +1,47 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the VS Code Swift open source project +// +// Copyright (c) 2025 the VS Code Swift project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of VS Code Swift project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import * as path from "path"; +import { expect } from "chai"; +import { createProcessList, Process } from "../../../src/process-list"; + +suite("ProcessList Tests", () => { + function expectProcessName(processes: Process[], command: string) { + expect( + processes.findIndex(proc => path.basename(proc.command) === command), + `Expected the list of processes to include '${command}':\n ${processes.map(proc => `${proc.id} - ${path.basename(proc.command)}`).join("\n")}\n\n` + ).to.be.greaterThanOrEqual(0); + } + + test("retreives the list of available processes", async function () { + // We can guarantee that certain VS Code processes will be present during tests + const processes = await createProcessList().listAllProcesses(); + switch (process.platform) { + case "darwin": + expectProcessName(processes, "Code Helper"); + expectProcessName(processes, "Code Helper (GPU)"); + expectProcessName(processes, "Code Helper (Plugin)"); + expectProcessName(processes, "Code Helper (Renderer)"); + break; + case "win32": + expectProcessName(processes, "Code.exe"); + break; + case "linux": + expectProcessName(processes, "code"); + break; + default: + this.skip(); + } + }); +}); diff --git a/test/unit-tests/debugger/attachDebugger.test.ts b/test/unit-tests/debugger/attachDebugger.test.ts deleted file mode 100644 index baaba6dc7..000000000 --- a/test/unit-tests/debugger/attachDebugger.test.ts +++ /dev/null @@ -1,70 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the VS Code Swift open source project -// -// Copyright (c) 2021-2024 the VS Code Swift project authors -// Licensed under Apache License v2.0 -// -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of VS Code Swift project authors -// -// SPDX-License-Identifier: Apache-2.0 -// -//===----------------------------------------------------------------------===// - -import { expect } from "chai"; -import * as vscode from "vscode"; -import * as lldb from "../../../src/debugger/lldb"; -import { attachDebugger } from "../../../src/commands/attachDebugger"; -import { - mockObject, - mockGlobalObject, - mockGlobalModule, - MockedObject, - instance, -} from "../../MockUtils"; -import { SwiftToolchain } from "../../../src/toolchain/toolchain"; -import { WorkspaceContext } from "../../../src/WorkspaceContext"; -import { registerDebugger } from "../../../src/debugger/debugAdapterFactory"; -import { Version } from "../../../src/utilities/version"; -import { SwiftOutputChannel } from "../../../src/ui/SwiftOutputChannel"; - -suite("attachDebugger Unit Test Suite", () => { - const lldbMock = mockGlobalModule(lldb); - const windowMock = mockGlobalObject(vscode, "window"); - const debugMock = mockGlobalObject(vscode, "debug"); - - let mockContext: MockedObject; - let mockToolchain: MockedObject; - - setup(() => { - mockToolchain = mockObject({ - swiftVersion: new Version(6, 0, 0), - }); - mockContext = mockObject({ - toolchain: instance(mockToolchain), - outputChannel: instance(mockObject({})), - }); - }); - - test("should call startDebugging with correct debugConfig", async () => { - // Setup fake debug adapter - registerDebugger(instance(mockContext)); - - // Mock the list of processes returned by getLldbProcess - const processPickItems = [ - { pid: 1234, label: "1234: Process1" }, - { pid: 2345, label: "2345: Process2" }, - ]; - lldbMock.getLldbProcess.resolves(processPickItems); - windowMock.showQuickPick.callsFake(async items => (await items)[0]); - - // Call attachDebugger - await attachDebugger(instance(mockContext)); - - // Verify startDebugging was called with the right pid. - // NB(separate itest): actual config return a fulfilled promise. - expect(debugMock.startDebugging).to.have.been.calledOnce; - expect(debugMock.startDebugging.args[0][1]).to.containSubset({ pid: 1234 }); - }); -}); diff --git a/test/unit-tests/debugger/debugAdapterFactory.test.ts b/test/unit-tests/debugger/debugAdapterFactory.test.ts index 81292548c..60bda3226 100644 --- a/test/unit-tests/debugger/debugAdapterFactory.test.ts +++ b/test/unit-tests/debugger/debugAdapterFactory.test.ts @@ -75,12 +75,13 @@ suite("LLDBDebugConfigurationProvider Tests", () => { instance(mockToolchain), instance(mockOutputChannel) ); - const launchConfig = await configProvider.resolveDebugConfiguration(undefined, { - name: "Test Launch Config", - type: SWIFT_LAUNCH_CONFIG_TYPE, - request: "launch", - program: "${workspaceFolder}/.build/debug/executable", - }); + const launchConfig = + await configProvider.resolveDebugConfigurationWithSubstitutedVariables(undefined, { + name: "Test Launch Config", + type: SWIFT_LAUNCH_CONFIG_TYPE, + request: "launch", + program: "${workspaceFolder}/.build/debug/executable", + }); expect(launchConfig).to.containSubset({ type: LaunchConfigType.CODE_LLDB }); }); @@ -93,7 +94,7 @@ suite("LLDBDebugConfigurationProvider Tests", () => { instance(mockOutputChannel) ); await expect( - configProvider.resolveDebugConfiguration(undefined, { + configProvider.resolveDebugConfigurationWithSubstitutedVariables(undefined, { name: "Test Launch Config", type: SWIFT_LAUNCH_CONFIG_TYPE, request: "launch", @@ -115,7 +116,7 @@ suite("LLDBDebugConfigurationProvider Tests", () => { instance(mockOutputChannel) ); await expect( - configProvider.resolveDebugConfiguration(undefined, { + configProvider.resolveDebugConfigurationWithSubstitutedVariables(undefined, { name: "Test Launch Config", type: SWIFT_LAUNCH_CONFIG_TYPE, request: "launch", @@ -138,7 +139,7 @@ suite("LLDBDebugConfigurationProvider Tests", () => { instance(mockOutputChannel) ); await expect( - configProvider.resolveDebugConfiguration(undefined, { + configProvider.resolveDebugConfigurationWithSubstitutedVariables(undefined, { name: "Test Launch Config", type: SWIFT_LAUNCH_CONFIG_TYPE, request: "launch", @@ -172,12 +173,13 @@ suite("LLDBDebugConfigurationProvider Tests", () => { instance(mockToolchain), instance(mockOutputChannel) ); - const launchConfig = await configProvider.resolveDebugConfiguration(undefined, { - name: "Test Launch Config", - type: SWIFT_LAUNCH_CONFIG_TYPE, - request: "launch", - program: "${workspaceFolder}/.build/debug/executable", - }); + const launchConfig = + await configProvider.resolveDebugConfigurationWithSubstitutedVariables(undefined, { + name: "Test Launch Config", + type: SWIFT_LAUNCH_CONFIG_TYPE, + request: "launch", + program: "${workspaceFolder}/.build/debug/executable", + }); expect(launchConfig).to.containSubset({ type: LaunchConfigType.LLDB_DAP, debugAdapterExecutable: "/path/to/lldb-dap", @@ -192,7 +194,7 @@ suite("LLDBDebugConfigurationProvider Tests", () => { instance(mockOutputChannel) ); await expect( - configProvider.resolveDebugConfiguration(undefined, { + configProvider.resolveDebugConfigurationWithSubstitutedVariables(undefined, { name: "Test Launch Config", type: SWIFT_LAUNCH_CONFIG_TYPE, request: "launch", @@ -208,12 +210,13 @@ suite("LLDBDebugConfigurationProvider Tests", () => { instance(mockToolchain), instance(mockOutputChannel) ); - const launchConfig = await configProvider.resolveDebugConfiguration(undefined, { - name: "Test Launch Config", - type: SWIFT_LAUNCH_CONFIG_TYPE, - request: "launch", - program: "${workspaceFolder}/.build/debug/executable", - }); + const launchConfig = + await configProvider.resolveDebugConfigurationWithSubstitutedVariables(undefined, { + name: "Test Launch Config", + type: SWIFT_LAUNCH_CONFIG_TYPE, + request: "launch", + program: "${workspaceFolder}/.build/debug/executable", + }); expect(launchConfig).to.containSubset({ program: "${workspaceFolder}/.build/debug/executable.exe", }); @@ -225,12 +228,13 @@ suite("LLDBDebugConfigurationProvider Tests", () => { instance(mockToolchain), instance(mockOutputChannel) ); - const launchConfig = await configProvider.resolveDebugConfiguration(undefined, { - name: "Test Launch Config", - type: SWIFT_LAUNCH_CONFIG_TYPE, - request: "launch", - program: "${workspaceFolder}/.build/debug/executable.exe", - }); + const launchConfig = + await configProvider.resolveDebugConfigurationWithSubstitutedVariables(undefined, { + name: "Test Launch Config", + type: SWIFT_LAUNCH_CONFIG_TYPE, + request: "launch", + program: "${workspaceFolder}/.build/debug/executable.exe", + }); expect(launchConfig).to.containSubset({ program: "${workspaceFolder}/.build/debug/executable.exe", }); @@ -242,12 +246,13 @@ suite("LLDBDebugConfigurationProvider Tests", () => { instance(mockToolchain), instance(mockOutputChannel) ); - const launchConfig = await configProvider.resolveDebugConfiguration(undefined, { - name: "Test Launch Config", - type: SWIFT_LAUNCH_CONFIG_TYPE, - request: "launch", - program: "${workspaceFolder}/.build/debug/executable", - }); + const launchConfig = + await configProvider.resolveDebugConfigurationWithSubstitutedVariables(undefined, { + name: "Test Launch Config", + type: SWIFT_LAUNCH_CONFIG_TYPE, + request: "launch", + program: "${workspaceFolder}/.build/debug/executable", + }); expect(launchConfig).to.containSubset({ program: "${workspaceFolder}/.build/debug/executable", }); @@ -259,12 +264,13 @@ suite("LLDBDebugConfigurationProvider Tests", () => { instance(mockToolchain), instance(mockOutputChannel) ); - const launchConfig = await configProvider.resolveDebugConfiguration(undefined, { - name: "Test Launch Config", - type: SWIFT_LAUNCH_CONFIG_TYPE, - request: "launch", - program: "${workspaceFolder}/.build/debug/executable", - }); + const launchConfig = + await configProvider.resolveDebugConfigurationWithSubstitutedVariables(undefined, { + name: "Test Launch Config", + type: SWIFT_LAUNCH_CONFIG_TYPE, + request: "launch", + program: "${workspaceFolder}/.build/debug/executable", + }); expect(launchConfig).to.containSubset({ program: "${workspaceFolder}/.build/debug/executable", }); @@ -276,16 +282,17 @@ suite("LLDBDebugConfigurationProvider Tests", () => { instance(mockToolchain), instance(mockOutputChannel) ); - const launchConfig = await configProvider.resolveDebugConfiguration(undefined, { - name: "Test Launch Config", - type: SWIFT_LAUNCH_CONFIG_TYPE, - request: "launch", - program: "${workspaceFolder}/.build/debug/executable", - env: { - VAR1: "value1", - VAR2: "value2", - }, - }); + const launchConfig = + await configProvider.resolveDebugConfigurationWithSubstitutedVariables(undefined, { + name: "Test Launch Config", + type: SWIFT_LAUNCH_CONFIG_TYPE, + request: "launch", + program: "${workspaceFolder}/.build/debug/executable", + env: { + VAR1: "value1", + VAR2: "value2", + }, + }); expect(launchConfig) .to.have.property("env") .that.deep.equals(["VAR1=value1", "VAR2=value2"]); @@ -297,12 +304,13 @@ suite("LLDBDebugConfigurationProvider Tests", () => { instance(mockToolchain), instance(mockOutputChannel) ); - const launchConfig = await configProvider.resolveDebugConfiguration(undefined, { - name: "Test Launch Config", - type: SWIFT_LAUNCH_CONFIG_TYPE, - request: "launch", - program: "${workspaceFolder}/.build/debug/executable", - }); + const launchConfig = + await configProvider.resolveDebugConfigurationWithSubstitutedVariables(undefined, { + name: "Test Launch Config", + type: SWIFT_LAUNCH_CONFIG_TYPE, + request: "launch", + program: "${workspaceFolder}/.build/debug/executable", + }); expect(launchConfig).to.not.have.property("env"); }); @@ -312,12 +320,13 @@ suite("LLDBDebugConfigurationProvider Tests", () => { instance(mockToolchain), instance(mockOutputChannel) ); - const launchConfig = await configProvider.resolveDebugConfiguration(undefined, { - type: SWIFT_LAUNCH_CONFIG_TYPE, - request: "launch", - name: "Test Launch", - env: {}, - }); + const launchConfig = + await configProvider.resolveDebugConfigurationWithSubstitutedVariables(undefined, { + type: SWIFT_LAUNCH_CONFIG_TYPE, + request: "launch", + name: "Test Launch", + env: {}, + }); expect(launchConfig).to.have.property("env").that.deep.equals([]); }); @@ -333,12 +342,13 @@ suite("LLDBDebugConfigurationProvider Tests", () => { instance(mockToolchain), instance(mockOutputChannel) ); - const launchConfig = await configProvider.resolveDebugConfiguration(undefined, { - type: SWIFT_LAUNCH_CONFIG_TYPE, - request: "launch", - name: "Test Launch", - env, - }); + const launchConfig = + await configProvider.resolveDebugConfigurationWithSubstitutedVariables(undefined, { + type: SWIFT_LAUNCH_CONFIG_TYPE, + request: "launch", + name: "Test Launch", + env, + }); // Verify that all 1000 environment variables are properly converted const expectedEnv = Array.from({ length: 1000 }, (_, i) => `VAR${i}=value${i}`); diff --git a/test/unit-tests/debugger/lldb.test.ts b/test/unit-tests/debugger/lldb.test.ts index f7034e460..c3bd582df 100644 --- a/test/unit-tests/debugger/lldb.test.ts +++ b/test/unit-tests/debugger/lldb.test.ts @@ -12,7 +12,6 @@ // //===----------------------------------------------------------------------===// -import * as vscode from "vscode"; import * as util from "../../../src/utilities/utilities"; import * as lldb from "../../../src/debugger/lldb"; import * as fs from "fs/promises"; @@ -23,14 +22,11 @@ import { MockedObject, mockFn, mockGlobalModule, - mockGlobalObject, mockObject, MockedFunction, mockGlobalValue, } from "../../MockUtils"; import { SwiftToolchain } from "../../../src/toolchain/toolchain"; -import { WorkspaceContext } from "../../../src/WorkspaceContext"; -import { SwiftOutputChannel } from "../../../src/ui/SwiftOutputChannel"; suite("debugger.lldb Tests", () => { suite("getLLDBLibPath Tests", () => { @@ -136,81 +132,4 @@ suite("debugger.lldb Tests", () => { expect(result).to.be.null; }); }); - - suite("getLldbProcess Unit Test Suite", () => { - const utilMock = mockGlobalModule(util); - const windowMock = mockGlobalObject(vscode, "window"); - - let mockContext: MockedObject; - let mockToolchain: MockedObject; - - setup(() => { - windowMock.createOutputChannel.returns({ - appendLine() {}, - } as unknown as vscode.LogOutputChannel); - - mockToolchain = mockObject({ - getLLDB: mockFn(s => s.resolves("/path/to/lldb")), - }); - mockContext = mockObject({ - toolchain: instance(mockToolchain), - outputChannel: new SwiftOutputChannel("mockChannel"), - }); - }); - - test("should return an empty list when no processes are found", async () => { - utilMock.execFile.resolves({ stdout: "", stderr: "" }); - - const result = await lldb.getLldbProcess(instance(mockContext)); - - expect(result).to.be.an("array").that.is.empty; - }); - - test("should return a list with one process", async () => { - utilMock.execFile.resolves({ - stdout: `1234 5678 user1 group1 SingleProcess\n`, - stderr: "", - }); - - const result = await lldb.getLldbProcess(instance(mockContext)); - - expect(result).to.deep.equal([{ pid: 1234, label: "1234: SingleProcess" }]); - }); - - test("should return a list with many processes", async () => { - const manyProcessesOutput = Array(1000) - .fill(0) - .map((_, i) => { - return `${1000 + i} 2000 user${i} group${i} Process${i}`; - }) - .join("\n"); - utilMock.execFile.resolves({ - stdout: manyProcessesOutput, - stderr: "", - }); - - const result = await lldb.getLldbProcess(instance(mockContext)); - - // Assert that the result is an array with 1000 processes - const expected = Array(1000) - .fill(0) - .map((_, i) => ({ - pid: 1000 + i, - label: `${1000 + i}: Process${i}`, - })); - expect(result).to.deep.equal(expected); - }); - - test("should handle errors correctly", async () => { - utilMock.execFile.rejects(new Error("LLDB Error")); - utilMock.getErrorDescription.returns("LLDB Error"); - - const result = await lldb.getLldbProcess(instance(mockContext)); - - expect(result).to.equal(undefined); - expect(windowMock.showErrorMessage).to.have.been.calledWith( - "Failed to run LLDB: LLDB Error" - ); - }); - }); }); From b5fc65c52c7e45bb6b95b1c358cf689b813d816c Mon Sep 17 00:00:00 2001 From: Paul LeMarquand Date: Fri, 14 Mar 2025 11:31:58 -0400 Subject: [PATCH 43/86] Ensure environment from settings is added to resolved tasks (#1429) The environment in the task.json definitions was not adding workspace/global env settings. Layer in the `swiftEnvironmentVariables` the same way as in generated build tasks. --- src/tasks/SwiftTaskProvider.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/tasks/SwiftTaskProvider.ts b/src/tasks/SwiftTaskProvider.ts index 40ac5179f..e0d02c86c 100644 --- a/src/tasks/SwiftTaskProvider.ts +++ b/src/tasks/SwiftTaskProvider.ts @@ -439,6 +439,11 @@ export class SwiftTaskProvider implements vscode.TaskProvider { const args = platform?.args ?? task.definition.args; const env = platform?.env ?? task.definition.env; const fullCwd = resolveTaskCwd(task, platform?.cwd ?? task.definition.cwd); + const fullEnv = { + ...configuration.swiftEnvironmentVariables, + ...swiftRuntimeEnv(), + ...env, + }; const presentation = task.definition.presentation ?? task.presentationOptions ?? {}; const newTask = new vscode.Task( @@ -448,7 +453,7 @@ export class SwiftTaskProvider implements vscode.TaskProvider { "swift", new SwiftExecution(swift, args, { cwd: fullCwd, - env: { ...env, ...swiftRuntimeEnv() }, + env: fullEnv, presentation, }), task.problemMatchers From e659d4c368515ce69f638a9e4cbcaf64399c433f Mon Sep 17 00:00:00 2001 From: Paul LeMarquand Date: Fri, 14 Mar 2025 11:32:27 -0400 Subject: [PATCH 44/86] Reload extension when swiftEnvironmentVariables changes (#1430) The generated Swift build tasks are created once when the workspace folder is added to the workspace. The environment variables are captured at task creation time. If they're updated during the lifetime of the extension they wont be attached to the generated tasks until the extension restarts. Issue: #1428 --- src/extension.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index 15e45be46..bc160594a 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -97,9 +97,8 @@ export async function activate(context: vscode.ExtensionContext): Promise { showReloadExtensionNotification( "Changing the Swift path requires Visual Studio Code be reloaded." ); - } - // on sdk config change, restart sourcekit-lsp - if ( + } else if ( + // on sdk config change, restart sourcekit-lsp event.affectsConfiguration("swift.SDK") || event.affectsConfiguration("swift.swiftSDK") ) { @@ -108,6 +107,10 @@ export async function activate(context: vscode.ExtensionContext): Promise { showReloadExtensionNotification( "Changing the Swift SDK path requires the project be reloaded." ); + } else if (event.affectsConfiguration("swift.swiftEnvironmentVariables")) { + showReloadExtensionNotification( + "Changing environment variables requires the project be reloaded." + ); } }) ); From 4121e2e98264b100e12cf8c11796ee644dfdccac Mon Sep 17 00:00:00 2001 From: Rishi Date: Fri, 14 Mar 2025 13:46:04 -0400 Subject: [PATCH 45/86] Set swift.swiftSDK for target platforms (#1390) * Set swift.swiftSDK for target platforms SourceKit-LSP now provides code editing support for non-macOS Darwin platforms starting Swift 6.1 using the --swift-sdk flag. Set this flag to the appropriate target triple when using the "Select Target Platform" feature on macOS. Issue: #1335 * Restrict Switch Platform to macOS and Swift 6.1 Currently this command only supports Darwin SDKs on macOS, and code editing support from Swift 6.1 and above. Only show the command for these cases. Also add a unit test for the command. Issue: #1335 --- README.md | 2 +- package.json | 2 +- src/commands.ts | 6 +- src/commands/switchPlatform.ts | 47 ++++++---- src/contextKeys.ts | 15 ++++ src/extension.ts | 10 +-- .../commands/switchPlatform.test.ts | 85 +++++++++++++++++++ 7 files changed, 140 insertions(+), 27 deletions(-) create mode 100644 test/unit-tests/commands/switchPlatform.test.ts diff --git a/README.md b/README.md index 4638e905e..308402e85 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ The extension adds the following commands, available via the command palette. The following command is only available on macOS: -- **Select Target Platform**: This is an experimental command that offers code completion for iOS and tvOS projects. +- **Select Target Platform**: This is an experimental command that offers code editing support for iOS, tvOS, watchOS and visionOS projects. #### Building and Debugging diff --git a/package.json b/package.json index 6e6def414..8d1668258 100644 --- a/package.json +++ b/package.json @@ -884,7 +884,7 @@ }, { "command": "swift.switchPlatform", - "when": "swift.isActivated && isMac" + "when": "swift.isActivated && isMac && swift.switchPlatformAvailable" }, { "command": "swift.insertFunctionComment", diff --git a/src/commands.ts b/src/commands.ts index 82d28fe38..7b9c7bb83 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -124,8 +124,10 @@ export function register(ctx: WorkspaceContext): vscode.Disposable[] { return runTestMultipleTimes(ctx.currentFolder, item, true); } }), - // Note: This is only available on macOS (gated in `package.json`) because its the only OS that has the iOS SDK available. - vscode.commands.registerCommand("swift.switchPlatform", () => switchPlatform()), + // Note: switchPlatform is only available on macOS and Swift 6.1 or later + // (gated in `package.json`) because it's the only OS and toolchain combination that + // has Darwin SDKs available and supports code editing with SourceKit-LSP + vscode.commands.registerCommand("swift.switchPlatform", () => switchPlatform(ctx)), vscode.commands.registerCommand(Commands.RESET_PACKAGE, () => resetPackage(ctx)), vscode.commands.registerCommand("swift.runScript", () => runSwiftScript(ctx)), vscode.commands.registerCommand("swift.openPackage", () => { diff --git a/src/commands/switchPlatform.ts b/src/commands/switchPlatform.ts index 821a8ccfb..e65dfda2c 100644 --- a/src/commands/switchPlatform.ts +++ b/src/commands/switchPlatform.ts @@ -13,13 +13,18 @@ //===----------------------------------------------------------------------===// import * as vscode from "vscode"; -import { DarwinCompatibleTarget, SwiftToolchain } from "../toolchain/toolchain"; +import { + DarwinCompatibleTarget, + SwiftToolchain, + getDarwinTargetTriple, +} from "../toolchain/toolchain"; import configuration from "../configuration"; +import { WorkspaceContext } from "../WorkspaceContext"; /** - * Switches the target SDK to the platform selected in a QuickPick UI. + * Switches the appropriate SDK setting to the platform selected in a QuickPick UI. */ -export async function switchPlatform() { +export async function switchPlatform(ctx: WorkspaceContext) { const picked = await vscode.window.showQuickPick( [ { value: undefined, label: "macOS" }, @@ -29,28 +34,34 @@ export async function switchPlatform() { { value: DarwinCompatibleTarget.visionOS, label: "visionOS" }, ], { - placeHolder: "Select a new target", + placeHolder: "Select a new target platform", } ); if (picked) { + // show a status item as getSDKForTarget can sometimes take a long while to run xcrun to find the SDK + const statusItemText = `Setting target platform to ${picked.label}`; + ctx.statusItem.start(statusItemText); try { - const sdkForTarget = picked.value - ? await SwiftToolchain.getSDKForTarget(picked.value) - : ""; - if (sdkForTarget !== undefined) { - if (sdkForTarget !== "") { - configuration.sdk = sdkForTarget; - vscode.window.showWarningMessage( - `Selecting the ${picked.label} SDK will provide code editing support, but compiling with this SDK will have undefined results.` - ); - } else { - configuration.sdk = undefined; - } + if (picked.value) { + // verify that the SDK for the platform actually exists + await SwiftToolchain.getSDKForTarget(picked.value); + } + const swiftSDKTriple = picked.value ? getDarwinTargetTriple(picked.value) : ""; + if (swiftSDKTriple !== "") { + // set a swiftSDK for non-macOS Darwin platforms so that SourceKit-LSP can provide syntax highlighting + configuration.swiftSDK = swiftSDKTriple; + vscode.window.showWarningMessage( + `Selecting the ${picked.label} target platform will provide code editing support, but compiling with a ${picked.label} SDK will have undefined results.` + ); } else { - vscode.window.showErrorMessage("Unable to obtain requested SDK path"); + // set swiftSDK to an empty string for macOS and other platforms + configuration.swiftSDK = ""; } } catch { - vscode.window.showErrorMessage("Unable to obtain requested SDK path"); + vscode.window.showErrorMessage( + `Unable set the Swift SDK setting to ${picked.label}, verify that the SDK exists` + ); } + ctx.statusItem.end(statusItemText); } } diff --git a/src/contextKeys.ts b/src/contextKeys.ts index e550c7707..ed735af9c 100644 --- a/src/contextKeys.ts +++ b/src/contextKeys.ts @@ -77,6 +77,11 @@ interface ContextKeys { * Whether the SourceKit-LSP server supports documentation live preview. */ supportsDocumentationLivePreview: boolean; + + /** + * Whether the swift.switchPlatform command is available. + */ + switchPlatformAvailable: boolean; } /** Creates the getters and setters for the VS Code Swift extension's context keys. */ @@ -92,6 +97,7 @@ function createContextKeys(): ContextKeys { let createNewProjectAvailable: boolean = false; let supportsReindexing: boolean = false; let supportsDocumentationLivePreview: boolean = false; + let switchPlatformAvailable: boolean = false; return { get isActivated() { @@ -200,6 +206,15 @@ function createContextKeys(): ContextKeys { value ); }, + + get switchPlatformAvailable() { + return switchPlatformAvailable; + }, + + set switchPlatformAvailable(value: boolean) { + switchPlatformAvailable = value; + vscode.commands.executeCommand("setContext", "swift.switchPlatformAvailable", value); + }, }; } diff --git a/src/extension.ts b/src/extension.ts index bc160594a..7361075f1 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -76,12 +76,16 @@ export async function activate(context: vscode.ExtensionContext): Promise { contextKeys.createNewProjectAvailable = toolchain.swiftVersion.isGreaterThanOrEqual( new Version(5, 8, 0) ); + contextKeys.switchPlatformAvailable = toolchain.swiftVersion.isGreaterThanOrEqual( + new Version(6, 1, 0) + ); return toolchain; }) .catch(error => { outputChannel.log("Failed to discover Swift toolchain"); outputChannel.log(error); contextKeys.createNewProjectAvailable = false; + contextKeys.switchPlatformAvailable = false; return undefined; }); @@ -102,11 +106,7 @@ export async function activate(context: vscode.ExtensionContext): Promise { event.affectsConfiguration("swift.SDK") || event.affectsConfiguration("swift.swiftSDK") ) { - // FIXME: There is a bug stopping us from restarting SourceKit-LSP directly. - // As long as it's fixed we won't need to reload on newer versions. - showReloadExtensionNotification( - "Changing the Swift SDK path requires the project be reloaded." - ); + vscode.commands.executeCommand("swift.restartLSPServer"); } else if (event.affectsConfiguration("swift.swiftEnvironmentVariables")) { showReloadExtensionNotification( "Changing environment variables requires the project be reloaded." diff --git a/test/unit-tests/commands/switchPlatform.test.ts b/test/unit-tests/commands/switchPlatform.test.ts new file mode 100644 index 000000000..f2c54f105 --- /dev/null +++ b/test/unit-tests/commands/switchPlatform.test.ts @@ -0,0 +1,85 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the VS Code Swift open source project +// +// Copyright (c) 2021-2025 the VS Code Swift project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of VS Code Swift project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import { expect } from "chai"; +import * as vscode from "vscode"; +import { + mockObject, + mockGlobalObject, + mockGlobalModule, + MockedObject, + mockFn, + instance, +} from "../../MockUtils"; +import { + DarwinCompatibleTarget, + SwiftToolchain, + getDarwinTargetTriple, +} from "../../../src/toolchain/toolchain"; +import { WorkspaceContext } from "../../../src/WorkspaceContext"; +import { switchPlatform } from "../../../src/commands/switchPlatform"; +import { StatusItem } from "../../../src/ui/StatusItem"; +import configuration from "../../../src/configuration"; + +suite("Switch Target Platform Unit Tests", () => { + const mockedConfiguration = mockGlobalModule(configuration); + const windowMock = mockGlobalObject(vscode, "window"); + const mockSwiftToolchain = mockGlobalModule(SwiftToolchain); + let mockContext: MockedObject; + let mockedStatusItem: MockedObject; + + setup(() => { + mockedStatusItem = mockObject({ + start: mockFn(), + end: mockFn(), + }); + mockContext = mockObject({ + statusItem: instance(mockedStatusItem), + }); + }); + + test("Call Switch Platform and switch to iOS", async () => { + const selectedItem = { value: DarwinCompatibleTarget.iOS, label: "iOS" }; + windowMock.showQuickPick.resolves(selectedItem); + mockSwiftToolchain.getSDKForTarget.resolves(""); + expect(mockedConfiguration.swiftSDK).to.equal(""); + + await switchPlatform(instance(mockContext)); + + expect(windowMock.showQuickPick).to.have.been.calledOnce; + expect(windowMock.showWarningMessage).to.have.been.calledOnceWithExactly( + "Selecting the iOS target platform will provide code editing support, but compiling with a iOS SDK will have undefined results." + ); + expect(mockedStatusItem.start).to.have.been.called; + expect(mockedStatusItem.end).to.have.been.called; + expect(mockedConfiguration.swiftSDK).to.equal( + getDarwinTargetTriple(DarwinCompatibleTarget.iOS) + ); + }); + + test("Call Switch Platform and switch to macOS", async () => { + const selectedItem = { value: undefined, label: "macOS" }; + windowMock.showQuickPick.resolves(selectedItem); + mockSwiftToolchain.getSDKForTarget.resolves(""); + expect(mockedConfiguration.swiftSDK).to.equal(""); + + await switchPlatform(instance(mockContext)); + + expect(windowMock.showQuickPick).to.have.been.calledOnce; + expect(windowMock.showWarningMessage).to.not.have.been.called; + expect(mockedStatusItem.start).to.have.been.called; + expect(mockedStatusItem.end).to.have.been.called; + expect(mockedConfiguration.swiftSDK).to.equal(""); + }); +}); From 41e8872b12a02d308e3fbcacbac5a3128db6ad26 Mon Sep 17 00:00:00 2001 From: Matthew Bastien Date: Fri, 14 Mar 2025 13:54:01 -0500 Subject: [PATCH 46/86] use xcode-select to determine the actual DEVELOPER_DIR value (#1433) --- src/ui/ToolchainSelection.ts | 52 +++++++++++++++++++++--------------- 1 file changed, 30 insertions(+), 22 deletions(-) diff --git a/src/ui/ToolchainSelection.ts b/src/ui/ToolchainSelection.ts index f21d8b94a..e2381f90d 100644 --- a/src/ui/ToolchainSelection.ts +++ b/src/ui/ToolchainSelection.ts @@ -63,7 +63,7 @@ export async function selectToolchainFolder() { if (!selected || selected.length !== 1) { return; } - await setToolchainPath(selected[0].fsPath, "public"); + await setToolchainPath(selected[0].fsPath); } /** @@ -275,15 +275,25 @@ export async function showToolchainSelectionQuickPick(activeToolchain: SwiftTool } if (selected?.type === "toolchain") { // Select an Xcode to build with - let developerDir: string | undefined; - if (selected.category === "xcode") { - developerDir = selected.xcodePath; - } else if (xcodePaths.length === 1) { - developerDir = xcodePaths[0]; - } else if (process.platform === "darwin" && xcodePaths.length > 1) { - developerDir = await showDeveloperDirQuickPick(xcodePaths); - if (!developerDir) { - return; + let developerDir: string | undefined = undefined; + if (process.platform === "darwin") { + let selectedXcodePath: string | undefined = undefined; + if (selected.category === "xcode") { + selectedXcodePath = selected.xcodePath; + } else if (xcodePaths.length === 1) { + selectedXcodePath = xcodePaths[0]; + } else if (xcodePaths.length > 1) { + selectedXcodePath = await showDeveloperDirQuickPick(xcodePaths); + if (!selectedXcodePath) { + return; + } + } + // Find the actual DEVELOPER_DIR based on the selected Xcode app + if (selectedXcodePath) { + developerDir = await SwiftToolchain.getXcodeDeveloperDir({ + ...process.env, + DEVELOPER_DIR: selectedXcodePath, + }); } } // Update the toolchain path @@ -376,7 +386,7 @@ async function removeToolchainPath() { */ async function setToolchainPath( swiftFolderPath: string | undefined, - developerDir: string | undefined + developerDir?: string ): Promise { let target: vscode.ConfigurationTarget | undefined; const items: (vscode.QuickPickItem & { @@ -410,17 +420,15 @@ async function setToolchainPath( } const swiftConfiguration = vscode.workspace.getConfiguration("swift"); await swiftConfiguration.update("path", swiftFolderPath, target); - if (developerDir) { - const swiftEnv = configuration.swiftEnvironmentVariables; - await swiftConfiguration.update( - "swiftEnvironmentVariables", - { - ...swiftEnv, - DEVELOPER_DIR: developerDir, - }, - target - ); - } + const swiftEnv = configuration.swiftEnvironmentVariables; + await swiftConfiguration.update( + "swiftEnvironmentVariables", + { + ...swiftEnv, + DEVELOPER_DIR: developerDir, + }, + target + ); await checkAndRemoveWorkspaceSetting(target); return true; } From 767fb08df7b99be7f42aa1d01ab5efe997b41826 Mon Sep 17 00:00:00 2001 From: Paul LeMarquand Date: Mon, 17 Mar 2025 15:29:34 -0400 Subject: [PATCH 47/86] Notify sourcekit-lsp of active document changes (#1404) `sourcekit-lsp` recently added an LSP extension method to notify the server of the active documnent, so it doesn't need to infer it from information in other requests. This capability was added in https://github.com/swiftlang/sourcekit-lsp/pull/1989. Co-authored-by: Alex Hoppen --- src/TestExplorer/LSPTestDiscovery.ts | 26 ++---- src/sourcekit-lsp/LanguageClientManager.ts | 45 ++++++++++ src/sourcekit-lsp/didChangeActiveDocument.ts | 75 ++++++++++++++++ .../DidChangeActiveDocumentRequest.ts | 34 ++++++++ .../LanguageClientManager.test.ts | 86 +++++++++++++++++++ 5 files changed, 246 insertions(+), 20 deletions(-) create mode 100644 src/sourcekit-lsp/didChangeActiveDocument.ts create mode 100644 src/sourcekit-lsp/extensions/DidChangeActiveDocumentRequest.ts diff --git a/src/TestExplorer/LSPTestDiscovery.ts b/src/TestExplorer/LSPTestDiscovery.ts index fa2ebf88f..b91517d91 100644 --- a/src/TestExplorer/LSPTestDiscovery.ts +++ b/src/TestExplorer/LSPTestDiscovery.ts @@ -20,7 +20,10 @@ import { WorkspaceTestsRequest, } from "../sourcekit-lsp/extensions"; import { SwiftPackage, TargetType } from "../SwiftPackage"; -import { LanguageClientManager } from "../sourcekit-lsp/LanguageClientManager"; +import { + checkExperimentalCapability, + LanguageClientManager, +} from "../sourcekit-lsp/LanguageClientManager"; import { LanguageClient } from "vscode-languageclient/node"; /** @@ -45,7 +48,7 @@ export class LSPTestDiscovery { return await this.languageClient.useLanguageClient(async (client, token) => { // Only use the lsp for this request if it supports the // textDocument/tests method, and is at least version 2. - if (this.checkExperimentalCapability(client, TextDocumentTestsRequest.method, 2)) { + if (checkExperimentalCapability(client, TextDocumentTestsRequest.method, 2)) { const testsInDocument = await client.sendRequest( TextDocumentTestsRequest.type, { textDocument: { uri: document.toString() } }, @@ -66,7 +69,7 @@ export class LSPTestDiscovery { return await this.languageClient.useLanguageClient(async (client, token) => { // Only use the lsp for this request if it supports the // workspace/tests method, and is at least version 2. - if (this.checkExperimentalCapability(client, WorkspaceTestsRequest.method, 2)) { + if (checkExperimentalCapability(client, WorkspaceTestsRequest.method, 2)) { const tests = await client.sendRequest(WorkspaceTestsRequest.type, token); return this.transformToTestClass(client, swiftPackage, tests); } else { @@ -75,23 +78,6 @@ export class LSPTestDiscovery { }); } - /** - * Returns `true` if the LSP supports the supplied `method` at or - * above the supplied `minVersion`. - */ - private checkExperimentalCapability( - client: LanguageClient, - method: string, - minVersion: number - ) { - const experimentalCapability = client.initializeResult?.capabilities.experimental; - if (!experimentalCapability) { - throw new Error(`${method} requests not supported`); - } - const targetCapability = experimentalCapability[method]; - return (targetCapability?.version ?? -1) >= minVersion; - } - /** * Convert from `LSPTestItem[]` to `TestDiscovery.TestClass[]`, * updating the format of the location. diff --git a/src/sourcekit-lsp/LanguageClientManager.ts b/src/sourcekit-lsp/LanguageClientManager.ts index a71b55754..cf2d69c40 100644 --- a/src/sourcekit-lsp/LanguageClientManager.ts +++ b/src/sourcekit-lsp/LanguageClientManager.ts @@ -47,6 +47,8 @@ import { activateGetReferenceDocument } from "./getReferenceDocument"; import { uriConverters } from "./uriConverters"; import { LanguageClientFactory } from "./LanguageClientFactory"; import { SourceKitLogMessageNotification, SourceKitLogMessageParams } from "./extensions"; +import { LSPActiveDocumentManager } from "./didChangeActiveDocument"; +import { DidChangeActiveDocumentNotification } from "./extensions/DidChangeActiveDocumentRequest"; /** * Manages the creation and destruction of Language clients as we move between @@ -136,6 +138,7 @@ export class LanguageClientManager implements vscode.Disposable { private legacyInlayHints?: vscode.Disposable; private peekDocuments?: vscode.Disposable; private getReferenceDocument?: vscode.Disposable; + private didChangeActiveDocument?: vscode.Disposable; private restartedPromise?: Promise; private currentWorkspaceFolder?: vscode.Uri; private waitingOnRestartCount: number; @@ -151,6 +154,7 @@ export class LanguageClientManager implements vscode.Disposable { public subFolderWorkspaces: vscode.Uri[]; private namedOutputChannels: Map = new Map(); private swiftVersion: Version; + private activeDocumentManager = new LSPActiveDocumentManager(); /** Get the current state of the underlying LanguageClient */ public get state(): State { @@ -534,6 +538,8 @@ export class LanguageClientManager implements vscode.Disposable { workspaceFolder: workspaceFolder, outputChannel: new SwiftOutputChannel("SourceKit Language Server"), middleware: { + didOpen: this.activeDocumentManager.didOpen.bind(this.activeDocumentManager), + didClose: this.activeDocumentManager.didClose.bind(this.activeDocumentManager), provideCodeLenses: async (document, token, next) => { const result = await next(document, token); return result?.map(codelens => { @@ -666,6 +672,13 @@ export class LanguageClientManager implements vscode.Disposable { }; } + if (this.swiftVersion.isGreaterThanOrEqual(new Version(6, 1, 0))) { + options = { + ...options, + "window/didChangeActiveDocument": true, // the client can send `window/didChangeActiveDocument` notifications + }; + } + if (configuration.swiftSDK !== "") { options = { ...options, @@ -715,6 +728,21 @@ export class LanguageClientManager implements vscode.Disposable { this.peekDocuments = activatePeekDocuments(client); this.getReferenceDocument = activateGetReferenceDocument(client); this.workspaceContext.subscriptions.push(this.getReferenceDocument); + try { + if ( + checkExperimentalCapability( + client, + DidChangeActiveDocumentNotification.method, + 1 + ) + ) { + this.didChangeActiveDocument = + this.activeDocumentManager.activateDidChangeActiveDocument(client); + this.workspaceContext.subscriptions.push(this.didChangeActiveDocument); + } + } catch { + // do nothing + } }) .catch(reason => { this.workspaceContext.outputChannel.log(`${reason}`); @@ -846,3 +874,20 @@ type SourceKitDocumentSelector = { scheme: string; language: string; }[]; + +/** + * Returns `true` if the LSP supports the supplied `method` at or + * above the supplied `minVersion`. + */ +export function checkExperimentalCapability( + client: LanguageClient, + method: string, + minVersion: number +) { + const experimentalCapability = client.initializeResult?.capabilities.experimental; + if (!experimentalCapability) { + throw new Error(`${method} requests not supported`); + } + const targetCapability = experimentalCapability[method]; + return (targetCapability?.version ?? -1) >= minVersion; +} diff --git a/src/sourcekit-lsp/didChangeActiveDocument.ts b/src/sourcekit-lsp/didChangeActiveDocument.ts new file mode 100644 index 000000000..dc26625b3 --- /dev/null +++ b/src/sourcekit-lsp/didChangeActiveDocument.ts @@ -0,0 +1,75 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the VS Code Swift open source project +// +// Copyright (c) 2025 the VS Code Swift project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of VS Code Swift project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import * as vscode from "vscode"; +import * as langclient from "vscode-languageclient/node"; +import { checkExperimentalCapability } from "./LanguageClientManager"; +import { DidChangeActiveDocumentNotification } from "./extensions/DidChangeActiveDocumentRequest"; + +/** + * Monitors the active document and notifies the LSP whenever it changes. + * Only sends notifications for documents that produce `textDocument/didOpen`/`textDocument/didClose` + * requests to the client. + */ +export class LSPActiveDocumentManager { + private openDocuments = new Set(); + private lastActiveDocument: langclient.TextDocumentIdentifier | null = null; + + // These are LSP middleware functions that listen for document open and close events. + public async didOpen( + document: vscode.TextDocument, + next: (data: vscode.TextDocument) => Promise + ) { + this.openDocuments.add(document.uri); + next(document); + } + + public async didClose( + document: vscode.TextDocument, + next: (data: vscode.TextDocument) => Promise + ) { + this.openDocuments.add(document.uri); + next(document); + } + + public activateDidChangeActiveDocument(client: langclient.LanguageClient): vscode.Disposable { + // Fire an inital notification on startup if there is an open document. + this.sendNotification(client, vscode.window.activeTextEditor?.document); + + // Listen for the active editor to change and send a notification. + return vscode.window.onDidChangeActiveTextEditor(event => { + this.sendNotification(client, event?.document); + }); + } + + private sendNotification( + client: langclient.LanguageClient, + document: vscode.TextDocument | undefined + ) { + if (checkExperimentalCapability(client, DidChangeActiveDocumentNotification.method, 1)) { + const textDocument = + document && this.openDocuments.has(document.uri) + ? client.code2ProtocolConverter.asTextDocumentIdentifier(document) + : null; + + // Avoid sending multiple identical notifications in a row. + if (textDocument !== this.lastActiveDocument) { + client.sendNotification(DidChangeActiveDocumentNotification.method, { + textDocument: textDocument, + }); + } + this.lastActiveDocument = textDocument; + } + } +} diff --git a/src/sourcekit-lsp/extensions/DidChangeActiveDocumentRequest.ts b/src/sourcekit-lsp/extensions/DidChangeActiveDocumentRequest.ts new file mode 100644 index 000000000..75189c5bd --- /dev/null +++ b/src/sourcekit-lsp/extensions/DidChangeActiveDocumentRequest.ts @@ -0,0 +1,34 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the VS Code Swift open source project +// +// Copyright (c) 2025 the VS Code Swift project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of VS Code Swift project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import { MessageDirection, NotificationType, TextDocumentIdentifier } from "vscode-languageclient"; + +// We use namespaces to store request information just like vscode-languageclient +/* eslint-disable @typescript-eslint/no-namespace */ + +export interface DidChangeActiveDocumentParams { + /** + * The document that is being displayed in the active editor. + */ + textDocument?: TextDocumentIdentifier; +} + +/** + * Notify the server that the active document has changed. + */ +export namespace DidChangeActiveDocumentNotification { + export const method = "window/didChangeActiveDocument" as const; + export const messageDirection: MessageDirection = MessageDirection.clientToServer; + export const type = new NotificationType(method); +} diff --git a/test/unit-tests/sourcekit-lsp/LanguageClientManager.test.ts b/test/unit-tests/sourcekit-lsp/LanguageClientManager.test.ts index a50da8e44..fc46eb3a7 100644 --- a/test/unit-tests/sourcekit-lsp/LanguageClientManager.test.ts +++ b/test/unit-tests/sourcekit-lsp/LanguageClientManager.test.ts @@ -43,6 +43,11 @@ import { LanguageClientManager } from "../../../src/sourcekit-lsp/LanguageClient import configuration from "../../../src/configuration"; import { FolderContext } from "../../../src/FolderContext"; import { LanguageClientFactory } from "../../../src/sourcekit-lsp/LanguageClientFactory"; +import { LSPActiveDocumentManager } from "../../../src/sourcekit-lsp/didChangeActiveDocument"; +import { + DidChangeActiveDocumentNotification, + DidChangeActiveDocumentParams, +} from "../../../src/sourcekit-lsp/extensions/DidChangeActiveDocumentRequest"; suite("LanguageClientManager Suite", () => { let languageClientFactoryMock: MockedObject; @@ -112,6 +117,7 @@ suite("LanguageClientManager Suite", () => { }); mockedConverter = mockObject({ asUri: mockFn(s => s.callsFake(uri => uri.fsPath)), + asTextDocumentIdentifier: mockFn(s => s.callsFake(doc => ({ uri: doc.uri.fsPath }))), }); changeStateEmitter = new AsyncEventEmitter(); languageClientMock = mockObject({ @@ -123,6 +129,15 @@ suite("LanguageClientManager Suite", () => { dispose: mockFn(), }) ), + initializeResult: { + capabilities: { + experimental: { + "window/didChangeActiveDocument": { + version: 1, + }, + }, + }, + }, start: mockFn(s => s.callsFake(async () => { const oldState = languageClientMock.state; @@ -422,6 +437,77 @@ suite("LanguageClientManager Suite", () => { ]); }); + suite("active document changes", () => { + const mockWindow = mockGlobalObject(vscode, "window"); + + setup(() => { + mockedWorkspace.swiftVersion = new Version(6, 1, 0); + }); + + test("Notifies when the active document changes", async () => { + const document: vscode.TextDocument = instance( + mockObject({ + uri: vscode.Uri.file("/folder1/file.swift"), + }) + ); + + let _listener: ((e: vscode.TextEditor | undefined) => any) | undefined; + mockWindow.onDidChangeActiveTextEditor.callsFake((listener, _2, _1) => { + _listener = listener; + return { dispose: () => {} }; + }); + + new LanguageClientManager(instance(mockedWorkspace), languageClientFactoryMock); + await waitForReturnedPromises(languageClientMock.start); + + const activeDocumentManager = new LSPActiveDocumentManager(); + activeDocumentManager.activateDidChangeActiveDocument(instance(languageClientMock)); + activeDocumentManager.didOpen(document, async () => {}); + + if (_listener) { + _listener(instance(mockObject({ document }))); + } + + expect(languageClientMock.sendNotification).to.have.been.calledOnceWith( + DidChangeActiveDocumentNotification.method, + { + textDocument: { + uri: "/folder1/file.swift", + }, + } as DidChangeActiveDocumentParams + ); + }); + + test("Notifies on startup with the active document", async () => { + const document: vscode.TextDocument = instance( + mockObject({ + uri: vscode.Uri.file("/folder1/file.swift"), + }) + ); + mockWindow.activeTextEditor = instance( + mockObject({ + document, + }) + ); + new LanguageClientManager(instance(mockedWorkspace), languageClientFactoryMock); + await waitForReturnedPromises(languageClientMock.start); + + const activeDocumentManager = new LSPActiveDocumentManager(); + activeDocumentManager.didOpen(document, async () => {}); + + activeDocumentManager.activateDidChangeActiveDocument(instance(languageClientMock)); + + expect(languageClientMock.sendNotification).to.have.been.calledOnceWith( + DidChangeActiveDocumentNotification.method, + { + textDocument: { + uri: "/folder1/file.swift", + }, + } as DidChangeActiveDocumentParams + ); + }); + }); + suite("SourceKit-LSP version doesn't support workspace folders", () => { let folder1: MockedObject; let folder2: MockedObject; From 2735bc3f010777cc0466de32139e9aaa89297475 Mon Sep 17 00:00:00 2001 From: Paul LeMarquand Date: Mon, 17 Mar 2025 16:55:59 -0400 Subject: [PATCH 48/86] Use FileWatcher for background compilation (#1431) * Use FileWatcher for background compilation This `swift.backgroundCompilation` setting listened for file save events from VS Code and triggered the build task. This meant that only modifications made by saving in a VS Code editor would trigger background compilation. Switch to using a `vscode.FileWatcher` so background compilation still gets kicked off even if a file is modified from outside VS Code. This is useful if you have a command plugin that modifies source files like swift-format. Issue: #1274 --- package-lock.json | 32 ++++++++++++ package.json | 2 + src/BackgroundCompilation.ts | 94 +++++++++++++++++++----------------- src/FolderContext.ts | 1 + src/WorkspaceContext.ts | 3 -- 5 files changed, 84 insertions(+), 48 deletions(-) diff --git a/package-lock.json b/package-lock.json index dd60b2bb7..77c81c417 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,6 +21,7 @@ "@types/chai-subset": "^1.3.6", "@types/glob": "^7.1.6", "@types/lcov-parse": "^1.0.2", + "@types/lodash.debounce": "^4.0.9", "@types/lodash.throttle": "^4.1.9", "@types/mocha": "^10.0.10", "@types/mock-fs": "^4.13.4", @@ -44,6 +45,7 @@ "esbuild": "^0.25.1", "eslint": "^8.57.0", "eslint-config-prettier": "^10.1.1", + "lodash.debounce": "^4.0.8", "lodash.throttle": "^4.1.1", "mocha": "^10.8.2", "mock-fs": "^5.5.0", @@ -1115,6 +1117,15 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/lodash.debounce": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/lodash.debounce/-/lodash.debounce-4.0.9.tgz", + "integrity": "sha512-Ma5JcgTREwpLRwMM+XwBR7DaWe96nC38uCBDFKZWbNKD+osjVzdpnUSwBcqCptrp16sSOLBAUb50Car5I0TCsQ==", + "dev": true, + "dependencies": { + "@types/lodash": "*" + } + }, "node_modules/@types/lodash.throttle": { "version": "4.1.9", "resolved": "https://registry.npmjs.org/@types/lodash.throttle/-/lodash.throttle-4.1.9.tgz", @@ -4064,6 +4075,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "dev": true + }, "node_modules/lodash.get": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", @@ -6809,6 +6826,15 @@ "integrity": "sha512-w/P33JFeySuhN6JLkysYUK2gEmy9kHHFN7E8ro0tkfmlDOgxBDzWEZ/J8cWA+fHqFevpswDTFZnDx+R9lbL6xw==", "dev": true }, + "@types/lodash.debounce": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/lodash.debounce/-/lodash.debounce-4.0.9.tgz", + "integrity": "sha512-Ma5JcgTREwpLRwMM+XwBR7DaWe96nC38uCBDFKZWbNKD+osjVzdpnUSwBcqCptrp16sSOLBAUb50Car5I0TCsQ==", + "dev": true, + "requires": { + "@types/lodash": "*" + } + }, "@types/lodash.throttle": { "version": "4.1.9", "resolved": "https://registry.npmjs.org/@types/lodash.throttle/-/lodash.throttle-4.1.9.tgz", @@ -8945,6 +8971,12 @@ "p-locate": "^5.0.0" } }, + "lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "dev": true + }, "lodash.get": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", diff --git a/package.json b/package.json index 8d1668258..25bf3bf52 100644 --- a/package.json +++ b/package.json @@ -1648,6 +1648,7 @@ "@types/glob": "^7.1.6", "@types/lcov-parse": "^1.0.2", "@types/lodash.throttle": "^4.1.9", + "@types/lodash.debounce": "^4.0.9", "@types/mocha": "^10.0.10", "@types/mock-fs": "^4.13.4", "@types/node": "^18.19.80", @@ -1671,6 +1672,7 @@ "eslint": "^8.57.0", "eslint-config-prettier": "^10.1.1", "lodash.throttle": "^4.1.1", + "lodash.debounce": "^4.0.8", "mocha": "^10.8.2", "mock-fs": "^5.5.0", "node-pty": "^1.0.0", diff --git a/src/BackgroundCompilation.ts b/src/BackgroundCompilation.ts index 583788b08..f54f48534 100644 --- a/src/BackgroundCompilation.ts +++ b/src/BackgroundCompilation.ts @@ -13,63 +13,67 @@ //===----------------------------------------------------------------------===// import * as vscode from "vscode"; -import * as path from "path"; -import { isPathInsidePath } from "./utilities/filesystem"; import { getBuildAllTask } from "./tasks/SwiftTaskProvider"; import configuration from "./configuration"; import { FolderContext } from "./FolderContext"; -import { WorkspaceContext } from "./WorkspaceContext"; import { TaskOperation } from "./tasks/TaskQueue"; +// eslint-disable-next-line @typescript-eslint/no-require-imports +import debounce = require("lodash.debounce"); -export class BackgroundCompilation { - private waitingToRun = false; +export class BackgroundCompilation implements vscode.Disposable { + private workspaceFileWatcher?: vscode.FileSystemWatcher; + private configurationEventDisposable?: vscode.Disposable; + private validFileTypes = ["swift", "c", "cpp", "h", "hpp", "m", "mm"]; + private disposables: vscode.Disposable[] = []; - constructor(private folderContext: FolderContext) {} - - /** - * Start onDidSave handler which will kick off compilation tasks - * - * The task works out which folder the saved file is in and then - * will call `runTask` on the background compilation attached to - * that folder. - * */ - static start(workspaceContext: WorkspaceContext): vscode.Disposable { - const onDidSaveDocument = vscode.workspace.onDidSaveTextDocument(event => { - if (configuration.backgroundCompilation === false) { - return; - } - - // is document a valid type for rebuild - const languages = ["swift", "c", "cpp", "objective-c", "objective-cpp"]; - let foundLanguage = false; - languages.forEach(lang => { - if (event.languageId === lang) { - foundLanguage = true; + constructor(private folderContext: FolderContext) { + // We only want to configure the file watcher if background compilation is enabled. + this.configurationEventDisposable = vscode.workspace.onDidChangeConfiguration(event => { + if (event.affectsConfiguration("swift.backgroundCompilation", folderContext.folder)) { + if (configuration.backgroundCompilation) { + this.setupFileWatching(); + } else { + this.stopFileWatching(); } - }); - if (foundLanguage === false) { - return; } + }); + + if (configuration.backgroundCompilation) { + this.setupFileWatching(); + } + } - // is editor document in any of the current FolderContexts - const folderContext = workspaceContext.folders.find(context => { - return isPathInsidePath(event.uri.fsPath, context.folder.fsPath); - }); + private setupFileWatching() { + const fileTypes = this.validFileTypes.join(","); + const rootFolders = ["Sources", "Tests", "Snippets", "Plugins"].join(","); + this.disposables.push( + (this.workspaceFileWatcher = vscode.workspace.createFileSystemWatcher( + `**/{${rootFolders}}/**/*.{${fileTypes}}` + )) + ); - if (!folderContext) { - return; - } + // Throttle events since many change events can be recieved in a short time if the user + // does a "Save All" or a process writes several files in quick succession. + this.disposables.push( + this.workspaceFileWatcher.onDidChange( + debounce( + () => { + this.runTask(); + }, + 100 /* 10 times per second */, + { trailing: true } + ) + ) + ); + } - // don't run auto-build if saving Package.swift as it clashes with the resolve - // that is run after the Package.swift is saved - if (path.join(folderContext.folder.fsPath, "Package.swift") === event.uri.fsPath) { - return; - } + private stopFileWatching() { + this.disposables.forEach(disposable => disposable.dispose()); + } - // run background compilation task - folderContext.backgroundCompilation.runTask(); - }); - return { dispose: () => onDidSaveDocument.dispose() }; + dispose() { + this.configurationEventDisposable?.dispose(); + this.disposables.forEach(disposable => disposable.dispose()); } /** diff --git a/src/FolderContext.ts b/src/FolderContext.ts index 422cb182c..d1b70e327 100644 --- a/src/FolderContext.ts +++ b/src/FolderContext.ts @@ -54,6 +54,7 @@ export class FolderContext implements vscode.Disposable { this.linuxMain?.dispose(); this.packageWatcher.dispose(); this.testExplorer?.dispose(); + this.backgroundCompilation.dispose(); } /** diff --git a/src/WorkspaceContext.ts b/src/WorkspaceContext.ts index 4095593d7..ed5d26b8c 100644 --- a/src/WorkspaceContext.ts +++ b/src/WorkspaceContext.ts @@ -22,7 +22,6 @@ import { isPathInsidePath } from "./utilities/filesystem"; import { LanguageClientManager } from "./sourcekit-lsp/LanguageClientManager"; import { TemporaryFolder } from "./utilities/tempFolder"; import { TaskManager } from "./tasks/TaskManager"; -import { BackgroundCompilation } from "./BackgroundCompilation"; import { makeDebugConfigurations } from "./debugger/launch"; import configuration from "./configuration"; import contextKeys from "./contextKeys"; @@ -121,7 +120,6 @@ export class WorkspaceContext implements vscode.Disposable { }); } }); - const backgroundCompilationOnDidSave = BackgroundCompilation.start(this); const contextKeysUpdate = this.onDidChangeFolders(event => { switch (event.operation) { case FolderOperation.remove: @@ -174,7 +172,6 @@ export class WorkspaceContext implements vscode.Disposable { swiftFileWatcher, onDidEndTask, this.commentCompletionProvider, - backgroundCompilationOnDidSave, contextKeysUpdate, onChangeConfig, this.tasks, From 0fc9512e247b9cf4099bb1a80723226436cb7509 Mon Sep 17 00:00:00 2001 From: Rishi Date: Tue, 18 Mar 2025 10:20:13 -0400 Subject: [PATCH 49/86] Update diagnostics parsing to handle new warnings (#1440) Starting with Swift 6.2 some warnings may end with a tag such as [#no-usage]. Update parsing logic to remove these tags from the warnings to ensure we can combine them with the warnings from SourceKit. Issue: #1434 --- src/DiagnosticsManager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DiagnosticsManager.ts b/src/DiagnosticsManager.ts index e2ab4570d..c087f777d 100644 --- a/src/DiagnosticsManager.ts +++ b/src/DiagnosticsManager.ts @@ -374,7 +374,7 @@ export class DiagnosticsManager implements vscode.Disposable { ): ParsedDiagnostic | vscode.DiagnosticRelatedInformation | undefined { const diagnosticRegex = /^(?:\S+\s+)?(.*?):(\d+)(?::(\d+))?:\s+(warning|error|note):\s+(.*)$/g; - const switfcExtraWarningsRegex = /\[-W.*?\]/g; + const switfcExtraWarningsRegex = /\[(-W|#).*?\]/g; const match = diagnosticRegex.exec(line); if (!match) { return; From 5bb7a2d34d93ee04e00c04d96599bbfd4ca0d6f3 Mon Sep 17 00:00:00 2001 From: Priyambada Roul <75459506+roulpriya@users.noreply.github.com> Date: Tue, 18 Mar 2025 23:48:36 +0530 Subject: [PATCH 50/86] Issue vscode-swift#1173:Update NodeJs 20 and vsce 3 (#1436) --- .devcontainer/Dockerfile | 2 +- .github/workflows/nightly.yml | 12 +- .github/workflows/pull_request.yml | 4 +- .github/workflows/scripts/setup-linux.sh | 4 +- .../scripts/windows/install-nodejs.ps1 | 2 +- .nvmrc | 2 +- package-lock.json | 368 +++++++++++++----- package.json | 4 +- scripts/update_swift_docc_render.ts | 4 +- 9 files changed, 289 insertions(+), 113 deletions(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 5821317c3..a3778250a 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -13,7 +13,7 @@ RUN export DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true \ RUN mkdir -p /usr/local/nvm ENV NVM_DIR /usr/local/nvm -ENV NODE_VERSION v18.19.0 +ENV NODE_VERSION v20.18.2 RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash RUN /bin/bash -c "source $NVM_DIR/nvm.sh && nvm install $NODE_VERSION" ENV NODE_PATH $NVM_DIR/versions/node/$NODE_VERSION/bin diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 9f22338bd..cdde23e48 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -16,8 +16,8 @@ jobs: uses: actions/checkout@v4 - name: Build Extension run: | - export NODE_VERSION=v18.19.0 - export NODE_PATH=/usr/local/nvm/versions/node/v18.19.0/bin + export NODE_VERSION=v20.18.2 + export NODE_PATH=/usr/local/nvm/versions/node/v20.18.2/bin export NVM_DIR=/usr/local/nvm . .github/workflows/scripts/setup-linux.sh [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" @@ -37,8 +37,8 @@ jobs: with: # Linux linux_env_vars: | - NODE_VERSION=v18.19.0 - NODE_PATH=/usr/local/nvm/versions/node/v18.19.0/bin + NODE_VERSION=v20.18.2 + NODE_PATH=/usr/local/nvm/versions/node/v20.18.2/bin NVM_DIR=/usr/local/nvm CI=1 linux_pre_build_command: . .github/workflows/scripts/setup-linux.sh @@ -58,8 +58,8 @@ jobs: # Linux linux_exclude_swift_versions: '[{"swift_version": "5.8"}, {"swift_version": "5.9"}, {"swift_version": "5.10"}, {"swift_version": "nightly-6.1"}, {"swift_version": "nightly-main"}]' linux_env_vars: | - NODE_VERSION=v18.19.0 - NODE_PATH=/usr/local/nvm/versions/node/v18.19.0/bin + NODE_VERSION=v20.18.2 + NODE_PATH=/usr/local/nvm/versions/node/v20.18.2/bin NVM_DIR=/usr/local/nvm CI=1 VSCODE_TEST=1 diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index f9e673fee..f482d7643 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -12,8 +12,8 @@ jobs: # Linux linux_exclude_swift_versions: '[{"swift_version": "nightly-6.1"},{"swift_version": "nightly-main"}]' linux_env_vars: | - NODE_VERSION=v18.19.0 - NODE_PATH=/usr/local/nvm/versions/node/v18.19.0/bin + NODE_VERSION=v20.18.2 + NODE_PATH=/usr/local/nvm/versions/node/v20.18.2/bin NVM_DIR=/usr/local/nvm CI=1 VSCODE_TEST=1 diff --git a/.github/workflows/scripts/setup-linux.sh b/.github/workflows/scripts/setup-linux.sh index 82e70d523..6d660d94e 100755 --- a/.github/workflows/scripts/setup-linux.sh +++ b/.github/workflows/scripts/setup-linux.sh @@ -13,8 +13,8 @@ ## ##===----------------------------------------------------------------------===## -export NODE_VERSION=v18.19.0 -export NODE_PATH=/usr/local/nvm/versions/node/v18.19.0/bin +export NODE_VERSION=v20.18.2 +export NODE_PATH=/usr/local/nvm/versions/node/v20.18.2/bin export NVM_DIR=/usr/local/nvm apt-get update && apt-get install -y rsync curl gpg libasound2 libgbm1 libgtk-3-0 libnss3 xvfb build-essential diff --git a/.github/workflows/scripts/windows/install-nodejs.ps1 b/.github/workflows/scripts/windows/install-nodejs.ps1 index 2105d63ad..f314cc473 100644 --- a/.github/workflows/scripts/windows/install-nodejs.ps1 +++ b/.github/workflows/scripts/windows/install-nodejs.ps1 @@ -1,4 +1,4 @@ -$NODEJS='https://nodejs.org/dist/v18.20.4/node-v18.20.4-x64.msi' +$NODEJS='https://nodejs.org/dist/v20.18.2/node-v20.18.2-x64.msi' $NODEJS_SHA256='c2654d3557abd59de08474c6dd009b1d358f420b8e4010e4debbf130b1dfb90a' Set-Variable ErrorActionPreference Stop Set-Variable ProgressPreference SilentlyContinue diff --git a/.nvmrc b/.nvmrc index 436d5c5d1..6263619f7 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -18.19.0 \ No newline at end of file +20.18.2 \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 77c81c417..3538814c0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,7 +25,7 @@ "@types/lodash.throttle": "^4.1.9", "@types/mocha": "^10.0.10", "@types/mock-fs": "^4.13.4", - "@types/node": "^18.19.80", + "@types/node": "^20.0.0", "@types/plist": "^3.0.5", "@types/semver": "^7.5.8", "@types/sinon": "^17.0.4", @@ -37,7 +37,7 @@ "@vscode/debugprotocol": "^1.68.0", "@vscode/test-cli": "^0.0.10", "@vscode/test-electron": "^2.4.1", - "@vscode/vsce": "^2.32.0", + "@vscode/vsce": "^3.0.0", "chai": "^4.5.0", "chai-as-promised": "^7.1.2", "chai-subset": "^1.6.0", @@ -1159,13 +1159,13 @@ } }, "node_modules/@types/node": { - "version": "18.19.80", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.80.tgz", - "integrity": "sha512-kEWeMwMeIvxYkeg1gTc01awpwLbfMRZXdIhwRcakd/KlK53jmRC26LqcbIt7fnAQTu5GzlnWmzA3H6+l1u6xxQ==", + "version": "20.17.24", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.24.tgz", + "integrity": "sha512-d7fGCyB96w9BnWQrOsJtpyiSaBcAYYr75bnK6ZRjDbql2cGLj/3GsL5OYmLPNq76l7Gf2q4Rv9J2o6h5CrD9sA==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~5.26.4" + "undici-types": "~6.19.2" } }, "node_modules/@types/plist": { @@ -1686,10 +1686,11 @@ } }, "node_modules/@vscode/vsce": { - "version": "2.32.0", - "resolved": "https://registry.npmjs.org/@vscode/vsce/-/vsce-2.32.0.tgz", - "integrity": "sha512-3EFJfsgrSftIqt3EtdRcAygy/OJ3hstyI1cDmIgkU9CFZW5C+3djr6mfosndCUqcVYuyjmxOK1xmFp/Bq7+NIg==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@vscode/vsce/-/vsce-3.2.2.tgz", + "integrity": "sha512-4TqdUq/yKlQTHcQMk/DamR632bq/+IJDomSbexOMee/UAYWqYm0XHWA6scGslsCpzY+sCWEhhl0nqdOB0XW1kw==", "dev": true, + "license": "MIT", "dependencies": { "@azure/identity": "^4.1.0", "@vscode/vsce-sign": "^2.0.0", @@ -1697,19 +1698,19 @@ "chalk": "^2.4.2", "cheerio": "^1.0.0-rc.9", "cockatiel": "^3.1.2", - "commander": "^6.2.1", + "commander": "^12.1.0", "form-data": "^4.0.0", - "glob": "^7.0.6", + "glob": "^11.0.0", "hosted-git-info": "^4.0.2", "jsonc-parser": "^3.2.0", "leven": "^3.1.0", - "markdown-it": "^12.3.2", + "markdown-it": "^14.1.0", "mime": "^1.3.4", "minimatch": "^3.0.3", "parse-semver": "^1.1.1", "read": "^1.0.7", "semver": "^7.5.2", - "tmp": "^0.2.1", + "tmp": "^0.2.3", "typed-rest-client": "^1.8.4", "url-join": "^4.0.1", "xml2js": "^0.5.0", @@ -1720,7 +1721,7 @@ "vsce": "vsce" }, "engines": { - "node": ">= 16" + "node": ">= 20" }, "optionalDependencies": { "keytar": "^7.7.0" @@ -1873,6 +1874,16 @@ "node": ">=4" } }, + "node_modules/@vscode/vsce/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, "node_modules/@vscode/vsce/node_modules/chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -1911,6 +1922,46 @@ "node": ">=0.8.0" } }, + "node_modules/@vscode/vsce/node_modules/glob": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.1.tgz", + "integrity": "sha512-zrQDm8XPnYEKawJScsnM0QzobJxlT/kHOOlRTio8IH/GrmxRE5fjllkzdaHclIuNjUQTJYH2xHNIGfdpJkDJUw==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^4.0.1", + "minimatch": "^10.0.0", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@vscode/vsce/node_modules/glob/node_modules/minimatch": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", + "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@vscode/vsce/node_modules/has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -1920,6 +1971,49 @@ "node": ">=4" } }, + "node_modules/@vscode/vsce/node_modules/jackspeak": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.0.tgz", + "integrity": "sha512-9DDdhb5j6cpeitCbvLO7n7J4IxnbM6hoF6O1g4HQ5TfhvvKN8ywDM7668ZhMHRqVmxqhps/F6syWK2KcPxYlkw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@vscode/vsce/node_modules/lru-cache": { + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.2.tgz", + "integrity": "sha512-123qHRfJBmo2jXDbo/a5YOQrJoHF/GNQTLzQ5+IdK5pWpceK17yRc6ozlWd25FxvGKQbIUs91fDFkXmDHTKcyA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@vscode/vsce/node_modules/path-scurry": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", + "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "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", @@ -2493,12 +2587,13 @@ } }, "node_modules/commander": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", - "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", "dev": true, + "license": "MIT", "engines": { - "node": ">= 6" + "node": ">=18" } }, "node_modules/concat-map": { @@ -4052,12 +4147,13 @@ } }, "node_modules/linkify-it": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", - "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", "dev": true, + "license": "MIT", "dependencies": { - "uc.micro": "^1.0.1" + "uc.micro": "^2.0.0" } }, "node_modules/locate-path": { @@ -4197,35 +4293,42 @@ } }, "node_modules/markdown-it": { - "version": "12.3.2", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", - "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==", + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", + "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", "dev": true, + "license": "MIT", "dependencies": { "argparse": "^2.0.1", - "entities": "~2.1.0", - "linkify-it": "^3.0.1", - "mdurl": "^1.0.1", - "uc.micro": "^1.0.5" + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" }, "bin": { - "markdown-it": "bin/markdown-it.js" + "markdown-it": "bin/markdown-it.mjs" } }, "node_modules/markdown-it/node_modules/entities": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", - "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, "funding": { "url": "https://github.com/fb55/entities?sponsor=1" } }, "node_modules/mdurl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", - "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=", - "dev": true + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "dev": true, + "license": "MIT" }, "node_modules/meow": { "version": "13.2.0", @@ -5033,6 +5136,16 @@ "node": ">=6" } }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/qs": { "version": "6.12.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.12.0.tgz", @@ -5716,15 +5829,13 @@ "dev": true }, "node_modules/tmp": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", - "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", + "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==", "dev": true, - "dependencies": { - "rimraf": "^3.0.0" - }, + "license": "MIT", "engines": { - "node": ">=8.17.0" + "node": ">=14.14" } }, "node_modules/to-regex-range": { @@ -5860,10 +5971,11 @@ } }, "node_modules/uc.micro": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", - "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", - "dev": true + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "dev": true, + "license": "MIT" }, "node_modules/underscore": { "version": "1.13.6", @@ -5872,10 +5984,11 @@ "dev": true }, "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "dev": true + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "dev": true, + "license": "MIT" }, "node_modules/unicorn-magic": { "version": "0.1.0", @@ -6866,12 +6979,12 @@ } }, "@types/node": { - "version": "18.19.80", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.80.tgz", - "integrity": "sha512-kEWeMwMeIvxYkeg1gTc01awpwLbfMRZXdIhwRcakd/KlK53jmRC26LqcbIt7fnAQTu5GzlnWmzA3H6+l1u6xxQ==", + "version": "20.17.24", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.24.tgz", + "integrity": "sha512-d7fGCyB96w9BnWQrOsJtpyiSaBcAYYr75bnK6ZRjDbql2cGLj/3GsL5OYmLPNq76l7Gf2q4Rv9J2o6h5CrD9sA==", "dev": true, "requires": { - "undici-types": "~5.26.4" + "undici-types": "~6.19.2" } }, "@types/plist": { @@ -7239,9 +7352,9 @@ } }, "@vscode/vsce": { - "version": "2.32.0", - "resolved": "https://registry.npmjs.org/@vscode/vsce/-/vsce-2.32.0.tgz", - "integrity": "sha512-3EFJfsgrSftIqt3EtdRcAygy/OJ3hstyI1cDmIgkU9CFZW5C+3djr6mfosndCUqcVYuyjmxOK1xmFp/Bq7+NIg==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@vscode/vsce/-/vsce-3.2.2.tgz", + "integrity": "sha512-4TqdUq/yKlQTHcQMk/DamR632bq/+IJDomSbexOMee/UAYWqYm0XHWA6scGslsCpzY+sCWEhhl0nqdOB0XW1kw==", "dev": true, "requires": { "@azure/identity": "^4.1.0", @@ -7250,20 +7363,20 @@ "chalk": "^2.4.2", "cheerio": "^1.0.0-rc.9", "cockatiel": "^3.1.2", - "commander": "^6.2.1", + "commander": "^12.1.0", "form-data": "^4.0.0", - "glob": "^7.0.6", + "glob": "^11.0.0", "hosted-git-info": "^4.0.2", "jsonc-parser": "^3.2.0", "keytar": "^7.7.0", "leven": "^3.1.0", - "markdown-it": "^12.3.2", + "markdown-it": "^14.1.0", "mime": "^1.3.4", "minimatch": "^3.0.3", "parse-semver": "^1.1.1", "read": "^1.0.7", "semver": "^7.5.2", - "tmp": "^0.2.1", + "tmp": "^0.2.3", "typed-rest-client": "^1.8.4", "url-join": "^4.0.1", "xml2js": "^0.5.0", @@ -7280,6 +7393,15 @@ "color-convert": "^1.9.0" } }, + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, "chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -7312,12 +7434,62 @@ "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", "dev": true }, + "glob": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.1.tgz", + "integrity": "sha512-zrQDm8XPnYEKawJScsnM0QzobJxlT/kHOOlRTio8IH/GrmxRE5fjllkzdaHclIuNjUQTJYH2xHNIGfdpJkDJUw==", + "dev": true, + "requires": { + "foreground-child": "^3.1.0", + "jackspeak": "^4.0.1", + "minimatch": "^10.0.0", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + }, + "dependencies": { + "minimatch": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", + "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + } + } + }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", "dev": true }, + "jackspeak": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.0.tgz", + "integrity": "sha512-9DDdhb5j6cpeitCbvLO7n7J4IxnbM6hoF6O1g4HQ5TfhvvKN8ywDM7668ZhMHRqVmxqhps/F6syWK2KcPxYlkw==", + "dev": true, + "requires": { + "@isaacs/cliui": "^8.0.2" + } + }, + "lru-cache": { + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.2.tgz", + "integrity": "sha512-123qHRfJBmo2jXDbo/a5YOQrJoHF/GNQTLzQ5+IdK5pWpceK17yRc6ozlWd25FxvGKQbIUs91fDFkXmDHTKcyA==", + "dev": true + }, + "path-scurry": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", + "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", + "dev": true, + "requires": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + } + }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -7812,9 +7984,9 @@ } }, "commander": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", - "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", "dev": true }, "concat-map": { @@ -8954,12 +9126,12 @@ } }, "linkify-it": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", - "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", "dev": true, "requires": { - "uc.micro": "^1.0.1" + "uc.micro": "^2.0.0" } }, "locate-path": { @@ -9075,30 +9247,31 @@ } }, "markdown-it": { - "version": "12.3.2", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", - "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==", + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", + "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", "dev": true, "requires": { "argparse": "^2.0.1", - "entities": "~2.1.0", - "linkify-it": "^3.0.1", - "mdurl": "^1.0.1", - "uc.micro": "^1.0.5" + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" }, "dependencies": { "entities": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", - "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", "dev": true } } }, "mdurl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", - "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", "dev": true }, "meow": { @@ -9691,6 +9864,12 @@ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true }, + "punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "dev": true + }, "qs": { "version": "6.12.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.12.0.tgz", @@ -10164,13 +10343,10 @@ "dev": true }, "tmp": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", - "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", - "dev": true, - "requires": { - "rimraf": "^3.0.0" - } + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", + "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==", + "dev": true }, "to-regex-range": { "version": "5.0.1", @@ -10260,9 +10436,9 @@ "dev": true }, "uc.micro": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", - "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", "dev": true }, "underscore": { @@ -10272,9 +10448,9 @@ "dev": true }, "undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", "dev": true }, "unicorn-magic": { diff --git a/package.json b/package.json index 25bf3bf52..e854648f1 100644 --- a/package.json +++ b/package.json @@ -1651,7 +1651,7 @@ "@types/lodash.debounce": "^4.0.9", "@types/mocha": "^10.0.10", "@types/mock-fs": "^4.13.4", - "@types/node": "^18.19.80", + "@types/node": "^20.0.0", "@types/plist": "^3.0.5", "@types/semver": "^7.5.8", "@types/sinon": "^17.0.4", @@ -1663,7 +1663,7 @@ "@vscode/debugprotocol": "^1.68.0", "@vscode/test-cli": "^0.0.10", "@vscode/test-electron": "^2.4.1", - "@vscode/vsce": "^2.32.0", + "@vscode/vsce": "^3.0.0", "chai": "^4.5.0", "chai-as-promised": "^7.1.2", "chai-subset": "^1.6.0", diff --git a/scripts/update_swift_docc_render.ts b/scripts/update_swift_docc_render.ts index 689f0dc9d..accb306b6 100644 --- a/scripts/update_swift_docc_render.ts +++ b/scripts/update_swift_docc_render.ts @@ -27,9 +27,9 @@ function checkNodeVersion() { "Unable to determine the version of NodeJS that this script is running under." ); } - if (!semver.satisfies(nodeVersion, "18")) { + if (!semver.satisfies(nodeVersion, "20")) { throw new Error( - `Cannot build swift-docc-render with NodeJS v${nodeVersion.raw}. Please install and use NodeJS v18.` + `Cannot build swift-docc-render with NodeJS v${nodeVersion.raw}. Please install and use NodeJS v20.` ); } } From 1bd8acc1498ac6a1c255e1f5345d4c669921f4db Mon Sep 17 00:00:00 2001 From: Paul LeMarquand Date: Tue, 18 Mar 2025 16:36:37 -0400 Subject: [PATCH 51/86] Resolve settings variables in Swift settings (#1439) VS Code defines several predefined variables that can be substituted in task and launch configurations. It is useful to have some of these defined for use in Swift settings, specifically the ones that are comprised of paths. This patch introduces a limited list of variables that are substituted in some settings. The variables supported are: - `${workspaceFolder}` - `${workspaceFolderBasename}` - `${cwd}` - `${userHome}` - `${pathSeparator}` The settings that support variable subsitution are: - `${swift.path}` - `${swift.runtimePath}` - `${swift.sdk}` - `${swift.buildArguments}` - `${swift.packageArguments}` - `${swift.buildPath}` - `${swift.serverArguments}` - `${swift.additionalTestArguments}` - `${swift.attachmentsPath}` - `${swift.debugger.customDebugAdapterPath}` - `${swift.excludeFromCodeCoverage}` - `${swift.sourcekit-lsp.serverPath}` Issue: #1438 --- src/configuration.ts | 92 ++++++++++++++++---- test/integration-tests/configuration.test.ts | 64 ++++++++++++++ 2 files changed, 140 insertions(+), 16 deletions(-) create mode 100644 test/integration-tests/configuration.test.ts diff --git a/src/configuration.ts b/src/configuration.ts index 5397071b8..e4bf5eb30 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -13,6 +13,8 @@ //===----------------------------------------------------------------------===// import * as vscode from "vscode"; +import * as os from "os"; +import * as path from "path"; export type DebugAdapters = "auto" | "lldb-dap" | "CodeLLDB"; export type SetupCodeLLDBOptions = @@ -106,14 +108,17 @@ const configuration = { get lsp(): LSPConfiguration { return { get serverPath(): string { - return vscode.workspace - .getConfiguration("swift.sourcekit-lsp") - .get("serverPath", ""); + return substituteVariablesInString( + vscode.workspace + .getConfiguration("swift.sourcekit-lsp") + .get("serverPath", "") + ); }, get serverArguments(): string[] { return vscode.workspace .getConfiguration("swift.sourcekit-lsp") - .get("serverArguments", []); + .get("serverArguments", []) + .map(substituteVariablesInString); }, get inlayHintsEnabled(): boolean { return vscode.workspace @@ -192,7 +197,8 @@ const configuration = { get additionalTestArguments(): string[] { return vscode.workspace .getConfiguration("swift", workspaceFolder) - .get("additionalTestArguments", []); + .get("additionalTestArguments", []) + .map(substituteVariablesInString); }, /** auto-generate launch.json configurations */ get autoGenerateLaunchConfigurations(): boolean { @@ -213,9 +219,11 @@ const configuration = { .get("searchSubfoldersForPackages", false); }, get attachmentsPath(): string { - return vscode.workspace - .getConfiguration("swift", workspaceFolder) - .get("attachmentsPath", "./.build/attachments"); + return substituteVariablesInString( + vscode.workspace + .getConfiguration("swift", workspaceFolder) + .get("attachmentsPath", "./.build/attachments") + ); }, pluginPermissions(pluginId?: string): PluginPermissionConfiguration { return pluginSetting("pluginPermissions", pluginId, false) ?? {}; @@ -256,7 +264,9 @@ const configuration = { } }, get customDebugAdapterPath(): string { - return vscode.workspace.getConfiguration("swift.debugger").get("path", ""); + return substituteVariablesInString( + vscode.workspace.getConfiguration("swift.debugger").get("path", "") + ); }, get disable(): boolean { return vscode.workspace @@ -274,7 +284,8 @@ const configuration = { get excludeFromCodeCoverage(): string[] { return vscode.workspace .getConfiguration("swift") - .get("excludeFromCodeCoverage", []); + .get("excludeFromCodeCoverage", []) + .map(substituteVariablesInString); }, /** Files and directories to exclude from the Package Dependencies view. */ get excludePathsFromPackageDependencies(): string[] { @@ -284,15 +295,21 @@ const configuration = { }, /** Path to folder that include swift executable */ get path(): string { - return vscode.workspace.getConfiguration("swift").get("path", ""); + return substituteVariablesInString( + vscode.workspace.getConfiguration("swift").get("path", "") + ); }, /** Path to folder that include swift runtime */ get runtimePath(): string { - return vscode.workspace.getConfiguration("swift").get("runtimePath", ""); + return substituteVariablesInString( + vscode.workspace.getConfiguration("swift").get("runtimePath", "") + ); }, /** Path to custom --sdk */ get sdk(): string { - return vscode.workspace.getConfiguration("swift").get("SDK", ""); + return substituteVariablesInString( + vscode.workspace.getConfiguration("swift").get("SDK", "") + ); }, set sdk(value: string | undefined) { vscode.workspace.getConfiguration("swift").update("SDK", value); @@ -306,18 +323,26 @@ const configuration = { }, /** swift build arguments */ get buildArguments(): string[] { - return vscode.workspace.getConfiguration("swift").get("buildArguments", []); + return vscode.workspace + .getConfiguration("swift") + .get("buildArguments", []) + .map(substituteVariablesInString); }, /** swift package arguments */ get packageArguments(): string[] { - return vscode.workspace.getConfiguration("swift").get("packageArguments", []); + return vscode.workspace + .getConfiguration("swift") + .get("packageArguments", []) + .map(substituteVariablesInString); }, /** thread/address sanitizer */ get sanitizer(): string { return vscode.workspace.getConfiguration("swift").get("sanitizer", "off"); }, get buildPath(): string { - return vscode.workspace.getConfiguration("swift").get("buildPath", ""); + return substituteVariablesInString( + vscode.workspace.getConfiguration("swift").get("buildPath", "") + ); }, get disableSwiftPMIntegration(): boolean { return vscode.workspace @@ -437,4 +462,39 @@ const configuration = { }, }; +const vsCodeVariableRegex = new RegExp(/\$\{(.+?)\}/g); +function substituteVariablesInString(val: string): string { + return val.replace(vsCodeVariableRegex, (substring: string, varName: string) => + typeof varName === "string" ? computeVscodeVar(varName) || substring : substring + ); +} + +function computeVscodeVar(varName: string): string | null { + const workspaceFolder = () => { + const activeEditor = vscode.window.activeTextEditor; + if (activeEditor) { + const documentUri = activeEditor.document.uri; + const folder = vscode.workspace.getWorkspaceFolder(documentUri); + if (folder) { + return folder.uri.fsPath; + } + } + + // If there is no active editor then return the first workspace folder + return vscode.workspace.workspaceFolders?.at(0)?.uri.fsPath ?? ""; + }; + + // https://code.visualstudio.com/docs/editor/variables-reference + // Variables to be substituted should be added here. + const supportedVariables: { [k: string]: () => string } = { + workspaceFolder, + workspaceFolderBasename: () => path.basename(workspaceFolder()), + cwd: () => process.cwd(), + userHome: () => os.homedir(), + pathSeparator: () => path.sep, + }; + + return varName in supportedVariables ? supportedVariables[varName]() : null; +} + export default configuration; diff --git a/test/integration-tests/configuration.test.ts b/test/integration-tests/configuration.test.ts new file mode 100644 index 000000000..6fa093d67 --- /dev/null +++ b/test/integration-tests/configuration.test.ts @@ -0,0 +1,64 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the VS Code Swift open source project +// +// Copyright (c) 2025 the VS Code Swift project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of VS Code Swift project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import * as vscode from "vscode"; +import * as path from "path"; +import { activateExtensionForSuite, updateSettings } from "./utilities/testutilities"; +import { expect } from "chai"; +import { afterEach } from "mocha"; +import configuration from "../../src/configuration"; +import { createBuildAllTask } from "../../src/tasks/SwiftTaskProvider"; +import { WorkspaceContext } from "../../src/WorkspaceContext"; + +suite("Configuration Test Suite", function () { + let workspaceContext: WorkspaceContext; + + activateExtensionForSuite({ + async setup(ctx) { + workspaceContext = ctx; + }, + }); + + let resetSettings: (() => Promise) | undefined; + afterEach(async () => { + if (resetSettings) { + await resetSettings(); + } + }); + + test("Should substitute variables in build task", async function () { + resetSettings = await updateSettings({ + "swift.buildPath": "${workspaceFolder}/somepath", + }); + + const task = createBuildAllTask(workspaceContext.folders[0], false); + expect(task).to.not.be.undefined; + expect(task.definition.args).to.not.be.undefined; + const index = task.definition.args.indexOf("--scratch-path"); + expect(task.definition.args[index + 1]).to.equal( + vscode.workspace.workspaceFolders?.at(0)?.uri.fsPath + "/somepath" + ); + }); + + test("Should substitute variables in configuration", async function () { + resetSettings = await updateSettings({ + "swift.buildPath": "${workspaceFolder}${pathSeparator}${workspaceFolderBasename}", + }); + + const basePath = vscode.workspace.workspaceFolders?.at(0)?.uri.fsPath; + const baseName = path.basename(basePath ?? ""); + const sep = path.sep; + expect(configuration.buildPath).to.equal(`${basePath}${sep}${baseName}`); + }); +}); From a4da9bf25707be300c9de4788679c7bef3795ce2 Mon Sep 17 00:00:00 2001 From: Paul LeMarquand Date: Tue, 18 Mar 2025 18:53:45 -0400 Subject: [PATCH 52/86] Swap expected and actual assertion values (#1444) The values of the error message in the VS Code UI is swapped from how they appear in XCTAssertEqual. Issue: #1441 --- src/TestExplorer/TestParsers/XCTestOutputParser.ts | 4 ++-- .../testexplorer/XCTestOutputParser.test.ts | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/TestExplorer/TestParsers/XCTestOutputParser.ts b/src/TestExplorer/TestParsers/XCTestOutputParser.ts index 4089e792f..ed6a7a861 100644 --- a/src/TestExplorer/TestParsers/XCTestOutputParser.ts +++ b/src/TestExplorer/TestParsers/XCTestOutputParser.ts @@ -462,8 +462,8 @@ export class XCTestOutputParser implements IXCTestOutputParser { const match = message.match(regex); if (match && match[1] !== match[2]) { return { - expected: match[1], - actual: match[2], + actual: match[1], + expected: match[2], }; } diff --git a/test/integration-tests/testexplorer/XCTestOutputParser.test.ts b/test/integration-tests/testexplorer/XCTestOutputParser.test.ts index 4f1fe0737..2b2342f4c 100644 --- a/test/integration-tests/testexplorer/XCTestOutputParser.test.ts +++ b/test/integration-tests/testexplorer/XCTestOutputParser.test.ts @@ -165,8 +165,8 @@ Test Case '-[MyTests.MyTests testFail]' failed (0.106 seconds). ), isKnown: false, diff: { - expected: '"1"', - actual: '"2"', + actual: '"1"', + expected: '"2"', }, }, ], @@ -485,7 +485,7 @@ Test Suite 'Selected tests' failed at 2024-10-20 22:01:46.306. }); suite("Diffs", () => { - const testRun = (message: string, expected?: string, actual?: string) => { + const testRun = (message: string, actual?: string, expected?: string) => { const input = `Test Case '-[MyTests.MyTests testFail]' started. /Users/user/Developer/MyTests/MyTests.swift:59: error: -[MyTests.MyTests testFail] : ${message} Test Case '-[MyTests.MyTests testFail]' failed (0.106 seconds). @@ -589,8 +589,8 @@ Test Case 'MyTests.testFail' failed (0.106 seconds). ), isKnown: false, diff: { - expected: '"1"', - actual: '"2"', + actual: '"1"', + expected: '"2"', }, }, ], From eb6ba7c8c3bf6c7e0db3528fa288c75da065fae0 Mon Sep 17 00:00:00 2001 From: Matthew Bastien Date: Wed, 19 Mar 2025 13:41:35 -0500 Subject: [PATCH 53/86] bump version to 2.1.0 (#1446) --- package-lock.json | 4 ++-- package.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3538814c0..02999c16d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "swift-vscode", - "version": "2.0.1", + "version": "2.1.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "swift-vscode", - "version": "2.0.1", + "version": "2.1.0", "hasInstallScript": true, "dependencies": { "@vscode/codicons": "^0.0.36", diff --git a/package.json b/package.json index e854648f1..4779a37ad 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "swift-vscode", "displayName": "Swift", "description": "Swift Language Support for Visual Studio Code.", - "version": "2.0.1", + "version": "2.1.0", "publisher": "swiftlang", "icon": "icon.png", "repository": { @@ -1636,7 +1636,7 @@ "coverage": "npm test -- --coverage", "compile-tests": "del-cli ./assets/test/**/.build && npm run compile", "package": "vsce package", - "dev-package": "vsce package --no-update-package-json 2.0.1-dev", + "dev-package": "vsce package --no-update-package-json 2.1.0-dev", "preview-package": "vsce package --pre-release", "tag": "./scripts/tag_release.sh $npm_package_version", "contributors": "./scripts/generate_contributors_list.sh" From 3a7b448c147fe16a5a522611416ce13821f1914c Mon Sep 17 00:00:00 2001 From: Paul LeMarquand Date: Wed, 19 Mar 2025 15:54:52 -0400 Subject: [PATCH 54/86] Only show Swift tasks in the Project Panel (#1447) * Only show Swift tasks in the Project Panel Scope tasks to currently open project and ensure they have a unique ID. --- src/ui/ProjectPanelProvider.ts | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/src/ui/ProjectPanelProvider.ts b/src/ui/ProjectPanelProvider.ts index 793bebe1f..720077504 100644 --- a/src/ui/ProjectPanelProvider.ts +++ b/src/ui/ProjectPanelProvider.ts @@ -21,6 +21,7 @@ import { FolderOperation } from "../WorkspaceContext"; import contextKeys from "../contextKeys"; import { Dependency, ResolvedDependency, Target } from "../SwiftPackage"; import { SwiftPluginTaskProvider } from "../tasks/SwiftPluginTaskProvider"; +import { FolderContext } from "../FolderContext"; const LOADING_ICON = "loading~spin"; /** @@ -174,13 +175,14 @@ export class FileNode { class TaskNode { constructor( public type: string, + public id: string, public name: string, private active: boolean ) {} toTreeItem(): vscode.TreeItem { const item = new vscode.TreeItem(this.name, vscode.TreeItemCollapsibleState.None); - item.id = `${this.type}-${this.name}`; + item.id = `${this.type}-${this.id}`; item.iconPath = new vscode.ThemeIcon(this.active ? LOADING_ICON : "play"); item.contextValue = "task"; item.accessibilityInformation = { label: this.name }; @@ -445,7 +447,12 @@ export class ProjectPanelProvider implements vscode.TreeDataProvider { ] : []), new HeaderNode("targets", "Targets", "book", this.wrapInAsync(this.targets.bind(this))), - new HeaderNode("tasks", "Tasks", "debug-continue-small", this.tasks.bind(this)), + new HeaderNode( + "tasks", + "Tasks", + "debug-continue-small", + this.tasks.bind(this, folderContext) + ), ...(snippets.length > 0 ? [ new HeaderNode("snippets", "Snippets", "notebook", () => @@ -508,16 +515,23 @@ export class ProjectPanelProvider implements vscode.TreeDataProvider { ); } - private async tasks(): Promise { + private async tasks(folderContext: FolderContext): Promise { const tasks = await vscode.tasks.fetchTasks(); + return ( tasks // Plugin tasks are shown under the Commands header - .filter(task => task.source !== "swift-plugin") - .map( + .filter( task => + task.definition.type === "swift" && + task.definition.cwd === folderContext.folder.fsPath && + task.source !== "swift-plugin" + ) + .map( + (task, i) => new TaskNode( "task", + `${task.definition.cwd}-${task.name}-${task.detail ?? ""}-${i}`, task.name, this.activeTasks.has(task.detail ?? task.name) ) @@ -531,9 +545,10 @@ export class ProjectPanelProvider implements vscode.TreeDataProvider { const tasks = await provider.provideTasks(new vscode.CancellationTokenSource().token); return tasks .map( - task => + (task, i) => new TaskNode( "command", + `${task.definition.cwd}-${task.name}-${task.detail ?? ""}-${i}`, task.name, this.activeTasks.has(task.detail ?? task.name) ) From 7588123b4ceafebf9a799460727b61d1cda33ef3 Mon Sep 17 00:00:00 2001 From: Rishi Date: Thu, 20 Mar 2025 10:26:41 -0400 Subject: [PATCH 55/86] Update Process List test to handle insider builds (#1448) The process names for insider versions of VS Code are slightly different from the release versions. Update the test to account for this. Issue: #1442 --- .../process-list/processList.test.ts | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/test/integration-tests/process-list/processList.test.ts b/test/integration-tests/process-list/processList.test.ts index 65360d2f8..3fae5648e 100644 --- a/test/integration-tests/process-list/processList.test.ts +++ b/test/integration-tests/process-list/processList.test.ts @@ -27,18 +27,26 @@ suite("ProcessList Tests", () => { test("retreives the list of available processes", async function () { // We can guarantee that certain VS Code processes will be present during tests const processes = await createProcessList().listAllProcesses(); + let processNameDarwin: string = "Code"; + let processNameWin32: string = "Code"; + let processNameLinux: string = "code"; + if (process.env["VSCODE_VERSION"] === "insiders") { + processNameDarwin = "Code - Insiders"; + processNameWin32 = "Code - Insiders"; + processNameLinux = "code-insiders"; + } switch (process.platform) { case "darwin": - expectProcessName(processes, "Code Helper"); - expectProcessName(processes, "Code Helper (GPU)"); - expectProcessName(processes, "Code Helper (Plugin)"); - expectProcessName(processes, "Code Helper (Renderer)"); + expectProcessName(processes, `${processNameDarwin} Helper`); + expectProcessName(processes, `${processNameDarwin} Helper (GPU)`); + expectProcessName(processes, `${processNameDarwin} Helper (Plugin)`); + expectProcessName(processes, `${processNameDarwin} Helper (Renderer)`); break; case "win32": - expectProcessName(processes, "Code.exe"); + expectProcessName(processes, `${processNameWin32}.exe`); break; case "linux": - expectProcessName(processes, "code"); + expectProcessName(processes, `${processNameLinux}`); break; default: this.skip(); From b871ec56fb0cf78b39a870411b7f3eb0e4a66ea5 Mon Sep 17 00:00:00 2001 From: Matthew Bastien Date: Thu, 20 Mar 2025 12:27:59 -0500 Subject: [PATCH 56/86] update debugging section in the README (#1451) --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 308402e85..f6731b0f8 100644 --- a/README.md +++ b/README.md @@ -101,10 +101,12 @@ Additionally, the extension will monitor `Package.swift` and `Package.resolved` The Swift extension uses the [LLDB DAP](https://marketplace.visualstudio.com/items?itemName=llvm-vs-code-extensions.lldb-dap) extension for debugging. -When you open a Swift package (a directory containing a `Package.swift` file), the extension automatically generates build tasks and launch configurations for each executable within the package. Additionally, if the package includes tests, the extension creates a configuration specifically designed to run those tests. These configurations all leverage the CodeLLDB extension as the debugger of choice. +When you open a Swift package (a directory containing a `Package.swift` file), the extension automatically generates build tasks and launch configurations for each executable within the package. Additionally, if the package includes tests, the extension creates a configuration specifically designed to run those tests. These configurations all leverage the LLDB DAP extension as the debugger of choice. Use the **Run > Start Debugging** menu item to run an executable and start debugging. If you have multiple launch configurations you can choose which launch configuration to use in the debugger view. +LLDB DAP is only available starting in Swift 6.0. On older versions of Swift the [CodeLLDB](https://marketplace.visualstudio.com/items?itemName=vadimcn.vscode-lldb) extension will be used for debugging instead. You will be prompted to install the CodeLLDB extension in this case. + CodeLLDB includes a version of `lldb` that it uses by default for debugging, but this version of `lldb` doesn’t support Swift. The Swift extension will automatically identify the required version and offer to update the CodeLLDB configuration as necessary so that debugging is supported. ### Test Explorer From 487d9438ef4386468caa4e47ce0b261f4b65bde7 Mon Sep 17 00:00:00 2001 From: Matthew Bastien Date: Thu, 20 Mar 2025 12:31:29 -0500 Subject: [PATCH 57/86] Build pre-release VSIX in CI (#1450) --- .github/workflows/nightly.yml | 1 + .vscode/launch.json | 7 +++ package.json | 5 +- scripts/check_package_json.ts | 25 ++++++++ scripts/lib/utilities.ts | 92 +++++++++++++++++++++++++++++ scripts/preview_package.ts | 53 +++++++++++++++++ scripts/soundness.sh | 3 + scripts/update_swift_docc_render.ts | 35 +---------- 8 files changed, 187 insertions(+), 34 deletions(-) create mode 100644 scripts/check_package_json.ts create mode 100644 scripts/lib/utilities.ts create mode 100644 scripts/preview_package.ts diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index cdde23e48..5037d9908 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -24,6 +24,7 @@ jobs: npm ci npm run compile npm run package + npm run preview-package - name: Archive production artifacts uses: actions/upload-artifact@v4 if: always() diff --git a/.vscode/launch.json b/.vscode/launch.json index 50413336b..bbf089324 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -45,6 +45,13 @@ "request": "launch", "runtimeExecutable": "${workspaceFolder}/node_modules/.bin/tsx", "runtimeArgs": ["${workspaceFolder}/scripts/update_swift_docc_render.ts"] + }, + { + "name": "Preview Package", + "type": "node", + "request": "launch", + "runtimeExecutable": "${workspaceFolder}/node_modules/.bin/tsx", + "runtimeArgs": ["${workspaceFolder}/scripts/preview_package.ts"] } ] } diff --git a/package.json b/package.json index 4779a37ad..e7c3e0eab 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "swift-vscode", "displayName": "Swift", "description": "Swift Language Support for Visual Studio Code.", - "version": "2.1.0", + "version": "2.0.2", "publisher": "swiftlang", "icon": "icon.png", "repository": { @@ -1630,6 +1630,7 @@ "postinstall": "npm run update-swift-docc-render", "pretest": "npm run compile-tests", "soundness": "scripts/soundness.sh", + "check-package-json": "tsx ./scripts/check_package_json.ts", "test": "vscode-test", "integration-test": "npm test -- --label integrationTests", "unit-test": "npm test -- --label unitTests", @@ -1637,7 +1638,7 @@ "compile-tests": "del-cli ./assets/test/**/.build && npm run compile", "package": "vsce package", "dev-package": "vsce package --no-update-package-json 2.1.0-dev", - "preview-package": "vsce package --pre-release", + "preview-package": "tsx ./scripts/preview_package.ts", "tag": "./scripts/tag_release.sh $npm_package_version", "contributors": "./scripts/generate_contributors_list.sh" }, diff --git a/scripts/check_package_json.ts b/scripts/check_package_json.ts new file mode 100644 index 000000000..394708a67 --- /dev/null +++ b/scripts/check_package_json.ts @@ -0,0 +1,25 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the VS Code Swift open source project +// +// Copyright (c) 2025 the VS Code Swift project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of VS Code Swift project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// +/* eslint-disable no-console */ + +import { getExtensionVersion, main } from "./lib/utilities"; + +main(async () => { + const version = await getExtensionVersion(); + if (version.minor % 2 !== 0) { + throw new Error( + `Invalid version number in package.json. ${version.toString()} does not have an even numbered minor version.` + ); + } +}); diff --git a/scripts/lib/utilities.ts b/scripts/lib/utilities.ts new file mode 100644 index 000000000..3af9b17d0 --- /dev/null +++ b/scripts/lib/utilities.ts @@ -0,0 +1,92 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the VS Code Swift open source project +// +// Copyright (c) 2025 the VS Code Swift project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of VS Code Swift project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// +/* eslint-disable no-console */ + +import * as child_process from "child_process"; +import { readFile } from "fs/promises"; +import * as path from "path"; +import * as semver from "semver"; + +/** + * Executes the provided main function for the script while logging any errors. + * + * If an error is caught then the process will exit with code 1. + * + * @param mainFn The main function of the script that will be run. + */ +export async function main(mainFn: () => Promise): Promise { + try { + await mainFn(); + } catch (error) { + console.error(error); + process.exit(1); + } +} + +/** + * Returns the root directory of the repository. + */ +export function getRootDirectory(): string { + return path.join(__dirname, "..", ".."); +} + +/** + * Retrieves the version number from the package.json. + */ +export async function getExtensionVersion(): Promise { + const packageJSON = JSON.parse( + await readFile(path.join(getRootDirectory(), "package.json"), "utf-8") + ); + if (typeof packageJSON.version !== "string") { + throw new Error("Version number in package.json is not a string"); + } + const version = semver.parse(packageJSON.version); + if (version === null) { + throw new Error("Unable to parse version number in package.json"); + } + return version; +} + +/** + * Executes the given command, inheriting the current process' stdio. + * + * @param command The command to execute. + * @param args The arguments to provide to the command. + * @param options The options for executing the command. + */ +export async function exec( + command: string, + args: string[], + options: child_process.SpawnOptionsWithoutStdio = {} +): Promise { + let logMessage = "> " + command; + if (args.length > 0) { + logMessage += " " + args.join(" "); + } + console.log(logMessage + "\n"); + return new Promise((resolve, reject) => { + const childProcess = child_process.spawn(command, args, { stdio: "inherit", ...options }); + childProcess.once("error", reject); + childProcess.once("close", (code, signal) => { + if (signal !== null) { + reject(new Error(`Process exited due to signal '${signal}'`)); + } else if (code !== 0) { + reject(new Error(`Process exited with code ${code}`)); + } else { + resolve(); + } + console.log(""); + }); + }); +} diff --git a/scripts/preview_package.ts b/scripts/preview_package.ts new file mode 100644 index 000000000..53698927b --- /dev/null +++ b/scripts/preview_package.ts @@ -0,0 +1,53 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the VS Code Swift open source project +// +// Copyright (c) 2025 the VS Code Swift project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of VS Code Swift project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// +/* eslint-disable no-console */ + +import { exec, getExtensionVersion, getRootDirectory, main } from "./lib/utilities"; + +/** + * Formats the given date as a string in the form "YYYYMMddhhmm". + * + * @param date The date to format as a string. + * @returns The formatted date. + */ +function formatDate(date: Date): string { + const year = date.getUTCFullYear().toString().padStart(4, "0"); + const month = (date.getUTCMonth() + 1).toString().padStart(2, "0"); + const day = date.getUTCDate().toString().padStart(2, "0"); + const hour = date.getUTCHours().toString().padStart(2, "0"); + const minutes = date.getUTCMinutes().toString().padStart(2, "0"); + return year + month + day + hour + minutes; +} + +main(async () => { + const rootDirectory = getRootDirectory(); + const version = await getExtensionVersion(); + // Increment the minor version and set the patch version to today's date + const minor = version.minor + 1; + const patch = formatDate(new Date()); + const previewVersion = `${version.major}.${minor}.${patch}`; + // Make sure that the new minor version is odd + if (minor % 2 !== 1) { + throw new Error( + `The minor version for the pre-release extension is even (${previewVersion}).` + + " The version in the package.json has probably been incorrectly set to an odd minor version." + ); + } + // Use VSCE to package the extension + await exec( + "npx", + ["vsce", "package", "--pre-release", "--no-update-package-json", previewVersion], + { cwd: rootDirectory } + ); +}); diff --git a/scripts/soundness.sh b/scripts/soundness.sh index ab4f0a54f..e877feebb 100755 --- a/scripts/soundness.sh +++ b/scripts/soundness.sh @@ -36,6 +36,9 @@ function replace_acceptable_years() { sed -e 's/20[12][0123456789]-20[12][0123456789]/YEARS/' -e 's/20[12][0123456789]/YEARS/' } +printf "=> Checking package.json..." +npm run check-package-json + printf "=> Checking license headers... " tmp=$(mktemp /tmp/.vscode-swift-soundness_XXXXXX) diff --git a/scripts/update_swift_docc_render.ts b/scripts/update_swift_docc_render.ts index accb306b6..f7aef5878 100644 --- a/scripts/update_swift_docc_render.ts +++ b/scripts/update_swift_docc_render.ts @@ -14,11 +14,11 @@ /* eslint-disable no-console */ import simpleGit, { ResetMode } from "simple-git"; -import { spawn } from "child_process"; import { stat, mkdtemp, mkdir, rm, readdir } from "fs/promises"; import * as path from "path"; import { tmpdir } from "os"; import * as semver from "semver"; +import { exec, getRootDirectory, main } from "./lib/utilities"; function checkNodeVersion() { const nodeVersion = semver.parse(process.versions.node); @@ -57,34 +57,8 @@ async function cloneSwiftDocCRender(buildDirectory: string): Promise { return swiftDocCRenderDirectory; } -async function exec( - command: string, - args: string[], - options: { cwd?: string; env?: { [key: string]: string } } = {} -): Promise { - let logMessage = "> " + command; - if (args.length > 0) { - logMessage += " " + args.join(" "); - } - console.log(logMessage + "\n"); - return new Promise((resolve, reject) => { - const childProcess = spawn(command, args, { stdio: "inherit", ...options }); - childProcess.once("error", reject); - childProcess.once("close", (code, signal) => { - if (signal !== null) { - reject(new Error(`Process exited due to signal '${signal}'`)); - } else if (code !== 0) { - reject(new Error(`Process exited with code ${code}`)); - } else { - resolve(); - } - console.log(""); - }); - }); -} - -(async () => { - const outputDirectory = path.join(__dirname, "..", "assets", "swift-docc-render"); +main(async () => { + const outputDirectory = path.join(getRootDirectory(), "assets", "swift-docc-render"); if (process.argv.includes("postinstall")) { try { await stat(outputDirectory); @@ -114,7 +88,4 @@ async function exec( console.error(error); }); } -})().catch(error => { - console.error(error); - process.exit(1); }); From 64de5b7d7c5f24c9f83a03d6247d027a775744dd Mon Sep 17 00:00:00 2001 From: Matthew Bastien Date: Thu, 20 Mar 2025 13:47:55 -0500 Subject: [PATCH 58/86] Release activities for 2.0.2 (#1452) Co-authored-by: Adam Ward --- CHANGELOG.md | 6 ++++++ package-lock.json | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c9698985..e59f7967e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 2.0.2 - 2025-02-20 + +### Fixed + +- Fix debugging of Swift tests when using Xcode 16.1 Beta ([#1396](https://github.com/swiftlang/vscode-swift/pull/1396)) + ## 2.0.1 - 2025-02-10 Rename the `displayName` of the extension back to `Swift` now that the old `sswg` extension has been renamed to `Swift (Deprecated)`. diff --git a/package-lock.json b/package-lock.json index 02999c16d..e24077d46 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "swift-vscode", - "version": "2.1.0", + "version": "2.0.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "swift-vscode", - "version": "2.1.0", + "version": "2.0.2", "hasInstallScript": true, "dependencies": { "@vscode/codicons": "^0.0.36", From 674cae72cab23ee50bab541986f795332823d991 Mon Sep 17 00:00:00 2001 From: Paul LeMarquand Date: Fri, 21 Mar 2025 08:59:08 -0400 Subject: [PATCH 59/86] Show error message in project panel when manifest can't be built (#1453) Because we use `swift package describe` to extract metadata about the package we need the Package.swift to be buildable. If it isn't, create a header node in the project panel that calls out the fact that the Package.swift can't be built. Clicking it will take the user to the Package.swift. --- src/commands.ts | 6 ++ src/ui/ProjectPanelProvider.ts | 57 ++++++++++++++++++- .../ui/ProjectPanelProvider.test.ts | 9 +++ 3 files changed, 69 insertions(+), 3 deletions(-) diff --git a/src/commands.ts b/src/commands.ts index 7b9c7bb83..7a2577399 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -12,6 +12,7 @@ // //===----------------------------------------------------------------------===// +import * as path from "path"; import * as vscode from "vscode"; import { WorkspaceContext } from "./WorkspaceContext"; import { PackageNode } from "./ui/ProjectPanelProvider"; @@ -93,6 +94,7 @@ export enum Commands { RUN_ALL_TESTS_PARALLEL = "swift.runAllTestsParallel", DEBUG_ALL_TESTS = "swift.debugAllTests", COVER_ALL_TESTS = "swift.coverAllTests", + OPEN_MANIFEST = "swift.openManifest", } /** @@ -209,6 +211,10 @@ export function register(ctx: WorkspaceContext): vscode.Disposable[] { vscode.commands.registerCommand("swift.openEducationalNote", uri => openEducationalNote(uri) ), + vscode.commands.registerCommand(Commands.OPEN_MANIFEST, (uri: vscode.Uri) => { + const packagePath = path.join(uri.fsPath, "Package.swift"); + vscode.commands.executeCommand("vscode.open", vscode.Uri.file(packagePath)); + }), ]; } diff --git a/src/ui/ProjectPanelProvider.ts b/src/ui/ProjectPanelProvider.ts index 720077504..659a36c47 100644 --- a/src/ui/ProjectPanelProvider.ts +++ b/src/ui/ProjectPanelProvider.ts @@ -311,12 +311,44 @@ class HeaderNode { } } +class ErrorNode { + constructor( + public name: string, + private folder: vscode.Uri + ) {} + + get path(): string { + return ""; + } + + toTreeItem(): vscode.TreeItem { + const item = new vscode.TreeItem(this.name, vscode.TreeItemCollapsibleState.None); + item.id = `error-${this.folder.fsPath}`; + item.iconPath = new vscode.ThemeIcon("error", new vscode.ThemeColor("errorForeground")); + item.contextValue = "error"; + item.accessibilityInformation = { label: this.name }; + item.tooltip = + "Could not build the Package.swift, fix the error to refresh the project panel"; + + item.command = { + command: "swift.openManifest", + arguments: [this.folder], + title: "Open Manifest", + }; + return item; + } + + getChildren(): Promise { + return Promise.resolve([]); + } +} + /** * A node in the Package Dependencies {@link vscode.TreeView TreeView}. * - * Can be either a {@link PackageNode}, {@link FileNode}, {@link TargetNode}, {@link TaskNode} or {@link HeaderNode}. + * Can be either a {@link PackageNode}, {@link FileNode}, {@link TargetNode}, {@link TaskNode}, {@link ErrorNode} or {@link HeaderNode}. */ -type TreeNode = PackageNode | FileNode | HeaderNode | TaskNode | TargetNode; +type TreeNode = PackageNode | FileNode | HeaderNode | TaskNode | TargetNode | ErrorNode; /** * A {@link vscode.TreeDataProvider TreeDataProvider} for project dependencies, tasks and commands {@link vscode.TreeView TreeView}. @@ -328,6 +360,7 @@ export class ProjectPanelProvider implements vscode.TreeDataProvider { private workspaceObserver?: vscode.Disposable; private disposables: vscode.Disposable[] = []; private activeTasks: Set = new Set(); + private lastComputedNodes: TreeNode[] = []; onDidChangeTreeData = this.didChangeTreeDataEmitter.event; @@ -426,6 +459,24 @@ export class ProjectPanelProvider implements vscode.TreeDataProvider { return []; } + if (!element && folderContext.hasResolveErrors) { + return [ + new ErrorNode("Error Parsing Package.swift", folderContext.folder), + ...this.lastComputedNodes, + ]; + } + + const nodes = await this.computeChildren(folderContext, element); + + // If we're fetching the root nodes then save them in case we have an error later, + // in which case we show the ErrorNode along with the last known good nodes. + if (!element) { + this.lastComputedNodes = nodes; + } + return nodes; + } + + async computeChildren(folderContext: FolderContext, element?: TreeNode): Promise { if (element) { return element.getChildren(); } @@ -515,7 +566,7 @@ export class ProjectPanelProvider implements vscode.TreeDataProvider { ); } - private async tasks(folderContext: FolderContext): Promise { + private async tasks(folderContext: FolderContext): Promise { const tasks = await vscode.tasks.fetchTasks(); return ( diff --git a/test/integration-tests/ui/ProjectPanelProvider.test.ts b/test/integration-tests/ui/ProjectPanelProvider.test.ts index e1cb5c433..d14a34259 100644 --- a/test/integration-tests/ui/ProjectPanelProvider.test.ts +++ b/test/integration-tests/ui/ProjectPanelProvider.test.ts @@ -282,6 +282,15 @@ suite("ProjectPanelProvider Test Suite", function () { expect(items.find(n => n.name === "swift-markdown")).to.not.be.undefined; expect(items.find(n => n.name === "defaultpackage")).to.not.be.undefined; }); + + test("Shows an error node when there is a problem compiling Package.swift", async () => { + workspaceContext.folders[0].hasResolveErrors = true; + workspaceContext.currentFolder = workspaceContext.folders[0]; + const treeProvider = new ProjectPanelProvider(workspaceContext); + const children = await treeProvider.getChildren(); + const errorNode = children.find(n => n.name === "Error Parsing Package.swift"); + expect(errorNode).to.not.be.undefined; + }); }); async function getHeaderChildren(headerName: string) { From d2bb4651b1d9cde9ddd816242b8dd83ba92e0553 Mon Sep 17 00:00:00 2001 From: Paul LeMarquand Date: Fri, 21 Mar 2025 09:52:04 -0400 Subject: [PATCH 60/86] Show all task types in project panel (#1454) If users want to have non-Swift task types in their package they should be able to. --- src/ui/ProjectPanelProvider.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ui/ProjectPanelProvider.ts b/src/ui/ProjectPanelProvider.ts index 659a36c47..79f2a237c 100644 --- a/src/ui/ProjectPanelProvider.ts +++ b/src/ui/ProjectPanelProvider.ts @@ -574,7 +574,6 @@ export class ProjectPanelProvider implements vscode.TreeDataProvider { // Plugin tasks are shown under the Commands header .filter( task => - task.definition.type === "swift" && task.definition.cwd === folderContext.folder.fsPath && task.source !== "swift-plugin" ) From 8a0a18caef55423d3513f21c1782e4575c1c16cd Mon Sep 17 00:00:00 2001 From: Matthew Bastien Date: Fri, 21 Mar 2025 13:31:38 -0500 Subject: [PATCH 61/86] shorten pre-release version number (#1456) --- scripts/preview_package.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/scripts/preview_package.ts b/scripts/preview_package.ts index 53698927b..a2216d963 100644 --- a/scripts/preview_package.ts +++ b/scripts/preview_package.ts @@ -16,7 +16,7 @@ import { exec, getExtensionVersion, getRootDirectory, main } from "./lib/utilities"; /** - * Formats the given date as a string in the form "YYYYMMddhhmm". + * Formats the given date as a string in the form "YYYYMMdd". * * @param date The date to format as a string. * @returns The formatted date. @@ -25,9 +25,7 @@ function formatDate(date: Date): string { const year = date.getUTCFullYear().toString().padStart(4, "0"); const month = (date.getUTCMonth() + 1).toString().padStart(2, "0"); const day = date.getUTCDate().toString().padStart(2, "0"); - const hour = date.getUTCHours().toString().padStart(2, "0"); - const minutes = date.getUTCMinutes().toString().padStart(2, "0"); - return year + month + day + hour + minutes; + return year + month + day; } main(async () => { From 5d9898b1fbd9b31d7c6bb82c188f88ad5e0a4435 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 25 Mar 2025 09:37:19 -0400 Subject: [PATCH 62/86] Bump the all-dependencies group with 5 updates (#1460) Bumps the all-dependencies group with 5 updates: | Package | From | To | | --- | --- | --- | | [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `20.17.24` | `20.17.27` | | [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) | `8.26.1` | `8.28.0` | | [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) | `8.26.1` | `8.28.0` | | [@vscode/vsce](https://github.com/Microsoft/vsce) | `3.2.2` | `3.3.0` | | [sinon](https://github.com/sinonjs/sinon) | `19.0.2` | `20.0.0` | Updates `@types/node` from 20.17.24 to 20.17.27 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) Updates `@typescript-eslint/eslint-plugin` from 8.26.1 to 8.28.0 - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.28.0/packages/eslint-plugin) Updates `@typescript-eslint/parser` from 8.26.1 to 8.28.0 - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.28.0/packages/parser) Updates `@vscode/vsce` from 3.2.2 to 3.3.0 - [Release notes](https://github.com/Microsoft/vsce/releases) - [Commits](https://github.com/Microsoft/vsce/compare/v3.2.2...v3.3.0) Updates `sinon` from 19.0.2 to 20.0.0 - [Release notes](https://github.com/sinonjs/sinon/releases) - [Changelog](https://github.com/sinonjs/sinon/blob/main/docs/changelog.md) - [Commits](https://github.com/sinonjs/sinon/compare/v19.0.2...v20.0.0) --- updated-dependencies: - dependency-name: "@types/node" dependency-type: direct:development update-type: version-update:semver-patch dependency-group: all-dependencies - dependency-name: "@typescript-eslint/eslint-plugin" dependency-type: direct:development update-type: version-update:semver-minor dependency-group: all-dependencies - dependency-name: "@typescript-eslint/parser" dependency-type: direct:development update-type: version-update:semver-minor dependency-group: all-dependencies - dependency-name: "@vscode/vsce" dependency-type: direct:development update-type: version-update:semver-minor dependency-group: all-dependencies - dependency-name: sinon dependency-type: direct:development update-type: version-update:semver-major dependency-group: all-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 301 ++++++++++++++++++---------------------------- package.json | 10 +- 2 files changed, 120 insertions(+), 191 deletions(-) diff --git a/package-lock.json b/package-lock.json index e24077d46..8495d4aab 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,19 +25,19 @@ "@types/lodash.throttle": "^4.1.9", "@types/mocha": "^10.0.10", "@types/mock-fs": "^4.13.4", - "@types/node": "^20.0.0", + "@types/node": "^20.17.27", "@types/plist": "^3.0.5", "@types/semver": "^7.5.8", "@types/sinon": "^17.0.4", "@types/sinon-chai": "^3.2.12", "@types/vscode": "^1.88.0", "@types/xml2js": "^0.4.14", - "@typescript-eslint/eslint-plugin": "^8.26.1", - "@typescript-eslint/parser": "^8.26.1", + "@typescript-eslint/eslint-plugin": "^8.28.0", + "@typescript-eslint/parser": "^8.28.0", "@vscode/debugprotocol": "^1.68.0", "@vscode/test-cli": "^0.0.10", "@vscode/test-electron": "^2.4.1", - "@vscode/vsce": "^3.0.0", + "@vscode/vsce": "^3.3.0", "chai": "^4.5.0", "chai-as-promised": "^7.1.2", "chai-subset": "^1.6.0", @@ -53,7 +53,7 @@ "prettier": "^3.5.3", "semver": "^7.7.1", "simple-git": "^3.27.0", - "sinon": "^19.0.2", + "sinon": "^20.0.0", "sinon-chai": "^3.7.0", "source-map-support": "^0.5.21", "strip-ansi": "^6.0.1", @@ -1033,9 +1033,9 @@ } }, "node_modules/@sinonjs/fake-timers": { - "version": "13.0.2", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-13.0.2.tgz", - "integrity": "sha512-4Bb+oqXZTSTZ1q27Izly9lv8B9dlV61CROxPiVtywwzv5SnytJqhvYe6FclHYuXml4cd1VHPo1zd5PmTeJozvA==", + "version": "13.0.5", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-13.0.5.tgz", + "integrity": "sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -1054,13 +1054,6 @@ "type-detect": "^4.1.0" } }, - "node_modules/@sinonjs/text-encoding": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.3.tgz", - "integrity": "sha512-DE427ROAphMQzU4ENbliGYrBSYPXF+TtLg9S8vzeA+OF4ZKzoDdzfL8sxuMUGS/lgRhM6j1URSk9ghf7Xo1tyA==", - "dev": true, - "license": "(Unlicense OR Apache-2.0)" - }, "node_modules/@types/chai": { "version": "4.3.19", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.19.tgz", @@ -1159,9 +1152,9 @@ } }, "node_modules/@types/node": { - "version": "20.17.24", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.24.tgz", - "integrity": "sha512-d7fGCyB96w9BnWQrOsJtpyiSaBcAYYr75bnK6ZRjDbql2cGLj/3GsL5OYmLPNq76l7Gf2q4Rv9J2o6h5CrD9sA==", + "version": "20.17.27", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.27.tgz", + "integrity": "sha512-U58sbKhDrthHlxHRJw7ZLiLDZGmAUOZUbpw0S6nL27sYUdhvgBLCRu/keSd6qcTsfArd1sRFCCBxzWATGr/0UA==", "dev": true, "license": "MIT", "dependencies": { @@ -1228,17 +1221,17 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.26.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.26.1.tgz", - "integrity": "sha512-2X3mwqsj9Bd3Ciz508ZUtoQQYpOhU/kWoUqIf49H8Z0+Vbh6UF/y0OEYp0Q0axOGzaBGs7QxRwq0knSQ8khQNA==", + "version": "8.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.28.0.tgz", + "integrity": "sha512-lvFK3TCGAHsItNdWZ/1FkvpzCxTHUVuFrdnOGLMa0GGCFIbCgQWVk3CzCGdA7kM3qGVc+dfW9tr0Z/sHnGDFyg==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.26.1", - "@typescript-eslint/type-utils": "8.26.1", - "@typescript-eslint/utils": "8.26.1", - "@typescript-eslint/visitor-keys": "8.26.1", + "@typescript-eslint/scope-manager": "8.28.0", + "@typescript-eslint/type-utils": "8.28.0", + "@typescript-eslint/utils": "8.28.0", + "@typescript-eslint/visitor-keys": "8.28.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -1258,16 +1251,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.26.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.26.1.tgz", - "integrity": "sha512-w6HZUV4NWxqd8BdeFf81t07d7/YV9s7TCWrQQbG5uhuvGUAW+fq1usZ1Hmz9UPNLniFnD8GLSsDpjP0hm1S4lQ==", + "version": "8.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.28.0.tgz", + "integrity": "sha512-LPcw1yHD3ToaDEoljFEfQ9j2xShY367h7FZ1sq5NJT9I3yj4LHer1Xd1yRSOdYy9BpsrxU7R+eoDokChYM53lQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.26.1", - "@typescript-eslint/types": "8.26.1", - "@typescript-eslint/typescript-estree": "8.26.1", - "@typescript-eslint/visitor-keys": "8.26.1", + "@typescript-eslint/scope-manager": "8.28.0", + "@typescript-eslint/types": "8.28.0", + "@typescript-eslint/typescript-estree": "8.28.0", + "@typescript-eslint/visitor-keys": "8.28.0", "debug": "^4.3.4" }, "engines": { @@ -1283,14 +1276,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.26.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.26.1.tgz", - "integrity": "sha512-6EIvbE5cNER8sqBu6V7+KeMZIC1664d2Yjt+B9EWUXrsyWpxx4lEZrmvxgSKRC6gX+efDL/UY9OpPZ267io3mg==", + "version": "8.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.28.0.tgz", + "integrity": "sha512-u2oITX3BJwzWCapoZ/pXw6BCOl8rJP4Ij/3wPoGvY8XwvXflOzd1kLrDUUUAIEdJSFh+ASwdTHqtan9xSg8buw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.26.1", - "@typescript-eslint/visitor-keys": "8.26.1" + "@typescript-eslint/types": "8.28.0", + "@typescript-eslint/visitor-keys": "8.28.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1301,14 +1294,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.26.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.26.1.tgz", - "integrity": "sha512-Kcj/TagJLwoY/5w9JGEFV0dclQdyqw9+VMndxOJKtoFSjfZhLXhYjzsQEeyza03rwHx2vFEGvrJWJBXKleRvZg==", + "version": "8.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.28.0.tgz", + "integrity": "sha512-oRoXu2v0Rsy/VoOGhtWrOKDiIehvI+YNrDk5Oqj40Mwm0Yt01FC/Q7nFqg088d3yAsR1ZcZFVfPCTTFCe/KPwg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.26.1", - "@typescript-eslint/utils": "8.26.1", + "@typescript-eslint/typescript-estree": "8.28.0", + "@typescript-eslint/utils": "8.28.0", "debug": "^4.3.4", "ts-api-utils": "^2.0.1" }, @@ -1325,9 +1318,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.26.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.26.1.tgz", - "integrity": "sha512-n4THUQW27VmQMx+3P+B0Yptl7ydfceUj4ON/AQILAASwgYdZ/2dhfymRMh5egRUrvK5lSmaOm77Ry+lmXPOgBQ==", + "version": "8.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.28.0.tgz", + "integrity": "sha512-bn4WS1bkKEjx7HqiwG2JNB3YJdC1q6Ue7GyGlwPHyt0TnVq6TtD/hiOdTZt71sq0s7UzqBFXD8t8o2e63tXgwA==", "dev": true, "license": "MIT", "engines": { @@ -1339,14 +1332,14 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.26.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.26.1.tgz", - "integrity": "sha512-yUwPpUHDgdrv1QJ7YQal3cMVBGWfnuCdKbXw1yyjArax3353rEJP1ZA+4F8nOlQ3RfS2hUN/wze3nlY+ZOhvoA==", + "version": "8.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.28.0.tgz", + "integrity": "sha512-H74nHEeBGeklctAVUvmDkxB1mk+PAZ9FiOMPFncdqeRBXxk1lWSYraHw8V12b7aa6Sg9HOBNbGdSHobBPuQSuA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.26.1", - "@typescript-eslint/visitor-keys": "8.26.1", + "@typescript-eslint/types": "8.28.0", + "@typescript-eslint/visitor-keys": "8.28.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -1392,16 +1385,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.26.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.26.1.tgz", - "integrity": "sha512-V4Urxa/XtSUroUrnI7q6yUTD3hDtfJ2jzVfeT3VK0ciizfK2q/zGC0iDh1lFMUZR8cImRrep6/q0xd/1ZGPQpg==", + "version": "8.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.28.0.tgz", + "integrity": "sha512-OELa9hbTYciYITqgurT1u/SzpQVtDLmQMFzy/N8pQE+tefOyCWT79jHsav294aTqV1q1u+VzqDGbuujvRYaeSQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.26.1", - "@typescript-eslint/types": "8.26.1", - "@typescript-eslint/typescript-estree": "8.26.1" + "@typescript-eslint/scope-manager": "8.28.0", + "@typescript-eslint/types": "8.28.0", + "@typescript-eslint/typescript-estree": "8.28.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1416,13 +1409,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.26.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.26.1.tgz", - "integrity": "sha512-AjOC3zfnxd6S4Eiy3jwktJPclqhFHNyd8L6Gycf9WUPoKZpgM5PjkxY1X7uSy61xVpiJDhhk7XT2NVsN3ALTWg==", + "version": "8.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.28.0.tgz", + "integrity": "sha512-hbn8SZ8w4u2pRwgQ1GlUrPKE+t2XvcCW5tTRF7j6SMYIuYG37XuzIW44JCZPa36evi0Oy2SnM664BlIaAuQcvg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.26.1", + "@typescript-eslint/types": "8.28.0", "eslint-visitor-keys": "^4.2.0" }, "engines": { @@ -1686,9 +1679,9 @@ } }, "node_modules/@vscode/vsce": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/@vscode/vsce/-/vsce-3.2.2.tgz", - "integrity": "sha512-4TqdUq/yKlQTHcQMk/DamR632bq/+IJDomSbexOMee/UAYWqYm0XHWA6scGslsCpzY+sCWEhhl0nqdOB0XW1kw==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@vscode/vsce/-/vsce-3.3.0.tgz", + "integrity": "sha512-HA/pUyvh/TQWkc4wG7AudPIWUvsR8i4jiWZZgM/a69ncPi9Nm5FDogf/wVEk4EWJs4/UdxU7J6X18dfAwfPbxA==", "dev": true, "license": "MIT", "dependencies": { @@ -4067,13 +4060,6 @@ "setimmediate": "^1.0.5" } }, - "node_modules/just-extend": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-6.2.0.tgz", - "integrity": "sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw==", - "dev": true, - "license": "MIT" - }, "node_modules/jwa": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", @@ -4591,20 +4577,6 @@ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, - "node_modules/nise": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/nise/-/nise-6.1.1.tgz", - "integrity": "sha512-aMSAzLVY7LyeM60gvBS423nBmIPP+Wy7St7hsb+8/fc1HmeoHJfLO8CKse4u3BtOZvQLJghYPI2i/1WZrEj5/g==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^3.0.1", - "@sinonjs/fake-timers": "^13.0.1", - "@sinonjs/text-encoding": "^0.7.3", - "just-extend": "^6.2.0", - "path-to-regexp": "^8.1.0" - } - }, "node_modules/node-abi": { "version": "3.45.0", "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.45.0.tgz", @@ -4987,16 +4959,6 @@ "node": "14 || >=16.14" } }, - "node_modules/path-to-regexp": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.1.0.tgz", - "integrity": "sha512-Bqn3vc8CMHty6zuD+tG23s6v2kwxslHEhTj4eYaVKGIEB+YX/2wd0/rgXLFD9G9id9KCtbVy/3ZgmvZjpa0UdQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=16" - } - }, "node_modules/path-type": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-5.0.0.tgz", @@ -5529,17 +5491,16 @@ } }, "node_modules/sinon": { - "version": "19.0.2", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-19.0.2.tgz", - "integrity": "sha512-euuToqM+PjO4UgXeLETsfQiuoyPXlqFezr6YZDFwHR3t4qaX0fZUe1MfPMznTL5f8BWrVS89KduLdMUsxFCO6g==", + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-20.0.0.tgz", + "integrity": "sha512-+FXOAbdnj94AQIxH0w1v8gzNxkawVvNqE3jUzRLptR71Oykeu2RrQXXl/VQjKay+Qnh73fDt/oDfMo6xMeDQbQ==", "dev": true, "license": "BSD-3-Clause", "dependencies": { "@sinonjs/commons": "^3.0.1", - "@sinonjs/fake-timers": "^13.0.2", + "@sinonjs/fake-timers": "^13.0.5", "@sinonjs/samsam": "^8.0.1", "diff": "^7.0.0", - "nise": "^6.1.1", "supports-color": "^7.2.0" }, "funding": { @@ -6864,9 +6825,9 @@ } }, "@sinonjs/fake-timers": { - "version": "13.0.2", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-13.0.2.tgz", - "integrity": "sha512-4Bb+oqXZTSTZ1q27Izly9lv8B9dlV61CROxPiVtywwzv5SnytJqhvYe6FclHYuXml4cd1VHPo1zd5PmTeJozvA==", + "version": "13.0.5", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-13.0.5.tgz", + "integrity": "sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==", "dev": true, "requires": { "@sinonjs/commons": "^3.0.1" @@ -6883,12 +6844,6 @@ "type-detect": "^4.1.0" } }, - "@sinonjs/text-encoding": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.3.tgz", - "integrity": "sha512-DE427ROAphMQzU4ENbliGYrBSYPXF+TtLg9S8vzeA+OF4ZKzoDdzfL8sxuMUGS/lgRhM6j1URSk9ghf7Xo1tyA==", - "dev": true - }, "@types/chai": { "version": "4.3.19", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.19.tgz", @@ -6979,9 +6934,9 @@ } }, "@types/node": { - "version": "20.17.24", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.24.tgz", - "integrity": "sha512-d7fGCyB96w9BnWQrOsJtpyiSaBcAYYr75bnK6ZRjDbql2cGLj/3GsL5OYmLPNq76l7Gf2q4Rv9J2o6h5CrD9sA==", + "version": "20.17.27", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.27.tgz", + "integrity": "sha512-U58sbKhDrthHlxHRJw7ZLiLDZGmAUOZUbpw0S6nL27sYUdhvgBLCRu/keSd6qcTsfArd1sRFCCBxzWATGr/0UA==", "dev": true, "requires": { "undici-types": "~6.19.2" @@ -7044,16 +6999,16 @@ } }, "@typescript-eslint/eslint-plugin": { - "version": "8.26.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.26.1.tgz", - "integrity": "sha512-2X3mwqsj9Bd3Ciz508ZUtoQQYpOhU/kWoUqIf49H8Z0+Vbh6UF/y0OEYp0Q0axOGzaBGs7QxRwq0knSQ8khQNA==", + "version": "8.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.28.0.tgz", + "integrity": "sha512-lvFK3TCGAHsItNdWZ/1FkvpzCxTHUVuFrdnOGLMa0GGCFIbCgQWVk3CzCGdA7kM3qGVc+dfW9tr0Z/sHnGDFyg==", "dev": true, "requires": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.26.1", - "@typescript-eslint/type-utils": "8.26.1", - "@typescript-eslint/utils": "8.26.1", - "@typescript-eslint/visitor-keys": "8.26.1", + "@typescript-eslint/scope-manager": "8.28.0", + "@typescript-eslint/type-utils": "8.28.0", + "@typescript-eslint/utils": "8.28.0", + "@typescript-eslint/visitor-keys": "8.28.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -7061,54 +7016,54 @@ } }, "@typescript-eslint/parser": { - "version": "8.26.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.26.1.tgz", - "integrity": "sha512-w6HZUV4NWxqd8BdeFf81t07d7/YV9s7TCWrQQbG5uhuvGUAW+fq1usZ1Hmz9UPNLniFnD8GLSsDpjP0hm1S4lQ==", + "version": "8.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.28.0.tgz", + "integrity": "sha512-LPcw1yHD3ToaDEoljFEfQ9j2xShY367h7FZ1sq5NJT9I3yj4LHer1Xd1yRSOdYy9BpsrxU7R+eoDokChYM53lQ==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "8.26.1", - "@typescript-eslint/types": "8.26.1", - "@typescript-eslint/typescript-estree": "8.26.1", - "@typescript-eslint/visitor-keys": "8.26.1", + "@typescript-eslint/scope-manager": "8.28.0", + "@typescript-eslint/types": "8.28.0", + "@typescript-eslint/typescript-estree": "8.28.0", + "@typescript-eslint/visitor-keys": "8.28.0", "debug": "^4.3.4" } }, "@typescript-eslint/scope-manager": { - "version": "8.26.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.26.1.tgz", - "integrity": "sha512-6EIvbE5cNER8sqBu6V7+KeMZIC1664d2Yjt+B9EWUXrsyWpxx4lEZrmvxgSKRC6gX+efDL/UY9OpPZ267io3mg==", + "version": "8.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.28.0.tgz", + "integrity": "sha512-u2oITX3BJwzWCapoZ/pXw6BCOl8rJP4Ij/3wPoGvY8XwvXflOzd1kLrDUUUAIEdJSFh+ASwdTHqtan9xSg8buw==", "dev": true, "requires": { - "@typescript-eslint/types": "8.26.1", - "@typescript-eslint/visitor-keys": "8.26.1" + "@typescript-eslint/types": "8.28.0", + "@typescript-eslint/visitor-keys": "8.28.0" } }, "@typescript-eslint/type-utils": { - "version": "8.26.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.26.1.tgz", - "integrity": "sha512-Kcj/TagJLwoY/5w9JGEFV0dclQdyqw9+VMndxOJKtoFSjfZhLXhYjzsQEeyza03rwHx2vFEGvrJWJBXKleRvZg==", + "version": "8.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.28.0.tgz", + "integrity": "sha512-oRoXu2v0Rsy/VoOGhtWrOKDiIehvI+YNrDk5Oqj40Mwm0Yt01FC/Q7nFqg088d3yAsR1ZcZFVfPCTTFCe/KPwg==", "dev": true, "requires": { - "@typescript-eslint/typescript-estree": "8.26.1", - "@typescript-eslint/utils": "8.26.1", + "@typescript-eslint/typescript-estree": "8.28.0", + "@typescript-eslint/utils": "8.28.0", "debug": "^4.3.4", "ts-api-utils": "^2.0.1" } }, "@typescript-eslint/types": { - "version": "8.26.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.26.1.tgz", - "integrity": "sha512-n4THUQW27VmQMx+3P+B0Yptl7ydfceUj4ON/AQILAASwgYdZ/2dhfymRMh5egRUrvK5lSmaOm77Ry+lmXPOgBQ==", + "version": "8.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.28.0.tgz", + "integrity": "sha512-bn4WS1bkKEjx7HqiwG2JNB3YJdC1q6Ue7GyGlwPHyt0TnVq6TtD/hiOdTZt71sq0s7UzqBFXD8t8o2e63tXgwA==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "8.26.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.26.1.tgz", - "integrity": "sha512-yUwPpUHDgdrv1QJ7YQal3cMVBGWfnuCdKbXw1yyjArax3353rEJP1ZA+4F8nOlQ3RfS2hUN/wze3nlY+ZOhvoA==", + "version": "8.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.28.0.tgz", + "integrity": "sha512-H74nHEeBGeklctAVUvmDkxB1mk+PAZ9FiOMPFncdqeRBXxk1lWSYraHw8V12b7aa6Sg9HOBNbGdSHobBPuQSuA==", "dev": true, "requires": { - "@typescript-eslint/types": "8.26.1", - "@typescript-eslint/visitor-keys": "8.26.1", + "@typescript-eslint/types": "8.28.0", + "@typescript-eslint/visitor-keys": "8.28.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -7138,24 +7093,24 @@ } }, "@typescript-eslint/utils": { - "version": "8.26.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.26.1.tgz", - "integrity": "sha512-V4Urxa/XtSUroUrnI7q6yUTD3hDtfJ2jzVfeT3VK0ciizfK2q/zGC0iDh1lFMUZR8cImRrep6/q0xd/1ZGPQpg==", + "version": "8.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.28.0.tgz", + "integrity": "sha512-OELa9hbTYciYITqgurT1u/SzpQVtDLmQMFzy/N8pQE+tefOyCWT79jHsav294aTqV1q1u+VzqDGbuujvRYaeSQ==", "dev": true, "requires": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.26.1", - "@typescript-eslint/types": "8.26.1", - "@typescript-eslint/typescript-estree": "8.26.1" + "@typescript-eslint/scope-manager": "8.28.0", + "@typescript-eslint/types": "8.28.0", + "@typescript-eslint/typescript-estree": "8.28.0" } }, "@typescript-eslint/visitor-keys": { - "version": "8.26.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.26.1.tgz", - "integrity": "sha512-AjOC3zfnxd6S4Eiy3jwktJPclqhFHNyd8L6Gycf9WUPoKZpgM5PjkxY1X7uSy61xVpiJDhhk7XT2NVsN3ALTWg==", + "version": "8.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.28.0.tgz", + "integrity": "sha512-hbn8SZ8w4u2pRwgQ1GlUrPKE+t2XvcCW5tTRF7j6SMYIuYG37XuzIW44JCZPa36evi0Oy2SnM664BlIaAuQcvg==", "dev": true, "requires": { - "@typescript-eslint/types": "8.26.1", + "@typescript-eslint/types": "8.28.0", "eslint-visitor-keys": "^4.2.0" }, "dependencies": { @@ -7352,9 +7307,9 @@ } }, "@vscode/vsce": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/@vscode/vsce/-/vsce-3.2.2.tgz", - "integrity": "sha512-4TqdUq/yKlQTHcQMk/DamR632bq/+IJDomSbexOMee/UAYWqYm0XHWA6scGslsCpzY+sCWEhhl0nqdOB0XW1kw==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@vscode/vsce/-/vsce-3.3.0.tgz", + "integrity": "sha512-HA/pUyvh/TQWkc4wG7AudPIWUvsR8i4jiWZZgM/a69ncPi9Nm5FDogf/wVEk4EWJs4/UdxU7J6X18dfAwfPbxA==", "dev": true, "requires": { "@azure/identity": "^4.1.0", @@ -9057,12 +9012,6 @@ "setimmediate": "^1.0.5" } }, - "just-extend": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-6.2.0.tgz", - "integrity": "sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw==", - "dev": true - }, "jwa": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", @@ -9472,19 +9421,6 @@ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, - "nise": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/nise/-/nise-6.1.1.tgz", - "integrity": "sha512-aMSAzLVY7LyeM60gvBS423nBmIPP+Wy7St7hsb+8/fc1HmeoHJfLO8CKse4u3BtOZvQLJghYPI2i/1WZrEj5/g==", - "dev": true, - "requires": { - "@sinonjs/commons": "^3.0.1", - "@sinonjs/fake-timers": "^13.0.1", - "@sinonjs/text-encoding": "^0.7.3", - "just-extend": "^6.2.0", - "path-to-regexp": "^8.1.0" - } - }, "node-abi": { "version": "3.45.0", "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.45.0.tgz", @@ -9761,12 +9697,6 @@ } } }, - "path-to-regexp": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.1.0.tgz", - "integrity": "sha512-Bqn3vc8CMHty6zuD+tG23s6v2kwxslHEhTj4eYaVKGIEB+YX/2wd0/rgXLFD9G9id9KCtbVy/3ZgmvZjpa0UdQ==", - "dev": true - }, "path-type": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-5.0.0.tgz", @@ -10118,16 +10048,15 @@ } }, "sinon": { - "version": "19.0.2", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-19.0.2.tgz", - "integrity": "sha512-euuToqM+PjO4UgXeLETsfQiuoyPXlqFezr6YZDFwHR3t4qaX0fZUe1MfPMznTL5f8BWrVS89KduLdMUsxFCO6g==", + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-20.0.0.tgz", + "integrity": "sha512-+FXOAbdnj94AQIxH0w1v8gzNxkawVvNqE3jUzRLptR71Oykeu2RrQXXl/VQjKay+Qnh73fDt/oDfMo6xMeDQbQ==", "dev": true, "requires": { "@sinonjs/commons": "^3.0.1", - "@sinonjs/fake-timers": "^13.0.2", + "@sinonjs/fake-timers": "^13.0.5", "@sinonjs/samsam": "^8.0.1", "diff": "^7.0.0", - "nise": "^6.1.1", "supports-color": "^7.2.0" }, "dependencies": { diff --git a/package.json b/package.json index e7c3e0eab..babcfdf35 100644 --- a/package.json +++ b/package.json @@ -1652,19 +1652,19 @@ "@types/lodash.debounce": "^4.0.9", "@types/mocha": "^10.0.10", "@types/mock-fs": "^4.13.4", - "@types/node": "^20.0.0", + "@types/node": "^20.17.27", "@types/plist": "^3.0.5", "@types/semver": "^7.5.8", "@types/sinon": "^17.0.4", "@types/sinon-chai": "^3.2.12", "@types/vscode": "^1.88.0", "@types/xml2js": "^0.4.14", - "@typescript-eslint/eslint-plugin": "^8.26.1", - "@typescript-eslint/parser": "^8.26.1", + "@typescript-eslint/eslint-plugin": "^8.28.0", + "@typescript-eslint/parser": "^8.28.0", "@vscode/debugprotocol": "^1.68.0", "@vscode/test-cli": "^0.0.10", "@vscode/test-electron": "^2.4.1", - "@vscode/vsce": "^3.0.0", + "@vscode/vsce": "^3.3.0", "chai": "^4.5.0", "chai-as-promised": "^7.1.2", "chai-subset": "^1.6.0", @@ -1680,7 +1680,7 @@ "prettier": "^3.5.3", "semver": "^7.7.1", "simple-git": "^3.27.0", - "sinon": "^19.0.2", + "sinon": "^20.0.0", "sinon-chai": "^3.7.0", "source-map-support": "^0.5.21", "strip-ansi": "^6.0.1", From 61f8df757093583279e13db6d167a679c569c322 Mon Sep 17 00:00:00 2001 From: Paul LeMarquand Date: Tue, 25 Mar 2025 09:47:02 -0400 Subject: [PATCH 63/86] Use `synchronize-request` in LSP tests on 6.2 (#1461) The tests used `workspace/_pollIndex` to determine if sourcekit-lsp had finished indexing, however this hidden request was removed from sourcekit-lsp recently in favour of `workspace/_synchronize` Issue: #1457 --- .../LanguageClientIntegration.test.ts | 17 +++++++++- .../utilities/lsputilities.ts | 31 +++++++++++++++++-- 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/test/integration-tests/language/LanguageClientIntegration.test.ts b/test/integration-tests/language/LanguageClientIntegration.test.ts index 883cc49a1..db33e8d87 100644 --- a/test/integration-tests/language/LanguageClientIntegration.test.ts +++ b/test/integration-tests/language/LanguageClientIntegration.test.ts @@ -20,7 +20,11 @@ import { WorkspaceContext } from "../../../src/WorkspaceContext"; import { testAssetUri } from "../../fixtures"; import { executeTaskAndWaitForResult, waitForNoRunningTasks } from "../../utilities/tasks"; import { getBuildAllTask, SwiftTask } from "../../../src/tasks/SwiftTaskProvider"; -import { activateExtensionForSuite, folderInRootWorkspace } from "../utilities/testutilities"; +import { + activateExtensionForSuite, + folderInRootWorkspace, + updateSettings, +} from "../utilities/testutilities"; import { waitForClientState, waitForIndex } from "../utilities/lsputilities"; async function buildProject(ctx: WorkspaceContext, name: string) { @@ -42,11 +46,22 @@ suite("Language Client Integration Suite @slow", function () { async setup(ctx) { workspaceContext = ctx; + const resetSettings = await updateSettings({ + "swift.sourcekit-lsp.serverArguments": [ + "--experimental-feature", + "synchronize-request", + ], + }); + + // Restart the LSP after changing its settings + await ctx.languageClientManager.restart(); + await buildProject(ctx, "defaultPackage"); // Ensure lsp client is ready clientManager = ctx.languageClientManager; await waitForClientState(clientManager, langclient.State.Running); + return resetSettings; }, }); diff --git a/test/integration-tests/utilities/lsputilities.ts b/test/integration-tests/utilities/lsputilities.ts index 066b791ce..a3f8e3fc0 100644 --- a/test/integration-tests/utilities/lsputilities.ts +++ b/test/integration-tests/utilities/lsputilities.ts @@ -15,6 +15,7 @@ import * as vscode from "vscode"; import * as langclient from "vscode-languageclient/node"; import { LanguageClientManager } from "../../../src/sourcekit-lsp/LanguageClientManager"; +import { Version } from "../../../src/utilities/version"; export async function waitForClient( languageClientManager: LanguageClientManager, @@ -41,10 +42,34 @@ export namespace PollIndexRequest { export const type = new langclient.RequestType(method); } +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace WorkspaceSynchronizeRequest { + export const method = "workspace/_synchronize" as const; + export const messageDirection: langclient.MessageDirection = + langclient.MessageDirection.clientToServer; + export const type = new langclient.RequestType(method); +} + export async function waitForIndex(languageClientManager: LanguageClientManager): Promise { - await languageClientManager.useLanguageClient(async (client, token) => - client.sendRequest(PollIndexRequest.type, {}, token) - ); + if ( + languageClientManager.workspaceContext.swiftVersion.isGreaterThanOrEqual( + new Version(6, 2, 0) + ) + ) { + await languageClientManager.useLanguageClient(async (client, token) => + client.sendRequest( + WorkspaceSynchronizeRequest.type, + { + index: true, + }, + token + ) + ); + } else { + await languageClientManager.useLanguageClient(async (client, token) => + client.sendRequest(PollIndexRequest.type, {}, token) + ); + } } export async function waitForClientState( From cef4e6cae786dacd1d10a98a6062a50bf96ab112 Mon Sep 17 00:00:00 2001 From: Paul LeMarquand Date: Tue, 25 Mar 2025 09:47:11 -0400 Subject: [PATCH 64/86] Fix `swift-testing Runs parameterized test` on nightly toolchains (#1459) The format of the ID used by parameterized test cases has changed recently in the nightly (6.2) toolchains. This ID doesn't matter much to the extension, it just needs to be unique, so only the tests need to be updated. Issue: #1458 --- .../TestExplorerIntegration.test.ts | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/test/integration-tests/testexplorer/TestExplorerIntegration.test.ts b/test/integration-tests/testexplorer/TestExplorerIntegration.test.ts index 0dd40e008..77950b546 100644 --- a/test/integration-tests/testexplorer/TestExplorerIntegration.test.ts +++ b/test/integration-tests/testexplorer/TestExplorerIntegration.test.ts @@ -631,11 +631,26 @@ suite("Test Explorer Suite", function () { const testId = "PackageTests.parameterizedTest(_:)"; const testRun = await runTest(testExplorer, runProfile, testId); - assertTestResults(testRun, { - passed: [ + let passed: string[]; + let failedId: string; + if ( + workspaceContext.swiftVersion.isGreaterThanOrEqual(new Version(6, 2, 0)) + ) { + passed = [ + `${testId}/PackageTests.swift:59:2/Parameterized test case ID: argumentIDs: [Testing.Test.Case.Argument.ID(bytes: [49])], discriminator: 0, isStable: true`, + `${testId}/PackageTests.swift:59:2/Parameterized test case ID: argumentIDs: [Testing.Test.Case.Argument.ID(bytes: [51])], discriminator: 0, isStable: true`, + ]; + failedId = `${testId}/PackageTests.swift:59:2/Parameterized test case ID: argumentIDs: [Testing.Test.Case.Argument.ID(bytes: [50])], discriminator: 0, isStable: true`; + } else { + passed = [ `${testId}/PackageTests.swift:59:2/argumentIDs: Optional([Testing.Test.Case.Argument.ID(bytes: [49])])`, `${testId}/PackageTests.swift:59:2/argumentIDs: Optional([Testing.Test.Case.Argument.ID(bytes: [51])])`, - ], + ]; + failedId = `${testId}/PackageTests.swift:59:2/argumentIDs: Optional([Testing.Test.Case.Argument.ID(bytes: [50])])`; + } + + assertTestResults(testRun, { + passed, failed: [ { issues: [ @@ -644,7 +659,7 @@ suite("Test Explorer Suite", function () { text: "Expectation failed: (arg → 2) != 2", })}`, ], - test: `${testId}/PackageTests.swift:59:2/argumentIDs: Optional([Testing.Test.Case.Argument.ID(bytes: [50])])`, + test: failedId, }, { issues: [], From 15c4e6bc224d32a732ba48c75cfaea24b8edb932 Mon Sep 17 00:00:00 2001 From: Matthew Bastien Date: Wed, 26 Mar 2025 11:32:31 -0500 Subject: [PATCH 65/86] convert "pid" property in launch configuration to an integer (#1465) --- src/debugger/debugAdapterFactory.ts | 23 ++++++ .../debugger/debugAdapterFactory.test.ts | 78 +++++++++++++++++++ 2 files changed, 101 insertions(+) diff --git a/src/debugger/debugAdapterFactory.ts b/src/debugger/debugAdapterFactory.ts index 8bea3adde..b42bcffcb 100644 --- a/src/debugger/debugAdapterFactory.ts +++ b/src/debugger/debugAdapterFactory.ts @@ -88,6 +88,29 @@ export class LLDBDebugConfigurationProvider implements vscode.DebugConfiguration launchConfig.program += ".exe"; } + // Convert "pid" property from a string to a number to make the process picker work. + if ("pid" in launchConfig) { + const pid = Number.parseInt(launchConfig.pid, 10); + if (isNaN(pid)) { + return await vscode.window + .showErrorMessage( + "Failed to launch debug session", + { + modal: true, + detail: `Invalid process ID: "${launchConfig.pid}" is not a valid integer. Please update your launch configuration`, + }, + "Configure" + ) + .then(userSelection => { + if (userSelection === "Configure") { + return null; // Opens the launch configuration when returned from a DebugConfigurationProvider + } + return undefined; // Only stops the debug session from starting + }); + } + launchConfig.pid = pid; + } + // Delegate to the appropriate debug adapter extension launchConfig.type = DebugAdapter.getLaunchConfigType(this.toolchain.swiftVersion); if (launchConfig.type === LaunchConfigType.CODE_LLDB) { diff --git a/test/unit-tests/debugger/debugAdapterFactory.test.ts b/test/unit-tests/debugger/debugAdapterFactory.test.ts index 60bda3226..ebc48f7e1 100644 --- a/test/unit-tests/debugger/debugAdapterFactory.test.ts +++ b/test/unit-tests/debugger/debugAdapterFactory.test.ts @@ -46,6 +46,84 @@ suite("LLDBDebugConfigurationProvider Tests", () => { }); }); + test("allows specifying a 'pid' in the launch configuration", async () => { + const configProvider = new LLDBDebugConfigurationProvider( + "darwin", + instance(mockToolchain), + instance(mockOutputChannel) + ); + const launchConfig = await configProvider.resolveDebugConfigurationWithSubstitutedVariables( + undefined, + { + name: "Test Launch Config", + type: SWIFT_LAUNCH_CONFIG_TYPE, + request: "attach", + pid: 41038, + } + ); + expect(launchConfig).to.containSubset({ pid: 41038 }); + }); + + test("converts 'pid' property from a string to a number", async () => { + const configProvider = new LLDBDebugConfigurationProvider( + "darwin", + instance(mockToolchain), + instance(mockOutputChannel) + ); + const launchConfig = await configProvider.resolveDebugConfigurationWithSubstitutedVariables( + undefined, + { + name: "Test Launch Config", + type: SWIFT_LAUNCH_CONFIG_TYPE, + request: "attach", + pid: "41038", + } + ); + expect(launchConfig).to.containSubset({ pid: 41038 }); + }); + + test("shows an error when the 'pid' property is a string that isn't a number", async () => { + // Simulate the user clicking the "Configure" button + mockWindow.showErrorMessage.resolves("Configure" as any); + + const configProvider = new LLDBDebugConfigurationProvider( + "darwin", + instance(mockToolchain), + instance(mockOutputChannel) + ); + const launchConfig = await configProvider.resolveDebugConfigurationWithSubstitutedVariables( + undefined, + { + name: "Test Launch Config", + type: SWIFT_LAUNCH_CONFIG_TYPE, + request: "attach", + pid: "not-a-number", + } + ); + expect(launchConfig).to.be.null; + }); + + test("shows an error when the 'pid' property isn't a number or string", async () => { + // Simulate the user clicking the "Configure" button + mockWindow.showErrorMessage.resolves("Configure" as any); + + const configProvider = new LLDBDebugConfigurationProvider( + "darwin", + instance(mockToolchain), + instance(mockOutputChannel) + ); + const launchConfig = await configProvider.resolveDebugConfigurationWithSubstitutedVariables( + undefined, + { + name: "Test Launch Config", + type: SWIFT_LAUNCH_CONFIG_TYPE, + request: "attach", + pid: {}, + } + ); + expect(launchConfig).to.be.null; + }); + suite("CodeLLDB selected in settings", () => { let mockLldbConfiguration: MockedObject; const mockLLDB = mockGlobalModule(lldb); From 882a038554cef7cbeed218dedb2e9c49fd304111 Mon Sep 17 00:00:00 2001 From: Paul LeMarquand Date: Wed, 26 Mar 2025 14:53:42 -0400 Subject: [PATCH 66/86] Resolve session promise on test run cancellation (#1466) When running a standard test session (not debugging) the xctest process was terminated when the user hit the cancel test button, but the promise to continue and mark the test run as ended was not resolved. This kept the UI in a state that looked like the test was still running. --- src/TestExplorer/TestRunner.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/TestExplorer/TestRunner.ts b/src/TestExplorer/TestRunner.ts index bf79fd20c..35a4bb644 100644 --- a/src/TestExplorer/TestRunner.ts +++ b/src/TestExplorer/TestRunner.ts @@ -776,6 +776,7 @@ export class TestRunner { // If the test run is iterrupted by a cancellation request from VS Code, ensure the task is terminated. const cancellationDisposable = this.testRun.token.onCancellationRequested(() => { task.execution.terminate("SIGINT"); + reject("Test run cancelled"); }); task.execution.onDidClose(code => { From 82fb69cb701ffc43ba87f35c12b83db9b8b70c74 Mon Sep 17 00:00:00 2001 From: Paul LeMarquand Date: Fri, 28 Mar 2025 18:45:02 -0400 Subject: [PATCH 67/86] Enable synchronize request in sourcekit-lsp options (#1463) --- .../LanguageClientIntegration.test.ts | 17 +--------- .../utilities/lsputilities.ts | 32 +++++++------------ 2 files changed, 13 insertions(+), 36 deletions(-) diff --git a/test/integration-tests/language/LanguageClientIntegration.test.ts b/test/integration-tests/language/LanguageClientIntegration.test.ts index db33e8d87..883cc49a1 100644 --- a/test/integration-tests/language/LanguageClientIntegration.test.ts +++ b/test/integration-tests/language/LanguageClientIntegration.test.ts @@ -20,11 +20,7 @@ import { WorkspaceContext } from "../../../src/WorkspaceContext"; import { testAssetUri } from "../../fixtures"; import { executeTaskAndWaitForResult, waitForNoRunningTasks } from "../../utilities/tasks"; import { getBuildAllTask, SwiftTask } from "../../../src/tasks/SwiftTaskProvider"; -import { - activateExtensionForSuite, - folderInRootWorkspace, - updateSettings, -} from "../utilities/testutilities"; +import { activateExtensionForSuite, folderInRootWorkspace } from "../utilities/testutilities"; import { waitForClientState, waitForIndex } from "../utilities/lsputilities"; async function buildProject(ctx: WorkspaceContext, name: string) { @@ -46,22 +42,11 @@ suite("Language Client Integration Suite @slow", function () { async setup(ctx) { workspaceContext = ctx; - const resetSettings = await updateSettings({ - "swift.sourcekit-lsp.serverArguments": [ - "--experimental-feature", - "synchronize-request", - ], - }); - - // Restart the LSP after changing its settings - await ctx.languageClientManager.restart(); - await buildProject(ctx, "defaultPackage"); // Ensure lsp client is ready clientManager = ctx.languageClientManager; await waitForClientState(clientManager, langclient.State.Running); - return resetSettings; }, }); diff --git a/test/integration-tests/utilities/lsputilities.ts b/test/integration-tests/utilities/lsputilities.ts index a3f8e3fc0..30173287d 100644 --- a/test/integration-tests/utilities/lsputilities.ts +++ b/test/integration-tests/utilities/lsputilities.ts @@ -44,32 +44,24 @@ export namespace PollIndexRequest { // eslint-disable-next-line @typescript-eslint/no-namespace export namespace WorkspaceSynchronizeRequest { - export const method = "workspace/_synchronize" as const; + export const method = "workspace/synchronize" as const; export const messageDirection: langclient.MessageDirection = langclient.MessageDirection.clientToServer; export const type = new langclient.RequestType(method); } - export async function waitForIndex(languageClientManager: LanguageClientManager): Promise { - if ( - languageClientManager.workspaceContext.swiftVersion.isGreaterThanOrEqual( - new Version(6, 2, 0) + const swiftVersion = languageClientManager.workspaceContext.swiftVersion; + const requestType = swiftVersion.isGreaterThanOrEqual(new Version(6, 2, 0)) + ? WorkspaceSynchronizeRequest.type + : PollIndexRequest.type; + + await languageClientManager.useLanguageClient(async (client, token) => + client.sendRequest( + requestType, + requestType === WorkspaceSynchronizeRequest.type ? { index: true } : {}, + token ) - ) { - await languageClientManager.useLanguageClient(async (client, token) => - client.sendRequest( - WorkspaceSynchronizeRequest.type, - { - index: true, - }, - token - ) - ); - } else { - await languageClientManager.useLanguageClient(async (client, token) => - client.sendRequest(PollIndexRequest.type, {}, token) - ); - } + ); } export async function waitForClientState( From a1a6b0fc69b229280cbef82ca13bf410a6e03571 Mon Sep 17 00:00:00 2001 From: Matthew Bastien Date: Mon, 31 Mar 2025 09:07:32 -0500 Subject: [PATCH 68/86] add documentation for installing pre-release versions on the marketplace (#1467) --- CONTRIBUTING.md | 14 +++++++++++++- docs/images/install-pre-release.png | Bin 0 -> 772226 bytes 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 docs/images/install-pre-release.png diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a7f02d894..1dcca8aaa 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -32,7 +32,7 @@ When you first open the project in VS Code you will be recommended to also insta To run your version of the Swift extension while in VS Code, press `F5`. This will open up another instance of VS Code with it running. You can use the original version of VS Code to debug it. -### Installing a pre-released version +### Installing Pre-Release Builds If you'd like to try out a change during your day to day work that has not yet been released to the VS Code Marketplace you can build and install your own `.vsix` package from this repository. @@ -54,6 +54,18 @@ Alternatively you can install the extension from the Extensions panel by clickin If you'd like to return to using the released version of the extension you can uninstall then reinstall Swift for VS Code from the Extensions panel. +#### Pre-Release Builds on the Marketplace + +Occasionally, pre-release builds will be published to the VS Code Marketplace. You can switch to the pre-release version by clicking on the `Switch to Pre-Release Version` button in the Extensions View: + +![](docs/images/install-pre-release.png) + +Switching back to the release version can be done by clicking on the `Switch to Release Version` button. + +Release builds for the extension will always have an even minor version number (e.g. `2.0.2`). Pre-release versions will always be one minor version above the latest release version with a patch version set to the day that the VSIX was built (e.g. `2.1.20250327`). These rules are enforced by CI. + +The version number in the [package.json](package.json) should always match the most recently published build on the VS Code Marketplace. + ## Submitting a bug or issue Please ensure to include the following in your bug report: diff --git a/docs/images/install-pre-release.png b/docs/images/install-pre-release.png new file mode 100644 index 0000000000000000000000000000000000000000..d15c36905542ca34511d66d8a0894043c02f5cf4 GIT binary patch literal 772226 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 z z)zYWxoQ6dH{S!w&{BSbeJ86C@D?bw>qmNnl=8u$*P%f4eJ8qM|3vWo;iQJ|Z7*8Y? zEbPw5FoTFO*j97X<8~+ny`x7EaVNm59Bg2zyA}ty_zMCrk82hBhF$GwR+XX!+g)%{ zy1GxJln1H1Iv*2M%9!oo3;s54Y98_9yx@vfXkjxj>58|*)S0GI29XAa3;R&o< z$XwPyxU2fMfLcGJuK<_mz-9N6tn+^4Jhb#s3Sb_^B=#HMRtF@bW1>(sa;7UCLa`|26i_&e3CpH!7Q>K1|aZtn8~eC{D1!p|OJLk|0$d7

J!}4HFZ`j^BdKTMsJ|oR80U3WfPmH?;NqT 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 z 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%0Aq)_sx8U; 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%0A4xizMw&f#CU63Z+BYr@M zI`O%`&mbG+sS@7&50i~d;roh^0}FqsX)NqCq-mebFJW;zp{7zk<{7T-``hOfU(TqB zj;`NuDSVP0u34@1oLxxiz}z4Cqh&UJn&j2ACgf~7s|mq=2>(T_Biu_M5pdXilOy>E)V2oa1RVwa9KQ0hsG<6QvO z;-mG~0AcELr<8>Xp`7n_a}!Q)*5@ict47S#qukCy!cBHOh-DEl@>kDuqCNrkVTD~m2imLAdqB1&5idR&D7a_Zl{zxM-;%&_Vr`6 zWpaO=Wag9(lYsTSt`^`VE~frcy2+$c`n2V*+Tya?U2Q%>Jlyzv^09IM269VSYmLu{ z^6STR!y0>kK?SYJ>Q}utr)sP0Lh~x@UMO^>i&z6_3c_+K-<@SD|Do9%eq+hzg(?z) zIKyYw!nR9{m-)TC8eSxprWdO_Oyyb%MrAkscqF8sL4Mm*y_8o{oMydRr`hUA#S?x% z0^0v@+P;*q8feTrsWjo}!JZ-O3WGyo{VCcS;PASl)HFfyvv^;~tn2orJX4FL48w|} zkw~VBp6(annca;Mw&cKe5cylCw0MHLB*h(BU3Z6G+iO>b!Vl(;K#Xlgk5M{xGvETu zgFU|;f;6;d9T(fwLDAFw^lmf&@Ql~ZR)l@k)Ik~fjaJ^Gsb~XHR`AXi(rvCX;lTbFU@Qt zS^Tuq-`L-K+mm=J9R>^7pT@4Fg!69ECi;4fMi+L#1akUiVbFLsy(* z4&P$1M%PH9@1~-0hHIhlNHl(`W`#LxcPmf1rq&+LqwfUf+hf7}etvuh8)N2};#go81;zJqa)3rjx{=s17sbXzdG1R_nit@bLXq;7=& za=Z>|(-VyzOYHUa#x%zF!(#3ADB4&KL;%@5qH2%mnDc_eh*dDSmJ# zCK7j`5LC42Yk2m7*Dlv(XReVs;Wfk%yd_itUdN>7%>ekY`YZNw#)A1%ALBVs=GW}g znRws}HN6F4O75kKwoBAGY*pSg=K^hrv4)&QTI%QJODUnjtk#&R*Sd(|1p`2a;JW#B z?kUrGSy~md>z+yMu$jVt=yyUN@@oQfW0Go)K17>XOvQQG1UZ5yKR{%3E5nWE1HW~( zO!)w7!UxS{rDILOT*@aHK_J^(Z{L2ipGkGw$2makawwcA^Z7_J2QuBVpOCX+!@8I-wB^u^}McSN$ z%(^eOrfZ?>_`nVf1Znl}5n?}9gm^S$m#dDy5WWuo7U>3rD|!Q+#(cjckf!nxDZ~e} z@ct+2YOKJO!zUX9~x_+xfNN_orS_i9}fI$)}ghF{lA{ zOuCpOk1AU7gU0eW+o|iJeZ0}QDk)4*gV}^ZV(;h&S$8y4WdnZEc-9SttO1~3r zH?0Xp%rzv^_}7>hV3*UYkv_V8V77=3reRR(s;GNb$8|ZK9227Y@(PqZxJ}Glk9GWx zGfKt*Jl5`C0rUK{+bVEHPxOD@bpMkD$aoi5K_PH2gS+TssV?X*^IIGH?>OT+ob&r2 z!)=UGqyCyrI8*wq1Rq9(76!wSK|i^ev+X8pBCSUYGgu6_dQMaYYp(t9lXNbt5u0fX z558jl2;WNTPL{~66HOH(+^i2r`#Jb=6L?pBYDF#j$TgAzHWH89^MLd?Vju9HFIkqv z#t5|k%axuW`;z`-6ZBUeQy|*J?Y8OS-g3LQFaXM~Le%vNN08VkZz4 z2OJSOo+mpyKu6^{NMR-f0y3YyR1oyui(`#QSr3J$2O#X%w3;XPX=jgEg>hXkEf+AF z4uIZm7#4^tjr*Q&|A#zLBUl8^){|1jd{AOQx}66mV3@vBIm}uWxWBJ{6OUQmRL4U7 zO?e)OIIJqWf-%IIV+;5a=XNc_GOGzPq-!#akAF*&c4(Soy`AT6(jWR4I)(xW$mYNo zR1v0BE-F0&1(_2u{DW=%Ojk&ufE<7TZxQ0;g;Jj~vb3b(+rQm(0%Q0E`Z;mrE~918 zS%$v;Y%pP)krQ=$(AHLT6G9@Im)lzEZP)*dC}-1q~#nlO>~Ojv3F7=l_<=tC`&I_Z;{*lwilNt}!s* z9UYj=Cn-LMuXoa|z%bh_>P>~~czIfF44!S1Hi{S~SYCJp=!_@1cjkahYKLwokX=dx=u3HJ|8$^^-NWvi|?TS%!sv`_}Sj z?pODrE7gLr93}0=%%g4+)f!H+-}IKYe?VnU=3+Qc+;i*djXhtv!I(9JcXT5gsE?DQDOcYQrWZfUYKA0sfY$B=7B>rreLTQA z*XcmnhRh>2N$%XIs4E?Az>ayJJdV+w*PrE_;Ze_7ADL`VgzAkMED8$1ZXW-_Pg_vF zb0#08rQF)%^3!%5tod`meStM{b2ZrFi=Ohn zy7%5ar`Wbt>g96{J#Ad0ngpZcwFnv^#D&a$HK=hLuv>JNFE7C6?QcG?)^9VPk^v5b zaXZ|lf$x!IFm)s8S;V&Ay9Q4c487UL_s%CO5FDUt2o^JsS8tN4srw%7++7|<3x2Aq0RzTZwk+T)+rIpkHx-dMJ( zXU}dPRT3Gm?R}4Zup=e$jwnY(K=3swiS<9$D5O{II`$iE0`A(Bzt$qBm%5sIiyyv=sGXZz0MRe{wv207{-_{x zrJ@s+%|?{*{I|%TjbMZP-z z-|O}BE{0PeRrjW`^X6#hyhA^46$1_T&wAV6LcXz`u@KcdfN+t!yUbWYux&+nEY|bs z=7$3ieDD?or<#hfl@r|+;Td|Qx(<|x-`Gj5#_g|Qe$W&BChKaD|xp)MzjT1-8AkmT~sz#ZTJ>x_WlPjDV#_K$ZfBC$o2%>zgR!lee>R zMi(6u=;6^kHEE`#`vCGyr16DWIGFS%6VVzu%Ve@sEN!{kg@4#O-$+f61+c&^8tVA7D=^#df zuvNe@%N4l#oY)rPXQ)y^y_j!p5erx%bEHwC9;2JFe003@M;K(#Mwp}4nT(h zngu%m0@@l=H3wuP{7qqF7{@r#X*8$>5FPb4KY|(R3xnkhG3}VN-^>bYQUXcDH%jf^ z%i~5-K9UsA56q5c5Xfr(p0ur`OVpz7^(t7PPIt;^b8NC#9k^TF(7-fk&1eF70G<%1Jh~^ZYme7}!o|Y_0-b+?+@4(iGJE6(M zml+^|%(t=)TddqpNcNe-RpD3BWAkToA&Hd=)!CBdI+dRs%f)~#dj!aeCl>WW?V(ii zRfMnsaWte{D;>_jcGY+=3|kyF1W;%Wg*eUMpfx*Y?+Hs8sPy=D6|Vs#drwU&!D-+C&2Z`yem+}kRc_WD zW#RNKZo8(5vi8+T8*D~TB-~)FS`_&9SBxRvha&<6x>c3YLs%0In8p_UbwL$2PW!C$6jqpRBLF&b*)eKv=AGTNdZn>)ogcRZgGZs2lUQtz$sbV~` z&f-!2mQ!NCH9!^kne!?1z)&+JT>kt2|QxJ1K!KN^48@%2aDj{^(!wxM~27(y0W-@EK zX#ogC!B9OuSRX!lsdB+Dx!?Zm7hdyO^(%87CiA@ivatLMpUK}lJnSVOfFBZ3{K#wh z>ErS^f;4c&My~qU{rkZjf-F<@6~0}Az1c} z_g}i4ktDj$9hP_JgyJg6!+3K>RZ`jaA^11Q9f!ojktyKs=MCS}E(Wi5q>(Wea^hzz zYs)+&(IHkrT%7-zf3uOEUgMtlusFkVV`potc<0SaLeG~iPv$`yq+An<9u1h7w~nat zdJ<$!Jk}lx@{rK{x|MDDk+X))+vd6kE3E*So<1%v@g@GSMtiHh)^g!)L zxS!vbHm8Y}fA5QlHAyvhpExT2%_E&@bXJ>Q2ac+x-EbKnW<})g(%|>JaCavjlK|DD za9<7#L}a653Mh7%2bY>I9q+t2Fid8vt+_~Kr{7of*=r_D)*9Oo;~yul*@vA}ieUuo z0+*wrx0ZtVyDD3pyi7NLpuZN|Fkk5xk$6K+Ho=-qoPGUAx1Z)H|0Ju(J4Qp*GvT0z zqtQJ3c+RJzy`MCo=FyL5sa!Uyc)6e=DgG5VJkg|((v9_=u-%E!$VpbuvqJ0g+A1wW zsD*s!k7)BwASJ{Wa{U4XMTZy#vt=S`pQ?a8?lM4O6htF=Qj;{B!1b{{_cE&X&)EtK zgFmFcPXYue$%pyO0?6AftBX1xmYnxt|LCs!m(N-UzD#}zh;Z+rQ@!!M)xT0$yf=)I z#UbWSonjz^CSo@y4}@KHr-;hk<7fa0LHGDDmy|ty?P;w52^uzeQQ)198 zm}O77otbm^m{494-&GJnvAC3@ru!=@*pU?aY~I!FpHac%6hifpD^K*jI>)F7BdbCt zH9{j*BU#G5{7F`Rsnwe#MC6&gEaGG?rmP6f{fR_un0?sb6#vnQ_q^)qni9RtgB#NI z`5HKU^ zR)|=9MmWC1d^xcmLv8V%LUeWkz}bclasAy^`7b~CzyBlnjmgx@;HPdkD8Fp6Fz~rJ z((jgh+EZZDIM~z^)JOaHBQNf>aJ+3F6J;w35$-320tVf{KReuxY(_AfzzoWAUVz(mt0Cv;w|G$(EIY6c*+B5-dKlL|lH#B6QbAOS(CSpj_h-iz*!I z^d4Ppi~ivS+Gek~DeLWjiu(WHZ{r9X?V`HWGLq>tLJIpLltYHUu;ztUGzyt~QF5~P zLT_rK-M)qyB4TBLvsyvZ@_jsx)-4voqoPrS~mEKNEry~aO;?ut8@-(OPu0s42NX)*X1h#WfEEXWLk z)oLO8?^S<_wGdVtg_AOUq9qW%b#LtNn^6DrO8NU|kNv-XQJgROfnG}c>BSzc&qhMx zbYo7z6R03dxTPpp_9~aV3lh)_coa-xl0Y51<}TJR)SJne%^V|LqnvH9(pPL|@+DQH zf3-KxzZhf)B-1?XH7edtMLw-|P(8GRu`9c5jo(|B^o`_J>y;8Ggrb&Wj6V*Pfc*Ej zSO~K@#o(++3y*!VN5tA4do1*f$2s2&e~9%A@s1S@{8N899*Lb*cIrU%mF`ij+!wp~ zrt66Nj@Urwo8ETV0kfKg3FSBzG)aVWN~4t=(-VWZ8rG_GublCVcrVQBAzI$4x|?z# zeRdkOTJ#XQ4Nfx*#JRH|Hxjb_U{c>dUIcnf7lL>)46>{NB)ze3NZ-E$i40ChzrG0T zb(^5!k=(Ls-B4xw%oH`oMC^oPK>eOxt~V<@Kw_!tsg0Gj&%Ph;MaaAFgjs`P zU7CFS$P4WfbpQgYz-d+X0DFY<;DQ$9xPm+cwY4LdiRMZIQ|0`GKKu`Ag*kCg31N|F zyrwRFr1SZAd2>@ZUlZp#Bo}>k2=ajxgU=%WzoC8qolj${&8p2yc5F1JZYmCoIZsKr@vjG$Y?@lR_CA_&wo@B$SM} z5{D{EESXxt8NY~?MBWEP%4+6co&x=HtoY^|Y?v~NPQs}WD_h2GkB3w7yI_U5W?*gO zlFAo<004^?q<+5=F7-cPA?**5{NJCV)eyxdJGT@ShWEux1yjh$euV%;yE#C#o4zKD zpAJb-Ydv%NAgj|U3a;+#$I?;JQOAoCm`hBzV2L_PJ`hf>5^@Qs>jCglQtT_)X4i)OTeTiF&MCf(Oc}r! z_lVuA{#kv65O8lNtByo)Vfel!rPZoW`>U0UjVbR}goryt^}Gr_++Boy(GND_T=qTz z=0);)H6^9`k>Uovne4wjzBap2GN!CW;~;KTKGi0{0B7?KOo$NDPq%Tx zZ>fA3%?XtVsGlrE?W?8wL?R~wzVy3cv_n?9e|J-Ad3otJ?%m?_Dcc9%VbCR`KO zn3AzjZ!H&kw!tF$av+ISVXEAC9`s?cwPpY40r{72Ymf{j4zXx2SKslvxohbogsvWX zbZh!v>`4`KmfWa+L?3NLz_Vu$N)}q+tv+^OC%_Ni4P>~A6NYqvU^RPt%05DRi3ThN zSe3n}`3ReVO}8yY8)pVk9r0q9Fq&?bw-bcAtO7fm_hEM!yGky_j z8k{{hzy96@j|u`)TvF~pw_U92P>dzIVXF(j2e&r&fj)Vsx%7S2q^v3L&IbzvzORSc zuE0Rj%-@P0l~sH=`q$+xtF#Cb$bPvjxLcl#s__R7)SG|)m4XmbpPIRG*Ji27bvMeL znb@T_`=$#vQ||}(#Q&6o7^u+44|N%nWEt0LB5oIU=!ndL@l*h`oGHCKEX>*mu(h5i z%U@AXQvg@G6JoKG4r+fu&2m>2<+?#FZzx&WZ8kDSITc;s;DuPZ*RrdM;QrWUecR`O zII`cW@p-?=3fjAU21*&5A;m z>R$oc=PPkAMk0xgPhk#kus`PiJ*;BsP1F@!^&ZU_tzMDh(y$5J8S_u87o5#{Tx48q zj-sTmoh1!t4h$Rzh3CNpfrO9*!)O{7K|m7E^MNDXNn5O9#-uNQFv0j}zn|X~cq&?1 zo)jtj8aFxFW6R5*K(=+bA&CfK!Lf=zdPQfzbAGtF!dURqI4H{kTsO;7hBd9-= zHruas?SK>V_G<581GqSve^<0Zi7k{OX(`rn=Czrg#|0jNm)nKE1!bDly)lyG{}>x) z=NM4hRC?dwx^yz^j>p>jD4()L z{eC_QL`QIUG<(-7>*&}=6A9iRXygPz=+#fro!36~q{Q$0n>crB_KF>Kx@?TvR@}4) z&tO(uk^9J`)LvHomPrEsO5-bv%=+!o74w|aF61VtB!w%QS{6(}1li^1+7@bW%<1|v z(Mwn~BOs}}CK@@YppHH|Q5Y`(*?b3#D^=$9OX%<`b$QWS#8x0~Q8&2W?f*G-Koru( zE(WZnG>0)iqyDD{uaFCu(GZz2(fY<2tP|LS40TS0(c6JC4+6Va_6m#`S~ zo2NjNx^^1#D78^Agzs4`CzH#5IS?0)ms|W;2Zix_YjfL<_anHuOlGOf8i;xrQ`t*R zZ;8j9Q?>D*C|c!}uGf^Vs7k_S;us^GMQo<;d$B8LmIO5P*D2lrMwBqJ^2c3o)xS3h znvdqH*n#%d@9mvr5ngcS*jC@#1?@!J+?dFwi*9DHlNVOSmg!_!f&~*Eo3tKv8~J`4 z@rQF_7}CT^#Jz?0_D0RM&pL(1-E2BP%LhNF3djGnQTaV1_L^^qQ#^heG*Vv4@VJVo zf{?j|F|TXnyV#n1=n{7n8H8Q&%~BauQ=sZbo=JeHQ2@+@_) zNM5d%kb;OYi816v!BIcNW70obtYtDY3IaAKBjuDVZVCWfPVnAuatz}yB8C8X-)-vq zpL%ZRp+BII-4IEigboN4?GOb2TneT&*y}jiSL!vd`SVDX!j83QCUEo>yPsFH<(7i7 z>i>Q$`DGI?%`@+EB^Wz_qH+0x!~8Q(o1s<PC4?kcL!%ObvBu7 zXrcLw@hW{|HV?n8l2DB;{0kHwC&&Z96mml^=)%-`vz|^n=AMrskwsPUD7+d3!a5Co zj}R%W2Wk=<^WBmcJ|IyK4`Idv5k@7LSdiU6tp;MqB5Qa2y(ew_ILS|+p&mZwOO|kc z-Seq99$DQk+S=p+YB@OxH|0Eo_irY-_CePUnhe&Y{p%lKxzB*=OZRY)t-p2i-}o@W zrF$xOMLS(NGyaP|s~Xo)pAQjtuKT6RC}bf=5Q6{0T!2-82+v$fxkgcr!W6s6^Q}5a zX0gSa-oRPqfeWyn2lx*Zl5waznfraL*@OY0^b zx+n^IL(wHZcR4|0mbB~EwXF!HT$UDq;P5G04})}TdI_d$M|W#R3ssOpQo$>gNYh%~ zicSL?&dXG;KI)w}i*`f-Tl|q^Pd@s6Ae?lKRJ#mIrrrU-jaxM8V=n08VxYq;Pu<^y z*7q$@J)k4?p>YF6Z;92vC;n371QNen>h;ve|70F&uCIt+WMfRoL}6@!9{?=Aq~R!H z_2g?a8cd&JCt_SHQ{WZ@-@W)C_&OpL&rfAW^b8nqQ9RyDet2^G)qdCTZs>34a{JwK z09Qxwyi;BcHbnWO8ZHn@Gs6jzulSkZYKuA5vekB;J~2j1&hA~l`n@+Hx6(P>uGrFX zH8e3^^J(j%tmm(v`4D7wZ`+W6{F&nn-n+(3KP}15d3Ry?|E2n=#X_P zaztp~`e@>^QRIjqp1H~_mx;cw7kuVaEv!y;#nzZS4DuNqXXbPl9eIycB+I$w2E6AQ?0acZ)q#INQ4n_;j$OMN215uXJK+0dlIMowII7q?4hs(* z{y%r(|0b4HdNFdT zpBzHoy*AM7_pgnNB-xm2tnmcDccI1a-7cFFCparHqc(RkkIii5ui&D*B!8FI%oP=6 zY+?J|82UO8*;ft{zkgwDgafde_8WfyvPSHGmk;p8n^H%^C~sA$|M`}6U>hl0VUL*A zLR<@VrcIrSTfZiWgdh!c&~9FOUTDE7F{n#OSFwT;1g+Frz&{8(EH=L=E;t1&!;7U4 zQ5E7@N;+$GeW*7NL4wBMs@N4K6KZDyGCjk0+Sy~&>Z}r~n+R=?*6lJXpvYn3jXX17 zA8dPinvfw-1y9GFDH=pgtFMf7BCUitLbqR$srguyFr4R=3#8n&4S;ra{FaAr%6-(r z&jsnaHN%hD)G4ne*(LFnuDGH`lRv z-kFHLxX`Sp&=tlp$^-~kPUolP->W0Iof+>|8Zmuar!yiW{ExJ<02%0_8lY}&c0`j| zsvAkDqMRghsk)=IzkE#^4uQ>trE(xRyu6PMby}N#)uViS)Nl_{STY1sW@x zRJ~*R^US^-99QYhY8fJ*6rtkx19_pZ9G~}Rp+?@LJ=H6ewe zeieI4yFk^Vj{<29!R6p#4U8zRN}m8v>i@lW`9{on4~N+=j9is{90V6MUdhr~)$S=` zPM7=iLzd?>D*#_vM8*pd?GMtV2xE)2|m(=eys2sQ61^P4u#P97Y ztOf*A{Va|o9HHLY+wIuQiR5hA9??qFus+2Tn>>QnRqk*!Xu5hUqv$1@geRY}Se1+daZrZy0w`VnVLU|^BAOZ<>2GIoR0lbQ3n*@Jdc9kIRL;Z$ zSdrCx{P2nc^V3+^;pkQTl~MbraeTB_OTvSZboO2dtOVPmh#a+D z)70@ARPzbdpoi_}9dScFLTxxh^F%JL1MnSb%eao^^3=On^nhk5~89e>_*DJn@gBbsQNw!Yt zLm%)M`MV=8t_AUskT{!}@w(PxT@qLmC6fhLw+Q`lD^Sr5P!GtS%CNE-n-uE&gI||2! z{hqTJI@Owa`pFgvT#4+-aIxVarhs!Ow$N~xaveE&)p_fWCPo21W{;&k5*hgFqOI`@ zQWzRKT(Mnzo)|~T`>Y|9jN^t3mQnO_C~Xtu>JiVLSz={fa3#JD+AdUS|M#Xw&+&Dx z;U3!QDixcR(mo!@&2UsZwrSfUybqJqK7x@=RzM3sl8~KEP+qWE6FqISNghnbe{5gC$JG@V9KVH&2*`s&Rn$ zjpZp9-2@Wn?dhs=-%AXksTH`sudmwctsC^L%c`B&5jlMO4dX^P8wN6(d!+Zf%=OIzeH;8g+ivQW z<#fZ zOhFA?_gu?ZgNG5_*BG|tY^00%9%B@Pni3itE!MTg6R#*S7 zVOQw4qWUY@I@@B<6_PVH9PX%LYCy3cv8{SuxsT4@2p;`>iX z^=vA?-{}8VY8yro_zkRjknO;SJgq_teY>sKf#>M$l!>S9yAz--U;HG|QsHSQ!aF(qcE5)kvX>8Nu>;sCESA^b3styB# ziTBaOa{p7QcW^ZQ+izEQJ{sB)nCqRPa-M@3K$SrupwWR(vWF?~P+JNh7@`JB*+|yx z{&Du8M&vi#HSBO1jRqXoa%IDcz&f}+}4BNK6wd{eD%v;VnVeoG2jA3fht zbUm}Nqd{tKi`BK;O4^7@W3uc~sE{y{n45Qf;lMynE>PynQP5+fDUs{zPAB0j-ilL~ zjYDO>n+HW-rMYOCdoE|U&gHyO=No0;F)CWSNg=1;Z8hymaC-8tg%?5GZ}hdnv0;F> zkd$3&W$JXN$h81>Sn}A&v*^Pj_Y13uAvWC3tJiJ>xTibx-3weh0oWNT#;oH%KSf!^ zIL%*$S>X6p$A~!aMp)yK3ORsYH;9$G(dIE+9Dg89d<)uPURAMBd1xY;AgqLL zIbBP|<_t4k+h*|G)=JJ&{c{e?=LEa6(=RH42$sk2=9+5Il}xqmGiAyp9_H81EsCo3 zay4AHa7xPRQ_g=S!rP+K(VXo)g%+~*o6&w1-^xqy*K2D{r(~^2N}itYXS9Z;N4g7v zS!9?TV z<7Y+PzuT#j7@G>EXpG7aB)HBN!Hu;rm?GTo4TPrN?+|mW(n}r|QRI(D3`+ge272?4o|xa<|pT z^wPEL5eDp=pIAUxSLcogw;Fl9^`_S%e>ZKm4!A<1(wCt$qTJ?D^Sfc+cS6HOoablt z7KEZ$>fuupPnRL(Yhm0C*UsvBuACz}FCE*_?g}2RuUB`ZYb+~ypReA%$i#3J63*1? z7KIj8RR~W#?Jb)s-Jp7BJ82-ypuKkXDQmIORq0~mS-HVfMf1bt3!PxUhw1HG_glB_ z?nx9ImE_^k6x0x%JlzH07exi$<3}Io-=#BskZ|5Imna&VQeF#qY`}zH3czSP0Jr1_yF@#Radi}l z8@$Gs@j#?~)Z3Qe)F$?q@)qG3YFKHCC4*7DOq#HvmQUkMA(6y`ueD3p+)!OGFQM#vGNC;{3;3N?ur z->4fBYU|kHcdXlqF2dAOotbGfr?`H#GP{3tczV^5o$*tl^$4}r6hS+?37s=#R%a2+ zgwD+1!2zxpt|Xyf7HXOcdJ%%?zhO*kbR=-Y^Jq-Zi|=;l$wV4*bIe0$>dOO;!Bn4g zg)>!BxJ!H$#7j3tW@B|4n8oF9+j-UBJ?)Bnf=5WVu{Mw#*AY~kq6R#t^xgh97&s&8 z#7B|8XMuinV-XB396-K{v;B?WT8P)W??ynmuD-Fb7c$tbY4Vvhg}BI{{@H(0h&0{t zW3C=O(%?(j9xG^UKCHZB#H-;F@#|vP*P-CTRe{lB;e20V>hsT(a?j>I7UQm}M7@jh zM0Qrz@wnqx>zGDEN&TD~(H{G&9rHgM3l>umd_K>0G^H>@pA%FS%Je_yOO6b)sI{D* zMk{>vKB*H_DGZgpu0uFooG1C>b3Mh9)v1HlxVTYEOyvaMEqu-qtm+yW(8QBg86h64 zi2#<4p$&lqCd~&pE`P`)qmT47aFql6SDCZ*h7Ep}a^1b^qdb-+k9Pvxe0XxNEjXfz zw~u%#FiiJjC&=Zj41DM;roNAUb?EcNeB9aQ#n$!Y{#T?VS;Kunr-)bd_M&6=Npd}? zu)M+!Rf<}Yd^zar$0v>_G?g+bAd(CoB}AFD6~W82CGKunoK5Z%kM+~x^ zI&}LwyLYv0Fr;&oy0aji0>Tp>#$Q?$y0XFzv@FZcDY{CcHs4C!i*8tGKcBTCkw{pyN1?-A2}w{HTfClsm~x1>|CywQJqEPuj`^OfSLIt*pQ*An*{R^j9xt7SQ=UgOayL_0@4c$r-cNKDvp$wPIe2r+$kt6mEXrx)&S1wviN%> z{`A9)>R2Ywq9|K)dA|_N@}Q%a1tm{3EYd- zCGPfJQ9HU=|Bed^ElWFKCA>AI>wn`8SDVG2)<ll(#-x?@e66i;4oGu5dj2c%|8 z59B;Bbn#~~uxbSHtDKv4`Xr8cqUD|ojTXkfot>P+N3E|$vfoAyrHb;;NwUo|G?$(f zPil|;R>K_+L!(FON{#ACmM*npd6Ib+@u!_Dy)yqly3PWq%5Hu8uZU98CAsNt70Hd1 zlz>RLz(zp28w8Q=ZX_h6yFsLL(;d>?-QVK;zZ377IWyaV8D`|!oAo?v-S>T6zspmz z5v)HY0fZq(C3p2zYi#=(J7ikj*im-Ce(-T)#9LHy2MW}jqQi^qP(cy?GkJbyC}&_G zT&skUtw|rBaG_q>&Ey%Mn7K6>zABG#YJsSC=wq^NLI;gv)fjE2B^2%`eJ+IXsx6Ih z0xunaJenN#OQp6j9qLo~{(@j==?UtOn{MC3pPe4IKrJC44<=ra>t1}w0_jls>c`oo$MH!&I=jGw>Q_z4Yl z7ChanhEkjABo8j$^X|olDcP$+#@fxRgbgOvPhR7Y#oW6Z*zn{+Dh*5>i-h80Xq)ev z22TY6hfwt+e)?tpTHn7vJC5P>=NvDLTTG$Ggon3htxEgPE@3h?yW8^)Q?y~A}aEdZ}uA? zwTjKl737q-FYFNu$lc5%>-S>)AO?b}*OFCvMlfqtj## zbAA@1ZMvlK3sz>;lQg5fN4)2IU63f(DAzm2G}+5q%xpSa&+}OGSys5)Dn$h~_1-Ke zeV%(#`Miyy(aqwo`yn)L^fUZZI1r$c!9xuer^-^BtWp1at#bPvk%#G zz`}K4=V)wMHT5Z7V9^J67I12kO`=rh0XSn1)Wh~zk0CA_l#W0LS@(b^i5KQ_18Hs| zjRJ>Zd|M3X;0Ra$kPJ))vikObw$4G+_ZcHc4EXQoAF8?^R1FU@tG#*GeS0kDsYs3A zXRGVUwsOWGuQ%!+#z#2xx*&XGvfFry(i{m6ttftAmZN8XS!Sop6SK;&Bm?=aJ9V6`3y|M#L5<*dQin4fQW zfo`TG>-iah0g$53fhKAR;ddp3Fd%O10v~vQXcz&QrxW=k@Je~at#Ms z$FL%b6ClG}+XHI20jWJhXmSo{HhxxPXo=l^$TXHe0XoUp`jcUqDDIdCS*yvzv{Y`w zIpYL>e#Gjorl>|9t7sF*KKSUJ6uy4|Na`JyZRMA#fxSC z17j&PQ#XuJo(1luBp*uVpX6$E()s_b3;*W<=+_6)zkbuh=n%b4%Aa{_y4a{X>o0#{ zu0odJ8`;Q$csL?y3(~-lWy8LTISt)rB3QG`Nn9JqmkI2xg`tMO9}+#`Uk;U@Yh;(t zN>J=#a{8kziW-4T^w-o*xT!CnnKZTF^m+uc5SdV~`(k%sEo|4!L|3rGHMN7{>qc{&}V%9KXZ1+prb?Ukx!t<#w><{ z_h^Kxg}3x+jprM8tuKlp$gOeEmwXt6TUhvxeD`GMSM)-76zRh#wWnC}#2zR8-A_6U zQ(7ngY;`svBT5-EhF()Jkb_dzA{%~_5PMWWy~{jz$9+HV{UtS4zN6nJ=MK~a?;{gl z#+u%0gf^dMf^qfL`oR4N`f1zT<08@}DEaFbL>Z=h3Rz58@&>hs-KBd^J+n%G3$3VL zm95mL-6N;ro8T~2-5fuAM+WjGwd*TkR z!z&A-E86a!ifYh7NDs75Ho(GVLkT>X6r7@RY(2W_#hy2|&;h~o*}iL38y46i(6VEW zAbf~*eWfF60!_l>A;hj^necb}FZ+LZ$eX)``d;SyFh3;$#G<~_W!*LI_09j?O6XLnk5lnOqIlAlh)Rq?68zOBId5Ce4CGb70Rn}xo_3|; zt;hCG%4>Cc>qnd{153`4kop8j@A!sjQ~Y8VxywY*GVZnAYISYUEaz)XdHu9XU8HK0 z&bN!*m_F4c?rWl5CK7WzeS)r+1QwNU`bN|8b#wU!q7tzxF!nTF+-8^u3yNN~`G|5W z_hR^pn&*OrOjT9t?mAkn@=+>%&t9`K+AA_2Xv7MTWYP>7DW6RjB+Qh_fCTBijijS8 zTWxDGqqHYu#S6W}`Qr|iriFjz_nPgEqZ06@3LO<4r{|r0;C2qfAK$m!?^HU!IM0Sh z%4BPGCDfahRn3&_5>ukm7+_~u3KVIIG`L?4_mj~~+3wx-2|Lm7?->wU5O??=$YfFt zG#}7iGfoQo-?l~3baVGs9tXp9q}Jf4iR|5fF4Mep=_h7oJ1sY=rmSSoa~7~oR(y14 zYaPR$(X`&ld#;|HtQ#Aq{V3-LlBHaGqeMaV(3W zf{8*+n@=A*V0j>g-F44Fjol8MZQ(Oq> z5}Syyy{pe{LEp5VEC3y=EKo#b2HduDAg2=B4(7-r=X4fCWy!X+5tV-j2t` zl)&cfR|ay@VRC+^wjdD!J%OF%)n~kU_gQcww-7{f{kESe))ngH-iJLOPo`iGg$T_@ z@GAZewF!TZT^7X^diY>e%u?{rEkMg)&XTGsGHRU747Z?+qcTdi_T3)KSIynl@_BnF z)N>)#A?LqjwjPS1{{p5ofHZOPmc$$nletPK(G^oglc0v4|98C6-rzbf=xoHIi$(jq3WYr-VK3PeTKO|GVhNLs?7R86zUF;-~@r{7L zRi<^bUh6IPnG~Rrwv_aa6&KOdU{;x7NCyuC+PUeu~f{LL)v~UK*Vv ztQ4MV7OW8$H%0Pa7r_w%_PZUM>^oh!w51T27B{+^>j;Hz*y77$UW2o=iaktbcd@#C zZ#f_%e{@mdgRq`?_z?w1nYk-VtNYo68if(tiH}SKGrjx0tG6HWk5YH7V#Ru2&789y z=vV5nyrZlD-F-fEw8-{h^Rj|$dt%*4n?K=s77g?y1hjFGQeg^~o;7LtaI&@XW}71T zInjG0?aCiXS0l3}tNh%_j$OTx9jB_OW6CN$u5T+pur`m6)^X(K9(z{8&+6sV)=(i` z!^yZ2R1!`{CH}eP_iEAM-@>N#w5v^v?T7U!s1^12sXVJy+6oe;xXCznSntn;?Q(~$ zCg}N>(>VqHD!IOz*Vir|wyM?}$m}p(y-WRbVY@y`-R@S4lw@S4Z`3r;9ZYMwp`u)3 zGJEV@>EY@29!CA$z0U&Nm#-?(QjT@q?hP-AK2My}4AjFnHfu1kmfld%^B>jZmCwb5 z1v@+pbH@7MN;f?R`%wkGNKP61tA36c%NKsNr>Exy9du+0KLpZjBa#k?5E6LkCWf0c zbURX0E6_LN-_Na3FtD@<}kH%_h!*az`16nE)_Ug3O*oMkVI>ORlk%Ysr_j-tD;W+{;JD>Y&# zFe%nbN3n6RBYz~+vJy@=@7v60pnSZ>R=*Qj^Yx^?+$92|TWLC5w)+kulZ>ZeGF4_s zL4#_F{3)E=c~8Ddbe>4EWD#o-4<;=rZu_TK@o7yP%7!wAa z_w#q^x9?0o*8NH6phOWM!ZTJM4i3hGuw@^-ke8<84<)}4R;h`om`@wh6t>q55$$k3_4ab(_C0 zY0HB-{Eu1(MzFG+ikG=&yLiSFhxGeka@x=xP1I~$86Vwy$UC3vJOj2%Em?%$P zKoFPq#0j;d8%)grErf#7*26v|=Qeu%YTW~uG3A<$F2pv)CQMjgg2L5?jM}hKwe`=9 zr!;u<2E%beE23A4rLIe={??p-BaHYV;JGtLH{}3nN5$n=VjCtG9{am)iNR~CMf?oE z3BLoZaBG|`tlg4yytqNtuc0_Cp`zAV%(8Ab%uQ?I;GY<&*r7VaR(ud!7p7DQIIKF* z3Xk@%nM5SME0gjLrvl4ZSt}(SJWckh3z;Y`)zWC%QCULz^H-cak*wf85=x6UO6pZDC|(Tkd`&vS+7^mp0CT0GUM6yM+p2@y2lAh%MxfNP zk~cd;^Ke8+)!);8?Och_lbdbS77-(f34?I|j^rca%+Gvt^DxyHCqE|G7}A+XCB6s_ zg9(T~_Y9-Q5|w^S@hElo#Xzh$)6z}UH=APCM0`|;QCZ4Xb#C0KnpbDMGgk|)U-vAr z%xh291^bSUz0?=31TrFqv#N@p=v#|<>r6#NWTTsGvOE0>O1g5TWRN9=Bktu*Ca}Z$ ze|jF-&zFnuwNN`6Yj;vz9P&nWt(>8_5cy)FVj+77j(WXVaNOpuMdGRa}Cdks2~gx7p-^(x(sknOzv z*ziPc5rb}>xyCiPeF;qrphqMl21hOnmpY_Y;+p3i&W~uj*Avpe( zRO!-$xTZr9j1@FEQKHTg5%YCq8FXSFkiDqVmsF=n>+Lo_{L%iVFY?UC$G=2JJ8er9 zv-~?=>pb}|_izo}NGUg;lzLW`6;G$4gcsrohe@!(f z4>Mvdj6R!Ic*cPJ`#cJ(c%vk)S`nuVi{e-N4Zkv}tNc5==`q$%o#@$o=u*$z_0e#=B z0x*Z5W$ntnvz_xsH>5eH(L=xOMuuE6Fi!nIFKeHnkmE8Ci>@6O|C}l^cRD)oiEU)n z^XTB!HwTou!|9z6b3v0>%qin%9;=%6#i+}6885lXAg*31Ph&6qCb#!>cqF9CKTrT=Mp=QSmMa;kFQ{$;lXV%SDYB1bj6CdvvzhC**7tU=Q+M63An&XD_$awYkTv3c*SJT%qoEXf;O7%e zb-f_!?jkMM?$aN6<=KtZ#(Symo`KiZ3)4Eo_de)o*r;QwO6m&h&6+b~QO_E!1qOsx zFA5asH0SQM1zI%KKN#kexsSN~Bw*-0 z=2iU_VfkjKpEh|p)^r>_jT06U(tQe(e_L}`buwj~Uw$wfw-b)F-?c^;RuY(fQ6`Qr@!geKkrPqXK4=6tm3Q z0m!=7_UE%5S|NXiyoF&M{xkqtu$luHV3GZByr`WFWy-_L(B*c`y3YMReUdFRM|l+u zvl?GsKOC++l`Q{B^!W!uK=!uQHsk(jv1`J2V7lxMF7)A{vbNoiUe%H1XWu=IA-6RD z+gkQw68@6IXSD>Z3aVk;?n^TYJsql#i{Hy9mSh%1dWhYK-qt}#+$3MV<xK65=DnY;t43YPJwASOw{*L@+4OK}Ow~>VBW~V7K(*-GW zomJ~NT_t{6{7U_OINrorGp{l#jfk_OvCyV$QXXGkRzLCj;&gqi>-X#%%z19T40ATu z)d5vA*x+#!O#fToYke(p?3hz7B!3vzz`=2!tKuwwYP{nWbo)uh7M8rU_!XY;9@Ux!{*QNZ`jFIuG(eOaNd+UxFyN1NnzY=L*yB_VN z-?>nW(zdOTq-sT`V3+Ntg8iqfU47Xgh3izrMf*o6VB``w+e;RiP0965rH-mw8hXe# zi}jR+(q^KKcik{EZp}x_*4>O7g*UDy6#sVreXF3r#YdGvKH}+AYSAqj%yPHwDYdqw zV)_Z!xnx<#a^HHK$4(OH0FDG$E**bpxV&r<%OUnzaKAaV9Z}TvwY>$hfhg0ZOUe=N zsiMXSDX0-RsmBN)8M$79U9`0k)c#%qdFJI0$?P23W!Yu@HDW&W$l+zmx>_Pit@P^5 z&K)U1@%UkR{(Lj;s%OXU4urk5(^LYgp6aLJH03lYT2#GsGM%mn*g`>6_};W_a*;Y+ z&|Y1QPC4_3ZY<9|R?5SKunm!mS>2)DK|6Aa$0KeCsIJ<$wF3fj!lBjEpYtmMmWdgp z%Ou-scMO(J0bgTcnzL+@%4{=Lh62}iu1#!C zt7>&24#_hX!k2Uu3usdYx|QD0_F>8$*8BL^WTF2wCqU;+6x3;a}mC;q^5Sc<;A z=*4yvy}X>{?8B%DL&nzqN2kVvZ}s2%&G#OxFYGx;Mx*W3xM6bU)_2C+lo6f?kq7%P zV?BJJArYQeD`7~?!HeG^jc$Y4p_o&>gyvf(hV4eAk)d!Nu{*rSu7~KaRE`674eksQ zJ@cHGG@*lfe(YPNgDN*_x?3)L^x?FQ`0{F*@uXB%6(6TJp{#hWJMp;Oq$l0Y#ZM3H zmkfkX@(SxZdQz+8$hj|+3!FRzOxg*T88)Lhbnfuvj}=KY$2Ozfi!M%>pPn@o>NPC& zHmlMeOAzWGV0p7!DAR@uUaqeG@_HN<{s1q|E!XbI41;R?QBAZfc@pMP+s$rgE7%oO z9Ujp6$KdK@^8r|FCm=~P@BsfZ>l7B!Oa(?Ua;5*NrvAuCHRyCoi};=WxLYHJH{}S+ ziNeURJ1qzeYDy_%7#m(2xw}9zrdzhgQXjcRd}Y3NKHZQoGlk0)*63VA!hN>R5nWim z%zLssPJ^W&)VCiur=o+oC=0^O8*f+2Gs{vM%XnjBo@))iVqvYv|3H6CFWABUy;Geo{m582)4Jm_dK6-N%lnmZ zsKvDyAan&`&ap&dQe3%5p#>n-#_Rjfra-3rtrY$7Q+czpWN8~iJh~Sw`_DGRUQnox zH~zd-`@2Z=Ju4R0dT4j}cTR^g8Hhk0@8 zJlL!FbnJuw!b5IWz9*phQ>rXv-(fw7$*bh?@@?s$DcLY59W&H=sPiyDO4CpvRP6*? z_X=>r94~Xka-6r9V#u41iZ94s+%q8w$>+Dt@^KxZ&w>jlbRxAm1~TH$ zj{8q&;kbNNeKSkd_R*Uaxuv)neR-t|PB%o*%nj@Ms*w6#Eh9~vDr^Y~czagEq{(g~ zb4}E_aT=CFw+C_5GWYIi*WB4`&6~F=FDxTmjg-jMt$W#=XFgBlYmS)jR14m3Zhs~uKITiQy+GwY` ze6Aew&RV|V*4)bao*7p(doiP`sI%m_?sSfXIvYuA=~?`yvs+5qkLYdc6!cf-n)42B zx?xF6`_5^Lm03r+X^d(|#ZlZx>s0~ywM&ukSa~f^q`1fGnJe56IyY(+ zc3E17)>T_#btO|brQ&mCeVUty%4F#+ZGIk>0nIOiuImfY=84+u?F#YxUhQ-hyPddG zWHj+E`Ef)S)N0SLT<)+1H2*MsjFKl2M!p9G8yJYJ{<$FL-6I|&;d&UJG9?`3VJ?)$ z%)FH)73Ga7X|6u?o{&u|zIFvj_>H!hHEJuCRZm=`w=qRX)?<%#@Y1m&XD$wxzh0kI zzSnxk6%|<;{S`(EBYc=1MrD!83mp2)7-xpeKG0k>fV-lGTGN0nAj)!OdQD(rvTURK zDRb>cMv&I}K+4$7Oe_GlpoonEU9kWSdv^~7zhO`G=h{0ERZB~m{@{z=`|}l(?clQ2 zwZ?JYIp_B}##p&-GqDZwy?c`xT;eP)dNwd%re((TavSdW=;3%)1%pg_UGdgWJ6a0KSYl)pBi2wA%UB*hvt<@<)Mr=QmNWY zb41taXukAO?5V-96~T1fB{UA2Bd<{Fwt(B4!e!9`EnWb6P6A*y_EZJz%4k3YuIMMOS0-8yeHTz087@!HX|r--Ot*=Q=l$hax9jVeS2v+I zOGdAqPJ#!ATyK2?dYiP5CG#K`O^c~m!Dfwmn^Fk@tvE^)vTW#-^_h~1s-#0xg=zAV z(Cq>EYyq!~RbOI~WSzCHVah$vTdD2v8p>!L4=1DZ5_(B#3THxkvRiv9%6yAhtAV$2 zUG>e}W^pN(V4$XMUm%gSrI*TZs~Vb;3Ozo*QQViap5`Y1{Yp&H2OhWUTA&Pu+o@(h zXd`&8^LFg}2U9uBLq}y&`yRr?a95D;FvwU*6$aeoxQ z;Tz@N+uU)R?r`o1Zh%iYk)9S9&6y+}Rm3^m9Q8E{81T}Ycqs)xWU@ZtV(M_f5{fY| z6=;Lwt7;7uHs4+C6_gLy++R&JM`BS+gg&#c(wJ~tGVge3^{DEyUcU2}$}p)9{9Jz9 z0GqAJxW9|kF5=oIT`AK}bV)%PxCX6NZDXeJ8gLxB61f^?Vxp#eM&yLOcMRql_!Q|G z5Pk<1@zM{mwm;x+_voP#|g&(KvH!#$~_SjU|T!wuC^c41}k+k$+Up)TLR!)ahqLq1&I3UWmY# z1{I}qI44b>(cT_y=cltRL{GLE08s30FYM;$w8-OM!w4uZ8^^FUVWa>Tum@OzXhgC^ zyggT?ZQL2U1Ip`LyjDoBLF1U0vON~7$dg$Ey8431E1wP{ZHHcn=vY@odNU6 zFXs&}DZ5|BGaNIUsk(Q5FHYflL|)rkXR+$$O-na-9AJ$#NDijJHwrR=CLa7d`H)*G z28vJIrVTd3wO;i5l)<;nubWX$FVf(GEhh_)ZK7WCRS)!HNXE9s`FTPx(e06gfkNJA z=F;17&ib{dQm&B9P`yE>&l{Jx&L=j24XIl*DM(yctv#ZQ7C)AIh*@n29jdq=an>{! z^))S-j$CI?vKV9t*1OveyJ8&e&eBMr%J^v5A$fggzDMOaFX-I%J!HCg@ocWFw*IP% zt>u~ipH<8-IZPC=xg8k~etQ=S40g!i0(*GwOYaM=lAx}eUaIIQ>Vn-x39+%JY~45w z(kb36D;2D}r$^l&#N9tvsqh-kL+G|I65H9LB zy`<~M0vLos96f^TFJ`DXb?uAj-)9@HS)z%IPgdycCMu|`4H2yzVf1fzk7^(NS>MC- z+|4q)h!gU=PH`naDu+qP2hwIT*u?2##)l?e1T6!!y%95Xbs-H;Dv}kxp1k&cn2RnG zOy4i8ZeKXW-9RwgJRWj}OURsWX1Vut6HfsoMgZmhpcT{4%%1_M6_dn@rIWa2?u6<< zn;vbahc=0Szu`sq71AX~tXvA;w>2R6`%?x2zV9xUOP#+>fSV3g_dPa->DbShlTQ&0 z2Kt*|lCdsC=u}xQ27JUTMNL;*VhSIY=-1DDaOECw_ZigrpEQ7@A~ts^xQ>c7jQCa0 zgCen1VfDe^*K^k8id(j;QWf5>dfW&|jaqzNK&{YY*6T@PB2(B7eVrz1xG zDWd?X=s&1QxQ;wQ(()ci6GlcH0O{!b{;4ou)<71mSz<~)Dxkk^T(&Lcg0*-*@xp>! z`;J}!aa-rlWP9u^TVj}9+quN}?MtJ9WQ23YX>sQw&xntdm*>XMMMb%@f^Nq^;k-n(O{UN^AdUfO$j8Zx;GhxgGNc`fMhgOKtQQ-@q{C5=<4)X=VUPKml|*xJD1VQjkkuW`I$p|1>s)zaY$caYVHs6 zs|TSHkdp3BI&}Xd!R}vujsMute?kzpoj*)i*H@rqv3xp*&p;hIuXlIP52h|A$%&U1 zoSMllwAtS=ycy*Cm{0J8V8k+Kd~YnQ8B?sbmz;R_Q@_pz_c5YORpmXv#y705qF|I! z+N?&TputY~TRL6aaV0mKk0OTlUZ?5V9S?-e^;DYH=;;pFRZGmc5h5ope7q~Z+Rabc z-mDI@QuIvR<~}5nn9#}$g`Zs2*Sim1l-Sq7p7*nygKlB#96_jsGWedbsZI9Xl zJ1xdB;ad{r4;?aP@X#ba{=Q40$IT8f1M{T-=Qj3`@XDxnA>vZKueR$PWaGl079Xsu zhz&D`Zr~I>D$^uimFj<(tR}a{)fFeeHe>cbg>3&M?&{NW-F{iH92WDniEF?UheP5= zW?FTb3uuniY9Z+X zWkOVxv+r@&+ITw^wx|IUh~KSMFV`w!Qd+Rg;ohqZ^F+4Qw}7p!T&=ozeYyV9EJSnb zU`F=yRHcZBE6*EqWx!Q1g5-mtwtN?D`Qd4pXGA#DD{i*S*@$OX2mDBRga{1hWaoyN zSIo7F=b!g2TrHAGcFS2Hw#QbE2jhiWubZ`R1>~Q3TCRlc)zKeujR?CCI^(7Naf2ra z&J__h5sJ+2u6JOgsei1+td$@BkP5G@C|;WGbKhr8tT$RnTe^t&>-TKpYjqo;?_d8K z1H(n1p^bwSm6w4rO{g{#9x_fT$IPJJbgewc10sc>;UaB z20V7EiY$Wyo|p!7h<`G{`1BhNVJ?m zzl&m9{!S+wdn5F;C4Rd^IbX#~t=l#H0Z#JRW)X-8$s%rsS$6TZ>#16;p0$x#0ycN3)b01L7&fp~!|kKH+v|E=&)YF`z_PQb z98I5~Qb_UYa{RR`OkVpk!aH*_we#%;K>j5UYsSQ9ougy#iwLSlW&ANl3jB^(j*x<3Z^;ziJszHOvl7|UCxQ@|Jf^zeS%0;^xRFr2WNgZ z?e2@G1PT)79%%zFiuobtP&fIElYqn0K{t}ephCxu5P&BF#HdL@@l!@~w;dpV&Pl~j zvuA|T+Qy_5KLrjhO#aKUEBvU#pD)xLMn^n`Vx*U%-v9I>>UbB7!l}+0SS3e+CJLt{ z(UturQheE^f);|!QV3k2bksNQRc(2|waIljeudvW`VsFBF-k$n)}9HmM2k{;$w){=Tv#g*$r!?gowg`&_L*X(0?vL|m9cV-IK^2qcY^Tw?f^UUov zPN0Y>P3@M8zfg{{0Z!2;$dYCzx+xDmGov;vOj?kk7WDI*bR>NVoxd_xff!#fIwj6S zl$4%dk(#`S&+7%Z7Xr<`c(HW85h2)I`i9-Zp}OkZz)QG@9m(k<+~thr837Voo=_31 zBLPINjbjgEj?Y73-fo@sByz?iCnZ*8-Awcg?Kaia%YcWC`_nYJ+d&KEqAwo_x7l;a zpD;qhSfeym?5va!&?=In!l0fR2^ zy#)K0LEIOi`Fylh$-%f+iGCnH zUgx;C6Ea^z+QhJ-n9?oxDg5<~D62+}70qClKG9Jt?AAJ!#+pofboE6oYiU%Y^g;8= zpj@}xV2*n?w>H5dAmvWKIx6+eb#8Ib^bWPPuIOYY;HbUIWfT6kdwMy=VmQ)ookUoIELwtfcY|{_JcA@?DLrVrtafn0 z&uFh+=acXBoE!H7lE7kOL8?haaoIoo5p<8&aS1Jn%M!D%y9Ro0yVpBWIes34bk43+ z6SEpZi{taf7R~DDyiUqIx|r>-&9#6gbF@fF>U$-2{sP;s;2y#F*g5yN>#D^wJ?5hx zA$F?9QMC5IRyAuJE$2G~t%tHLe&K7@!1tzyw0BUZP6e$B)&C}=Aaok@5#h=MoK5UQJB8lopzpFlQs^2MJF>+X{t(S zbo-Sw%Jnr=Ku*$N^=5X`(@5BCGf2O!2bL)zg^0@XtPjJd zar8|C-+Bsa^W-&UjHa^eYsZPUZ424QnX7KfRl0n__w@rh0e!nuxPWHaM1!JKgn%~F zWZH3>d+wn$d%TGwVxS@U`w?Z7S&fOwP{y}wxaVD3 z^yt2*Vm-n>3A7Oxdqa`o-TVxJ={s^DmA607cYlAvC&dU`G5KBJ5xj-pybTC`^czbA zM^a?XZoRmAcp=@zC`~Rcoqw*on%r@s;&b6o$NiBx-(piw_5(LObZj#EbaIDDz4mm6 zoXoZBD1)ZEGV4o)*pTCQX#fnN2O?Gnv=!X}`oJqnuy_31#5T1=rTaHCg7IwA7&x>XZy z=*i~N7G220&qn&+mdXg~CKGgn$Yq0REO!^Dmmu>rmUG(qo$#P2f!_#^#|PnM_TFRD z&_B$Cw=tU1D0wtMH!ZveNm`zA;EKSWkgvoc><* zJ5vimr>9;5pMefvfSs{Gh;>TKWBym{n11orM<=vYzV?1a#N(t{ace6&l@mq->d#X> ztW}&$re|p|W~flHcsa|eiLVz^3#zu5eq?l|))dzsYySQnJI=w-l+k$4bhX}e#=QIU zdI2hPJ)TjMO_(zvdw)=SH=Q4Y`g{hyQ8PAN$CL8(N<$zbuCa(j{&uzUq<7||b}h3{ zs$*o04^w*~{=;$Hh`riChZyuM?jM8~b$mMoHK_HtdUxZyaq z8Yj!wINVIBKQ$eWIH*QBJ6UqBP+UR;sT#^CQL=)HV8hD1V=13FyVumNuG21TIX-?CpdfhK2pwn8z%pIQ-MU&$D zazYm+lAO_Iwn`;$nGP2rBhk9N8;Ty6TyHVhI*big20bZMW&Xl(>Dw*jMV=H}zkZT{VcRto+_TeUe743&;ruhS zufsy(Y=YUTMRoUqx+*pz+i%f5J{{F0rv?~D<6;7sez#K>u~DOhu4=@} zCJXSpooqYOzLt|;!&}p;sQaj3%Cp`K$@7_VKbekkQU5_sZfO1U^mZjoo*tWNc}y;9 zeD*WKn-F&RQKD&)6qVBWue>iQuMjo-f&kiF)Qx5JViVsCN-<3nMuTv)r) zvmYSfh-|W5`F0;ar^9Bi!*#%{`qYoBnK|9H6dbfN@=n7@OwUo@Y-h0);RqqE1AKeeWcM}@~l z_~Xo?n^De=T$yS~jOpHHSg8l5SL0GSf z9KUfkX4qaFEHd|{HemmA-TnX7cZPW4>ZzJsF$#^+{MT*WnDDE2WZjot=koo<47H37 zrgl{v=1r&X;%rzWYr%Ay=hCju^{zI%4_x=|4eh4yb^$?=fV)|1j*=>ZgW@W%!=kPO zb|NiFbEfsMo?wouig%4Z_`7VQyi4GsCG)WXL9I%2y}+OU9E1l|{Mt<7{;$gdN+R+u zCpE1(nZra}_7hj*4ZFiICY<-faofAO>vzT{Sgz6wooWd}oQI-Gev9<(X-8aPd`;08 zA9{>?{XDib@?Qo4_}AS^B!u=Y#FnFfp$gvWA>R%n7i97vxpVP+K zEW(vZ+1Kc^*FJ20nuydrUOtJ|wN(##XUf_e7N*Y<(_C5k%8Kb{|KHcuzxIy*`{w&K zN`w96cdPb1gH~OL8|}?@Ezx2(q0zUz7i-@_-WFiLwX9GPcw%ogX%eJ2*IZH+y!}k) zRq))WT@JgDM|!>J6R40Wqf8E(an~et5VyMnm;{@zmXj>%On89cbK7Or$#SN8!W4Y; zoL*pv@mdQ6S3dwdF#w;$DW1n#KOXAtf|F-i+D@Pc2nU#!t}`=y7)uKO{nrqAb0w5Q z6UZP}$2Ts?KXEVR>4$-3ko=r$^RcaUiX3%}ul(HwQ?m_+JRxVE>9hL=Hlg1_S_SN37g#}cHY<0DtjgT^qHqmby@?6cLDYks9eWDot7+0NC&A>LSjLBQR!WpfFKbB zsS>3Vnsh`IDWQr&2qhuF{j%rWfA-$z-{)M{opBrt3}ccn-}}DL`;`0u0l(tQ|L0o% z4_}yzo@#65WK1}pdsYgrnas(?zO>?8kg3rX^%9=y7hJvZtOFk0bQwdIjv!aFNtqgF zqYb2JHg4ie&b_O zZZH%U>YuKe=5OekS@^`&Xp+f8KHt#gc+#Y_dNn}@=I{BJFzlnffp=ONJ+fmW!G5s# z!~?}%<7Rs+-=)ropxwmOcSJXJA&!^NId?_b9onuwutmXUCv~M zHw0Fuyt(rMfQtX)IsH%HeZj$e<$i=boT8#fy6?PLh>IDW7s$@8_RA84x-X$n%{C=23wgtvi0it<71a!aoA ztVv%{o8aZ5_#sYXgO%Q^X_M(x7Top%^?;nf{bCEWtTX$)9$guE_IHdP3~S@rwIGbq@-G@544bN%iZ%B-#1RL4Z;S8^!OzO)x+ai61Q)*Xe`50W5>w1KCWctykvf? zRrMnONKz##Z>0WCM(WP+ILtOl)jY{MnyqX(&6@HcO!}5Ts*ZHUHU9QOP`*`0vHI&6 z^S-WJY<&;0w)ErYnE$yo`M0a^pZ*!l_OLxB(T_3Crs8mwecz_n!VojTMlKStKD)`1 z8(=oS_y#^}HjI1)S(omt7EV{hladMe{d@8Z~VD}<^NQia0ioo{DK)5xRH#0)TodWu4q5jwHQI> z7rS?5@M(_DnxDQga`$8KHIsqPhsEe9t#ieQN@rh#a=1p^=pE83KZIjr$v9K-*h@K= ztV%Y1jGJ5Qz!gn9ng7oh;=dfdhXn20MI(n=lS3*2qX&aGF1_=VRMrz8Hh=*hcFy%Sk_Cs8A79n9CbH{t?EM+54^WLdC z>fIScCQGZ?Ty!6&f2`u87t{=ddz9uz5l&=&T-o(2PSKh1M`_jJ4|99U-#&G2CrN8D z+!gtd-Ytyt@xg&XvxI{ilPU|_0sD4pdrtABatbLD%cj28vD;W5$Y4=%T$#zCPBm5)N!jC6L()(vX`MHP=qzEsZmtP*=gye1=ne2L3Vx-@<&?Rga{!;nirimLS@5P^;`h>+ugD4V#`T>n=Rhz=(L*{wz?6wTq5p3{4{0aTzzhC2t78 zI)bYiCP#^GYNJH4@Ua(arGoM=Q6z_5H_vY`ZBv&QM&#-}Sgu%Hpcj)g_vn!AKc1g? zCEL(@Fm-P+bKy^CRntUK&?#c;GW5^G3=V?Or&P{~FmQxp?cc~#Zx?Nt8>(A2Qa7oS ztj*>*$f+8sj?H~hUT9bP;yBm(^6=y&*W}rmN|*n1Pn9Hppy`8z5jou93_M)hfSYDn zJiZ2V8!NvDg0Kt84xRC5S|b>-Dk&lvSDszb*VnHD`=y&ZAOe#^JD%IZ&-)92YZ#6h z+valcX72_E+7Fe>?uCYwL&lIHHJ`YUe1Tur1|pnng9W$`D&ktZn9I!a5BC9}R8;EQ zvi`9w{C#I^2?;Bn96wPmej2~6O^g^&#%By{eZ@W`fd_jP)K(*RaZfP11j%bY10s zZM@-NCWNVAEJSWZmQr^=VY6ZfJLYp&A*G9Z#qmhq$LiEbEj5Knk{D9m)Y#@xSB5ZK zR!t=8nK*-kfkL$RWIL4d`J%Sy$71=4-Pq%if$fUN9x4hd%x=jk0EHf{ zw7vhA5fZR6?}5axVn?J;w-9^&Sjcg+PeF`oq#JcxV^+gIL9PJ@kloxIh6B2gRZugq z67!=sa?5veV!bGGZy>C6ro@Z-{_A)7^NjFoBe+91*WDom1nz)nmKCGUS68yZ+QMu1 zeSIdpH|@lVm}&&%*{+M(5sDSmc+l%O#LMiR)o~H-`=9QrJ!;v#n02r+05Nn^x>vh( z4R@5f_$tRy$}sypnw;v?nw`+hm6NHe(wkFz(Y>zU)h=T9+<6{Y;@;2Qls8x1Q!NUn zm>(R>zH>5Oa`^UMS7k8BLY>m=-G^(TUQGGwQ8qL4z-GK`!PFSzRfmxC=<5HMSLjXH z16fS;S5gvc45(4o)mu38u!9`B2js??&!R)~Xv@IG%SUL-VUa`#mQH3^S}F6d%P zxJd+ZdaHFhDIr=PqeEuHTB3ajN@sKBe4sCDz^^zg2_%Wawi@Cz`=mcQ3N%=}N_?#R zvO&H(oOpPmY-64q_eK|0Z#PoovT#utN^;7_rzf~2#FoMhBnf6XykZinTSvUz7(U5) zfJcD73mdDNp~K^#?mydO=3x1Fb|xmlei#vQqe&Z{ewi@&gr3>-%ikTN?=ydo`@RYs z@n*b72ijNT0`s2rY&~jkkPUL)S#K}Iwwn3J6?`n{Z%>=;bR~otuk^O-Y`8e|=2Zj@ zQTT|0@^e+Q+^G7q=V3Lai3yx(L}_@ozS6DYJ6UK#!e}&9ayTOjVdG|uqMk)KkVf6< z+()X7BrH80A$}j^;YsJtZ zfoy^Ed(ziCpdM*90%PAn{B+$LFD)5@;SrUa@}Y8>@VW9Jd-9iNdMY|3 zk&Ip?plu$tP)dATWg}E~*&G4@lx$FcHs+>3L6}2nS!$sZ;K1i$$(0%6USiGLJ)AG) z{VCOumHFE_V=c-=Rgy#K;p!^m6H)8{a)>y-zE0T7eDt@T;1wypH-XThx&SU!S-+um zX!tvZ#BhetY|{Oe9=Sl4qWrh5WMX;3X8JoMI+d^o|l~kYW>mu*@Ca=ZGW#p2r>kq0@sNSxjW2gCET|WLe zuwjD9U;c?M=OyPPYjWR+;YYI#`x=(x$gd}77Zk3qcXd;NqwlZ@?;}^T#R*f7L+-sE z>usyfgC_$0e|xO>C2+$KqXRQF#1yweREDN?TgRUrFhP9$iVxm=rs#|z4Dgrtg~Wvv z?^th{d-O7?%ey|hnl6=8S1n{Ps_3!OP+tw*@_oChZhvpfYxTsD6s*ms^~6E56yzHf zycA?CPjS98n(X2`Q%0Q>6WX_45s>#Fx~ZCi{IM5uV@3zfcHNb2y|w>8rlJ7;R{%C# z2mEnO9TB@Czu+GTOSJ{U{t#hCAe*;&==>!RzL&Nj_7Zh5Mp@l{!L%1S0;}RBh@{xN zskPxZ7u9=>(NyQe1oc^a%LGafkrn1tBEK1>+sHS}&vhza>&=aG=SSPGeU?RATPn`= z<^c`V*$8zwebJq4qLGzCr2NaEktoqO^8ytM*z07gA^6ObM&qr$6i(AsaG<3JA{(R(;EsE*c&C3zWOdH81Nzc{E zZt*BN2(05xJaO)A{sJO%hvV-;2t{ z#bCZYqzF9Xbe}`K&!xK-XezX0f}^?DILq846%xuvUVnCQle~!iy?WLhQPj%l1{FsmIXWP$;FU%ECqhoLjvju^DU5Hp$a!tHFx^e}K zT`#s=8SSYH2OO-goW86auTOqAHZ~>=REE+Z3C;5{6zD8GdkC7spGN6`KIANo-LMYQ zCteN=&DyT-V@-vD3F==oeb%v}-vnJ&_#XWHp6--$%6GB1X3TOXTCCzE=;?nwD)$Np+uQT4G*V^r-q&B@ zwar_vy%_559(x~lWOfaBAqY%?NRt`zVlGlIOX=SC57GfYCV*S`i7;R#UX=Rd)tn>x zaBmtht*{Mvp6gGfPd4 zHD!;E6N7oo;uC!AnqCo7Cz6z+g@ntkLSr4G^=(2&LbCX-?SIp43@b(k3s6IbdU&38rQB~@C>vE=Qkg1hN%^N^^eF78)B3F4Hjoh7p9PV}|d4ZTM-HC=# zQ>!Py144ERED~o^KSxv1)d9{2v)+FBU*jMe_Gc8YMc5sKGbqAEyYa|=FXi~0b>$vF(KGNtXVnS&2uD^LdH_-S&dL^Rt|cQ=-2Z#4Q2sMYGY zMo~z*EK1A2KK18iCJrJp(JS?L;-wr&gJW!ErSPa3flyxcA&4ul14#^a zQ0-VJVI%( zhepmYVc?na5>FPxIsh+1!JW3#1?=e9+E)lg5R29d%;P!7Y(v(^LCAGWq{Ps@2nlcwp^9;Nkcbbq}FTt9?;B10OJhha$tbH#@>>dg#}*} ziYf!DXdnFw=yH@D<#71Lq!oZL!P zn0l;&d*96x7mcYfl*5Q4`O#;lmvJr(AC?PDa`iqIN!%tDCn$fH(@y`DK;5;&W1CS7 z4!x~rZZ8=jH3USzsy6s7@kC(5XUxJ8-fOH5GI)k@dX8F^gU0TTta<$6`~CNuS$_f= ztslMO6H=r={B;qTba4v5A5|>c37OurRkll9tPzUPq|^LW-_Uz&!^o`*Q`Au`IvHJmEtXPo|9x4WL_g5=PJxfq zGTj`ghH4?x|k=D`8!v)Y}^rLdY}rCzI~+*0M7TJYMT30n608Vpt0z~6RVUk85SyiMF<-GyDw|!KEXC&S3q&UH!q@gU za}T*C8KoZv&PE{0>}r zXqTbz`c~n*NL*Da&0k)}(_#C^XI=9? zncW#ijG|g=w#~&yBM8(i>x_q2h}G^a2ac0gq@RHB^3i?E5V!+DaEQMQ6bFecG-Y{) z=U1SPQ|g!tj#fIBN9GeS)HxT1Y5jwZ1PmPoxDpJ(JD1DnKn>vlexp@IQ_KZE{1VjP zYrEA_KhSIsXpPpr*(ecHw!DQh%s|LDR>h<^h3bPH%;Ejrkz(~^#+|SR5{6t>8aQ&Q`oxg3%(&UCilb~&hafDhJ)1Hw$-PE!s)?Ba zrTVZ5vH6*PzYS~*#bBZ4Ik0KiTt-4JDJn>kvMT>vuC@9oyS7crxbK^j^3b?~b-aSm z$)s2Mb*JdI+!(wgvDE0Y4aKC6+gcnHia?j3m6Ls?F=ajdv4fsvq z3R)M!&EtkyV4LX!VOY%m>MgC3DK5B1J7Akfn2c=_6mVq2DzY^&JObgYnF|7^7dIyY+(QiWcrnxsLHXp9{xluSFZ531 zF!CthJOTTNDN=qIT;vsgfBjJ3`(eyH!0%k#*Ig2;FbsZZ`j%!1h-lF*zJ%Lf^FoN` z0Do4$*~^{8--CgIg&%e&ibT*_x#g#yEd9p*!Nsd>B1nMH0nd{(RghD$e?1at4s||1 zJBXJXDZ>dUUTVxgxIvDT2%RR5LGt77NZkBG57Wiv_8GS=o-MQi-kElRcSIv>k6+7O zo5U}|m60UDwyL_@>(nG;ujq4Fiey>4?>-rObT^VrIDbQo3 zaILy`cB*ezeqekKq}@WU|f(fpDs3wdZ{MhWA{D(9A+x6zg#jcHFAXFZ@@P^3Bf&+qF2GjTz2h^*K&&C4aVu zmoP?cQ6KmBR=qA=GIp)FaA#2}PH}V#h|FCSa<@SOGjBx`5&s5wi$yetZWX-DwlX|( zP^BwU?g@Id8Bjq^11F^5=#5Vrj77#Sp9S2ML8Px+w1?AgHDN#b)_-9RRfHx%S#Z;f z9GiKn!EkE&@L;DE=pb0WQO0V=WN|UsgTL~MT0ecyYfm(P#HuNjRFHHaX`0xL@Ia*@ zy4w{!44Q||-h7o%2hxywL>SD7tmicji))5W(F^nBTb&VG(umc<1WwX3q<`JKo8X@g zLFxqPK0vdaU zRb5)wQYsp5q@^YczwXdLNwlbpE9}8X! z-Du}HzTZ1nGut$&q5!vO0(lq1Wv%40N5N;d4i`Z`^&l--pbE47Ha9su81!YWz?LdC z^CX-1h1r&$)u0=XNMTn$$d;jd&};GL#r7iGL#blVF;{vH$0omt-xc(OYJ(!*C$?^|<{iP^9GzrJ6Ar(5?mDv#-!$ z<5@RiZGh9<%1@IhrcK}*XwVGo&dVZiEOV-(0<{Etc23ge_epPei57xPq)#_-QyV?J z`yh_V{R8$~03g+ywq)IFImO0&DVs*SEymr%t-bpaJc3lO6#gw3^#jW>R$+tdYz%p? zM&+kbo3ekXU1OA~_D&;VoaY7OH3Yf>D-?RS!n`l0Q`5!vvu6nhUMs(Xu?N!Ah%ehR zHZw!P_$k;lPEl0bM(9|dwxL48d0|Hh`q92SSrHGQn#pPG1DxnsG5Em;=hlp}xyAKC z&Y&orZuAI37e|!QMpL39L~&z=xkO_#@eZR~jLxgW6o1dm#}%carOUA;r#V3e_IrW|bk zWRXIvgOkXOlEAR>$S1V!_g=S9YIR6>;5S+t&4|@0`j}iye?`@y~hve&?IY*5NV& zqnGbXR0#g7?0`R-)f9;0z7(Ylh?dW{?&gfO9uQqhw;Ec1V}3E!KDLCE{x)k}1~MBj z8D2FTY(4G4qpV!*KQcJY7he!RI1uGKM99l?IQ^Apb7}GU0b=sQMDpy5!&b)D6G(%N zQkj0$J|UQYtTLDSs?Z{?Ml%>MJE1wOrC^6KjTOVlP3Um+!F9g zH>1fL$)3!Z4(OVfZdpH85G+T!XJ z>u>5#wSC8L`Wibcs9?~KNgqUUyeEAEl5&Gaw{e2#vswW)e5zUxqWQEXRL*UsdBrmx zqS1Hj=7&`xjt49o>Bx`Ao#5llhgBR>u!nlH(wZeF83&XWvjQHynT?90@%LHyo^wOT zfqXU_Zc?tp9WBky_szm7`@ zg>4~`d^8wnkPFRf7x#DhFW-&zPd-;1;B(BAm*eC2`?$TY>o5?bsc&fat9;c9vuC9& zp}>(h8{2jEp~PPI8M_5=IdV(lsKkKY^*Ixpw$vdy%QP=qE_je#9ZpdzqHkS)6D*|7Ywl;LeZ*TC8EPiFi? zlqU|o-va3HNeznrbw&UUL#J+c>~O%PJItj9GR6#Hb129MoKfO!-fxcKfvCr4k|4{@bUErN3-0_Grj9Lwt>CmhFEdj@YuZgeb|vaXvR+^>d@REB2%1w!H zb5vp8%5O1i8X*_%S{$_7d9P9foo}apscI{cSrk7VX(XZw6R<{6;WHm>##f1Q{!Wj2 zBpb!2Fo*m97&&&Q7S%Z@+oNmdRna@|e}%54w4RD}tEj)bGM98CcKmi~)R&>#GxW1f z;9dQ)EZYfYW4Y|VqOP;>WtCsl-XBarpIvv9Jk|#UKRq=)`kGlRkI}%L87>mR-yg=mw2;4O5+R%ZdXhBJ?l}Cm&THtL27yZo6*VH zr}YoP5dZBSb)!?{p$xw@T#=yei&d;K5#Q)D+zB5JQ*oPOEH7X+p-smqpQ}DAB^&eu z^gGU=S^W!gupXh3)u)KwH4EuiKK)6)%eQ6mh{iq3K6wDX%8s0_^r+$fsqo*C`~T^Y zlP^H_y9r!OVt9Hmb@f*o@#TXRLl3_FdMF|TwvuKNP=CM(=^#jbu9<&Vj`w%vXvei3){)gpaSztC_~Q?7daiz2$q zLL5P|W-E6+k1&R#{loLxKNFl5GDw>m(MgbS8>695zX16`J&LVY}4U+Yffc#g0+keEUoAIM) z@h3blK=JTV#i}Yzv%F9t`_`SNMYW^e_YiV976!)#s{DQUFXmf^JwpjA+H8L~&S)_E z^D>4as7Xlr-khY}0yX?NlkZT?}GC68(lRW3pYfvAJhgK->?WN4DQxUz}^RoseAGEAb-LjsRgUyA@zit@L6ov?O`sfLqs zfwdOJ?IiE2zz`$Wc{!E z2KFZK_&aB44uU_&B>ds-nq3wt=j|lJ?|FSwMc{jkZ%VvZE$iDL~NVU(cs7iEWX5m1z@PH$urFoILV77FAi&&tOVEXo^-mYFJ7%j#2 zvl@Chj}7JXg(rLSmmAbGGx7R;y1G)4)HVgER$^;8CeLp80T7=@GG^fTF{7N2uyima z=ntT4bZdCk2A*xPq^!u!RZV0WTpB)A?Rx)R7El%`w{zr4=g;(WqIZ2?Np7xIqTVat z#fisnmkhi1pr)Mug(3AXteOA5Z+$~O`NFqry4-qIkG7nN9ArJcX8a|pWN0WaCb5}O z;suR@uyvo5S6^U~?Y%IFFd*w=N_LalIv9;}`j;4MOp}Aq1U^Y<44DmA23Ez>Fn{mc zhSkv5`g_PBt3|ViCBDHKDN+nCw1!{OWj;=k8D>DrAA%Ki*n4L@2$wc@CK*ZXr=q4O%GqQku}zbtIWXuYkLfbh!J!jsKZLbza)bW-nH!Y&@Ewm+D)Q@ zkt0R>{`613)zew}O1Xx0(8sepby(~lOQPZ216kCf%CzplaB4HDXzKONakp9E71g7M ztgHeCdu)xtnwbM!V}r|?Q#xVavvTm=ZK&pcumFg@SM4sqvSQlk)vh$OHAt&y?vd(KTcG`a<+Q>764Hp z@FCN$$hSmLY!ra)^>0P|;CXD+ukJraDLQw}L?XWZ&BYpj8H>afvf}#4fVB-tl>LA^ zQ7rjhxo`oJgIpJx+9?vxEB;y(BGoex=(On;=SWV*%8(uK_-}xfaQw-6vNX!CrutoP z_IbG(mlrCyv+X7AQ?$;e6XlBv{PFqR%qj%J`mKIHGG9NzL*V*6+a{`2OZ?qjyh?ag zqC%O+k*oZVKI$e4KcP)kG!v&$>n~f=U^%MW6}7~6Rd74^kemw^FK!D$+WP=gNQbLy zbxl|>TQzFhe7d5k2-=eM!-MHV`c#_$i{Q}R!~N+)AwEZrLVdmyXShtb8UkHK;aRb- zh9y;k*MeO>yo2Y&T(%lRrY^}XI{F56NZTq&Ib_oknU&et?1;d6V{;vMxQCmO!jSdr zfYk?KtO5lLd~Sxk-x_lp&s9+7>e+8-YzF$;!X(-b*u^y-xmY;8G1qwl9`JrPTC9aH zf}14=^fZFnuLJNEVI`UFRk|)yRq&{ArS!Ut<)j-w-PCen=%~F`$idULpETsMtVndN z6EXv?60+mHwnzmWBM!jXMM_JW>oGKgvQpA-5hotzXgoMvfZhK&B@KLU8aSRDJmBP6 zKC!}JJEvLAXCx&f0@o0Tkc3aF0EtX#L3YAoWVG~`|JMtE0$Ul{oGDg5s99o6*FnF| zlcB%}#_*bMn-l=NBukCM0)$~1dOo7L#LkLw_{d(N2!CUbg+hM-Ezb7r|mn9d)nM%Y6W(39p+n8Jf*&>$P715O||jfXIP zP!Qs%MslHT5uW7+l4u;Aa;w$o(IF~1gIywHucNy8__XTL=el1V%SfxN$Xnk;E+|XL zL>VE}@AOEcTb?JX^iYHCr-Ap`a8yp#<>*~T=y#9=S=n1ejN1UXk!v8>ueEwMuBE^w zY!(XTBLHkDb?U|r7^DJ>nF{Q|>c-`}pAmdO(;x_hIQ{wlTI1I+PtOZuJPMxHc)ZxK z#UU7F&MHdy(BiLG34SB49m~0nFNY0!I$YPzs|MZ+&Q^iF6%;I&(|&)D%`CSdvOuI# zK@)a;IpDKnDs9jIATr6URG^t4IF--mtQ9*s_)XbT2Wor&9l6~5)eiu6|02=DC8ZZW zfA{-N!5xxR@P z!=6_CLUZ4n)Q1iLugGk8D6K$xUnI*g=3VxFmHo7Fr0pF;+p4OVRMYykh=IT6(h!;+ z#}`|EW9ScNI?59$A52RAK1>#QU^dHUA`} z+ogqKZ%~;XU97KmRfl0pZ=%{%kRKUIoh|C6gTsqSzC{Q&Ljyef)z%mSlFu zQE6o@1c>`D88aIZ@g%?w2g>7~ve~k1!Amn2ijz~)=`fBCHQU}0nn9QB+H!HOWOo0) zg9NZI4qgs?pFNCygY3ex$+g@4f}E zu0swtAVn3C34jRw>59eO-nq6rvm?F;fBZ5H!@D7TOlPTL_>P38MC0+_nd=`C^sh{E zFhrM)7oNIN9y}-Oz}t`$cv%b^kPVTFdhN3hX_2Ax0rp07iD-DRvdi8@C?#E|GzLn( zuYD*oZGT{m>Ee7>PTd{NKvFuZsPlzLH}M{9C~Jf=FyGnltQaX)tX{3Udy&)P1@95| zkNMR>lWFqTy6@Hz-lG0fMZ0gbB^U5O^UToEL)1q_LprQ^kF@{12=lEBCbFhl z2cfqJ3wVP-A{%hRopFNgS=9P0 zU9><4-`ODPhIuyKZ0?;`v1bBu6_I440K~^;_xlN0n(f{< z1Lz8EWqCsw+((pEF*l|d>VbZirFS%2)P+s;6(T`UpDtQ!uKH^PZ*a-PSBFW}@kj$D zf(#~yDdQ)Ik)Hvrnbk_0u{tsrM0U3}e%q>8|F+XyTg8#1q+S*a0cd;jLB9zRcb3vF zg{~Y)nx-x&u;pEz{cO1g2vOH(2da!c*j9l}$*Ej&uhTI{IdG@V-Q=Dfb&U*$hBai# zTFJKIswCb`T)UO}zx2xg+C8g<9f_FtPO}iWX5u2~_x&4jiYuaC>;1`VCbm5iuWc%D z4nbP`5D(cWyADHaLw6-pRu4Iy;;EdSSlfNRN<0ZOuTv07AZM0h1Qmi_5uCU+u#t=* zrU&M)rCSrwWNFh{<`Sdh)(r9q-NMz(U;R9if_8_n>g1b4#hLl%3*$QT@YaMA+~^)W zHVr7I>rgS|WS#x;nz!Vw7mMWNV!*9H%+0s6_ogdve7@DNp*aH0Jbell$3J4>n+ovq zAWF2@HJe9@g>%I;_#}e1JS?&wp*2GY3}sk%WGnuDlR(q2&|kwN8G6kh4_A?ge<`as zDyAF%#Vl6=XdyGlA-h++>y|w<SG4_)q?T>SQ-oZlNnq!|4>_ zW)$vpJq_O^9PI~vdJ-cul;7;<93y78b^bCy0i<0=N=z0t;jcHq^lvE6c{>i=KQ>)j zk6f;ONd-59`g@;j;YZGN;aL^YS14e87NQ7-N8cS@GpFgSV|W?=K`oA3@D^& zHgb_0us8Km1hB!EeS3e9U;GDri`Jf}!+9mL&5L~B_Q~tFn1utNYx+;PY4XPBq;=Mr zbx_M+=E0L`t=JVNa8^}L{4|pb0jsHD-TI`_SaKU5ueV#}aR$W~#8^PCb4+$ReN@W+ z$KEd-?8)Az2=x)tz*ULWYV7aO#yWZ1{)(e8lUH)*ct&ED#d@o9VX>vTDwy;|JsM63=`9Lz$)1FQZ^y;Xg+M%1X^p3B(kyi+V^ zw3bx|x54eR$C2fV5fk$!=mi%n*Xl*s=2;b_inpJJy&PG(Ke!r09`1;2RXy9EMtpV0 zdezL`zUwRmLnw~_^~fxzZvXR{9ctSnV2pp-*`>SKC3q9Rroh8hp%_{#?Y5nNuZ?^^ zBDJ{B!*-qbY?Rj<;4*2?(h&-Fx&~raAX~JB##;Et76y7LTZh|Q^E>t!lNEmkje*w+ zm~mFO#Oul>X#R4#C&QhYX6z4EegSuKX~N9tcbb#r(@0+n==pQ7xmX$Htp4Vp(A^TF zg6EuDz*5+7Ri`&R?dG+cVJVKq-ao;Bp!*}@1N%E`a+5^u)aC5Si5Y%Z&Uq6n&3KWl zd)I=uc0u869&ty}7(zvq^UD)%*!M%CMULdain?*?mG+B_O7lROCT*^o>%g*JQWzaeC1oDI+$dzr5OUh#C6nCC7m7 z=eIYlWxNB~BljLds4o<)Yx<<}Xp5U+m#WD2pbn-n{s3DaeCYLQWX8TyNy7!k-x8bp zWgMY!Gi1&_z5I>;%V`av8mh|>2f6eG>2Mb>JYouUNSb>L zO{E8i%$Kusz4&#ItBh%?i#N^FBT0kff`qxp(Y!uRp+C*#KWcG%l+?NKJT40wtT}cK z8r6fAO*IE9uZu2;#tP95_Cq3vs(*R|!we{j$lb<*Sj$bjyr!}dxTXaBc z{%EuAGM$NL?Pc3%{bCa_#aRB+cg{3r@IVXc-h&UvJ(e>!0b;5OiF{noh)^dN4hcTv zQx$<{O%yySiPT}f?qj*;QTsJzEapW37){H56cylU)2kp$#G)B^k{=dpc<0ECV71zM zzj-rTkTiei_PGH@dlDcxm6jO`;iwzpz5?&7h^A3FKOa(A(n5kaoo|MK z2M;=SIUfSkJM~Pq!@4@8?GEo;Qgq%#Qm>sg*`bET%zMe$T`v|&OpG9}m^G;ucXTI+ z!u-i%?4d)PfQ`2Bsu1DUKL^cyM;xyB68&)XR8r@Q{_xSxyM6^1H~UVVZgxLgzz9p` zGUnE!OFwpTy;3ZFvUd)@D2nkmK+|22+JM@7Khs||(zWhLizzK+G-hZYR(6&N&!w0Y z=X%58w|L`3IhOD+2ca2gbmO`A6ff zSJxNMT2oJ){yz6j$pJZkHAyLO{d>RR3ZLx{GH^=Q6=|uc?BJr&GykID7+`MKV4)s-}#1z$Os6q`Ody(f^@ z>HC9+ZXV6UP&3R<+#hA9vvE(ID(bg$8?g}ekfkx2(o{|LP;Y=OuWI$~Uq(v%Psi~+ zibWN!Y#zz$nzoA{Y@{8}oM@N9d)6>EL{!zcJ0y`5RxeTOPrq>UTpV+SJ>&;Qy zt0_69L}Pakbhh&uXnabKkrbNHyi%p9Syh-MoZvk=ILRi}r-RkWriUyrB`7Ni@fKxw zww-vNgL04?O;4E7cVDne3d#=Jy_7rqDMboOr0y~qH_xYH$68P;ct{zWLr!eau4qn2 z5(I{-R-g1gr^_X0|Ay0me&o+3vG}(^gwa2p-|FhwR9dF@q` zfz9sRi6EH&f*tV0yxSuSY^#jNk6*R%zF5Qi^+x+>jt^wTI*n55=7rbE^p4l%em5GT ze)w!u9@Po;U8oHo3%um`*5msFwB5I3o;CyTz}pw2Lhzrdr;%lr<-SH5#k>Q_Ku>z4 zaH`pNEV#woe1nC2#ji;e46E#kYkE+dL2$g=43Lj7V!*<8X!VDZw?ZvIEyc4_{#yMO zKy+(n9REFgtQY6#6jc*ycba!+V??H~L)TCAYf{v*^A@B)uze2F7bNgoAycVRF@auq=McA zJG@bvbr!G`^oXsr=Os(WL!OT6sD2pMp6HJ2=_I6f_HF9y1gt*G9lN4?+F(=gLXlyG zDvtLUb?dwHKvJ6hknlS_O8q>ftn~HG&90~!9UkGfWr&n$1t=sOII5+?hZi;SQyX1k zQdy0GHCv1Q<4$p+VySW(k@)2&wAIDU@3bJEBFeng*{;`(KO3^y^Ca0uc*N+A(i>%l zoM$cq1P&?EuW6V~lO7t#MEVS8oqy{x6aLh5>V?Cs$G|>ii(WuGcB!&4UEX6-e`BpK zHzwn$(;WX|o15Ji`y!R3aO`UpHs_VPLTrI#XzoH@x5gD6Si_as{F+vd0!S;P1F!#9 z4?=1ES$~4^T!ZsmYG*OLToq7iDjT0CG|XPYR7yZ7jzOLj@HPNzH8klh!|Md-npyL% z3%q>HiOxCPCC}2N`IJYvYLL2zW~loOg(uG=t}yxeUi39qCphtvWsx4Uw==L=)in$# z|4q4E`HgjBaYC$3cN(^?o{|4%jcBE6E)lRFcXiK}h2B`$nkX+%SL#K|*=op7fQ4g4 z?cT;>Z~F9*)5mKbO7Z2IK!&=a7uI97cI_Aq{i5{50g3$p*6`Q0eg==3#r`}PxusXE1M#1I(byw} zS{_);n=?iOtIC8R&LVqbCvFU|DJ^RgLF%g`{Q-$yug@&XD+eYoMlO|!h(Qr=S&?Hl zW5{zOlvpkKR$}{G^GSDpq@sUpyNu*(INJyI+m4r$to#wFY9g@@_ z2_0q=2GQswoo48$QlsOj)TpVZnGSP4uUYH2#@^5O`}_X!d%b?2?|(CERvEq5ec#u0 z-Piqo%S>(6hqB3FJyhan`(*4u=4oO{a-KZ~cd_(-p40g|c86^*>ZE>KqGbHYxdEZI z8_$jJ>Mv~=?5PQRdB5aQzf{~Gy_5Gy&Gr0YuBFqKx#;eRIfK2PcPB&s2O0S9VTJ!> zF8F!}oqy<&pu&T$4@Z@zIMna38{W$N^`RoBqh*h85bKX8aq0r9-SXiw4mGL zk*_7Moqtj5!)&?pwxB5@{rmjzO~1eWIzINpPf=Tl_)Few=w8z9bo*Uj9-S&icVE2W zcpdFUY#R0XNSHgy{XK&iC4E?sU2=#)#4Mq739VO4p9w;8iE74rdfZ6Z?+TF}2nG(M z#x5%oJwT^@Q_pl|c-=U&;tcu0|IDBNPw0M1{9*Zj{Fh-4SE1d5NUbCZAYb(KoMZEF z+HI;WFOKa7(4o4I=*|R-Y`KzlhCQ_&QY^q)pWOsZ6+xQA zEx2my^6TgrYqJ}BueK>e>=*IMPk)!-2fJXERj0ns3l9(`_29Ny5v{*M`JX>x0(Qo|M-euq65` z79=@#h1{^sRQ>iGkN!C~nSu9@m=YX?M;8LG<`7Au0tR>RRON~pJ;!2ApO&6;5qShH zepK*_Ltp>@#f~)NTfaxo_%%g0sRjE@@RC1lQ3_zUY6FDskw8n2*}xqc{oS$Q2|q74 zW$1B}4?Q}e{a3B9`HwI@_=u~i zc7!uT(Z}FyylBa$k$(284S;qH8xjgSbZat*@z?GP9-%wMH@a*XJ2$Egwo5&IEbJlr zh^_P)zL~U{^MtBf!qu%E^`zpLH?P7r6s}oJe0^NTjx?xmmO7mB3!X}KN6)b;Bh0xj zTgA6k_|Eb69q*rL{lAzYSN#0%>`T}GwY6_}0u}!I*8FDZ{ZbyqXi$BO+k?5ctgo+d zx09nTx;;se>iNKN?11lFA*LH=$O-ZpV|58@3HWib3@=)m(tV<1F?#4bwc!a8yz>0r z4EEdAqa5z*myp&<(tXUMVWTbz&^~^j!2YSkydfw$J(*!a$l0_(+2ot`Ftz>eexC@B zy@hDbdow&9c$2Wq10C?23v3u0Wi?|f)7y8Ati`r6e%&|1pdYJP(s+k%?`uUbtRgtr z#V|^}H}9jG%R{kVSZ<9}k|v9d@gJgiSlMLUcRn z{r}ZIoyX%BjH7zt&ktFsu8qq@50=VAGZeAPakdXJW#axK+-d$N<(^A>EPq{QMsaH0 z!bMJ7{q88#2bFF%q0oa);Uj{Mk59?S*YlQX*06`eHW)g-@5rQu?bRmNZZq6dB$mTB zmna0Ar1j%1++K-r$JM~s`UnRvN;9ifEpTD~N`$@iu( z)aTEXH9qo%Gq9;X|4#ooc*CaqZbW>h{U(`VOv~S2O*eI9bZ(bR2pZan`h7l;rjOL~if7yhDeDzz^Hk zosnzUi`v%j@E7t2hLQV{_7TQ z_TcWhi(k(44ytPn4r^bU4jAgahSS|zgF86$g62PUx&8<4;`WaeS$4c7cy`v)+vi2v z!4ebQ3C+1C`p-VpR|I^?^DskKVbwt^-L<^x_SGyE^N1o06Hjez1ou}R7PECXhpfwx zuWfGbsg};iKYW-nE4>P*{JuEp?%keGKhr!SQhDFM?YIzB9(O)$?%oH3!A3eR_Iv4z z?z;VdHXZ)|ysf@=>GIS1Rh?{fc=QVD{Ew5M1G_B!ZO;wyU%P#YIaY>~b#uO3*TLB87_*J%(#!OUcsWy1R?jq5}&N|qM%os3cDXjhaM3rnx63QPj?-8 zx%gpR-wzQDZ)INfCqp_f)qhW~_|MNa4)xwvrK9O;AK$FcKc5?8eY63n6-_q;+C^@w zf?x1k$itr)o)Psc>}|4N0EcZi9WiSNC|z8C2i!!LZA5qeJ{|E9-K)KD>8ys7WBKQI zdW&u@$YN1O(7B)r>7^Bs=9`XxdL3w(^%DP>mrNq zt^Dy&H#$t2-&9bDf1K+1mnDJd)pBIng2xH{Uk;w4hC^V?^snx>Px^6+H@tlkc$&Ht zgWLLJ+wqC(mpA9b*4-&rf#I|Q*>TkP7k+f zcPPQt@#Y~21i

oQQ)+l^XM@hkP@Y(Z#JB3ALh6v$YP$7^uY}{Oq&MlOJ^06P?^$ z&C#X8l?f^0q>mQJSbH1WADo&)^BQi!2L>FmsYE8>N&zRAt-5;Qx+gxsp72NF?h@Uh>iFsq&<7)@mYJDvg{454XTc_rzepm>yfk}0y0WRUuRr*dU zcws{{W4!9Kn4mZ8N!!nQZM>XL31S0NYDU{^d7$xyjr^~wZ!Z`uz!KPO;2OOzdJs<) zO8~eZ)JGztY?zDa=tw81BOn|$qDEi)K>rxu=N7tO;d;x3)h07nEl%CluBRhD%%U0V>0;0xW%)|9-am8LOQWu-5#U7pBlT{c;P9 z*Dl@T&@rthg|nb@(|$cfUEpI=ChJU+IhE3>>SsIS=?0c~5#Hc@Mg4LV5 zkNq4<`whReYuSFwU%$@WEtqq==sUBnzUs$+UIC;sI_CY=TR9cjOIPUzOZM``UC(3h z0Z=PE?3E>{9X64gGL!$~i^$AQlBu(^I7U!T`9AtJh0x&U;x(v9@p zq7GRX>GF4w<7*;cR)bDI^W64W;tCQ$n{P-*2+jyNHxqTb2lZ*`euCIUX_(lo{1Z`~Y1lzHALi|M# zgA`U~lah~ojJs!DiCk}+W{=h90>0D50`V9&=Sfe)7*SvuEa$WWQ>j^CBy14r-AH4` z=R-QEW(4a$ivjn!D!5e5sRsM!so-k0*Z{xg5mow>?7tYt68>M036IAl@u^^+@fi_T zIh4Eoj*&H9xXK>Z_kV|YkqtWx&NcaZCfOaaNQnvl*haGz=*5k9JD-XP+TqY)LFO^b z=iK@E)lZkCbFh~>Ivd={r#lgl)`-Ns@UO;y%37i{<8@M&lkpwiX6fWpdC-vc8{HMA zRSZ?LoW`_*6^EdSPD?-^u6gLJ6)A0jAwJjUO)mLX26Cd2>n;W$$x;B{j3FLd=>=d!?)MZS=ewF(r|>_R72#vo;TUQ^tE({92kDdpOq%GLs|WF@B~?r(xa`Q?$8WwXhPJ}aR{k-Xbcx(cbq0Qd zWl>ub41bv*R;ExizJ?($)MUHY z6_jo$@GI1YhPn%y#(2i(r&_CqQVIGHQXOyDfSwYx7jAr0Slx%{TK7Ap0xwnj6Qo^2 z_jEe2nVevyl-M-9r$T4f<;W_|Wvn453?(g^?_XsFiF#al)SF8i?j$jHoKW1khQ&{7 zG{QmYg#Mvy8NyzslrZ2rx>Cqb2+fmc6B%Wx(Zbeq^VAikAwubp|_2OY=0k zr&JK>jCF$A67WyY!tBZsiElE^sfT8W_DGtOAbZ|_=_?@~ca1q~&xQ{X`@1nciJBVI z-KyMVW-d+{@W$!Eg@_7rh!65Ll#n)d54yP-3fi6vK4HN5yit>hA$|r$le#th2}=b$ zu&1(QG+$Re_C4Kx*rAbkZ%iXydC@Q@GL4=_?Pi;^%@K)E1$W)L?E<})?ZHz*h*(DU zJpaPOk4EA|z3D5ebeXO^(HhcdiLbj)E}CNQdFeY7r1dBG;1n1I^90;%KoqMrhBwIN zU4*CM*oNU2S+4>4p>CR8iQEOn+NiOLEF{PIu}f|C{LN&7*oH9&6V=QBsPqW#R#&n*^6m#{45H?#Oc0g~RP!|4E z3;M*7bfux48bmkbg2-!&>N4b`Yo+dEqz-?t8Zuig(qHP}H{PXqJ}@|c#z5q?))(;% ziY8joWH3D$JP$eU$|9^O7K9x*5?*rvlZYfU_Z>!3_NdO?#&(MvLQScZAUdcV&IFGz z8-P)AyIa(H8$p6^_>K3PI}7xKewwB`BLhfx3da14B;TxM%UR){o zcFSwx6Q?aEZ8q>EwCsqfz3cWf66u|ASw2%V^FHlQ(n0i~SNTp+k_}l&eI0iYyV-O` z`}XLYZ`^rbpF1SiaCRZ%4;%b}8XCvmzexNZ+)9m6W-PNBKFY3X3|poUYt))f_taFn zM4+jq$4%B$v#}8isnV}DS;N0E3y0ow+ZZZaa)oxD__%9$;Bt*9a6mdV;#SRio}^uX zH}rDzv}CM^DXFc@9CCo`f#6Chd+d>8XM!(JTQ(7?rcbt%SqWj1&PfmBn=ky@1z$utr!^CDl&suQDH47deILh{>JY`rF z{CtJ^%8KQb5DA_8soIfBo;vqIdM8(x3mtdm0_8lU+qX!ULGydjIS-&W?B9@Z5DVK- zwM$5Zh>D^7ykw>&brXht&vuAw_BPIA2l1$OHa((Zgek$OY8xj#@l6RyBka9@>dm{3 z&)L1*`}U8!XAquFtspSBhuDRX|B7S&X~}sjnnU>`t%l)w70sRea=rinhThF5CiDJm z<-SkQ`4DJO22T?`VS6mb6bL|bOCtv}u%^0qU+(0gthvPG7hK$`2c`o@ehN@-lvse2 zTvk(W+;J&jc*Y;lAA&ILwTpMb(rFE3!=y<*dqL+q0u#-M$zO|iMiYmC%fu7WEHnDx zFulEBBZm5wWqQsuo9h=I`b8ef;h}hJnQ;*T2ZS|kc6g_H;l;vUed8kf^kMM;@~zsA zeHaAPaQU}*L_r#zGAGk#%0LqHNXVO?RXjlTH(bMT4Sl(2K$xDPsBILP|~G<~$JSOcBgH~l;>fEVxz zu-AA~glghQuHK+d!@fBH?q}WZCPrYe&5ATpu@#k~=J&4Y?~QbMJQr{bu&+O8u>moq z&&v%J4N9Aqa?lzmHy|^a zyG^!GI-7`_r*wHc!lmBm7@nr7xYbuje#YnEhx44NF&dEvA^6N(aXoflg}d@}ins~u zcPI7dlW#M`L&Y|EnGq3`Yh6vQYjTQ}rN4q}$jXKj5VRK=F{Ot$cL%bP2zTdyq)D{9 zk!Sd8@Gwyv#{X7jGh2t&^c{^s9D^_LkbVMR43ir6EUWwezFUr6{ltnTkRlctFvO~P zha0EaBsd%vfBX#bznC`L^{sFs@FU^#yPlGN3zO4Y+4hKw`jaX}MH}u&=!HmfaVz_% z<=>^V%r00ijv}n#oJ|^cNfj^KnZpoefoEaA+-~wF+7$i>@+ySUaB9L&YlKZ#4uuvzy841(W}`(I%a$E_jVKAfSc zg%OM7i2*Q{Ouf0;GUQ^>2aKguDC@+m>1=f`f(}}(#`HH2?wKMpnAt@Q9N?uy_^F=x zI13+eMA*f3Q!Iy~YgXM;l36v36q=}iU@<7V$$v$v2rSXsAduDkgPLwv zEtB%vO8O|%-(?ls@S+mGngjANsIt^ZkJH_}!F;V{f+D2}H9{t;r!yPq>-VzqJ6n)R zOpfj*nwOqQ1!l!gDHvl`_uqP^F5++SskYE&MX&dhgdRL8@ws|}DjDcCmSr}2V(0p1Z{g!4w zeph0ww{6Z1#uSa>>8MCWY(Z)@C^qz$m-DdyjqD4nP7pqeaeBjPHhW6#PI^xTN&0H^ zO?7ILHu@Vac7UgelETe z;Xm|HnPTUowNv|U4(4h_s49KR7HR;m(RcW=*NyU00U&h_o3*Ulq!oVb{u9dyh=SsN zz)H0@d$kXr4#SOn>{`3x}_w=6|2vD7s}IWc~Kf8ye5U zdROur@;cY?LH7$346-5AZ9e@5tp3`0#nI5+z_$jqRgD|_b(z#)$75B}TU4mwaqCzb zji*T^@>FX#C>YY!F z&rCwjz*p+8{(9Zl5`fl{Fmy|gwgwIOJ+KUaJu?By(jn*MyOsU41{|(hfQ=3M_yn8wbKm)U*wRk3?W{CtC*oEZKz2QFoQj{5KkqFILWgh})q-q|NUUozxsB6QLW zexFdq2oku%|7k@L^~Z4KpTAjQ=Q{>mOpxy!oRLw`k%%G(nfZLnD#fsE^=ER;Q?(Ud z-*EmQ?<=>>9j|pW<>9(YQ_b;bAX7T6f!GB2Z^k@`aKYb6b*rZ4C-F3x9#i^YNutG@ z+mtwWJ?68LTaRxoz~reOhNx(^04{QFq-3c9p}&|jxD<73bOG&^&xUvSZ+9cu`Q_; zUF?QK*5n8Ns@`uq2+|cW9(K}nyRoqs9m5bgR4!7&ZUkWF^ z8;Ti!xq=0hoh~X!n?P8AK3v94xclUSiSi;e(EzV#Z4Xc)5BD!SwQrlxr!g$Ie5F+u zNTxi!)`GrqISrx|f1+IGJBp=-e%H^hUeiC(r@w;+dit<0OK0Ge$YJ5Z3tKj2VS6Ph zGNOJYK>os>7p)nYG3sePU3Xv8nMSvw-aIlI+uY^N=KzD22%KB{*g@YB`7~filJa!E z-jTX(w;y}LY*~eAI-2W~#vkV6!XH6BXCdu-x@PFGtllKYnY!uKV+$%}cg!^_!|;qz zA4^=NCPVvJ?bCG$Y>rzZ{6N{?=hWw!Bb()9;+x}jITM3pUy?q-Qv`C?95=)E5#iM) z;*nmTG?PY88~I*zmBvO-=aJ3$j&NLt4==8BUMzf_GMwa%Oz;$qjV0cg*gl8VvNcfi ziD(i|Tfp&gcYR4NN=#YTU28-8a5TIck;r^13okQW^&f2fV)}ZYIXsmJQJ9C)fo`N1 z{sBN@4fSRNa{ma3$N{CjTD4MFdUtxF2}awG{5KQG_10-n@a3~XedHGtKif)V8rH8v zHUne$7u8x$`AMg+K7tx5Z@cFT@(QwAfr9e(gkCxKY=u!@Gh#JVBpIU%9J;iIPA!`C zsYtni*7mU)JYva36&BDxDo9L>RJZDNg^XzfzmTlb^A=n+GJ`K@F(5eBjqyUQys71C zS~d7Z&Zq{DmB;XkLvOG$z;x%xo4PU?ExznN0yn4@;n}Z_D#Kr-U%?WN0+ZkfIS=YO zo82ev@V>f;wy(<1hU5M=LyE~TfhmwixP|HzOWs_EGxuQI10zuolguw{6_A{gniDkw z2gG9`j_iUT;>c~_5=JHkmS`Sq3d+jSmE6C$`En+0Y$q(wySxZ_250FvE=SV_6by4K&Qb22h3XTS6l)+zB|MU6B&oC}5i0O*k*4EaoXzXkP>IPG7$?|1i;O~zb1jcRIWa^~Vs6ThGuTVo^~vMU_+8~nD; z;q&kKtW(3KJg_yf8g%}(jf}3eZ$NiIyW`4TBm|}DMuvAfa@;wC5-28e9oN}EqZEJ1 zC+Aa2Rk`fYs*em;ka@A&<+N3DM=l#|)lOK(Rhf$awU&Rh1#5sAu7AZgvo6vtL4}N9 zGIT|5t?7L?WL=e~`>W5RB30l@-X-Z*A7yGFhxrk(WWT0VszG@$le#d!(o#FwxXgyv zJ|aJ__Nk;wQhb4cq0SZHl}IT4Yn~%aUKhBw7UP@T(TW+g1nzGJCiidNW+fx91830( zTw#-mR(%h=_RQCBuuiZ)k?FsuXZiQb_v&W4SASjB&A^cni&MA+-Ck_uEC)n*^mrobg0EID7)*7u_hZso+bKmh!p|sU_}KRLB|4JnU3^Y*LCl zm=U)IYOtiwC0L~C=nYBL8Ve)`tb}8bUPCO0Ue=@);}JEgj0PLE6nmr(3j6Pi7WJe1 zW=<9*t@AJkR0e{pqdDbr5?%*yIdA;o#$$a(jb?gIC2~wZG0mn~hj8fDb4hRjumxp& z&8_x`llodKfMQ3O((vB!@2$8OVMp!$m&Bq}UqBRk2I!cJ>f$K|SuSYx(&XacXJ6M+ zb}2FlB6a53GIM@5K{J9@&p1=IQ@QS!2)u}AFmT7{0hCK14RlHxPxe7-U%h0X18Qu7 z?I`)YM)39E_?BErm~V$WQzE_YsfwN`F5>Z8S)I?>ohH<`QthROtqpPe;Ti%#xZOi1 z_vhzD*Ep90+OmCy$J*MLZK|I)O*~HcZom79nOFDrTYt6N7A@ODKy~Wm+v*E%SZ}M%V+bdE{s3_tt84dm!tEhPn{(4XH`HNK8 zg8wDF9hNe|&83SJ{B0mU-qevDA)F`UVPTzMXA-#Iu|0%98S?p<}wxYUf>}_Bt zF{g|=OHAw+F@`oOfz)mPv^+Y|juwl@YopYlhXSd~4DeeXdd{>y%<=ifQRlQ-hCF^!d-?sF#rl#Z?THt_IYN&Y+}|1bARZBHkjrO4Fn&pFF*`tL!xNix^K^9tu=#@pu_BduepS*HlpdJUEf3VJgC(wU!e^o2K|0@? zu6ZriL@x!;2b-Qo5Vlxf$`m{0`a8hZoaR zkI`u!DDBtA7BKw5leN=;Dt!RN`UG<5xQRdxf`Vm;f6Ock%sDTKl{0O@@c^H} zL*-iTgg#SStQBkpew#GUu z?`DIa(TY0=9mN;&*sreI(Ld|C<|$~g)s7CbRj3RnM*l^!||g8kfz3oC82UG z-!N`SCrzuFiArc5#t~92eiFrvL_ycnyw>@Kxzu};-Ohs2T3;H5v*bh3p@bUWl@(TX zXqyqK$zqqUbW;>3)z0@K%Pt&@y?+fzH~~E;b&*!%0ik8L$yWS4YDp+G;l3o5h6jhj z@@d+p^Iph_HHb*KD!Bh`Q+FyrhaT!;-cW}k-?6_Ar=FhC@!rXEJ870p z`E9R14v1&!u9)ktrzZ6_nz z(Z*W)8x6hpGEQh>A^hVJYY6J5)heVc(S#-78l0^`FYETI7s2nTZ31XGp4hkhJ~kvF zg)QZ2L$4eoHKDXiPt-R|11QGQM&&$Fd68y{Twc}nquj<*dK%jc@gLx_$mw}g|J@4! zMOUTTOv@lSbNaRsdONa#=(A^yEhu9zO8sl3QQGd)3+_9|hzsfl^y)!&t4UX`--d3N zUgH}zOewfk4d$Z#@V}K9`LXe;gVXAyZ&{$=m@IM>{x!4PjC44D%9|2}JZHWVC?u{Q z$}CpMe8M1F zK^rr4e?@AVrZuU;G|pmi=rmpX5|@+}idER~bO+hOXEyoS8lO!3qedI^**?M*ZuOVg zWCGMcW3(-S=YZ0>1mQL0_gvC zpmccJWF;!=#36;-O_34u4f;NGvf`xSuRMRCWJp9XT}vv`YqGYT5E7O_lNLxFrc8=v zrA8X9qs1iW8MX5vaIwUOTG87>OXkIm4@!&kOrO^l&@_`GYg8{?JoY|8wUtujjPD(Y z)$~?B0G0Gfcj-_F@+;&>t$0RVYX+jCskpHRF=pl%1l_2{6~r6MZM6*5ysd;NIdWgp z_YcHH^$u{vxMrp33YKcQ?mW6Z8k3!nBpkh3_QO4sERcTwp9Tf0qM<>le;4n;Ld0|u zQqjgScCOIQ$JCP0eOu9H)Zc=++@OzTgCc6dos@09>nfS2%W^B&hL75)xA9*Pjb>1f z`64O)!&N$gV_$m-uSN~C95ZTsoKuEE*0doDO>T^I^#5OdU5JWDC}Cpc5>|nwy{8Dn z&M(-}G9vp<>MFX&+cg#!sgl~z6xqW7Q25d526;4V-<#%N3rfB6%OLDzR0sb+gdcrpE=Y-3l` zah_E8m~;@k@UTJn#P>jRs4`kU6SG$8Vv3x?C(rHXsU;J({!ZSk+CLK=3>1#WqPDIv z>`O*5R`)LUO}A~6a%aDu5P4VFkR^Q==8_R7T-Gh9%u$kuy~z0eN1CJg?Y@ko$z>XD zRi6fHIy~n-(3&r5UAxX1DNqu#e-CR8T7)*2OU09L)KsOB_4E(E7SPg1AGa-gC7OC3 zXH+_a@zKa;8>cWMj>@Zkk4{B4BSQ&I6Sb z=xvWrdt&N?J;7m{yrdHT);G{)!r+m0y|SFM7gte2G-?BV?h{fYS0x#En=zh7k6@iN zM>F>!e6~SEcT(n4pHgPLtHj)WnfV(&k>Fs^L`#0(J?Xl8`G?sXvl@JE$})3qGZi}K zpTc;VtF(AeE^Z0*HJ?k*(~w0{x~A5E^oc+&G3{c(Zs3jB%lduo}R$srmI zv=V~OfR|AryJf>iKSCSPzFE5=6l&^7g{}`shdJGiaafsZ-eZn6e8}2iO^jF|@jd_QWENf~yq!J|jXlQjbEkxnTF%EMc$( zX|7!7@L!O8vTf@`7U(bdIEIpzVr}DAoIUb(tbPm|I{yt?fyK1ZoD5?+{>FUm1lh_~ zfa!u-Ol&!*RQdod+5z$)qs-a*hfxiUDcgv-=@>|#9Q6K1awiS^el@MfJ4~1ddAcmf znVg;wKRgGOGucp^!5Qq8Y*kKA=&0cFq*(5p#2W;P^dm2*{WSC5MOKYBO_mY)BoqY{ z+Papj8#*#a`nrWzX$Y<{k?X@Q3N@p$ge#rm`iz}Kw}Nbq80`dX)TBv7QHEdgBS(hR zmG%*L0A+Y>qYs_piH+2KqBr=*zsA!<8bN%Oi}{`Jwm8l?@||$!)6lQd@z?UNi`Iv@ z#3(+o2bo!noEM_5X=Ch7<{nC_sW2%Hn zfL0VKus8Sw$_h{Rg<*dDgPTz`@>^b-paqe`*hzQnooDbTly*~|q%(arBa<$Qq85zR z+{;NPvvOiI#dU;PS<@8AWa53~%ORfOQ>~jR7_{YTv*I&l4v?Xe$wjY0RoOgzeKy|23C zNFQ_g_8g44MWF=86&$0s2Xwgf{@uO&JKWG*&JmAOkW9i+vz+^*2jwm~f^lQzb-+GA z<*i8A_1ka$-^P&tIz-BPJhayCGD4iFg z5vTHha$RdV7hlW#2pR#V)dBmSh8-9}(}trL9$Qj3Q=huxn|%|a$Dk}e$0o}$EnMr? zCtRJ+F2?#?#VwvpAi66zwK>9*wZ2yy4-X{XUc}dJQ7-j_KirmoSIG)XdV|gB{q7j9SHOQtgg@l|wctbK6H({e0v;q z{{!?3gQ~5|P_x#H+{tJ7i6J>4ogm)e>z0l!~;m zqg?-PL=YJ@#*7$Hj>>?(^Vi1qqs*1l%H|jX2jfqEYv!YfM`;k;I!MuW8i=UBkAw^w z(axx;-hjIECe=yun_r9ug@TVuzcyn3=fx4%hdch(pWe)Slca6(D6PlqXiN3_ZzesfH zhhf_Zf(zaS8uB7X?68-LA}jhtT9+3XG{cLoolO`TlG0?);RSd~3`$u9#qjTw+SPQ! z!xncGLe{Q@d0V~ukNEMc3`H&iunF#XV&9jKAJK4pYM!cz{g zM^SjqF(?&`djI9KRvnXiB(f52+uAYMf$s=-^u-|P%WAzpfI6>)UtWa|EbHXQkBPOU z`rpV#>4^~)BIn8X_M!zA6ZXF1vz}+36o2|XO z+Lj0d{Osc1ePwTM(;&-k20{7ULD~UgfTNUzD}=Pw)FaxLIRX53_y_v3vpQ2cr-447 zm@BCPj$i`LSJQZU!JI`1dE{uXnB7$ynpjdRz+PeG0?M(Y_QinGIx87yv|w${QA>nz zH;dw46h8;tmFJNP-;qkFgoUw}fsp7&<^(75(5MYL;zAL0txcMJFqVNay%i5mu7}C1} zWB^U6bPC!ys5By*nEoiMlC~1m*JIF%jYMY>4lYAAB^Oj)o^tpD)p!Dp{i9ff63(WH zj~hR?V>eC1l|1^?k5hBdmaooXZF%C`H6&EeKnT$!iSjtj�}cmx39% zY-lSo>;Q)kcRHLsn4DB1+^Ox^=iX$~9AJ+3quv<`jY6yLC3wT|QHJ2%^pQUUZ_a%3 zb#3p~A7}sIO4t!OYgd+wKAFD$=qmO(*^ihI?k_(OlRFp=(g|h<=z9z(VFak4_%oe8 zp^bRZ6c})WBu%*7+m-_oX;F^GM8Ew+?xa`~@tg8=P}IL7J4(ccB#;}@h;{4$;;wBV zP3e- zRP50xNO-7C%D;kPN^PamPjv!)wVsE%D<>yH`GU$#>jYwCe6MT88uBVoJiz|SMe=ly zD|s95G_;1E->uq$Ul&W(xduK>chxe|_rV)bBh+)gXm{niFpK3iT>7lsfXyM_736{; zIARVhx;FBq_SK1`5-+PaRtH_NouMj^drhlZ32%n5FCFUU&v(_Vsr1#HI5phKXTY7C z3ZPF{nAtJz@<7IpdN#bqI%Z=H-7uf-8Xs{n;go7;7O;g+ywOq3cUT=W>eGt_irZ$bD751CBZ|vdtlk$|WwcHH%i{h6HvuM+7W9u$(UPkPpv+%{KO{fS} z_}SR2(buBAxWVOUV=f%_wmS4pj~b(+>^1aiR9&v&}}u$>qJ6o3S;Y&!E&E+W4G@cwc>n{tNUyLYve;?=oEVpLmxk?C6EyHG$LESMc=Hd zhVrq%(^^zYYo|bQ5WzJCPT@~^lNr&0&jdiFr1z1=VJ93`?zIPr(43Gf>Acz3K!(pO}b96D^NANDod-x5PVNrbk>@ zwbCskbix!_tRX~e8xAFgU8=77_ldveE#_EXN^dl&q@M8Gcn15(q;StwZ&&t+JjBb< zpr~Qc+&MGEgHQR}(OCu3drkfQW3Ipn(eCG#c&h>O$s|CZms;b?Sq)o)q8E8IiTHdL zf7>e@^x9S%aU*W%nYBh<*!vORJ_if18}3*Q-U%@U_EJw8PT?u9ZA_8f&cvb9q%)oK zl8}*Zxu^K3Cc6Kg5S4^^p zEQi;2yX|NMAwvE*>LiD5x48c0+evNM-Eh=piE0$8f5$&9ztnZ>f|0B^AmsH)jdbIS z^_lB+PPZDUSVN;Eb3udLd13d&R~6876K6+V-DW0!)|rrw9oqb_V9!()&qe0r38pFXBY zv7WJh+pG&rpE>kpKX#3rw%-2R%zOKa$oxYXXN!us-e-BqPX;dgx_ezurZIDv8*Qk# z(oIQ3q}Jj7u+$Tm8hm0=p0k${;`*Y)u-)!oH174jmdv+RNpydWb{+sqi9;MI11b9xd8I+7(fV47 z+;j7mgM4t;NbDd_PMj59EmNL3r+fU3Lh-`gW3k`4S)FEGZiaY-AE zFDBItJFYC2&CZz!M7?(VP&@mpAN6%;i?(W<7e!U^K>Kg$_aCPYYR{AJRmK`K@2e!l zPmHCyD_%)kECyQuOuJo-Tp``_UC)V@RK?t~PWkPj{rHx>1L!dA*<9U+<5R z)Nss ze2h4aL)3`mfj&2`b4Z$z`d?lqDo;Xv^nLSmotD`7+6~KRI=Mv5SbRzu%_j`5plbGT zF&nx`jvM!HKat{Y1$$F|S|*FudJEDEVw{=-W}8<>3x^(V{IEK5m}`Ug-qzgULC1`a zShgndB#7`-+41!EqhYGp;Z8ThhppMpRKrT#>Qvf#szztN+Hs)a!NABOw1bB>R0o9C zPy=AFdO>4(4VHAmk86X7+;eR-^azJ{8OEadZUA>q*9+QgK$Oh7OG zD~ifNTEUYA+^Tynf?efJ|9I>8JgfuqCMDb`9{z2Q^vf+Z#-SbeY{1^!s3>%qIsaQf z#2O5ZgY4{;AX|;2;CywHN)D6c7dlBo;FE3DPT+i1qGqV}n2!pj8bh%NAAM*KRL~LK zZMLsQ+jN%`$4kNZ>|JofmrrOY+q9+UwZhx7K1(xlEwXD{hAG;$7P6KibvXMZ5z5Jh zwT~rAZ_8XANeob^9gA$0y=AELB$l$0zUi+SCSGLSP^fdfLK`UWAGdnOnf2h0Q_H1i zYU{!z9y#Cr9TLBcG&Md{Nix{ZxiM53Gl!}g>imO{>#r%5`|)gh+qa{T2A(jX z?BFJfH)+Sy9exy3OKP{hpgC2NFUOX7wyh>B6~rO_fZ;L`duK>)2J~wOB_C>i=^9Pn z#?^Fx=?9a3tC;YgoOj@ySG{;lkJvV1iVTW-+uq#vkK@&Z$}1(Qfzq8r3E1kgxWC(| zR1O@PIt5$Lu0*1(reV^kWzaXHim`Nc4h-3ev28i1Ln_c{S>iapHill$zl}Xsl&Ss= zZ_t23KTy+bfg;?sB+zqRWR;+O^ru#OTl$3?)u82j=W(mDxcYp}6pE<0eZ7A>sQ>X- z5^sUZ$~@jCWW;$i4Lbm&>RqAxZmm!|>`*#>V3@-l3FDkc|0V%La)+tUr%-dh4Hx$o@~RTrV3jK>Ye%*#vj$7_ zE2+aLn1EtGA3YF@=yxbRI$Df*WYwIrn_Z-jYZ*U3X)%ds zjibBk(J3LZjodbDWrM5D!-P%wnAa|ys#ExJl2m@n-{8Yq3I^MR9_4j6?Y|g7SR%B) z8!fe_TByG)lw7w#iB#=uXIEIR@71IhUPuGTRlFbIT5vpesns0&~OO^V-};L#ZI| zZ+jzCSt{U^3($_M(FY;;U4qusiEPvhk&PLU?P) z5dBCb9A+mGVs!`9Xf4W)+@d^_w1~AifF*T_3ZIk{i0kV@MK=WwbS=~W{-AAkZR?B_ zP)6S3$Eb?CnnT={3ngVy@pq(40jtVEw!MDii*(XYhSwR+gvq7hsh>=*m}S}p&DDY+pq}#1H;~l=Y5YJVg7NKWzUTt19X9WuL}O_$Ya7n3Wi>d z&m*_f`It)CLuiy!vI1EInNQRMGuE%JuaOeKa^f)$OPPT8 z#P-SBoodzOF>MQvHVEnrz6Qr*F1l?zWKvFnbetZ8k7J|D#EBnp7E=vamVJ&yzQ^n| zH#E4_SItX)jz1mrERQ@8f?W78cF(H0k&X&Evf025-3OE0n8yW}SpVP@`ueP1L7XWt zIIsa`-1&}ry*MlV--rK&Za1~~lH%k~S^tXOuM;Wu|M9%_YQQQ>++Um4^?vfj5FE9l zU*Aa#4&~jpVol#rXA>>qNE|OQa&9i9;3>ugKe?^}mx>g{wZ;Du9ODcQymtO0~KU2F_J-m=dqQy@k?JfaWh`~Oh&F7Qxi|Nl6t?5=9IwS~yE+iohAP~s zl`>hEQ7RRZ+|A`(sU$KL7KKTw31up`T*svlGK2=D!C)AdF~*qt`~5$o-Oum)f1Jmo z$6GTEIq!4M>-D_7D?cTHc?^E%&Eixdld3=glZY_kzBdj*A_hSrAq4*r0E$s3zwFTavYGx+7i8MjOVJa z(w^M|t~Xe&p0wJ-l82?pH9BXYBWu4^teNd{kK;@o0{RTC)1(S+%lR!22NZbCURdy` z9cxNh#EGhpCakGvn4mhEy_i(fNQ+8s3c0n}cpu`gy1~?-=ouWnABX{wqBn)sNWq4&jAA1vI2YwLU-WQNUTgD{~>_VT;%%N(>s zSg=+nj>7eg!3v9jR!4h$SCI*ZbNfn0R8&E_w17Grux4ns!!8$x<*$dP^Eg70gIHv^ zhublC4a?m&*|i*-Nu4I##xeo6eon}e97=!tV{H3lH3Y6M?|}q&^0q`2$fB8AWuP-m ziE!qt4NkpEUlH>ISOXk22s)vakw`>r12cwEeY^gziHg12Pe9|qa9?SenkTjemN#;1 z^gP@DpX=gZj67v#Xx{t&XTLzKcn}%SheUEq0d!*h3q8(nUreVEI1h!r#h^Wd@$9+k zGOk@+9qu34RQcW~j}y@}O{Ybx?!+)2)6M*6?$V;gPpKiI{V@*ri^UrvZD%_jLZW~S zfM)DACtHe85Wgj}-Z-G z$7p6Ab~5a0@ppl9%&HmT4>Za)VL|j5xm`q1%_IWqLjDCwzeP*;pIgL)^Ag2IT~3wo znA6X=2dddvCkz)mERma%FuzuHS6i=4x?Lv3(LiAsxM_lq7-7-vzJn5=e7K7+7a$B{ zWDqG51#+1}#8fihpoPI1vgS<0*J$GVM35!87AfZuz7-yz0-3v_hC8>T=9Fo4ftU=0 z?F0k{)kZ0x8U9z#t1oEZ98wasO|eMAFu0?g#Do&nDm{gnOIAzeiY^+eF z)9RZJ$w`Rnn2N^~m`#sJi#T;9;UqsKZD#s}Fs;&_>7W9HjdXoqL8y2;@eEgaguhF-7NpyP% zm0yRm>J2#*c1kcnH#D|;Q-XzELSQ_&O*tB{U^k#NlmE=W$a-Xzo+|piraeTZcDw>5aJEgovG&|8A zpgcjb!o(X$WXcY_6T=*|*Unn!ATYpqePo0*kR!&sPXvT{Ak5ccK>~%CIbkbT#9f{`0EH00P zXP$TxMxBu!x$4+aq;Q~PFNkY!ujQg%S=O4s!oW$=RM7hK; zVp;XqcFBAUDfXwg38j1dYky;SVTJwG7vja^*}?*ywM^NOx#}x!cFI!yPT5QwOYqj0 zRh$a6v6=GOyo2aIp@?ySw*up)lR=YAi`w} zx2uT9GFPdKLh@JqoZ1l#v^U`rwcw_?$1Q)p$9@1Y`*fRrX*Rb;Fl%2+sFurK=)IU) zolLL)ilxlLhrbMsANK7et85DH#g93H6YwyNL!}^fzEPxWK@Du@Ia8^4GqP0{ z=lyW#{S?=PXX;=TG8&WB`45sM$5B0x&;HmK(pW@X=lmu)R=g_awUEbkmnth_SojF#*aMSVM_sJ!Ew+eKN6=JcoZ7mW zt)Wv(oa{*n`C*kiPV=B1>UY!25d*l42qo7KXpM0{VC`)qI>HL!^k41nS$|k6t5d8X6$1<*Sh!mm?0SE9O?*(5DY6AFyBRegD%dj}xn~-Hr~=R7c6`0_1~s@L3VH zgy$D@uF@YCT4DYV_|^EhwX)i6d1&TFVPa@zo0>W{6U`fuKbuYHJd13nyi&UrMs^l{ z!!bb|2!4b!_lWSWgen|J!+xjwDq^kLIowku^P>*=VP=;{N+&wUvSJCGnO&KJ=~@#P zT0jN}p;Bhp-fX?!`&oF0aYP0v_h(|CZAb&ps_V}Gq?dX%*V9wu9&gL%tLDQK-PJ-u>&l&zfO+9UKLi}vexcZS zQf@$@ENic05Vm(@Lk)m+gq@@+eA&ROD{k(6mcnse59w{+x!Vqp_M5E^5D$qRDnQ%C zwpLQ7Rk;r66GqSJ5RjkJ@X~iQhm$T)Z38ZoWMdJzrfTQL1($vApcj>BJk4vNl?AO|j^Xb!Gy{H}#$}mR+EFSlO1G{9x+Wek0ir=6wda-y2SvMz=f(f* zs8`N^6JBJL!t^h_=a3pE1Bv)#^N2YvSbobXKKz?8K#h2mwk|5?W@32;YQ32gPz}e| z*_h^w)tmsRLI~(-kc|@-_>vHE;EmY?@GO0K(Vo)2w6!8Bw$>nK*FXTI5wV;C)*!{kGl?#GD?f6+0d5csl57o|Ud-Kjlp z`p0s~^!pR~Ol09t2bHA$_SivF(dxj2yYO4QQ;6Y%D4~fD?&)tXQQaF4AKynh&GIy{ z!_;AO1%rvImG?E3n>DQI3kLAz67L_5?rQqe{bJOHMF-t7F3j*JqGs&WF3R80&)}M0 z9A!(~4WHzFy>V|7^Tfv-XA>KW6`yp&u_62RpfB!5lK6K|`zYNGI~>CT87t|QWkvG| zQWMENKi`xKaNT1ULTBBB-{INd2A&-fk|;8t9IdFOk@vztlsvr-157|6BJ8|pGx=;n zwDYCcK-YxK>;19iwWUriWG#m3^ps6OW`*js1bz6kD!esYJ$(VtODnZNsm^Gp+J6O# z$D)-ZrLpdm?ZNrLr3shG*apo84QlA*EIAc8@8`LJQrx`y6iC|sPL0&MAnR%=J>w9` z1BQFwO}<8I?OUGsMA*L|GY0f(a`rZ^(WODu8 zR7s+S>fC+JBTN^~YQgnq<9?dD%5^FC4%+X+<8Oo>rEQDkuV?ZnZtcf#>3Yn26;;lp zP^-+*6_mtl&eGYD9W%R4Fy-=9RmFshdMU94WE@A%A*VW={3l?CR2Pff21+OJP;y8= zLVoKW%gPuH6pzLvxQR4Y@9|DEL0m1;6h6zG5?m8a5&V|wLZ0%=LYq-PxLu?B&%8RZtn z&(LAa;G#M%3>NHuC-lz<2op0LVjxnI2YHa*Y?1-g%eOrpYQcS7VnNeIn=Rq$X3J|( z2CG>^F~vgVB3-z2}oVnbvH6L542=^8{0mF{C@OnYaQPrg?zg$$CL!0S!2GAA+ekn|mtKqJZ9FOaZtEXusL^Z8SC-<(dInPTEUo^uYV0^mdjcP1fGEt;Y{qWMLcThNs3yp!f-exzy?a#rBveq0CC*B;^4zV7YqPo zw-H$0(m^DYh*25{u&5WeK?se4UNo=^AXy+=h@dvNN#-T>YSyzkO;=1l@pbLceuWAN zAb}(*Nz8ac(L5pg#Ts5&g$?2ORepkXCCs^&Ib3%*iRUcS?vYF|L!?{6KkP{Z{i$Ta z4e(;2$YH({89)b-aDP@#_E)Xy+0q+@klQe5U2% z$<+n^OE=`{74()PHyewJTR*K}41$%lVr?)zvRJ*+Y}!bvr-|{e%f!2xVdMMl+-<{c z+<>-SEkFU5nimP4 zzlpo80JMDdUKvbzCg>LaafVS{+Ff&NTSI1cWtr=n!m~s)LX=AXvQfnAQD8bQLe`Qm zJj%yKg%lk=9W{21e*1NeQ&CsJfZt%pfOX&OvQGk&YUtLgh}6aYFPFY4{urG!TltsR z>+NCk;^kDT!OI;+v!lNSUK-K)$M!xsm5alK z*7D4ZPNVvyYRPAjAyzdF;$`S8>yLZn(HrwRvUQnoJqz73-BVkV)!+VNi5Uq#1e*#+ zuSK&OKzB#H4!xvt$tWzQ?;Quobj$yoA~bmF=@*e8WS#NzG3v6f6OLRS>O)m^GuqAPD2Rm7HD>BEg$?xm`Aegj(;sUj zot$amTc*;IqdlI3C0}z)paH*lV&VXZcfn(iL@}gYtfw8!qE4mGS zvrl^QQRfl$b^94TY>z*^uD*^vo5HZG3VYk|mMw{GCUm|TEYhg0p}~B%lhj}O!PjbY zLj5z;soAJ|UB18z+I>5gy4M9wOW#5|%IthU8bd%A>|K}n?uJ=N1B>zc&BtaoYtTAV z@Qw}yeUY;cT!J##AwbC}^@l60k#D&lJX@YXflM(oFr0882m^jD;cjBwPQH8A%hihqgdA{jms1S~#)K0wepFLegnJbrZoD;?z!-p>=Eh$<9O z6?q()dWpcK%+)8a%2lzdOX@vzBkRJ4Sw*&Vqy^BOT~tAhHGPaFqOCr!GHuV3QQ6Yl z?&U%gF4jF1e!5ikN!Kl6Usw_7uWzSN^g~vK{|=D+C!T=5#R`RoQ)Spv9zi~sY*wag zf|-=^&nBQ<0WCw2Eg{&|`Y9uNuH`+BZXEREimnUVGvS%-z$ebxzgT3FQ&X2-Q^U^3 zE_&^~k$8BK(7RG-K1tjg0`Q9^KF*(J6R$(hu!QwJ=^&`@4tyPD3Gq)2aB=b3vWK(u z=dWURt;7J2jcuBKmNJJU6kq>fgiN5xC{7nxF+egTbczuum%r>m4MarUx$@s^2TQZxQJ-O z)RFlp)aZ)k8-R6NVy1|W1R5UD;>9|3%DW+`Y$kUqBUydG&AYcN=T9*T^rm&M4KR@* z6anXJA9Vzp#meDd@f(@x5;G-URwC|HIWT*>K=WF1ID^TlHHnE^hoRV9P?b|@OM40% z%6U+P?3uIn270CN902NlVxLx+ci<&!il;}T4%vdH%FGzR;p8S!vOS48NxY6jZ^w|M zv-{pic}`=yhT2V)HgmHScej3uVf97(XqQ7tSC2jX!N~Fod(Zo^wIs9CRo$c?E}9L4 zP^;&9X^eeUwI3MwFsqZMddjBh%ML^}$@efZ9ouE1V=@P6$e}7e@2yE4zLtkRz1CG1 z;81<*O5yA=;$XpE8VYsNi6OMcSXd3Poo4)}Yw?dD+sumvIm|K9dsGv9HSGgfj)pjj zFdm2{3BZF-fj7=O!sf#&!)k#9mZ}CfaJ->RCM^;9nYGXFg4$vLqEdbWQ7Hfmt~}X> znU}%=RwYYz^c)N9hb)3fb3-@+Ua|!iZgqu-c7JO|MlLuUn|Y(=(MGh609NhY!_a?~ z2=>J{9iXa-M65`_nlwrN6(llbGUwk!oPQJe3KH-brC&(-g(JXwt=P2qsE8tFtqac;angd}3Tsm$~%91^00I6gH_ij2ALa`hB zLVL8hvp;#!L(JK=n!6S`JE-LLp^X)zA>G1cXA0_5@V$=%aZ8VUC0lQ36xoD!&sO8K zQDIUbE6A}76`E98-IoRr4r3?$O_=6vl*K~5z(F4URlDO>5V|NZ=kgNJd$_J&g@(Af zp#oO#06ylR3886X9Pgu}I20M5azTFTwn>!*@L;!9aucCzoHwSjT|nkaQ4zk`4l1R7 z4ij=a?%_@Udgmo9q_wXpSPZyIS%ZzQ6;d{X%z$;qD19Zv+l`D6hCO~gLjHzM*|HtS zABpNDDZmkC3t*}7_zhNou_5jiH5h~1!Kc(74jDOnl=CeF2iySYHK!FkHR+k;eiXCr zy>K4onpiQZH&Ul=zMFox#kPQQle&iVix4bF`{=`){zXYhu&Qoj0Sak^-JRJmnT609 zA$G_Bd0dHeO6&7LWTfjjn*Bb!VSX6**rQ(m>^VX}sEbO4$ciw=1dQq*-~sK7!gQ#e&McI2=?3S|MoN!0Fora^+v zBK9K-&+f{(cCLRxbS~>&esOig(}}Z~&$7nw0IP#ZZm)OCTzs3i0IhVQGy!M*VB&no z?j}GWB(}d;W_>byDK?(C*V+qH=y02PZ%fA3q}fAax2Mftbq}ttAhkJ#2mnO7I!ZaD zM{ocTbPs!ws`rmYU&M(6O65A2*N(c(D9rl~l|QNKaB4x7lx_D_N((;4;}qcvKS8He z*p>4p`2?FbS%+7JT8Tbsu0F+lupo1&*TfynW_Q zd_M(_oQ~-pS{oTak+xB;)8JnyKUPLG)g#7~vVp$O(gEv#_FW!h$%h6I*Ny9$D~3iY z9(ogZH9mA$6yv<>o!=h-p1(k8>>AsaIpK$XM9)Zhzt{^|da-+e&2SvfvJL=VW`dzt zx4$Um_C0}AxvoxhK#?u$|3G<)rlj6RGhvYv_G6~cfZmgoEksJ~BPZ=Ax*)9VIwx_H z;jpRl;%LEAppXOLaZJiVRodKuD0*F07#BsbnH=kHcX`xgN0~gQKGx)>VU<>^D{^}D4Ke!RrK|5q{vqprpkE5XLL`>%UfRqZFySz*X;E1n4i_GHPoVYw1)!B z21RGiYJJX#!1h-;muBT146>_C zw*bl;HeHb6FdrNoRD55%F}+(M2o>m})EXf4%!{<(5AqQLK2vynYXjO3tWVP|pg*uw zfAF{RM;)di>NQkXH|0d9;jh?M`+fFJ242IQNpP6-r9rUH$S!GSxr4!1vg)AyCBY-f z-2-XNZ22@}2~9a=O|AmCR%b&@f1LkZnln?7Es%xIzKrPiYiwL|l|Hrn z;%{4}s_=q{XZ^l$^n{V)oemPcR0pyJY*KYSt@W+o$#064RRGqAikL{gxnz%2ZfSjE z+K}@LMPAp8DwiL_o=BwYU6^#7)rX{{YqSfmr-cPLldt}wrJw8QRd=!{y54)wW)>n$ z?t49q%oOOUPsjA1^F>ogwmruXFQxJf!GMxFRs#wcya>lxr67vX%T_-&dlnWN@LKvV z%L)L@!3XUBhEATk;ICv95wCGy6~8?L=0877m1cmq&wT*W-93VORFZKrh`VrrnuO|v zXCILnofmoLe>LVn{)$jR?pwfz8)Q`Zoz3}QWA3Y{op-)tt^ZAYNBM#P(Qn?b(y5>3 z1AqT(AzB^UIWxR{70_D&ZtB+5?cp2so>5LWV2z@H8O(|k z$1ppwF)xONGA@%S5(EdJ$=_zVJG-=ZC3K2kz5n(2&4U*9m#*R6`w}n6h0+xfi6?%t z`IQ&vfJhnB0?a!2#7x(Mr8Gh@i77D#CIU9*0GfQ?=Q~t;9cRw0pm4Eo1E-7HO-V|Scd6X^IxV#zlS~d&N^f3xtM}Z7@*1dN5Mzv?Me4pe z=qL2Zyq@cubq}yJ>Z#&3F{!hod*>QP3ufj!yf5FxbpI1Z{(B))mD>R|!>zZn%^WXT z9*3i@NE!e7-T(hx90+eF3J7yL$jYHzy?q^3JP8&XjChOIBJj_P4JA){hwY_ZJeZD& zU0udCEop6kJ=ibs%CkI;Z*bRc+gWedvOG#9!f7}X#ljxjr zUkOe=x&nhC1M8G#mtIw4k2R(2EI%`Sf66ld;`Jo)bgn4TW3_YD*qVx-6m3y6ZRd%})gJHh1yBx1nf*ym}%CF9?Ju+XJ1^`TcCiY}3m@~Ckz>hj#g#y_)wL$n& z!d$zAtTPXSfzpR`P9zvNK2f5~6a@0}qj~~h@&}VwJ^xXxUz7^_4ItQSghNDju)+G9 zrvgj~Ulr_7>T5^9?c9o~JhH5dypPnB)A_#rE0!Vt&G=p3an)wfwTnbxLta`lt>MPj|R5(0q}^H>6Ou zXQH6_=}eb`qQ4rNBA-e`+sY2zpe0y&9K|-i-Y{YbpYs2BxR$t`@;V1ZXJJVgd+7VI zZo=+G-%&L2bC(uA*}0)eCrUFjEJkWDJ1e{s79sf~Sh({*QpgC+dAIeuS!eOC=}^-m!d7OMA~I?6RX^FuGX#-+#y! zayM74_juJKB?!-Ea#Kw{^hr7QkqMBtV~4cvn3CJ^uG+j(P}c)%ql9K^JZZT@jWh#7 zH7&7t%bb!-M1Im!{7EGPsOs>Jb0S9Y1vRpdkNh-33zXa#d^C}sHE#h?W6P&;XF=Kw z2V!!^8EEOUZw=%*jS&r(Gzm(P^tU*_k`E@Umq>}X`$v%ixzm7N_vOBr*%yztoKM4 zk8~4`LBG(90`m84R;6_qD0<{%!5~q9UN!v}Voa`d9V$hSe2_;6H&EB`WF1v5%qYF_ zrJaGutz=EgW7-?m-)~zZ3!(b!Ih;tzr$&8Ll*U%#?wCkkF4Xiu^eO2E-k1>7z%k~fexJ;Tlp*_%SoW3lQfPJ;@iF@A z_xx}N<5j%uW>vWryE&aGzbmkp2=1q^MOiEwd4`VnsxQi@kj$DDKX9JabYac~sh(?I z*1kVeJ$l+h)s~7wa-q9kb2vjUaoxBAdQ7tsjQJ{k$DPcoqdE`}>I2INJOQts={~&y z{uye<4s!y7E~j2(z#Bm@elv5JLyJUy{iM+cl_8DViGdy;JmhizJ&O`5tvKc|Y2C${^-isq3XdRc0yLcN14J#$dI}y#Rrn1Cg!Kr zgM8}zKuux#Am;3bSnt+%_Om_Mha zZel&(C6V=>!tL-rZGu>o@KES6j^?0!(C?wq6Vz5FKfJH0k`Izav-&y(#>T@0Sxx?l znMXRdI}LwSaDC;Y+Wn#FRRIyhK}_9p%BRhJr7l?RM~2*h_?VCuH=In8uFh61SG8yz zXB`s+^oJ|kpD`WT4y*O!wNn>=N%GN;AKL%aK{4~IcA2+fY154}w7E{)19PK@{O>Ww zl;7S@Mf^mSdnX-!Jiqm(nGKVWtZ`ymwP2Nu4(r*`4SCL1ai$) zby&5_J_ojy)l2!3A6xgzcyE86bb)Oshv9*wBq$FH)*}{|zh>Q0=;ZZ)7`|W%-N&4Z z><^T%rf7ZGa!8`e5}+u7Ld8|`%%2>B^T8KnCH%*-jPPYlk;O7nF+3tURJ>nSY^| ztzeWH1bcR33-8*9u=3GzGY(S`+Iq6|Lny@Jrv!c?H2wL>2}0hLX`EXB8BPC4<4*6= zQmQzWs&eD{r>ZpThEY>OU%VgUBp*18wHPWdfhIN4Wt}*}MO6MkF3rYX!#*BaUY!PS zJL~@`E9t-`mw30eBWUcy+CzEeB;smlSkwsw~OPFm2Z94B~p z+3o$HfR0`uzebcB1S5rXN{F*6U$4qZ5fkI2YSenDImLm$FuB(CV(q@}rtlG?dcj!C zVie0k)kdet@v8jrrIn*G^?bv_{<=XoUVIvm+jNk>PvI4)a*4Uiz2l!z`y=B!V$ z4{q`j8JRwXC>f%Ly*qf@CAIvTLEX>$hE# z!Ch226DUh5d!*oCbh-$3d!|d|!EElxZN7l3o1VxQkX>_`C99v8ltUrN8dp9l!b!rI zB8(j7{!VFEWh19%lCTvjz{yRePVQe_!p7^JqaMug~nXk<8ZR*-P)3hqn6~ zxdm6UZTzHN@~X$*{$2`SsrTTgI zUB88uG<&HqQtR8DDT%jLz zFRM3F^+Nkv@I2>5(G&GX`2=^zx(o_Mwz;aOY?28@v47jsk0wK-ifBx`B&G^GtGd$I zt-kQbN^?1cXJgNWHMCO1Tw8iqit`K-NSqoo#JmdnDxZA$U2eBK9J5Ja%a0-Sp0Da& z?B%~`PchN+1Ad!4UU5JbUkaXzR+f~f>WQw_4;c@vHy(3h1fSCx)x1+DP5K^99_5GU z`-}!7GwNDd?X3MSFj-1k#tQVBo z$|8tY%d*#?uv^PKSYH?l4b!^qm(%F+C9`$hD7@%Xro0bpYdnUp?-anv-`}7*mG5<7 ziZ~w`@f%`_gWtIzKv10mt1z#TSZw#Nh}|o06l@1^8eR?JgzSp7Taa}9Bk+@tQp|F0+c7-9<17(|63pIxoqAAAXd=U;fIQpRXO}h4S&Ytzf>) zTBA8g9+t_@bgAJev&Oy)NHs5?vvI&__@He}3R%^x%;`6>baTI7Jb+#^M ziLrD*>#-e`{9XQX3NM#drSSDA_Z(hdCzH%(qcA(|K8EGX^!oU|B!2kUV+j^*raKc+ zk(akA9>`;m*EI}{RXS@wE-tY8{84Z0dVM#^ZMuKGEiHJUy{7u|%rG6**xU@$#Or$ZJ5K#R1$T#+B>!uopvkBv%ZQFmNd7ma3(13p`Vr^dq3Re|ims4>wS*V8sA z+nuzSpy^{wLCf~_E;e{oVqk3f7`{q@U(AiSS!T6jK zxN-$_i#belzqKAouy<1hoxk;M^^lFrp!D7zyWJ9ouIc;7Un{Hom@og5;8^=40`+1U z9=bEd>0_2d0;AZ~ExLVJ+`;Py%fPPrFmnnHy+nWYNWqR>@#n`WLc`)dtng+cz8ow+ zK6vRb2?Lo^#YLkgKBx1#&Irvlt&z`ox{YiFhB(5f!> zVTl^YJH!$^KPHBJHm0kh3&ae5AbW~grPG!PEkq`^)m))}+Q zbKw~-%<=0i8|v&@C(r3T;>SB#O35ID?fEgwzH~61;A+BcmG)8Qk=U$= zt;;UAYbX*FkLE_rpYA#1Va!_-UA)tf5_*6lwmCy^v(22*=gE@gcUAwur7J&Kvq$YE zS({$6+!ekUl7>YRc`YgXJ$J{9Mrd4C4%%3}-{(uHvq8dIU+}#U2Ngg4IJxa9C3N$Q zqblAUrQ%=P^Pr(K`D*gN0tg0NFnqKtmh#2oQZ)`ZDQlkYLd(*IUT9ap8I7ki2o<2so8MXcD_{&+#P zl3;e*Qlk9PllQ?v0pFES?L)gMNf4)lRhG1U zl4Yw|-)>%mIj*X=kv(LlAS3q&X!0>F@6Xn?a?614m)cBS!BiVfLVa33&+)3(W6T-) zg$Rvvb6FeW%y&jkacqbo&0r3s%-5j#Vm2j>7|E%#1t`#prHOv8UJAv&d%pXnar~3I zv!_P&f4Vag*VE~MG9D>2BWT!HeK%rCwL-nFo{7-J)0J_+(Nvu@07qg8Div@nrpwPqvxgIjED8L#!4bsC#{H70rY zQ;B_Aq!z7ytx-`m>P^_l9}4VvBnSu`N|Vz!v1NZT6$HN5#ImKOEumk}f4yuz#OXi< zOwr&@LHce=5frjDtO)58*basIPoWJKZasa4*2O$d`-qjW{>D5)uU-%w`BG0m;tVo| zg;P`_W-BKF{kBXp$l2UTQn)BL%omVdX!WnvZi6c%Q2b;*5QRLzoP44A0xSp}Y@5l~ zGDzT3j9A3bl`e1%>hfTqFhvkLOq&T5HzX{o-xI`va4)VL1PBUN7|mx@fn!TC2_YU0 zLyLA;+V?TbpaJcj>i$4|8HT{CB!L2}w<8AGFG924ul<0gSk$}SShfR_grQfNb6)u= z@m9g>P!=lB6{+Uuq{{*XIWz6FhZn}FHcp5mQ`Bj81AT{=NeRKJT2B~X^3+N-pRI+u zSgBF(n!5H(A267%d_oH{X1}^$GItDXk8Y>h%kJ(rJk&Zhi77PG(w{M{>AJXTKlf_n z(;6^0lHl$3X76lfu9DpfBQM+JQ$}q!az)jai*E-}cK%@hk(JuI*cE&H(8|IbbjAcu z}n9yr;+2^e~*UJ1v0 z-c0O`DLCnz_Nq`l?a(v$Z-7U3*A;*d^}P``zfv1Oo))zVnpmSg2m24Cf~MWsdAqB( zuCd(v3I#kxLzZTh?Wq!Sa;~2MR;e7-Epj3*2!B6r1Qje$t(D)6Gak`u z@ip@n6Jg-lowd}CaK#2KOl!1$`g|mAa~H#-{%jscyt4x?t_e=JVRH}%(Bze&$9#=1 zCR3fomh7O2yO9j|sknYcfPuBW)^D`Z+YXz==h(bbccrIoK2Bn)BYv6 z^?X%hNTz(uR9Ch5=aDy1MDq5@IBVd^*cMLPLW4Oa>qLM&YD)u}hHIg+j|maSFn)Da+wa!uClk$$3Zy z{$s1{11F*U6_``GlCr8Yp?n)T%U+D2c<57i>&}q5HmbV{aEhP$ zMf>@eQt0fg?806{?=8VkDdQI##}((T`$rOcR)%@@XYEArfvyl z6%udr4!t>Oh9C_8{8(8sc?ib(YJo002hIa!qI$gE^9)vYOSJP^`GK2*&d2iw0lF+# zF;95=Tl3wARd)kwyt!^w4ggM2mljaPn#X2AMRIz{C|opI&4E>s4INXo&nEFN`mn5? zQ8;qYPt0!Mn_XIJ{*UOlouYKI73@qKk{lHbGjj-i&z)59j@H&6!?CrQ0U{g9<$4>qj-P z@^I5jzj8XkZCd!ocPw~|k49ms#88MsYP<={x30ReC_=NNy6Vp7etGl>&D)HctPQ*- zZd`CeS*nja3CHxA#`Vnnq5YIHa)3M=Ipp>M|8Uu9ixiN8URveY@bLTTUwvnGEFr{CNhg#k!9CHQ1LvS@QNbR1mo$dk&v-b@ zIBGv`hPoL3;yhoLGjpzUOZW1&%)$ahXgPV^^RiM9x?~*qPUE1Tp|JSP@ZeGt)})uL zA>wbir}VM19JdD?UrUc?%f~z2>UFrjV!W7L;o=H#Mu;9d`(zBJ(Wg}Ya%hv9Zl1dUjh!{qaL5wc}y-V*RxIr6vhcTqf^1ir``lQXM?jY%8`IeFI^6#oF zw7oK&I1{@NYb&oG#r{U-k2wShJ#MfpP5-A*$;j z700LIwHS%dPNH~GnYv@T^rnm3g8p^ z+|iq!p>3)?9w2^<98P76sv}c=*AdC3l{V6BQ=1u04Eb0#&EZN9)^E`lx-IYTq!$!f znpU+b%JwORt>KE*cXPpRJ9R+i!dVs7gAE7~?Le&x$Sj#%tD8xb_gQkON}OGe->a%# zESZB1*!j858Jc4uw$MA8fC~vFLxnS}_rf8ZaWH0bMo?U1v0L2my5{+-T&5QXdaCAe z{x=vLLM|Y6R8K#txMDJxO+R7?=&i4#G=^?RHjDyVzz>NDGimJbj?pHyY^5~yaX z7RE1;)`A8t>lKQyEM&kK>oYU`F=QoS4Y$IB_bZW4^O>Ih(m`Ez>MEcRya}pC`s5B< z4e1UKyeeLkPxHeUk6**}CgBtYs)-NctqsK%cL(6Cy)o2Rd)@mC*K0*K(TEq*OXFE8 zN1x1Y@}`fbo{e{?R?4e08T%18mCF`U@)iG(XTCG-|13k3t4phpgAUx5(kV`9lgr}Z zti3oyB)}@57DT-LA{_3|mYITfJoX2>nB|5qon(S^od7MXuzw)uB^h%y;kl3(o7n|L zG}_=Ye>wM#^C0qqd5P2=-hjA5N1+VTu8?gp_wCf29cD(w`djNcYHuD~{Zk)u(;QT; z{~MncjqG_@LZWJH%%+Z(|OJw>FW@zmwraTS%Su&wPRp9g|8`NYZ=EjbEh+Pi>|g<)C0|4}eG{aQQ4?DtQ) zPmU~|@S$XEZ}7L9k}c43aTb*#sWVJ_V44J_7@X<5eC)Z|nGfcl6CMz9vBhQw0o`{Dio{GO zK&5c+S0;Bc(|tyQpK@0oP-#-b9+>+UGlT%+4>{E$a^i&6#E|M2@jMm$Q-G-RK9!&cyv$&unJm^-K%-W%? z(+g;yHagrz9(b~J>r~aKC2yXf?tE}L@-m<5qvL->6{P<7KNw14YiuO=_@sI6{To-y7w`iIH9~hu zsQ>;%A;^y6>UDkC3m1gL0lZSM#o z3smuN=>>^{0PDgG7%mSN-*Q6*%+~2#?$LT^)Z6Akk9rs1tb-01dIoJLT#o1+X zZ2NT<`Gic1pKSSfyI$x2BkR56nmYUc;b0VMQPSR40U6QKO1)77q98L^s?<8rqT&F8 zphN{k3{waPq=G0AtpiXXASx=zmLZUUC?G2g8M49-J0XLRanAW&!S?<>uh--GM?wseGbG6?p#y6sjK()d-+W!;`=UMfJHFE@Sd(o>+UDJ0k}a-l%h-IDvE z;p?LcV7e3Rkm4CxXSN=iCAQ~2`h4W`x8ZF4?945+QGSc`zKIF!1wU`fLSBu&kPv2iA8VZHV5=JAgOA9(E^v3B86Vc>)jBd-~0m80xn`E>MrMQ7;g z=v}-M#Opxkkt$8EfjS8^>3BrWj^1uK3%};#I-#pdmC<;38wPt`}c#|+aWsFUvoz(G&;NVy@cA4*`$yV^O#ENlQo!#9_;DoGwbudtE^ zp|J+)ngI7y56av=Ma-a^<=9Cawp_~@ohQ33wL^nHFV-WchP_c1${>=-OMb zwH(dB%-kQKJItl3CW~Y|JnjM?lf%}GCgYp%ZQ@6K%aMhIj;kXvz8?Xvz7++)BeOO# zra9fU@5A3k;o{$X|L0Zy*KZkti6hViUAlviI&BiDzq@ls?&Vwid)mVO*{L>G ztpK$0IK0e9$~RE8?zOz(E_^uJlThO|>4RBowsw1Sg5TUhGSnpaSWZr*EPBR+vX>+{Gl|B^5_gA7;*a%9ntu8eo-!IMQ? ze>CTNX^7Mrs}YQBL{h^SUM%Q|Cz^RQEzgQBBsC z?Ux)OM{o@J*Rtfd57Pv<2(zcsLTQIIqVO1ebA7RnqDY0sxRK{W!A4On|M!xm-$m;B zPo=6P!~MqPGm|mQ}4Bdpl8~KhN7)~ z=5*iQ72d(8)OR|m`pJqrm0q}1h!MC_of2NAD{c4AP}d#X95|0|hmDa^#%|bx+F*;j z3gAOlsl34tj?LGlzao91R8IRcEZxS^BN?*j{e5K(kvT4XJu{a*>#C;D>AI4(QE6dq zOqV|xlYE!$p5`XC7v?GsxiXGQJCcH3~xi?jMy^5#g2*>A# z)t)lgHNMv&_1z?v_s4xaXzGZ5;rW+vA`5`#b?58|_F2)5KYtMl(ne#$CU|*xHSK~2 z;#lM(@}3*vuM=<8OT4Z-haYet7CeXy72^g(*6lbVC3vW;clI4rt7Pb2uhPz(yZaU8 zT$_ArgcbkrBYPb_&{L);Fce_d3DB$!0EL31nB`#XptaD%#SJh6MG8indDTRU6tQgC z>|z~W41+0zR}nE%!{GQ%z;f;{4$e46v0!Uo5uF1 zGKh0Mlg@5QV{|F3DpLyTmJ&mL)|VpH0O!!Ht;Tp`7_>pA&pKxF^EbNZU(tKE!B%iL zo4#3)Awv6!vS53nIc9b!IGy|WiwdUeWbA>!HEXETfyDWd9WS z#jmon!u(Vx6Y!5IY1okgnJQ9{#9SL1lO3`#xtOpr*0@Ub1$U3PO=kMs!A{*+vm7;$ zxSgP)S(L;o<-Y)=__@8sJw7v(WNUBTq13Rk75tu)i&n7iwcs@yn?7z4e$g`&b$G08 zqBJ{d?QGGoq%E@c2$Hk1_gycA#*V=9op_1Q9I7Wxc&Swib>$ur-PU{}9k;FOCsN0Z za2Nx86E+Xp8|H^;^0h9AAPqxj`KNuPCHo2>q+8NQPG2gVJ1VCpqxs))Zps&aLCvqk zZ$;0&T|17B5>Gu_W^(3xjv@mAzW_!D_88LX)i~Fp5HGw&LJ`?$d^QzTz*UQ3mv~OIJ7&p)dbyyH^&|*oD%xJDy1zzAo*G=HcVKZ6;`J{RjWF%P-arg+*P_jsI0dD|DAJ7l0TmbZagzi zu?`XKGgtG77gX)}WeH*o-@H;Vw0_L)#q|dvwS!{N&5(^udJ%jyB{s$i55OnU+tX{H3kpoO=HE-kZCj}-r z^8&DJE*^vH|5WqjRc22#dpOE!EJU)wYK%M^i>aBh#WPs*P>z30aDL{s^yfr?gC+mf zLR#Q_aB&Y;$Hm@8x}hgCh1T+x!Fnv|{OC`2!1}55AMFZ?Aw@!X{*7fU%UY zU%w#$2EFw5GATZZR3A`4Vap1lQEYNF0s|au613$4A13sl#tK!xv8K-Hc_~E;|ota%0~8GmsIN)I2#MX?WCFs?8;v{ zV0H(z?j5oxnh`QmwKo%&=02T?qg}OU&RpA}k>u9>O;l}0*!W&-6#&5}fZ9PDV;0Nq zV@lb5L_zQty1BZ>9rq%tkubBbO!4&ZBQVM-N>mZxh zLLFR+@NQtHCf=ujH=pSQa7=VS8(>Sv@q!!;yprNCZWkru58nu477D@odk)@LvC>2P z`QiA=x=1chrZ4$;$1gHu!7;dLRaHHm8hZRLG##xjP4=X&48Q~NlQru>O%)%{1>@zL~vC?Zd4!%Fwx{ zz`%!F*$K59k%I(&^@t&Zu;vh5g?FJx<)C2S?P0oL{JwIOa_yAA+WCf2ZIQFAW2(r^ za>cbFrQ|Fx!(DmNSK@)P%iQVyPA8hQn)n*8=`%&Pnhvy|zF@@#Is|z`8c|FX<)-&p z1X|5^m+^jeNY&VAiJ_$U(0wlrfl+`RtDG*Oxhx(A$np4zt`Ade6GkBN=s+QA%5IMq zLb7EcmxFveDE6|5FEx(9c710&i=Rhi0jz@u*fcJeDQG(7a}t_AV++1eMQQQx4^fK;t7;`L{cEec+#g-e(S)*}*Lr@U8RX_@I?w#1->fan5kj+wRMZ zrX+c6qd*g$#;`kRJ8D--9ne|mB-8veP1}D@R9KghXs;}*islA&KCN>L-fPKN*&9x1 zwBF^i0Y`8!_pZv{>Pho*QNy_NjiS^pQsNgwPcl)*h5D2Nr|uuwldMT{{KqK4y{)B- zTW7Yzv(^61W~~^%QV*HW91ukRc#shy9kwktIUgSy3zRKZ2Hj=g7pLo2-JPoGr%l>L zQ#`bKKQqCgNk|RLzUIZUXno|0={?JZdP9o~A%tUAZq{x}+WBbjjNH3XL#DtLK~=|; zawJkpmv*7^S5?T2D*7i0AWxvvJq^dKdd;M*{TEq`!D90Bxlo^4FSbcDosk?E|79NW zo!&OPN})*(ME7u>%IacNyM_Q!{m-RuauR%N_wh=;Vj4XJ z@)BV=UR18jF}6-|36p5pno`g66be3*t{E&d;8b&;rL>{sjg!c1UJhi%EVNMfLWb-O zY>*HLH>T_V$;so3|f`8kd*JdK*s0-Kgj5R2@%Xey{npVk#$>r~aL z%!z(`8af2?oJGd8I@oF4kUV|PnkJ8DAwbm~EueXw2es?9PVVCkQx{0Z+$l#B%bZnK z({*Z9KrNw^sM=aW)sr8F`kUBA6Cs}C&#@JgQ$I0+cqnCEI6c&%c}c+6 zR2jP@KVsLh%T}5=;r&?%x1_ zCp!tB0FCH(dJW8R68B0c{`fH!1w!R8svCK^165~Aj|{7xBQR`8dv=jty7Of#rYw{RRjSIC-xX=P$6LBi zOdY2rFOJm=PGe?VTTVaIVp2X6(RJ_nM^dxG{Hyfv8o{wj4n*pljh0JoCs2SSHP(bB zt1g~yJ{vOK?J~WdWrpu0Fbh5#Fs8eaSPM>u^x>z_-X@?*v*SXC4-ogCx(k=A&AW-n zyCjY1V(`CGLZF_o6Lr7J*n`8AN(ISrc4E4MUt%2+iMRVzNS13LoZF>;brbh26z-Ha z8E>9f4G>!Z3FwQ!cm7)lLDk>tUum63BGwNg;%=}5Ec1!e%S;9s+Dr*@Ko{E|`8JM; z_GXmpJ{JGijZGfKGq)>MiG>y(+i$!0mF<^-%`9G)4o@OAi3Od(Trq*C)KHNL6zfVu zc4GS(6-hD$lp*t^Fis7(x7u3K(DC$CZIoHb8Ayn92wT-8%TlV5s}5O*npIRuu1z#P zv};1B!>;ESMw0C5i&iRbNOH+L(@cy7&4l+TOjKnm%1W@^BeIR=-U}NuQl+czH+Lo-si zL|Q0gTH6eKhOq`-d9|~kTCEA$FQ#>qZOi>obN>w+{&(-^>H0gql1c)3-`4!<@&x~J z);nl3=M9^bPJ6u8+Vf#&Xc^ipsv@W8r!aOV>a!k*f!=2Wbe!o~>el_?x<<$|cBN$o z-i)_en0tLnZykfYm>b3$BMn3^k`I+;vW>=+T*bM%-~i>_k-^EF-VIaSl_oHuO7wFZ z*~iUgqB3E;QMjsRwC12kDnZA5Zgs=M9mGV`$?%j}e6ys1 zS%QCy|0}Hgd$>XNGE9icnR!irn;)>anXoG&zYJQ!1kt|`LmRNh^ta)lk1WoZGj|jy z3yE2HrD?|;^Qs>D?lQHy4O6Sy>7WT;hgx4l%zjH@>bTz~T0Gn6eh0`HmWRDU%_Yvc z-xNnkpmqLR@Z(0p+4i?^AHJ2}kZG{(@PcPr?p6QC@pO{Lu#}q144dWF zLZJ!+jRep@uzxiB+dv_noka!|-MkQvkxpKRekA>h#ZK4eWU+lRC(Bm|CXnF;-&D(n z)SaJ+m!Jzz5_kk90!zK|;L|tQR^>^-wdXY|t${g!ATq*_ACk0Ay4zo9@}0Qs+1o&B z?i*IJ*99Fic8y`tJyz-UL4_<(5E~XWkR`~G4w&+_%S+JR@?n^mMPh)3)8#|9RvOo$ z-5R``ZqVD&A^;C~%X*{BS~Aw&>VRhN!6n;ycsX`W>%=Ft#cUd|+&&e<^44OK{EOsX zp@ZDY_k0pyn!n%h04mYhqc_g>Rvpn_#9)hp8<_g>3TIq04h{!s6u_hGc^U=_I5#q2 zar7FsO`n8k5%b;`+llImS8)`s0aYUK%$2Nd+S*g|&erK43`IeDD{z#Ax4(*6&KCZt zwSUp>-K8?qlRd>#NTqih90CrOWX<77!$|WV6{GE2ay2`ug=Wm6U>_j7@Nu7ywDum_ zh$d$BOOWy1`wi`_M(D5TBIa{w9%t<-n$}RYh(eegw!GJiOm0%!GydGF(&aREGD-3iN4$aa*9vuT=oFx=1>lkLmDhAAPMmh7jo?oH z0tAl$<;5&4*iD1@K}p@aq<;QOgk)>_3+iQ|K!888!NUqY&rE;aOWdAymf=QxDZI{l z_9VEor{OZ@%yCdlP68pgiX_r6m)2i?jAg_3Pb_?;_PvVxwyLJz`uT* zu#YnRg-=dLk_&vEXtUl$us%ag!wce*4ZsI5{4TYVwGqxOXzv=F1UZ#M-w$S?!?Jqh zwCn`aTq6Xmqu2%;H45MaX~4(nG)G_&Bfj)Azp1csHE(&_Tq^7K_4}bV?ErblrnM6D zHM?r>t%_R=)ksi#1np*a*nhvL`#1d2L1U>t5e#Y_l?=~#&>^guGZU4YT;zqGNAJMO z({{?d?_t_3O}k64D%$B{FWX3Da1fsFNUL|!og(r9{UL3|$(!KvlSTz!@KkXUav^rr z7pQMD+84l+chqdDjSB%r!j*W=Y7#$z_oEXAW6C`6+uq~J!HZQXOm}tflk(H?`Dx4W z(wBf=kP7Q{AK58q8;JTxzB72uwBJtp9|}0yZ~P9LOY8xRMj#u11Sq3wax!E{!Cb32 zfs*^QQ{i%HGHw$mFqn4@q=?_hc?IOxC9Y!-uP02(JVBh-JsN{KtlxF4XhA)~Y><_H z8nPWtViMh)%okK#WIj{TDwVv+!XhP4t0|znk$E9x>$Ff_h*Sqq8<%V|fok#00D5_F zX(+f0Y4WAclXkI~%x6oNsZd#jccQ*LGGE7%myTJ0A?Asr7ZZ=N+ zCDw2(OtU*lpWrP?VOd@@*YxXc#pLh0u!(L|H++Jq#@a&(lKa2)>mE}A)+HXoDlu`N zKGU4A`+$Sk3a&rL*hzefA1pgEZd_+DzVRo(^UB$|;d82Swb zCBTiucPGUj_YY(=WAnCzTeStyhsqqM?iZiBa&7Fw8vsdkbo0KKySMY101k@2e+)FV zUc_IP@q`o58S+_&knX!KF!*!utUnmlov=XUd}sQGpbhryRv!ePclKSOG^8(GTE2Ys znfE;|kcM=t^*&cQrmI?1Kylx@&)d@SY8;Rk8+^vBUw1t&RLGwM?&CP1VY z?TbNG1`C)w=R4VxAqR;C&K><|5z}&>Zsczk*gix)oGqcRGFc+j`29wDwo5jPe@wv7 z)@X!h9O9f%?y*geHFG5Y79~xdslKp=krlQyyb9RAim&BJA7-urWIfkOSQ;j2V-06; zhmR>QorddICC^x1$`Pj`Au?t|I@ifaaLTw!8UY?;{y~(63wJ?hB^H0^>Y zNvahB#|X&IOL`o|Ja9H`E$w-5uv0Q6esGbEB%dFL-iV#4Sz+G=VOZBd38kF+bHki zg~-rbVcq=SnI=YNH`;Ke(F1)m5A9_@ybG5H`WLA8O|cg%80ai>?kQ;;X!b@}-zKZx zW?JB{cs_vYpVxlka~D-YGr+CN0{CRPtZqTcGkTs>9~|lyXCJ-pAsc;%rE#xuO~P1@%9pO-*3=+((;Os`Hz;TA}6!{ezK}y{PIa?yVhMo zwow+*o*I*j>8-U!Y8+sR)bboY6^u=sJPp*|Ns?!BnYl}j-k!%ngdnj^M+i+*doEHcwHRd`-80H`KBD z`DQ^X@{0SKp1<{uiKUz%ZnJ%2mp12eS`X8V?ryLX8vB5MuTaW)oWyyYBjxlCQ?#X4 zFwX|^0S&sHZp97rIRUcvP;1yCFdQnkqf+X!1Eyqj#X>r8(G$>zXcjb>17(4uAR;Rk zULq!$;mb5gF#h#&W1fR-Az=PbVhSX1XHldTETih^qfzIlGR~XO>k{$y>xD0RGX|J4 z$b1L;qLr`e=tQN(j`+_@U-p<8)CyU{Vt<(U9+w9H^c`up%bBFNVKk!*`Q_` zaH3JCqv$4K3*W=M|Bj|2W>@;C@45Y*6%jJJFhq=(pa*87&`-_ICgXQ%&3a1I(h9@V zt21kU#g1ieKOEeOkv7qfke~QoFeU-#NuuhbX#CATu!5SSwU{N$B)`>8{aWXz#MME& zN#~7GO}O6Io8Cq{A}qg>xAT%<6G_MQVs;;_a1VPgg^L$)xNC{3n>?o-O@rDy331jt zKdYWJJr$1Ene@Z^%n@M@?CCidwf6aiuNIZ9NKRU*V#vJ#Ql~uok2&fJLwXiaO$X6{ z3YQw85p%f0p=}-s_A;FN^~xL)9QypLL{Oou&i#;=CKH#1+|ycl?c%>3yFc1%Q~;I@ z`<4}$RB@963=u_4sVhL(gKYYdYMVHr;XY;Z19%3?j11Kb~iVS{;tdc_;gOMqd1%P_?xqL z0|-{;Fz=*SR3gj@$*=4g49Cua`r42sikz}o%#cEvFjsNUd?Tn$dSJWTZs?-^o+*kq!to~^|%;}RHNwo-Q+7=q{Kaw{9NkO z=Ptd)z}6mdoqIO0m~Ex@2Hf~IQw3e%{yW&fEGVi*D-2I2I&Pscuu-^ebisxxWnpjw zvF?S!q(^=UEDkC84^OSuu#3yOZ^u@uQF+7)zT=0LS_0Wx2hf{!8y}Z%M_PLR$X*$n zQQP?`gBw1KZR7wTPl=j3!p)!J-eUG>w<10;$UNvX<0i53c?qBD_wz*W#+xWUO<=pLDU8(n6n` z2~Ft?x4Q63@J9w0ZNHL3OM4HM{g7xrrN_37bK*e15&7dNyEAp>?7$N_Ky{%DTXEZ} zGN{$_ZM$m%?U}MbXSbz$xrZiM(~ja~+rGC>bs(=fm;csLDz>QX)t3=CPT|gcbe)Ac z`jI(;#;-O`NV2b5G1@!5ku**KBm(bd7W4oTZ~gCb?Uk}*lWmy!08+35gQ7^$52s!t z=J_LMNEdSzU{mPuG#>bVZYzN6lTa#n@=Ih^h9f`ycjnix9@MY;Rh)a@0G#@`s8Ii? zl1in(?60#Aj1N@WDbC%B&lQO6i!#wK$O$pt9VC@xN1s{%ROG$gV!OV_af=}mJLjCa z*KA*b<|!M-Xp^IIV*bq1kCm2t1f5W}C zD}8A5)N~#Q15o>GjZSMk4}mhlpv@q^A=x&pT0zFa`J@h{9HE5)k>Y86Y*IPfTY3 z<~dnl6E-O$vC)bw5nRZnQ&G#^dJFfAm4u3)mVQ-eSNp_l9h?j)2VDu53qj`(OZ3?I zM9-#G0{Lw=x~W4HHX*hHi^kbIOHVQ-Ad>yz**7C$)^5;53*1pR5pI4@d`RB$f;&|g zkh9)dJZ)bKsOTr`yZ45fx!Dt0a0hw*fp3b1NKfT%aR{2utmt>O_&xyi%D=T*t}$12 zH(wuQuH}8Py_y$db5gJ`nxFQ`RAij9<3g1|obQD1t+Y?rIPvtEG)n8LTFvt5z<$^g zfEaetd!A|OYw_kzrbYNKz=J>^Ack6EPL<`vSyyT7lDcvJ2&qM8NjC9muukCN`hX^p zkx%bElU?1<`|TD<*`{}C@#gJJ@B46x4xdRe0i89n9y&7kl@?F{{rbeCR>z_FUZt}~ zErQT=+;Paqf>$D|Tf8?<>eGJ*d0PND4wPZtpK=wfn5JGim;?VPyg$*7+biNJuH~+L zx120Okg?;$F_S>l!502SshgK|6vqT+F__xyr35CE*EN7o>~Ur1pC*l}$2QjV7&Fmx zscCBGF%2s$wlH@|bFhyoVZWjO5ln~!YoX&r0e74vPE(tc4k8>e!TZ`^AN}H^69D+R29RK`*AuBjXa}5DEWZ63n zu$M0M9-OJ5=sA|xOq9G1<*FaU+yCbNzw72Q_>r2(Jh3 zK&GfZUJa})+{Kmdwi4ET-)l3%?*-lxs7`IpH@o<3C%#XulO)sjjfw?f#S>rJPsY!j zy>E90D9oZslQ?N?=~ChL;iUH`Ejyv>^|i*u7Y)pWZ$9b00h>TIL8wCA?w3ns(o#MZ3*^e#T75X^& zuTU%b!$-RJYSs_MBU_Ql&04IUr*FnuOSaI800pkXY{d&TeHX}Q-y7e26NJ<*jh3$z zFjh%tIe(D*%>XohUK9F^oseXsWh!|ERMYr3>FVrM%UEJNk@Fr4Fp4Jn&KEn*JBvM` zmIy9Ug^`B#*Ale4zW9AJ`&93J1I=*w`-wz~r1LGY>o45tdS55a4m0?<2|tbb9Pe^& z8mw&qT)lobau5L5#`Nf7AlE^;BPo_SBET12AB9g#?*Sy;a|oJi(5>nP@g#24qPI*T zpl&7aLl&cHyXDz%;iAyJbL|(>!CCZz9>au1%vdIg3*Cu8HpBEjhRB>?2ju50uV2v1 zF?=rfB`5;3&^t{1i60=^_S-~RPB+XAMc-SzLjyqORUe!nyCOyzkPrsc(T5uhNCe-J ztz!vQuC2t`3>b1xDM}Jx3cF%Ax=2oqQxqyOPZW|;?YtEl2-hd>OcKyntx>NiuJZdX zSy^lNJjUI8-tU(nzaw?{1=|F>Xgh{?t^d)b91w(~MPXmUb zqikN)+ig=dD57M23rUTdBfj=^GoI#6MQ33@$$Xs)&ofFcI@GuR5SEMxDVVP@V z2!f*pbVap4Bcto|`OHzm(g6<6Ai2jGxLp%#7F1bw%Tg7pGs-G7a{tA<<)BFAQIFAI zy=y{_L)y$%6IajfWp`v_Opl?v7br+<|K}e7@F~kR^Z!8Xm^))@C?Ibmr(6+*lNa@v z6WMZv+6n8TlV!Q}_0V(Z0GZ z({|IuF(>X(popNq!s~#QT9~2IB=`bbpn8Cqr65+Y=joES&XRQ7`$teBu0w(dYL`61 zr#9*S8Fkeh^B6~6@;z^~OXml3Z|xIh@?7PWwfa?~Sn71at0@%xj!8 zIa2k$wa*N&BZbld{p-#+^M?{3J;v4pGf0zN35A1b9weUZNlTf#^W4*RlEr|60J)OM z^+S%x$Gv`l^4^1h4Qjq0^_%Npu5s-6#Q*S_-gd||IlAC%yMXDQ(ZXJg16JdYQY`sq z^gH%-rZiW|>NyQgowaKo4vd(lU5NE@O*u!}HekGRAV^{K7*dtmE(4-pJ8@8BIPX>5 zuuF6OU%T&lHWlhA%jXjM)Z`+I#`M`oew1&HbNi^!luVV8MwB;#IGU7Fhwm&dMSdv} zi7pSew6f$#xY~HSXX-v7IF!fT+Wd25lbxZckgT{{I4oNRx$I=nJqI566Cu%cH8@y3>ho{03XJTJdyq@N{Md@=gf5g}MBuQ{ZV#-HNK7n6YmUN&Swb z^!iHo=upy|eRT#To=YFwQV54L_cPtikq2*@J_)`DIaazqoWK5MiOD?0EIvw<0Xhr5 zq%O^d?Zi+g^S8lH9PHRbg!`CO^a~L!4+L2{fS2aRKO`EX2|#4@`Ok6E&Za0ni=KrB zW4{n(=qni+hzk`v30MTRCb$UV2)cj`%V_%u0T`2r%tbbO>n#wcyN1ST=+0R1H@Y^P_A$*-JyPWe=@m(*2V14wimCHBD?88;HKtVM zJ05<|YP=?pg|tRmY~bDO*SzhQkBProS*3kN$xg_dj*XAdeA4nd@`nGF$eY(RN6)3& zu*E;)-Fd&L=vD>03I)Hc^8#6oAOSJqt)9vN&j@&{rccs6^Uz0t9s=IgSJ0N#;x+pj zAm&|UPOHq{!uz~B&KftxNkax5Dnb#7rd+Z0M-IMDW z0#HM}AbIM~-8vl1i8bEBzC7slMn@By46*rs!q+xZzG&*6yKu6+H_!#_^4$^(P6K7cY~;`h^or~UD4wq28JF>qwCzvC z=*k1A9G%8a{BTv|C??6PD!#6S)an1aKM#KJ_^AS8+!Ny?gd^AGM;N>5R-1%#YI|$0 z*HA97gO|Ze&WH7^7|tzYh3G(`e(%KVA35WB)K0^o`m?(0q;-~LMVAh7RBSi-=b&RY zlp`a@fzkRN5jZDySZC3L$830S5f389-LX|YM<}gX(UBJEBydyG-k2D_Wi4lA4s|Ma z>cgJrCec9A=`AZ(pV}XZZoH`Nu%oBujf+tQ|1exMe2gg=Ca=XTq1koeTM6zwY{ON? z_2)H$LZN|Hk^`PkrZ2Fc;|IdI^}GKd#}<2@*DilgoXSB5Wl$qBeE{7#c>@5BX{6RC zVUK{KMN#wfSu>@lkb%XQNRTu7Cuj$}pB%Lt!ecnA@_?wdA3WY4&`J<~FI`gDZ2rCU z&8jODZqqmdz+!is6e~6qka%Iet~T0#+dIhZuT?!OCy~mzRyd?@FEh>VNv49xkRt60 z%0dV~=Mr)SBeXE+Dnq2wqrK(@j>bfHsvFk^`+=@kuB9rJ$P6yj=rON*(0!+hplC!q z&0^<6VylG=*!}G6Q+O^i7{fcuWJcBnj%dqVFY#9SoT{>2P(El|v%>t5TEno)Rdb!k zjL;&@`!s8JW#ttA28S-opIWzo5;s=x(r&hAwu3Ppy}t@eY-edJ!F*dveD&`-@onnb zl;-*yFDx%Oi_zFH(XF5 z=jBIX=A2FFXZ=!p3~ip&rRf%=FnnebTXO(Szi9_gq3Hd7ox&=}!+HY$%QPW04d^Rk zF2rkrtgO(8y|J2INp5u>^Z*g3_%zTEDRii&MhXC2i)fgj=+zY6+R|kJ->wg{xajQD zR6uaqRuW_)D}p>=;+(D4cO4$yz&7RxxAxOQ%4*pk`77?@pHe=n7Q;JV)w-(+Z9mr) z2m5^ZX)``P({@_UyZX(f#SyKhd=y8;b$({;@$+BGIJU%{ z3ZzckFj0fsAt6Vt%{ysc-5;GM7ODgmLUanwL@OY8G*C)fWFfV5IageV0(1qm-tQRy zMc<6%g8enHyn2qC`wq1K07;?qS+Rt1woZ@?x%Qt{A@1deHM&D~3<%_I{`*V1j$=3OB!H8F`_MTX2A0+9NE;Ku%dAs-PZ{(fp4dL3^5Ql852x zJPEgBFRr8O zED@GDd!SSgK2zW>Zed$%eJCGcw`}fN!At%Dg|-1@c>^)|IjHp#&h(dly94~ZfK3DY zXylo_hDVkMk`_i@V?F{-YF9}4KKuu+EnT4)XH__0d{bQ14NIQM*~?D05`q*FHIYuea0Zu61q}HGF&6}^U;|e{zngpheC?SyCWhHAR`2@wm^w*u ziRmju9?m0OytR{s5UX_5bz)PEiDfvGm@&7!Mr1P z3i+5;OmMpWAn@Sp(FK-!VjBqx0MniKBgfYb22M^RpnnVM3&;htoD1rK|Oz z;ZfZR%qhrE*An9{=dCvb8U-yKT~514PS9?jG)wdN2s8>V59rR?uAc&<|CQNW^%%g7 z->AND{;J>i?O9s07^`8t$MhrnentbakIYl0?D2;8lOj$ceWKgsOT90_kWwt-x3J^v zQI*=?-CqQ+d?#7?L}{EZ6Z?3-tSlOTrr5^E>FWYxYf`^wt?{jW+$A_Jul_GsFYP6q zL9vhft%diec20phM-f)yIk4GezlL#rVJDsneIzvLFMW&If%YG26LY+y5>D-Pgp3|( zoJ||G0E3?vY?n=&G?pG);3VsNtqQ-xG<5);N6jln30oKoB!IDoARf1$ti7dY{K(J) zSzxzol6M$QY=qJ*lph6xd*H>{+*d5;hd$vyWrr=T+hE9}x?OhIv7zeNDjlcCN47=z znD1)YkvkVN{G6K(f`YL7&bnNv$b@nt%Ub9K&mubQ4TV>Kj5M+tw38;er?qmd3sHO< z&AKby8?-yvp2mK?T|wzt_{SlgYOa^2ZPa#&Zl^zoG>5e=Qr;C;ccg-vXGT7E>b=>5 zSGbh{Xn*zR-PVofXq;IH1W(eSO!anUrkyj8Dwsj?H-=r|qV<;z zo@esHSl-$uGe2)Fhcrdt?vm|t>8SF3zO?boI^p=ctpw(aBqzZ8oWR z+Y_KD8o~jT1zfpDs0kKS+Tq&I%Uu6lew$J_neA_bf% zPnQaqju3w^m&5MCD9@5+aI&!!FbT8B!a|qD_tyv3^}tW;)u$dg0%5d2lh;vF4by>rWBU-M9P7B|vdQY|-Qx*#GPyUDV z|E(Et1(Tp?VJMkpKU|iGdtOLKlM|+M=hQ(=PBvoVvC($njzUP+q^SUozM3BIH1)uD zLNR_QSO|09-hcZ;k%DsOvX`RX{;)WS#H=^*Q}ilLyP`%kLC9Z|g5qAz90p_}P?q{$ z&K#IV&ds@Qk8lRi6j$-c)@^`dBK2t5;PU@q3qw{HY1Rx;;)CwF%*XOslK4m^|4j$2 z#Z!rX<3=D60=IqX(}q?a@wC?npr_5o>#%+(MVu9koe;F-T+=wd z!#l_l0IjrXGC1I63y_5-_+uBnI|6B~p9y&PdPfx)E!PE<#3FHgkIEXu+WUyWr2t(Z zh5q!voI@SeMo@col8!4Y(zX-?QY96n$CSDM?2k>e@&ZXR05Bl6lqskTl058>*jd^C z*AErrRMjC?-|4Opcm7k1jK5(+OO-b(lzK`A6y?c~mUN$@R^jP0h?9XIewN>}PV;Gz zu^gpv-)^+PWma9jLe+J4sQZi!XMI-luMv@EcuAG6lj?siT5d_`44n!Y(ky8%#Zcdq zMOrow$fT4-9Y=k?Dp5MU%2=${9PC=h?qy0r`I~7~G`e$iZ2?+riK^=HfqrmSoS!#S7?UvW@K)ha2+TM z0bo;8X0v<9ZW2cN7g@wIdkfuLCEYwt*g0$AX7!7%at~*-o3^Vq&jti?S}rCLn9=w0 zKh?4D#iH61}A*n{0@8C(8n>zze^6p*~q zlptxWbpj|{g{$NsXU{zMJD-1qdnyb0QcqcR_Rkj`AzgNrog!T0P5DRQPoOA1*^KIv zouZZs!-OlqkgqyOPE2&yG2tWIs&gL{=Iq3B+Fu$rp-(`3A}k>Eu#{W;ygT-pC!z|`c6xUitAX|lEV zj=e)>f&AR`1W{J>!Yk}McgJJ|R190uzK?rBxbDB6jITVPSZCxqNl|~_kzt1h6to_Q z_nb8zF5`JQE$mJW0F$G+1n7l?ikRwu@H8pXs*n(Y#N6^45u*xz3e<4`R10kUr#QFR zqM>f7<^ZPKi&!#Ae{7gf6qa;j4)mkj#Mpg)M7drM^Lafg?_`Pdr-WP^LdF7d-* zmRUJem#pfcJE}+#&!P=wM{)*3FE$-ScfMcYy|M-2XuILOBUAob4($6k70$x$Z>>)c zKkS=kf;t*x1ZYkA!Vx5BhmX;P46 zLpo=%?IYEwphjW@$cBZ;m`$v-a%Ht;Z4hxQ!T zZ3?bB^>Zi>Q>2qXH@a}O^rHQjEZI(bspVfSCsO(2t7@>V0Zw7H4;He7SqEV^kx|R& z&;NNhqb$I<)s3G!q~q$!2VqHOG;7XO8)D_jGk|vc%&fGHjy5`9U-EdW?i9SyM3=BrXrPkt>evh6q~pv*IAEZ%vRhbnTKMi%z>BKHT4FxU3KGR#!f^To~^6Q z&m6*?uA~i(!4oXnCrj5zZaPMjh#s#Q0 zU$44btQQrpT{X0guxk!Cu~74q`xJ4p3F#vW4dx=~V^9jw{Wh{W%0)i>K!GaBwgsK! zt?tDng0#uqCJKBd(JtFGhibX%F}cClNXW;5pm9Z)@?nMhD)J2z%*0mIEp9C+^KyfW z<#mGT2m?Y)I7I$`2}?`>yuZHU=>fV0#7F%v6h?_5V%)UgJlzkV2B?ogllNp=Z~c2 zTGuucTahejCiOBoVjE9M^}FBJ96yw?R^vmJoYbatzuR}RC4WwPihsn4x58lyJfnTV zIryO&rzfE0#7QYDI8SLXyHFnTP1@(6c)h(5aEPZ(itFk$FM24Jey) zZFyF5m)4lHB?t*uIX)qc!u4~;-X3S4ID01n1;`8b5TPSeh@Tp1!9NP}&bnmB(zHy? z=L5~EQ=hB;X4Jwu^04q{ShZi$H@K6!Pj=|Zmo5*-vlGxbbINq7QMeVhdsixm{@JDP za&H?&nMTf*i>ceXO?nR0qwy@)b<_hWfQgDr4w2fdJ3NZte(8K9AxV6sYmuGQKhS$S z=H=&Nx{DtN2$QQvH4RWIzVYqa000P&8u$E0YG(pj=G^uU$0AlylDBuj+Y5-;lg|#b zfSXsG9?Cowj$XjH-o>&^+*cYe1$m?ipc8rZ1ixPXk$04PZ`K4?J8-;=A0h4z-qCTO zNYR?R!5oA7lzV3sDEtWqIfd`L1+B%%H7n9V@xR#uiBlNt-#Tw}KZkN%=Lbesd+fW%& z;g1w2Xpk-Kr18QL+6F+qt*iU-BZ+lb!H#5q##`zwtt#?llr828CiQZc%0Vwt6hqAm zn34M<=750=+7DnblH1sBwv(*(7+ibmEYkL4AMJ&eM%$II$X~LGz2eOjO{2?9oG07z za{o(Y60x^V5k0a_r-h52fISFY6kSU_g&W{Qy(gn`F)l+ImtVY;wXYa|IK=Sjz*lg? z443+t&+29o8Cg8iMWuBTQmOD!Xnqwce&f^nOf@e0SFEM#cfHmvDHRP7Tc|)$v{`_{ zUD0YdipP&nD3>yq`>+thVx=lZ)-2?e?wogT@zZh!8C7?Wdh)&HQ6`ffO0=i~9n$T! zfEL9o3Wb=pRk7S81JqbbyuWdWxrFSp!LC^wsYArP4cV3aa~6YfY+(PDsRKXXb9iH8AV!8<@H7 zhZWhK2$i}eHf4xU&^&J|36#~4XK?Zu{Uw!cHY010?W#_=$wRTTpMUMO^)gyL@Y`@A zwKizXxmnAw1xrly<(S)5L|w7L>q;K1cBaOEag3@k-OFAAEbv0Yat&bS+nKo4#G{>n z=vpK>tL-Pj&uTjo;8?D<30J8GW*etE-OI_{DIHO4m;=ylYIzaXilBT3(f zcucB8w3OJbAX?8sJ}~&yO1RnkIl-s}lw-(<3P^Ksv@~{Ot$~4v=f2UZat)u`%)YZl z(bmu22!E~(29YQvdD>FQ$Zhie*`#!m+$vz_3s?2g!wZ2ScNSZtiyksQ?6C*NwmW!v z<3e}hB`@BZzfdIwVh0WV%qU!i%k%1vJcKFS^S2#yQ{L+C;MQ~_$I^yBcU(J^=xEk$ zR{sZDd~uN-FvRWjc{UE!?e8x9Ea?mT8l<~;y0o^%d1_cb2-FW2y$YEH25v2pzt5btrWu6aN@cm-^AiVKN_Qzk0$@7KwS=1Ie?mv^*I=qlJh9B2o zC2fq^b$I)ROWKRJ`$vA_?CcG)zW>`G=dp=SS+S=qQ6>vc;ye_XNs;^^$Zh1?JB}6S zQ}=xOy=k^hV;w}!?K+PiC8Kf2w()P<$MgSRd)NBZ#1+M@RmaxxfeKWL1OXFgtTKQg zOcSE{V-C6A%;_3rvC7!} z8u7VnV9bP9fUnC=9h7}X#-J;b@OmgcO*)SMYm7*4A)fp6^2z4klIil(KePW#QOSxZ1&ZTTM z+8o!C`1e|>EKKksVVgua8JsX|zC)Q=oOIB=K;gdNxjf1x%A=KmZ2r-yy6dot8$&zG zO@lbB)HJbdT&s#Y>^t)>nq1> zjchv7H9QE#2|yU@k-a_7x7CnQZlhCUvUy=OA&kFEL@V~tO|&WF1c36JI2U%?J7D8_ z9VF@0!v6EdSlD8x^);f2*ZK zY~!9xmY3KN!?alIHc05*)Y|?JCRS8FNH3h+@H!%oeyb|`VJW|t*;u;DV@f(Zf~0xe z6Xk^RcWrJBK%5lu0w^V<#1cg5Mq>$QeZk4`)JzE|$xY|CGBRNZXj~}TX3Tj*!qpEquq@iW7yLHUDzEkvvTsIIijD}-dx3monE+a!}q zHx3C)Is~krNSA!Ll^g}@tXy6v`PHu3Re7|HDUa~#!i0T)gBxFkoSHiC+#2wHTwnVD zTZM!TwtH&*9+qND+Lr7k!fCV<>cCJ5mP-_@H15S}Fb`}yvJOl4I^#i3AoVsb4Co-H zO@HY&At9ba+5U_yt6gaP$rp`Q7L*; z8DWWlz}pPRmcOvFU0pVbfO-5o0uuoy*vHfJ42C{au>#xlpq zOA7ykbTB)keG6gMFWsZ$ZakPhcwRKsmScdmmgCb{Cr>iVZBSX95AaS*s*)#@nl)&m zpruHQ#Z`$r=uiB`f>cE3eU{?K^_HUCQUi@|(|A*SD9F?2PhXiX)nf6_hEg>&>rp%w z6zp6a2KR&v`p~xw)S^;jcMM+khV*B=$6T#rm>{UQf;ddp+yA31O)WVQml1hyc;YU} z3`S$9L=n@W0S8pt;}FCDFZAde=pnMAygOyr5XcY9)yd3oQY|^(2^rPxZ70QWYJ~w? zYLysTc(}*_!N}xn__xd6TfSoT2KUe2ZQd2(|I`T!;x)Flk$E9ytuO1FmQgEWfAl`d I=O-@z16p3W*Z=?k literal 0 HcmV?d00001 From 7d239b60bec2c2cc3c33dde0099feaae605fb5ed Mon Sep 17 00:00:00 2001 From: Matthew Bastien Date: Mon, 31 Mar 2025 12:23:34 -0500 Subject: [PATCH 69/86] dev-package: generate version number from the version in the package.json (#1474) --- package.json | 2 +- scripts/dev_package.ts | 28 ++++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 scripts/dev_package.ts diff --git a/package.json b/package.json index babcfdf35..df99e1c1a 100644 --- a/package.json +++ b/package.json @@ -1637,7 +1637,7 @@ "coverage": "npm test -- --coverage", "compile-tests": "del-cli ./assets/test/**/.build && npm run compile", "package": "vsce package", - "dev-package": "vsce package --no-update-package-json 2.1.0-dev", + "dev-package": "tsx ./scripts/dev_package.ts", "preview-package": "tsx ./scripts/preview_package.ts", "tag": "./scripts/tag_release.sh $npm_package_version", "contributors": "./scripts/generate_contributors_list.sh" diff --git a/scripts/dev_package.ts b/scripts/dev_package.ts new file mode 100644 index 000000000..9fec4c630 --- /dev/null +++ b/scripts/dev_package.ts @@ -0,0 +1,28 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the VS Code Swift open source project +// +// Copyright (c) 2025 the VS Code Swift project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of VS Code Swift project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// +/* eslint-disable no-console */ + +import { exec, getExtensionVersion, getRootDirectory, main } from "./lib/utilities"; + +main(async () => { + const rootDirectory = getRootDirectory(); + const version = await getExtensionVersion(); + // Increment the patch version from the package.json + const patch = version.patch + 1; + const devVersion = `${version.major}.${version.minor}.${patch}-dev`; + // Use VSCE to package the extension + await exec("npx", ["vsce", "package", "--no-update-package-json", devVersion], { + cwd: rootDirectory, + }); +}); From 3039bb310372f24cc34b7b297b8811a5e576d92e Mon Sep 17 00:00:00 2001 From: Paul LeMarquand Date: Mon, 31 Mar 2025 13:50:18 -0400 Subject: [PATCH 70/86] Use Swiftly's toolchain path if active toolchain is managed by Swiftly (#1470) On macOS the toolchain path is determined by `xcrun --find swift`. `xcrun` will return paths inside the active Xcode. If the active toolchain is being managed by the newly released Swiftly 1.0 on macOS then the swift path and the toolchain path will be mismatched. Check to see if the active toolchain is managed by Swiftly, and if so point the toolchain path to the Swiftly installed toolchain. The name of the `.xctoolchain` folder needs to be reverse engineered from Swiftly's in use toolchain name using the ToolchainVersion.parse method which was ported from Swiftly's codebase. A nice feature in the future would be to be able to ask Swiftly where its managed toolchains are stored on disk. --- src/toolchain/ToolchainVersion.ts | 231 ++++++++++++++++++ src/toolchain/toolchain.ts | 61 ++++- .../toolchain/ToolchainVersion.test.ts | 33 +++ 3 files changed, 318 insertions(+), 7 deletions(-) create mode 100644 src/toolchain/ToolchainVersion.ts create mode 100644 test/unit-tests/toolchain/ToolchainVersion.test.ts diff --git a/src/toolchain/ToolchainVersion.ts b/src/toolchain/ToolchainVersion.ts new file mode 100644 index 000000000..752d9199b --- /dev/null +++ b/src/toolchain/ToolchainVersion.ts @@ -0,0 +1,231 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the VS Code Swift open source project +// +// Copyright (c) 2025 the VS Code Swift project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of VS Code Swift project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +export interface SwiftlyConfig { + installedToolchains: string[]; + inUse: string; + version: string; +} + +/** + * This code is a port of the toolchain version parsing in Swiftly. + * Until Swiftly can report the location of the toolchains under its management + * use `ToolchainVersion.parse(versionString)` to reconstruct the directory name of the toolchain on disk. + * https://github.com/swiftlang/swiftly/blob/bd6884316817e400a0ec512599f046fa437e9760/Sources/SwiftlyCore/ToolchainVersion.swift# + */ +// +// Enum representing a fully resolved toolchain version (e.g. 5.6.7 or 5.7-snapshot-2022-07-05). +export class ToolchainVersion { + private type: "stable" | "snapshot"; + private value: StableRelease | Snapshot; + + constructor( + value: + | { + type: "stable"; + major: number; + minor: number; + patch: number; + } + | { + type: "snapshot"; + branch: Branch; + date: string; + } + ) { + if (value.type === "stable") { + this.type = "stable"; + this.value = new StableRelease(value.major, value.minor, value.patch); + } else { + this.type = "snapshot"; + this.value = new Snapshot(value.branch, value.date); + } + } + + private static stableRegex = /^(?:Swift )?(\d+)\.(\d+)\.(\d+)$/; + private static mainSnapshotRegex = /^main-snapshot-(\d{4}-\d{2}-\d{2})$/; + private static releaseSnapshotRegex = /^(\d+)\.(\d+)-snapshot-(\d{4}-\d{2}-\d{2})$/; + + /** + * Parse a toolchain version from the provided string + **/ + static parse(string: string): ToolchainVersion { + let match: RegExpMatchArray | null; + + // Try to match as stable release + match = string.match(this.stableRegex); + if (match) { + const major = parseInt(match[1], 10); + const minor = parseInt(match[2], 10); + const patch = parseInt(match[3], 10); + + if (isNaN(major) || isNaN(minor) || isNaN(patch)) { + throw new Error(`invalid stable version: ${string}`); + } + + return new ToolchainVersion({ + type: "stable", + major, + minor, + patch, + }); + } + + // Try to match as main snapshot + match = string.match(this.mainSnapshotRegex); + if (match) { + return new ToolchainVersion({ + type: "snapshot", + branch: Branch.main(), + date: match[1], + }); + } + + // Try to match as release snapshot + match = string.match(this.releaseSnapshotRegex); + if (match) { + const major = parseInt(match[1], 10); + const minor = parseInt(match[2], 10); + + if (isNaN(major) || isNaN(minor)) { + throw new Error(`invalid release snapshot version: ${string}`); + } + + return new ToolchainVersion({ + type: "snapshot", + branch: Branch.release(major, minor), + date: match[3], + }); + } + + throw new Error(`invalid toolchain version: "${string}"`); + } + + get name(): string { + if (this.type === "stable") { + const release = this.value as StableRelease; + return `${release.major}.${release.minor}.${release.patch}`; + } else { + const snapshot = this.value as Snapshot; + if (snapshot.branch.type === "main") { + return `main-snapshot-${snapshot.date}`; + } else { + return `${snapshot.branch.major}.${snapshot.branch.minor}-snapshot-${snapshot.date}`; + } + } + } + + get identifier(): string { + if (this.type === "stable") { + const release = this.value as StableRelease; + if (release.patch === 0) { + if (release.minor === 0) { + return `swift-${release.major}-RELEASE`; + } + return `swift-${release.major}.${release.minor}-RELEASE`; + } + return `swift-${release.major}.${release.minor}.${release.patch}-RELEASE`; + } else { + const snapshot = this.value as Snapshot; + if (snapshot.branch.type === "main") { + return `swift-DEVELOPMENT-SNAPSHOT-${snapshot.date}-a`; + } else { + return `swift-${snapshot.branch.major}.${snapshot.branch.minor}-DEVELOPMENT-SNAPSHOT-${snapshot.date}-a`; + } + } + } + + get description(): string { + return this.value.description; + } +} + +class Branch { + static main(): Branch { + return new Branch("main", null, null); + } + + static release(major: number, minor: number): Branch { + return new Branch("release", major, minor); + } + + private constructor( + public type: "main" | "release", + public _major: number | null, + public _minor: number | null + ) {} + + get description(): string { + switch (this.type) { + case "main": + return "main"; + case "release": + return `${this._major}.${this._minor} development`; + } + } + + get name(): string { + switch (this.type) { + case "main": + return "main"; + case "release": + return `${this._major}.${this._minor}`; + } + } + + get major(): number | null { + return this._major; + } + + get minor(): number | null { + return this._minor; + } +} + +// Snapshot class +class Snapshot { + // Branch enum + + branch: Branch; + date: string; + + constructor(branch: Branch, date: string) { + this.branch = branch; + this.date = date; + } + + get description(): string { + if (this.branch.type === "main") { + return `main-snapshot-${this.date}`; + } else { + return `${this.branch.major}.${this.branch.minor}-snapshot-${this.date}`; + } + } +} + +class StableRelease { + major: number; + minor: number; + patch: number; + + constructor(major: number, minor: number, patch: number) { + this.major = major; + this.minor = minor; + this.patch = patch; + } + + get description(): string { + return `Swift ${this.major}.${this.minor}.${this.patch}`; + } +} diff --git a/src/toolchain/toolchain.ts b/src/toolchain/toolchain.ts index 1e6646f1f..4976def5e 100644 --- a/src/toolchain/toolchain.ts +++ b/src/toolchain/toolchain.ts @@ -24,6 +24,7 @@ import { expandFilePathTilde, pathExists } from "../utilities/filesystem"; import { Version } from "../utilities/version"; import { BuildFlags } from "./BuildFlags"; import { Sanitizer } from "./Sanitizer"; +import { SwiftlyConfig, ToolchainVersion } from "./ToolchainVersion"; /** * Contents of **Info.plist** on Windows. @@ -229,12 +230,13 @@ export class SwiftToolchain { } /** - * Reads the swiftly configuration file to find a list of installed toolchains. + * Finds the list of toolchains managed by Swiftly. * * @returns an array of toolchain paths */ public static async getSwiftlyToolchainInstalls(): Promise { // Swiftly is only available on Linux right now + // TODO: Add support for macOS if (process.platform !== "linux") { return []; } @@ -243,12 +245,8 @@ export class SwiftToolchain { if (!swiftlyHomeDir) { return []; } - const swiftlyConfigRaw = await fs.readFile( - path.join(swiftlyHomeDir, "config.json"), - "utf-8" - ); - const swiftlyConfig: unknown = JSON.parse(swiftlyConfigRaw); - if (!(swiftlyConfig instanceof Object) || !("installedToolchains" in swiftlyConfig)) { + const swiftlyConfig = await SwiftToolchain.getSwiftlyConfig(); + if (!swiftlyConfig || !("installedToolchains" in swiftlyConfig)) { return []; } const installedToolchains = swiftlyConfig.installedToolchains; @@ -263,6 +261,23 @@ export class SwiftToolchain { } } + /** + * Reads the Swiftly configuration file, if it exists. + * + * @returns A parsed Swiftly configuration. + */ + private static async getSwiftlyConfig(): Promise { + const swiftlyHomeDir: string | undefined = process.env["SWIFTLY_HOME_DIR"]; + if (!swiftlyHomeDir) { + return; + } + const swiftlyConfigRaw = await fs.readFile( + path.join(swiftlyHomeDir, "config.json"), + "utf-8" + ); + return JSON.parse(swiftlyConfigRaw); + } + /** * Checks common directories for available swift toolchain installations. * @@ -272,6 +287,7 @@ export class SwiftToolchain { if (process.platform !== "darwin") { return []; } + // TODO: If Swiftly is managing these toolchains then omit them return Promise.all([ this.findToolchainsIn("/Library/Developer/Toolchains/"), this.findToolchainsIn(path.join(os.homedir(), "Library/Developer/Toolchains/")), @@ -602,6 +618,12 @@ export class SwiftToolchain { if (configuration.path !== "") { return path.dirname(configuration.path); } + + const swiftlyToolchainLocation = await this.swiftlyToolchainLocation(); + if (swiftlyToolchainLocation) { + return swiftlyToolchainLocation; + } + const { stdout } = await execFile("xcrun", ["--find", "swift"], { env: configuration.swiftEnvironmentVariables, }); @@ -617,6 +639,31 @@ export class SwiftToolchain { } } + /** + * Determine if Swiftly is being used to manage the active toolchain and if so, return + * the path to the active toolchain. + * @returns The location of the active toolchain if swiftly is being used to manage it. + */ + private static async swiftlyToolchainLocation(): Promise { + const swiftlyHomeDir: string | undefined = process.env["SWIFTLY_HOME_DIR"]; + if (swiftlyHomeDir) { + const { stdout: swiftLocation } = await execFile("which", ["swift"]); + if (swiftLocation.indexOf(swiftlyHomeDir) === 0) { + const swiftlyConfig = await SwiftToolchain.getSwiftlyConfig(); + if (swiftlyConfig) { + const version = ToolchainVersion.parse(swiftlyConfig.inUse); + return path.join( + os.homedir(), + "Library/Developer/Toolchains/", + `${version.identifier}.xctoolchain`, + "usr" + ); + } + } + } + return undefined; + } + /** * @param targetInfo swift target info * @returns path to Swift runtime diff --git a/test/unit-tests/toolchain/ToolchainVersion.test.ts b/test/unit-tests/toolchain/ToolchainVersion.test.ts new file mode 100644 index 000000000..288159788 --- /dev/null +++ b/test/unit-tests/toolchain/ToolchainVersion.test.ts @@ -0,0 +1,33 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the VS Code Swift open source project +// +// Copyright (c) 2025 the VS Code Swift project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of VS Code Swift project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import { expect } from "chai"; +import { ToolchainVersion } from "../../../src/toolchain/ToolchainVersion"; + +suite("ToolchainVersion Unit Test Suite", () => { + test("Parses snapshot", () => { + const version = ToolchainVersion.parse("main-snapshot-2025-03-28"); + expect(version.identifier).to.equal("swift-DEVELOPMENT-SNAPSHOT-2025-03-28-a"); + }); + + test("Parses release snapshot", () => { + const version = ToolchainVersion.parse("6.0-snapshot-2025-03-28"); + expect(version.identifier).to.equal("swift-6.0-DEVELOPMENT-SNAPSHOT-2025-03-28-a"); + }); + + test("Parses stable", () => { + const version = ToolchainVersion.parse("6.0.3"); + expect(version.identifier).to.equal("swift-6.0.3-RELEASE"); + }); +}); From 7e92f4c31061e6f1e2b6ad06fe0efc7e3f3029d7 Mon Sep 17 00:00:00 2001 From: Paul LeMarquand Date: Mon, 31 Mar 2025 14:58:36 -0400 Subject: [PATCH 71/86] Ensure only one reload extension dialog can be shown at a time (#1473) If `showReloadExtensionNotification` is in flight and being awaited then return the promise being awaited instead of showing another dialog. Also, debounce `showReloadExtensionNotification` with a 5 second window because its possible for users to close the first dialog very fast and have another one appear immediately if we update several settings at a time that require an extension restart. Issue: #1471 --- src/ui/ReloadExtension.ts | 48 +++++++++++++++++----- test/unit-tests/ui/ReloadExtension.test.ts | 25 ++++++++++- 2 files changed, 61 insertions(+), 12 deletions(-) diff --git a/src/ui/ReloadExtension.ts b/src/ui/ReloadExtension.ts index b8d201589..a23de1628 100644 --- a/src/ui/ReloadExtension.ts +++ b/src/ui/ReloadExtension.ts @@ -14,23 +14,49 @@ import * as vscode from "vscode"; import { Workbench } from "../utilities/commands"; +// eslint-disable-next-line @typescript-eslint/no-require-imports +import debounce = require("lodash.debounce"); /** * Prompts the user to reload the extension in cases where we are unable to do - * so automatically. + * so automatically. Only one of these prompts will be shown at a time. * * @param message the warning message to display to the user * @param items extra buttons to display * @returns the selected button or undefined if cancelled */ -export async function showReloadExtensionNotification( - message: string, - ...items: T[] -): Promise<"Reload Extensions" | T | undefined> { - const buttons: ("Reload Extensions" | T)[] = ["Reload Extensions", ...items]; - const selected = await vscode.window.showWarningMessage(message, ...buttons); - if (selected === "Reload Extensions") { - await vscode.commands.executeCommand(Workbench.ACTION_RELOADWINDOW); - } - return selected; +export function showReloadExtensionNotificationInstance() { + let inFlight: Promise<"Reload Extensions" | T | undefined> | null = null; + + return async function ( + message: string, + ...items: T[] + ): Promise<"Reload Extensions" | T | undefined> { + if (inFlight) { + return inFlight; + } + + const buttons: ("Reload Extensions" | T)[] = ["Reload Extensions", ...items]; + inFlight = (async () => { + try { + const selected = await vscode.window.showWarningMessage(message, ...buttons); + if (selected === "Reload Extensions") { + await vscode.commands.executeCommand(Workbench.ACTION_RELOADWINDOW); + } + return selected; + } finally { + inFlight = null; + } + })(); + + return inFlight; + }; } + +// In case the user closes the dialog immediately we want to debounce showing it again +// for 10 seconds to prevent another popup perhaps immediately appearing. +export const showReloadExtensionNotification = debounce( + showReloadExtensionNotificationInstance(), + 10_000, + { leading: true } +); diff --git a/test/unit-tests/ui/ReloadExtension.test.ts b/test/unit-tests/ui/ReloadExtension.test.ts index 422bfce23..8c539b062 100644 --- a/test/unit-tests/ui/ReloadExtension.test.ts +++ b/test/unit-tests/ui/ReloadExtension.test.ts @@ -11,15 +11,24 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// +import { beforeEach } from "mocha"; import { expect } from "chai"; import { mockGlobalObject } from "../../MockUtils"; import * as vscode from "vscode"; -import { showReloadExtensionNotification } from "../../../src/ui/ReloadExtension"; +import { showReloadExtensionNotificationInstance } from "../../../src/ui/ReloadExtension"; import { Workbench } from "../../../src/utilities/commands"; suite("showReloadExtensionNotification()", async function () { const mockedVSCodeWindow = mockGlobalObject(vscode, "window"); const mockedVSCodeCommands = mockGlobalObject(vscode, "commands"); + let showReloadExtensionNotification: ( + message: string, + ...items: string[] + ) => Promise; + + beforeEach(() => { + showReloadExtensionNotification = showReloadExtensionNotificationInstance(); + }); test("displays a warning message asking the user if they would like to reload the window", async () => { mockedVSCodeWindow.showWarningMessage.resolves(undefined); @@ -57,4 +66,18 @@ suite("showReloadExtensionNotification()", async function () { ); expect(mockedVSCodeCommands.executeCommand).to.not.have.been.called; }); + + test("only shows one dialog at a time", async () => { + mockedVSCodeWindow.showWarningMessage.resolves(undefined); + + await Promise.all([ + showReloadExtensionNotification("Want to reload?"), + showReloadExtensionNotification("Want to reload?"), + ]); + + expect(mockedVSCodeWindow.showWarningMessage).to.have.been.calledOnceWithExactly( + "Want to reload?", + "Reload Extensions" + ); + }); }); From 1a60d97cd5b243818a8b885fa50d798ff51fa775 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Apr 2025 08:31:04 -0400 Subject: [PATCH 72/86] Bump the all-dependencies group with 6 updates (#1475) Bumps the all-dependencies group with 6 updates: | Package | From | To | | --- | --- | --- | | [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `20.17.27` | `20.17.28` | | [@types/semver](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/semver) | `7.5.8` | `7.7.0` | | [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) | `8.28.0` | `8.29.0` | | [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) | `8.28.0` | `8.29.0` | | [@vscode/vsce](https://github.com/Microsoft/vsce) | `3.3.0` | `3.3.2` | | [esbuild](https://github.com/evanw/esbuild) | `0.25.1` | `0.25.2` | Updates `@types/node` from 20.17.27 to 20.17.28 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) Updates `@types/semver` from 7.5.8 to 7.7.0 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/semver) Updates `@typescript-eslint/eslint-plugin` from 8.28.0 to 8.29.0 - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.29.0/packages/eslint-plugin) Updates `@typescript-eslint/parser` from 8.28.0 to 8.29.0 - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.29.0/packages/parser) Updates `@vscode/vsce` from 3.3.0 to 3.3.2 - [Release notes](https://github.com/Microsoft/vsce/releases) - [Commits](https://github.com/Microsoft/vsce/compare/v3.3.0...v3.3.2) Updates `esbuild` from 0.25.1 to 0.25.2 - [Release notes](https://github.com/evanw/esbuild/releases) - [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG.md) - [Commits](https://github.com/evanw/esbuild/compare/v0.25.1...v0.25.2) --- updated-dependencies: - dependency-name: "@types/node" dependency-type: direct:development update-type: version-update:semver-patch dependency-group: all-dependencies - dependency-name: "@types/semver" dependency-type: direct:development update-type: version-update:semver-minor dependency-group: all-dependencies - dependency-name: "@typescript-eslint/eslint-plugin" dependency-type: direct:development update-type: version-update:semver-minor dependency-group: all-dependencies - dependency-name: "@typescript-eslint/parser" dependency-type: direct:development update-type: version-update:semver-minor dependency-group: all-dependencies - dependency-name: "@vscode/vsce" dependency-type: direct:development update-type: version-update:semver-patch dependency-group: all-dependencies - dependency-name: esbuild dependency-type: direct:development update-type: version-update:semver-patch dependency-group: all-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 635 +++++++++++++++++++++++----------------------- package.json | 12 +- 2 files changed, 324 insertions(+), 323 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8495d4aab..a3d8eab00 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,24 +25,24 @@ "@types/lodash.throttle": "^4.1.9", "@types/mocha": "^10.0.10", "@types/mock-fs": "^4.13.4", - "@types/node": "^20.17.27", + "@types/node": "^20.17.28", "@types/plist": "^3.0.5", - "@types/semver": "^7.5.8", + "@types/semver": "^7.7.0", "@types/sinon": "^17.0.4", "@types/sinon-chai": "^3.2.12", "@types/vscode": "^1.88.0", "@types/xml2js": "^0.4.14", - "@typescript-eslint/eslint-plugin": "^8.28.0", - "@typescript-eslint/parser": "^8.28.0", + "@typescript-eslint/eslint-plugin": "^8.29.0", + "@typescript-eslint/parser": "^8.29.0", "@vscode/debugprotocol": "^1.68.0", "@vscode/test-cli": "^0.0.10", "@vscode/test-electron": "^2.4.1", - "@vscode/vsce": "^3.3.0", + "@vscode/vsce": "^3.3.2", "chai": "^4.5.0", "chai-as-promised": "^7.1.2", "chai-subset": "^1.6.0", "del-cli": "^6.0.0", - "esbuild": "^0.25.1", + "esbuild": "^0.25.2", "eslint": "^8.57.0", "eslint-config-prettier": "^10.1.1", "lodash.debounce": "^4.0.8", @@ -297,9 +297,9 @@ "dev": true }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.1.tgz", - "integrity": "sha512-kfYGy8IdzTGy+z0vFGvExZtxkFlA4zAxgKEahG9KE1ScBjpQnFsNOX8KTU5ojNru5ed5CVoJYXFtoxaq5nFbjQ==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.2.tgz", + "integrity": "sha512-wCIboOL2yXZym2cgm6mlA742s9QeJ8DjGVaL39dLN4rRwrOgOyYSnOaFPhKZGLb2ngj4EyfAFjsNJwPXZvseag==", "cpu": [ "ppc64" ], @@ -314,9 +314,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.1.tgz", - "integrity": "sha512-dp+MshLYux6j/JjdqVLnMglQlFu+MuVeNrmT5nk6q07wNhCdSnB7QZj+7G8VMUGh1q+vj2Bq8kRsuyA00I/k+Q==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.2.tgz", + "integrity": "sha512-NQhH7jFstVY5x8CKbcfa166GoV0EFkaPkCKBQkdPJFvo5u+nGXLEH/ooniLb3QI8Fk58YAx7nsPLozUWfCBOJA==", "cpu": [ "arm" ], @@ -331,9 +331,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.1.tgz", - "integrity": "sha512-50tM0zCJW5kGqgG7fQ7IHvQOcAn9TKiVRuQ/lN0xR+T2lzEFvAi1ZcS8DiksFcEpf1t/GYOeOfCAgDHFpkiSmA==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.2.tgz", + "integrity": "sha512-5ZAX5xOmTligeBaeNEPnPaeEuah53Id2tX4c2CVP3JaROTH+j4fnfHCkr1PjXMd78hMst+TlkfKcW/DlTq0i4w==", "cpu": [ "arm64" ], @@ -348,9 +348,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.1.tgz", - "integrity": "sha512-GCj6WfUtNldqUzYkN/ITtlhwQqGWu9S45vUXs7EIYf+7rCiiqH9bCloatO9VhxsL0Pji+PF4Lz2XXCES+Q8hDw==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.2.tgz", + "integrity": "sha512-Ffcx+nnma8Sge4jzddPHCZVRvIfQ0kMsUsCMcJRHkGJ1cDmhe4SsrYIjLUKn1xpHZybmOqCWwB0zQvsjdEHtkg==", "cpu": [ "x64" ], @@ -365,9 +365,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.1.tgz", - "integrity": "sha512-5hEZKPf+nQjYoSr/elb62U19/l1mZDdqidGfmFutVUjjUZrOazAtwK+Kr+3y0C/oeJfLlxo9fXb1w7L+P7E4FQ==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.2.tgz", + "integrity": "sha512-MpM6LUVTXAzOvN4KbjzU/q5smzryuoNjlriAIx+06RpecwCkL9JpenNzpKd2YMzLJFOdPqBpuub6eVRP5IgiSA==", "cpu": [ "arm64" ], @@ -382,9 +382,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.1.tgz", - "integrity": "sha512-hxVnwL2Dqs3fM1IWq8Iezh0cX7ZGdVhbTfnOy5uURtao5OIVCEyj9xIzemDi7sRvKsuSdtCAhMKarxqtlyVyfA==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.2.tgz", + "integrity": "sha512-5eRPrTX7wFyuWe8FqEFPG2cU0+butQQVNcT4sVipqjLYQjjh8a8+vUTfgBKM88ObB85ahsnTwF7PSIt6PG+QkA==", "cpu": [ "x64" ], @@ -399,9 +399,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.1.tgz", - "integrity": "sha512-1MrCZs0fZa2g8E+FUo2ipw6jw5qqQiH+tERoS5fAfKnRx6NXH31tXBKI3VpmLijLH6yriMZsxJtaXUyFt/8Y4A==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.2.tgz", + "integrity": "sha512-mLwm4vXKiQ2UTSX4+ImyiPdiHjiZhIaE9QvC7sw0tZ6HoNMjYAqQpGyui5VRIi5sGd+uWq940gdCbY3VLvsO1w==", "cpu": [ "arm64" ], @@ -416,9 +416,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.1.tgz", - "integrity": "sha512-0IZWLiTyz7nm0xuIs0q1Y3QWJC52R8aSXxe40VUxm6BB1RNmkODtW6LHvWRrGiICulcX7ZvyH6h5fqdLu4gkww==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.2.tgz", + "integrity": "sha512-6qyyn6TjayJSwGpm8J9QYYGQcRgc90nmfdUb0O7pp1s4lTY+9D0H9O02v5JqGApUyiHOtkz6+1hZNvNtEhbwRQ==", "cpu": [ "x64" ], @@ -433,9 +433,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.1.tgz", - "integrity": "sha512-NdKOhS4u7JhDKw9G3cY6sWqFcnLITn6SqivVArbzIaf3cemShqfLGHYMx8Xlm/lBit3/5d7kXvriTUGa5YViuQ==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.2.tgz", + "integrity": "sha512-UHBRgJcmjJv5oeQF8EpTRZs/1knq6loLxTsjc3nxO9eXAPDLcWW55flrMVc97qFPbmZP31ta1AZVUKQzKTzb0g==", "cpu": [ "arm" ], @@ -450,9 +450,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.1.tgz", - "integrity": "sha512-jaN3dHi0/DDPelk0nLcXRm1q7DNJpjXy7yWaWvbfkPvI+7XNSc/lDOnCLN7gzsyzgu6qSAmgSvP9oXAhP973uQ==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.2.tgz", + "integrity": "sha512-gq/sjLsOyMT19I8obBISvhoYiZIAaGF8JpeXu1u8yPv8BE5HlWYobmlsfijFIZ9hIVGYkbdFhEqC0NvM4kNO0g==", "cpu": [ "arm64" ], @@ -467,9 +467,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.1.tgz", - "integrity": "sha512-OJykPaF4v8JidKNGz8c/q1lBO44sQNUQtq1KktJXdBLn1hPod5rE/Hko5ugKKZd+D2+o1a9MFGUEIUwO2YfgkQ==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.2.tgz", + "integrity": "sha512-bBYCv9obgW2cBP+2ZWfjYTU+f5cxRoGGQ5SeDbYdFCAZpYWrfjjfYwvUpP8MlKbP0nwZ5gyOU/0aUzZ5HWPuvQ==", "cpu": [ "ia32" ], @@ -484,9 +484,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.1.tgz", - "integrity": "sha512-nGfornQj4dzcq5Vp835oM/o21UMlXzn79KobKlcs3Wz9smwiifknLy4xDCLUU0BWp7b/houtdrgUz7nOGnfIYg==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.2.tgz", + "integrity": "sha512-SHNGiKtvnU2dBlM5D8CXRFdd+6etgZ9dXfaPCeJtz+37PIUlixvlIhI23L5khKXs3DIzAn9V8v+qb1TRKrgT5w==", "cpu": [ "loong64" ], @@ -501,9 +501,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.1.tgz", - "integrity": "sha512-1osBbPEFYwIE5IVB/0g2X6i1qInZa1aIoj1TdL4AaAb55xIIgbg8Doq6a5BzYWgr+tEcDzYH67XVnTmUzL+nXg==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.2.tgz", + "integrity": "sha512-hDDRlzE6rPeoj+5fsADqdUZl1OzqDYow4TB4Y/3PlKBD0ph1e6uPHzIQcv2Z65u2K0kpeByIyAjCmjn1hJgG0Q==", "cpu": [ "mips64el" ], @@ -518,9 +518,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.1.tgz", - "integrity": "sha512-/6VBJOwUf3TdTvJZ82qF3tbLuWsscd7/1w+D9LH0W/SqUgM5/JJD0lrJ1fVIfZsqB6RFmLCe0Xz3fmZc3WtyVg==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.2.tgz", + "integrity": "sha512-tsHu2RRSWzipmUi9UBDEzc0nLc4HtpZEI5Ba+Omms5456x5WaNuiG3u7xh5AO6sipnJ9r4cRWQB2tUjPyIkc6g==", "cpu": [ "ppc64" ], @@ -535,9 +535,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.1.tgz", - "integrity": "sha512-nSut/Mx5gnilhcq2yIMLMe3Wl4FK5wx/o0QuuCLMtmJn+WeWYoEGDN1ipcN72g1WHsnIbxGXd4i/MF0gTcuAjQ==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.2.tgz", + "integrity": "sha512-k4LtpgV7NJQOml/10uPU0s4SAXGnowi5qBSjaLWMojNCUICNu7TshqHLAEbkBdAszL5TabfvQ48kK84hyFzjnw==", "cpu": [ "riscv64" ], @@ -552,9 +552,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.1.tgz", - "integrity": "sha512-cEECeLlJNfT8kZHqLarDBQso9a27o2Zd2AQ8USAEoGtejOrCYHNtKP8XQhMDJMtthdF4GBmjR2au3x1udADQQQ==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.2.tgz", + "integrity": "sha512-GRa4IshOdvKY7M/rDpRR3gkiTNp34M0eLTaC1a08gNrh4u488aPhuZOCpkF6+2wl3zAN7L7XIpOFBhnaE3/Q8Q==", "cpu": [ "s390x" ], @@ -569,9 +569,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.1.tgz", - "integrity": "sha512-xbfUhu/gnvSEg+EGovRc+kjBAkrvtk38RlerAzQxvMzlB4fXpCFCeUAYzJvrnhFtdeyVCDANSjJvOvGYoeKzFA==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.2.tgz", + "integrity": "sha512-QInHERlqpTTZ4FRB0fROQWXcYRD64lAoiegezDunLpalZMjcUcld3YzZmVJ2H/Cp0wJRZ8Xtjtj0cEHhYc/uUg==", "cpu": [ "x64" ], @@ -586,9 +586,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.1.tgz", - "integrity": "sha512-O96poM2XGhLtpTh+s4+nP7YCCAfb4tJNRVZHfIE7dgmax+yMP2WgMd2OecBuaATHKTHsLWHQeuaxMRnCsH8+5g==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.2.tgz", + "integrity": "sha512-talAIBoY5M8vHc6EeI2WW9d/CkiO9MQJ0IOWX8hrLhxGbro/vBXJvaQXefW2cP0z0nQVTdQ/eNyGFV1GSKrxfw==", "cpu": [ "arm64" ], @@ -603,9 +603,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.1.tgz", - "integrity": "sha512-X53z6uXip6KFXBQ+Krbx25XHV/NCbzryM6ehOAeAil7X7oa4XIq+394PWGnwaSQ2WRA0KI6PUO6hTO5zeF5ijA==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.2.tgz", + "integrity": "sha512-voZT9Z+tpOxrvfKFyfDYPc4DO4rk06qamv1a/fkuzHpiVBMOhpjK+vBmWM8J1eiB3OLSMFYNaOaBNLXGChf5tg==", "cpu": [ "x64" ], @@ -620,9 +620,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.1.tgz", - "integrity": "sha512-Na9T3szbXezdzM/Kfs3GcRQNjHzM6GzFBeU1/6IV/npKP5ORtp9zbQjvkDJ47s6BCgaAZnnnu/cY1x342+MvZg==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.2.tgz", + "integrity": "sha512-dcXYOC6NXOqcykeDlwId9kB6OkPUxOEqU+rkrYVqJbK2hagWOMrsTGsMr8+rW02M+d5Op5NNlgMmjzecaRf7Tg==", "cpu": [ "arm64" ], @@ -637,9 +637,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.1.tgz", - "integrity": "sha512-T3H78X2h1tszfRSf+txbt5aOp/e7TAz3ptVKu9Oyir3IAOFPGV6O9c2naym5TOriy1l0nNf6a4X5UXRZSGX/dw==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.2.tgz", + "integrity": "sha512-t/TkWwahkH0Tsgoq1Ju7QfgGhArkGLkF1uYz8nQS/PPFlXbP5YgRpqQR3ARRiC2iXoLTWFxc6DJMSK10dVXluw==", "cpu": [ "x64" ], @@ -654,9 +654,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.1.tgz", - "integrity": "sha512-2H3RUvcmULO7dIE5EWJH8eubZAI4xw54H1ilJnRNZdeo8dTADEZ21w6J22XBkXqGJbe0+wnNJtw3UXRoLJnFEg==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.2.tgz", + "integrity": "sha512-cfZH1co2+imVdWCjd+D1gf9NjkchVhhdpgb1q5y6Hcv9TP6Zi9ZG/beI3ig8TvwT9lH9dlxLq5MQBBgwuj4xvA==", "cpu": [ "x64" ], @@ -671,9 +671,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.1.tgz", - "integrity": "sha512-GE7XvrdOzrb+yVKB9KsRMq+7a2U/K5Cf/8grVFRAGJmfADr/e/ODQ134RK2/eeHqYV5eQRFxb1hY7Nr15fv1NQ==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.2.tgz", + "integrity": "sha512-7Loyjh+D/Nx/sOTzV8vfbB3GJuHdOQyrOryFdZvPHLf42Tk9ivBU5Aedi7iyX+x6rbn2Mh68T4qq1SDqJBQO5Q==", "cpu": [ "arm64" ], @@ -688,9 +688,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.1.tgz", - "integrity": "sha512-uOxSJCIcavSiT6UnBhBzE8wy3n0hOkJsBOzy7HDAuTDE++1DJMRRVCPGisULScHL+a/ZwdXPpXD3IyFKjA7K8A==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.2.tgz", + "integrity": "sha512-WRJgsz9un0nqZJ4MfhabxaD9Ft8KioqU3JMinOTvobbX6MOSUigSBlogP8QB3uxpJDsFS6yN+3FDBdqE5lg9kg==", "cpu": [ "ia32" ], @@ -705,9 +705,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.1.tgz", - "integrity": "sha512-Y1EQdcfwMSeQN/ujR5VayLOJ1BHaK+ssyk0AEzPjC+t1lITgsnccPqFjb6V+LsTp/9Iov4ysfjxLaGJ9RPtkVg==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.2.tgz", + "integrity": "sha512-kM3HKb16VIXZyIeVrM1ygYmZBKybX8N4p754bw390wGO3Tf2j4L2/WYL+4suWujpgf6GBYs3jv7TyUivdd05JA==", "cpu": [ "x64" ], @@ -1152,9 +1152,9 @@ } }, "node_modules/@types/node": { - "version": "20.17.27", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.27.tgz", - "integrity": "sha512-U58sbKhDrthHlxHRJw7ZLiLDZGmAUOZUbpw0S6nL27sYUdhvgBLCRu/keSd6qcTsfArd1sRFCCBxzWATGr/0UA==", + "version": "20.17.28", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.28.tgz", + "integrity": "sha512-DHlH/fNL6Mho38jTy7/JT7sn2wnXI+wULR6PV4gy4VHLVvnrV/d3pHAMQHhc4gjdLmK2ZiPoMxzp6B3yRajLSQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1172,10 +1172,11 @@ } }, "node_modules/@types/semver": { - "version": "7.5.8", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", - "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", - "dev": true + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.0.tgz", + "integrity": "sha512-k107IF4+Xr7UHjwDc7Cfd6PRQfbdkiRabXGRjo07b4WyPahFBZCZ1sE+BNxYIJPPg73UkfOsVOLwqVc/6ETrIA==", + "dev": true, + "license": "MIT" }, "node_modules/@types/sinon": { "version": "17.0.4", @@ -1221,17 +1222,17 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.28.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.28.0.tgz", - "integrity": "sha512-lvFK3TCGAHsItNdWZ/1FkvpzCxTHUVuFrdnOGLMa0GGCFIbCgQWVk3CzCGdA7kM3qGVc+dfW9tr0Z/sHnGDFyg==", + "version": "8.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.29.0.tgz", + "integrity": "sha512-PAIpk/U7NIS6H7TEtN45SPGLQaHNgB7wSjsQV/8+KYokAb2T/gloOA/Bee2yd4/yKVhPKe5LlaUGhAZk5zmSaQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.28.0", - "@typescript-eslint/type-utils": "8.28.0", - "@typescript-eslint/utils": "8.28.0", - "@typescript-eslint/visitor-keys": "8.28.0", + "@typescript-eslint/scope-manager": "8.29.0", + "@typescript-eslint/type-utils": "8.29.0", + "@typescript-eslint/utils": "8.29.0", + "@typescript-eslint/visitor-keys": "8.29.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -1251,16 +1252,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.28.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.28.0.tgz", - "integrity": "sha512-LPcw1yHD3ToaDEoljFEfQ9j2xShY367h7FZ1sq5NJT9I3yj4LHer1Xd1yRSOdYy9BpsrxU7R+eoDokChYM53lQ==", + "version": "8.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.29.0.tgz", + "integrity": "sha512-8C0+jlNJOwQso2GapCVWWfW/rzaq7Lbme+vGUFKE31djwNncIpgXD7Cd4weEsDdkoZDjH0lwwr3QDQFuyrMg9g==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.28.0", - "@typescript-eslint/types": "8.28.0", - "@typescript-eslint/typescript-estree": "8.28.0", - "@typescript-eslint/visitor-keys": "8.28.0", + "@typescript-eslint/scope-manager": "8.29.0", + "@typescript-eslint/types": "8.29.0", + "@typescript-eslint/typescript-estree": "8.29.0", + "@typescript-eslint/visitor-keys": "8.29.0", "debug": "^4.3.4" }, "engines": { @@ -1276,14 +1277,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.28.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.28.0.tgz", - "integrity": "sha512-u2oITX3BJwzWCapoZ/pXw6BCOl8rJP4Ij/3wPoGvY8XwvXflOzd1kLrDUUUAIEdJSFh+ASwdTHqtan9xSg8buw==", + "version": "8.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.29.0.tgz", + "integrity": "sha512-aO1PVsq7Gm+tcghabUpzEnVSFMCU4/nYIgC2GOatJcllvWfnhrgW0ZEbnTxm36QsikmCN1K/6ZgM7fok2I7xNw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.28.0", - "@typescript-eslint/visitor-keys": "8.28.0" + "@typescript-eslint/types": "8.29.0", + "@typescript-eslint/visitor-keys": "8.29.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1294,14 +1295,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.28.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.28.0.tgz", - "integrity": "sha512-oRoXu2v0Rsy/VoOGhtWrOKDiIehvI+YNrDk5Oqj40Mwm0Yt01FC/Q7nFqg088d3yAsR1ZcZFVfPCTTFCe/KPwg==", + "version": "8.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.29.0.tgz", + "integrity": "sha512-ahaWQ42JAOx+NKEf5++WC/ua17q5l+j1GFrbbpVKzFL/tKVc0aYY8rVSYUpUvt2hUP1YBr7mwXzx+E/DfUWI9Q==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.28.0", - "@typescript-eslint/utils": "8.28.0", + "@typescript-eslint/typescript-estree": "8.29.0", + "@typescript-eslint/utils": "8.29.0", "debug": "^4.3.4", "ts-api-utils": "^2.0.1" }, @@ -1318,9 +1319,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.28.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.28.0.tgz", - "integrity": "sha512-bn4WS1bkKEjx7HqiwG2JNB3YJdC1q6Ue7GyGlwPHyt0TnVq6TtD/hiOdTZt71sq0s7UzqBFXD8t8o2e63tXgwA==", + "version": "8.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.29.0.tgz", + "integrity": "sha512-wcJL/+cOXV+RE3gjCyl/V2G877+2faqvlgtso/ZRbTCnZazh0gXhe+7gbAnfubzN2bNsBtZjDvlh7ero8uIbzg==", "dev": true, "license": "MIT", "engines": { @@ -1332,14 +1333,14 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.28.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.28.0.tgz", - "integrity": "sha512-H74nHEeBGeklctAVUvmDkxB1mk+PAZ9FiOMPFncdqeRBXxk1lWSYraHw8V12b7aa6Sg9HOBNbGdSHobBPuQSuA==", + "version": "8.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.29.0.tgz", + "integrity": "sha512-yOfen3jE9ISZR/hHpU/bmNvTtBW1NjRbkSFdZOksL1N+ybPEE7UVGMwqvS6CP022Rp00Sb0tdiIkhSCe6NI8ow==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.28.0", - "@typescript-eslint/visitor-keys": "8.28.0", + "@typescript-eslint/types": "8.29.0", + "@typescript-eslint/visitor-keys": "8.29.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -1385,16 +1386,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.28.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.28.0.tgz", - "integrity": "sha512-OELa9hbTYciYITqgurT1u/SzpQVtDLmQMFzy/N8pQE+tefOyCWT79jHsav294aTqV1q1u+VzqDGbuujvRYaeSQ==", + "version": "8.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.29.0.tgz", + "integrity": "sha512-gX/A0Mz9Bskm8avSWFcK0gP7cZpbY4AIo6B0hWYFCaIsz750oaiWR4Jr2CI+PQhfW1CpcQr9OlfPS+kMFegjXA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.28.0", - "@typescript-eslint/types": "8.28.0", - "@typescript-eslint/typescript-estree": "8.28.0" + "@typescript-eslint/scope-manager": "8.29.0", + "@typescript-eslint/types": "8.29.0", + "@typescript-eslint/typescript-estree": "8.29.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1409,13 +1410,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.28.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.28.0.tgz", - "integrity": "sha512-hbn8SZ8w4u2pRwgQ1GlUrPKE+t2XvcCW5tTRF7j6SMYIuYG37XuzIW44JCZPa36evi0Oy2SnM664BlIaAuQcvg==", + "version": "8.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.29.0.tgz", + "integrity": "sha512-Sne/pVz8ryR03NFK21VpN88dZ2FdQXOlq3VIklbrTYEt8yXtRFr9tvUhqvCeKjqYk5FSim37sHbooT6vzBTZcg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.28.0", + "@typescript-eslint/types": "8.29.0", "eslint-visitor-keys": "^4.2.0" }, "engines": { @@ -1679,9 +1680,9 @@ } }, "node_modules/@vscode/vsce": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/@vscode/vsce/-/vsce-3.3.0.tgz", - "integrity": "sha512-HA/pUyvh/TQWkc4wG7AudPIWUvsR8i4jiWZZgM/a69ncPi9Nm5FDogf/wVEk4EWJs4/UdxU7J6X18dfAwfPbxA==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/@vscode/vsce/-/vsce-3.3.2.tgz", + "integrity": "sha512-XQ4IhctYalSTMwLnMS8+nUaGbU7v99Qm2sOoGfIEf2QC7jpiLXZZMh7NwArEFsKX4gHTJLx0/GqAUlCdC3gKCw==", "dev": true, "license": "MIT", "dependencies": { @@ -2971,9 +2972,9 @@ } }, "node_modules/esbuild": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.1.tgz", - "integrity": "sha512-BGO5LtrGC7vxnqucAe/rmvKdJllfGaYWdyABvyMoXQlfYMb2bbRuReWR5tEGE//4LcNJj9XrkovTqNYRFZHAMQ==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.2.tgz", + "integrity": "sha512-16854zccKPnC+toMywC+uKNeYSv+/eXkevRAfwRD/G9Cleq66m8XFIrigkbvauLLlCfDL45Q2cWegSg53gGBnQ==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -2984,31 +2985,31 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.1", - "@esbuild/android-arm": "0.25.1", - "@esbuild/android-arm64": "0.25.1", - "@esbuild/android-x64": "0.25.1", - "@esbuild/darwin-arm64": "0.25.1", - "@esbuild/darwin-x64": "0.25.1", - "@esbuild/freebsd-arm64": "0.25.1", - "@esbuild/freebsd-x64": "0.25.1", - "@esbuild/linux-arm": "0.25.1", - "@esbuild/linux-arm64": "0.25.1", - "@esbuild/linux-ia32": "0.25.1", - "@esbuild/linux-loong64": "0.25.1", - "@esbuild/linux-mips64el": "0.25.1", - "@esbuild/linux-ppc64": "0.25.1", - "@esbuild/linux-riscv64": "0.25.1", - "@esbuild/linux-s390x": "0.25.1", - "@esbuild/linux-x64": "0.25.1", - "@esbuild/netbsd-arm64": "0.25.1", - "@esbuild/netbsd-x64": "0.25.1", - "@esbuild/openbsd-arm64": "0.25.1", - "@esbuild/openbsd-x64": "0.25.1", - "@esbuild/sunos-x64": "0.25.1", - "@esbuild/win32-arm64": "0.25.1", - "@esbuild/win32-ia32": "0.25.1", - "@esbuild/win32-x64": "0.25.1" + "@esbuild/aix-ppc64": "0.25.2", + "@esbuild/android-arm": "0.25.2", + "@esbuild/android-arm64": "0.25.2", + "@esbuild/android-x64": "0.25.2", + "@esbuild/darwin-arm64": "0.25.2", + "@esbuild/darwin-x64": "0.25.2", + "@esbuild/freebsd-arm64": "0.25.2", + "@esbuild/freebsd-x64": "0.25.2", + "@esbuild/linux-arm": "0.25.2", + "@esbuild/linux-arm64": "0.25.2", + "@esbuild/linux-ia32": "0.25.2", + "@esbuild/linux-loong64": "0.25.2", + "@esbuild/linux-mips64el": "0.25.2", + "@esbuild/linux-ppc64": "0.25.2", + "@esbuild/linux-riscv64": "0.25.2", + "@esbuild/linux-s390x": "0.25.2", + "@esbuild/linux-x64": "0.25.2", + "@esbuild/netbsd-arm64": "0.25.2", + "@esbuild/netbsd-x64": "0.25.2", + "@esbuild/openbsd-arm64": "0.25.2", + "@esbuild/openbsd-x64": "0.25.2", + "@esbuild/sunos-x64": "0.25.2", + "@esbuild/win32-arm64": "0.25.2", + "@esbuild/win32-ia32": "0.25.2", + "@esbuild/win32-x64": "0.25.2" } }, "node_modules/escalade": { @@ -6425,177 +6426,177 @@ "dev": true }, "@esbuild/aix-ppc64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.1.tgz", - "integrity": "sha512-kfYGy8IdzTGy+z0vFGvExZtxkFlA4zAxgKEahG9KE1ScBjpQnFsNOX8KTU5ojNru5ed5CVoJYXFtoxaq5nFbjQ==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.2.tgz", + "integrity": "sha512-wCIboOL2yXZym2cgm6mlA742s9QeJ8DjGVaL39dLN4rRwrOgOyYSnOaFPhKZGLb2ngj4EyfAFjsNJwPXZvseag==", "dev": true, "optional": true }, "@esbuild/android-arm": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.1.tgz", - "integrity": "sha512-dp+MshLYux6j/JjdqVLnMglQlFu+MuVeNrmT5nk6q07wNhCdSnB7QZj+7G8VMUGh1q+vj2Bq8kRsuyA00I/k+Q==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.2.tgz", + "integrity": "sha512-NQhH7jFstVY5x8CKbcfa166GoV0EFkaPkCKBQkdPJFvo5u+nGXLEH/ooniLb3QI8Fk58YAx7nsPLozUWfCBOJA==", "dev": true, "optional": true }, "@esbuild/android-arm64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.1.tgz", - "integrity": "sha512-50tM0zCJW5kGqgG7fQ7IHvQOcAn9TKiVRuQ/lN0xR+T2lzEFvAi1ZcS8DiksFcEpf1t/GYOeOfCAgDHFpkiSmA==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.2.tgz", + "integrity": "sha512-5ZAX5xOmTligeBaeNEPnPaeEuah53Id2tX4c2CVP3JaROTH+j4fnfHCkr1PjXMd78hMst+TlkfKcW/DlTq0i4w==", "dev": true, "optional": true }, "@esbuild/android-x64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.1.tgz", - "integrity": "sha512-GCj6WfUtNldqUzYkN/ITtlhwQqGWu9S45vUXs7EIYf+7rCiiqH9bCloatO9VhxsL0Pji+PF4Lz2XXCES+Q8hDw==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.2.tgz", + "integrity": "sha512-Ffcx+nnma8Sge4jzddPHCZVRvIfQ0kMsUsCMcJRHkGJ1cDmhe4SsrYIjLUKn1xpHZybmOqCWwB0zQvsjdEHtkg==", "dev": true, "optional": true }, "@esbuild/darwin-arm64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.1.tgz", - "integrity": "sha512-5hEZKPf+nQjYoSr/elb62U19/l1mZDdqidGfmFutVUjjUZrOazAtwK+Kr+3y0C/oeJfLlxo9fXb1w7L+P7E4FQ==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.2.tgz", + "integrity": "sha512-MpM6LUVTXAzOvN4KbjzU/q5smzryuoNjlriAIx+06RpecwCkL9JpenNzpKd2YMzLJFOdPqBpuub6eVRP5IgiSA==", "dev": true, "optional": true }, "@esbuild/darwin-x64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.1.tgz", - "integrity": "sha512-hxVnwL2Dqs3fM1IWq8Iezh0cX7ZGdVhbTfnOy5uURtao5OIVCEyj9xIzemDi7sRvKsuSdtCAhMKarxqtlyVyfA==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.2.tgz", + "integrity": "sha512-5eRPrTX7wFyuWe8FqEFPG2cU0+butQQVNcT4sVipqjLYQjjh8a8+vUTfgBKM88ObB85ahsnTwF7PSIt6PG+QkA==", "dev": true, "optional": true }, "@esbuild/freebsd-arm64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.1.tgz", - "integrity": "sha512-1MrCZs0fZa2g8E+FUo2ipw6jw5qqQiH+tERoS5fAfKnRx6NXH31tXBKI3VpmLijLH6yriMZsxJtaXUyFt/8Y4A==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.2.tgz", + "integrity": "sha512-mLwm4vXKiQ2UTSX4+ImyiPdiHjiZhIaE9QvC7sw0tZ6HoNMjYAqQpGyui5VRIi5sGd+uWq940gdCbY3VLvsO1w==", "dev": true, "optional": true }, "@esbuild/freebsd-x64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.1.tgz", - "integrity": "sha512-0IZWLiTyz7nm0xuIs0q1Y3QWJC52R8aSXxe40VUxm6BB1RNmkODtW6LHvWRrGiICulcX7ZvyH6h5fqdLu4gkww==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.2.tgz", + "integrity": "sha512-6qyyn6TjayJSwGpm8J9QYYGQcRgc90nmfdUb0O7pp1s4lTY+9D0H9O02v5JqGApUyiHOtkz6+1hZNvNtEhbwRQ==", "dev": true, "optional": true }, "@esbuild/linux-arm": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.1.tgz", - "integrity": "sha512-NdKOhS4u7JhDKw9G3cY6sWqFcnLITn6SqivVArbzIaf3cemShqfLGHYMx8Xlm/lBit3/5d7kXvriTUGa5YViuQ==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.2.tgz", + "integrity": "sha512-UHBRgJcmjJv5oeQF8EpTRZs/1knq6loLxTsjc3nxO9eXAPDLcWW55flrMVc97qFPbmZP31ta1AZVUKQzKTzb0g==", "dev": true, "optional": true }, "@esbuild/linux-arm64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.1.tgz", - "integrity": "sha512-jaN3dHi0/DDPelk0nLcXRm1q7DNJpjXy7yWaWvbfkPvI+7XNSc/lDOnCLN7gzsyzgu6qSAmgSvP9oXAhP973uQ==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.2.tgz", + "integrity": "sha512-gq/sjLsOyMT19I8obBISvhoYiZIAaGF8JpeXu1u8yPv8BE5HlWYobmlsfijFIZ9hIVGYkbdFhEqC0NvM4kNO0g==", "dev": true, "optional": true }, "@esbuild/linux-ia32": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.1.tgz", - "integrity": "sha512-OJykPaF4v8JidKNGz8c/q1lBO44sQNUQtq1KktJXdBLn1hPod5rE/Hko5ugKKZd+D2+o1a9MFGUEIUwO2YfgkQ==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.2.tgz", + "integrity": "sha512-bBYCv9obgW2cBP+2ZWfjYTU+f5cxRoGGQ5SeDbYdFCAZpYWrfjjfYwvUpP8MlKbP0nwZ5gyOU/0aUzZ5HWPuvQ==", "dev": true, "optional": true }, "@esbuild/linux-loong64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.1.tgz", - "integrity": "sha512-nGfornQj4dzcq5Vp835oM/o21UMlXzn79KobKlcs3Wz9smwiifknLy4xDCLUU0BWp7b/houtdrgUz7nOGnfIYg==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.2.tgz", + "integrity": "sha512-SHNGiKtvnU2dBlM5D8CXRFdd+6etgZ9dXfaPCeJtz+37PIUlixvlIhI23L5khKXs3DIzAn9V8v+qb1TRKrgT5w==", "dev": true, "optional": true }, "@esbuild/linux-mips64el": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.1.tgz", - "integrity": "sha512-1osBbPEFYwIE5IVB/0g2X6i1qInZa1aIoj1TdL4AaAb55xIIgbg8Doq6a5BzYWgr+tEcDzYH67XVnTmUzL+nXg==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.2.tgz", + "integrity": "sha512-hDDRlzE6rPeoj+5fsADqdUZl1OzqDYow4TB4Y/3PlKBD0ph1e6uPHzIQcv2Z65u2K0kpeByIyAjCmjn1hJgG0Q==", "dev": true, "optional": true }, "@esbuild/linux-ppc64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.1.tgz", - "integrity": "sha512-/6VBJOwUf3TdTvJZ82qF3tbLuWsscd7/1w+D9LH0W/SqUgM5/JJD0lrJ1fVIfZsqB6RFmLCe0Xz3fmZc3WtyVg==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.2.tgz", + "integrity": "sha512-tsHu2RRSWzipmUi9UBDEzc0nLc4HtpZEI5Ba+Omms5456x5WaNuiG3u7xh5AO6sipnJ9r4cRWQB2tUjPyIkc6g==", "dev": true, "optional": true }, "@esbuild/linux-riscv64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.1.tgz", - "integrity": "sha512-nSut/Mx5gnilhcq2yIMLMe3Wl4FK5wx/o0QuuCLMtmJn+WeWYoEGDN1ipcN72g1WHsnIbxGXd4i/MF0gTcuAjQ==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.2.tgz", + "integrity": "sha512-k4LtpgV7NJQOml/10uPU0s4SAXGnowi5qBSjaLWMojNCUICNu7TshqHLAEbkBdAszL5TabfvQ48kK84hyFzjnw==", "dev": true, "optional": true }, "@esbuild/linux-s390x": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.1.tgz", - "integrity": "sha512-cEECeLlJNfT8kZHqLarDBQso9a27o2Zd2AQ8USAEoGtejOrCYHNtKP8XQhMDJMtthdF4GBmjR2au3x1udADQQQ==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.2.tgz", + "integrity": "sha512-GRa4IshOdvKY7M/rDpRR3gkiTNp34M0eLTaC1a08gNrh4u488aPhuZOCpkF6+2wl3zAN7L7XIpOFBhnaE3/Q8Q==", "dev": true, "optional": true }, "@esbuild/linux-x64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.1.tgz", - "integrity": "sha512-xbfUhu/gnvSEg+EGovRc+kjBAkrvtk38RlerAzQxvMzlB4fXpCFCeUAYzJvrnhFtdeyVCDANSjJvOvGYoeKzFA==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.2.tgz", + "integrity": "sha512-QInHERlqpTTZ4FRB0fROQWXcYRD64lAoiegezDunLpalZMjcUcld3YzZmVJ2H/Cp0wJRZ8Xtjtj0cEHhYc/uUg==", "dev": true, "optional": true }, "@esbuild/netbsd-arm64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.1.tgz", - "integrity": "sha512-O96poM2XGhLtpTh+s4+nP7YCCAfb4tJNRVZHfIE7dgmax+yMP2WgMd2OecBuaATHKTHsLWHQeuaxMRnCsH8+5g==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.2.tgz", + "integrity": "sha512-talAIBoY5M8vHc6EeI2WW9d/CkiO9MQJ0IOWX8hrLhxGbro/vBXJvaQXefW2cP0z0nQVTdQ/eNyGFV1GSKrxfw==", "dev": true, "optional": true }, "@esbuild/netbsd-x64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.1.tgz", - "integrity": "sha512-X53z6uXip6KFXBQ+Krbx25XHV/NCbzryM6ehOAeAil7X7oa4XIq+394PWGnwaSQ2WRA0KI6PUO6hTO5zeF5ijA==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.2.tgz", + "integrity": "sha512-voZT9Z+tpOxrvfKFyfDYPc4DO4rk06qamv1a/fkuzHpiVBMOhpjK+vBmWM8J1eiB3OLSMFYNaOaBNLXGChf5tg==", "dev": true, "optional": true }, "@esbuild/openbsd-arm64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.1.tgz", - "integrity": "sha512-Na9T3szbXezdzM/Kfs3GcRQNjHzM6GzFBeU1/6IV/npKP5ORtp9zbQjvkDJ47s6BCgaAZnnnu/cY1x342+MvZg==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.2.tgz", + "integrity": "sha512-dcXYOC6NXOqcykeDlwId9kB6OkPUxOEqU+rkrYVqJbK2hagWOMrsTGsMr8+rW02M+d5Op5NNlgMmjzecaRf7Tg==", "dev": true, "optional": true }, "@esbuild/openbsd-x64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.1.tgz", - "integrity": "sha512-T3H78X2h1tszfRSf+txbt5aOp/e7TAz3ptVKu9Oyir3IAOFPGV6O9c2naym5TOriy1l0nNf6a4X5UXRZSGX/dw==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.2.tgz", + "integrity": "sha512-t/TkWwahkH0Tsgoq1Ju7QfgGhArkGLkF1uYz8nQS/PPFlXbP5YgRpqQR3ARRiC2iXoLTWFxc6DJMSK10dVXluw==", "dev": true, "optional": true }, "@esbuild/sunos-x64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.1.tgz", - "integrity": "sha512-2H3RUvcmULO7dIE5EWJH8eubZAI4xw54H1ilJnRNZdeo8dTADEZ21w6J22XBkXqGJbe0+wnNJtw3UXRoLJnFEg==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.2.tgz", + "integrity": "sha512-cfZH1co2+imVdWCjd+D1gf9NjkchVhhdpgb1q5y6Hcv9TP6Zi9ZG/beI3ig8TvwT9lH9dlxLq5MQBBgwuj4xvA==", "dev": true, "optional": true }, "@esbuild/win32-arm64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.1.tgz", - "integrity": "sha512-GE7XvrdOzrb+yVKB9KsRMq+7a2U/K5Cf/8grVFRAGJmfADr/e/ODQ134RK2/eeHqYV5eQRFxb1hY7Nr15fv1NQ==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.2.tgz", + "integrity": "sha512-7Loyjh+D/Nx/sOTzV8vfbB3GJuHdOQyrOryFdZvPHLf42Tk9ivBU5Aedi7iyX+x6rbn2Mh68T4qq1SDqJBQO5Q==", "dev": true, "optional": true }, "@esbuild/win32-ia32": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.1.tgz", - "integrity": "sha512-uOxSJCIcavSiT6UnBhBzE8wy3n0hOkJsBOzy7HDAuTDE++1DJMRRVCPGisULScHL+a/ZwdXPpXD3IyFKjA7K8A==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.2.tgz", + "integrity": "sha512-WRJgsz9un0nqZJ4MfhabxaD9Ft8KioqU3JMinOTvobbX6MOSUigSBlogP8QB3uxpJDsFS6yN+3FDBdqE5lg9kg==", "dev": true, "optional": true }, "@esbuild/win32-x64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.1.tgz", - "integrity": "sha512-Y1EQdcfwMSeQN/ujR5VayLOJ1BHaK+ssyk0AEzPjC+t1lITgsnccPqFjb6V+LsTp/9Iov4ysfjxLaGJ9RPtkVg==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.2.tgz", + "integrity": "sha512-kM3HKb16VIXZyIeVrM1ygYmZBKybX8N4p754bw390wGO3Tf2j4L2/WYL+4suWujpgf6GBYs3jv7TyUivdd05JA==", "dev": true, "optional": true }, @@ -6934,9 +6935,9 @@ } }, "@types/node": { - "version": "20.17.27", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.27.tgz", - "integrity": "sha512-U58sbKhDrthHlxHRJw7ZLiLDZGmAUOZUbpw0S6nL27sYUdhvgBLCRu/keSd6qcTsfArd1sRFCCBxzWATGr/0UA==", + "version": "20.17.28", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.28.tgz", + "integrity": "sha512-DHlH/fNL6Mho38jTy7/JT7sn2wnXI+wULR6PV4gy4VHLVvnrV/d3pHAMQHhc4gjdLmK2ZiPoMxzp6B3yRajLSQ==", "dev": true, "requires": { "undici-types": "~6.19.2" @@ -6953,9 +6954,9 @@ } }, "@types/semver": { - "version": "7.5.8", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", - "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.0.tgz", + "integrity": "sha512-k107IF4+Xr7UHjwDc7Cfd6PRQfbdkiRabXGRjo07b4WyPahFBZCZ1sE+BNxYIJPPg73UkfOsVOLwqVc/6ETrIA==", "dev": true }, "@types/sinon": { @@ -6999,16 +7000,16 @@ } }, "@typescript-eslint/eslint-plugin": { - "version": "8.28.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.28.0.tgz", - "integrity": "sha512-lvFK3TCGAHsItNdWZ/1FkvpzCxTHUVuFrdnOGLMa0GGCFIbCgQWVk3CzCGdA7kM3qGVc+dfW9tr0Z/sHnGDFyg==", + "version": "8.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.29.0.tgz", + "integrity": "sha512-PAIpk/U7NIS6H7TEtN45SPGLQaHNgB7wSjsQV/8+KYokAb2T/gloOA/Bee2yd4/yKVhPKe5LlaUGhAZk5zmSaQ==", "dev": true, "requires": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.28.0", - "@typescript-eslint/type-utils": "8.28.0", - "@typescript-eslint/utils": "8.28.0", - "@typescript-eslint/visitor-keys": "8.28.0", + "@typescript-eslint/scope-manager": "8.29.0", + "@typescript-eslint/type-utils": "8.29.0", + "@typescript-eslint/utils": "8.29.0", + "@typescript-eslint/visitor-keys": "8.29.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -7016,54 +7017,54 @@ } }, "@typescript-eslint/parser": { - "version": "8.28.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.28.0.tgz", - "integrity": "sha512-LPcw1yHD3ToaDEoljFEfQ9j2xShY367h7FZ1sq5NJT9I3yj4LHer1Xd1yRSOdYy9BpsrxU7R+eoDokChYM53lQ==", + "version": "8.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.29.0.tgz", + "integrity": "sha512-8C0+jlNJOwQso2GapCVWWfW/rzaq7Lbme+vGUFKE31djwNncIpgXD7Cd4weEsDdkoZDjH0lwwr3QDQFuyrMg9g==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "8.28.0", - "@typescript-eslint/types": "8.28.0", - "@typescript-eslint/typescript-estree": "8.28.0", - "@typescript-eslint/visitor-keys": "8.28.0", + "@typescript-eslint/scope-manager": "8.29.0", + "@typescript-eslint/types": "8.29.0", + "@typescript-eslint/typescript-estree": "8.29.0", + "@typescript-eslint/visitor-keys": "8.29.0", "debug": "^4.3.4" } }, "@typescript-eslint/scope-manager": { - "version": "8.28.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.28.0.tgz", - "integrity": "sha512-u2oITX3BJwzWCapoZ/pXw6BCOl8rJP4Ij/3wPoGvY8XwvXflOzd1kLrDUUUAIEdJSFh+ASwdTHqtan9xSg8buw==", + "version": "8.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.29.0.tgz", + "integrity": "sha512-aO1PVsq7Gm+tcghabUpzEnVSFMCU4/nYIgC2GOatJcllvWfnhrgW0ZEbnTxm36QsikmCN1K/6ZgM7fok2I7xNw==", "dev": true, "requires": { - "@typescript-eslint/types": "8.28.0", - "@typescript-eslint/visitor-keys": "8.28.0" + "@typescript-eslint/types": "8.29.0", + "@typescript-eslint/visitor-keys": "8.29.0" } }, "@typescript-eslint/type-utils": { - "version": "8.28.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.28.0.tgz", - "integrity": "sha512-oRoXu2v0Rsy/VoOGhtWrOKDiIehvI+YNrDk5Oqj40Mwm0Yt01FC/Q7nFqg088d3yAsR1ZcZFVfPCTTFCe/KPwg==", + "version": "8.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.29.0.tgz", + "integrity": "sha512-ahaWQ42JAOx+NKEf5++WC/ua17q5l+j1GFrbbpVKzFL/tKVc0aYY8rVSYUpUvt2hUP1YBr7mwXzx+E/DfUWI9Q==", "dev": true, "requires": { - "@typescript-eslint/typescript-estree": "8.28.0", - "@typescript-eslint/utils": "8.28.0", + "@typescript-eslint/typescript-estree": "8.29.0", + "@typescript-eslint/utils": "8.29.0", "debug": "^4.3.4", "ts-api-utils": "^2.0.1" } }, "@typescript-eslint/types": { - "version": "8.28.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.28.0.tgz", - "integrity": "sha512-bn4WS1bkKEjx7HqiwG2JNB3YJdC1q6Ue7GyGlwPHyt0TnVq6TtD/hiOdTZt71sq0s7UzqBFXD8t8o2e63tXgwA==", + "version": "8.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.29.0.tgz", + "integrity": "sha512-wcJL/+cOXV+RE3gjCyl/V2G877+2faqvlgtso/ZRbTCnZazh0gXhe+7gbAnfubzN2bNsBtZjDvlh7ero8uIbzg==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "8.28.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.28.0.tgz", - "integrity": "sha512-H74nHEeBGeklctAVUvmDkxB1mk+PAZ9FiOMPFncdqeRBXxk1lWSYraHw8V12b7aa6Sg9HOBNbGdSHobBPuQSuA==", + "version": "8.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.29.0.tgz", + "integrity": "sha512-yOfen3jE9ISZR/hHpU/bmNvTtBW1NjRbkSFdZOksL1N+ybPEE7UVGMwqvS6CP022Rp00Sb0tdiIkhSCe6NI8ow==", "dev": true, "requires": { - "@typescript-eslint/types": "8.28.0", - "@typescript-eslint/visitor-keys": "8.28.0", + "@typescript-eslint/types": "8.29.0", + "@typescript-eslint/visitor-keys": "8.29.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -7093,24 +7094,24 @@ } }, "@typescript-eslint/utils": { - "version": "8.28.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.28.0.tgz", - "integrity": "sha512-OELa9hbTYciYITqgurT1u/SzpQVtDLmQMFzy/N8pQE+tefOyCWT79jHsav294aTqV1q1u+VzqDGbuujvRYaeSQ==", + "version": "8.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.29.0.tgz", + "integrity": "sha512-gX/A0Mz9Bskm8avSWFcK0gP7cZpbY4AIo6B0hWYFCaIsz750oaiWR4Jr2CI+PQhfW1CpcQr9OlfPS+kMFegjXA==", "dev": true, "requires": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.28.0", - "@typescript-eslint/types": "8.28.0", - "@typescript-eslint/typescript-estree": "8.28.0" + "@typescript-eslint/scope-manager": "8.29.0", + "@typescript-eslint/types": "8.29.0", + "@typescript-eslint/typescript-estree": "8.29.0" } }, "@typescript-eslint/visitor-keys": { - "version": "8.28.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.28.0.tgz", - "integrity": "sha512-hbn8SZ8w4u2pRwgQ1GlUrPKE+t2XvcCW5tTRF7j6SMYIuYG37XuzIW44JCZPa36evi0Oy2SnM664BlIaAuQcvg==", + "version": "8.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.29.0.tgz", + "integrity": "sha512-Sne/pVz8ryR03NFK21VpN88dZ2FdQXOlq3VIklbrTYEt8yXtRFr9tvUhqvCeKjqYk5FSim37sHbooT6vzBTZcg==", "dev": true, "requires": { - "@typescript-eslint/types": "8.28.0", + "@typescript-eslint/types": "8.29.0", "eslint-visitor-keys": "^4.2.0" }, "dependencies": { @@ -7307,9 +7308,9 @@ } }, "@vscode/vsce": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/@vscode/vsce/-/vsce-3.3.0.tgz", - "integrity": "sha512-HA/pUyvh/TQWkc4wG7AudPIWUvsR8i4jiWZZgM/a69ncPi9Nm5FDogf/wVEk4EWJs4/UdxU7J6X18dfAwfPbxA==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/@vscode/vsce/-/vsce-3.3.2.tgz", + "integrity": "sha512-XQ4IhctYalSTMwLnMS8+nUaGbU7v99Qm2sOoGfIEf2QC7jpiLXZZMh7NwArEFsKX4gHTJLx0/GqAUlCdC3gKCw==", "dev": true, "requires": { "@azure/identity": "^4.1.0", @@ -8216,36 +8217,36 @@ "dev": true }, "esbuild": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.1.tgz", - "integrity": "sha512-BGO5LtrGC7vxnqucAe/rmvKdJllfGaYWdyABvyMoXQlfYMb2bbRuReWR5tEGE//4LcNJj9XrkovTqNYRFZHAMQ==", - "dev": true, - "requires": { - "@esbuild/aix-ppc64": "0.25.1", - "@esbuild/android-arm": "0.25.1", - "@esbuild/android-arm64": "0.25.1", - "@esbuild/android-x64": "0.25.1", - "@esbuild/darwin-arm64": "0.25.1", - "@esbuild/darwin-x64": "0.25.1", - "@esbuild/freebsd-arm64": "0.25.1", - "@esbuild/freebsd-x64": "0.25.1", - "@esbuild/linux-arm": "0.25.1", - "@esbuild/linux-arm64": "0.25.1", - "@esbuild/linux-ia32": "0.25.1", - "@esbuild/linux-loong64": "0.25.1", - "@esbuild/linux-mips64el": "0.25.1", - "@esbuild/linux-ppc64": "0.25.1", - "@esbuild/linux-riscv64": "0.25.1", - "@esbuild/linux-s390x": "0.25.1", - "@esbuild/linux-x64": "0.25.1", - "@esbuild/netbsd-arm64": "0.25.1", - "@esbuild/netbsd-x64": "0.25.1", - "@esbuild/openbsd-arm64": "0.25.1", - "@esbuild/openbsd-x64": "0.25.1", - "@esbuild/sunos-x64": "0.25.1", - "@esbuild/win32-arm64": "0.25.1", - "@esbuild/win32-ia32": "0.25.1", - "@esbuild/win32-x64": "0.25.1" + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.2.tgz", + "integrity": "sha512-16854zccKPnC+toMywC+uKNeYSv+/eXkevRAfwRD/G9Cleq66m8XFIrigkbvauLLlCfDL45Q2cWegSg53gGBnQ==", + "dev": true, + "requires": { + "@esbuild/aix-ppc64": "0.25.2", + "@esbuild/android-arm": "0.25.2", + "@esbuild/android-arm64": "0.25.2", + "@esbuild/android-x64": "0.25.2", + "@esbuild/darwin-arm64": "0.25.2", + "@esbuild/darwin-x64": "0.25.2", + "@esbuild/freebsd-arm64": "0.25.2", + "@esbuild/freebsd-x64": "0.25.2", + "@esbuild/linux-arm": "0.25.2", + "@esbuild/linux-arm64": "0.25.2", + "@esbuild/linux-ia32": "0.25.2", + "@esbuild/linux-loong64": "0.25.2", + "@esbuild/linux-mips64el": "0.25.2", + "@esbuild/linux-ppc64": "0.25.2", + "@esbuild/linux-riscv64": "0.25.2", + "@esbuild/linux-s390x": "0.25.2", + "@esbuild/linux-x64": "0.25.2", + "@esbuild/netbsd-arm64": "0.25.2", + "@esbuild/netbsd-x64": "0.25.2", + "@esbuild/openbsd-arm64": "0.25.2", + "@esbuild/openbsd-x64": "0.25.2", + "@esbuild/sunos-x64": "0.25.2", + "@esbuild/win32-arm64": "0.25.2", + "@esbuild/win32-ia32": "0.25.2", + "@esbuild/win32-x64": "0.25.2" } }, "escalade": { diff --git a/package.json b/package.json index df99e1c1a..2722d4152 100644 --- a/package.json +++ b/package.json @@ -1652,24 +1652,24 @@ "@types/lodash.debounce": "^4.0.9", "@types/mocha": "^10.0.10", "@types/mock-fs": "^4.13.4", - "@types/node": "^20.17.27", + "@types/node": "^20.17.28", "@types/plist": "^3.0.5", - "@types/semver": "^7.5.8", + "@types/semver": "^7.7.0", "@types/sinon": "^17.0.4", "@types/sinon-chai": "^3.2.12", "@types/vscode": "^1.88.0", "@types/xml2js": "^0.4.14", - "@typescript-eslint/eslint-plugin": "^8.28.0", - "@typescript-eslint/parser": "^8.28.0", + "@typescript-eslint/eslint-plugin": "^8.29.0", + "@typescript-eslint/parser": "^8.29.0", "@vscode/debugprotocol": "^1.68.0", "@vscode/test-cli": "^0.0.10", "@vscode/test-electron": "^2.4.1", - "@vscode/vsce": "^3.3.0", + "@vscode/vsce": "^3.3.2", "chai": "^4.5.0", "chai-as-promised": "^7.1.2", "chai-subset": "^1.6.0", "del-cli": "^6.0.0", - "esbuild": "^0.25.1", + "esbuild": "^0.25.2", "eslint": "^8.57.0", "eslint-config-prettier": "^10.1.1", "lodash.throttle": "^4.1.1", From 38db866516e604a26f6cdb7dec9f35386734d45b Mon Sep 17 00:00:00 2001 From: David Cummings Date: Tue, 1 Apr 2025 15:25:01 -0400 Subject: [PATCH 73/86] Convert docs to DocC (#1437) This change migrates the markdown based documentation into a DocC bundle with the goal to publish this documentation on swift.org. This change: * Adds a DocC bundle with the existing documentation * Pares down the readme to remove most of the documentation and links out to the documentation instead * Adds a command to the extension to open the documentation * Adds the open documentation command to the project view --- .gitignore | 1 + README.md | 113 +----------------- package.json | 11 ++ src/commands.ts | 2 + src/commands/openDocumentation.ts | 24 ++++ .../userdocs.docc/automatic-task-creation.md | 13 ++ userdocs/userdocs.docc/commands.md | 56 +++++++++ .../userdocs.docc}/coverage-render.png | Bin .../userdocs.docc}/coverage-report.png | Bin .../userdocs.docc}/coverage-run.png | Bin userdocs/userdocs.docc/debugging.md | 11 ++ .../userdocs.docc}/install-extension.png | Bin .../userdocs.docc}/install-pre-release.png | Bin userdocs/userdocs.docc/installation.md | 7 ++ userdocs/userdocs.docc/language-features.md | 5 + .../userdocs.docc}/package-dependencies.png | Bin userdocs/userdocs.docc/project-view.md | 9 ++ .../userdocs.docc}/remote-dev.md | 2 + {docs => userdocs/userdocs.docc}/settings.md | 2 + .../userdocs.docc/supported-toolchains.md | 18 +++ .../userdocs.docc}/test-coverage.md | 8 +- userdocs/userdocs.docc/test-explorer.md | 9 ++ .../userdocs.docc}/test-explorer.png | Bin userdocs/userdocs.docc/userdocs.md | 40 +++++++ 24 files changed, 219 insertions(+), 112 deletions(-) create mode 100644 src/commands/openDocumentation.ts create mode 100644 userdocs/userdocs.docc/automatic-task-creation.md create mode 100644 userdocs/userdocs.docc/commands.md rename {docs/images => userdocs/userdocs.docc}/coverage-render.png (100%) rename {docs/images => userdocs/userdocs.docc}/coverage-report.png (100%) rename {docs/images => userdocs/userdocs.docc}/coverage-run.png (100%) create mode 100644 userdocs/userdocs.docc/debugging.md rename {images => userdocs/userdocs.docc}/install-extension.png (100%) rename {docs/images => userdocs/userdocs.docc}/install-pre-release.png (100%) create mode 100644 userdocs/userdocs.docc/installation.md create mode 100644 userdocs/userdocs.docc/language-features.md rename {images => userdocs/userdocs.docc}/package-dependencies.png (100%) create mode 100644 userdocs/userdocs.docc/project-view.md rename {docs => userdocs/userdocs.docc}/remote-dev.md (98%) rename {docs => userdocs/userdocs.docc}/settings.md (98%) create mode 100644 userdocs/userdocs.docc/supported-toolchains.md rename {docs => userdocs/userdocs.docc}/test-coverage.md (89%) create mode 100644 userdocs/userdocs.docc/test-explorer.md rename {images => userdocs/userdocs.docc}/test-explorer.png (100%) create mode 100644 userdocs/userdocs.docc/userdocs.md diff --git a/.gitignore b/.gitignore index da0219e89..d42891036 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ assets/documentation-webview assets/test/**/Package.resolved assets/swift-docc-render ud +userdocs/userdocs.docc/.docc-build diff --git a/README.md b/README.md index f6731b0f8..872434390 100644 --- a/README.md +++ b/README.md @@ -10,118 +10,13 @@ This extension adds language support for Swift to Visual Studio Code, providing * Package dependency view * Test Explorer view -This extension uses [SourceKit LSP](https://github.com/apple/sourcekit-lsp) for the [language server](https://microsoft.github.io/language-server-protocol/overviews/lsp/overview/), which powers code completion. It also has a dependency on [LLDB DAP](https://marketplace.visualstudio.com/items?itemName=llvm-vs-code-extensions.lldb-dap) for debugging. - -To propose new features, you can post on the [swift.org forums](https://forums.swift.org) in the [VS Code Swift Extension category](https://forums.swift.org/c/related-projects/vscode-swift-extension/). If you run into something that doesn't work the way you'd expect, you can [file an issue in the GitHub repository](https://github.com/swiftlang/vscode-swift/issues/new). - -## Installation - -The Swift extension is supported on macOS, Linux, and Windows. To install, firstly ensure you have [Swift installed on your system](https://www.swift.org/install/). Then [install the Swift extension](https://marketplace.visualstudio.com/items?itemName=swiftlang.swift-vscode). Once your machine is ready, you can get started with the **Swift: Create New Project...** command. - -## Features - -### Language features - -The extension provides language features such as code completion and jump to definition via [SourceKit-LSP](https://github.com/apple/sourcekit-lsp). To ensure the extension functions correctly, it’s important to first build the project so that SourceKit-LSP has access to all the symbol data. Whenever you add a new dependency to your project, make sure to rebuild it so that SourceKit-LSP can update its information. - -### Automatic task creation - -For workspaces that contain a **Package.swift** file, this extension will add the following tasks: - -- **Build All**: Build all targets in the Package -- **Build Debug **: Each executable in a Package.swift get a task for building a debug build -- **Build Release **: Each executable in a Package.swift get a task for building a release build - -These tasks are available via **Terminal ▸ Run Task...** and **Terminal ▸ Run Build Task...**. - -### Commands - -The extension adds the following commands, available via the command palette. - -#### Configuration - -- **Create New Project...**: Create a new Swift project using a template. This opens a dialog to guide you through creating a new project structure. -- **Create New Swift File...**: Create a new `.swift` file in the current workspace. -- **Select Toolchain**: Select the locally installed Swift toolchain (including Xcode toolchains on macOS) that you want to use Swift tools from. - -The following command is only available on macOS: - -- **Select Target Platform**: This is an experimental command that offers code editing support for iOS, tvOS, watchOS and visionOS projects. - -#### Building and Debugging - -- **Run Build**: Run `swift build` for the package associated with the open file. -- **Debug Build**: Run `swift build` with debugging enabled for the package associated with the open file, launching the binary and attaching the debugger. -- **Attach to Process...**: Attach the debugger to an already running process for debugging. -- **Clean Build Folder**: Clean the build folder for the package associated with the open file, removing all previously built products. - -#### Dependency Management - -- **Resolve Package Dependencies**: Run `swift package resolve` on packages associated with the open file. -- **Update Package Dependencies**: Run `swift package update` on packages associated with the open file. -- **Reset Package Dependencies**: Run `swift package reset` on packages associated with the open file. -- **Add to Workspace**: Add the current package to the active workspace in VS Code. -- **Clean Build**: Run `swift package clean` on packages associated with the open file. -- **Open Package.swift**: Open `Package.swift` for the package associated with the open file. -- **Use Local Version**: Switch the package dependency to use a local version of the package instead of the remote repository version. -- **Edit Locally**: Make the package dependency editable locally, allowing changes to the dependency to be reflected immediately. -- **Revert To Original Version**: Revert the package dependency to its original, unedited state after local changes have been made. -- **View Repository**: Open the external repository of the selected Swift package in a browser. +# Documentation -#### Testing +The official documentation for this extension is available at [vscode-swift](https://www.swift.org/vscode/documentation/vscode) -- **Test: Run All Tests**: Run all the tests across all test targes in the open project. -- **Test: Rerun Last Run**: Perform the last test run again. -- **Test: Open Coverage**: Open the last generated coverage report, if one exists. -- **Test: Run All Tests in Parallel**: Run all tests in parallel. This action only affects XCTests. Swift-testing tests are parallel by default, and their parallelism [is controlled in code](https://developer.apple.com/documentation/testing/parallelization). - -#### Snippets and Scripts - -- **Insert Function Comment**: Insert a standard comment block for documenting a Swift function in the current file. -- **Run Swift Script**: Run the currently open file, as a Swift script. The file must not be part of a build target. If the file has not been saved it will save it to a temporary file so it can be run. -- **Run Swift Snippet**: If the currently open file is a Swift snippet then run it. -- **Debug Swift Snippet**: If the currently open file is a Swift snippet then debug it. - -#### Diagnostics - -- **Capture VS Code Swift Diagnostic Bundle**: Capture a diagnostic bundle from VS Code, containing logs and information to aid in troubleshooting Swift-related issues. -- **Clear Diagnostics Collection**: Clear all collected diagnostics in the current workspace to start fresh. -- **Restart LSP Server**: Restart the Swift Language Server Protocol (LSP) server for the current workspace. -- **Re-Index Project**: Force a re-index of the project to refresh code completion and symbol navigation support. - -### Package dependencies - -If your workspace contains a package that has dependencies, this extension will add a **Package Dependencies** view to the Explorer: - -![](images/package-dependencies.png) - -Additionally, the extension will monitor `Package.swift` and `Package.resolved` for changes, resolve any changes to the dependencies, and update the view as needed. - -### Debugging - -The Swift extension uses the [LLDB DAP](https://marketplace.visualstudio.com/items?itemName=llvm-vs-code-extensions.lldb-dap) extension for debugging. - -When you open a Swift package (a directory containing a `Package.swift` file), the extension automatically generates build tasks and launch configurations for each executable within the package. Additionally, if the package includes tests, the extension creates a configuration specifically designed to run those tests. These configurations all leverage the LLDB DAP extension as the debugger of choice. - -Use the **Run > Start Debugging** menu item to run an executable and start debugging. If you have multiple launch configurations you can choose which launch configuration to use in the debugger view. - -LLDB DAP is only available starting in Swift 6.0. On older versions of Swift the [CodeLLDB](https://marketplace.visualstudio.com/items?itemName=vadimcn.vscode-lldb) extension will be used for debugging instead. You will be prompted to install the CodeLLDB extension in this case. - -CodeLLDB includes a version of `lldb` that it uses by default for debugging, but this version of `lldb` doesn’t support Swift. The Swift extension will automatically identify the required version and offer to update the CodeLLDB configuration as necessary so that debugging is supported. - -### Test Explorer - -If your package contains tests then they can be viewed, run and debugged in the Test Explorer. - -![](images/test-explorer.png) - -Once your project is built, the Test Explorer will list all your tests. These tests are grouped by package, then test target, and finally, by XCTestCase class. From the Test Explorer, you can initiate a test run, debug a test run, and if a file has already been opened, you can jump to the source code for a test. - -### Documentation +This extension uses [SourceKit LSP](https://github.com/apple/sourcekit-lsp) for the [language server](https://microsoft.github.io/language-server-protocol/overviews/lsp/overview/), which powers code completion. It also has a dependency on [LLDB DAP](https://marketplace.visualstudio.com/items?itemName=llvm-vs-code-extensions.lldb-dap) for debugging. -* [Extension Settings](docs/settings.md) -* [Test Coverage](docs/test-coverage.md) -* [Visual Studio Code Dev Containers](docs/remote-dev.md) +To propose new features, you can post on the [swift.org forums](https://forums.swift.org) in the [VS Code Swift Extension category](https://forums.swift.org/c/related-projects/vscode-swift-extension/). If you run into something that doesn't work the way you'd expect, you can [file an issue in the GitHub repository](https://github.com/swiftlang/vscode-swift/issues/new). ## Contributing diff --git a/package.json b/package.json index 2722d4152..8ffc02a25 100644 --- a/package.json +++ b/package.json @@ -299,6 +299,12 @@ "title": "Run Tests with Coverage", "category": "Test", "icon": "$(debug-coverage)" + }, + { + "command": "swift.openDocumentation", + "title": "Open Documentation", + "category": "Swift", + "icon": "$(book)" } ], "configuration": [ @@ -1068,6 +1074,11 @@ "command": "swift.nestedDependenciesList", "when": "view == projectPanel && swift.flatDependenciesList", "group": "navigation@5" + }, + { + "command": "swift.openDocumentation", + "when": "view == projectPanel", + "group": "navigation@6" } ], "view/item/context": [ diff --git a/src/commands.ts b/src/commands.ts index 7a2577399..9bd986ef6 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -45,6 +45,7 @@ import { updateDependenciesViewList } from "./commands/dependencies/updateDepVie import { runTask } from "./commands/runTask"; import { TestKind } from "./TestExplorer/TestKind"; import { pickProcess } from "./commands/pickProcess"; +import { openDocumentation } from "./commands/openDocumentation"; /** * References: @@ -215,6 +216,7 @@ export function register(ctx: WorkspaceContext): vscode.Disposable[] { const packagePath = path.join(uri.fsPath, "Package.swift"); vscode.commands.executeCommand("vscode.open", vscode.Uri.file(packagePath)); }), + vscode.commands.registerCommand("swift.openDocumentation", () => openDocumentation()), ]; } diff --git a/src/commands/openDocumentation.ts b/src/commands/openDocumentation.ts new file mode 100644 index 000000000..db703ce2a --- /dev/null +++ b/src/commands/openDocumentation.ts @@ -0,0 +1,24 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the VS Code Swift open source project +// +// Copyright (c) 2025 the VS Code Swift project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of VS Code Swift project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import * as vscode from "vscode"; + +/** + * Handle the user requesting to show the vscode-swift documentation. + */ +export async function openDocumentation(): Promise { + return await vscode.env.openExternal( + vscode.Uri.parse("https://www.swift.org/vscode/documentation/vscode") + ); +} diff --git a/userdocs/userdocs.docc/automatic-task-creation.md b/userdocs/userdocs.docc/automatic-task-creation.md new file mode 100644 index 000000000..1df207c9f --- /dev/null +++ b/userdocs/userdocs.docc/automatic-task-creation.md @@ -0,0 +1,13 @@ +# Automatic Task Creation + +vscode-swift automatically adds tasks for common operations with your Package. + +> Tip: Tasks use workflows common to all VSCode extensions. For more information see https://code.visualstudio.com/docs/editor/tasks + +For workspaces that contain a **Package.swift** file, this extension will add the following tasks: + +- **Build All**: Build all targets in the Package +- **Build Debug **: Each executable in a Package.swift get a task for building a debug build +- **Build Release **: Each executable in a Package.swift get a task for building a release build + +These tasks are available via **Terminal ▸ Run Task...** and **Terminal ▸ Run Build Task...**. \ No newline at end of file diff --git a/userdocs/userdocs.docc/commands.md b/userdocs/userdocs.docc/commands.md new file mode 100644 index 000000000..0c5e936d1 --- /dev/null +++ b/userdocs/userdocs.docc/commands.md @@ -0,0 +1,56 @@ +# Commands + +vscode-swift adds various commands to Visual Studio Code. + +The extension adds the following commands, available via the command palette. + +#### Configuration + +- **Create New Project...**: Create a new Swift project using a template. This opens a dialog to guide you through creating a new project structure. +- **Create New Swift File...**: Create a new `.swift` file in the current workspace. +- **Select Toolchain**: Select the locally installed Swift toolchain (including Xcode toolchains on macOS) that you want to use Swift tools from. + +The following command is only available on macOS: + +- **Select Target Platform**: This is an experimental command that offers code completion for iOS and tvOS projects. + +#### Building and Debugging + +- **Run Build**: Run `swift build` for the package associated with the open file. +- **Debug Build**: Run `swift build` with debugging enabled for the package associated with the open file, launching the binary and attaching the debugger. +- **Attach to Process...**: Attach the debugger to an already running process for debugging. +- **Clean Build Folder**: Clean the build folder for the package associated with the open file, removing all previously built products. + +#### Dependency Management + +- **Resolve Package Dependencies**: Run `swift package resolve` on packages associated with the open file. +- **Update Package Dependencies**: Run `swift package update` on packages associated with the open file. +- **Reset Package Dependencies**: Run `swift package reset` on packages associated with the open file. +- **Add to Workspace**: Add the current package to the active workspace in VS Code. +- **Clean Build**: Run `swift package clean` on packages associated with the open file. +- **Open Package.swift**: Open `Package.swift` for the package associated with the open file. +- **Use Local Version**: Switch the package dependency to use a local version of the package instead of the remote repository version. +- **Edit Locally**: Make the package dependency editable locally, allowing changes to the dependency to be reflected immediately. +- **Revert To Original Version**: Revert the package dependency to its original, unedited state after local changes have been made. +- **View Repository**: Open the external repository of the selected Swift package in a browser. + +#### Testing + +- **Test: Run All Tests**: Run all the tests across all test targes in the open project. +- **Test: Rerun Last Run**: Perform the last test run again. +- **Test: Open Coverage**: Open the last generated coverage report, if one exists. +- **Test: Run All Tests in Parallel**: Run all tests in parallel. This action only affects XCTests. Swift-testing tests are parallel by default, and their parallelism [is controlled in code](https://developer.apple.com/documentation/testing/parallelization). + +#### Snippets and Scripts + +- **Insert Function Comment**: Insert a standard comment block for documenting a Swift function in the current file. +- **Run Swift Script**: Run the currently open file, as a Swift script. The file must not be part of a build target. If the file has not been saved it will save it to a temporary file so it can be run. +- **Run Swift Snippet**: If the currently open file is a Swift snippet then run it. +- **Debug Swift Snippet**: If the currently open file is a Swift snippet then debug it. + +#### Diagnostics + +- **Capture VS Code Swift Diagnostic Bundle**: Capture a diagnostic bundle from VS Code, containing logs and information to aid in troubleshooting Swift-related issues. +- **Clear Diagnostics Collection**: Clear all collected diagnostics in the current workspace to start fresh. +- **Restart LSP Server**: Restart the Swift Language Server Protocol (LSP) server for the current workspace. +- **Re-Index Project**: Force a re-index of the project to refresh code completion and symbol navigation support. \ No newline at end of file diff --git a/docs/images/coverage-render.png b/userdocs/userdocs.docc/coverage-render.png similarity index 100% rename from docs/images/coverage-render.png rename to userdocs/userdocs.docc/coverage-render.png diff --git a/docs/images/coverage-report.png b/userdocs/userdocs.docc/coverage-report.png similarity index 100% rename from docs/images/coverage-report.png rename to userdocs/userdocs.docc/coverage-report.png diff --git a/docs/images/coverage-run.png b/userdocs/userdocs.docc/coverage-run.png similarity index 100% rename from docs/images/coverage-run.png rename to userdocs/userdocs.docc/coverage-run.png diff --git a/userdocs/userdocs.docc/debugging.md b/userdocs/userdocs.docc/debugging.md new file mode 100644 index 000000000..fd8dd77e1 --- /dev/null +++ b/userdocs/userdocs.docc/debugging.md @@ -0,0 +1,11 @@ +# Debugging + +vscode-swift allows you to debug your Swift packages. + +> Tip: Debugging works best when using a version of the Swift toolchain 6.0 or higher + +When you open a Swift package (a directory containing a `Package.swift` file), the extension automatically generates build tasks and launch configurations for each executable within the package. Additionally, if the package includes tests, the extension creates a configuration specifically designed to run those tests. These configurations all leverage the CodeLLDB extension as the debugger of choice. + +Use the **Run > Start Debugging** menu item to run an executable and start debugging. If you have multiple launch configurations you can choose which launch configuration to use in the debugger view. + +Debugging uses workflows common to all VSCode extensions. For more information see https://code.visualstudio.com/docs/editor/debugging \ No newline at end of file diff --git a/images/install-extension.png b/userdocs/userdocs.docc/install-extension.png similarity index 100% rename from images/install-extension.png rename to userdocs/userdocs.docc/install-extension.png diff --git a/docs/images/install-pre-release.png b/userdocs/userdocs.docc/install-pre-release.png similarity index 100% rename from docs/images/install-pre-release.png rename to userdocs/userdocs.docc/install-pre-release.png diff --git a/userdocs/userdocs.docc/installation.md b/userdocs/userdocs.docc/installation.md new file mode 100644 index 000000000..d45dec611 --- /dev/null +++ b/userdocs/userdocs.docc/installation.md @@ -0,0 +1,7 @@ +# Installation + +vscode-code Swift is installed through the extension marketplace. + +The Swift extension is supported on macOS, Linux, and Windows. + +To install, firstly ensure you have [Swift installed on your system](https://www.swift.org/install/). Then [install the Swift extension](https://marketplace.visualstudio.com/items?itemName=swiftlang.swift-vscode). Once your machine is ready, you can get started with the **Swift: Create New Project...** command. diff --git a/userdocs/userdocs.docc/language-features.md b/userdocs/userdocs.docc/language-features.md new file mode 100644 index 000000000..f36d2a172 --- /dev/null +++ b/userdocs/userdocs.docc/language-features.md @@ -0,0 +1,5 @@ +# Language Features + +vscode-swift provides various language features to help you write Swift code. + +The extension provides language features such as code completion and jump to definition via [SourceKit-LSP](https://github.com/apple/sourcekit-lsp). To ensure the extension functions correctly, it’s important to first build the project so that SourceKit-LSP has access to all the symbol data. Whenever you add a new dependency to your project, make sure to rebuild it so that SourceKit-LSP can update its information. \ No newline at end of file diff --git a/images/package-dependencies.png b/userdocs/userdocs.docc/package-dependencies.png similarity index 100% rename from images/package-dependencies.png rename to userdocs/userdocs.docc/package-dependencies.png diff --git a/userdocs/userdocs.docc/project-view.md b/userdocs/userdocs.docc/project-view.md new file mode 100644 index 000000000..2d4b98e07 --- /dev/null +++ b/userdocs/userdocs.docc/project-view.md @@ -0,0 +1,9 @@ +# Project View + +vscode-swift provides project view + +If your workspace contains a package, this extension will add a **Swift Project** view to the Explorer: + +![](package-dependencies.png) + +Additionally, the extension will monitor `Package.swift` and `Package.resolved` for changes, resolve any changes to the dependencies, and update the view as needed. diff --git a/docs/remote-dev.md b/userdocs/userdocs.docc/remote-dev.md similarity index 98% rename from docs/remote-dev.md rename to userdocs/userdocs.docc/remote-dev.md index 195926a3e..8964062a1 100644 --- a/docs/remote-dev.md +++ b/userdocs/userdocs.docc/remote-dev.md @@ -1,5 +1,7 @@ # Visual Studio Code Dev Containers +Dev containers can be used as an easy way to develop when building for other platforms. + [VS Code Dev Containers](https://code.visualstudio.com/docs/remote/containers) allows you to run your code and environment in a container. This is especially useful for Swift when developing on macOS and deploying to Linux. You can ensure there are no compatibility issues in Foundation when running your code. The extension also works with [GitHub Codespaces](https://github.com/features/codespaces) to allow you to write your code on the web. ## Requirements diff --git a/docs/settings.md b/userdocs/userdocs.docc/settings.md similarity index 98% rename from docs/settings.md rename to userdocs/userdocs.docc/settings.md index b3e8c8527..8f60ba69b 100644 --- a/docs/settings.md +++ b/userdocs/userdocs.docc/settings.md @@ -1,5 +1,7 @@ # Extension Settings +vscode-swift provides various settings to configure its behaviour. + The Visual Studio Code Swift extension comes with a number of settings you can use to control how it works. Detailed descriptions of each setting is provided in the extension settings page. This document outlines useful configuration options not covered by the settings descriptions in the extension settings page. diff --git a/userdocs/userdocs.docc/supported-toolchains.md b/userdocs/userdocs.docc/supported-toolchains.md new file mode 100644 index 000000000..b2eb13cfb --- /dev/null +++ b/userdocs/userdocs.docc/supported-toolchains.md @@ -0,0 +1,18 @@ +# Supported Toolchains + +vscode-swift supports several versions of the Swift toolchain. + +vscode-swift supports the following Swift toolchains: + * 5.9 + * 5.10 + * 6.0 + * 6.1 + +The extension also strives to work with the latest nightly toolchains built from the main branch. + +Certain features of vscode-swift will only work with newer versions of the toolchains. We recommend using the latest version of the Swift toolchain to get the most benefit of the extension. The following features only work with certain toolchains as listed: + +Feature | Minimum Toolchain Required +------------------------ | ------------------------------------- +lldb-dap debugging | 6.0 + diff --git a/docs/test-coverage.md b/userdocs/userdocs.docc/test-coverage.md similarity index 89% rename from docs/test-coverage.md rename to userdocs/userdocs.docc/test-coverage.md index 167f1f043..5620a36c7 100644 --- a/docs/test-coverage.md +++ b/userdocs/userdocs.docc/test-coverage.md @@ -1,15 +1,17 @@ # Test Coverage +vscode-swift provides mechanisms to see coverage of your tests. + Test coverage is a measurement of how much of your code is tested by your tests. It defines how many lines of code were actually run when you ran your tests and how many were not. When a line of code is not run by your tests it will not have been tested and perhaps you need to extend your tests. The Swift extension integrates with VS Code's Code Coverage APIs to record what code has been hit or missed by your tests. -![](images/coverage-run.png) +![](coverage-run.png) Once you've performed a code coverage run a coverage report will be displayed in a section of the primary side bar. This report lists all the source files in your project and what percentage of lines were hit by tests. You can click on each file to open that file in the code editor. If you close the report you can always get it back by running the command `Test: Open Coverage`. -![](images/coverage-report.png) +![](coverage-report.png) After generating code coverage lines numbers in covered files will be coloured red or green depending on if they ran during the test run. Hovering over the line numbers shows how many times each line was run. Hitting the "Toggle Inline Coverage" link that appears when hovering over the line numbers will keep this information visible. -![](images/coverage-render.png) +![](coverage-render.png) diff --git a/userdocs/userdocs.docc/test-explorer.md b/userdocs/userdocs.docc/test-explorer.md new file mode 100644 index 000000000..8d585dbcf --- /dev/null +++ b/userdocs/userdocs.docc/test-explorer.md @@ -0,0 +1,9 @@ +# Test Explorer + +vscode-swift shows test results in the test explorer. + +If your package contains tests then they can be viewed, run and debugged in the Test Explorer. + +![](test-explorer.png) + +Once your project is built, the Test Explorer will list all your tests. These tests are grouped by package, then test target, and finally, by XCTestCase class. From the Test Explorer, you can initiate a test run, debug a test run, and if a file has already been opened, you can jump to the source code for a test. diff --git a/images/test-explorer.png b/userdocs/userdocs.docc/test-explorer.png similarity index 100% rename from images/test-explorer.png rename to userdocs/userdocs.docc/test-explorer.png diff --git a/userdocs/userdocs.docc/userdocs.md b/userdocs/userdocs.docc/userdocs.md new file mode 100644 index 000000000..983fa99dc --- /dev/null +++ b/userdocs/userdocs.docc/userdocs.md @@ -0,0 +1,40 @@ +# vscode-swift + +@Metadata { + @TechnologyRoot +} + +Language support for Swift in Visual Studio Code. + +This extension adds language support for Swift to Visual Studio Code, providing a seamless experience for developing Swift applications on platforms such as macOS, Linux and Windows. It supports: + +* Code completion +* Jump to definition, peek definition, find all references, symbol search +* Error annotations and apply suggestions from errors +* Automatic generation of launch configurations for debugging +* Automatic task creation +* Package dependency view +* Test Explorer view + +## Topics + +- +- + +### Features + +- +- +- +- +- +- + +### Advanced + +- + +### Reference + +- +- \ No newline at end of file From 1637ecb53e6dc894734d69b13eb80de39ef54ae7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 2 Apr 2025 11:54:34 -0400 Subject: [PATCH 74/86] Bump tar-fs from 2.1.1 to 2.1.2 (#1468) Bumps [tar-fs](https://github.com/mafintosh/tar-fs) from 2.1.1 to 2.1.2. - [Commits](https://github.com/mafintosh/tar-fs/compare/v2.1.1...v2.1.2) --- updated-dependencies: - dependency-name: tar-fs dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index a3d8eab00..ecbd51ff7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5740,10 +5740,11 @@ } }, "node_modules/tar-fs": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", - "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.2.tgz", + "integrity": "sha512-EsaAXwxmx8UB7FRKqeozqEPop69DXcmYwTQwXvyAPF352HJsPdkVhvTaDPYqfNgruveJIJy3TA2l+2zj8LJIJA==", "dev": true, + "license": "MIT", "optional": true, "dependencies": { "chownr": "^1.1.1", @@ -10226,9 +10227,9 @@ "dev": true }, "tar-fs": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", - "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.2.tgz", + "integrity": "sha512-EsaAXwxmx8UB7FRKqeozqEPop69DXcmYwTQwXvyAPF352HJsPdkVhvTaDPYqfNgruveJIJy3TA2l+2zj8LJIJA==", "dev": true, "optional": true, "requires": { From 4d2fb35c6cae0f396eeb6328f16c78db559b0081 Mon Sep 17 00:00:00 2001 From: Matthew Bastien Date: Thu, 3 Apr 2025 12:29:37 -0500 Subject: [PATCH 75/86] wait for settings to update before starting a debug session with CodeLLDB (#1482) --- src/debugger/debugAdapterFactory.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/debugger/debugAdapterFactory.ts b/src/debugger/debugAdapterFactory.ts index b42bcffcb..1c2da9fa5 100644 --- a/src/debugger/debugAdapterFactory.ts +++ b/src/debugger/debugAdapterFactory.ts @@ -211,24 +211,28 @@ export class LLDBDebugConfigurationProvider implements vscode.DebugConfiguration } switch (userSelection) { case "Global": - lldbConfig.update("library", libLldbPath, vscode.ConfigurationTarget.Global); - lldbConfig.update( + await lldbConfig.update("library", libLldbPath, vscode.ConfigurationTarget.Global); + await lldbConfig.update( "launch.expressions", "native", vscode.ConfigurationTarget.Global ); // clear workspace setting - lldbConfig.update("library", undefined, vscode.ConfigurationTarget.Workspace); + await lldbConfig.update("library", undefined, vscode.ConfigurationTarget.Workspace); // clear workspace setting - lldbConfig.update( + await lldbConfig.update( "launch.expressions", undefined, vscode.ConfigurationTarget.Workspace ); break; case "Workspace": - lldbConfig.update("library", libLldbPath, vscode.ConfigurationTarget.Workspace); - lldbConfig.update( + await lldbConfig.update( + "library", + libLldbPath, + vscode.ConfigurationTarget.Workspace + ); + await lldbConfig.update( "launch.expressions", "native", vscode.ConfigurationTarget.Workspace From 91a5b94d9c11a19028c70026ad7ac06b4f75bfb8 Mon Sep 17 00:00:00 2001 From: Paul LeMarquand Date: Fri, 4 Apr 2025 14:59:26 -0400 Subject: [PATCH 76/86] Fix failing windows builds (#1432) --- .github/workflows/nightly.yml | 3 - .github/workflows/pull_request.yml | 2 - .vscode/launch.json | 3 +- scripts/test_windows.ps1 | 50 +++++- src/FolderContext.ts | 5 +- src/SwiftPackage.ts | 11 +- src/TestExplorer/TestExplorer.ts | 3 + src/extension.ts | 2 +- src/process-list/BaseProcessList.ts | 4 +- src/toolchain/BuildFlags.ts | 11 +- src/toolchain/toolchain.ts | 38 ++-- src/ui/ProjectPanelProvider.ts | 2 +- src/utilities/utilities.ts | 3 +- .../DiagnosticsManager.test.ts | 4 +- test/integration-tests/SwiftPackage.test.ts | 17 +- .../WorkspaceContext.test.ts | 48 +++--- .../commands/dependency.test.ts | 4 +- .../tasks/SwiftPluginTaskProvider.test.ts | 11 +- .../TestExplorerIntegration.test.ts | 8 +- .../ui/ProjectPanelProvider.test.ts | 162 +++++++++++++----- .../utilities/testutilities.ts | 34 +++- test/utilities/tasks.ts | 4 +- 22 files changed, 306 insertions(+), 123 deletions(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 5037d9908..acf93bee1 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -47,7 +47,6 @@ jobs: # Windows windows_env_vars: | CI=1 - VSCODE_TEST=1 windows_pre_build_command: .github\workflows\scripts\windows\install-nodejs.ps1 windows_build_command: scripts\test_windows.ps1 enable_windows_docker: false @@ -63,7 +62,6 @@ jobs: NODE_PATH=/usr/local/nvm/versions/node/v20.18.2/bin NVM_DIR=/usr/local/nvm CI=1 - VSCODE_TEST=1 VSCODE_VERSION=insiders linux_pre_build_command: . .github/workflows/scripts/setup-linux.sh linux_build_command: ./scripts/test.sh @@ -71,7 +69,6 @@ jobs: windows_exclude_swift_versions: '[{"swift_version": "5.9"}, {"swift_version": "nightly-6.1"}, {"swift_version": "nightly"}]' windows_env_vars: | CI=1 - VSCODE_TEST=1 VSCODE_VERSION=insiders windows_pre_build_command: .github\workflows\scripts\windows\install-nodejs.ps1 windows_build_command: scripts\test_windows.ps1 diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index f482d7643..e71060c70 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -16,7 +16,6 @@ jobs: NODE_PATH=/usr/local/nvm/versions/node/v20.18.2/bin NVM_DIR=/usr/local/nvm CI=1 - VSCODE_TEST=1 FAST_TEST_RUN=${{ contains(github.event.pull_request.labels.*.name, 'full-test-run') && '0' || '1'}} linux_pre_build_command: . .github/workflows/scripts/setup-linux.sh linux_build_command: ./scripts/test.sh @@ -24,7 +23,6 @@ jobs: windows_exclude_swift_versions: '[{"swift_version": "nightly-6.1"},{"swift_version": "nightly"}]' windows_env_vars: | CI=1 - VSCODE_TEST=1 FAST_TEST_RUN=${{ contains(github.event.pull_request.labels.*.name, 'full-test-run') && '0' || '1'}} windows_pre_build_command: .github\workflows\scripts\windows\install-nodejs.ps1 windows_build_command: scripts\test_windows.ps1 diff --git a/.vscode/launch.json b/.vscode/launch.json index bbf089324..6c72352c7 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -25,8 +25,7 @@ "args": ["--profile=testing-debug"], "outFiles": ["${workspaceFolder}/dist/**/*.js"], "env": { - "VSCODE_DEBUG": "1", - "VSCODE_TEST": "1" + "VSCODE_DEBUG": "1" }, "preLaunchTask": "compile-tests" }, diff --git a/scripts/test_windows.ps1 b/scripts/test_windows.ps1 index 78a45704f..bb9c7b89b 100644 --- a/scripts/test_windows.ps1 +++ b/scripts/test_windows.ps1 @@ -12,8 +12,54 @@ ## ##===----------------------------------------------------------------------===## -$env:CI = "1" -$env:FAST_TEST_RUN = "1" +# In newer Visual C++ Tools they've added compiler intrinsics headers in wchar.h +# that end up creating a cyclic dependency between the `ucrt` and compiler intrinsics modules. +# Newer versions of swift (6.2) have a fixed modulemap that resolves the issue: https://github.com/swiftlang/swift/pull/79751 +$windowsSdkVersion = "10.0.22000.0" +$vcToolsVersion = "14.43.34808" + +# As a workaround we can pin the tools/SDK versions to older versions that are present in the GH Actions Windows image. +# In the future we may only want to apply this workaround to older versions of Swift that don't have the fixed module map. +$jsonFilePath = "./assets/test/.vscode/settings.json" +try { + $jsonContent = Get-Content -Raw -Path $jsonFilePath | ConvertFrom-Json +} catch { + Write-Host "Invalid JSON content in $jsonFilePath" + exit 1 +} +if ($jsonContent.PSObject.Properties['swift.buildArguments']) { + $jsonContent.PSObject.Properties.Remove('swift.buildArguments') +} + +$windowsSdkRoot = "C:\Program Files (x86)\Windows Kits\10\" + +$jsonContent | Add-Member -MemberType NoteProperty -Name "swift.buildArguments" -Value @( + "-Xbuild-tools-swiftc", "-windows-sdk-root", "-Xbuild-tools-swiftc", $windowsSdkRoot, + "-Xbuild-tools-swiftc", "-windows-sdk-version", "-Xbuild-tools-swiftc", $windowsSdkVersion, + "-Xbuild-tools-swiftc", "-visualc-tools-version", "-Xbuild-tools-swiftc", $vcToolsVersion, + "-Xswiftc", "-windows-sdk-root", "-Xswiftc", $windowsSdkRoot, + "-Xswiftc", "-windows-sdk-version", "-Xswiftc", $windowsSdkVersion, + "-Xswiftc", "-visualc-tools-version", "-Xswiftc", $vcToolsVersion +) + +if ($jsonContent.PSObject.Properties['swift.packageArguments']) { + $jsonContent.PSObject.Properties.Remove('swift.packageArguments') +} + +$jsonContent | Add-Member -MemberType NoteProperty -Name "swift.packageArguments" -Value @( + "-Xbuild-tools-swiftc", "-windows-sdk-root", "-Xbuild-tools-swiftc", $windowsSdkRoot, + "-Xbuild-tools-swiftc", "-windows-sdk-version", "-Xbuild-tools-swiftc", $windowsSdkVersion, + "-Xbuild-tools-swiftc", "-visualc-tools-version", "-Xbuild-tools-swiftc", $vcToolsVersion, + "-Xswiftc", "-windows-sdk-root", "-Xswiftc", $windowsSdkRoot, + "-Xswiftc", "-windows-sdk-version", "-Xswiftc", $windowsSdkVersion, + "-Xswiftc", "-visualc-tools-version", "-Xswiftc", $vcToolsVersion +) + +$jsonContent | ConvertTo-Json -Depth 32 | Set-Content -Path $jsonFilePath + +Write-Host "Contents of ${jsonFilePath}:" +Get-Content -Path $jsonFilePath + npm ci -ignore-script node-pty npm run lint npm run format diff --git a/src/FolderContext.ts b/src/FolderContext.ts index d1b70e327..f9299038e 100644 --- a/src/FolderContext.ts +++ b/src/FolderContext.ts @@ -22,6 +22,7 @@ import { WorkspaceContext, FolderOperation } from "./WorkspaceContext"; import { BackgroundCompilation } from "./BackgroundCompilation"; import { TaskQueue } from "./tasks/TaskQueue"; import { isPathInsidePath } from "./utilities/filesystem"; +import { SwiftOutputChannel } from "./ui/SwiftOutputChannel"; export class FolderContext implements vscode.Disposable { private packageWatcher: PackageWatcher; @@ -135,8 +136,8 @@ export class FolderContext implements vscode.Disposable { } /** Load Swift Plugins and store in Package */ - async loadSwiftPlugins() { - await this.swiftPackage.loadSwiftPlugins(this.workspaceContext.toolchain); + async loadSwiftPlugins(outputChannel: SwiftOutputChannel) { + await this.swiftPackage.loadSwiftPlugins(this.workspaceContext.toolchain, outputChannel); } /** diff --git a/src/SwiftPackage.ts b/src/SwiftPackage.ts index 8df08f560..cb35ca9b8 100644 --- a/src/SwiftPackage.ts +++ b/src/SwiftPackage.ts @@ -19,6 +19,7 @@ import { execSwift, getErrorDescription, hashString } from "./utilities/utilitie import { isPathInsidePath } from "./utilities/filesystem"; import { SwiftToolchain } from "./toolchain/toolchain"; import { BuildFlags } from "./toolchain/BuildFlags"; +import { SwiftOutputChannel } from "./ui/SwiftOutputChannel"; /** Swift Package Manager contents */ export interface PackageContents { @@ -278,7 +279,8 @@ export class SwiftPackage implements PackageContents { private static async loadPlugins( folder: vscode.Uri, - toolchain: SwiftToolchain + toolchain: SwiftToolchain, + outputChannel: SwiftOutputChannel ): Promise { try { const { stdout } = await execSwift(["package", "plugin", "--list"], toolchain, { @@ -298,7 +300,8 @@ export class SwiftPackage implements PackageContents { } } return plugins; - } catch { + } catch (error) { + outputChannel.appendLine(`Failed to laod plugins: ${error}`); // failed to load resolved file return undefined return []; } @@ -338,8 +341,8 @@ export class SwiftPackage implements PackageContents { this.workspaceState = await SwiftPackage.loadWorkspaceState(this.folder); } - public async loadSwiftPlugins(toolchain: SwiftToolchain) { - this.plugins = await SwiftPackage.loadPlugins(this.folder, toolchain); + public async loadSwiftPlugins(toolchain: SwiftToolchain, outputChannel: SwiftOutputChannel) { + this.plugins = await SwiftPackage.loadPlugins(this.folder, toolchain, outputChannel); } /** Return if has valid contents */ diff --git a/src/TestExplorer/TestExplorer.ts b/src/TestExplorer/TestExplorer.ts index e4123ca20..15e62221e 100644 --- a/src/TestExplorer/TestExplorer.ts +++ b/src/TestExplorer/TestExplorer.ts @@ -412,6 +412,9 @@ export class TestExplorer { * @param errorDescription Error description to display */ private setErrorTestItem(errorDescription: string | undefined, title = "Test Discovery Error") { + this.folderContext.workspaceContext.outputChannel.log( + `Test Discovery Error: ${errorDescription}` + ); this.controller.items.forEach(item => { this.controller.items.delete(item.id); }); diff --git a/src/extension.ts b/src/extension.ts index 7361075f1..32b8930b4 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -203,7 +203,7 @@ export async function activate(context: vscode.ExtensionContext): Promise { folder.workspaceFolder.uri )})`, async () => { - await folder.loadSwiftPlugins(); + await folder.loadSwiftPlugins(outputChannel); workspace.updatePluginContextKey(); folder.fireEvent(FolderOperation.pluginsUpdated); } diff --git a/src/process-list/BaseProcessList.ts b/src/process-list/BaseProcessList.ts index 567150a81..0e5f621bd 100644 --- a/src/process-list/BaseProcessList.ts +++ b/src/process-list/BaseProcessList.ts @@ -42,7 +42,9 @@ export abstract class BaseProcessList implements ProcessList { protected abstract createParser(): ProcessListParser; async listAllProcesses(): Promise { - const execCommand = exec(this.getCommand(), this.getCommandArguments()); + const execCommand = exec(this.getCommand(), this.getCommandArguments(), { + maxBuffer: 10 * 1024 * 1024, // Increase the max buffer size to 10Mb + }); const parser = this.createParser(); return (await execCommand).stdout.split("\n").flatMap(line => { const process = parser(line.toString()); diff --git a/src/toolchain/BuildFlags.ts b/src/toolchain/BuildFlags.ts index 01d1164e9..1c4f3a6ee 100644 --- a/src/toolchain/BuildFlags.ts +++ b/src/toolchain/BuildFlags.ts @@ -71,11 +71,14 @@ export class BuildFlags { withSwiftPackageFlags(args: string[]): string[] { switch (args[0]) { - case "package": - if (args[1] === "resolve" || args[1] === "update") { - return [...args, ...configuration.packageArguments]; + case "package": { + if (args[1] === "init") { + return args; } - return args; + const newArgs = [...args]; + newArgs.splice(1, 0, ...configuration.packageArguments); + return newArgs; + } case "build": case "run": case "test": diff --git a/src/toolchain/toolchain.ts b/src/toolchain/toolchain.ts index 4976def5e..f765f692c 100644 --- a/src/toolchain/toolchain.ts +++ b/src/toolchain/toolchain.ts @@ -121,24 +121,28 @@ export class SwiftToolchain { const swiftFolderPath = await this.getSwiftFolderPath(); const toolchainPath = await this.getToolchainPath(swiftFolderPath); const targetInfo = await this.getSwiftTargetInfo(); - const swiftVersion = await this.getSwiftVersion(targetInfo); - const runtimePath = await this.getRuntimePath(targetInfo); - const defaultSDK = await this.getDefaultSDK(); + const swiftVersion = this.getSwiftVersion(targetInfo); + const [runtimePath, defaultSDK] = await Promise.all([ + this.getRuntimePath(targetInfo), + this.getDefaultSDK(), + ]); const customSDK = this.getCustomSDK(); - const xcTestPath = await this.getXCTestPath( - targetInfo, - swiftFolderPath, - swiftVersion, - runtimePath, - customSDK ?? defaultSDK - ); - const swiftTestingPath = await this.getSwiftTestingPath( - targetInfo, - swiftVersion, - runtimePath, - customSDK ?? defaultSDK - ); - const swiftPMTestingHelperPath = await this.getSwiftPMTestingHelperPath(toolchainPath); + const [xcTestPath, swiftTestingPath, swiftPMTestingHelperPath] = await Promise.all([ + this.getXCTestPath( + targetInfo, + swiftFolderPath, + swiftVersion, + runtimePath, + customSDK ?? defaultSDK + ), + this.getSwiftTestingPath( + targetInfo, + swiftVersion, + runtimePath, + customSDK ?? defaultSDK + ), + this.getSwiftPMTestingHelperPath(toolchainPath), + ]); return new SwiftToolchain( swiftFolderPath, diff --git a/src/ui/ProjectPanelProvider.ts b/src/ui/ProjectPanelProvider.ts index 79f2a237c..8a923cd10 100644 --- a/src/ui/ProjectPanelProvider.ts +++ b/src/ui/ProjectPanelProvider.ts @@ -348,7 +348,7 @@ class ErrorNode { * * Can be either a {@link PackageNode}, {@link FileNode}, {@link TargetNode}, {@link TaskNode}, {@link ErrorNode} or {@link HeaderNode}. */ -type TreeNode = PackageNode | FileNode | HeaderNode | TaskNode | TargetNode | ErrorNode; +export type TreeNode = PackageNode | FileNode | HeaderNode | TaskNode | TargetNode | ErrorNode; /** * A {@link vscode.TreeDataProvider TreeDataProvider} for project dependencies, tasks and commands {@link vscode.TreeView TreeView}. diff --git a/src/utilities/utilities.ts b/src/utilities/utilities.ts index 030c91fa3..f33e8de0c 100644 --- a/src/utilities/utilities.ts +++ b/src/utilities/utilities.ts @@ -113,8 +113,9 @@ export async function execFile( cp.execFile(executable, args, options, (error, stdout, stderr) => { if (error) { reject(new ExecFileError(error, stdout, stderr)); + } else { + resolve({ stdout, stderr }); } - resolve({ stdout, stderr }); }); }); } diff --git a/test/integration-tests/DiagnosticsManager.test.ts b/test/integration-tests/DiagnosticsManager.test.ts index 5ac97f374..825fd88c8 100644 --- a/test/integration-tests/DiagnosticsManager.test.ts +++ b/test/integration-tests/DiagnosticsManager.test.ts @@ -60,7 +60,7 @@ function assertWithoutDiagnostic(uri: vscode.Uri, expected: vscode.Diagnostic) { suite("DiagnosticsManager Test Suite", async function () { // Was hitting a timeout in suiteSetup during CI build once in a while - this.timeout(5000); + this.timeout(15000); const swiftConfig = vscode.workspace.getConfiguration("swift"); @@ -126,7 +126,7 @@ suite("DiagnosticsManager Test Suite", async function () { activateExtensionForSuite({ async setup(ctx) { - this.timeout(60000 * 2); + this.timeout(60000 * 5); workspaceContext = ctx; toolchain = workspaceContext.toolchain; diff --git a/test/integration-tests/SwiftPackage.test.ts b/test/integration-tests/SwiftPackage.test.ts index 7e29cf209..2a73e78a0 100644 --- a/test/integration-tests/SwiftPackage.test.ts +++ b/test/integration-tests/SwiftPackage.test.ts @@ -18,9 +18,10 @@ import { SwiftPackage } from "../../src/SwiftPackage"; import { SwiftToolchain } from "../../src/toolchain/toolchain"; import { Version } from "../../src/utilities/version"; -let toolchain: SwiftToolchain; +suite("SwiftPackage Test Suite", function () { + this.timeout(5 * 60 * 1000); // 5 minute timeout + let toolchain: SwiftToolchain; -suite("SwiftPackage Test Suite", () => { setup(async () => { toolchain = await SwiftToolchain.create(); }); @@ -28,13 +29,13 @@ suite("SwiftPackage Test Suite", () => { test("No package", async () => { const spmPackage = await SwiftPackage.create(testAssetUri("empty-folder"), toolchain); assert.strictEqual(spmPackage.foundPackage, false); - }).timeout(10000); + }); test("Invalid package", async () => { const spmPackage = await SwiftPackage.create(testAssetUri("invalid-package"), toolchain); assert.strictEqual(spmPackage.foundPackage, true); assert.strictEqual(spmPackage.isValid, false); - }).timeout(10000); + }); test("Library package", async () => { const spmPackage = await SwiftPackage.create(testAssetUri("package2"), toolchain); @@ -43,7 +44,7 @@ suite("SwiftPackage Test Suite", () => { assert.strictEqual(spmPackage.libraryProducts[0].name, "package2"); assert.strictEqual(spmPackage.dependencies.length, 0); assert.strictEqual(spmPackage.targets.length, 2); - }).timeout(10000); + }); test("Package resolve v2", async function () { if (!toolchain) { @@ -59,7 +60,7 @@ suite("SwiftPackage Test Suite", () => { const spmPackage = await SwiftPackage.create(testAssetUri("package5.6"), toolchain); assert.strictEqual(spmPackage.isValid, true); assert(spmPackage.resolved !== undefined); - }).timeout(20000); + }); test("Identity case-insensitivity", async () => { const spmPackage = await SwiftPackage.create(testAssetUri("identity-case"), toolchain); @@ -68,7 +69,7 @@ suite("SwiftPackage Test Suite", () => { assert(spmPackage.resolved !== undefined); assert.strictEqual(spmPackage.resolved.pins.length, 1); assert.strictEqual(spmPackage.resolved.pins[0].identity, "yams"); - }).timeout(10000); + }); test("Identity different from name", async () => { const spmPackage = await SwiftPackage.create(testAssetUri("identity-different"), toolchain); @@ -77,5 +78,5 @@ suite("SwiftPackage Test Suite", () => { assert(spmPackage.resolved !== undefined); assert.strictEqual(spmPackage.resolved.pins.length, 1); assert.strictEqual(spmPackage.resolved.pins[0].identity, "swift-log"); - }).timeout(10000); + }); }); diff --git a/test/integration-tests/WorkspaceContext.test.ts b/test/integration-tests/WorkspaceContext.test.ts index afd14f630..9f4827f0d 100644 --- a/test/integration-tests/WorkspaceContext.test.ts +++ b/test/integration-tests/WorkspaceContext.test.ts @@ -14,12 +14,13 @@ import * as vscode from "vscode"; import * as assert from "assert"; +import { afterEach } from "mocha"; import { testAssetUri } from "../fixtures"; import { FolderOperation, WorkspaceContext } from "../../src/WorkspaceContext"; import { createBuildAllTask } from "../../src/tasks/SwiftTaskProvider"; import { Version } from "../../src/utilities/version"; import { SwiftExecution } from "../../src/tasks/SwiftExecution"; -import { activateExtensionForSuite } from "./utilities/testutilities"; +import { activateExtensionForSuite, updateSettings } from "./utilities/testutilities"; import { FolderContext } from "../../src/FolderContext"; import { assertContains } from "./testexplorer/utilities"; @@ -82,31 +83,32 @@ suite("WorkspaceContext Test Suite", () => { }).timeout(60000 * 2); }); - suite("Tasks", async function () { + suite("Tasks", function () { activateExtensionForSuite({ async setup(ctx) { workspaceContext = ctx; }, }); + let resetSettings: (() => Promise) | undefined; + afterEach(async () => { + if (resetSettings) { + await resetSettings(); + resetSettings = undefined; + } + }); + // Was hitting a timeout in suiteSetup during CI build once in a while this.timeout(5000); - const swiftConfig = vscode.workspace.getConfiguration("swift"); - - suiteTeardown(async () => { - await swiftConfig.update("buildArguments", undefined); - await swiftConfig.update("packageArguments", undefined); - await swiftConfig.update("path", undefined); - await swiftConfig.update("diagnosticsStyle", undefined); - }); - test("Default Task values", async () => { const folder = workspaceContext.folders.find( f => f.folder.fsPath === packageFolder.fsPath ); assert(folder); - await swiftConfig.update("diagnosticsStyle", undefined); + resetSettings = await updateSettings({ + "swift.diagnosticsStyle": "", + }); const buildAllTask = createBuildAllTask(folder); const execution = buildAllTask.execution; assert.strictEqual(buildAllTask.definition.type, "swift"); @@ -123,7 +125,9 @@ suite("WorkspaceContext Test Suite", () => { f => f.folder.fsPath === packageFolder.fsPath ); assert(folder); - await swiftConfig.update("diagnosticsStyle", "default"); + resetSettings = await updateSettings({ + "swift.diagnosticsStyle": "default", + }); const buildAllTask = createBuildAllTask(folder); const execution = buildAllTask.execution; assert.strictEqual(buildAllTask.definition.type, "swift"); @@ -139,7 +143,9 @@ suite("WorkspaceContext Test Suite", () => { f => f.folder.fsPath === packageFolder.fsPath ); assert(folder); - await swiftConfig.update("diagnosticsStyle", "swift"); + resetSettings = await updateSettings({ + "swift.diagnosticsStyle": "swift", + }); const buildAllTask = createBuildAllTask(folder); const execution = buildAllTask.execution; assert.strictEqual(buildAllTask.definition.type, "swift"); @@ -156,12 +162,13 @@ suite("WorkspaceContext Test Suite", () => { f => f.folder.fsPath === packageFolder.fsPath ); assert(folder); - await swiftConfig.update("diagnosticsStyle", undefined); - await swiftConfig.update("buildArguments", ["--sanitize=thread"]); + resetSettings = await updateSettings({ + "swift.diagnosticsStyle": "", + "swift.buildArguments": ["--sanitize=thread"], + }); const buildAllTask = createBuildAllTask(folder); const execution = buildAllTask.execution as SwiftExecution; assertContainsArg(execution, "--sanitize=thread"); - await swiftConfig.update("buildArguments", []); }); test("Package Arguments Settings", async () => { @@ -169,12 +176,13 @@ suite("WorkspaceContext Test Suite", () => { f => f.folder.fsPath === packageFolder.fsPath ); assert(folder); - await swiftConfig.update("diagnosticsStyle", undefined); - await swiftConfig.update("packageArguments", ["--replace-scm-with-registry"]); + resetSettings = await updateSettings({ + "swift.diagnosticsStyle": "", + "swift.packageArguments": ["--replace-scm-with-registry"], + }); const buildAllTask = createBuildAllTask(folder); const execution = buildAllTask.execution as SwiftExecution; assertContainsArg(execution, "--replace-scm-with-registry"); - await swiftConfig.update("packageArguments", []); }); test("Swift Path", async () => { diff --git a/test/integration-tests/commands/dependency.test.ts b/test/integration-tests/commands/dependency.test.ts index 771d3c8a4..579bb0afb 100644 --- a/test/integration-tests/commands/dependency.test.ts +++ b/test/integration-tests/commands/dependency.test.ts @@ -25,8 +25,8 @@ import { getBuildAllTask, SwiftTask } from "../../../src/tasks/SwiftTaskProvider suite("Dependency Commmands Test Suite", function () { // full workflow's interaction with spm is longer than the default timeout - // 120 seconds for each test should be more than enough - this.timeout(120 * 1000); + // 3 minutes for each test should be more than enough + this.timeout(3 * 60 * 1000); let defaultContext: FolderContext; let depsContext: FolderContext; diff --git a/test/integration-tests/tasks/SwiftPluginTaskProvider.test.ts b/test/integration-tests/tasks/SwiftPluginTaskProvider.test.ts index bf3715c49..9b0159ee6 100644 --- a/test/integration-tests/tasks/SwiftPluginTaskProvider.test.ts +++ b/test/integration-tests/tasks/SwiftPluginTaskProvider.test.ts @@ -32,18 +32,21 @@ import { import { mutable } from "../../utilities/types"; import { SwiftExecution } from "../../../src/tasks/SwiftExecution"; import { SwiftTask } from "../../../src/tasks/SwiftTaskProvider"; +import { SwiftOutputChannel } from "../../../src/ui/SwiftOutputChannel"; suite("SwiftPluginTaskProvider Test Suite", function () { let workspaceContext: WorkspaceContext; let folderContext: FolderContext; - this.timeout(60000); // Mostly only when running suite with .only + this.timeout(120000); // Mostly only when running suite with .only activateExtensionForSuite({ async setup(ctx) { workspaceContext = ctx; + const outputChannel = new SwiftOutputChannel("SwiftPluginTaskProvider.tests"); folderContext = await folderInRootWorkspace("command-plugin", workspaceContext); - await folderContext.loadSwiftPlugins(); + await folderContext.loadSwiftPlugins(outputChannel); + expect(outputChannel.logs.length).to.equal(0, `Expected no output channel logs`); expect(workspaceContext.folders).to.not.have.lengthOf(0); }, }); @@ -168,13 +171,17 @@ suite("SwiftPluginTaskProvider Test Suite", function () { afterEach(async () => { if (resetSettings) { await resetSettings(); + resetSettings = undefined; } }); test("sets arguments", async () => { const tasks = await vscode.tasks.fetchTasks({ type: "swift-plugin" }); const task = tasks.find(t => t.name === "command-plugin"); + expect(task).to.not.be.undefined; + const swiftExecution = task?.execution as SwiftExecution; + expect(swiftExecution).to.not.be.undefined; assert.deepEqual( swiftExecution.args, workspaceContext.toolchain.buildFlags.withAdditionalFlags([ diff --git a/test/integration-tests/testexplorer/TestExplorerIntegration.test.ts b/test/integration-tests/testexplorer/TestExplorerIntegration.test.ts index 77950b546..b4aeb49c6 100644 --- a/test/integration-tests/testexplorer/TestExplorerIntegration.test.ts +++ b/test/integration-tests/testexplorer/TestExplorerIntegration.test.ts @@ -47,6 +47,8 @@ import { updateSettings, } from "../utilities/testutilities"; import { Commands } from "../../../src/commands"; +import { executeTaskAndWaitForResult } from "../../utilities/tasks"; +import { createBuildAllTask } from "../../../src/tasks/SwiftTaskProvider"; suite("Test Explorer Suite", function () { const MAX_TEST_RUN_TIME_MINUTES = 5; @@ -67,6 +69,8 @@ suite("Test Explorer Suite", function () { testExplorer = targetFolder.addTestExplorer(); + await executeTaskAndWaitForResult(createBuildAllTask(targetFolder)); + // Set up the listener before bringing the text explorer in to focus, // which starts searching the workspace for tests. await waitForTestExplorerReady(testExplorer); @@ -119,6 +123,7 @@ suite("Test Explorer Suite", function () { afterEach(async () => { if (resetSettings) { await resetSettings(); + resetSettings = undefined; } }); @@ -144,6 +149,7 @@ suite("Test Explorer Suite", function () { afterEach(async () => { if (resetSettings) { await resetSettings(); + resetSettings = undefined; } }); @@ -196,7 +202,7 @@ suite("Test Explorer Suite", function () { ["testPassing()", "testPassingSuffix()"], ], ]); - } else if (workspaceContext.swiftVersion.isLessThanOrEqual(new Version(5, 10, 0))) { + } else if (workspaceContext.swiftVersion.isLessThan(new Version(6, 0, 0))) { // 5.10 uses `swift test list` which returns test alphabetically, without the round brackets. // Does not include swift-testing tests. assertTestControllerHierarchy(testExplorer.controller, [ diff --git a/test/integration-tests/ui/ProjectPanelProvider.test.ts b/test/integration-tests/ui/ProjectPanelProvider.test.ts index d14a34259..10a786178 100644 --- a/test/integration-tests/ui/ProjectPanelProvider.test.ts +++ b/test/integration-tests/ui/ProjectPanelProvider.test.ts @@ -16,7 +16,12 @@ import { expect } from "chai"; import { beforeEach, afterEach } from "mocha"; import * as vscode from "vscode"; import * as path from "path"; -import { ProjectPanelProvider, PackageNode, FileNode } from "../../../src/ui/ProjectPanelProvider"; +import { + ProjectPanelProvider, + PackageNode, + FileNode, + TreeNode, +} from "../../../src/ui/ProjectPanelProvider"; import { executeTaskAndWaitForResult, waitForNoRunningTasks } from "../../utilities/tasks"; import { getBuildAllTask, SwiftTask } from "../../../src/tasks/SwiftTaskProvider"; import { testAssetPath } from "../../fixtures"; @@ -28,11 +33,13 @@ import { import contextKeys from "../../../src/contextKeys"; import { WorkspaceContext } from "../../../src/WorkspaceContext"; import { Version } from "../../../src/utilities/version"; +import { wait } from "../../../src/utilities/utilities"; +import { SwiftOutputChannel } from "../../../src/ui/SwiftOutputChannel"; suite("ProjectPanelProvider Test Suite", function () { let workspaceContext: WorkspaceContext; let treeProvider: ProjectPanelProvider; - this.timeout(2 * 60 * 1000); // Allow up to 2 minutes to build + this.timeout(5 * 60 * 1000); // Allow up to 5 minutes to build activateExtensionForSuite({ async setup(ctx) { @@ -45,7 +52,9 @@ suite("ProjectPanelProvider Test Suite", function () { const buildAllTask = await getBuildAllTask(folderContext); buildAllTask.definition.dontTriggerTestDiscovery = true; await executeTaskAndWaitForResult(buildAllTask as SwiftTask); - await folderContext.loadSwiftPlugins(); + const outputChannel = new SwiftOutputChannel("ProjectPanelProvider.tests"); + await folderContext.loadSwiftPlugins(outputChannel); + expect(outputChannel.logs.length).to.equal(0, `Expected no output channel logs`); treeProvider = new ProjectPanelProvider(workspaceContext); await workspaceContext.focusFolder(folderContext); }, @@ -66,35 +75,44 @@ suite("ProjectPanelProvider Test Suite", function () { afterEach(async () => { if (resetSettings) { await resetSettings(); + resetSettings = undefined; } }); test("Includes top level nodes", async () => { - const commands = await treeProvider.getChildren(); - const commandNames = commands.map(n => n.name); - expect(commandNames).to.deep.equal([ - "Dependencies", - "Targets", - "Tasks", - "Snippets", - "Commands", - ]); + await waitForChildren( + () => treeProvider.getChildren(), + commands => { + const commandNames = commands.map(n => n.name); + expect(commandNames).to.deep.equal([ + "Dependencies", + "Targets", + "Tasks", + "Snippets", + "Commands", + ]); + } + ); }); suite("Targets", () => { test("Includes targets", async () => { - const targets = await getHeaderChildren("Targets"); - const targetNames = targets.map(target => target.name); - expect( - targetNames, - `Expected to find dependencies target, but instead items were ${targetNames}` - ).to.deep.equal([ - "ExecutableTarget", - "LibraryTarget", - "PluginTarget", - "AnotherTests", - "TargetsTests", - ]); + await waitForChildren( + () => getHeaderChildren("Targets"), + targets => { + const targetNames = targets.map(target => target.name); + expect( + targetNames, + `Expected to find dependencies target, but instead items were ${targetNames}` + ).to.deep.equal([ + "ExecutableTarget", + "LibraryTarget", + "PluginTarget", + "AnotherTests", + "TargetsTests", + ]); + } + ); }); }); @@ -144,32 +162,70 @@ suite("ProjectPanelProvider Test Suite", function () { suite("Snippets", () => { test("Includes snippets", async () => { - const snippets = await getHeaderChildren("Snippets"); - const snippetNames = snippets.map(n => n.name); - expect(snippetNames).to.deep.equal(["AnotherSnippet", "Snippet"]); + await waitForChildren( + () => getHeaderChildren("Snippets"), + snippets => { + const snippetNames = snippets.map(n => n.name); + expect(snippetNames).to.deep.equal(["AnotherSnippet", "Snippet"]); + } + ); }); - test("Executes a snippet", async () => { - const snippets = await getHeaderChildren("Snippets"); - const snippet = snippets.find(n => n.name === "Snippet"); - expect(snippet).to.not.be.undefined; + test("Executes a snippet", async function () { + if ( + process.platform === "win32" && + workspaceContext.toolchain.swiftVersion.isLessThanOrEqual(new Version(5, 9, 0)) + ) { + this.skip(); + } + const snippet = await waitForChildren( + () => getHeaderChildren("Snippets"), + snippets => { + const snippet = snippets.find(n => n.name === "Snippet"); + expect(snippet).to.not.be.undefined; + return snippet; + } + ); const result = await vscode.commands.executeCommand("swift.runSnippet", snippet?.name); expect(result).to.be.true; }); }); suite("Commands", () => { - test("Includes commands", async () => { - const commands = await getHeaderChildren("Commands"); - const commandNames = commands.map(n => n.name); - expect(commandNames).to.deep.equal(["PluginTarget"]); + test("Includes commands", async function () { + if ( + process.platform === "win32" && + workspaceContext.toolchain.swiftVersion.isLessThanOrEqual(new Version(6, 0, 0)) + ) { + this.skip(); + } + + await waitForChildren( + () => getHeaderChildren("Commands"), + commands => { + const commandNames = commands.map(n => n.name); + expect(commandNames).to.deep.equal(["PluginTarget"]); + } + ); }); - test("Executes a command", async () => { - const commands = await getHeaderChildren("Commands"); - const command = commands.find(n => n.name === "PluginTarget"); - expect(command).to.not.be.undefined; + test("Executes a command", async function () { + if ( + process.platform === "win32" && + workspaceContext.toolchain.swiftVersion.isLessThanOrEqual(new Version(6, 0, 0)) + ) { + this.skip(); + } + + const command = await waitForChildren( + () => getHeaderChildren("Commands"), + commands => { + const command = commands.find(n => n.name === "PluginTarget"); + expect(command).to.not.be.undefined; + return command; + } + ); const treeItem = command?.toTreeItem(); expect(treeItem?.command).to.not.be.undefined; expect(treeItem?.command?.arguments).to.not.be.undefined; @@ -300,6 +356,34 @@ suite("ProjectPanelProvider Test Suite", function () { return await header.getChildren(); } + async function waitForChildren( + getChildren: () => Promise, + predicate: (children: TreeNode[]) => T + ) { + let counter = 0; + let error: unknown; + // Check the predicate once a second for 30 seconds. + while (counter < 30) { + const children = await getChildren(); + try { + return predicate(children); + } catch (err) { + error = err; + counter += 1; + } + + if (!error) { + break; + } + + await wait(1000); + } + + if (error) { + throw error; + } + } + function assertPathsEqual(path1: string | undefined, path2: string | undefined) { expect(path1).to.not.be.undefined; expect(path2).to.not.be.undefined; diff --git a/test/integration-tests/utilities/testutilities.ts b/test/integration-tests/utilities/testutilities.ts index 23a7530cc..51a79a97f 100644 --- a/test/integration-tests/utilities/testutilities.ts +++ b/test/integration-tests/utilities/testutilities.ts @@ -97,10 +97,9 @@ const extensionBootstrapper = (() => { } catch (error: any) { // Mocha will throw an error to break out of a test if `.skip` is used. if (error.message?.indexOf("sync skip;") === -1) { - console.error(`Error during test/suite setup: ${JSON.stringify(error)}`); - console.error("Captured logs are:"); + console.error(`Error during test/suite setup, captured logs are:`); workspaceContext.outputChannel.logs.map(log => console.error(log)); - console.error("================ end test logs ================"); + console.log("======== END OF LOGS ========\n\n"); } throw error; } @@ -126,6 +125,7 @@ const extensionBootstrapper = (() => { }); after(async function () { + let userTeardownError: unknown | undefined; try { // First run the users supplied teardown, then await the autoTeardown if it exists. if (teardown) { @@ -134,18 +134,29 @@ const extensionBootstrapper = (() => { if (autoTeardown) { await autoTeardown(); } - if (restoreSettings) { - await restoreSettings(); - } } catch (error) { if (workspaceContext) { console.error(`Error during test/suite teardown, captured logs are:`); workspaceContext.outputChannel.logs.map(log => console.log(log)); + console.log("======== END OF LOGS ========\n\n"); } - throw error; + // We always want to restore settings and deactivate the extension even if the + // user supplied teardown fails. That way we have the best chance at not causing + // issues with the next test. + // + // Store the error and re-throw it after extension deactivation. + userTeardownError = error; } + if (restoreSettings) { + await restoreSettings(); + } await extensionBootstrapper.deactivateExtension(); + + // Re-throw the user supplied teardown error + if (userTeardownError) { + throw userTeardownError; + } }); } @@ -335,10 +346,17 @@ export async function updateSettings(settings: SettingsMap): Promise<() => Promi // to their new value before continuing. for (const setting of Object.keys(settings)) { const { section, name } = decomposeSettingName(setting); + // If the setting is being unset then its possible the setting will evaluate to the + // default value, and so we should be checking to see if its switched to that instead. + const expected = !settings[setting] + ? (vscode.workspace.getConfiguration(section, { languageId: "swift" }).inspect(name) + ?.defaultValue ?? settings[setting]) + : settings[setting]; + while ( isDeepStrictEqual( vscode.workspace.getConfiguration(section, { languageId: "swift" }).get(name), - settings[setting] + expected ) === false ) { // Not yet, wait a bit and try again. diff --git a/test/utilities/tasks.ts b/test/utilities/tasks.ts index dcd72f31a..688c6636c 100644 --- a/test/utilities/tasks.ts +++ b/test/utilities/tasks.ts @@ -109,7 +109,9 @@ export function waitForNoRunningTasks(options?: { timeout: number }): Promise e.task.name); reject( - `Timed out waiting for tasks to complete. The following ${runningTasks.length} tasks are still running: ${runningTasks}.` + new Error( + `Timed out waiting for tasks to complete. The following ${runningTasks.length} tasks are still running: ${runningTasks}.` + ) ); }, options.timeout); } From 962681f2cd04725d107f64013811dc54240381f0 Mon Sep 17 00:00:00 2001 From: Louis Qian Date: Mon, 7 Apr 2025 08:31:46 -0500 Subject: [PATCH 77/86] feat: secondary quick pick for selecting Swift Version with `runSwiftScript` command (#1476) Implement a secondary quick pick for selecting desired Swift Version for Run Swift Script command, and also enabling global settings for it in vscode workspace. --- package.json | 16 ++++++++++++++++ src/commands/runSwiftScript.ts | 27 +++++++++++++++++++++++++-- src/configuration.ts | 5 +++++ 3 files changed, 46 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 8ffc02a25..30ed73df8 100644 --- a/package.json +++ b/package.json @@ -324,6 +324,22 @@ }, "markdownDescription": "Additional arguments to pass to `swift build` and `swift test`. Keys and values should be provided as individual entries in the list. If you have created a copy of the build task in `tasks.json` then these build arguments will not be propagated to that task." }, + "swift.scriptSwiftLanguageVersion": { + "type": "string", + "enum": [ + "6", + "5", + "Ask Every Run" + ], + "enumDescriptions": [ + "Use Swift 6 when running Swift scripts.", + "Use Swift 5 when running Swift scripts.", + "Prompt to select the Swift version each time a script is run." + ], + "default": "6", + "markdownDescription": "The default Swift version to use when running Swift scripts.", + "scope": "machine-overridable" + }, "swift.packageArguments": { "type": "array", "default": [], diff --git a/src/commands/runSwiftScript.ts b/src/commands/runSwiftScript.ts index 5eb77bfc9..713534160 100644 --- a/src/commands/runSwiftScript.ts +++ b/src/commands/runSwiftScript.ts @@ -18,6 +18,7 @@ import * as fs from "fs/promises"; import { createSwiftTask } from "../tasks/SwiftTaskProvider"; import { WorkspaceContext } from "../WorkspaceContext"; import { Version } from "../utilities/version"; +import configuration from "../configuration"; /** * Run the active document through the Swift REPL @@ -40,6 +41,29 @@ export async function runSwiftScript(ctx: WorkspaceContext) { return; } + let target: string; + + const defaultVersion = configuration.scriptSwiftLanguageVersion; + if (defaultVersion === "Ask Every Run") { + const picked = await vscode.window.showQuickPick( + [ + // Potentially add more versions here + { value: "5", label: "Swift 5" }, + { value: "6", label: "Swift 6" }, + ], + { + placeHolder: "Select a target Swift version", + } + ); + + if (!picked) { + return; + } + target = picked.value; + } else { + target = defaultVersion; + } + let filename = document.fileName; let isTempFile = false; if (document.isUntitled) { @@ -52,9 +76,8 @@ export async function runSwiftScript(ctx: WorkspaceContext) { // otherwise save document await document.save(); } - const runTask = createSwiftTask( - [filename], + ["-swift-version", target, filename], `Run ${filename}`, { scope: vscode.TaskScope.Global, diff --git a/src/configuration.ts b/src/configuration.ts index e4bf5eb30..1cdfceca4 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -328,6 +328,11 @@ const configuration = { .get("buildArguments", []) .map(substituteVariablesInString); }, + get scriptSwiftLanguageVersion(): string { + return vscode.workspace + .getConfiguration("swift") + .get("scriptSwiftLanguageVersion", "6"); + }, /** swift package arguments */ get packageArguments(): string[] { return vscode.workspace From 43e391a62fdaf32b9e0bef5ce39b4b3107b27096 Mon Sep 17 00:00:00 2001 From: Paul LeMarquand Date: Mon, 7 Apr 2025 13:25:58 -0400 Subject: [PATCH 78/86] Fix potential error when no toolchain found (#1485) If there is no toolchain available we short circuit extension activation but still try and stop the workspace, which hasn't been created. --- src/extension.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/extension.ts b/src/extension.ts index 32b8930b4..4b3202c28 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -122,7 +122,6 @@ export async function activate(context: vscode.ExtensionContext): Promise { outputChannel, activate: () => activate(context), deactivate: async () => { - await workspaceContext.stop(); await deactivate(context); }, }; From a09143c3b317dbbedc54ebe732d6bff5334520a2 Mon Sep 17 00:00:00 2001 From: Paul LeMarquand Date: Mon, 7 Apr 2025 13:36:39 -0400 Subject: [PATCH 79/86] Support all supported swift language versions in swift.scriptSwiftLanguageVersion (#1486) Support all supported swift language versions in swift.scriptSwiftLanguageVersion --- package.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.json b/package.json index 30ed73df8..22cd674af 100644 --- a/package.json +++ b/package.json @@ -329,6 +329,8 @@ "enum": [ "6", "5", + "4.2", + "4", "Ask Every Run" ], "enumDescriptions": [ From 3655586b2b9c5f69bb721f134b6c255d25d7066f Mon Sep 17 00:00:00 2001 From: Paul LeMarquand Date: Wed, 9 Apr 2025 09:58:00 -0400 Subject: [PATCH 80/86] Run windows tests directly in same shell (#1487) Occasionally the Windows tests will hang after all tests have completed until the GitHub action times out. Using `Start-Process` with the `-Wait` flag will wait for the process and all of its child processes to terminate. This means that if the VS Code instance under test has any lingering processes the build may hang forever. Instead run the test command directly, referencing the success status with `$LASTEXITCODE`. This should hopefully report the status more reliably without hanging. --- scripts/test_windows.ps1 | 6 ++--- .../utilities/testutilities.ts | 24 ++++++++++++------- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/scripts/test_windows.ps1 b/scripts/test_windows.ps1 index bb9c7b89b..2f95c72e3 100644 --- a/scripts/test_windows.ps1 +++ b/scripts/test_windows.ps1 @@ -64,10 +64,10 @@ npm ci -ignore-script node-pty npm run lint npm run format npm run package -$Process = Start-Process npm "run integration-test" -Wait -PassThru -NoNewWindow -if ($Process.ExitCode -eq 0) { +npm run integration-test +if ($LASTEXITCODE -eq 0) { Write-Host 'SUCCESS' } else { - Write-Host ('FAILED ({0})' -f $Process.ExitCode) + Write-Host ('FAILED ({0})' -f $LASTEXITCODE) exit 1 } diff --git a/test/integration-tests/utilities/testutilities.ts b/test/integration-tests/utilities/testutilities.ts index 51a79a97f..7d3692ae4 100644 --- a/test/integration-tests/utilities/testutilities.ts +++ b/test/integration-tests/utilities/testutilities.ts @@ -23,6 +23,7 @@ import { waitForNoRunningTasks } from "../../utilities/tasks"; import { closeAllEditors } from "../../utilities/commands"; import { isDeepStrictEqual } from "util"; import { Version } from "../../../src/utilities/version"; +import { SwiftOutputChannel } from "../../../src/ui/SwiftOutputChannel"; import configuration from "../../../src/configuration"; function getRootWorkspaceFolder(): vscode.WorkspaceFolder { @@ -31,6 +32,12 @@ function getRootWorkspaceFolder(): vscode.WorkspaceFolder { return result; } +function printLogs(outputChannel: SwiftOutputChannel, message: string) { + console.error(`${message}, captured logs are:`); + outputChannel.logs.map(log => console.log(log)); + console.log("======== END OF LOGS ========\n\n"); +} + const extensionBootstrapper = (() => { let activator: (() => Promise) | undefined = undefined; let activatedAPI: Api | undefined = undefined; @@ -116,11 +123,10 @@ const extensionBootstrapper = (() => { mocha.afterEach(function () { if (this.currentTest && activatedAPI && this.currentTest.isFailed()) { - console.log(`Captured logs during ${testTitle(this.currentTest)}:`); - for (const log of activatedAPI.outputChannel.logs) { - console.log(log); - } - console.log("======== END OF LOGS ========\n\n"); + printLogs( + activatedAPI.outputChannel, + `Test failed: ${testTitle(this.currentTest)}` + ); } }); @@ -136,9 +142,7 @@ const extensionBootstrapper = (() => { } } catch (error) { if (workspaceContext) { - console.error(`Error during test/suite teardown, captured logs are:`); - workspaceContext.outputChannel.logs.map(log => console.log(log)); - console.log("======== END OF LOGS ========\n\n"); + printLogs(workspaceContext.outputChannel, "Error during test/suite teardown"); } // We always want to restore settings and deactivate the extension even if the // user supplied teardown fails. That way we have the best chance at not causing @@ -197,6 +201,10 @@ const extensionBootstrapper = (() => { } if (!workspaceContext) { + printLogs( + activatedAPI.outputChannel, + "Error during test/suite setup, workspace context could not be created" + ); throw new Error("Extension did not activate. Workspace context is not available."); } From d5933b815906243e7d2aa3737e9a37bd85de4992 Mon Sep 17 00:00:00 2001 From: Paul LeMarquand Date: Wed, 9 Apr 2025 11:34:58 -0400 Subject: [PATCH 81/86] Enable unit tests on Windows (#1492) Only the integration tests were running on Windows in CI. Switch to running `npm run test` which will run both integration and unit tests, and fix path normalization issues that were preventing some tests from passing on Windows. --- scripts/test_windows.ps1 | 2 +- .../LanguageClientManager.test.ts | 11 ++++---- .../tasks/SwiftPluginTaskProvider.test.ts | 6 ++++- test/unit-tests/toolchain/BuildFlags.test.ts | 27 ++++++++++++------- test/unit-tests/toolchain/toolchain.test.ts | 9 ++++--- .../ui/PackageDependencyProvider.test.ts | 5 ++-- test/unit-tests/utilities/filesystem.test.ts | 3 ++- 7 files changed, 40 insertions(+), 23 deletions(-) diff --git a/scripts/test_windows.ps1 b/scripts/test_windows.ps1 index 2f95c72e3..8bc6211ba 100644 --- a/scripts/test_windows.ps1 +++ b/scripts/test_windows.ps1 @@ -64,7 +64,7 @@ npm ci -ignore-script node-pty npm run lint npm run format npm run package -npm run integration-test +npm run test if ($LASTEXITCODE -eq 0) { Write-Host 'SUCCESS' } else { diff --git a/test/unit-tests/sourcekit-lsp/LanguageClientManager.test.ts b/test/unit-tests/sourcekit-lsp/LanguageClientManager.test.ts index fc46eb3a7..421fa0499 100644 --- a/test/unit-tests/sourcekit-lsp/LanguageClientManager.test.ts +++ b/test/unit-tests/sourcekit-lsp/LanguageClientManager.test.ts @@ -13,6 +13,7 @@ //===----------------------------------------------------------------------===// import * as vscode from "vscode"; +import * as path from "path"; import { expect } from "chai"; import { match } from "sinon"; import { FolderEvent, FolderOperation, WorkspaceContext } from "../../../src/WorkspaceContext"; @@ -301,7 +302,7 @@ suite("LanguageClientManager Suite", () => { DidChangeWorkspaceFoldersNotification.type, { event: { - added: [{ name: "folder1", uri: "/folder1" }], + added: [{ name: "folder1", uri: path.normalize("/folder1") }], removed: [], }, } as DidChangeWorkspaceFoldersParams @@ -320,7 +321,7 @@ suite("LanguageClientManager Suite", () => { DidChangeWorkspaceFoldersNotification.type, { event: { - added: [{ name: "folder2", uri: "/folder2" }], + added: [{ name: "folder2", uri: path.normalize("/folder2") }], removed: [], }, } as DidChangeWorkspaceFoldersParams @@ -340,7 +341,7 @@ suite("LanguageClientManager Suite", () => { { event: { added: [], - removed: [{ name: "folder1", uri: "/folder1" }], + removed: [{ name: "folder1", uri: path.normalize("/folder1") }], }, } as DidChangeWorkspaceFoldersParams ); @@ -472,7 +473,7 @@ suite("LanguageClientManager Suite", () => { DidChangeActiveDocumentNotification.method, { textDocument: { - uri: "/folder1/file.swift", + uri: path.normalize("/folder1/file.swift"), }, } as DidChangeActiveDocumentParams ); @@ -501,7 +502,7 @@ suite("LanguageClientManager Suite", () => { DidChangeActiveDocumentNotification.method, { textDocument: { - uri: "/folder1/file.swift", + uri: path.normalize("/folder1/file.swift"), }, } as DidChangeActiveDocumentParams ); diff --git a/test/unit-tests/tasks/SwiftPluginTaskProvider.test.ts b/test/unit-tests/tasks/SwiftPluginTaskProvider.test.ts index 762d24f3b..c7d54d27c 100644 --- a/test/unit-tests/tasks/SwiftPluginTaskProvider.test.ts +++ b/test/unit-tests/tasks/SwiftPluginTaskProvider.test.ts @@ -14,6 +14,7 @@ import * as vscode from "vscode"; import * as assert from "assert"; +import * as path from "path"; import { match } from "sinon"; import { WorkspaceContext } from "../../../src/WorkspaceContext"; import { SwiftPluginTaskProvider } from "../../../src/tasks/SwiftPluginTaskProvider"; @@ -148,7 +149,10 @@ suite("SwiftPluginTaskProvider Unit Test Suite", () => { new vscode.CancellationTokenSource().token ); const swiftExecution = resolvedTask.execution as SwiftExecution; - assert.equal(swiftExecution.options.cwd, `${workspaceFolder.uri.fsPath}/myCWD`); + assert.equal( + swiftExecution.options.cwd, + path.normalize(`${workspaceFolder.uri.fsPath}/myCWD`) + ); }); test("includes fallback cwd", async () => { diff --git a/test/unit-tests/toolchain/BuildFlags.test.ts b/test/unit-tests/toolchain/BuildFlags.test.ts index 0d57cade7..628c9e455 100644 --- a/test/unit-tests/toolchain/BuildFlags.test.ts +++ b/test/unit-tests/toolchain/BuildFlags.test.ts @@ -12,6 +12,7 @@ // //===----------------------------------------------------------------------===// +import * as path from "path"; import { expect } from "chai"; import { DarwinCompatibleTarget, SwiftToolchain } from "../../../src/toolchain/toolchain"; import { ArgumentFilter, BuildFlags } from "../../../src/toolchain/BuildFlags"; @@ -204,35 +205,41 @@ suite("BuildFlags Test Suite", () => { expect( BuildFlags.buildDirectoryFromWorkspacePath("/some/full/workspace/test/path", false) - ).to.equal(".build"); + ).to.equal(path.normalize(".build")); expect( BuildFlags.buildDirectoryFromWorkspacePath("/some/full/workspace/test/path", true) - ).to.equal("/some/full/workspace/test/path/.build"); + ).to.equal(path.normalize("/some/full/workspace/test/path/.build")); }); test("absolute configuration provided", () => { - buildPathConfig.setValue("/some/other/full/test/path"); + buildPathConfig.setValue(path.normalize("/some/other/full/test/path")); expect( - BuildFlags.buildDirectoryFromWorkspacePath("/some/full/workspace/test/path", false) - ).to.equal("/some/other/full/test/path"); + BuildFlags.buildDirectoryFromWorkspacePath( + path.normalize("/some/full/workspace/test/path"), + false + ) + ).to.equal(path.normalize("/some/other/full/test/path")); expect( - BuildFlags.buildDirectoryFromWorkspacePath("/some/full/workspace/test/path", true) - ).to.equal("/some/other/full/test/path"); + BuildFlags.buildDirectoryFromWorkspacePath( + path.normalize("/some/full/workspace/test/path"), + true + ) + ).to.equal(path.normalize("/some/other/full/test/path")); }); test("relative configuration provided", () => { - buildPathConfig.setValue("some/relative/test/path"); + buildPathConfig.setValue(path.normalize("some/relative/test/path")); expect( BuildFlags.buildDirectoryFromWorkspacePath("/some/full/workspace/test/path", false) - ).to.equal("some/relative/test/path"); + ).to.equal(path.normalize("some/relative/test/path")); expect( BuildFlags.buildDirectoryFromWorkspacePath("/some/full/workspace/test/path", true) - ).to.equal("/some/full/workspace/test/path/some/relative/test/path"); + ).to.equal(path.normalize("/some/full/workspace/test/path/some/relative/test/path")); }); }); diff --git a/test/unit-tests/toolchain/toolchain.test.ts b/test/unit-tests/toolchain/toolchain.test.ts index cc491fc7f..5b1af995a 100644 --- a/test/unit-tests/toolchain/toolchain.test.ts +++ b/test/unit-tests/toolchain/toolchain.test.ts @@ -13,6 +13,7 @@ //===----------------------------------------------------------------------===// import { expect } from "chai"; +import * as path from "path"; import * as mockFS from "mock-fs"; import * as utilities from "../../../src/utilities/utilities"; import { SwiftToolchain } from "../../../src/toolchain/toolchain"; @@ -78,7 +79,9 @@ suite("SwiftToolchain Unit Test Suite", () => { }); await expect(sut.getLLDBDebugAdapter()).to.eventually.equal( - "/Library/Developer/Toolchains/swift-6.0.1-RELEASE.xctoolchain/usr/bin/lldb-dap" + path.normalize( + "/Library/Developer/Toolchains/swift-6.0.1-RELEASE.xctoolchain/usr/bin/lldb-dap" + ) ); }); @@ -174,7 +177,7 @@ suite("SwiftToolchain Unit Test Suite", () => { }); await expect(sut.getLLDBDebugAdapter()).to.eventually.equal( - "/toolchains/swift-6.0.0/usr/bin/lldb-dap" + path.normalize("/toolchains/swift-6.0.0/usr/bin/lldb-dap") ); }); @@ -213,7 +216,7 @@ suite("SwiftToolchain Unit Test Suite", () => { }); await expect(sut.getLLDBDebugAdapter()).to.eventually.equal( - "/toolchains/swift-6.0.0/usr/bin/lldb-dap.exe" + path.normalize("/toolchains/swift-6.0.0/usr/bin/lldb-dap.exe") ); }); diff --git a/test/unit-tests/ui/PackageDependencyProvider.test.ts b/test/unit-tests/ui/PackageDependencyProvider.test.ts index b7cca7b3a..585e31045 100644 --- a/test/unit-tests/ui/PackageDependencyProvider.test.ts +++ b/test/unit-tests/ui/PackageDependencyProvider.test.ts @@ -13,6 +13,7 @@ //===----------------------------------------------------------------------===// import { expect } from "chai"; +import * as path from "path"; import * as vscode from "vscode"; import * as fs from "fs/promises"; import { FileNode, PackageNode } from "../../../src/ui/ProjectPanelProvider"; @@ -100,13 +101,13 @@ suite("PackageDependencyProvider Unit Test Suite", function () { expect(childFiles).to.deep.equal([ new FileNode( "file1", - "/path/to/.build/swift-markdown/file1", + path.normalize("/path/to/.build/swift-markdown/file1"), false, "SwiftMarkdown-1.2.3" ), new FileNode( "file2", - "/path/to/.build/swift-markdown/file2", + path.normalize("/path/to/.build/swift-markdown/file2"), false, "SwiftMarkdown-1.2.3" ), diff --git a/test/unit-tests/utilities/filesystem.test.ts b/test/unit-tests/utilities/filesystem.test.ts index 477b1abf1..4bd084372 100644 --- a/test/unit-tests/utilities/filesystem.test.ts +++ b/test/unit-tests/utilities/filesystem.test.ts @@ -12,6 +12,7 @@ // //===----------------------------------------------------------------------===// +import * as path from "path"; import { isPathInsidePath, expandFilePathTilde } from "../../../src/utilities/filesystem"; import { expect } from "chai"; @@ -30,7 +31,7 @@ suite("File System Utilities Unit Test Suite", () => { suite("expandFilePathTilde", () => { test("expands tilde", () => { expect(expandFilePathTilde("~/Test", "/Users/John", "darwin")).to.equal( - "/Users/John/Test" + path.normalize("/Users/John/Test") ); }); From 3e5f7d9a99cd9e43235dae5e2ae3c718b0ccab7c Mon Sep 17 00:00:00 2001 From: award999 Date: Fri, 11 Apr 2025 09:48:43 -0400 Subject: [PATCH 82/86] Only run insiders job against 6.1 (#1496) 6.1 is stable, don't want to run insiders against multiple versions --- .github/workflows/nightly.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index acf93bee1..999f93b1f 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -56,7 +56,7 @@ jobs: uses: swiftlang/github-workflows/.github/workflows/swift_package_test.yml@main with: # Linux - linux_exclude_swift_versions: '[{"swift_version": "5.8"}, {"swift_version": "5.9"}, {"swift_version": "5.10"}, {"swift_version": "nightly-6.1"}, {"swift_version": "nightly-main"}]' + linux_exclude_swift_versions: '[{"swift_version": "5.8"}, {"swift_version": "5.9"}, {"swift_version": "5.10"}, {"swift_version": "6.0"}, {"swift_version": "nightly-6.1"}, {"swift_version": "nightly-main"}]' linux_env_vars: | NODE_VERSION=v20.18.2 NODE_PATH=/usr/local/nvm/versions/node/v20.18.2/bin @@ -66,7 +66,7 @@ jobs: linux_pre_build_command: . .github/workflows/scripts/setup-linux.sh linux_build_command: ./scripts/test.sh # Windows - windows_exclude_swift_versions: '[{"swift_version": "5.9"}, {"swift_version": "nightly-6.1"}, {"swift_version": "nightly"}]' + windows_exclude_swift_versions: '[{"swift_version": "5.9"}, {"swift_version": "6.0"}, {"swift_version": "nightly-6.1"}, {"swift_version": "nightly"}]' windows_env_vars: | CI=1 VSCODE_VERSION=insiders From 8569c49dbcfa4fdfe6d33ffb2ab5ca3357e5301a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 11 Apr 2025 10:44:08 -0400 Subject: [PATCH 83/86] Bump the all-dependencies group with 4 updates (#1488) Bumps the all-dependencies group with 4 updates: [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node), [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin), [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) and [typescript](https://github.com/microsoft/TypeScript). Updates `@types/node` from 20.17.28 to 20.17.30 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) Updates `@typescript-eslint/eslint-plugin` from 8.29.0 to 8.29.1 - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.29.1/packages/eslint-plugin) Updates `@typescript-eslint/parser` from 8.29.0 to 8.29.1 - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.29.1/packages/parser) Updates `typescript` from 5.8.2 to 5.8.3 - [Release notes](https://github.com/microsoft/TypeScript/releases) - [Changelog](https://github.com/microsoft/TypeScript/blob/main/azure-pipelines.release-publish.yml) - [Commits](https://github.com/microsoft/TypeScript/commits) --- updated-dependencies: - dependency-name: "@types/node" dependency-version: 20.17.30 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: all-dependencies - dependency-name: "@typescript-eslint/eslint-plugin" dependency-version: 8.29.1 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: all-dependencies - dependency-name: "@typescript-eslint/parser" dependency-version: 8.29.1 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: all-dependencies - dependency-name: typescript dependency-version: 5.8.3 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: all-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 200 +++++++++++++++++++++++----------------------- package.json | 8 +- 2 files changed, 104 insertions(+), 104 deletions(-) diff --git a/package-lock.json b/package-lock.json index ecbd51ff7..9cde3898d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,15 +25,15 @@ "@types/lodash.throttle": "^4.1.9", "@types/mocha": "^10.0.10", "@types/mock-fs": "^4.13.4", - "@types/node": "^20.17.28", + "@types/node": "^20.17.30", "@types/plist": "^3.0.5", "@types/semver": "^7.7.0", "@types/sinon": "^17.0.4", "@types/sinon-chai": "^3.2.12", "@types/vscode": "^1.88.0", "@types/xml2js": "^0.4.14", - "@typescript-eslint/eslint-plugin": "^8.29.0", - "@typescript-eslint/parser": "^8.29.0", + "@typescript-eslint/eslint-plugin": "^8.29.1", + "@typescript-eslint/parser": "^8.29.1", "@vscode/debugprotocol": "^1.68.0", "@vscode/test-cli": "^0.0.10", "@vscode/test-electron": "^2.4.1", @@ -58,7 +58,7 @@ "source-map-support": "^0.5.21", "strip-ansi": "^6.0.1", "tsx": "^4.19.3", - "typescript": "^5.8.2" + "typescript": "^5.8.3" }, "engines": { "vscode": "^1.88.0" @@ -1152,9 +1152,9 @@ } }, "node_modules/@types/node": { - "version": "20.17.28", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.28.tgz", - "integrity": "sha512-DHlH/fNL6Mho38jTy7/JT7sn2wnXI+wULR6PV4gy4VHLVvnrV/d3pHAMQHhc4gjdLmK2ZiPoMxzp6B3yRajLSQ==", + "version": "20.17.30", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.30.tgz", + "integrity": "sha512-7zf4YyHA+jvBNfVrk2Gtvs6x7E8V+YDW05bNfG2XkWDJfYRXrTiP/DsB2zSYTaHX0bGIujTBQdMVAhb+j7mwpg==", "dev": true, "license": "MIT", "dependencies": { @@ -1222,17 +1222,17 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.29.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.29.0.tgz", - "integrity": "sha512-PAIpk/U7NIS6H7TEtN45SPGLQaHNgB7wSjsQV/8+KYokAb2T/gloOA/Bee2yd4/yKVhPKe5LlaUGhAZk5zmSaQ==", + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.29.1.tgz", + "integrity": "sha512-ba0rr4Wfvg23vERs3eB+P3lfj2E+2g3lhWcCVukUuhtcdUx5lSIFZlGFEBHKr+3zizDa/TvZTptdNHVZWAkSBg==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.29.0", - "@typescript-eslint/type-utils": "8.29.0", - "@typescript-eslint/utils": "8.29.0", - "@typescript-eslint/visitor-keys": "8.29.0", + "@typescript-eslint/scope-manager": "8.29.1", + "@typescript-eslint/type-utils": "8.29.1", + "@typescript-eslint/utils": "8.29.1", + "@typescript-eslint/visitor-keys": "8.29.1", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -1252,16 +1252,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.29.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.29.0.tgz", - "integrity": "sha512-8C0+jlNJOwQso2GapCVWWfW/rzaq7Lbme+vGUFKE31djwNncIpgXD7Cd4weEsDdkoZDjH0lwwr3QDQFuyrMg9g==", + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.29.1.tgz", + "integrity": "sha512-zczrHVEqEaTwh12gWBIJWj8nx+ayDcCJs06yoNMY0kwjMWDM6+kppljY+BxWI06d2Ja+h4+WdufDcwMnnMEWmg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.29.0", - "@typescript-eslint/types": "8.29.0", - "@typescript-eslint/typescript-estree": "8.29.0", - "@typescript-eslint/visitor-keys": "8.29.0", + "@typescript-eslint/scope-manager": "8.29.1", + "@typescript-eslint/types": "8.29.1", + "@typescript-eslint/typescript-estree": "8.29.1", + "@typescript-eslint/visitor-keys": "8.29.1", "debug": "^4.3.4" }, "engines": { @@ -1277,14 +1277,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.29.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.29.0.tgz", - "integrity": "sha512-aO1PVsq7Gm+tcghabUpzEnVSFMCU4/nYIgC2GOatJcllvWfnhrgW0ZEbnTxm36QsikmCN1K/6ZgM7fok2I7xNw==", + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.29.1.tgz", + "integrity": "sha512-2nggXGX5F3YrsGN08pw4XpMLO1Rgtnn4AzTegC2MDesv6q3QaTU5yU7IbS1tf1IwCR0Hv/1EFygLn9ms6LIpDA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.29.0", - "@typescript-eslint/visitor-keys": "8.29.0" + "@typescript-eslint/types": "8.29.1", + "@typescript-eslint/visitor-keys": "8.29.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1295,14 +1295,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.29.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.29.0.tgz", - "integrity": "sha512-ahaWQ42JAOx+NKEf5++WC/ua17q5l+j1GFrbbpVKzFL/tKVc0aYY8rVSYUpUvt2hUP1YBr7mwXzx+E/DfUWI9Q==", + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.29.1.tgz", + "integrity": "sha512-DkDUSDwZVCYN71xA4wzySqqcZsHKic53A4BLqmrWFFpOpNSoxX233lwGu/2135ymTCR04PoKiEEEvN1gFYg4Tw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.29.0", - "@typescript-eslint/utils": "8.29.0", + "@typescript-eslint/typescript-estree": "8.29.1", + "@typescript-eslint/utils": "8.29.1", "debug": "^4.3.4", "ts-api-utils": "^2.0.1" }, @@ -1319,9 +1319,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.29.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.29.0.tgz", - "integrity": "sha512-wcJL/+cOXV+RE3gjCyl/V2G877+2faqvlgtso/ZRbTCnZazh0gXhe+7gbAnfubzN2bNsBtZjDvlh7ero8uIbzg==", + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.29.1.tgz", + "integrity": "sha512-VT7T1PuJF1hpYC3AGm2rCgJBjHL3nc+A/bhOp9sGMKfi5v0WufsX/sHCFBfNTx2F+zA6qBc/PD0/kLRLjdt8mQ==", "dev": true, "license": "MIT", "engines": { @@ -1333,14 +1333,14 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.29.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.29.0.tgz", - "integrity": "sha512-yOfen3jE9ISZR/hHpU/bmNvTtBW1NjRbkSFdZOksL1N+ybPEE7UVGMwqvS6CP022Rp00Sb0tdiIkhSCe6NI8ow==", + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.29.1.tgz", + "integrity": "sha512-l1enRoSaUkQxOQnbi0KPUtqeZkSiFlqrx9/3ns2rEDhGKfTa+88RmXqedC1zmVTOWrLc2e6DEJrTA51C9iLH5g==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.29.0", - "@typescript-eslint/visitor-keys": "8.29.0", + "@typescript-eslint/types": "8.29.1", + "@typescript-eslint/visitor-keys": "8.29.1", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -1386,16 +1386,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.29.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.29.0.tgz", - "integrity": "sha512-gX/A0Mz9Bskm8avSWFcK0gP7cZpbY4AIo6B0hWYFCaIsz750oaiWR4Jr2CI+PQhfW1CpcQr9OlfPS+kMFegjXA==", + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.29.1.tgz", + "integrity": "sha512-QAkFEbytSaB8wnmB+DflhUPz6CLbFWE2SnSCrRMEa+KnXIzDYbpsn++1HGvnfAsUY44doDXmvRkO5shlM/3UfA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.29.0", - "@typescript-eslint/types": "8.29.0", - "@typescript-eslint/typescript-estree": "8.29.0" + "@typescript-eslint/scope-manager": "8.29.1", + "@typescript-eslint/types": "8.29.1", + "@typescript-eslint/typescript-estree": "8.29.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1410,13 +1410,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.29.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.29.0.tgz", - "integrity": "sha512-Sne/pVz8ryR03NFK21VpN88dZ2FdQXOlq3VIklbrTYEt8yXtRFr9tvUhqvCeKjqYk5FSim37sHbooT6vzBTZcg==", + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.29.1.tgz", + "integrity": "sha512-RGLh5CRaUEf02viP5c1Vh1cMGffQscyHe7HPAzGpfmfflFg1wUz2rYxd+OZqwpeypYvZ8UxSxuIpF++fmOzEcg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.29.0", + "@typescript-eslint/types": "8.29.1", "eslint-visitor-keys": "^4.2.0" }, "engines": { @@ -5920,9 +5920,9 @@ } }, "node_modules/typescript": { - "version": "5.8.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz", - "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", "dev": true, "license": "Apache-2.0", "bin": { @@ -6936,9 +6936,9 @@ } }, "@types/node": { - "version": "20.17.28", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.28.tgz", - "integrity": "sha512-DHlH/fNL6Mho38jTy7/JT7sn2wnXI+wULR6PV4gy4VHLVvnrV/d3pHAMQHhc4gjdLmK2ZiPoMxzp6B3yRajLSQ==", + "version": "20.17.30", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.30.tgz", + "integrity": "sha512-7zf4YyHA+jvBNfVrk2Gtvs6x7E8V+YDW05bNfG2XkWDJfYRXrTiP/DsB2zSYTaHX0bGIujTBQdMVAhb+j7mwpg==", "dev": true, "requires": { "undici-types": "~6.19.2" @@ -7001,16 +7001,16 @@ } }, "@typescript-eslint/eslint-plugin": { - "version": "8.29.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.29.0.tgz", - "integrity": "sha512-PAIpk/U7NIS6H7TEtN45SPGLQaHNgB7wSjsQV/8+KYokAb2T/gloOA/Bee2yd4/yKVhPKe5LlaUGhAZk5zmSaQ==", + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.29.1.tgz", + "integrity": "sha512-ba0rr4Wfvg23vERs3eB+P3lfj2E+2g3lhWcCVukUuhtcdUx5lSIFZlGFEBHKr+3zizDa/TvZTptdNHVZWAkSBg==", "dev": true, "requires": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.29.0", - "@typescript-eslint/type-utils": "8.29.0", - "@typescript-eslint/utils": "8.29.0", - "@typescript-eslint/visitor-keys": "8.29.0", + "@typescript-eslint/scope-manager": "8.29.1", + "@typescript-eslint/type-utils": "8.29.1", + "@typescript-eslint/utils": "8.29.1", + "@typescript-eslint/visitor-keys": "8.29.1", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -7018,54 +7018,54 @@ } }, "@typescript-eslint/parser": { - "version": "8.29.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.29.0.tgz", - "integrity": "sha512-8C0+jlNJOwQso2GapCVWWfW/rzaq7Lbme+vGUFKE31djwNncIpgXD7Cd4weEsDdkoZDjH0lwwr3QDQFuyrMg9g==", + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.29.1.tgz", + "integrity": "sha512-zczrHVEqEaTwh12gWBIJWj8nx+ayDcCJs06yoNMY0kwjMWDM6+kppljY+BxWI06d2Ja+h4+WdufDcwMnnMEWmg==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "8.29.0", - "@typescript-eslint/types": "8.29.0", - "@typescript-eslint/typescript-estree": "8.29.0", - "@typescript-eslint/visitor-keys": "8.29.0", + "@typescript-eslint/scope-manager": "8.29.1", + "@typescript-eslint/types": "8.29.1", + "@typescript-eslint/typescript-estree": "8.29.1", + "@typescript-eslint/visitor-keys": "8.29.1", "debug": "^4.3.4" } }, "@typescript-eslint/scope-manager": { - "version": "8.29.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.29.0.tgz", - "integrity": "sha512-aO1PVsq7Gm+tcghabUpzEnVSFMCU4/nYIgC2GOatJcllvWfnhrgW0ZEbnTxm36QsikmCN1K/6ZgM7fok2I7xNw==", + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.29.1.tgz", + "integrity": "sha512-2nggXGX5F3YrsGN08pw4XpMLO1Rgtnn4AzTegC2MDesv6q3QaTU5yU7IbS1tf1IwCR0Hv/1EFygLn9ms6LIpDA==", "dev": true, "requires": { - "@typescript-eslint/types": "8.29.0", - "@typescript-eslint/visitor-keys": "8.29.0" + "@typescript-eslint/types": "8.29.1", + "@typescript-eslint/visitor-keys": "8.29.1" } }, "@typescript-eslint/type-utils": { - "version": "8.29.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.29.0.tgz", - "integrity": "sha512-ahaWQ42JAOx+NKEf5++WC/ua17q5l+j1GFrbbpVKzFL/tKVc0aYY8rVSYUpUvt2hUP1YBr7mwXzx+E/DfUWI9Q==", + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.29.1.tgz", + "integrity": "sha512-DkDUSDwZVCYN71xA4wzySqqcZsHKic53A4BLqmrWFFpOpNSoxX233lwGu/2135ymTCR04PoKiEEEvN1gFYg4Tw==", "dev": true, "requires": { - "@typescript-eslint/typescript-estree": "8.29.0", - "@typescript-eslint/utils": "8.29.0", + "@typescript-eslint/typescript-estree": "8.29.1", + "@typescript-eslint/utils": "8.29.1", "debug": "^4.3.4", "ts-api-utils": "^2.0.1" } }, "@typescript-eslint/types": { - "version": "8.29.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.29.0.tgz", - "integrity": "sha512-wcJL/+cOXV+RE3gjCyl/V2G877+2faqvlgtso/ZRbTCnZazh0gXhe+7gbAnfubzN2bNsBtZjDvlh7ero8uIbzg==", + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.29.1.tgz", + "integrity": "sha512-VT7T1PuJF1hpYC3AGm2rCgJBjHL3nc+A/bhOp9sGMKfi5v0WufsX/sHCFBfNTx2F+zA6qBc/PD0/kLRLjdt8mQ==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "8.29.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.29.0.tgz", - "integrity": "sha512-yOfen3jE9ISZR/hHpU/bmNvTtBW1NjRbkSFdZOksL1N+ybPEE7UVGMwqvS6CP022Rp00Sb0tdiIkhSCe6NI8ow==", + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.29.1.tgz", + "integrity": "sha512-l1enRoSaUkQxOQnbi0KPUtqeZkSiFlqrx9/3ns2rEDhGKfTa+88RmXqedC1zmVTOWrLc2e6DEJrTA51C9iLH5g==", "dev": true, "requires": { - "@typescript-eslint/types": "8.29.0", - "@typescript-eslint/visitor-keys": "8.29.0", + "@typescript-eslint/types": "8.29.1", + "@typescript-eslint/visitor-keys": "8.29.1", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -7095,24 +7095,24 @@ } }, "@typescript-eslint/utils": { - "version": "8.29.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.29.0.tgz", - "integrity": "sha512-gX/A0Mz9Bskm8avSWFcK0gP7cZpbY4AIo6B0hWYFCaIsz750oaiWR4Jr2CI+PQhfW1CpcQr9OlfPS+kMFegjXA==", + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.29.1.tgz", + "integrity": "sha512-QAkFEbytSaB8wnmB+DflhUPz6CLbFWE2SnSCrRMEa+KnXIzDYbpsn++1HGvnfAsUY44doDXmvRkO5shlM/3UfA==", "dev": true, "requires": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.29.0", - "@typescript-eslint/types": "8.29.0", - "@typescript-eslint/typescript-estree": "8.29.0" + "@typescript-eslint/scope-manager": "8.29.1", + "@typescript-eslint/types": "8.29.1", + "@typescript-eslint/typescript-estree": "8.29.1" } }, "@typescript-eslint/visitor-keys": { - "version": "8.29.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.29.0.tgz", - "integrity": "sha512-Sne/pVz8ryR03NFK21VpN88dZ2FdQXOlq3VIklbrTYEt8yXtRFr9tvUhqvCeKjqYk5FSim37sHbooT6vzBTZcg==", + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.29.1.tgz", + "integrity": "sha512-RGLh5CRaUEf02viP5c1Vh1cMGffQscyHe7HPAzGpfmfflFg1wUz2rYxd+OZqwpeypYvZ8UxSxuIpF++fmOzEcg==", "dev": true, "requires": { - "@typescript-eslint/types": "8.29.0", + "@typescript-eslint/types": "8.29.1", "eslint-visitor-keys": "^4.2.0" }, "dependencies": { @@ -10361,9 +10361,9 @@ } }, "typescript": { - "version": "5.8.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz", - "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", "dev": true }, "uc.micro": { diff --git a/package.json b/package.json index 22cd674af..a8943fc7c 100644 --- a/package.json +++ b/package.json @@ -1681,15 +1681,15 @@ "@types/lodash.debounce": "^4.0.9", "@types/mocha": "^10.0.10", "@types/mock-fs": "^4.13.4", - "@types/node": "^20.17.28", + "@types/node": "^20.17.30", "@types/plist": "^3.0.5", "@types/semver": "^7.7.0", "@types/sinon": "^17.0.4", "@types/sinon-chai": "^3.2.12", "@types/vscode": "^1.88.0", "@types/xml2js": "^0.4.14", - "@typescript-eslint/eslint-plugin": "^8.29.0", - "@typescript-eslint/parser": "^8.29.0", + "@typescript-eslint/eslint-plugin": "^8.29.1", + "@typescript-eslint/parser": "^8.29.1", "@vscode/debugprotocol": "^1.68.0", "@vscode/test-cli": "^0.0.10", "@vscode/test-electron": "^2.4.1", @@ -1714,7 +1714,7 @@ "source-map-support": "^0.5.21", "strip-ansi": "^6.0.1", "tsx": "^4.19.3", - "typescript": "^5.8.2" + "typescript": "^5.8.3" }, "dependencies": { "@vscode/codicons": "^0.0.36", From d6864b31df8d3ef2eabbfa349d2274d4707154cb Mon Sep 17 00:00:00 2001 From: Paul LeMarquand Date: Fri, 11 Apr 2025 12:03:57 -0400 Subject: [PATCH 84/86] Use latest Windows SDK to run tests on Swift >=6.1 (#1497) Determine the version of Swift we're using in Windows CI and only use the older Windows SDK if we're using a version lower than Swift 6.1. --- scripts/test_windows.ps1 | 112 +++++++++++++++++++++++++-------------- 1 file changed, 72 insertions(+), 40 deletions(-) diff --git a/scripts/test_windows.ps1 b/scripts/test_windows.ps1 index 8bc6211ba..06e93e25e 100644 --- a/scripts/test_windows.ps1 +++ b/scripts/test_windows.ps1 @@ -12,53 +12,85 @@ ## ##===----------------------------------------------------------------------===## -# In newer Visual C++ Tools they've added compiler intrinsics headers in wchar.h -# that end up creating a cyclic dependency between the `ucrt` and compiler intrinsics modules. -# Newer versions of swift (6.2) have a fixed modulemap that resolves the issue: https://github.com/swiftlang/swift/pull/79751 -$windowsSdkVersion = "10.0.22000.0" -$vcToolsVersion = "14.43.34808" - -# As a workaround we can pin the tools/SDK versions to older versions that are present in the GH Actions Windows image. -# In the future we may only want to apply this workaround to older versions of Swift that don't have the fixed module map. -$jsonFilePath = "./assets/test/.vscode/settings.json" -try { - $jsonContent = Get-Content -Raw -Path $jsonFilePath | ConvertFrom-Json -} catch { - Write-Host "Invalid JSON content in $jsonFilePath" - exit 1 -} -if ($jsonContent.PSObject.Properties['swift.buildArguments']) { - $jsonContent.PSObject.Properties.Remove('swift.buildArguments') -} +function Update-SwiftBuildAndPackageArguments { + param ( + [string]$jsonFilePath = "./assets/test/.vscode/settings.json", + [string]$windowsSdkVersion = "10.0.22000.0", + [string]$vcToolsVersion = "14.43.34808" + ) + + $windowsSdkRoot = "C:\Program Files (x86)\Windows Kits\10\" + + try { + $jsonContent = Get-Content -Raw -Path $jsonFilePath | ConvertFrom-Json + } catch { + Write-Host "Invalid JSON content in $jsonFilePath" + exit 1 + } + + if ($jsonContent.PSObject.Properties['swift.buildArguments']) { + $jsonContent.PSObject.Properties.Remove('swift.buildArguments') + } + + $jsonContent | Add-Member -MemberType NoteProperty -Name "swift.buildArguments" -Value @( + "-Xbuild-tools-swiftc", "-windows-sdk-root", "-Xbuild-tools-swiftc", $windowsSdkRoot, + "-Xbuild-tools-swiftc", "-windows-sdk-version", "-Xbuild-tools-swiftc", $windowsSdkVersion, + "-Xbuild-tools-swiftc", "-visualc-tools-version", "-Xbuild-tools-swiftc", $vcToolsVersion, + "-Xswiftc", "-windows-sdk-root", "-Xswiftc", $windowsSdkRoot, + "-Xswiftc", "-windows-sdk-version", "-Xswiftc", $windowsSdkVersion, + "-Xswiftc", "-visualc-tools-version", "-Xswiftc", $vcToolsVersion + ) + + if ($jsonContent.PSObject.Properties['swift.packageArguments']) { + $jsonContent.PSObject.Properties.Remove('swift.packageArguments') + } -$windowsSdkRoot = "C:\Program Files (x86)\Windows Kits\10\" + $jsonContent | Add-Member -MemberType NoteProperty -Name "swift.packageArguments" -Value @( + "-Xbuild-tools-swiftc", "-windows-sdk-root", "-Xbuild-tools-swiftc", $windowsSdkRoot, + "-Xbuild-tools-swiftc", "-windows-sdk-version", "-Xbuild-tools-swiftc", $windowsSdkVersion, + "-Xbuild-tools-swiftc", "-visualc-tools-version", "-Xbuild-tools-swiftc", $vcToolsVersion, + "-Xswiftc", "-windows-sdk-root", "-Xswiftc", $windowsSdkRoot, + "-Xswiftc", "-windows-sdk-version", "-Xswiftc", $windowsSdkVersion, + "-Xswiftc", "-visualc-tools-version", "-Xswiftc", $vcToolsVersion + ) -$jsonContent | Add-Member -MemberType NoteProperty -Name "swift.buildArguments" -Value @( - "-Xbuild-tools-swiftc", "-windows-sdk-root", "-Xbuild-tools-swiftc", $windowsSdkRoot, - "-Xbuild-tools-swiftc", "-windows-sdk-version", "-Xbuild-tools-swiftc", $windowsSdkVersion, - "-Xbuild-tools-swiftc", "-visualc-tools-version", "-Xbuild-tools-swiftc", $vcToolsVersion, - "-Xswiftc", "-windows-sdk-root", "-Xswiftc", $windowsSdkRoot, - "-Xswiftc", "-windows-sdk-version", "-Xswiftc", $windowsSdkVersion, - "-Xswiftc", "-visualc-tools-version", "-Xswiftc", $vcToolsVersion -) + $jsonContent | ConvertTo-Json -Depth 32 | Set-Content -Path $jsonFilePath -if ($jsonContent.PSObject.Properties['swift.packageArguments']) { - $jsonContent.PSObject.Properties.Remove('swift.packageArguments') + Write-Host "Contents of ${jsonFilePath}:" + Get-Content -Path $jsonFilePath } -$jsonContent | Add-Member -MemberType NoteProperty -Name "swift.packageArguments" -Value @( - "-Xbuild-tools-swiftc", "-windows-sdk-root", "-Xbuild-tools-swiftc", $windowsSdkRoot, - "-Xbuild-tools-swiftc", "-windows-sdk-version", "-Xbuild-tools-swiftc", $windowsSdkVersion, - "-Xbuild-tools-swiftc", "-visualc-tools-version", "-Xbuild-tools-swiftc", $vcToolsVersion, - "-Xswiftc", "-windows-sdk-root", "-Xswiftc", $windowsSdkRoot, - "-Xswiftc", "-windows-sdk-version", "-Xswiftc", $windowsSdkVersion, - "-Xswiftc", "-visualc-tools-version", "-Xswiftc", $vcToolsVersion -) +$swiftVersionOutput = & swift --version +if ($LASTEXITCODE -ne 0) { + Write-Host "Failed to execute 'swift --version'" + exit 1 +} + +Write-Host "Swift version:" +Write-Host "$swiftVersionOutput" + +$versionLine = $swiftVersionOutput[0] +if ($versionLine -match "Swift version (\d+)\.(\d+)") { + Write-Host "Matched Swift version: $($matches[0]), $($matches[1]), $($matches[2])" -$jsonContent | ConvertTo-Json -Depth 32 | Set-Content -Path $jsonFilePath + $majorVersion = [int]$matches[1] + $minorVersion = [int]$matches[2] -Write-Host "Contents of ${jsonFilePath}:" -Get-Content -Path $jsonFilePath + # In newer Visual C++ Tools they've added compiler intrinsics headers in wchar.h + # that end up creating a cyclic dependency between the `ucrt` and compiler intrinsics modules. + + # Newer versions of swift (>=6.1) have a fixed modulemap that resolves the issue: https://github.com/swiftlang/swift/pull/79751 + # As a workaround we can pin the tools/SDK versions to older versions that are present in the GH Actions Windows image. + # In the future we may only want to apply this workaround to older versions of Swift that don't have the fixed module map. + if ($majorVersion -lt 6 -or ($majorVersion -eq 6 -and $minorVersion -lt 1)) { + Write-Host "Swift version is < 6.1, injecting windows SDK build arguments" + Update-SwiftBuildAndPackageArguments + } +} else { + Write-Host "Match failed for output: `"$versionLine`"" + Write-Host "Unable to determine Swift version" + exit 1 +} npm ci -ignore-script node-pty npm run lint From aff48d1b707693240475712717084697ee94b55c Mon Sep 17 00:00:00 2001 From: David Cummings Date: Tue, 15 Apr 2025 10:00:26 -0400 Subject: [PATCH 85/86] Fix URL to docs from README.md (#1501) Docs are now published, but to a slightly different URL. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 872434390..c3dd6b31f 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ This extension adds language support for Swift to Visual Studio Code, providing # Documentation -The official documentation for this extension is available at [vscode-swift](https://www.swift.org/vscode/documentation/vscode) +The official documentation for this extension is available at [vscode-swift](https://docs.swift.org/vscode/documentation/userdocs) This extension uses [SourceKit LSP](https://github.com/apple/sourcekit-lsp) for the [language server](https://microsoft.github.io/language-server-protocol/overviews/lsp/overview/), which powers code completion. It also has a dependency on [LLDB DAP](https://marketplace.visualstudio.com/items?itemName=llvm-vs-code-extensions.lldb-dap) for debugging. From 96a8904cc64cce2d9a51d7cdebd172f4d7ffbf9b Mon Sep 17 00:00:00 2001 From: Matthew Bastien Date: Tue, 15 Apr 2025 09:07:33 -0500 Subject: [PATCH 86/86] Release activities for 2.2.0 (#1483) * release activities for 2.2.0 * Update CHANGELOG.md --------- Co-authored-by: Paul LeMarquand --- CHANGELOG.md | 23 +++++++++++++++++++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e59f7967e..c031228e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,28 @@ # Changelog +## 2.2.0 - 2025-04-07 + +### Added + +- Convert the Dependencies View into the Project Panel to view all aspects of your Swift project ([#1382](https://github.com/swiftlang/vscode-swift/pull/1382)) +- Use the LLDB DAP extension to debug when using a Swift 6 toolchain ([#1384](https://github.com/swiftlang/vscode-swift/pull/1384)) +- Added run and debug buttons to Swift editors ([#1378](https://github.com/swiftlang/vscode-swift/pull/1378)) +- Educational notes from compiler diagnostics can be viewed directly in VS Code ([#1423](https://github.com/swiftlang/vscode-swift/pull/1423)) +- Swift settings now support variable substitutions ([#1439](https://github.com/swiftlang/vscode-swift/pull/1439)) +- SwiftPM plugin tasks are now configurable via settings ([#1409](https://github.com/swiftlang/vscode-swift/pull/1409)) +- Added the `swift.scriptSwiftLanguageVersion` setting to choose Swift language mode when running scripts (thanks @louisunlimited) ([#1476](https://github.com/swiftlang/vscode-swift/pull/1476)) + +### Fixed + +- Prevent duplicate reload extension notifications from appearing ([#1473](https://github.com/swiftlang/vscode-swift/pull/1473)) +- "Actual" and "Expected" values are shown in the right order on test failure ([#1444](https://github.com/swiftlang/vscode-swift/issues/1444)) +- Correctly set the `DEVELOPER_DIR` environment variable when selecting between two Xcode installs ([#1433](https://github.com/swiftlang/vscode-swift/pull/1433)) +- Prompt to reload the extension when swiftEnvironmentVariables is changed ([#1430](https://github.com/swiftlang/vscode-swift/pull/1430)) +- Search for Swift packages in sub-folders of the workspace ([#1425](https://github.com/swiftlang/vscode-swift/pull/1425)) +- Fix missing test result output on Linux when using print ([#1401](https://github.com/swiftlang/vscode-swift/pull/1401)) +- Stop all actively running tests when stop button is pressed ([#1391](https://github.com/swiftlang/vscode-swift/pull/1391)) +- Properly set `--swift-sdk` when using `Swift: Select Target Platform` on Swift 6.1 ([#1390](https://github.com/swiftlang/vscode-swift/pull/1390)) + ## 2.0.2 - 2025-02-20 ### Fixed diff --git a/package-lock.json b/package-lock.json index 9cde3898d..bff572f58 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "swift-vscode", - "version": "2.0.2", + "version": "2.2.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "swift-vscode", - "version": "2.0.2", + "version": "2.2.0", "hasInstallScript": true, "dependencies": { "@vscode/codicons": "^0.0.36", diff --git a/package.json b/package.json index a8943fc7c..30c25a97e 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "swift-vscode", "displayName": "Swift", "description": "Swift Language Support for Visual Studio Code.", - "version": "2.0.2", + "version": "2.2.0", "publisher": "swiftlang", "icon": "icon.png", "repository": {