From 981177fa1b42c7e4049ad0aafef46a415e4b6033 Mon Sep 17 00:00:00 2001 From: Matt Masson Date: Mon, 8 Sep 2025 14:28:16 -0400 Subject: [PATCH 01/42] update action config --- .github/workflows/pr-gated.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pr-gated.yml b/.github/workflows/pr-gated.yml index a96cb277..bcd3ab8e 100644 --- a/.github/workflows/pr-gated.yml +++ b/.github/workflows/pr-gated.yml @@ -8,11 +8,11 @@ jobs: runs-on: windows-latest steps: - name: checkout - uses: actions/checkout@v4.1.2 + uses: actions/checkout@v5 - name: setup node - uses: actions/setup-node@v4.0.2 + uses: actions/setup-node@v5 with: - node-version: "18.17" + node-version-file: "package.json" - run: node -v - run: npm ci - run: npm audit From 67fb09aed417706a2cdc620a24fb155c7231eb12 Mon Sep 17 00:00:00 2001 From: Matt Masson Date: Mon, 8 Sep 2025 14:38:12 -0400 Subject: [PATCH 02/42] update node and typescript --- package-lock.json | 2953 ++++++++++------- package.json | 43 +- .../inspectTypeTBinOpExpression.ts | 3 +- src/test/testConstants.ts | 2 +- tsconfig.json | 14 +- 5 files changed, 1744 insertions(+), 1271 deletions(-) diff --git a/package-lock.json b/package-lock.json index eb2dba41..e28b3cdd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,107 +11,228 @@ "dependencies": { "@microsoft/powerquery-formatter": "0.3.17", "@microsoft/powerquery-parser": "0.18.1", - "vscode-languageserver-textdocument": "1.0.4", - "vscode-languageserver-types": "3.17.1" + "vscode-languageserver-textdocument": "1.0.12", + "vscode-languageserver-types": "3.17.5" }, "devDependencies": { - "@types/chai": "4.3.1", - "@types/mocha": "9.1.1", - "@types/node": "17.0.33", - "@typescript-eslint/eslint-plugin": "5.24.0", - "@typescript-eslint/parser": "5.24.0", - "chai": "4.3.6", - "eslint": "8.15.0", - "eslint-config-prettier": "8.5.0", - "eslint-plugin-prettier": "4.0.0", - "eslint-plugin-promise": "6.0.0", - "eslint-plugin-security": "1.5.0", + "@types/chai": "^4.3.20", + "@types/mocha": "^10.0.8", + "@types/node": "^22.7.3", + "@typescript-eslint/eslint-plugin": "^8.43.0", + "@typescript-eslint/parser": "^8.43.0", + "chai": "^4.5.0", + "cross-env": "^7.0.3", + "eslint": "^8.57.1", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-prettier": "^5.2.1", + "eslint-plugin-promise": "^7.1.0", + "eslint-plugin-security": "^3.0.1", "mocha": "^10.8.2", - "mocha-junit-reporter": "2.0.2", - "mocha-multi-reporters": "1.5.1", - "prettier": "2.6.2", - "ts-loader": "9.3.0", - "ts-node": "10.7.0", - "typescript": "4.6.4" + "mocha-junit-reporter": "^2.2.1", + "mocha-multi-reporters": "^1.5.1", + "prettier": "^3.3.3", + "ts-loader": "^9.5.1", + "ts-node": "^10.9.2", + "typescript": "^5.9.2" }, "engines": { - "node": ">=16.13.1" + "node": ">=22.0.0" } }, - "node_modules/@cspotcode/source-map-consumer": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz", - "integrity": "sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==", + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, "engines": { - "node": ">= 12" + "node": ">=12" } }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz", - "integrity": "sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==", + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", + "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", "dev": true, + "license": "MIT", "dependencies": { - "@cspotcode/source-map-consumer": "0.8.0" + "eslint-visitor-keys": "^3.4.3" }, "engines": { - "node": ">=12" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, "node_modules/@eslint/eslintrc": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.2.3.tgz", - "integrity": "sha512-uGo44hIwoLGNyduRpjdEpovcbMdd+Nv7amtmJxnKmI8xj6yd5LncmSwDa5NgX/41lIFJtkjD6YdVfgEzPfJ5UA==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", "dev": true, + "license": "MIT", "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.3.2", - "globals": "^13.9.0", + "espree": "^9.6.0", + "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "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, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@eslint/eslintrc/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, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "dev": true, + "license": "MIT", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, "node_modules/@humanwhocodes/config-array": { - "version": "0.9.2", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.2.tgz", - "integrity": "sha512-UXOuFCGcwciWckOpmfKDq/GyhlTf9pN/BzG//x8p8zTOFEcGuA68ANXheFS0AGvy3qgZqLBUkMs7hqzqCKOVwA==", + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", - "minimatch": "^3.0.4" + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" }, "engines": { "node": ">=10.10.0" } }, + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "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, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@humanwhocodes/config-array/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, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", - "dev": true + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true, + "license": "BSD-3-Clause" }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", - "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "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", "peer": true, "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping/node_modules/@jridgewell/trace-mapping": { + "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", + "peer": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, "node_modules/@jridgewell/resolve-uri": { @@ -119,48 +240,51 @@ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "dev": true, - "peer": true, + "license": "MIT", "engines": { "node": ">=6.0.0" } }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "node_modules/@jridgewell/source-map": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", "dev": true, + "license": "MIT", "peer": true, - "engines": { - "node": ">=6.0.0" + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" } }, - "node_modules/@jridgewell/source-map": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", - "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", + "node_modules/@jridgewell/source-map/node_modules/@jridgewell/trace-mapping": { + "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", "peer": true, "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "dev": true, - "peer": 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.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" } }, "node_modules/@microsoft/powerquery-formatter": { @@ -193,6 +317,7 @@ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", "dev": true, + "license": "MIT", "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" @@ -206,6 +331,7 @@ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", "dev": true, + "license": "MIT", "engines": { "node": ">= 8" } @@ -215,6 +341,7 @@ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", "dev": true, + "license": "MIT", "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" @@ -223,171 +350,256 @@ "node": ">= 8" } }, + "node_modules/@pkgr/core": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", + "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/pkgr" + } + }, "node_modules/@tsconfig/node10": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", - "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==", - "dev": true + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "dev": true, + "license": "MIT" }, "node_modules/@tsconfig/node12": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz", - "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==", - "dev": true + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, + "license": "MIT" }, "node_modules/@tsconfig/node14": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz", - "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==", - "dev": true + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true, + "license": "MIT" }, "node_modules/@tsconfig/node16": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz", - "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==", - "dev": true + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true, + "license": "MIT" }, "node_modules/@types/chai": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.1.tgz", - "integrity": "sha512-/zPMqDkzSZ8t3VtxOa4KPq7uzzW978M9Tvh+j7GHKuo6k6GTLxPJ4J5gE5cjfJ26pnXst0N5Hax8Sr0T2Mi9zQ==", - "dev": true + "version": "4.3.20", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.20.tgz", + "integrity": "sha512-/pC9HAB5I/xMlc5FP77qjCnI16ChlJfW0tGa0IUcFn38VJrTV6DeZ60NU5KZBtaOZqjdpwTWohz5HU1RrhiYxQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/eslint": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", + "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } }, "node_modules/@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "dev": true, + "license": "MIT", "peer": true }, "node_modules/@types/json-schema": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", - "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", - "dev": true + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT", + "peer": true }, "node_modules/@types/mocha": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-9.1.1.tgz", - "integrity": "sha512-Z61JK7DKDtdKTWwLeElSEBcWGRLY8g95ic5FoQqI9CMx0ns/Ghep3B4DfcEimiKMvtamNVULVNKEsiwV3aQmXw==", - "dev": true + "version": "10.0.10", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.10.tgz", + "integrity": "sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q==", + "dev": true, + "license": "MIT" }, "node_modules/@types/node": { - "version": "17.0.33", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.33.tgz", - "integrity": "sha512-miWq2m2FiQZmaHfdZNcbpp9PuXg34W5JZ5CrJ/BaS70VuhoJENBEQybeiYSaPBRNq6KQGnjfEnc/F3PN++D+XQ==", - "dev": true + "version": "22.18.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.1.tgz", + "integrity": "sha512-rzSDyhn4cYznVG+PCzGe1lwuMYJrcBS1fc3JqSa2PvtABwWo+dZ1ij5OVok3tqfpEBCBoaR4d7upFJk73HRJDw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.24.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.24.0.tgz", - "integrity": "sha512-6bqFGk6wa9+6RrU++eLknKyDqXU1Oc8nyoLu5a1fU17PNRJd9UBr56rMF7c4DRaRtnarlkQ4jwxUbvBo8cNlpw==", + "version": "8.43.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.43.0.tgz", + "integrity": "sha512-8tg+gt7ENL7KewsKMKDHXR1vm8tt9eMxjJBYINf6swonlWgkYn5NwyIgXpbbDxTNU5DgpDFfj95prcTq2clIQQ==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "5.24.0", - "@typescript-eslint/type-utils": "5.24.0", - "@typescript-eslint/utils": "5.24.0", - "debug": "^4.3.4", - "functional-red-black-tree": "^1.0.1", - "ignore": "^5.2.0", - "regexpp": "^3.2.0", - "semver": "^7.3.7", - "tsutils": "^3.21.0" + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.43.0", + "@typescript-eslint/type-utils": "8.43.0", + "@typescript-eslint/utils": "8.43.0", + "@typescript-eslint/visitor-keys": "8.43.0", + "graphemer": "^1.4.0", + "ignore": "^7.0.0", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.1.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^5.0.0", - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "@typescript-eslint/parser": "^8.43.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/parser": { - "version": "5.24.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.24.0.tgz", - "integrity": "sha512-4q29C6xFYZ5B2CXqSBBdcS0lPyfM9M09DoQLtHS5kf+WbpV8pBBhHDLNhXfgyVwFnhrhYzOu7xmg02DzxeF2Uw==", + "version": "8.43.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.43.0.tgz", + "integrity": "sha512-B7RIQiTsCBBmY+yW4+ILd6mF5h1FUwJsVvpqkrgpszYifetQ2Ke+Z4u6aZh0CblkUGIdR59iYVyXqqZGkZ3aBw==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "5.24.0", - "@typescript-eslint/types": "5.24.0", - "@typescript-eslint/typescript-estree": "5.24.0", + "@typescript-eslint/scope-manager": "8.43.0", + "@typescript-eslint/types": "8.43.0", + "@typescript-eslint/typescript-estree": "8.43.0", + "@typescript-eslint/visitor-keys": "8.43.0", "debug": "^4.3.4" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.43.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.43.0.tgz", + "integrity": "sha512-htB/+D/BIGoNTQYffZw4uM4NzzuolCoaA/BusuSIcC8YjmBYQioew5VUZAYdAETPjeed0hqCaW7EHg+Robq8uw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.43.0", + "@typescript-eslint/types": "^8.43.0", + "debug": "^4.3.4" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "5.24.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.24.0.tgz", - "integrity": "sha512-WpMWipcDzGmMzdT7NtTjRXFabx10WleLUGrJpuJLGaxSqpcyq5ACpKSD5VE40h2nz3melQ91aP4Du7lh9FliCA==", + "version": "8.43.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.43.0.tgz", + "integrity": "sha512-daSWlQ87ZhsjrbMLvpuuMAt3y4ba57AuvadcR7f3nl8eS3BjRc8L9VLxFLk92RL5xdXOg6IQ+qKjjqNEimGuAg==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "5.24.0", - "@typescript-eslint/visitor-keys": "5.24.0" + "@typescript-eslint/types": "8.43.0", + "@typescript-eslint/visitor-keys": "8.43.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.43.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.43.0.tgz", + "integrity": "sha512-ALC2prjZcj2YqqL5X/bwWQmHA2em6/94GcbB/KKu5SX3EBDOsqztmmX1kMkvAJHzxk7TazKzJfFiEIagNV3qEA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/type-utils": { - "version": "5.24.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.24.0.tgz", - "integrity": "sha512-uGi+sQiM6E5CeCZYBXiaIvIChBXru4LZ1tMoeKbh1Lze+8BO9syUG07594C4lvN2YPT4KVeIupOJkVI+9/DAmQ==", + "version": "8.43.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.43.0.tgz", + "integrity": "sha512-qaH1uLBpBuBBuRf8c1mLJ6swOfzCXryhKND04Igr4pckzSEW9JX5Aw9AgW00kwfjWJF0kk0ps9ExKTfvXfw4Qg==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/utils": "5.24.0", + "@typescript-eslint/types": "8.43.0", + "@typescript-eslint/typescript-estree": "8.43.0", + "@typescript-eslint/utils": "8.43.0", "debug": "^4.3.4", - "tsutils": "^3.21.0" + "ts-api-utils": "^2.1.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "*" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/types": { - "version": "5.24.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.24.0.tgz", - "integrity": "sha512-Tpg1c3shTDgTmZd3qdUyd+16r/pGmVaVEbLs+ufuWP0EruVbUiEOmpBBQxBb9a8iPRxi8Rb2oiwOxuZJzSq11A==", + "version": "8.43.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.43.0.tgz", + "integrity": "sha512-vQ2FZaxJpydjSZJKiSW/LJsabFFvV7KgLC5DiLhkBcykhQj8iK9BOaDmQt74nnKdLvceM5xmhaTF+pLekrxEkw==", "dev": true, + "license": "MIT", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", @@ -395,231 +607,269 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.24.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.24.0.tgz", - "integrity": "sha512-zcor6vQkQmZAQfebSPVwUk/FD+CvnsnlfKXYeQDsWXRF+t7SBPmIfNia/wQxCSeu1h1JIjwV2i9f5/DdSp/uDw==", + "version": "8.43.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.43.0.tgz", + "integrity": "sha512-7Vv6zlAhPb+cvEpP06WXXy/ZByph9iL6BQRBDj4kmBsW98AqEeQHlj/13X+sZOrKSo9/rNKH4Ul4f6EICREFdw==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "5.24.0", - "@typescript-eslint/visitor-keys": "5.24.0", + "@typescript-eslint/project-service": "8.43.0", + "@typescript-eslint/tsconfig-utils": "8.43.0", + "@typescript-eslint/types": "8.43.0", + "@typescript-eslint/visitor-keys": "8.43.0", "debug": "^4.3.4", - "globby": "^11.1.0", + "fast-glob": "^3.3.2", "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.1.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/utils": { - "version": "5.24.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.24.0.tgz", - "integrity": "sha512-K05sbWoeCBJH8KXu6hetBJ+ukG0k2u2KlgD3bN+v+oBKm8adJqVHpSSLHNzqyuv0Lh4GVSAUgZ5lB4icmPmWLw==", + "version": "8.43.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.43.0.tgz", + "integrity": "sha512-S1/tEmkUeeswxd0GGcnwuVQPFWo8NzZTOMxCvw8BX7OMxnNae+i8Tm7REQen/SwUIPoPqfKn7EaZ+YLpiB3k9g==", "dev": true, + "license": "MIT", "dependencies": { - "@types/json-schema": "^7.0.9", - "@typescript-eslint/scope-manager": "5.24.0", - "@typescript-eslint/types": "5.24.0", - "@typescript-eslint/typescript-estree": "5.24.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^3.0.0" + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.43.0", + "@typescript-eslint/types": "8.43.0", + "@typescript-eslint/typescript-estree": "8.43.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.24.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.24.0.tgz", - "integrity": "sha512-qzGwSXMyMnogcAo+/2fU+jhlPPVMXlIH2PeAonIKjJSoDKl1+lJVvG5Z5Oud36yU0TWK2cs1p/FaSN5J2OUFYA==", + "version": "8.43.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.43.0.tgz", + "integrity": "sha512-T+S1KqRD4sg/bHfLwrpF/K3gQLBM1n7Rp7OjjikjTEssI2YJzQpi5WXoynOaQ93ERIuq3O8RBTOUYDKszUCEHw==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "5.24.0", - "eslint-visitor-keys": "^3.3.0" + "@typescript-eslint/types": "8.43.0", + "eslint-visitor-keys": "^4.2.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "dev": true, + "license": "ISC" + }, "node_modules/@webassemblyjs/ast": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", - "integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "@webassemblyjs/helper-numbers": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6" + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" } }, "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", - "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", "dev": true, + "license": "MIT", "peer": true }, "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", - "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", "dev": true, + "license": "MIT", "peer": true }, "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz", - "integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", "dev": true, + "license": "MIT", "peer": true }, "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", - "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.11.6", - "@webassemblyjs/helper-api-error": "1.11.6", + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", "@xtuc/long": "4.2.2" } }, "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", - "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", "dev": true, + "license": "MIT", "peer": true }, "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz", - "integrity": "sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/wasm-gen": "1.12.1" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" } }, "node_modules/@webassemblyjs/ieee754": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", - "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "@xtuc/ieee754": "^1.2.0" } }, "node_modules/@webassemblyjs/leb128": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", - "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", "dev": true, + "license": "Apache-2.0", "peer": true, "dependencies": { "@xtuc/long": "4.2.2" } }, "node_modules/@webassemblyjs/utf8": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", - "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", "dev": true, + "license": "MIT", "peer": true }, "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz", - "integrity": "sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/helper-wasm-section": "1.12.1", - "@webassemblyjs/wasm-gen": "1.12.1", - "@webassemblyjs/wasm-opt": "1.12.1", - "@webassemblyjs/wasm-parser": "1.12.1", - "@webassemblyjs/wast-printer": "1.12.1" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" } }, "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz", - "integrity": "sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/ieee754": "1.11.6", - "@webassemblyjs/leb128": "1.11.6", - "@webassemblyjs/utf8": "1.11.6" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" } }, "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz", - "integrity": "sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/wasm-gen": "1.12.1", - "@webassemblyjs/wasm-parser": "1.12.1" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" } }, "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz", - "integrity": "sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-api-error": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/ieee754": "1.11.6", - "@webassemblyjs/leb128": "1.11.6", - "@webassemblyjs/utf8": "1.11.6" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" } }, "node_modules/@webassemblyjs/wast-printer": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz", - "integrity": "sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/ast": "1.14.1", "@xtuc/long": "4.2.2" } }, @@ -628,6 +878,7 @@ "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", "dev": true, + "license": "BSD-3-Clause", "peer": true }, "node_modules/@xtuc/long": { @@ -635,13 +886,15 @@ "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", "dev": true, + "license": "Apache-2.0", "peer": true }, "node_modules/acorn": { - "version": "8.9.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.9.0.tgz", - "integrity": "sha512-jaVNAFBHNLXspO543WnNNPZFRtavh3skAkITqD0/2aeMkKZTN+254PyhwxFYrk3vQ1xfY+2wbesJMs/JC8/PwQ==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, + "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -649,14 +902,18 @@ "node": ">=0.4.0" } }, - "node_modules/acorn-import-attributes": { - "version": "1.9.5", - "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", - "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", + "node_modules/acorn-import-phases": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", + "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", "dev": true, + "license": "MIT", "peer": true, + "engines": { + "node": ">=10.13.0" + }, "peerDependencies": { - "acorn": "^8" + "acorn": "^8.14.0" } }, "node_modules/acorn-jsx": { @@ -664,15 +921,20 @@ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, + "license": "MIT", "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "node_modules/acorn-walk": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", - "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, "engines": { "node": ">=0.4.0" } @@ -682,6 +944,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -693,21 +956,57 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", "dev": true, + "license": "MIT", "peer": true, + "dependencies": { + "ajv": "^8.0.0" + }, "peerDependencies": { - "ajv": "^6.9.1" + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/ansi-colors": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -717,15 +1016,33 @@ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, "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, + "license": "ISC", "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -738,56 +1055,54 @@ "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", "dev": true, - "engines": { - "node": ">=8" - } + "license": "Python-2.0" }, "node_modules/assertion-error": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", "dev": true, + "license": "MIT", "engines": { "node": "*" } }, "node_modules/balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" }, "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, + "license": "MIT", "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "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", - "concat-map": "0.0.1" + "balanced-match": "^1.0.0" } }, "node_modules/braces": { @@ -795,6 +1110,7 @@ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, + "license": "MIT", "dependencies": { "fill-range": "^7.1.1" }, @@ -806,12 +1122,13 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/browserslist": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz", - "integrity": "sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==", + "version": "4.25.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.4.tgz", + "integrity": "sha512-4jYpcjabC606xJ3kw2QwGEZKX0Aw7sgQdZCvIK9dhVSPh76BKo+C+btT1RRofH7B+8iNpEbgGNVWiLki5q93yg==", "dev": true, "funding": [ { @@ -827,12 +1144,13 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "peer": true, "dependencies": { - "caniuse-lite": "^1.0.30001646", - "electron-to-chromium": "^1.5.4", - "node-releases": "^2.0.18", - "update-browserslist-db": "^1.1.0" + "caniuse-lite": "^1.0.30001737", + "electron-to-chromium": "^1.5.211", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.3" }, "bin": { "browserslist": "cli.js" @@ -846,6 +1164,7 @@ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true, + "license": "MIT", "peer": true }, "node_modules/callsites": { @@ -853,14 +1172,28 @@ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/caniuse-lite": { - "version": "1.0.30001660", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001660.tgz", - "integrity": "sha512-GacvNTTuATm26qC74pt+ad1fW15mlQ/zuTzzY1ZoIzECTP8HURDfF43kNxPgf7H1jmelCBQTTbBNxdSXOA7Bqg==", + "version": "1.0.30001741", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001741.tgz", + "integrity": "sha512-QGUGitqsc8ARjLdgAfxETDhRbJ0REsP6O3I96TAth/mVjh2cYzN2u+3AzPP3aVSm2FehEItaJw1xd+IGBXWeSw==", "dev": true, "funding": [ { @@ -876,55 +1209,74 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "CC-BY-4.0", "peer": true }, "node_modules/chai": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.6.tgz", - "integrity": "sha512-bbcp3YfHCUzMOvKqsztczerVgBKSsEijCySNlHHbX3VG1nskvqjz5Rfso1gGwD6w6oOV3eI60pKuMOV5MV7p3Q==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.5.0.tgz", + "integrity": "sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==", "dev": true, + "license": "MIT", "dependencies": { "assertion-error": "^1.1.0", - "check-error": "^1.0.2", - "deep-eql": "^3.0.1", - "get-func-name": "^2.0.0", - "loupe": "^2.3.1", + "check-error": "^1.0.3", + "deep-eql": "^4.1.3", + "get-func-name": "^2.0.2", + "loupe": "^2.3.6", "pathval": "^1.1.1", - "type-detect": "^4.0.5" + "type-detect": "^4.1.0" }, "engines": { "node": ">=4" } }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/charenc": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", - "integrity": "sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc=", + "integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": "*" } }, "node_modules/check-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", - "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", + "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", "dev": true, + "license": "MIT", + "dependencies": { + "get-func-name": "^2.0.2" + }, "engines": { "node": "*" } }, "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/" - } - ], + "license": "MIT", "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -937,15 +1289,32 @@ "engines": { "node": ">= 8.10.0" }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, "optionalDependencies": { "fsevents": "~2.3.2" } }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/chrome-trace-event": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", - "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", + "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", "dev": true, + "license": "MIT", "peer": true, "engines": { "node": ">=6.0" @@ -956,36 +1325,80 @@ "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", "dev": true, + "license": "ISC", "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", "wrap-ansi": "^7.0.0" } }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, "node_modules/commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true, + "license": "MIT", "peer": true }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" }, "node_modules/create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true + "dev": true, + "license": "MIT" + }, + "node_modules/cross-env": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", + "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.1" + }, + "bin": { + "cross-env": "src/bin/cross-env.js", + "cross-env-shell": "src/bin/cross-env-shell.js" + }, + "engines": { + "node": ">=10.14", + "npm": ">=6", + "yarn": ">=1" + } }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, + "license": "MIT", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -998,17 +1411,19 @@ "node_modules/crypt": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", - "integrity": "sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs=", + "integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": "*" } }, "node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", "dev": true, + "license": "MIT", "dependencies": { "ms": "^2.1.3" }, @@ -1021,50 +1436,55 @@ } } }, + "node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/deep-eql": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", - "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.4.tgz", + "integrity": "sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==", "dev": true, + "license": "MIT", "dependencies": { "type-detect": "^4.0.0" }, "engines": { - "node": ">=0.12" + "node": ">=6" } }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/diff": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=0.3.1" } }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", "dev": true, + "license": "Apache-2.0", "dependencies": { "esutils": "^2.0.2" }, @@ -1073,23 +1493,26 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.23", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.23.tgz", - "integrity": "sha512-mBhODedOXg4v5QWwl21DjM5amzjmI1zw9EPrPK/5Wx7C8jt33bpZNrC7OhHUG3pxRtbLpr3W2dXT+Ph1SsfRZA==", + "version": "1.5.215", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.215.tgz", + "integrity": "sha512-TIvGp57UpeNetj/wV/xpFNpWGb0b/ROw372lHPx5Aafx02gjTBtWnEEcaSX3W2dLM3OSdGGyHX/cHl01JQsLaQ==", "dev": true, + "license": "ISC", "peer": true }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/enhanced-resolve": { - "version": "5.17.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", - "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", + "version": "5.18.3", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", + "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==", "dev": true, + "license": "MIT", "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" @@ -1099,10 +1522,11 @@ } }, "node_modules/es-module-lexer": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.3.0.tgz", - "integrity": "sha512-vZK7T0N2CBmBOixhmjdqx2gWVbFZ4DXZ/NyRMZVlJXPa7CyFS+/a4QQsDGDQy9ZfEzxFuNEsMLeQJnKP2p5/JA==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", "dev": true, + "license": "MIT", "peer": true }, "node_modules/escalade": { @@ -1110,51 +1534,70 @@ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/eslint": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.15.0.tgz", - "integrity": "sha512-GG5USZ1jhCu8HJkzGgeK8/+RGnHaNYZGrGDzUtigK3BsGESW/rs2az23XqE0WVwDxy1VRvvjSSGu5nB0Bu+6SA==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, + "license": "MIT", "dependencies": { - "@eslint/eslintrc": "^1.2.3", - "@humanwhocodes/config-array": "^0.9.2", - "ajv": "^6.10.0", + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.1", - "eslint-utils": "^3.0.0", - "eslint-visitor-keys": "^3.3.0", - "espree": "^9.3.2", - "esquery": "^1.4.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^6.0.1", - "globals": "^13.6.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", "ignore": "^5.2.0", - "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "regexpp": "^3.2.0", + "optionator": "^0.9.3", "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" + "text-table": "^0.2.0" }, "bin": { "eslint": "bin/eslint.js" @@ -1167,10 +1610,11 @@ } }, "node_modules/eslint-config-prettier": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz", - "integrity": "sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==", + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.2.tgz", + "integrity": "sha512-iI1f+D2ViGn+uvv5HuHVUamg8ll4tN+JRHGc6IJi4TP9Kl976C57fzPXgseXNs8v0iA8aSJpHsTWjDb9QJamGQ==", "dev": true, + "license": "MIT", "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -1179,231 +1623,159 @@ } }, "node_modules/eslint-plugin-prettier": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.0.0.tgz", - "integrity": "sha512-98MqmCJ7vJodoQK359bqQWaxOE0CS8paAz/GgjaZLyex4TTk3g9HugoO89EqWCrFiOqn9EVvcoo7gZzONCWVwQ==", + "version": "5.5.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.4.tgz", + "integrity": "sha512-swNtI95SToIz05YINMA6Ox5R057IMAmWZ26GqPxusAp1TZzj+IdY9tXNWWD3vkF/wEqydCONcwjTFpxybBqZsg==", "dev": true, + "license": "MIT", "dependencies": { - "prettier-linter-helpers": "^1.0.0" + "prettier-linter-helpers": "^1.0.0", + "synckit": "^0.11.7" }, "engines": { - "node": ">=6.0.0" + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-plugin-prettier" }, "peerDependencies": { - "eslint": ">=7.28.0", - "prettier": ">=2.0.0" + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0", + "prettier": ">=3.0.0" }, "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, "eslint-config-prettier": { "optional": true } } }, "node_modules/eslint-plugin-promise": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.0.0.tgz", - "integrity": "sha512-7GPezalm5Bfi/E22PnQxDWH2iW9GTvAlUNTztemeHb6c1BniSyoeTrM87JkC0wYdi6aQrZX9p2qEiAno8aTcbw==", + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-7.2.1.tgz", + "integrity": "sha512-SWKjd+EuvWkYaS+uN2csvj0KoP43YTu7+phKQ5v+xw6+A0gutVX2yqCeCkC3uLCJFiPfR2dD8Es5L7yUsmvEaA==", "dev": true, + "license": "ISC", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0" + }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" }, "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" + "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0" } }, "node_modules/eslint-plugin-security": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-security/-/eslint-plugin-security-1.5.0.tgz", - "integrity": "sha512-hAFVwLZ/UeXrlyVD2TDarv/x00CoFVpaY0IUZhKjPjiFxqkuQVixsK4f2rxngeQOqSxi6OUjzJM/jMwKEVjJ8g==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-security/-/eslint-plugin-security-3.0.1.tgz", + "integrity": "sha512-XjVGBhtDZJfyuhIxnQ/WMm385RbX3DBu7H1J7HNNhmB2tnGxMeqVSnYv79oAj992ayvIBZghsymwkYFS6cGH4Q==", "dev": true, + "license": "Apache-2.0", "dependencies": { "safe-regex": "^2.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" + "estraverse": "^5.2.0" }, "engines": { - "node": ">=8.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "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, - "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, - "engines": { - "node": ">=10" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", - "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, + "license": "Apache-2.0", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/eslint/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/eslint/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/eslint/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/eslint/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/eslint/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "engines": { - "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/eslint-scope": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", - "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/eslint/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" + "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "node_modules/eslint/node_modules/brace-expansion": { + "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, + "license": "MIT", "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "node_modules/eslint/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/eslint/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">= 4" } }, - "node_modules/eslint/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/eslint/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, + "license": "ISC", "dependencies": { - "has-flag": "^4.0.0" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=8" + "node": "*" } }, "node_modules/espree": { - "version": "9.3.2", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.2.tgz", - "integrity": "sha512-D211tC7ZwouTIuY5x9XnS0E9sWNChB7IYKX/Xp5eQj3nFXhqmiUDB9q27y76oFl8jTg3pXcQx/bpxMfs3CIZbA==", + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.7.1", + "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.3.0" + "eslint-visitor-keys": "^3.4.1" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "estraverse": "^5.1.0" }, @@ -1411,20 +1783,12 @@ "node": ">=0.10" } }, - "node_modules/esquery/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, "node_modules/esrecurse": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "estraverse": "^5.2.0" }, @@ -1432,20 +1796,12 @@ "node": ">=4.0" } }, - "node_modules/esrecurse/node_modules/estraverse": { + "node_modules/estraverse": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">=4.0" } @@ -1455,6 +1811,7 @@ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">=0.10.0" } @@ -1464,6 +1821,7 @@ "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", "dev": true, + "license": "MIT", "peer": true, "engines": { "node": ">=0.8.x" @@ -1473,47 +1831,84 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/fast-diff": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", - "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", - "dev": true + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true, + "license": "Apache-2.0" }, "node_modules/fast-glob": { - "version": "3.2.11", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", - "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", "dev": true, + "license": "MIT", "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", - "micromatch": "^4.0.4" + "micromatch": "^4.0.8" }, "engines": { "node": ">=8.6.0" } }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "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": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause", + "peer": true }, "node_modules/fastq": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", - "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", "dev": true, + "license": "ISC", "dependencies": { "reusify": "^1.0.4" } @@ -1523,6 +1918,7 @@ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", "dev": true, + "license": "MIT", "dependencies": { "flat-cache": "^3.0.4" }, @@ -1535,6 +1931,7 @@ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, + "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -1542,22 +1939,42 @@ "node": ">=8" } }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/flat": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", "dev": true, + "license": "BSD-3-Clause", "bin": { "flat": "cli.js" } }, "node_modules/flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", "dev": true, + "license": "MIT", "dependencies": { - "flatted": "^3.1.0", + "flatted": "^3.2.9", + "keyv": "^4.5.3", "rimraf": "^3.0.2" }, "engines": { @@ -1565,28 +1982,40 @@ } }, "node_modules/flatted": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.4.tgz", - "integrity": "sha512-8/sOawo8tJ4QOBX8YlQBMxL8+RLZfxMQOif9o0KUKTNTjMYElWPE0r/m5VNFxTRd0NSw8qSy8dajrwX4RYI1Hw==", - "dev": true + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" }, - "node_modules/functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", - "dev": true + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true, + "license": "ISC", "engines": { "node": "6.* || 8.* || >= 10.*" } @@ -1596,40 +2025,43 @@ "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", "dev": true, + "license": "MIT", "engines": { "node": "*" } }, "node_modules/glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "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.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "minimatch": "^5.0.1", + "once": "^1.3.0" }, "engines": { - "node": "*" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, + "license": "ISC", "dependencies": { - "is-glob": "^4.0.1" + "is-glob": "^4.0.3" }, "engines": { - "node": ">= 6" + "node": ">=10.13.0" } }, "node_modules/glob-to-regexp": { @@ -1637,38 +2069,33 @@ "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", "dev": true, + "license": "BSD-2-Clause", "peer": true }, - "node_modules/globals": { - "version": "13.15.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.15.0.tgz", - "integrity": "sha512-bpzcOlgDhMG070Av0Vy5Owklpv1I6+j96GhUI7Rh7IzDCKLzboflLrrfqMu8NquDbiR4EOQk7XzJwqVJxicxog==", + "node_modules/glob/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "dev": true, + "license": "ISC", "dependencies": { - "type-fest": "^0.20.2" + "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=10" } }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, + "license": "MIT", "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" + "type-fest": "^0.20.2" }, "engines": { - "node": ">=10" + "node": ">=8" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -1678,7 +2105,8 @@ "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/grapheme-splitter": { "version": "1.0.4", @@ -1686,29 +2114,49 @@ "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", "license": "MIT" }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true, + "license": "MIT", "bin": { "he": "bin/he" } }, "node_modules/ignore": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", - "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 4" } }, "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", "dev": true, + "license": "MIT", "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -1723,8 +2171,9 @@ "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.8.19" } @@ -1732,8 +2181,10 @@ "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", "dev": true, + "license": "ISC", "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -1743,13 +2194,15 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", "dev": true, + "license": "MIT", "dependencies": { "binary-extensions": "^2.0.0" }, @@ -1761,13 +2214,15 @@ "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -1777,6 +2232,7 @@ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -1786,6 +2242,7 @@ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, + "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" }, @@ -1798,15 +2255,37 @@ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.12.0" } }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-unicode-supported": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, @@ -1817,14 +2296,16 @@ "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" }, "node_modules/jest-worker": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "@types/node": "*", @@ -1835,11 +2316,29 @@ "node": ">= 10.13.0" } }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, "node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, + "license": "MIT", "dependencies": { "argparse": "^2.0.1" }, @@ -1847,30 +2346,51 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", "dev": true, + "license": "MIT", "peer": true }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", - "dev": true + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, + "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" @@ -1884,28 +2404,48 @@ "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", "dev": true, + "license": "MIT", "peer": true, "engines": { "node": ">=6.11.5" } }, + "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", + "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==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", "dev": true, + "license": "MIT", "dependencies": { "chalk": "^4.1.0", "is-unicode-supported": "^0.1.0" @@ -1917,108 +2457,29 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/log-symbols/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/log-symbols/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/log-symbols/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/log-symbols/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/log-symbols/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/log-symbols/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/loupe": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.4.tgz", - "integrity": "sha512-OvKfgCC2Ndby6aSTREl5aCCPTNIzlDfQZvZxNUrBrihDhL3xcrYegTblhmEiCrg2kKQz4XsFIaemE5BF4ybSaQ==", - "dev": true, - "dependencies": { - "get-func-name": "^2.0.0" - } - }, - "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", + "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", "dev": true, + "license": "MIT", "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" + "get-func-name": "^2.0.1" } }, "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/md5": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz", "integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "charenc": "0.0.2", "crypt": "0.0.2", @@ -2030,6 +2491,7 @@ "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", "dev": true, + "license": "MIT", "peer": true }, "node_modules/merge2": { @@ -2037,6 +2499,7 @@ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 8" } @@ -2046,6 +2509,7 @@ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, + "license": "MIT", "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" @@ -2059,6 +2523,7 @@ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "dev": true, + "license": "MIT", "peer": true, "engines": { "node": ">= 0.6" @@ -2069,6 +2534,7 @@ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "mime-db": "1.52.0" @@ -2078,34 +2544,35 @@ } }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "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": "^1.1.7" + "brace-expansion": "^2.0.1" }, "engines": { - "node": "*" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", - "dev": true - }, "node_modules/mkdirp": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.4.tgz", - "integrity": "sha512-iG9AK/dJLtJ0XNgTuDbSyNS3zECqDlAhnQW4CsNxBG3LQJBbHmRX1egw39DmtOdCAqY+dKXV+sgPgilNWUKMVw==", - "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", "dev": true, - "dependencies": { - "minimist": "^1.2.5" - }, + "license": "MIT", "bin": { - "mkdirp": "bin/cmd.js" + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/mocha": { @@ -2113,6 +2580,7 @@ "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.8.2.tgz", "integrity": "sha512-VZlYo/WE8t1tstuRmqgeyBgCbJc/lEdopaa+axcKzTBJ+UIdlAB9XnmvTCAH4pwR4ElNInaedhEBmZD8iCSVEg==", "dev": true, + "license": "MIT", "dependencies": { "ansi-colors": "^4.1.3", "browser-stdout": "^1.3.1", @@ -2128,139 +2596,53 @@ "minimatch": "^5.1.6", "ms": "^2.1.3", "serialize-javascript": "^6.0.2", - "strip-json-comments": "^3.1.1", - "supports-color": "^8.1.1", - "workerpool": "^6.5.1", - "yargs": "^16.2.0", - "yargs-parser": "^20.2.9", - "yargs-unparser": "^2.0.0" - }, - "bin": { - "_mocha": "bin/_mocha", - "mocha": "bin/mocha.js" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/mocha-junit-reporter": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/mocha-junit-reporter/-/mocha-junit-reporter-2.0.2.tgz", - "integrity": "sha512-vYwWq5hh3v1lG0gdQCBxwNipBfvDiAM1PHroQRNp96+2l72e9wEUTw+mzoK+O0SudgfQ7WvTQZ9Nh3qkAYAjfg==", - "dev": true, - "dependencies": { - "debug": "^2.2.0", - "md5": "^2.1.0", - "mkdirp": "~0.5.1", - "strip-ansi": "^6.0.1", - "xml": "^1.0.0" - }, - "peerDependencies": { - "mocha": ">=2.2.5" - } - }, - "node_modules/mocha-junit-reporter/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/mocha-junit-reporter/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "node_modules/mocha-multi-reporters": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/mocha-multi-reporters/-/mocha-multi-reporters-1.5.1.tgz", - "integrity": "sha512-Yb4QJOaGLIcmB0VY7Wif5AjvLMUFAdV57D2TWEva1Y0kU/3LjKpeRVmlMIfuO1SVbauve459kgtIizADqxMWPg==", - "dev": true, - "dependencies": { - "debug": "^4.1.1", - "lodash": "^4.17.15" - }, - "engines": { - "node": ">=6.0.0" - }, - "peerDependencies": { - "mocha": ">=3.1.2" - } - }, - "node_modules/mocha/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/mocha/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" + "strip-json-comments": "^3.1.1", + "supports-color": "^8.1.1", + "workerpool": "^6.5.1", + "yargs": "^16.2.0", + "yargs-parser": "^20.2.9", + "yargs-unparser": "^2.0.0" }, - "engines": { - "node": ">=10" + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha.js" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">= 14.0.0" } }, - "node_modules/mocha/node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "deprecated": "Glob versions prior to v9 are no longer supported", + "node_modules/mocha-junit-reporter": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/mocha-junit-reporter/-/mocha-junit-reporter-2.2.1.tgz", + "integrity": "sha512-iDn2tlKHn8Vh8o4nCzcUVW4q7iXp7cC4EB78N0cDHIobLymyHNwe0XG8HEHHjc3hJlXm0Vy6zcrxaIhnI2fWmw==", "dev": true, + "license": "MIT", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - }, - "engines": { - "node": ">=12" + "debug": "^4.3.4", + "md5": "^2.3.0", + "mkdirp": "^3.0.0", + "strip-ansi": "^6.0.1", + "xml": "^1.0.1" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "peerDependencies": { + "mocha": ">=2.2.5" } }, - "node_modules/mocha/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==", + "node_modules/mocha-multi-reporters": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/mocha-multi-reporters/-/mocha-multi-reporters-1.5.1.tgz", + "integrity": "sha512-Yb4QJOaGLIcmB0VY7Wif5AjvLMUFAdV57D2TWEva1Y0kU/3LjKpeRVmlMIfuO1SVbauve459kgtIizADqxMWPg==", "dev": true, + "license": "MIT", "dependencies": { - "p-locate": "^5.0.0" + "debug": "^4.1.1", + "lodash": "^4.17.15" }, "engines": { - "node": ">=10" + "node": ">=6.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "mocha": ">=3.1.2" } }, "node_modules/mocha/node_modules/minimatch": { @@ -2268,6 +2650,7 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, @@ -2275,60 +2658,50 @@ "node": ">=10" } }, - "node_modules/mocha/node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "node_modules/mocha/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, + "license": "MIT", "dependencies": { - "p-limit": "^3.0.2" + "has-flag": "^4.0.0" }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", - "dev": true + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" }, "node_modules/neo-async": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true, + "license": "MIT", "peer": true }, "node_modules/node-releases": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", - "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.20.tgz", + "integrity": "sha512-7gK6zSXEH6neM212JgfYFXe+GmZQM+fia5SsusuBIUgnPheLFBmIPhtFoAQRj8/7wASYQnbDlHPVwY0BefoFgA==", "dev": true, + "license": "MIT", "peer": true }, "node_modules/normalize-path": { @@ -2336,6 +2709,7 @@ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -2343,34 +2717,69 @@ "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "dev": true, + "license": "ISC", "dependencies": { "wrappy": "1" } }, "node_modules/optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dev": true, + "license": "MIT", "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", - "word-wrap": "^1.2.3" + "word-wrap": "^1.2.5" }, "engines": { "node": ">= 0.8.0" } }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, + "license": "MIT", "dependencies": { "callsites": "^3.0.0" }, @@ -2383,6 +2792,7 @@ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -2390,8 +2800,9 @@ "node_modules/path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -2401,15 +2812,7 @@ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -2419,6 +2822,7 @@ "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", "dev": true, + "license": "MIT", "engines": { "node": "*" } @@ -2430,10 +2834,11 @@ "license": "MIT" }, "node_modules/picocolors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", - "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "dev": true, + "license": "ISC", "peer": true }, "node_modules/picomatch": { @@ -2441,6 +2846,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true, + "license": "MIT", "engines": { "node": ">=8.6" }, @@ -2453,20 +2859,22 @@ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.8.0" } }, "node_modules/prettier": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.6.2.tgz", - "integrity": "sha512-PkUpF+qoXTqhOeWL9fu7As8LXsIUZ1WYaJiY/a7McAQzxjk82OF0tibkFXVCDImZtWxbvojFjerkiLb0/q8mew==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", + "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", "dev": true, + "license": "MIT", "bin": { - "prettier": "bin-prettier.js" + "prettier": "bin/prettier.cjs" }, "engines": { - "node": ">=10.13.0" + "node": ">=14" }, "funding": { "url": "https://github.com/prettier/prettier?sponsor=1" @@ -2477,6 +2885,7 @@ "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", "dev": true, + "license": "MIT", "dependencies": { "fast-diff": "^1.1.2" }, @@ -2485,10 +2894,11 @@ } }, "node_modules/punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -2511,13 +2921,15 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "MIT" }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", "dev": true, + "license": "MIT", "dependencies": { "safe-buffer": "^5.1.0" } @@ -2527,6 +2939,7 @@ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", "dev": true, + "license": "MIT", "dependencies": { "picomatch": "^2.2.1" }, @@ -2535,31 +2948,32 @@ } }, "node_modules/regexp-tree": { - "version": "0.1.24", - "resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.24.tgz", - "integrity": "sha512-s2aEVuLhvnVJW6s/iPgEGK6R+/xngd2jNQ+xy4bXNDKxZKJH6jpPHY6kVeVv1IeLCHgswRj+Kl3ELaDjG6V1iw==", + "version": "0.1.27", + "resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.27.tgz", + "integrity": "sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==", "dev": true, + "license": "MIT", "bin": { "regexp-tree": "bin/regexp-tree" } }, - "node_modules/regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "dev": true, + "license": "MIT", "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" + "node": ">=0.10.0" } }, - "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=", + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", "dev": true, + "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -2569,15 +2983,17 @@ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } }, "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", "dev": true, + "license": "MIT", "engines": { "iojs": ">=1.0.0", "node": ">=0.10.0" @@ -2587,7 +3003,9 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", "dev": true, + "license": "ISC", "dependencies": { "glob": "^7.1.3" }, @@ -2598,6 +3016,52 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rimraf/node_modules/brace-expansion": { + "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, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "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==", + "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/rimraf/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, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -2617,35 +3081,54 @@ "url": "https://feross.org/support" } ], + "license": "MIT", "dependencies": { "queue-microtask": "^1.2.2" } }, "node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" }, "node_modules/safe-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-2.1.1.tgz", "integrity": "sha512-rx+x8AMzKb5Q5lQ95Zoi6ZbJqwCLkqi3XuJXp5P3rT8OEc6sZCJG5AE5dU3lsgRr/F4Bs31jSlVN+j5KrsGu9A==", "dev": true, + "license": "MIT", "dependencies": { "regexp-tree": "~0.1.1" } }, "node_modules/schema-utils": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", - "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz", + "integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" }, "engines": { "node": ">= 10.13.0" @@ -2655,14 +3138,52 @@ "url": "https://opencollective.com/webpack" } }, - "node_modules/semver": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", - "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==", + "node_modules/schema-utils/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/schema-utils/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", "dev": true, + "license": "MIT", + "peer": true, "dependencies": { - "lru-cache": "^6.0.0" + "fast-deep-equal": "^3.1.3" }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/schema-utils/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT", + "peer": true + }, + "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==", + "dev": true, + "license": "ISC", "bin": { "semver": "bin/semver.js" }, @@ -2675,6 +3196,7 @@ "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "randombytes": "^2.1.0" } @@ -2684,39 +3206,32 @@ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, + "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, + }, "engines": { "node": ">=8" } }, - "node_modules/slash": { + "node_modules/shebang-regex": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", "dev": true, - "peer": true, + "license": "BSD-3-Clause", "engines": { - "node": ">=0.10.0" + "node": ">= 12" } }, "node_modules/source-map-support": { @@ -2724,17 +3239,30 @@ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, + "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -2749,6 +3277,7 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, + "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" }, @@ -2761,6 +3290,7 @@ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" }, @@ -2769,47 +3299,58 @@ } }, "node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, + "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" + "node": ">=8" } }, - "node_modules/supports-color/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/synckit": { + "version": "0.11.11", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.11.tgz", + "integrity": "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==", "dev": true, + "license": "MIT", + "dependencies": { + "@pkgr/core": "^0.2.9" + }, "engines": { - "node": ">=8" + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/synckit" } }, "node_modules/tapable": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.3.tgz", + "integrity": "sha512-ZL6DDuAlRlLGghwcfmSn9sK3Hr6ArtyudlSAiCqQ6IfE+b+HHbydbYDIG15IfS5do+7XQQBdBiubF/cV2dnDzg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, "node_modules/terser": { - "version": "5.32.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.32.0.tgz", - "integrity": "sha512-v3Gtw3IzpBJ0ugkxEX8U0W6+TnPKRRCWGh1jC/iM/e3Ki5+qvO1L1EAZ56bZasc64aXHwRHNIQEzm6//i5cemQ==", + "version": "5.44.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.44.0.tgz", + "integrity": "sha512-nIVck8DK+GM/0Frwd+nIhZ84pR/BX7rmXMfYwyg+Sri5oGVE99/E3KvXqpC2xHFxyqXyGHTKBSioxxplrO4I4w==", "dev": true, + "license": "BSD-2-Clause", "peer": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.8.2", + "acorn": "^8.15.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, @@ -2821,17 +3362,18 @@ } }, "node_modules/terser-webpack-plugin": { - "version": "5.3.10", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz", - "integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==", + "version": "5.3.14", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz", + "integrity": "sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "@jridgewell/trace-mapping": "^0.3.20", + "@jridgewell/trace-mapping": "^0.3.25", "jest-worker": "^27.4.5", - "schema-utils": "^3.1.1", - "serialize-javascript": "^6.0.1", - "terser": "^5.26.0" + "schema-utils": "^4.3.0", + "serialize-javascript": "^6.0.2", + "terser": "^5.31.1" }, "engines": { "node": ">= 10.13.0" @@ -2855,17 +3397,31 @@ } } }, + "node_modules/terser-webpack-plugin/node_modules/@jridgewell/trace-mapping": { + "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", + "peer": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "dev": true + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true, + "license": "MIT" }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, + "license": "MIT", "dependencies": { "is-number": "^7.0.0" }, @@ -2873,16 +3429,31 @@ "node": ">=8.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", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, "node_modules/ts-loader": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.3.0.tgz", - "integrity": "sha512-2kLLAdAD+FCKijvGKi9sS0OzoqxLCF3CxHpok7rVgCZ5UldRzH0TkbwG9XECKjBzHsAewntC5oDaI/FwKzEUog==", + "version": "9.5.4", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.4.tgz", + "integrity": "sha512-nCz0rEwunlTZiy6rXFByQU1kVVpCIgUpc/psFiKVrUwrizdnIbRFu8w7bxhUF0X613DYwT4XzrZHpVyMe758hQ==", "dev": true, + "license": "MIT", "dependencies": { "chalk": "^4.1.0", "enhanced-resolve": "^5.0.0", "micromatch": "^4.0.0", - "semver": "^7.3.4" + "semver": "^7.3.4", + "source-map": "^0.7.4" }, "engines": { "node": ">=12.0.0" @@ -2892,83 +3463,14 @@ "webpack": "^5.0.0" } }, - "node_modules/ts-loader/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/ts-loader/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/ts-loader/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/ts-loader/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/ts-loader/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/ts-loader/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/ts-node": { - "version": "10.7.0", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.7.0.tgz", - "integrity": "sha512-TbIGS4xgJoX2i3do417KSaep1uRAW/Lu+WAL2doDHC0D6ummjirVOXU5/7aiZotbQ5p1Zp9tP7U6cYhA0O7M8A==", + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "dev": true, + "license": "MIT", "dependencies": { - "@cspotcode/source-map-support": "0.7.0", + "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", "@tsconfig/node12": "^1.0.7", "@tsconfig/node14": "^1.0.0", @@ -2979,7 +3481,7 @@ "create-require": "^1.1.0", "diff": "^4.0.1", "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.0", + "v8-compile-cache-lib": "^3.0.1", "yn": "3.1.1" }, "bin": { @@ -3010,36 +3512,17 @@ "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/tslib": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.1.tgz", - "integrity": "sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA==", - "dev": true - }, - "node_modules/tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, - "dependencies": { - "tslib": "^1.8.1" - }, - "engines": { - "node": ">= 6" - }, - "peerDependencies": { - "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" - } - }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, + "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1" }, @@ -3048,10 +3531,11 @@ } }, "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", + "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } @@ -3061,6 +3545,7 @@ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true, + "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=10" }, @@ -3069,22 +3554,30 @@ } }, "node_modules/typescript": { - "version": "4.6.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.4.tgz", - "integrity": "sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg==", + "version": "5.9.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", + "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", "dev": true, + "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" }, "engines": { - "node": ">=4.2.0" + "node": ">=14.17" } }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, "node_modules/update-browserslist-db": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", - "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", "dev": true, "funding": [ { @@ -3100,10 +3593,11 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "peer": true, "dependencies": { - "escalade": "^3.1.2", - "picocolors": "^1.0.1" + "escalade": "^3.2.0", + "picocolors": "^1.1.1" }, "bin": { "update-browserslist-db": "cli.js" @@ -3117,37 +3611,36 @@ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" } }, - "node_modules/v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", - "dev": true - }, "node_modules/v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/vscode-languageserver-textdocument": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.4.tgz", - "integrity": "sha512-/xhqXP/2A2RSs+J8JNXpiiNVvvNM0oTosNVmQnunlKvq9o4mupHOBAnnzH0lwIPKazXKvAKsVp1kr+H/K4lgoQ==" + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz", + "integrity": "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==", + "license": "MIT" }, "node_modules/vscode-languageserver-types": { - "version": "3.17.1", - "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.1.tgz", - "integrity": "sha512-K3HqVRPElLZVVPtMeKlsyL9aK0GxGQpvtAUTfX4k7+iJ4mc1M+JM+zQwkgGy2LzY0f0IAafe8MKqIkJrxfGGjQ==" + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", + "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==", + "license": "MIT" }, "node_modules/watchpack": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz", - "integrity": "sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==", + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.4.tgz", + "integrity": "sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "glob-to-regexp": "^0.4.1", @@ -3158,21 +3651,24 @@ } }, "node_modules/webpack": { - "version": "5.94.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz", - "integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==", + "version": "5.101.3", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.101.3.tgz", + "integrity": "sha512-7b0dTKR3Ed//AD/6kkx/o7duS8H3f1a4w3BYpIriX4BzIhjkn4teo05cptsxvLesHFKK5KObnadmCHBwGc+51A==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "@types/estree": "^1.0.5", - "@webassemblyjs/ast": "^1.12.1", - "@webassemblyjs/wasm-edit": "^1.12.1", - "@webassemblyjs/wasm-parser": "^1.12.1", - "acorn": "^8.7.1", - "acorn-import-attributes": "^1.9.5", - "browserslist": "^4.21.10", + "@types/eslint-scope": "^3.7.7", + "@types/estree": "^1.0.8", + "@types/json-schema": "^7.0.15", + "@webassemblyjs/ast": "^1.14.1", + "@webassemblyjs/wasm-edit": "^1.14.1", + "@webassemblyjs/wasm-parser": "^1.14.1", + "acorn": "^8.15.0", + "acorn-import-phases": "^1.0.3", + "browserslist": "^4.24.0", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.17.1", + "enhanced-resolve": "^5.17.3", "es-module-lexer": "^1.2.1", "eslint-scope": "5.1.1", "events": "^3.2.0", @@ -3182,11 +3678,11 @@ "loader-runner": "^4.2.0", "mime-types": "^2.1.27", "neo-async": "^2.6.2", - "schema-utils": "^3.2.0", + "schema-utils": "^4.3.2", "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.3.10", + "terser-webpack-plugin": "^5.3.11", "watchpack": "^2.4.1", - "webpack-sources": "^3.2.3" + "webpack-sources": "^3.3.3" }, "bin": { "webpack": "bin/webpack.js" @@ -3205,20 +3701,48 @@ } }, "node_modules/webpack-sources": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", - "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.3.tgz", + "integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==", "dev": true, + "license": "MIT", "peer": true, "engines": { "node": ">=10.13.0" } }, + "node_modules/webpack/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/webpack/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "engines": { + "node": ">=4.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, + "license": "ISC", "dependencies": { "isexe": "^2.0.0" }, @@ -3234,6 +3758,7 @@ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -3242,13 +3767,15 @@ "version": "6.5.1", "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz", "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==", - "dev": true + "dev": true, + "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==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -3261,71 +3788,36 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/wrap-ansi/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" }, "node_modules/xml": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", - "integrity": "sha1-eLpyAgApxbyHuKgaPPzXS0ovweU=", - "dev": true + "integrity": "sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==", + "dev": true, + "license": "MIT" }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "dev": true, + "license": "ISC", "engines": { "node": ">=10" } }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/yargs": { "version": "16.2.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", "dev": true, + "license": "MIT", "dependencies": { "cliui": "^7.0.2", "escalade": "^3.1.1", @@ -3344,6 +3836,7 @@ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", "dev": true, + "license": "ISC", "engines": { "node": ">=10" } @@ -3353,6 +3846,7 @@ "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", "dev": true, + "license": "MIT", "dependencies": { "camelcase": "^6.0.0", "decamelize": "^4.0.0", @@ -3363,44 +3857,12 @@ "node": ">=10" } }, - "node_modules/yargs-unparser/node_modules/camelcase": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", - "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/yargs-unparser/node_modules/decamelize": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/yargs-unparser/node_modules/is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -3410,6 +3872,7 @@ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, diff --git a/package.json b/package.json index 1e29afcc..c3d1fdde 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "link:start": "npm link && npm uninstall @microsoft/powerquery-parser @microsoft/powerquery-formatter && git clean -xdf && npm install && npm link @microsoft/powerquery-parser @microsoft/powerquery-formatter", "link:stop": "npm unlink @microsoft/powerquery-parser @microsoft/powerquery-formatter && git clean -xdf && npm install && npm install @microsoft/powerquery-parser@latest @microsoft/powerquery-formatter@latest --save-exact", "lint": "eslint src --ext ts", - "test": "mocha --reporter mocha-multi-reporters --reporter-options configFile=src/test/mochaConfig.json -r ts-node/register src/test/**/*.ts" + "test": "npm run build && mocha --reporter mocha-multi-reporters --reporter-options configFile=src/test/mochaConfig.json lib/test/**/*.js" }, "homepage": "https://github.com/microsoft/powerquery-language-services#readme", "repository": { @@ -24,7 +24,7 @@ "main": "lib/powerquery-language-services/index.js", "types": "lib/powerquery-language-services/index.d.ts", "engines": { - "node": ">=16.13.1" + "node": ">=22.0.0" }, "keywords": [ "power query", @@ -34,29 +34,30 @@ "lib/powerquery-language-services/**/*" ], "devDependencies": { - "@types/chai": "4.3.1", - "@types/mocha": "9.1.1", - "@types/node": "17.0.33", - "@typescript-eslint/eslint-plugin": "5.24.0", - "@typescript-eslint/parser": "5.24.0", - "chai": "4.3.6", - "eslint": "8.15.0", - "eslint-config-prettier": "8.5.0", - "eslint-plugin-prettier": "4.0.0", - "eslint-plugin-promise": "6.0.0", - "eslint-plugin-security": "1.5.0", + "@types/chai": "^4.3.20", + "@types/mocha": "^10.0.8", + "@types/node": "^22.7.3", + "@typescript-eslint/eslint-plugin": "^8.43.0", + "@typescript-eslint/parser": "^8.43.0", + "chai": "^4.5.0", + "cross-env": "^7.0.3", + "eslint": "^8.57.1", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-prettier": "^5.2.1", + "eslint-plugin-promise": "^7.1.0", + "eslint-plugin-security": "^3.0.1", "mocha": "^10.8.2", - "mocha-junit-reporter": "2.0.2", - "mocha-multi-reporters": "1.5.1", - "prettier": "2.6.2", - "ts-loader": "9.3.0", - "ts-node": "10.7.0", - "typescript": "4.6.4" + "mocha-junit-reporter": "^2.2.1", + "mocha-multi-reporters": "^1.5.1", + "prettier": "^3.3.3", + "ts-loader": "^9.5.1", + "ts-node": "^10.9.2", + "typescript": "^5.9.2" }, "dependencies": { "@microsoft/powerquery-formatter": "0.3.17", "@microsoft/powerquery-parser": "0.18.1", - "vscode-languageserver-textdocument": "1.0.4", - "vscode-languageserver-types": "3.17.1" + "vscode-languageserver-textdocument": "1.0.12", + "vscode-languageserver-types": "3.17.5" } } diff --git a/src/powerquery-language-services/inspection/type/inspectType/inspectTypeTBinOpExpression.ts b/src/powerquery-language-services/inspection/type/inspectType/inspectTypeTBinOpExpression.ts index 57ec052c..b6a9b84b 100644 --- a/src/powerquery-language-services/inspection/type/inspectType/inspectTypeTBinOpExpression.ts +++ b/src/powerquery-language-services/inspection/type/inspectType/inspectTypeTBinOpExpression.ts @@ -68,7 +68,8 @@ export async function inspectTypeTBinOpExpression( if (allowedTypeKinds === undefined) { result = Type.NoneInstance; } else if (allowedTypeKinds.size === 1) { - result = TypeUtils.primitiveType(leftType.isNullable, allowedTypeKinds.values().next().value); + const [firstValue] = Array.from(allowedTypeKinds); + result = TypeUtils.primitiveType(leftType.isNullable, firstValue); } else { const unionedTypePairs: Type.TPowerQueryType[] = []; diff --git a/src/test/testConstants.ts b/src/test/testConstants.ts index fbb405e8..345195c3 100644 --- a/src/test/testConstants.ts +++ b/src/test/testConstants.ts @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import * as StandardLibrarySymbols from "./standard-library-symbols-en-us.json"; +import StandardLibrarySymbols from "./standard-library-symbols-en-us.json"; import * as PQP from "@microsoft/powerquery-parser"; import { Type, TypeUtils } from "@microsoft/powerquery-parser/lib/powerquery-parser/language"; diff --git a/tsconfig.json b/tsconfig.json index 5958ec35..12faf7a9 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,6 +4,7 @@ "downlevelIteration": true, "incremental": true, "module": "commonjs", + "moduleResolution": "node", "noEmitOnError": true, "noFallthroughCasesInSwitch": true, "noImplicitOverride": true, @@ -15,9 +16,16 @@ "rootDir": "src", "sourceMap": true, "strict": true, - "target": "es6", - "tsBuildInfoFile": "./tsconfig.tsbuildinfo" + "target": "es2022", + "tsBuildInfoFile": "./tsconfig.tsbuildinfo", + "esModuleInterop": true, + "allowSyntheticDefaultImports": true }, "include": ["src/**/*.ts"], - "exclude": ["node_modules"] + "exclude": ["node_modules"], + "ts-node": { + "compilerOptions": { + "module": "commonjs" + } + } } From ff221af269e9022336f730fb3d1f0c81ea3604d7 Mon Sep 17 00:00:00 2001 From: Matt Masson Date: Mon, 8 Sep 2025 14:42:40 -0400 Subject: [PATCH 03/42] add undefined check --- .../type/inspectType/inspectTypeTBinOpExpression.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/powerquery-language-services/inspection/type/inspectType/inspectTypeTBinOpExpression.ts b/src/powerquery-language-services/inspection/type/inspectType/inspectTypeTBinOpExpression.ts index b6a9b84b..33a53676 100644 --- a/src/powerquery-language-services/inspection/type/inspectType/inspectTypeTBinOpExpression.ts +++ b/src/powerquery-language-services/inspection/type/inspectType/inspectTypeTBinOpExpression.ts @@ -69,7 +69,11 @@ export async function inspectTypeTBinOpExpression( result = Type.NoneInstance; } else if (allowedTypeKinds.size === 1) { const [firstValue] = Array.from(allowedTypeKinds); - result = TypeUtils.primitiveType(leftType.isNullable, firstValue); + if (firstValue !== undefined) { + result = TypeUtils.primitiveType(leftType.isNullable, firstValue); + } else { + result = Type.NoneInstance; + } } else { const unionedTypePairs: Type.TPowerQueryType[] = []; From c0fc7c2069510b7e418c917905b7cf28afaa3b2b Mon Sep 17 00:00:00 2001 From: Matt Masson Date: Mon, 8 Sep 2025 15:14:47 -0400 Subject: [PATCH 04/42] fix mocha --- .mocharc.json | 5 +++++ mochaReporterConfig.json | 3 +++ package-lock.json | 4 ++-- package.json | 6 +++--- .../providers/languageCompletionItemProvider.ts | 6 +++++- .../providers/libraryProvider.ts | 4 +++- .../localDocumentProvider.ts | 17 +++++++++++------ ...owLanguageAutocompleteItemProviderFn.test.ts | 5 ++++- src/test/providers/slowLibraryProvider.test.ts | 5 ++++- .../providers/slowLocalDocumentProvider.test.ts | 11 ++++------- 10 files changed, 44 insertions(+), 22 deletions(-) create mode 100644 .mocharc.json create mode 100644 mochaReporterConfig.json diff --git a/.mocharc.json b/.mocharc.json new file mode 100644 index 00000000..916bf7ff --- /dev/null +++ b/.mocharc.json @@ -0,0 +1,5 @@ +{ + "require": ["ts-node/register"], + "extensions": ["ts"], + "spec": "src/test/**/*.test.ts" +} diff --git a/mochaReporterConfig.json b/mochaReporterConfig.json new file mode 100644 index 00000000..c06f661e --- /dev/null +++ b/mochaReporterConfig.json @@ -0,0 +1,3 @@ +{ + "reporterEnabled": "spec, mocha-junit-reporter" +} diff --git a/package-lock.json b/package-lock.json index e28b3cdd..14fbc4c2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,12 +31,12 @@ "mocha-junit-reporter": "^2.2.1", "mocha-multi-reporters": "^1.5.1", "prettier": "^3.3.3", - "ts-loader": "^9.5.1", + "ts-loader": "^9.5.4", "ts-node": "^10.9.2", "typescript": "^5.9.2" }, "engines": { - "node": ">=22.0.0" + "node": ">=22" } }, "node_modules/@cspotcode/source-map-support": { diff --git a/package.json b/package.json index c3d1fdde..1904447b 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "link:start": "npm link && npm uninstall @microsoft/powerquery-parser @microsoft/powerquery-formatter && git clean -xdf && npm install && npm link @microsoft/powerquery-parser @microsoft/powerquery-formatter", "link:stop": "npm unlink @microsoft/powerquery-parser @microsoft/powerquery-formatter && git clean -xdf && npm install && npm install @microsoft/powerquery-parser@latest @microsoft/powerquery-formatter@latest --save-exact", "lint": "eslint src --ext ts", - "test": "npm run build && mocha --reporter mocha-multi-reporters --reporter-options configFile=src/test/mochaConfig.json lib/test/**/*.js" + "test": "mocha --no-package --reporter mocha-multi-reporters --reporter-options configFile=mochaReporterConfig.json" }, "homepage": "https://github.com/microsoft/powerquery-language-services#readme", "repository": { @@ -24,7 +24,7 @@ "main": "lib/powerquery-language-services/index.js", "types": "lib/powerquery-language-services/index.d.ts", "engines": { - "node": ">=22.0.0" + "node": ">=22" }, "keywords": [ "power query", @@ -50,7 +50,7 @@ "mocha-junit-reporter": "^2.2.1", "mocha-multi-reporters": "^1.5.1", "prettier": "^3.3.3", - "ts-loader": "^9.5.1", + "ts-loader": "^9.5.4", "ts-node": "^10.9.2", "typescript": "^5.9.2" }, diff --git a/src/powerquery-language-services/providers/languageCompletionItemProvider.ts b/src/powerquery-language-services/providers/languageCompletionItemProvider.ts index 8664c9af..b162ed73 100644 --- a/src/powerquery-language-services/providers/languageCompletionItemProvider.ts +++ b/src/powerquery-language-services/providers/languageCompletionItemProvider.ts @@ -26,7 +26,11 @@ export class LanguageAutocompleteItemProvider implements IAutocompleteItemProvid KeywordKind.HashTime, ]; - constructor(protected readonly locale: string) {} + protected readonly locale: string; + + constructor(locale: string) { + this.locale = locale; + } public async getAutocompleteItems( context: AutocompleteItemProviderContext, diff --git a/src/powerquery-language-services/providers/libraryProvider.ts b/src/powerquery-language-services/providers/libraryProvider.ts index 81fc7136..de4d7696 100644 --- a/src/powerquery-language-services/providers/libraryProvider.ts +++ b/src/powerquery-language-services/providers/libraryProvider.ts @@ -19,10 +19,12 @@ import { ProviderTraceConstant } from "../trace"; export class LibraryProvider implements ILibraryProvider { public readonly library: Library.ILibrary; + protected readonly locale: string; protected readonly signatureInformationByLabel: Map; - constructor(library: Library.ILibrary, protected readonly locale: string) { + constructor(library: Library.ILibrary, locale: string) { this.library = library; + this.locale = locale; this.signatureInformationByLabel = new Map(); } diff --git a/src/powerquery-language-services/providers/localDocumentProvider/localDocumentProvider.ts b/src/powerquery-language-services/providers/localDocumentProvider/localDocumentProvider.ts index a2febf5b..0377eadb 100644 --- a/src/powerquery-language-services/providers/localDocumentProvider/localDocumentProvider.ts +++ b/src/powerquery-language-services/providers/localDocumentProvider/localDocumentProvider.ts @@ -31,12 +31,17 @@ import { ProviderTraceConstant } from "../../trace"; import { ScopeUtils } from "../../inspection"; export class LocalDocumentProvider implements ILocalDocumentProvider { - constructor( - private readonly uri: DocumentUri, - private readonly typeCache: Inspection.TypeCache, - private readonly library: ILibrary, - protected readonly locale: string, - ) {} + private readonly uri: DocumentUri; + private readonly typeCache: Inspection.TypeCache; + private readonly library: ILibrary; + protected readonly locale: string; + + constructor(uri: DocumentUri, typeCache: Inspection.TypeCache, library: ILibrary, locale: string) { + this.uri = uri; + this.typeCache = typeCache; + this.library = library; + this.locale = locale; + } public getAutocompleteItems( context: AutocompleteItemProviderContext, diff --git a/src/test/providers/slowLanguageAutocompleteItemProviderFn.test.ts b/src/test/providers/slowLanguageAutocompleteItemProviderFn.test.ts index adfdb652..2aed190c 100644 --- a/src/test/providers/slowLanguageAutocompleteItemProviderFn.test.ts +++ b/src/test/providers/slowLanguageAutocompleteItemProviderFn.test.ts @@ -10,8 +10,11 @@ import { } from "../../powerquery-language-services"; export class SlowLanguageAutocompleteItemProvider extends LanguageAutocompleteItemProvider { - constructor(locale: string, private readonly delayInMs: number) { + private readonly delayInMs: number; + + constructor(locale: string, delayInMs: number) { super(locale); + this.delayInMs = delayInMs; } public override async getAutocompleteItems( diff --git a/src/test/providers/slowLibraryProvider.test.ts b/src/test/providers/slowLibraryProvider.test.ts index 269548d4..68de34d4 100644 --- a/src/test/providers/slowLibraryProvider.test.ts +++ b/src/test/providers/slowLibraryProvider.test.ts @@ -15,8 +15,11 @@ import { import { ILibrary } from "../../powerquery-language-services/library/library"; export class SlowLibraryProvider extends LibraryProvider { - constructor(library: ILibrary, locale: string, private readonly delayInMs: number) { + private readonly delayInMs: number; + + constructor(library: ILibrary, locale: string, delayInMs: number) { super(library, locale); + this.delayInMs = delayInMs; } public override async getAutocompleteItems( diff --git a/src/test/providers/slowLocalDocumentProvider.test.ts b/src/test/providers/slowLocalDocumentProvider.test.ts index 41fb1af1..6032358c 100644 --- a/src/test/providers/slowLocalDocumentProvider.test.ts +++ b/src/test/providers/slowLocalDocumentProvider.test.ts @@ -21,14 +21,11 @@ import { ILibrary } from "../../powerquery-language-services/library/library"; import { TypeCache } from "../../powerquery-language-services/inspection"; export class SlowLocalDocumentProvider extends LocalDocumentProvider { - constructor( - uri: string, - typeCache: TypeCache, - library: ILibrary, - locale: string, - private readonly delayInMs: number, - ) { + private readonly delayInMs: number; + + constructor(uri: string, typeCache: TypeCache, library: ILibrary, locale: string, delayInMs: number) { super(uri, typeCache, library, locale); + this.delayInMs = delayInMs; } public override async getAutocompleteItems( From ecef8cd78f7a6274fd112b5bf3edff4e6b14fa6c Mon Sep 17 00:00:00 2001 From: Matt Masson Date: Mon, 8 Sep 2025 15:23:23 -0400 Subject: [PATCH 05/42] fix launch config --- .vscode/launch.json | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 27bc38fb..1fa50d71 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -7,17 +7,16 @@ { "type": "node", "request": "launch", - "name": "Run unit tests", + "name": "Run unit tests (npm test)", "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", - "args": [ - "--colors", - "--timeout", - "999999", - "-r", - "ts-node/register", - "${workspaceFolder}/src/test/**/*.ts" - ], - "internalConsoleOptions": "openOnSessionStart" + "args": ["--colors", "--no-package", "--reporter", "spec"], + "cwd": "${workspaceFolder}", + "internalConsoleOptions": "openOnSessionStart", + "skipFiles": ["/**"], + "env": { + "NODE_ENV": "test" + }, + "runtimeArgs": ["--no-warnings"] } ] } From a3ce0eefa96f016659c0001d0f98ddcb87d45951 Mon Sep 17 00:00:00 2001 From: Matt Masson Date: Mon, 8 Sep 2025 15:27:20 -0400 Subject: [PATCH 06/42] enable mocha tester in vscode --- .vscode/extensions.json | 2 ++ .vscode/settings.json | 19 ++++++++++++++++++- .vscode/settings.json.test | 22 ++++++++++++++++++++++ 3 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 .vscode/settings.json.test diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 68588daa..705598db 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -2,6 +2,8 @@ "recommendations": [ "dbaeumer.vscode-eslint", "esbenp.prettier-vscode", + "hbenl.vscode-mocha-test-adapter", + "ms-vscode.test-adapter-converter" ], "unwantedRecommendations": [] } diff --git a/.vscode/settings.json b/.vscode/settings.json index 40999991..ba9cdbb5 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,5 +3,22 @@ "files.exclude": { "src/**/*.js": true }, - "editor.formatOnSave": true + "editor.formatOnSave": true, + + // VS Code Test Runner configuration + "testing.openTesting": "openOnTestStart", + "testing.automaticallyOpenPeekView": "failureInVisibleDocument", + "testing.defaultGutterClickAction": "run", + "testing.followRunningTest": true, + + // Mocha Explorer settings + "mochaExplorer.files": ["src/test/**/*.test.ts"], + "mochaExplorer.require": ["ts-node/register"], + "mochaExplorer.env": { + "NODE_ENV": "test" + }, + "mochaExplorer.timeout": 60000, + "mochaExplorer.ui": "bdd", + "mochaExplorer.exit": true, + "mochaExplorer.optsFile": ".mocharc.json" } diff --git a/.vscode/settings.json.test b/.vscode/settings.json.test new file mode 100644 index 00000000..dcc466f0 --- /dev/null +++ b/.vscode/settings.json.test @@ -0,0 +1,22 @@ +{ + // VS Code Test Runner configuration + "testing.openTesting": "openOnTestStart", + "testing.automaticallyOpenPeekView": "failureInVisibleDocument", + "testing.defaultGutterClickAction": "run", + "testing.followRunningTest": true, + + // Mocha-specific settings + "mochaExplorer.files": [ + "src/test/**/*.test.ts" + ], + "mochaExplorer.require": [ + "ts-node/register" + ], + "mochaExplorer.env": { + "NODE_ENV": "test" + }, + "mochaExplorer.timeout": 60000, + "mochaExplorer.ui": "bdd", + "mochaExplorer.exit": true, + "mochaExplorer.optsFile": ".mocharc.json" +} From 77fe063ecbe22ea9b124d8b9f47641e8e911ae2e Mon Sep 17 00:00:00 2001 From: Matt Masson Date: Mon, 8 Sep 2025 15:29:22 -0400 Subject: [PATCH 07/42] 0.11.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 14fbc4c2..dac929d1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@microsoft/powerquery-language-services", - "version": "0.10.6", + "version": "0.11.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@microsoft/powerquery-language-services", - "version": "0.10.6", + "version": "0.11.0", "license": "MIT", "dependencies": { "@microsoft/powerquery-formatter": "0.3.17", diff --git a/package.json b/package.json index 1904447b..6e9cc6bd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@microsoft/powerquery-language-services", - "version": "0.10.6", + "version": "0.11.0", "author": "Microsoft", "license": "MIT", "scripts": { From d2c6085758f6bfc58186a24566dbaf4403988669 Mon Sep 17 00:00:00 2001 From: Matt Masson Date: Mon, 8 Sep 2025 15:40:55 -0400 Subject: [PATCH 08/42] simplify --- .../inspection/type/inspectType/inspectTypeTBinOpExpression.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/powerquery-language-services/inspection/type/inspectType/inspectTypeTBinOpExpression.ts b/src/powerquery-language-services/inspection/type/inspectType/inspectTypeTBinOpExpression.ts index 33a53676..3007aca2 100644 --- a/src/powerquery-language-services/inspection/type/inspectType/inspectTypeTBinOpExpression.ts +++ b/src/powerquery-language-services/inspection/type/inspectType/inspectTypeTBinOpExpression.ts @@ -68,7 +68,7 @@ export async function inspectTypeTBinOpExpression( if (allowedTypeKinds === undefined) { result = Type.NoneInstance; } else if (allowedTypeKinds.size === 1) { - const [firstValue] = Array.from(allowedTypeKinds); + const firstValue = allowedTypeKinds.values().next().value; if (firstValue !== undefined) { result = TypeUtils.primitiveType(leftType.isNullable, firstValue); } else { From cebbf1da120ea76c9e4f031d7bdf58c81aafbf21 Mon Sep 17 00:00:00 2001 From: Matt Masson Date: Mon, 8 Sep 2025 15:48:54 -0400 Subject: [PATCH 09/42] try reporting test results --- .github/workflows/pr-gated.yml | 25 ++++++++++++++++++++++++- mochaReporterConfig.json | 5 ++++- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pr-gated.yml b/.github/workflows/pr-gated.yml index bcd3ab8e..1fe878f8 100644 --- a/.github/workflows/pr-gated.yml +++ b/.github/workflows/pr-gated.yml @@ -6,6 +6,10 @@ on: jobs: build-and-test: runs-on: windows-latest + permissions: + contents: read + actions: read + checks: write steps: - name: checkout uses: actions/checkout@v5 @@ -17,4 +21,23 @@ jobs: - run: npm ci - run: npm audit - run: npm run build - - run: npm run test + - name: Run tests + run: npm run test > test-output.txt 2>&1 + continue-on-error: true + - name: Create test summary + if: always() + run: | + echo "## Test Results" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + Get-Content test-output.txt | Select-Object -Last 10 >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + shell: pwsh + - name: Publish test results + uses: dorny/test-reporter@v2.1.1 + if: success() || failure() + with: + name: Mocha Tests + path: test-results.xml + reporter: java-junit + fail-on-error: true diff --git a/mochaReporterConfig.json b/mochaReporterConfig.json index c06f661e..0a341481 100644 --- a/mochaReporterConfig.json +++ b/mochaReporterConfig.json @@ -1,3 +1,6 @@ { - "reporterEnabled": "spec, mocha-junit-reporter" + "reporterEnabled": "spec, mocha-junit-reporter", + "mochaJunitReporterReporterOptions": { + "mochaFile": "test-results.xml" + } } From 6668ab8ffcd062a0602c661fd5184cd500476bac Mon Sep 17 00:00:00 2001 From: Matt Masson Date: Mon, 8 Sep 2025 15:52:59 -0400 Subject: [PATCH 10/42] simplify test summary --- .github/workflows/pr-gated.yml | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/.github/workflows/pr-gated.yml b/.github/workflows/pr-gated.yml index 1fe878f8..bf78da5e 100644 --- a/.github/workflows/pr-gated.yml +++ b/.github/workflows/pr-gated.yml @@ -22,17 +22,7 @@ jobs: - run: npm audit - run: npm run build - name: Run tests - run: npm run test > test-output.txt 2>&1 - continue-on-error: true - - name: Create test summary - if: always() - run: | - echo "## Test Results" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - Get-Content test-output.txt | Select-Object -Last 10 >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - shell: pwsh + run: npm run test - name: Publish test results uses: dorny/test-reporter@v2.1.1 if: success() || failure() From 83f5100b719357f2cb2c0dbc110e0288e4d1fd96 Mon Sep 17 00:00:00 2001 From: Matt Masson Date: Mon, 8 Sep 2025 17:46:29 -0400 Subject: [PATCH 11/42] checkpoint --- .copilot-instructions.md | 58 +++ .gitignore | 3 + .../invokeExpression/invokeExpression.ts | 8 + .../inspectionSettings.ts | 2 + .../validate/validate.ts | 21 +- .../validate/validateFunctionExpression.ts | 12 +- .../validate/validateInvokeExpression.ts | 20 +- .../validate/validateUnknownIdentifiers.ts | 2 + .../validationSettings/validationSettings.ts | 5 +- src/test/files/LargeSectionDocument.pq | 94 +++++ .../LargeSectionDocument_WithDiagnostics.pq | 359 +++++++++++++++++ .../LargeSectionDocument_WithParserError.pq | 166 ++++++++ src/test/validation/asyncValidation.test.ts | 376 ++++++++++++++++++ 13 files changed, 1115 insertions(+), 11 deletions(-) create mode 100644 .copilot-instructions.md create mode 100644 src/test/files/LargeSectionDocument.pq create mode 100644 src/test/files/LargeSectionDocument_WithDiagnostics.pq create mode 100644 src/test/files/LargeSectionDocument_WithParserError.pq create mode 100644 src/test/validation/asyncValidation.test.ts diff --git a/.copilot-instructions.md b/.copilot-instructions.md new file mode 100644 index 00000000..c0b33ea6 --- /dev/null +++ b/.copilot-instructions.md @@ -0,0 +1,58 @@ +# PowerQuery Language Services Local Instructions + +## Project Overview + +This package provides intellisense functionality for the Power Query / M language. It is consumed through: + +- Applications using `monaco-editor` +- VS Code Language Server Protocol extension + +## Current Development Focus + +- Improving async processing of validation code path +- Enhancing cancellation token support for large file validation +- Addressing performance issues with large M documents + +## Key Architecture Points + +### Validation System + +- Main validation logic in `src\powerquery-language-services\validate\validate.ts` +- ValidationSettings includes cancellationToken support +- Current implementation has synchronous bottlenecks preventing effective cancellation +- Performance degrades significantly with large files (30+ seconds for complex documents) + +### Testing Patterns + +- Validation tests located in `src\test\validation\` +- Common utilities in `src\test\testUtils\validationTestUtils.ts` +- Test files for validation in `src\test\files\` +- Follow existing mocha patterns and style conventions + +### Critical Files for Async Validation Work + +- `src\powerquery-language-services\validate\validate.ts` - Main validation logic +- `src\powerquery-language-services\validate\validationSettings.ts` - Settings interface +- `src\powerquery-language-services\analysis\` - Analysis utilities used by validation +- `src\powerquery-language-services\inspection\` - Type inspection system + +## Development Guidelines + +- Maintain backward compatibility +- Ensure cancellation is graceful and doesn't leave inconsistent state +- Follow existing code patterns and style +- Add comprehensive tests for async behavior +- Use .copilot-current-task.md for task-specific tracking + +## Common Issues + +- Large M documents (like Kusto.pq example) take 30+ seconds to validate +- Cancellation tokens not effectively checked during validation processing +- Synchronous operations block proper async flow + +## Testing Strategy + +- Create complex test documents that demonstrate performance issues +- Test cancellation behavior with long-running validation +- Ensure existing validation functionality remains intact +- Measure performance improvements after async enhancements diff --git a/.gitignore b/.gitignore index a18ad0bf..3db78efc 100644 --- a/.gitignore +++ b/.gitignore @@ -48,6 +48,9 @@ Generated\ Files/ TestResult.xml nunit-*.xml +# Task-specific temporary files +.copilot-current-task.md + # Build Results of an ATL Project [Dd]ebugPS/ [Rr]eleasePS/ diff --git a/src/powerquery-language-services/inspection/invokeExpression/invokeExpression.ts b/src/powerquery-language-services/inspection/invokeExpression/invokeExpression.ts index 3b89da22..d1a11a08 100644 --- a/src/powerquery-language-services/inspection/invokeExpression/invokeExpression.ts +++ b/src/powerquery-language-services/inspection/invokeExpression/invokeExpression.ts @@ -93,6 +93,8 @@ async function inspectInvokeExpression( await tryType(settings, nodeIdMapCollection, previousNode.node.id, typeCache), ); + settings.cancellationToken?.throwIfCancelled(); + let invokeExpressionArgs: InvokeExpressionArguments | undefined; if (TypeUtils.isDefinedFunction(functionType)) { @@ -110,6 +112,8 @@ async function inspectInvokeExpression( const givenArguments: ReadonlyArray = iterableArguments.slice(0, givenArgumentTypes.length); + settings.cancellationToken?.throwIfCancelled(); + const [numMinExpectedArguments, numMaxExpectedArguments]: [number, number] = getNumExpectedArguments(functionType); @@ -166,6 +170,8 @@ async function getIsNameInLocalScope( initialCorrelationId: trace.id, }; + settings.cancellationToken?.throwIfCancelled(); + // Try to find out if the identifier is a local or external name. if (name !== undefined) { // Seed local scope @@ -211,6 +217,8 @@ async function getArgumentTypes( const result: Type.TPowerQueryType[] = []; for (const xorNode of argXorNodes) { + settings.cancellationToken?.throwIfCancelled(); + // eslint-disable-next-line no-await-in-loop const triedArgType: TriedType = await tryType(settings, nodeIdMapCollection, xorNode.node.id, typeCache); diff --git a/src/powerquery-language-services/inspectionSettings.ts b/src/powerquery-language-services/inspectionSettings.ts index 91ea4007..22fc546a 100644 --- a/src/powerquery-language-services/inspectionSettings.ts +++ b/src/powerquery-language-services/inspectionSettings.ts @@ -33,4 +33,6 @@ export interface InspectionSettings extends PQP.Settings { // While useful for in-depth analysis and/or Intellisense operations they can be costly in terms of time. // Changing the strategy determines how types are evaluated during inspections. readonly typeStrategy: TypeStrategy; + // Cancellation token for long-running inspection operations + readonly cancellationToken: PQP.ICancellationToken | undefined; } diff --git a/src/powerquery-language-services/validate/validate.ts b/src/powerquery-language-services/validate/validate.ts index 5cf9577c..20511996 100644 --- a/src/powerquery-language-services/validate/validate.ts +++ b/src/powerquery-language-services/validate/validate.ts @@ -35,7 +35,16 @@ export function validate( initialCorrelationId: trace.id, }; - const analysis: Analysis = AnalysisUtils.analysis(textDocument, analysisSettings); + // Create analysis settings with the updated validation settings (which include cancellation token) + const updatedAnalysisSettings: AnalysisSettings = { + ...analysisSettings, + inspectionSettings: updatedSettings, + }; + + const analysis: Analysis = AnalysisUtils.analysis(textDocument, updatedAnalysisSettings); + + validationSettings.cancellationToken?.throwIfCancelled(); + const parseState: ParseState | undefined = ResultUtils.assertOk(await analysis.getParseState()); const parseError: ParseError.ParseError | undefined = ResultUtils.assertOk(await analysis.getParseError()); @@ -45,6 +54,8 @@ export function validate( return undefined; } + validationSettings.cancellationToken?.throwIfCancelled(); + let functionExpressionDiagnostics: Diagnostic[]; let invokeExpressionDiagnostics: Diagnostic[]; let unknownIdentifiersDiagnostics: Diagnostic[]; @@ -53,7 +64,11 @@ export function validate( const typeCache: TypeCache = analysis.getTypeCache(); if (validationSettings.checkInvokeExpressions && nodeIdMapCollection) { - functionExpressionDiagnostics = validateFunctionExpression(validationSettings, nodeIdMapCollection); + validationSettings.cancellationToken?.throwIfCancelled(); + + functionExpressionDiagnostics = await validateFunctionExpression(validationSettings, nodeIdMapCollection); + + validationSettings.cancellationToken?.throwIfCancelled(); invokeExpressionDiagnostics = await validateInvokeExpression( validationSettings, @@ -66,6 +81,8 @@ export function validate( } if (validationSettings.checkUnknownIdentifiers && nodeIdMapCollection) { + validationSettings.cancellationToken?.throwIfCancelled(); + unknownIdentifiersDiagnostics = await validateUnknownIdentifiers( validationSettings, nodeIdMapCollection, diff --git a/src/powerquery-language-services/validate/validateFunctionExpression.ts b/src/powerquery-language-services/validate/validateFunctionExpression.ts index cfab5de7..70796451 100644 --- a/src/powerquery-language-services/validate/validateFunctionExpression.ts +++ b/src/powerquery-language-services/validate/validateFunctionExpression.ts @@ -20,10 +20,10 @@ import { ValidationSettings } from "./validationSettings"; import { ValidationTraceConstant } from "../trace"; // Check for repeat parameter names for FunctionExpressions. -export function validateFunctionExpression( +export async function validateFunctionExpression( validationSettings: ValidationSettings, nodeIdMapCollection: NodeIdMap.Collection, -): Diagnostic[] { +): Promise { const trace: Trace = validationSettings.traceManager.entry( ValidationTraceConstant.Validation, validateFunctionExpression.name, @@ -50,7 +50,9 @@ export function validateFunctionExpression( for (const nodeId of fnExpressionIds) { validationSettings.cancellationToken?.throwIfCancelled(); - diagnostics.push(validateNoDuplicateParameter(updatedSettings, nodeIdMapCollection, nodeId)); + const nodeDiagnostics = await validateNoDuplicateParameter(updatedSettings, nodeIdMapCollection, nodeId); + diagnostics.push(nodeDiagnostics); + updatedSettings.cancellationToken?.throwIfCancelled(); } @@ -59,11 +61,11 @@ export function validateFunctionExpression( return diagnostics.flat(); } -function validateNoDuplicateParameter( +async function validateNoDuplicateParameter( validationSettings: ValidationSettings, nodeIdMapCollection: NodeIdMap.Collection, fnExpressionId: number, -): Diagnostic[] { +): Promise { const fnExpression: TXorNode = NodeIdMapUtils.assertXorChecked( nodeIdMapCollection, fnExpressionId, diff --git a/src/powerquery-language-services/validate/validateInvokeExpression.ts b/src/powerquery-language-services/validate/validateInvokeExpression.ts index 37f254e9..b9296ac5 100644 --- a/src/powerquery-language-services/validate/validateInvokeExpression.ts +++ b/src/powerquery-language-services/validate/validateInvokeExpression.ts @@ -49,7 +49,15 @@ export async function validateInvokeExpression( inspectionTasks.push(Inspection.tryInvokeExpression(updatedSettings, nodeIdMapCollection, nodeId, typeCache)); } - const inspections: ReadonlyArray = await Promise.all(inspectionTasks); + // Process inspections with cancellation support + const inspections: Inspection.TriedInvokeExpression[] = []; + for (const task of inspectionTasks) { + validationSettings.cancellationToken?.throwIfCancelled(); + + // eslint-disable-next-line no-await-in-loop + const inspection = await task; + inspections.push(inspection); + } const diagnosticTasks: Promise>[] = []; @@ -67,7 +75,15 @@ export async function validateInvokeExpression( } } - const diagnostics: ReadonlyArray> = await Promise.all(diagnosticTasks); + // Process diagnostics with cancellation support + const diagnostics: ReadonlyArray[] = []; + for (const task of diagnosticTasks) { + validationSettings.cancellationToken?.throwIfCancelled(); + + // eslint-disable-next-line no-await-in-loop + const diagnostic = await task; + diagnostics.push(diagnostic); + } trace.exit(); return diagnostics.flat(); diff --git a/src/powerquery-language-services/validate/validateUnknownIdentifiers.ts b/src/powerquery-language-services/validate/validateUnknownIdentifiers.ts index e7eaebf1..ed08264a 100644 --- a/src/powerquery-language-services/validate/validateUnknownIdentifiers.ts +++ b/src/powerquery-language-services/validate/validateUnknownIdentifiers.ts @@ -130,6 +130,8 @@ function findUnknownIdentifiers( const unknownIdentifiers: UnknownIdentifier[] = []; for (const identifierWithNodeScope of identifiersWithNodeScope) { + validationSettings.cancellationToken?.throwIfCancelled(); + if (ResultUtils.isError(identifierWithNodeScope.triedNodeScope)) { continue; } diff --git a/src/powerquery-language-services/validate/validationSettings/validationSettings.ts b/src/powerquery-language-services/validate/validationSettings/validationSettings.ts index 527bd45c..a90cf2e0 100644 --- a/src/powerquery-language-services/validate/validationSettings/validationSettings.ts +++ b/src/powerquery-language-services/validate/validationSettings/validationSettings.ts @@ -1,17 +1,18 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import { ICancellationToken } from "@microsoft/powerquery-parser"; +import * as PQP from "@microsoft/powerquery-parser"; import { InspectionSettings } from "../../inspectionSettings"; import { Library } from "../../library"; export interface ValidationSettings extends InspectionSettings { - readonly cancellationToken: ICancellationToken | undefined; readonly checkForDuplicateIdentifiers: boolean; readonly checkInvokeExpressions: boolean; readonly checkUnknownIdentifiers: boolean; readonly isWorkspaceCacheAllowed: boolean; readonly library: Library.ILibrary; readonly source: string; + // Keep cancellationToken here for backward compatibility + readonly cancellationToken: PQP.ICancellationToken | undefined; } diff --git a/src/test/files/LargeSectionDocument.pq b/src/test/files/LargeSectionDocument.pq new file mode 100644 index 00000000..13c01076 --- /dev/null +++ b/src/test/files/LargeSectionDocument.pq @@ -0,0 +1,94 @@ +[Version = "1.0.0"] +section LargeSectionDocument; + +// This is a complex M section document designed to test validation performance +// It contains many functions and expressions - but NO parsing or diagnostic errors + +// Constants and basic values +BaseUrl = "https://api.example.com/v1/"; +MaxRetries = 5; +DefaultTimeout = 30; + +// Complex mathematical functions +MathematicalOperations = [ + // Calculate moving average + CalculateMovingAverage = (values as list, windowSize as number) as list => + let + ValuesCount = List.Count(values), + MovingAverages = List.Transform( + List.Numbers(0, ValuesCount - windowSize + 1), + (index) => + let + WindowValues = List.Range(values, index, windowSize), + Average = List.Average(WindowValues) + in + Average + ) + in + MovingAverages, + + // Calculate standard deviation + CalculateStandardDeviation = (values as list) as number => + let + Count = List.Count(values), + Mean = List.Average(values), + SquaredDifferences = List.Transform(values, each Number.Power(_ - Mean, 2)), + Variance = List.Sum(SquaredDifferences) / (Count - 1), + StandardDeviation = Number.Sqrt(Variance) + in + StandardDeviation +]; + +// Main data processing functions +shared LargeSectionDocument.CalculateStatistics = (data as list) as record => + [ + MovingAverage = MathematicalOperations[CalculateMovingAverage](data, 5), + StandardDeviation = MathematicalOperations[CalculateStandardDeviation](data), + Mean = List.Average(data), + Median = List.Median(data), + Min = List.Min(data), + Max = List.Max(data) + ]; + +// Additional complex functions to increase processing time +shared ComplexFunction1 = (param1 as text, param2 as number, param3 as logical) as table => + let + Source = #table({"Column1", "Column2", "Column3"}, {{param1, param2, param3}}), + Step1 = Table.AddColumn(Source, "NewCol", each Text.Length([Column1]) + [Column2]), + Step2 = Table.TransformColumns(Step1, {{"NewCol", each _ * 2}}), + Step3 = Table.SelectRows(Step2, each [Column3] = param3), + Result = Table.Sort(Step3, {{"NewCol", Order.Descending}}) + in + Result; + +shared ComplexFunction2 = (data as table, config as record) as table => + let + ColumnNames = Table.ColumnNames(data), + FirstColumn = Table.Column(data, ColumnNames{0}), + SecondColumn = if List.Count(ColumnNames) > 1 then Table.Column(data, ColumnNames{1}) else {}, + Processed1 = List.Transform(FirstColumn, each Text.From(_)), + Processed2 = List.Transform(SecondColumn, each Number.From(_)), + CombinedTable = #table({"Proc1", "Proc2"}, List.Zip({Processed1, Processed2})), + Aggregated = Table.Group(CombinedTable, {"Proc1"}, {{"Proc2Sum", each List.Sum([Proc2]), type number}}), + Final = Table.AddColumn(Aggregated, "Calculated", each [Proc1] & " = " & Text.From([Proc2Sum])) + in + Final; + +shared ComplexFunction3 = (inputList as list, operations as list) as list => + let + ProcessOperation = (current as list, operation as record) as list => + let + OperationType = Record.Field(operation, "Type"), + Parameter = Record.Field(operation, "Parameter") + in + if OperationType = "transform" then + List.Transform(current, each _ + Parameter) + else if OperationType = "filter" then + List.Select(current, each _ > Parameter) + else if OperationType = "sort" then + List.Sort(current) + else + current, + Result = List.Accumulate(operations, inputList, ProcessOperation) + in + Result; diff --git a/src/test/files/LargeSectionDocument_WithDiagnostics.pq b/src/test/files/LargeSectionDocument_WithDiagnostics.pq new file mode 100644 index 00000000..46ccf2b5 --- /dev/null +++ b/src/test/files/LargeSectionDocument_WithDiagnostics.pq @@ -0,0 +1,359 @@ +[Version = "1.0.0"] +section LargeSectionDocument_WithDiagnostics; + +// This is a complex M section document designed to test validation performance +// It contains many unknown identifiers and other diagnostic errors but NO parsing errors +// Constants and basic values +BaseUrl = "https://api.example.com/v1/"; +MaxRetries = 5; +DefaultTimeout = 30; + +// Complex functions with unknown identifiers to generate diagnostic errors +shared ComplexFunction1 = (param1 as text, param2 as number, param3 as logical) as table => + let + Source = UnknownTableFunction(param1, param2), + Step1 = Table.AddColumn(Source, "NewCol", each UnknownScalarFunction([Column1], [Column2])), + Step2 = Table.TransformColumns(Step1, {{"NewCol", each UnknownTransformFunction(_)}}), + Step3 = Table.SelectRows(Step2, each UnknownFilterFunction([NewCol], param3)), + Result = Table.Sort(Step3, {{"NewCol", UnknownOrderFunction}}) + in + Result; + +shared ComplexFunction2 = (data as table, config as record) as table => + let + Column1 = Table.Column(data, UnknownColumnName1), + Column2 = Table.Column(data, UnknownColumnName2), + Processed1 = List.Transform(Column1, each UnknownProcessor1(_)), + Processed2 = List.Transform(Column2, each UnknownProcessor2(_)), + CombinedTable = #table({"Proc1", "Proc2"}, List.Zip({Processed1, Processed2})), + Aggregated = Table.Group(CombinedTable, {"Proc1"}, {{"Proc2Sum", each List.Sum([Proc2]), type number}}), + Final = Table.AddColumn(Aggregated, "Calculated", each UnknownCalculation([Proc1], [Proc2Sum])) + in + Final; + +shared ComplexFunction3 = (inputList as list, operations as list) as list => + let + ProcessOperation = (current as list, operation as record) as list => + let + OperationType = Record.Field(operation, "Type"), Parameter = Record.Field(operation, "Parameter") + in + if OperationType = "unknown1" then + List.Transform(current, each UnknownFunction1(_, Parameter)) + else if OperationType = "unknown2" then + List.Select(current, each UnknownFunction2(_, Parameter)) + else if OperationType = "unknown3" then + List.Sort(current, each UnknownFunction3(_, Parameter)) + else + current, + Result = List.Accumulate(operations, inputList, ProcessOperation) + in + Result; + +shared ComplexFunction4 = (textData as text, patterns as list) as record => + let + ExtractPattern = (text as text, pattern as record) as any => + let + PatternType = Record.Field(pattern, "Type"), PatternValue = Record.Field(pattern, "Value") + in + if PatternType = "unknown" then + UnknownExtractor(textData, PatternValue) + else if PatternType = "custom" then + CustomUnknownExtractor(textData, PatternValue) + else + null, + Results = List.Transform(patterns, each ExtractPattern(textData, _)), + Summary = [ + TotalMatches = List.Count(List.Select(Results, each _ <> null)), + FirstMatch = UnknownFirstFunction(Results), + LastMatch = UnknownLastFunction(Results), + ProcessedResults = List.Transform(Results, each UnknownResultProcessor(_)) + ] + in + Summary; + +shared ComplexFunction5 = (numericData as list, analysisType as text) as record => + let + BasicStats = [ + Sum = List.Sum(numericData), + Average = List.Average(numericData), + Count = List.Count(numericData) + ], + AdvancedStats = + if analysisType = "advanced" then + [ + StandardDev = UnknownStandardDeviation(numericData), + Variance = UnknownVariance(numericData), + Median = UnknownMedian(numericData), + Quartiles = UnknownQuartiles(numericData), + Outliers = UnknownOutlierDetection(numericData) + ] + else + [], + CombinedStats = BasicStats & AdvancedStats, + ProcessedStats = Record.TransformFields( + CombinedStats, + { + {"Sum", each UnknownSumProcessor(_)}, + {"Average", each UnknownAverageProcessor(_)}, + {"Count", each UnknownCountProcessor(_)} + } + ) + in + ProcessedStats; + +shared MachineLearningPipeline = (trainingData as table, features as list, target as text) as record => + let + // Data preprocessing with unknown functions + CleanedData = UnknownDataCleaner(trainingData), + FeatureMatrix = UnknownFeatureExtractor(CleanedData, features), + NormalizedFeatures = UnknownNormalizer(FeatureMatrix), + EncodedFeatures = UnknownCategoricalEncoder(NormalizedFeatures), + // Feature selection with unknown functions + SelectedFeatures = UnknownFeatureSelector(EncodedFeatures, target), + ImportanceScores = UnknownFeatureImportance(SelectedFeatures, target), + // Model training with unknown functions + TrainTestSplit = UnknownTrainTestSplitter(SelectedFeatures, 0.8), + TrainingSet = TrainTestSplit[Training], + TestingSet = TrainTestSplit[Testing], + Models = [ + LinearRegression = UnknownLinearRegression(TrainingSet, target), + RandomForest = UnknownRandomForest(TrainingSet, target), + XGBoost = UnknownXGBoost(TrainingSet, target), + NeuralNetwork = UnknownNeuralNetwork(TrainingSet, target), + SVM = UnknownSVM(TrainingSet, target) + ], + // Model evaluation with unknown functions + Predictions = Record.TransformFields( + Models, + { + "LinearRegression", + each UnknownPredict(_, TestingSet), + "RandomForest", + each UnknownPredict(_, TestingSet), + "XGBoost", + each UnknownPredict(_, TestingSet), + "NeuralNetwork", + each UnknownPredict(_, TestingSet), + "SVM", + each UnknownPredict(_, TestingSet) + } + ), + Metrics = Record.TransformFields( + Predictions, + { + "LinearRegression", + each UnknownEvaluateModel(_, TestingSet[target]), + "RandomForest", + each UnknownEvaluateModel(_, TestingSet[target]), + "XGBoost", + each UnknownEvaluateModel(_, TestingSet[target]), + "NeuralNetwork", + each UnknownEvaluateModel(_, TestingSet[target]), + "SVM", + each UnknownEvaluateModel(_, TestingSet[target]) + } + ), + BestModel = UnknownModelSelector(Metrics), + Hyperparameters = UnknownHyperparameterTuner(BestModel, TrainingSet), + FinalModel = UnknownModelTrainer(BestModel, Hyperparameters, TrainingSet) + in + [ + Data = [ + Original = trainingData, + Cleaned = CleanedData, + Features = SelectedFeatures, + Training = TrainingSet, + Testing = TestingSet + ], + Models = Models, + Evaluation = [ + Predictions = Predictions, + Metrics = Metrics, + BestModel = BestModel + ], + FinalModel = FinalModel, + FeatureImportance = ImportanceScores + ]; + +shared TimeSeriesAnalysis = (timeSeries as table, dateColumn as text, valueColumn as text) as record => + let + // Data preparation with unknown functions + SortedData = Table.Sort(timeSeries, dateColumn), + DateParsed = Table.TransformColumns(SortedData, {{dateColumn, DateTime.From}}), + // Time series decomposition with unknown functions + Trend = UnknownTrendAnalyzer(DateParsed, dateColumn, valueColumn), + Seasonality = UnknownSeasonalityDetector(DateParsed, dateColumn, valueColumn), + Residuals = UnknownResidualCalculator(DateParsed, Trend, Seasonality), + // Statistical analysis with unknown functions + Stationarity = UnknownStationarityTest(DateParsed, valueColumn), + AutoCorrelation = UnknownAutoCorrelationFunction(DateParsed, valueColumn), + PartialAutoCorrelation = UnknownPartialAutoCorrelationFunction(DateParsed, valueColumn), + // Anomaly detection with unknown functions + Outliers = UnknownOutlierDetector(DateParsed, valueColumn), + ChangePoints = UnknownChangePointDetector(DateParsed, valueColumn), + AnomalyScores = UnknownAnomalyScorer(DateParsed, valueColumn), + // Forecasting models with unknown functions + ArimaModel = UnknownArimaModel(DateParsed, valueColumn), + ExpSmoothingModel = UnknownExponentialSmoothingModel(DateParsed, valueColumn), + ProphetModel = UnknownProphetModel(DateParsed, dateColumn, valueColumn), + LstmModel = UnknownLSTMModel(DateParsed, valueColumn), + // Model comparison with unknown functions + ModelComparison = UnknownModelComparator({ArimaModel, ExpSmoothingModel, ProphetModel, LstmModel}), + BestForecastModel = UnknownBestModelSelector(ModelComparison), + // Forecasting with unknown functions + ForecastHorizon = 30, + Forecast = UnknownForecaster(BestForecastModel, ForecastHorizon), + ConfidenceIntervals = UnknownConfidenceIntervalCalculator(Forecast), + // Feature engineering with unknown functions + LagFeatures = UnknownLagFeatureGenerator(DateParsed, valueColumn, {1, 7, 30}), + RollingFeatures = UnknownRollingFeatureGenerator(DateParsed, valueColumn, {7, 14, 30}), + CalendarFeatures = UnknownCalendarFeatureGenerator(DateParsed, dateColumn) + in + [ + Data = [ + Original = timeSeries, + Processed = DateParsed, + Features = LagFeatures & RollingFeatures & CalendarFeatures + ], + Decomposition = [ + Trend = Trend, + Seasonality = Seasonality, + Residuals = Residuals + ], + Statistics = [ + Stationarity = Stationarity, + AutoCorrelation = AutoCorrelation, + PartialAutoCorrelation = PartialAutoCorrelation + ], + Anomalies = [ + Outliers = Outliers, + ChangePoints = ChangePoints, + AnomalyScores = AnomalyScores + ], + Models = [ + ARIMA = ArimaModel, + ExponentialSmoothing = ExpSmoothingModel, + Prophet = ProphetModel, + LSTM = LstmModel, + Comparison = ModelComparison, + Best = BestForecastModel + ], + Forecast = [ + Values = Forecast, + ConfidenceIntervals = ConfidenceIntervals, + Horizon = ForecastHorizon + ] + ]; + +shared NaturalLanguageProcessing = (textData as table, textColumn as text) as record => + let + // Text preprocessing with unknown functions + CleanedText = Table.TransformColumns(textData, {{textColumn, each UnknownTextCleaner(_)}}), + Tokenized = Table.TransformColumns(CleanedText, {{textColumn, each UnknownTokenizer(_)}}), + Normalized = Table.TransformColumns(Tokenized, {{textColumn, each UnknownTextNormalizer(_)}}), + // Language detection with unknown functions + Languages = Table.AddColumn( + Normalized, "Language", each UnknownLanguageDetector(Record.Field(_, textColumn)) + ), + // Linguistic analysis with unknown functions + POSTags = Table.AddColumn(Languages, "POSTags", each UnknownPOSTagger(Record.Field(_, textColumn))), + NamedEntities = Table.AddColumn( + POSTags, "NamedEntities", each UnknownNamedEntityRecognizer(Record.Field(_, textColumn)) + ), + Dependencies = Table.AddColumn( + NamedEntities, "Dependencies", each UnknownDependencyParser(Record.Field(_, textColumn)) + ), + // Semantic analysis with unknown functions + Embeddings = Table.AddColumn( + Dependencies, "Embeddings", each UnknownTextEmbedder(Record.Field(_, textColumn)) + ), + Similarities = UnknownSemanticSimilarityCalculator(Embeddings, "Embeddings"), + SemanticClusters = UnknownSemanticClusterer(Embeddings, "Embeddings"), + // Sentiment analysis with unknown functions + Sentiments = Table.AddColumn( + Embeddings, "Sentiment", each UnknownSentimentAnalyzer(Record.Field(_, textColumn)) + ), + Emotions = Table.AddColumn(Sentiments, "Emotions", each UnknownEmotionDetector(Record.Field(_, textColumn))), + // Topic modeling with unknown functions + TopicModel = UnknownTopicModeler(textData, textColumn), + DocumentTopics = UnknownDocumentTopicAssigner(textData, TopicModel), + TopicEvolution = UnknownTopicEvolutionAnalyzer(DocumentTopics), + // Text classification with unknown functions + Categories = Table.AddColumn(Emotions, "Categories", each UnknownTextClassifier(Record.Field(_, textColumn))), + Intent = Table.AddColumn(Categories, "Intent", each UnknownIntentClassifier(Record.Field(_, textColumn))), + // Information extraction with unknown functions + KeyPhrases = Table.AddColumn( + Intent, "KeyPhrases", each UnknownKeyPhraseExtractor(Record.Field(_, textColumn)) + ), + Relationships = Table.AddColumn( + KeyPhrases, "Relationships", each UnknownRelationshipExtractor(Record.Field(_, textColumn)) + ), + // Text generation with unknown functions + Summaries = Table.AddColumn( + Relationships, "Summary", each UnknownTextSummarizer(Record.Field(_, textColumn)) + ), + GeneratedQuestions = Table.AddColumn( + Summaries, "Questions", each UnknownQuestionGenerator(Record.Field(_, textColumn)) + ), + // Text quality metrics with unknown functions + Readability = Table.AddColumn( + GeneratedQuestions, "Readability", each UnknownReadabilityAnalyzer(Record.Field(_, textColumn)) + ), + Complexity = Table.AddColumn( + Readability, "Complexity", each UnknownComplexityAnalyzer(Record.Field(_, textColumn)) + ), + // Statistical analysis with unknown functions + TokenStats = UnknownTokenStatistics(textData, textColumn), + VocabularyStats = UnknownVocabularyAnalyzer(textData, textColumn), + NGramAnalysis = UnknownNGramAnalyzer(textData, textColumn, {2, 3, 4}), + CollocationsAnalysis = UnknownCollocationAnalyzer(textData, textColumn) + in + [ + Data = [ + Original = textData, + Cleaned = CleanedText, + Processed = Complexity + ], + Linguistic = [ + Languages = Languages, + POSTags = POSTags, + NamedEntities = NamedEntities, + Dependencies = Dependencies + ], + Semantic = [ + Embeddings = Embeddings, + Similarities = Similarities, + Clusters = SemanticClusters + ], + Sentiment = [ + Scores = Sentiments, + Emotions = Emotions + ], + Topics = [ + Model = TopicModel, + DocumentTopics = DocumentTopics, + Evolution = TopicEvolution + ], + Classification = [ + Categories = Categories, + Intent = Intent + ], + Extraction = [ + KeyPhrases = KeyPhrases, + Relationships = Relationships + ], + Generation = [ + Summaries = Summaries, + Questions = GeneratedQuestions + ], + Quality = [ + Readability = Readability, + Complexity = Complexity + ], + Statistics = [ + Tokens = TokenStats, + Vocabulary = VocabularyStats, + NGrams = NGramAnalysis, + Collocations = CollocationsAnalysis + ] + ]; diff --git a/src/test/files/LargeSectionDocument_WithParserError.pq b/src/test/files/LargeSectionDocument_WithParserError.pq new file mode 100644 index 00000000..cf63c726 --- /dev/null +++ b/src/test/files/LargeSectionDocument_WithParserError.pq @@ -0,0 +1,166 @@ +[Version = "1.0.0"] +section LargeSectionDocument_WithParserError; + +// This is a complex M section document designed to test validation performance +// It contains parsing errors AND diagnostic errors + +// Constants and basic values +BaseUrl = "https://api.example.com/v1/"; +MaxRetries = 5; +DefaultTimeout = 30; + +// Complex functions with parsing errors +shared ComplexFunction1 = (param1 as text, param2 as number, param3 as logical) as table => + let + Source = UnknownTableFunction(param1, param2), + Step1 = Table.AddColumn(Source, "NewCol", each UnknownScalarFunction([Column1], [Column2])), + // Parsing error: missing closing parenthesis + Step2 = Table.TransformColumns(Step1, {{"NewCol", each UnknownTransformFunction(_)}), + Step3 = Table.SelectRows(Step2, each UnknownFilterFunction([NewCol], param3)), + Result = Table.Sort(Step3, {{"NewCol", UnknownOrderFunction}}) + in + Result; + +shared ComplexFunction2 = (data as table, config as record) as table => + let + Column1 = Table.Column(data, UnknownColumnName1), + Column2 = Table.Column(data, UnknownColumnName2), + Processed1 = List.Transform(Column1, each UnknownProcessor1(_)), + Processed2 = List.Transform(Column2, each UnknownProcessor2(_)), + CombinedTable = #table({"Proc1", "Proc2"}, List.Zip({Processed1, Processed2})), + // Parsing error: incorrect list syntax + Aggregated = Table.Group(CombinedTable, {"Proc1"}, [{"Proc2Sum", each List.Sum([Proc2]), type number}]), + Final = Table.AddColumn(Aggregated, "Calculated", each UnknownCalculation([Proc1], [Proc2Sum])) + in + Final; + +shared ComplexFunction3 = (inputList as list, operations as list) as list => + let + ProcessOperation = (current as list, operation as record) as list => + let + OperationType = Record.Field(operation, "Type"), + Parameter = Record.Field(operation, "Parameter") + in + if OperationType = "unknown1" then + List.Transform(current, each UnknownFunction1(_, Parameter)) + else if OperationType = "unknown2" then + List.Select(current, each UnknownFunction2(_, Parameter)) + else if OperationType = "unknown3" then + // Parsing error: malformed function call + List.Sort(current each UnknownFunction3(_, Parameter)) + else + current, + Result = List.Accumulate(operations, inputList, ProcessOperation) + in + Result; + +shared ComplexFunction4 = (textData as text, patterns as list) as record => + let + ExtractPattern = (text as text, pattern as record) as any => + let + PatternType = Record.Field(pattern, "Type"), + PatternValue = Record.Field(pattern, "Value") + in + if PatternType = "unknown" then + UnknownExtractor(textData, PatternValue) + else if PatternType = "custom" then + CustomUnknownExtractor(textData, PatternValue) + else + null, + Results = List.Transform(patterns, each ExtractPattern(textData, _)), + Summary = [ + TotalMatches = List.Count(List.Select(Results, each _ <> null)), + FirstMatch = UnknownFirstFunction(Results), + LastMatch = UnknownLastFunction(Results), + // Parsing error: incorrect record syntax + ProcessedResults = List.Transform(Results, each UnknownResultProcessor(_)) + + in + Summary; + +shared ComplexFunction5 = (numericData as list, analysisType as text) as record => + let + BasicStats = [ + Sum = List.Sum(numericData), + Average = List.Average(numericData), + Count = List.Count(numericData) + ], + AdvancedStats = + if analysisType = "advanced" then + [ + StandardDev = UnknownStandardDeviation(numericData), + Variance = UnknownVariance(numericData), + Median = UnknownMedian(numericData), + Quartiles = UnknownQuartiles(numericData), + Outliers = UnknownOutlierDetection(numericData) + ] + else + [], + CombinedStats = BasicStats & AdvancedStats, + ProcessedStats = Record.TransformFields( + CombinedStats, + { + {"Sum", each UnknownSumProcessor(_)}, + {"Average", each UnknownAverageProcessor(_)}, + // Parsing error: missing closing braces + {"Count", each UnknownCountProcessor(_) + } + ) + in + ProcessedStats; + +// Additional functions with both parsing and diagnostic errors +shared MachineLearningPipeline = (trainingData as table, features as list, target as text) as record => + let + // Data preprocessing with unknown functions + CleanedData = UnknownDataCleaner(trainingData), + FeatureMatrix = UnknownFeatureExtractor(CleanedData, features), + NormalizedFeatures = UnknownNormalizer(FeatureMatrix), + EncodedFeatures = UnknownCategoricalEncoder(NormalizedFeatures), + + // Feature selection with unknown functions + SelectedFeatures = UnknownFeatureSelector(EncodedFeatures, target), + ImportanceScores = UnknownFeatureImportance(SelectedFeatures, target), + + // Model training with unknown functions + TrainTestSplit = UnknownTrainTestSplitter(SelectedFeatures, 0.8), + TrainingSet = TrainTestSplit[Training], + TestingSet = TrainTestSplit[Testing], + + // Parsing error: incorrect list syntax + Models = [ + LinearRegression = UnknownLinearRegression(TrainingSet, target), + RandomForest = UnknownRandomForest(TrainingSet, target) + XGBoost = UnknownXGBoost(TrainingSet, target), + NeuralNetwork = UnknownNeuralNetwork(TrainingSet, target), + SVM = UnknownSVM(TrainingSet, target) + ], + + // Model evaluation with unknown functions + Predictions = Record.TransformFields( + Models, + { + "LinearRegression", each UnknownPredict(_, TestingSet), + "RandomForest", each UnknownPredict(_, TestingSet), + "XGBoost", each UnknownPredict(_, TestingSet), + "NeuralNetwork", each UnknownPredict(_, TestingSet), + "SVM", each UnknownPredict(_, TestingSet) + } + ), + + BestModel = UnknownModelSelector(Metrics), + Hyperparameters = UnknownHyperparameterTuner(BestModel, TrainingSet), + FinalModel = UnknownModelTrainer(BestModel, Hyperparameters, TrainingSet) + in + [ + Data = [ + Original = trainingData, + Cleaned = CleanedData, + Features = SelectedFeatures, + Training = TrainingSet, + Testing = TestingSet + ], + Models = Models, + FinalModel = FinalModel, + FeatureImportance = ImportanceScores + ]; diff --git a/src/test/validation/asyncValidation.test.ts b/src/test/validation/asyncValidation.test.ts new file mode 100644 index 00000000..87682577 --- /dev/null +++ b/src/test/validation/asyncValidation.test.ts @@ -0,0 +1,376 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import "mocha"; +import { assert, expect } from "chai"; +import { ICancellationToken } from "@microsoft/powerquery-parser"; +import * as fs from "fs"; +import * as path from "path"; + +import { ValidationSettings, AnalysisSettings, validate, ValidateOk } from "../../powerquery-language-services"; +import { TestConstants, TestUtils } from ".."; +import * as ValidateTestUtils from "../testUtils/validationTestUtils"; + +function createCancellationToken(): ICancellationToken { + let isCancelled = false; + + return { + isCancelled: () => isCancelled, + throwIfCancelled: () => { + if (isCancelled) { + throw new Error("Operation was cancelled"); + } + }, + cancel: (_reason: string) => { + isCancelled = true; + }, + }; +} + +describe("Async Validation", () => { + const analysisSettings: AnalysisSettings = TestConstants.SimpleLibraryAnalysisSettings; + + const baseValidationSettings: ValidationSettings = { + ...TestConstants.SimpleValidateNoneSettings, + checkInvokeExpressions: true, + checkUnknownIdentifiers: true, + checkForDuplicateIdentifiers: true, + }; + + describe("Large document validation", () => { + let largeSectionDocumentText: string; + let largeSectionDocumentWithDiagnosticsText: string; + let largeSectionDocumentWithParserErrorText: string; + + before(() => { + // Load the large section documents + const cleanFilePath = path.join(__dirname, "..", "files", "LargeSectionDocument.pq"); + const diagnosticsFilePath = path.join(__dirname, "..", "files", "LargeSectionDocument_WithDiagnostics.pq"); + const parserErrorFilePath = path.join(__dirname, "..", "files", "LargeSectionDocument_WithParserError.pq"); + + largeSectionDocumentText = fs.readFileSync(cleanFilePath, "utf8"); + largeSectionDocumentWithDiagnosticsText = fs.readFileSync(diagnosticsFilePath, "utf8"); + largeSectionDocumentWithParserErrorText = fs.readFileSync(parserErrorFilePath, "utf8"); + }); + + it("should validate clean large document successfully without cancellation", async () => { + const cancellationToken = createCancellationToken(); + const validationSettings: ValidationSettings = { + ...baseValidationSettings, + cancellationToken, + }; + + const startTime = Date.now(); + const result: ValidateOk = await ValidateTestUtils.assertValidate({ + text: largeSectionDocumentText, + analysisSettings, + validationSettings, + }); + + const endTime = Date.now(); + const duration = endTime - startTime; + + // Validation should complete successfully + expect(result).to.not.be.undefined; + expect(result.diagnostics).to.be.an("array"); + expect(result.hasSyntaxError).to.be.false; + + // Should have no parse errors since this is the clean file + expect(result.hasSyntaxError).to.be.false; + + // Should have significantly fewer diagnostic errors than the diagnostics file + expect(result.diagnostics.length).to.be.lessThan(100, "Clean document should have fewer diagnostic errors"); + + // Should take some time due to complexity + expect(duration).to.be.greaterThan(50, "Validation should take some time for complex document"); + + console.log( + `Clean large document validation took ${duration}ms with ${result.diagnostics.length} diagnostics`, + ); + }).timeout(60000); // 60 second timeout + + it("should validate document with diagnostic errors", async () => { + const cancellationToken = createCancellationToken(); + const validationSettings: ValidationSettings = { + ...baseValidationSettings, + cancellationToken, + }; + + const startTime = Date.now(); + const result: ValidateOk = await ValidateTestUtils.assertValidate({ + text: largeSectionDocumentWithDiagnosticsText, + analysisSettings, + validationSettings, + }); + + const endTime = Date.now(); + const duration = endTime - startTime; + + // Validation should complete successfully + expect(result).to.not.be.undefined; + expect(result.diagnostics).to.be.an("array"); + expect(result.hasSyntaxError).to.be.false; + + // Should have diagnostic errors due to unknown identifiers + expect(result.diagnostics.length).to.be.greaterThan(0, "Document with diagnostics should have errors"); + + console.log( + `Document with diagnostics validation took ${duration}ms, found ${result.diagnostics.length} diagnostics`, + ); + }).timeout(60000); + + it("should handle document with parser errors", async () => { + const cancellationToken = createCancellationToken(); + const validationSettings: ValidationSettings = { + ...baseValidationSettings, + cancellationToken, + }; + + const startTime = Date.now(); + const result: ValidateOk = await ValidateTestUtils.assertValidate({ + text: largeSectionDocumentWithParserErrorText, + analysisSettings, + validationSettings, + }); + + const endTime = Date.now(); + const duration = endTime - startTime; + + // Validation should complete but have syntax errors + expect(result).to.not.be.undefined; + expect(result.diagnostics).to.be.an("array"); + expect(result.hasSyntaxError).to.be.true; + + // Should have diagnostic errors due to parser errors + expect(result.diagnostics.length).to.be.greaterThan( + 0, + "Document with parser errors should have diagnostics", + ); + + console.log( + `Document with parser errors validation took ${duration}ms, found ${result.diagnostics.length} diagnostics`, + ); + }).timeout(60000); + + it("should respect cancellation token when cancelled during validation", async () => { + const cancellationToken = createCancellationToken(); + const validationSettings: ValidationSettings = { + ...baseValidationSettings, + cancellationToken, + }; + + // Start validation with the diagnostics file (takes longer) + const validationPromise = validate( + TestUtils.mockDocument(largeSectionDocumentWithDiagnosticsText), + analysisSettings, + validationSettings, + ); + + // Cancel after a short delay + setTimeout(() => { + cancellationToken.cancel("Test cancellation"); + }, 500); // Cancel after 500ms + + try { + await validationPromise; + // If we get here without an error, cancellation might not be working properly + // but we need to verify if the operation actually respected the cancellation + assert.fail("Expected validation to be cancelled, but it completed successfully"); + } catch (error: any) { + // Expect cancellation error + expect(error.message).to.contain("cancelled"); + } + }).timeout(30000); // 30 second timeout + + it("should handle cancellation gracefully at different stages", async () => { + const delays = [100, 250, 500, 1000]; // Different cancellation timings + + for (const delay of delays) { + const cancellationToken = createCancellationToken(); + const validationSettings: ValidationSettings = { + ...baseValidationSettings, + cancellationToken, + }; + + const validationPromise = validate( + TestUtils.mockDocument(largeSectionDocumentWithDiagnosticsText), + analysisSettings, + validationSettings, + ); + + // Cancel after the specified delay + setTimeout(() => { + cancellationToken.cancel("Test cancellation"); + }, delay); + + try { + await validationPromise; + // If validation completes despite cancellation, + // it might have finished before cancellation occurred + console.log(`Validation completed before cancellation at ${delay}ms`); + } catch (error: any) { + expect(error.message).to.contain("cancelled"); + console.log(`Successfully cancelled validation at ${delay}ms`); + } + } + }).timeout(60000); + }); + + describe("Cancellation token behavior", () => { + it("should not throw when cancellation token is undefined", async () => { + const validationSettings: ValidationSettings = { + ...baseValidationSettings, + cancellationToken: undefined, + }; + + const result = await ValidateTestUtils.assertValidate({ + text: "let x = 1 in x", + analysisSettings, + validationSettings, + }); + + expect(result).to.not.be.undefined; + }); + + it("should not throw when cancellation token is not cancelled", async () => { + const cancellationToken = createCancellationToken(); + const validationSettings: ValidationSettings = { + ...baseValidationSettings, + cancellationToken, + }; + + const result = await ValidateTestUtils.assertValidate({ + text: "let x = 1 in x", + analysisSettings, + validationSettings, + }); + + expect(result).to.not.be.undefined; + expect(cancellationToken.isCancelled()).to.be.false; + }); + + it("should throw immediately when cancellation token is already cancelled", async () => { + const cancellationToken = createCancellationToken(); + cancellationToken.cancel("Pre-cancelled for testing"); // Cancel before starting + + const validationSettings: ValidationSettings = { + ...baseValidationSettings, + cancellationToken, + }; + + try { + await validate(TestUtils.mockDocument("let x = 1 in x"), analysisSettings, validationSettings); + assert.fail("Expected validation to throw due to pre-cancelled token"); + } catch (error: any) { + expect(error.message).to.contain("cancelled"); + } + }); + }); + + describe("Performance with different document sizes", () => { + const smallDocument = "let x = 1 in x"; + const mediumDocument = ` + let + func1 = (param1 as text, param2 as number) => param1 & Text.From(param2), + func2 = (data as table) => Table.RowCount(data), + func3 = (list as list) => List.Sum(list), + result = func1("Hello", func2(#table({"Col1"}, {{"Value1"}, {"Value2"}, {"Value3"}}))) + in + result + `; + + it("should have reasonable performance for small documents", async () => { + const cancellationToken = createCancellationToken(); + const validationSettings: ValidationSettings = { + ...baseValidationSettings, + cancellationToken, + }; + + const startTime = Date.now(); + const result = await ValidateTestUtils.assertValidate({ + text: smallDocument, + analysisSettings, + validationSettings, + }); + const duration = Date.now() - startTime; + + expect(result).to.not.be.undefined; + expect(duration).to.be.lessThan(1000, "Small document should validate quickly"); + console.log(`Small document validation took ${duration}ms`); + }); + + it("should have reasonable performance for medium documents", async () => { + const cancellationToken = createCancellationToken(); + const validationSettings: ValidationSettings = { + ...baseValidationSettings, + cancellationToken, + }; + + const startTime = Date.now(); + const result = await ValidateTestUtils.assertValidate({ + text: mediumDocument, + analysisSettings, + validationSettings, + }); + const duration = Date.now() - startTime; + + expect(result).to.not.be.undefined; + expect(duration).to.be.lessThan(5000, "Medium document should validate in reasonable time"); + console.log(`Medium document validation took ${duration}ms`); + }); + }); + + describe("Async validation with different validation settings", () => { + it("should respect cancellation with all validation checks enabled", async () => { + const cancellationToken = createCancellationToken(); + const validationSettings: ValidationSettings = { + ...baseValidationSettings, + cancellationToken, + checkInvokeExpressions: true, + checkUnknownIdentifiers: true, + checkForDuplicateIdentifiers: true, + }; + + const validationPromise = validate( + TestUtils.mockDocument("let x = unknownFunc(1, 2) in x"), + analysisSettings, + validationSettings, + ); + + setTimeout(() => cancellationToken.cancel("Test cancellation"), 100); + + try { + await validationPromise; + console.log("Validation completed before cancellation"); + } catch (error: any) { + expect(error.message).to.contain("cancelled"); + } + }); + + it("should respect cancellation with only specific checks enabled", async () => { + const cancellationToken = createCancellationToken(); + const validationSettings: ValidationSettings = { + ...baseValidationSettings, + cancellationToken, + checkInvokeExpressions: false, + checkUnknownIdentifiers: true, + checkForDuplicateIdentifiers: false, + }; + + const validationPromise = validate( + TestUtils.mockDocument("let x = unknownVariable in x"), + analysisSettings, + validationSettings, + ); + + setTimeout(() => cancellationToken.cancel("Test cancellation"), 100); + + try { + await validationPromise; + console.log("Validation completed before cancellation"); + } catch (error: any) { + expect(error.message).to.contain("cancelled"); + } + }); + }); +}); From c6eb4141c48086b7a91606d9ef732a271361c385 Mon Sep 17 00:00:00 2001 From: Matt Masson Date: Mon, 8 Sep 2025 18:50:28 -0400 Subject: [PATCH 12/42] checkpoint --- .../inspectionSettings.ts | 2 - .../utils/promiseUtils.ts | 58 ++++ .../validate/validate.ts | 85 +++--- .../validate/validateDuplicateIdentifiers.ts | 6 +- src/test/utils/promiseUtils.test.ts | 274 ++++++++++++++++++ src/test/validation/asyncValidation.test.ts | 90 +++++- 6 files changed, 459 insertions(+), 56 deletions(-) create mode 100644 src/powerquery-language-services/utils/promiseUtils.ts create mode 100644 src/test/utils/promiseUtils.test.ts diff --git a/src/powerquery-language-services/inspectionSettings.ts b/src/powerquery-language-services/inspectionSettings.ts index 22fc546a..91ea4007 100644 --- a/src/powerquery-language-services/inspectionSettings.ts +++ b/src/powerquery-language-services/inspectionSettings.ts @@ -33,6 +33,4 @@ export interface InspectionSettings extends PQP.Settings { // While useful for in-depth analysis and/or Intellisense operations they can be costly in terms of time. // Changing the strategy determines how types are evaluated during inspections. readonly typeStrategy: TypeStrategy; - // Cancellation token for long-running inspection operations - readonly cancellationToken: PQP.ICancellationToken | undefined; } diff --git a/src/powerquery-language-services/utils/promiseUtils.ts b/src/powerquery-language-services/utils/promiseUtils.ts new file mode 100644 index 00000000..3bbe43ed --- /dev/null +++ b/src/powerquery-language-services/utils/promiseUtils.ts @@ -0,0 +1,58 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import { ICancellationToken } from "@microsoft/powerquery-parser"; + +/** + * Promise.all with cancellation support. Useful when you want parallel execution + * but need the ability to cancel if needed. + */ +export async function promiseAllWithCancellation( + promises: Promise[], + cancellationToken?: ICancellationToken, +): Promise { + if (cancellationToken) { + cancellationToken.throwIfCancelled(); + + return new Promise((resolve, reject) => { + const cancellationCheck = () => { + try { + cancellationToken.throwIfCancelled(); + } catch (error) { + reject(error); + return; + } + // Check again after a short delay + setTimeout(cancellationCheck, 10); + }; + + // Start cancellation checking + cancellationCheck(); + + // Start the actual Promise.all + Promise.all(promises).then(resolve, reject); + }); + } + + return Promise.all(promises); +} + +/** + * Sequential processing with cancellation support - often better than Promise.all + * for cancellation scenarios because we can stop between each operation. + */ +export async function processSequentiallyWithCancellation( + items: T[], + processor: (item: T) => Promise, + cancellationToken?: ICancellationToken, +): Promise { + const results: R[] = []; + + for (const item of items) { + cancellationToken?.throwIfCancelled(); + const result = await processor(item); + results.push(result); + } + + return results; +} diff --git a/src/powerquery-language-services/validate/validate.ts b/src/powerquery-language-services/validate/validate.ts index 20511996..a8525e8c 100644 --- a/src/powerquery-language-services/validate/validate.ts +++ b/src/powerquery-language-services/validate/validate.ts @@ -2,7 +2,6 @@ // Licensed under the MIT license. import { NodeIdMap, ParseError, ParseState } from "@microsoft/powerquery-parser/lib/powerquery-parser/parser"; -import { Diagnostic } from "vscode-languageserver-types"; import { TextDocument } from "vscode-languageserver-textdocument"; import { Trace } from "@microsoft/powerquery-parser/lib/powerquery-parser/common/trace"; @@ -17,6 +16,7 @@ import { validateParse } from "./validateParse"; import { validateUnknownIdentifiers } from "./validateUnknownIdentifiers"; import type { ValidationSettings } from "./validationSettings"; import { ValidationTraceConstant } from "../trace"; +import { processSequentiallyWithCancellation } from "../utils/promiseUtils"; export function validate( textDocument: TextDocument, @@ -35,17 +35,12 @@ export function validate( initialCorrelationId: trace.id, }; - // Create analysis settings with the updated validation settings (which include cancellation token) - const updatedAnalysisSettings: AnalysisSettings = { - ...analysisSettings, - inspectionSettings: updatedSettings, - }; - - const analysis: Analysis = AnalysisUtils.analysis(textDocument, updatedAnalysisSettings); - + const analysis: Analysis = AnalysisUtils.analysis(textDocument, analysisSettings); validationSettings.cancellationToken?.throwIfCancelled(); const parseState: ParseState | undefined = ResultUtils.assertOk(await analysis.getParseState()); + validationSettings.cancellationToken?.throwIfCancelled(); + const parseError: ParseError.ParseError | undefined = ResultUtils.assertOk(await analysis.getParseError()); if (parseState === undefined) { @@ -56,55 +51,53 @@ export function validate( validationSettings.cancellationToken?.throwIfCancelled(); - let functionExpressionDiagnostics: Diagnostic[]; - let invokeExpressionDiagnostics: Diagnostic[]; - let unknownIdentifiersDiagnostics: Diagnostic[]; - const nodeIdMapCollection: NodeIdMap.Collection = parseState.contextState.nodeIdMapCollection; const typeCache: TypeCache = analysis.getTypeCache(); - if (validationSettings.checkInvokeExpressions && nodeIdMapCollection) { - validationSettings.cancellationToken?.throwIfCancelled(); - - functionExpressionDiagnostics = await validateFunctionExpression(validationSettings, nodeIdMapCollection); - - validationSettings.cancellationToken?.throwIfCancelled(); + // Define validation operations to run sequentially + const validationOperations = [ + // Parse validation (if there are parse errors) + async () => await validateParse(parseError, updatedSettings), + ]; + + // Add conditional validations based on settings + if (validationSettings.checkForDuplicateIdentifiers && nodeIdMapCollection) { + validationOperations.push( + async () => + await validateDuplicateIdentifiers( + textDocument, + nodeIdMapCollection, + updatedSettings, + validationSettings.cancellationToken, + ), + ); + } - invokeExpressionDiagnostics = await validateInvokeExpression( - validationSettings, - nodeIdMapCollection, - typeCache, + if (validationSettings.checkInvokeExpressions && nodeIdMapCollection) { + validationOperations.push( + async () => await validateFunctionExpression(validationSettings, nodeIdMapCollection), + async () => await validateInvokeExpression(validationSettings, nodeIdMapCollection, typeCache), ); - } else { - functionExpressionDiagnostics = []; - invokeExpressionDiagnostics = []; } if (validationSettings.checkUnknownIdentifiers && nodeIdMapCollection) { - validationSettings.cancellationToken?.throwIfCancelled(); - - unknownIdentifiersDiagnostics = await validateUnknownIdentifiers( - validationSettings, - nodeIdMapCollection, - typeCache, + validationOperations.push( + async () => await validateUnknownIdentifiers(validationSettings, nodeIdMapCollection, typeCache), ); - } else { - unknownIdentifiersDiagnostics = []; } + // Execute all validation operations sequentially with cancellation support + const allDiagnostics = await processSequentiallyWithCancellation( + validationOperations, + operation => operation(), + validationSettings.cancellationToken, + ); + + // Flatten all diagnostics into a single array + const flattenedDiagnostics = allDiagnostics.flat(); + const result: ValidateOk = { - diagnostics: [ - ...validateDuplicateIdentifiers( - textDocument, - nodeIdMapCollection, - updatedSettings, - validationSettings.cancellationToken, - ), - ...(await validateParse(parseError, updatedSettings)), - ...functionExpressionDiagnostics, - ...invokeExpressionDiagnostics, - ...unknownIdentifiersDiagnostics, - ], + diagnostics: flattenedDiagnostics, hasSyntaxError: parseError !== undefined, }; diff --git a/src/powerquery-language-services/validate/validateDuplicateIdentifiers.ts b/src/powerquery-language-services/validate/validateDuplicateIdentifiers.ts index f04f5b5d..b0d22446 100644 --- a/src/powerquery-language-services/validate/validateDuplicateIdentifiers.ts +++ b/src/powerquery-language-services/validate/validateDuplicateIdentifiers.ts @@ -20,12 +20,12 @@ import { PositionUtils } from ".."; import { ValidationSettings } from "./validationSettings"; import { ValidationTraceConstant } from "../trace"; -export function validateDuplicateIdentifiers( +export async function validateDuplicateIdentifiers( textDocument: TextDocument, nodeIdMapCollection: NodeIdMap.Collection, validationSettings: ValidationSettings, cancellationToken: ICancellationToken | undefined, -): ReadonlyArray { +): Promise { const trace: Trace = validationSettings.traceManager.entry( ValidationTraceConstant.Validation, validateDuplicateIdentifiers.name, @@ -45,7 +45,7 @@ export function validateDuplicateIdentifiers( const documentUri: string = textDocument.uri; - const result: ReadonlyArray = [ + const result: Diagnostic[] = [ ...validateDuplicateIdentifiersForLetExpresion( documentUri, nodeIdMapCollection, diff --git a/src/test/utils/promiseUtils.test.ts b/src/test/utils/promiseUtils.test.ts new file mode 100644 index 00000000..1e7b0184 --- /dev/null +++ b/src/test/utils/promiseUtils.test.ts @@ -0,0 +1,274 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import "mocha"; +import { assert, expect } from "chai"; +import { ICancellationToken } from "@microsoft/powerquery-parser"; + +import { + promiseAllWithCancellation, + processSequentiallyWithCancellation, +} from "../../powerquery-language-services/utils/promiseUtils"; + +function createTestCancellationToken(): ICancellationToken & { cancel: (reason: string) => void } { + let isCancelled = false; + + return { + isCancelled: () => isCancelled, + throwIfCancelled: () => { + if (isCancelled) { + throw new Error("Operation was cancelled"); + } + }, + cancel: (_reason: string) => { + isCancelled = true; + }, + }; +} + +function createDelayedPromise(delayMs: number, value: string): Promise { + return new Promise(resolve => { + setTimeout(() => resolve(value), delayMs); + }); +} + +function createDelayedRejectingPromise(delayMs: number, errorMessage: string): Promise { + return new Promise((_, reject) => { + setTimeout(() => reject(new Error(errorMessage)), delayMs); + }); +} + +describe("Promise Utils", () => { + describe("promiseAllWithCancellation", () => { + it("should resolve all promises when no cancellation token is provided", async () => { + const promises = [ + createDelayedPromise(10, "first"), + createDelayedPromise(20, "second"), + createDelayedPromise(15, "third"), + ]; + + const result = await promiseAllWithCancellation(promises); + expect(result).to.deep.equal(["first", "second", "third"]); + }); + + it("should resolve all promises when cancellation token is not cancelled", async () => { + const cancellationToken = createTestCancellationToken(); + const promises = [ + createDelayedPromise(10, "first"), + createDelayedPromise(20, "second"), + createDelayedPromise(15, "third"), + ]; + + const result = await promiseAllWithCancellation(promises, cancellationToken); + expect(result).to.deep.equal(["first", "second", "third"]); + expect(cancellationToken.isCancelled()).to.be.false; + }); + + it("should reject immediately if cancellation token is already cancelled", async () => { + const cancellationToken = createTestCancellationToken(); + cancellationToken.cancel("Pre-cancelled"); + + const promises = [createDelayedPromise(100, "first"), createDelayedPromise(200, "second")]; + + try { + await promiseAllWithCancellation(promises, cancellationToken); + assert.fail("Expected promise to be rejected due to pre-cancelled token"); + } catch (error: any) { + expect(error.message).to.contain("cancelled"); + } + }); + + it("should reject when cancellation token is cancelled during execution", async () => { + const cancellationToken = createTestCancellationToken(); + const promises = [ + createDelayedPromise(100, "first"), + createDelayedPromise(200, "second"), + createDelayedPromise(300, "third"), + ]; + + // Cancel after 50ms + setTimeout(() => { + cancellationToken.cancel("Cancelled during execution"); + }, 50); + + try { + await promiseAllWithCancellation(promises, cancellationToken); + assert.fail("Expected promise to be rejected due to cancellation"); + } catch (error: any) { + expect(error.message).to.contain("cancelled"); + } + }); + + it("should handle promise rejections normally when not cancelled", async () => { + const cancellationToken = createTestCancellationToken(); + const promises = [ + createDelayedPromise(10, "first"), + createDelayedRejectingPromise(20, "Test error"), + createDelayedPromise(15, "third"), + ]; + + try { + await promiseAllWithCancellation(promises, cancellationToken); + assert.fail("Expected promise to be rejected due to promise rejection"); + } catch (error: any) { + expect(error.message).to.equal("Test error"); + } + }); + }); + + describe("processSequentiallyWithCancellation", () => { + it("should process all items sequentially when no cancellation token is provided", async () => { + const items = ["a", "b", "c"]; + const processor = async (item: string) => { + await new Promise(resolve => setTimeout(resolve, 10)); + return item.toUpperCase(); + }; + + const result = await processSequentiallyWithCancellation(items, processor); + expect(result).to.deep.equal(["A", "B", "C"]); + }); + + it("should process all items when cancellation token is not cancelled", async () => { + const cancellationToken = createTestCancellationToken(); + const items = ["a", "b", "c"]; + const processor = async (item: string) => { + await new Promise(resolve => setTimeout(resolve, 10)); + return item.toUpperCase(); + }; + + const result = await processSequentiallyWithCancellation(items, processor, cancellationToken); + expect(result).to.deep.equal(["A", "B", "C"]); + expect(cancellationToken.isCancelled()).to.be.false; + }); + + it("should stop processing when cancellation token is cancelled", async () => { + const cancellationToken = createTestCancellationToken(); + const items = ["a", "b", "c", "d", "e"]; + const processedItems: string[] = []; + + const processor = async (item: string) => { + processedItems.push(item); + await new Promise(resolve => setTimeout(resolve, 20)); + return item.toUpperCase(); + }; + + // Cancel after processing should have started + setTimeout(() => { + cancellationToken.cancel("Cancelled during processing"); + }, 35); // Should allow first item and start of second + + try { + await processSequentiallyWithCancellation(items, processor, cancellationToken); + assert.fail("Expected processing to be cancelled"); + } catch (error: any) { + expect(error.message).to.contain("cancelled"); + // Should have processed at least the first item, but not all + expect(processedItems.length).to.be.greaterThan(0); + expect(processedItems.length).to.be.lessThan(items.length); + } + }); + + it("should reject immediately if cancellation token is already cancelled", async () => { + const cancellationToken = createTestCancellationToken(); + cancellationToken.cancel("Pre-cancelled"); + + const items = ["a", "b", "c"]; + const processor = async (item: string) => item.toUpperCase(); + + try { + await processSequentiallyWithCancellation(items, processor, cancellationToken); + assert.fail("Expected processing to be rejected due to pre-cancelled token"); + } catch (error: any) { + expect(error.message).to.contain("cancelled"); + } + }); + + it("should handle empty arrays", async () => { + const cancellationToken = createTestCancellationToken(); + const items: string[] = []; + const processor = async (item: string) => item.toUpperCase(); + + const result = await processSequentiallyWithCancellation(items, processor, cancellationToken); + expect(result).to.deep.equal([]); + }); + + it("should handle processor errors normally when not cancelled", async () => { + const cancellationToken = createTestCancellationToken(); + const items = ["a", "b", "c"]; + const processor = async (item: string) => { + if (item === "b") { + throw new Error("Test processor error"); + } + return item.toUpperCase(); + }; + + try { + await processSequentiallyWithCancellation(items, processor, cancellationToken); + assert.fail("Expected processing to be rejected due to processor error"); + } catch (error: any) { + expect(error.message).to.equal("Test processor error"); + } + }); + + it("should check cancellation before each item", async () => { + const cancellationToken = createTestCancellationToken(); + const items = ["a", "b", "c"]; + let processorCallCount = 0; + + const processor = async (item: string) => { + processorCallCount++; + return item.toUpperCase(); + }; + + // Cancel before any processing + cancellationToken.cancel("Pre-cancelled"); + + try { + await processSequentiallyWithCancellation(items, processor, cancellationToken); + assert.fail("Expected processing to be cancelled"); + } catch (error: any) { + expect(error.message).to.contain("cancelled"); + expect(processorCallCount).to.equal(0, "Processor should not be called when pre-cancelled"); + } + }); + }); + + describe("cancellation timing behavior", () => { + it("should demonstrate different cancellation points in sequential processing", async () => { + const cancellationToken = createTestCancellationToken(); + const items = [1, 2, 3, 4, 5]; + const processedItems: number[] = []; + + const processor = async (item: number) => { + processedItems.push(item); + // Each item takes 30ms to process + await new Promise(resolve => setTimeout(resolve, 30)); + return item * 2; + }; + + // Cancel after 75ms (should process ~2-3 items) + setTimeout(() => { + cancellationToken.cancel("Timed cancellation"); + }, 75); + + try { + await processSequentiallyWithCancellation(items, processor, cancellationToken); + assert.fail("Expected processing to be cancelled"); + } catch (error: any) { + expect(error.message).to.contain("cancelled"); + console.log( + `Processed ${processedItems.length} items before cancellation: [${processedItems.join(", ")}]`, + ); + + // Should have processed some but not all items + expect(processedItems.length).to.be.greaterThan(0); + expect(processedItems.length).to.be.lessThan(items.length); + + // Should have processed items in order + for (let i = 0; i < processedItems.length; i++) { + expect(processedItems[i]).to.equal(i + 1); + } + } + }); + }); +}); diff --git a/src/test/validation/asyncValidation.test.ts b/src/test/validation/asyncValidation.test.ts index 87682577..80adf80c 100644 --- a/src/test/validation/asyncValidation.test.ts +++ b/src/test/validation/asyncValidation.test.ts @@ -81,9 +81,7 @@ describe("Async Validation", () => { // Should have significantly fewer diagnostic errors than the diagnostics file expect(result.diagnostics.length).to.be.lessThan(100, "Clean document should have fewer diagnostic errors"); - // Should take some time due to complexity - expect(duration).to.be.greaterThan(50, "Validation should take some time for complex document"); - + // Log performance for manual observation (no assertions on timing) console.log( `Clean large document validation took ${duration}ms with ${result.diagnostics.length} diagnostics`, ); @@ -214,6 +212,81 @@ describe("Async Validation", () => { } } }).timeout(60000); + + it("should demonstrate performance benefit of cancellation", async () => { + // Test that cancelled validation is faster than completed validation + + // First, measure time for completed validation + const startComplete = Date.now(); + const completedValidationSettings: ValidationSettings = { + ...baseValidationSettings, + cancellationToken: undefined, // No cancellation + }; + + await ValidateTestUtils.assertValidate({ + text: largeSectionDocumentWithDiagnosticsText, + analysisSettings, + validationSettings: completedValidationSettings, + }); + const completeDuration = Date.now() - startComplete; + + // Then, measure time for cancelled validation + const startCancelled = Date.now(); + const cancellationToken = createCancellationToken(); + const cancelledValidationSettings: ValidationSettings = { + ...baseValidationSettings, + cancellationToken, + }; + + // Cancel after enough time to start heavy work but before completion + // Use the same timing as the successful cancellation test + setTimeout( + () => { + cancellationToken.cancel("Performance test cancellation"); + }, + Math.min(completeDuration * 0.7, 400), // Cancel at 70% of completion time, max 400ms + ); + + let cancellationDuration = 0; + let wasCancelled = false; + try { + await validate( + TestUtils.mockDocument(largeSectionDocumentWithDiagnosticsText), + analysisSettings, + cancelledValidationSettings, + ); + // If we get here, validation completed before cancellation + cancellationDuration = Date.now() - startCancelled; + console.log( + `Cancellation test: validation completed in ${cancellationDuration}ms before cancellation could occur`, + ); + } catch (error: any) { + cancellationDuration = Date.now() - startCancelled; + wasCancelled = true; + expect(error.message).to.contain("cancelled"); + console.log(`Cancellation test: validation was cancelled after ${cancellationDuration}ms`); + } + + // Log the performance comparison + console.log( + `Performance comparison: Complete=${completeDuration}ms, Cancelled=${cancellationDuration}ms, WasCancelled=${wasCancelled}`, + ); + + // Verify that cancellation provided a performance benefit + if (wasCancelled) { + // If validation was actually cancelled, it should be significantly faster + expect(cancellationDuration).to.be.lessThan( + completeDuration, + `Cancelled validation (${cancellationDuration}ms) should be faster than complete validation (${completeDuration}ms)`, + ); + console.log(`✓ Cancellation provided ${completeDuration - cancellationDuration}ms performance benefit`); + } else { + // If validation completed before cancellation, that's also valuable information + console.log( + `ℹ Validation completed too quickly (${cancellationDuration}ms) for cancellation to take effect`, + ); + } + }).timeout(60000); }); describe("Cancellation token behavior", () => { @@ -268,6 +341,13 @@ describe("Async Validation", () => { }); describe("Performance with different document sizes", () => { + // Note: These tests validate functional correctness and log performance metrics + // without asserting on timing to avoid flaky tests across different environments. + // Performance can be monitored through: + // 1. Console output during test runs + // 2. Separate performance benchmarking tools + // 3. CI/CD performance tracking over time + const smallDocument = "let x = 1 in x"; const mediumDocument = ` let @@ -295,7 +375,7 @@ describe("Async Validation", () => { const duration = Date.now() - startTime; expect(result).to.not.be.undefined; - expect(duration).to.be.lessThan(1000, "Small document should validate quickly"); + // Log performance for manual observation (no assertions on timing) console.log(`Small document validation took ${duration}ms`); }); @@ -315,7 +395,7 @@ describe("Async Validation", () => { const duration = Date.now() - startTime; expect(result).to.not.be.undefined; - expect(duration).to.be.lessThan(5000, "Medium document should validate in reasonable time"); + // Log performance for manual observation (no assertions on timing) console.log(`Medium document validation took ${duration}ms`); }); }); From 4af63c8884e41aac0a290b03e23ca8ab0ed0766a Mon Sep 17 00:00:00 2001 From: Matt Masson Date: Tue, 9 Sep 2025 09:54:42 -0400 Subject: [PATCH 13/42] fix promise implementation --- .../utils/promiseUtils.ts | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/powerquery-language-services/utils/promiseUtils.ts b/src/powerquery-language-services/utils/promiseUtils.ts index 3bbe43ed..5fe3ffd7 100644 --- a/src/powerquery-language-services/utils/promiseUtils.ts +++ b/src/powerquery-language-services/utils/promiseUtils.ts @@ -15,10 +15,15 @@ export async function promiseAllWithCancellation( cancellationToken.throwIfCancelled(); return new Promise((resolve, reject) => { + let isFinished = false; + const cancellationCheck = () => { + if (isFinished) return; + try { cancellationToken.throwIfCancelled(); } catch (error) { + isFinished = true; reject(error); return; } @@ -30,7 +35,16 @@ export async function promiseAllWithCancellation( cancellationCheck(); // Start the actual Promise.all - Promise.all(promises).then(resolve, reject); + Promise.all(promises).then( + result => { + isFinished = true; + resolve(result); + }, + error => { + isFinished = true; + reject(error); + }, + ); }); } From 5de58e38567e8ee4bdf1275885d20bff44657f6d Mon Sep 17 00:00:00 2001 From: Matt Masson Date: Tue, 9 Sep 2025 09:54:49 -0400 Subject: [PATCH 14/42] update test settings --- .vscode/settings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index ba9cdbb5..76ab2d6a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -6,8 +6,8 @@ "editor.formatOnSave": true, // VS Code Test Runner configuration - "testing.openTesting": "openOnTestStart", "testing.automaticallyOpenPeekView": "failureInVisibleDocument", + "testing.automaticallyOpenTestResults": "openOnTestStart", "testing.defaultGutterClickAction": "run", "testing.followRunningTest": true, From 65983a9c3fb46b708a4a559628ac8a8dcb0ebe65 Mon Sep 17 00:00:00 2001 From: Matt Masson Date: Tue, 9 Sep 2025 10:55:29 -0400 Subject: [PATCH 15/42] checkpoint --- src/test/validation/asyncValidation.test.ts | 198 ++++++++++++-------- 1 file changed, 119 insertions(+), 79 deletions(-) diff --git a/src/test/validation/asyncValidation.test.ts b/src/test/validation/asyncValidation.test.ts index 80adf80c..a448de14 100644 --- a/src/test/validation/asyncValidation.test.ts +++ b/src/test/validation/asyncValidation.test.ts @@ -2,12 +2,18 @@ // Licensed under the MIT license. import "mocha"; -import { assert, expect } from "chai"; -import { ICancellationToken } from "@microsoft/powerquery-parser"; +import { expect } from "chai"; +import { ICancellationToken, ResultUtils } from "@microsoft/powerquery-parser"; import * as fs from "fs"; import * as path from "path"; -import { ValidationSettings, AnalysisSettings, validate, ValidateOk } from "../../powerquery-language-services"; +import { + ValidationSettings, + AnalysisSettings, + validate, + ValidateOk, + TypeStrategy, +} from "../../powerquery-language-services"; import { TestConstants, TestUtils } from ".."; import * as ValidateTestUtils from "../testUtils/validationTestUtils"; @@ -31,10 +37,12 @@ describe("Async Validation", () => { const analysisSettings: AnalysisSettings = TestConstants.SimpleLibraryAnalysisSettings; const baseValidationSettings: ValidationSettings = { - ...TestConstants.SimpleValidateNoneSettings, - checkInvokeExpressions: true, - checkUnknownIdentifiers: true, - checkForDuplicateIdentifiers: true, + // TODO: Using Standard library results in an error - "expected a 'Each' scope item with key '_'" + // https://github.com/microsoft/powerquery-language-services/issues/255 + // ...TestConstants.StandardLibraryValidateAllSettings, + ...TestConstants.SimpleLibraryValidateAllSettings, + isWorkspaceCacheAllowed: false, + typeStrategy: TypeStrategy.Extended, }; describe("Large document validation", () => { @@ -54,67 +62,88 @@ describe("Async Validation", () => { }); it("should validate clean large document successfully without cancellation", async () => { - const cancellationToken = createCancellationToken(); const validationSettings: ValidationSettings = { ...baseValidationSettings, - cancellationToken, + cancellationToken: undefined, // No cancellation for this test }; const startTime = Date.now(); - const result: ValidateOk = await ValidateTestUtils.assertValidate({ - text: largeSectionDocumentText, - analysisSettings, - validationSettings, - }); + try { + const result: ValidateOk = await ValidateTestUtils.assertValidate({ + text: largeSectionDocumentText, + analysisSettings, + validationSettings, + }); - const endTime = Date.now(); - const duration = endTime - startTime; + const endTime = Date.now(); + const duration = endTime - startTime; - // Validation should complete successfully - expect(result).to.not.be.undefined; - expect(result.diagnostics).to.be.an("array"); - expect(result.hasSyntaxError).to.be.false; + // Validation should complete successfully + expect(result).to.not.be.undefined; + expect(result.diagnostics).to.be.an("array"); + expect(result.hasSyntaxError).to.be.false; - // Should have no parse errors since this is the clean file - expect(result.hasSyntaxError).to.be.false; + // Should have no parse errors since this is the clean file + expect(result.hasSyntaxError).to.be.false; - // Should have significantly fewer diagnostic errors than the diagnostics file - expect(result.diagnostics.length).to.be.lessThan(100, "Clean document should have fewer diagnostic errors"); + // Should have significantly fewer diagnostic errors than the diagnostics file + expect(result.diagnostics.length).to.be.lessThan( + 100, + "Clean document should have fewer diagnostic errors", + ); - // Log performance for manual observation (no assertions on timing) - console.log( - `Clean large document validation took ${duration}ms with ${result.diagnostics.length} diagnostics`, - ); + // Log performance for manual observation (no assertions on timing) + console.log( + `Clean large document validation took ${duration}ms with ${result.diagnostics.length} diagnostics`, + ); + } catch (error: any) { + const endTime = Date.now(); + const duration = endTime - startTime; + + console.error(`❌ Validation failed after ${duration}ms`); + console.error(`Error: ${error.message}`); + + throw error; // Re-throw to fail the test + } }).timeout(60000); // 60 second timeout it("should validate document with diagnostic errors", async () => { - const cancellationToken = createCancellationToken(); const validationSettings: ValidationSettings = { ...baseValidationSettings, - cancellationToken, + cancellationToken: undefined, // No cancellation for this test }; const startTime = Date.now(); - const result: ValidateOk = await ValidateTestUtils.assertValidate({ - text: largeSectionDocumentWithDiagnosticsText, - analysisSettings, - validationSettings, - }); + try { + const result: ValidateOk = await ValidateTestUtils.assertValidate({ + text: largeSectionDocumentWithDiagnosticsText, + analysisSettings, + validationSettings, + }); - const endTime = Date.now(); - const duration = endTime - startTime; + const endTime = Date.now(); + const duration = endTime - startTime; - // Validation should complete successfully - expect(result).to.not.be.undefined; - expect(result.diagnostics).to.be.an("array"); - expect(result.hasSyntaxError).to.be.false; + // Validation should complete successfully + expect(result).to.not.be.undefined; + expect(result.diagnostics).to.be.an("array"); + expect(result.hasSyntaxError).to.be.false; - // Should have diagnostic errors due to unknown identifiers - expect(result.diagnostics.length).to.be.greaterThan(0, "Document with diagnostics should have errors"); + // Should have diagnostic errors due to unknown identifiers + expect(result.diagnostics.length).to.be.greaterThan(0, "Document with diagnostics should have errors"); - console.log( - `Document with diagnostics validation took ${duration}ms, found ${result.diagnostics.length} diagnostics`, - ); + console.log( + `Document with diagnostics validation took ${duration}ms, found ${result.diagnostics.length} diagnostics`, + ); + } catch (error: any) { + const endTime = Date.now(); + const duration = endTime - startTime; + + console.error(`❌ Validation failed after ${duration}ms`); + console.error(`Error: ${error.message}`); + + throw error; // Re-throw to fail the test + } }).timeout(60000); it("should handle document with parser errors", async () => { @@ -169,14 +198,16 @@ describe("Async Validation", () => { cancellationToken.cancel("Test cancellation"); }, 500); // Cancel after 500ms - try { - await validationPromise; - // If we get here without an error, cancellation might not be working properly - // but we need to verify if the operation actually respected the cancellation - assert.fail("Expected validation to be cancelled, but it completed successfully"); - } catch (error: any) { + const result = await validationPromise; + + if (ResultUtils.isOk(result)) { + // If validation completed successfully, it finished before cancellation + expect(result.value).to.not.be.undefined; + console.log("Validation completed before cancellation could take effect"); + } else { // Expect cancellation error - expect(error.message).to.contain("cancelled"); + expect(result.error.message).to.contain("cancelled"); + console.log("Validation was successfully cancelled"); } }).timeout(30000); // 30 second timeout @@ -201,13 +232,14 @@ describe("Async Validation", () => { cancellationToken.cancel("Test cancellation"); }, delay); - try { - await validationPromise; + const result = await validationPromise; + + if (ResultUtils.isOk(result)) { // If validation completes despite cancellation, // it might have finished before cancellation occurred console.log(`Validation completed before cancellation at ${delay}ms`); - } catch (error: any) { - expect(error.message).to.contain("cancelled"); + } else { + expect(result.error.message).to.contain("cancelled"); console.log(`Successfully cancelled validation at ${delay}ms`); } } @@ -244,26 +276,27 @@ describe("Async Validation", () => { () => { cancellationToken.cancel("Performance test cancellation"); }, - Math.min(completeDuration * 0.7, 400), // Cancel at 70% of completion time, max 400ms + Math.min(completeDuration * 0.2, 300), // Cancel at 20% of completion time, max 300ms ); let cancellationDuration = 0; let wasCancelled = false; - try { - await validate( - TestUtils.mockDocument(largeSectionDocumentWithDiagnosticsText), - analysisSettings, - cancelledValidationSettings, - ); + const result = await validate( + TestUtils.mockDocument(largeSectionDocumentWithDiagnosticsText), + analysisSettings, + cancelledValidationSettings, + ); + + if (ResultUtils.isOk(result)) { // If we get here, validation completed before cancellation cancellationDuration = Date.now() - startCancelled; console.log( `Cancellation test: validation completed in ${cancellationDuration}ms before cancellation could occur`, ); - } catch (error: any) { + } else { cancellationDuration = Date.now() - startCancelled; wasCancelled = true; - expect(error.message).to.contain("cancelled"); + expect(result.error.message).to.contain("cancelled"); console.log(`Cancellation test: validation was cancelled after ${cancellationDuration}ms`); } @@ -331,11 +364,16 @@ describe("Async Validation", () => { cancellationToken, }; - try { - await validate(TestUtils.mockDocument("let x = 1 in x"), analysisSettings, validationSettings); - assert.fail("Expected validation to throw due to pre-cancelled token"); - } catch (error: any) { - expect(error.message).to.contain("cancelled"); + const result = await validate( + TestUtils.mockDocument("let x = 1 in x"), + analysisSettings, + validationSettings, + ); + + expect(ResultUtils.isError(result), "Expected validation to return error due to pre-cancelled token").to.be + .true; + if (ResultUtils.isError(result)) { + expect(result.error.message).to.contain("cancelled"); } }); }); @@ -419,11 +457,12 @@ describe("Async Validation", () => { setTimeout(() => cancellationToken.cancel("Test cancellation"), 100); - try { - await validationPromise; + const result = await validationPromise; + + if (ResultUtils.isOk(result)) { console.log("Validation completed before cancellation"); - } catch (error: any) { - expect(error.message).to.contain("cancelled"); + } else { + expect(result.error.message).to.contain("cancelled"); } }); @@ -445,11 +484,12 @@ describe("Async Validation", () => { setTimeout(() => cancellationToken.cancel("Test cancellation"), 100); - try { - await validationPromise; + const result = await validationPromise; + + if (ResultUtils.isOk(result)) { console.log("Validation completed before cancellation"); - } catch (error: any) { - expect(error.message).to.contain("cancelled"); + } else { + expect(result.error.message).to.contain("cancelled"); } }); }); From d85e13f8091481a385d805bbb07b5f341aa6edf2 Mon Sep 17 00:00:00 2001 From: Matt Masson Date: Tue, 9 Sep 2025 11:34:42 -0400 Subject: [PATCH 16/42] regenerate lock file --- package-lock.json | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/package-lock.json b/package-lock.json index 08b1b972..9cc25d35 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2084,21 +2084,6 @@ "dev": true, "license": "ISC" }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", From ace79eb8bb0bafbe086cd33a2b25b80cd7cc5942 Mon Sep 17 00:00:00 2001 From: Matt Masson Date: Tue, 9 Sep 2025 11:35:01 -0400 Subject: [PATCH 17/42] 0.11.1 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9cc25d35..b55c3de7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@microsoft/powerquery-language-services", - "version": "0.11.0", + "version": "0.11.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@microsoft/powerquery-language-services", - "version": "0.11.0", + "version": "0.11.1", "license": "MIT", "dependencies": { "@microsoft/powerquery-formatter": "0.3.17", diff --git a/package.json b/package.json index d17b6b36..b8bee750 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@microsoft/powerquery-language-services", - "version": "0.11.0", + "version": "0.11.1", "author": "Microsoft", "license": "MIT", "scripts": { From 1bc60b2ba80789ba16599e2ea566b6c610ec53ed Mon Sep 17 00:00:00 2001 From: Matt Masson Date: Tue, 9 Sep 2025 13:47:16 -0400 Subject: [PATCH 18/42] fix lint and async issues --- eslint.config.js | 1 + .../utils/promiseUtils.ts | 18 +- .../validate/validate.ts | 25 +- .../validate/validateDuplicateIdentifiers.ts | 105 +++++--- .../validate/validateFunctionExpression.ts | 17 +- .../validate/validateInvokeExpression.ts | 7 +- src/test/testUtils/validationTestUtils.ts | 60 +++++ src/test/utils/promiseUtils.test.ts | 143 ++++++---- src/test/validation/asyncValidation.test.ts | 249 +++++++++++------- 9 files changed, 406 insertions(+), 219 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index 416dd638..be3ac261 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -35,6 +35,7 @@ module.exports = [ __dirname: "readonly", module: "readonly", setTimeout: "readonly", + console: "readonly", }, }, plugins: { diff --git a/src/powerquery-language-services/utils/promiseUtils.ts b/src/powerquery-language-services/utils/promiseUtils.ts index 5fe3ffd7..f29d3efe 100644 --- a/src/powerquery-language-services/utils/promiseUtils.ts +++ b/src/powerquery-language-services/utils/promiseUtils.ts @@ -7,17 +7,17 @@ import { ICancellationToken } from "@microsoft/powerquery-parser"; * Promise.all with cancellation support. Useful when you want parallel execution * but need the ability to cancel if needed. */ -export async function promiseAllWithCancellation( +export function promiseAllWithCancellation( promises: Promise[], cancellationToken?: ICancellationToken, ): Promise { if (cancellationToken) { cancellationToken.throwIfCancelled(); - return new Promise((resolve, reject) => { - let isFinished = false; + return new Promise((resolve: (value: T[]) => void, reject: (reason?: any) => void) => { + let isFinished: boolean = false; - const cancellationCheck = () => { + const cancellationCheck: () => void = (): void => { if (isFinished) return; try { @@ -25,8 +25,10 @@ export async function promiseAllWithCancellation( } catch (error) { isFinished = true; reject(error); + return; } + // Check again after a short delay setTimeout(cancellationCheck, 10); }; @@ -35,12 +37,13 @@ export async function promiseAllWithCancellation( cancellationCheck(); // Start the actual Promise.all + // eslint-disable-next-line promise/prefer-await-to-then Promise.all(promises).then( - result => { + (result: T[]) => { isFinished = true; resolve(result); }, - error => { + (error: any) => { isFinished = true; reject(error); }, @@ -64,7 +67,8 @@ export async function processSequentiallyWithCancellation( for (const item of items) { cancellationToken?.throwIfCancelled(); - const result = await processor(item); + // eslint-disable-next-line no-await-in-loop + const result: R = await processor(item); results.push(result); } diff --git a/src/powerquery-language-services/validate/validate.ts b/src/powerquery-language-services/validate/validate.ts index a8525e8c..c10cb046 100644 --- a/src/powerquery-language-services/validate/validate.ts +++ b/src/powerquery-language-services/validate/validate.ts @@ -2,11 +2,13 @@ // Licensed under the MIT license. import { NodeIdMap, ParseError, ParseState } from "@microsoft/powerquery-parser/lib/powerquery-parser/parser"; +import { Diagnostic } from "vscode-languageserver-types"; import { TextDocument } from "vscode-languageserver-textdocument"; import { Trace } from "@microsoft/powerquery-parser/lib/powerquery-parser/common/trace"; import { Analysis, AnalysisSettings, AnalysisUtils } from "../analysis"; import { CommonError, Result, ResultUtils } from "@microsoft/powerquery-parser"; +import { processSequentiallyWithCancellation } from "../utils/promiseUtils"; import { TypeCache } from "../inspection"; import { validateDuplicateIdentifiers } from "./validateDuplicateIdentifiers"; import { validateFunctionExpression } from "./validateFunctionExpression"; @@ -16,7 +18,6 @@ import { validateParse } from "./validateParse"; import { validateUnknownIdentifiers } from "./validateUnknownIdentifiers"; import type { ValidationSettings } from "./validationSettings"; import { ValidationTraceConstant } from "../trace"; -import { processSequentiallyWithCancellation } from "../utils/promiseUtils"; export function validate( textDocument: TextDocument, @@ -36,7 +37,6 @@ export function validate( }; const analysis: Analysis = AnalysisUtils.analysis(textDocument, analysisSettings); - validationSettings.cancellationToken?.throwIfCancelled(); const parseState: ParseState | undefined = ResultUtils.assertOk(await analysis.getParseState()); validationSettings.cancellationToken?.throwIfCancelled(); @@ -55,15 +55,15 @@ export function validate( const typeCache: TypeCache = analysis.getTypeCache(); // Define validation operations to run sequentially - const validationOperations = [ + const validationOperations: (() => Promise)[] = [ // Parse validation (if there are parse errors) - async () => await validateParse(parseError, updatedSettings), + async (): Promise => await validateParse(parseError, updatedSettings), ]; // Add conditional validations based on settings if (validationSettings.checkForDuplicateIdentifiers && nodeIdMapCollection) { validationOperations.push( - async () => + async (): Promise => await validateDuplicateIdentifiers( textDocument, nodeIdMapCollection, @@ -75,26 +75,29 @@ export function validate( if (validationSettings.checkInvokeExpressions && nodeIdMapCollection) { validationOperations.push( - async () => await validateFunctionExpression(validationSettings, nodeIdMapCollection), - async () => await validateInvokeExpression(validationSettings, nodeIdMapCollection, typeCache), + async (): Promise => + await validateFunctionExpression(validationSettings, nodeIdMapCollection), + async (): Promise => + await validateInvokeExpression(validationSettings, nodeIdMapCollection, typeCache), ); } if (validationSettings.checkUnknownIdentifiers && nodeIdMapCollection) { validationOperations.push( - async () => await validateUnknownIdentifiers(validationSettings, nodeIdMapCollection, typeCache), + async (): Promise => + await validateUnknownIdentifiers(validationSettings, nodeIdMapCollection, typeCache), ); } // Execute all validation operations sequentially with cancellation support - const allDiagnostics = await processSequentiallyWithCancellation( + const allDiagnostics: Diagnostic[][] = await processSequentiallyWithCancellation( validationOperations, - operation => operation(), + (operation: () => Promise) => operation(), validationSettings.cancellationToken, ); // Flatten all diagnostics into a single array - const flattenedDiagnostics = allDiagnostics.flat(); + const flattenedDiagnostics: Diagnostic[] = allDiagnostics.flat(); const result: ValidateOk = { diagnostics: flattenedDiagnostics, diff --git a/src/powerquery-language-services/validate/validateDuplicateIdentifiers.ts b/src/powerquery-language-services/validate/validateDuplicateIdentifiers.ts index b0d22446..1ee87db5 100644 --- a/src/powerquery-language-services/validate/validateDuplicateIdentifiers.ts +++ b/src/powerquery-language-services/validate/validateDuplicateIdentifiers.ts @@ -17,6 +17,7 @@ import { Trace } from "@microsoft/powerquery-parser/lib/powerquery-parser/common import { Localization, LocalizationUtils } from "../localization"; import { DiagnosticErrorCode } from "../diagnosticErrorCode"; import { PositionUtils } from ".."; +import { processSequentiallyWithCancellation } from "../utils/promiseUtils"; import { ValidationSettings } from "./validationSettings"; import { ValidationTraceConstant } from "../trace"; @@ -45,49 +46,64 @@ export async function validateDuplicateIdentifiers( const documentUri: string = textDocument.uri; - const result: Diagnostic[] = [ - ...validateDuplicateIdentifiersForLetExpresion( - documentUri, - nodeIdMapCollection, - updatedSettings, - trace.id, - cancellationToken, - ), - ...validateDuplicateIdentifiersForRecord( - documentUri, - nodeIdMapCollection, - updatedSettings, - trace.id, - cancellationToken, - ), - ...validateDuplicateIdentifiersForRecordType( - documentUri, - nodeIdMapCollection, - updatedSettings, - trace.id, - cancellationToken, - ), - ...validateDuplicateIdentifiersForSection( - documentUri, - nodeIdMapCollection, - updatedSettings, - trace.id, - cancellationToken, - ), + // Create an array of validation functions to process sequentially + const validationFunctions: Array<() => Promise>> = [ + (): Promise> => + validateDuplicateIdentifiersForLetExpresion( + documentUri, + nodeIdMapCollection, + updatedSettings, + trace.id, + cancellationToken, + ), + (): Promise> => + validateDuplicateIdentifiersForRecord( + documentUri, + nodeIdMapCollection, + updatedSettings, + trace.id, + cancellationToken, + ), + (): Promise> => + validateDuplicateIdentifiersForRecordType( + documentUri, + nodeIdMapCollection, + updatedSettings, + trace.id, + cancellationToken, + ), + (): Promise> => + validateDuplicateIdentifiersForSection( + documentUri, + nodeIdMapCollection, + updatedSettings, + trace.id, + cancellationToken, + ), ]; + // Process all validation functions sequentially with cancellation support + const diagnosticArrays: ReadonlyArray[] = await processSequentiallyWithCancellation( + validationFunctions, + (validationFunction: () => Promise>) => validationFunction(), + cancellationToken, + ); + + // Flatten the results + const result: Diagnostic[] = diagnosticArrays.flat(); + trace.exit(); return result; } -function validateDuplicateIdentifiersForLetExpresion( +async function validateDuplicateIdentifiersForLetExpresion( documentUri: DocumentUri, nodeIdMapCollection: NodeIdMap.Collection, validationSettings: ValidationSettings, correlationId: number, cancellationToken: ICancellationToken | undefined, -): ReadonlyArray { +): Promise> { const trace: Trace = validationSettings.traceManager.entry( ValidationTraceConstant.Validation, validateDuplicateIdentifiersForLetExpresion.name, @@ -96,7 +112,7 @@ function validateDuplicateIdentifiersForLetExpresion( const letIds: Set = nodeIdMapCollection.idsByNodeKind.get(Ast.NodeKind.LetExpression) ?? new Set(); - const result: ReadonlyArray = validateDuplicateIdentifiersForKeyValuePair( + const result: ReadonlyArray = await validateDuplicateIdentifiersForKeyValuePair( documentUri, nodeIdMapCollection, [letIds], @@ -111,13 +127,13 @@ function validateDuplicateIdentifiersForLetExpresion( return result; } -function validateDuplicateIdentifiersForRecord( +async function validateDuplicateIdentifiersForRecord( documentUri: DocumentUri, nodeIdMapCollection: NodeIdMap.Collection, validationSettings: ValidationSettings, correlationId: number, cancellationToken: ICancellationToken | undefined, -): ReadonlyArray { +): Promise> { const trace: Trace = validationSettings.traceManager.entry( ValidationTraceConstant.Validation, validateDuplicateIdentifiersForRecord.name, @@ -129,7 +145,7 @@ function validateDuplicateIdentifiersForRecord( nodeIdMapCollection.idsByNodeKind.get(Ast.NodeKind.RecordLiteral) ?? new Set(), ]; - const result: ReadonlyArray = validateDuplicateIdentifiersForKeyValuePair( + const result: ReadonlyArray = await validateDuplicateIdentifiersForKeyValuePair( documentUri, nodeIdMapCollection, recordIds, @@ -144,13 +160,13 @@ function validateDuplicateIdentifiersForRecord( return result; } -function validateDuplicateIdentifiersForRecordType( +async function validateDuplicateIdentifiersForRecordType( documentUri: DocumentUri, nodeIdMapCollection: NodeIdMap.Collection, validationSettings: ValidationSettings, correlationId: number, cancellationToken: ICancellationToken | undefined, -): ReadonlyArray { +): Promise> { const trace: Trace = validationSettings.traceManager.entry( ValidationTraceConstant.Validation, validateDuplicateIdentifiersForRecordType.name, @@ -161,7 +177,7 @@ function validateDuplicateIdentifiersForRecordType( nodeIdMapCollection.idsByNodeKind.get(Ast.NodeKind.RecordType) ?? new Set(), ]; - const result: ReadonlyArray = validateDuplicateIdentifiersForKeyValuePair( + const result: ReadonlyArray = await validateDuplicateIdentifiersForKeyValuePair( documentUri, nodeIdMapCollection, recordTypeIds, @@ -176,13 +192,13 @@ function validateDuplicateIdentifiersForRecordType( return result; } -function validateDuplicateIdentifiersForSection( +async function validateDuplicateIdentifiersForSection( documentUri: DocumentUri, nodeIdMapCollection: NodeIdMap.Collection, validationSettings: ValidationSettings, correlationId: number, cancellationToken: ICancellationToken | undefined, -): ReadonlyArray { +): Promise> { const trace: Trace = validationSettings.traceManager.entry( ValidationTraceConstant.Validation, validateDuplicateIdentifiersForSection.name, @@ -191,7 +207,7 @@ function validateDuplicateIdentifiersForSection( const sectionIds: Set = nodeIdMapCollection.idsByNodeKind.get(Ast.NodeKind.Section) ?? new Set(); - const result: ReadonlyArray = validateDuplicateIdentifiersForKeyValuePair( + const result: ReadonlyArray = await validateDuplicateIdentifiersForKeyValuePair( documentUri, nodeIdMapCollection, [sectionIds], @@ -210,7 +226,7 @@ function validateDuplicateIdentifiersForSection( // for node in nodeIds: // for childOfNode in iterNodeFactory(node): // ... -function validateDuplicateIdentifiersForKeyValuePair( +async function validateDuplicateIdentifiersForKeyValuePair( documentUri: DocumentUri, nodeIdMapCollection: NodeIdMap.Collection, nodeIdCollections: ReadonlyArray>, @@ -221,7 +237,7 @@ function validateDuplicateIdentifiersForKeyValuePair( validationSettings: ValidationSettings, correlationId: number, cancellationToken: ICancellationToken | undefined, -): ReadonlyArray { +): Promise> { const numIds: number = nodeIdCollections.reduce( (partial: number, set: Set) => partial + set.size, 0, @@ -243,6 +259,9 @@ function validateDuplicateIdentifiersForKeyValuePair( for (const collection of nodeIdCollections) { for (const nodeId of collection) { cancellationToken?.throwIfCancelled(); + // Yield control to allow for cancellation + // eslint-disable-next-line no-await-in-loop + await Promise.resolve(); const node: TXorNode = NodeIdMapUtils.assertXor(nodeIdMapCollection, nodeId); const duplicateFieldsByKey: Map = new Map(); diff --git a/src/powerquery-language-services/validate/validateFunctionExpression.ts b/src/powerquery-language-services/validate/validateFunctionExpression.ts index 70796451..48927098 100644 --- a/src/powerquery-language-services/validate/validateFunctionExpression.ts +++ b/src/powerquery-language-services/validate/validateFunctionExpression.ts @@ -48,12 +48,19 @@ export async function validateFunctionExpression( const diagnostics: Diagnostic[][] = []; for (const nodeId of fnExpressionIds) { - validationSettings.cancellationToken?.throwIfCancelled(); + const nodeDiagnostics: Diagnostic[] = validateNoDuplicateParameter( + updatedSettings, + nodeIdMapCollection, + nodeId, + ); - const nodeDiagnostics = await validateNoDuplicateParameter(updatedSettings, nodeIdMapCollection, nodeId); diagnostics.push(nodeDiagnostics); - updatedSettings.cancellationToken?.throwIfCancelled(); + // Yield control periodically for better async behavior + // eslint-disable-next-line no-await-in-loop + await Promise.resolve(); + + validationSettings.cancellationToken?.throwIfCancelled(); } trace.exit(); @@ -61,11 +68,11 @@ export async function validateFunctionExpression( return diagnostics.flat(); } -async function validateNoDuplicateParameter( +function validateNoDuplicateParameter( validationSettings: ValidationSettings, nodeIdMapCollection: NodeIdMap.Collection, fnExpressionId: number, -): Promise { +): Diagnostic[] { const fnExpression: TXorNode = NodeIdMapUtils.assertXorChecked( nodeIdMapCollection, fnExpressionId, diff --git a/src/powerquery-language-services/validate/validateInvokeExpression.ts b/src/powerquery-language-services/validate/validateInvokeExpression.ts index b9296ac5..c879e1d9 100644 --- a/src/powerquery-language-services/validate/validateInvokeExpression.ts +++ b/src/powerquery-language-services/validate/validateInvokeExpression.ts @@ -51,11 +51,12 @@ export async function validateInvokeExpression( // Process inspections with cancellation support const inspections: Inspection.TriedInvokeExpression[] = []; + for (const task of inspectionTasks) { validationSettings.cancellationToken?.throwIfCancelled(); // eslint-disable-next-line no-await-in-loop - const inspection = await task; + const inspection: Inspection.TriedInvokeExpression = await task; inspections.push(inspection); } @@ -77,13 +78,15 @@ export async function validateInvokeExpression( // Process diagnostics with cancellation support const diagnostics: ReadonlyArray[] = []; + for (const task of diagnosticTasks) { validationSettings.cancellationToken?.throwIfCancelled(); // eslint-disable-next-line no-await-in-loop - const diagnostic = await task; + const diagnostic: ReadonlyArray = await task; diagnostics.push(diagnostic); } + trace.exit(); return diagnostics.flat(); diff --git a/src/test/testUtils/validationTestUtils.ts b/src/test/testUtils/validationTestUtils.ts index aa043e48..760e8531 100644 --- a/src/test/testUtils/validationTestUtils.ts +++ b/src/test/testUtils/validationTestUtils.ts @@ -48,6 +48,66 @@ export async function assertValidate(params: { return triedValidation.value; } +/** + * Asserts that a validation result is an error and that the error message contains the expected text. + */ +export function assertValidationError( + result: Result, + expectedMessageContains: string, + assertionMessage?: string, +): void { + const message: string = + assertionMessage ?? `Expected validation to return error containing '${expectedMessageContains}'`; + + expect(ResultUtils.isError(result), message).to.be.true; + + if (ResultUtils.isError(result)) { + expect(result.error.message).to.contain(expectedMessageContains); + } +} + +/** + * Asserts that a validation result is an error caused by cancellation. + */ +export function assertValidationCancelled( + result: Result, + assertionMessage?: string, +): void { + assertValidationError( + result, + "cancelled", + assertionMessage ?? "Expected validation to return error due to cancellation", + ); +} + +/** + * Asserts that a validation result is successful (not an error). + */ +export function assertValidationSuccess(result: Result, assertionMessage?: string): void { + const message: string = assertionMessage ?? "Expected validation to succeed"; + expect(ResultUtils.isOk(result), message).to.be.true; +} + +/** + * Handles validation results that could be either successful or cancelled due to timing. + * This is useful for tests where cancellation timing is non-deterministic. + */ +export function assertValidationSuccessOrCancelled( + result: Result, + onSuccess?: () => void, + onCancelled?: () => void, +): void { + if (ResultUtils.isOk(result)) { + // Validation completed successfully + expect(result.value).to.not.be.undefined; + onSuccess?.(); + } else { + // Expect cancellation error + expect(result.error.message).to.contain("cancelled"); + onCancelled?.(); + } +} + export async function assertValidateDiagnostics(params: { readonly text: string; readonly analysisSettings: PQLS.AnalysisSettings; diff --git a/src/test/utils/promiseUtils.test.ts b/src/test/utils/promiseUtils.test.ts index 1e7b0184..174e99bf 100644 --- a/src/test/utils/promiseUtils.test.ts +++ b/src/test/utils/promiseUtils.test.ts @@ -6,34 +6,34 @@ import { assert, expect } from "chai"; import { ICancellationToken } from "@microsoft/powerquery-parser"; import { - promiseAllWithCancellation, processSequentiallyWithCancellation, + promiseAllWithCancellation, } from "../../powerquery-language-services/utils/promiseUtils"; function createTestCancellationToken(): ICancellationToken & { cancel: (reason: string) => void } { - let isCancelled = false; + let isCancelled: boolean = false; return { - isCancelled: () => isCancelled, - throwIfCancelled: () => { + isCancelled: (): boolean => isCancelled, + throwIfCancelled: (): void => { if (isCancelled) { throw new Error("Operation was cancelled"); } }, - cancel: (_reason: string) => { + cancel: (_reason: string): void => { isCancelled = true; }, }; } function createDelayedPromise(delayMs: number, value: string): Promise { - return new Promise(resolve => { + return new Promise((resolve: (value: string) => void) => { setTimeout(() => resolve(value), delayMs); }); } function createDelayedRejectingPromise(delayMs: number, errorMessage: string): Promise { - return new Promise((_, reject) => { + return new Promise((_: (value: string) => void, reject: (reason: Error) => void) => { setTimeout(() => reject(new Error(errorMessage)), delayMs); }); } @@ -41,34 +41,41 @@ function createDelayedRejectingPromise(delayMs: number, errorMessage: string): P describe("Promise Utils", () => { describe("promiseAllWithCancellation", () => { it("should resolve all promises when no cancellation token is provided", async () => { - const promises = [ + const promises: Promise[] = [ createDelayedPromise(10, "first"), createDelayedPromise(20, "second"), createDelayedPromise(15, "third"), ]; - const result = await promiseAllWithCancellation(promises); + const result: string[] = await promiseAllWithCancellation(promises); expect(result).to.deep.equal(["first", "second", "third"]); }); it("should resolve all promises when cancellation token is not cancelled", async () => { - const cancellationToken = createTestCancellationToken(); - const promises = [ + const cancellationToken: ICancellationToken & { cancel: (reason: string) => void } = + createTestCancellationToken(); + + const promises: Promise[] = [ createDelayedPromise(10, "first"), createDelayedPromise(20, "second"), createDelayedPromise(15, "third"), ]; - const result = await promiseAllWithCancellation(promises, cancellationToken); + const result: string[] = await promiseAllWithCancellation(promises, cancellationToken); expect(result).to.deep.equal(["first", "second", "third"]); expect(cancellationToken.isCancelled()).to.be.false; }); it("should reject immediately if cancellation token is already cancelled", async () => { - const cancellationToken = createTestCancellationToken(); + const cancellationToken: ICancellationToken & { cancel: (reason: string) => void } = + createTestCancellationToken(); + cancellationToken.cancel("Pre-cancelled"); - const promises = [createDelayedPromise(100, "first"), createDelayedPromise(200, "second")]; + const promises: Promise[] = [ + createDelayedPromise(100, "first"), + createDelayedPromise(200, "second"), + ]; try { await promiseAllWithCancellation(promises, cancellationToken); @@ -79,8 +86,10 @@ describe("Promise Utils", () => { }); it("should reject when cancellation token is cancelled during execution", async () => { - const cancellationToken = createTestCancellationToken(); - const promises = [ + const cancellationToken: ICancellationToken & { cancel: (reason: string) => void } = + createTestCancellationToken(); + + const promises: Promise[] = [ createDelayedPromise(100, "first"), createDelayedPromise(200, "second"), createDelayedPromise(300, "third"), @@ -100,8 +109,10 @@ describe("Promise Utils", () => { }); it("should handle promise rejections normally when not cancelled", async () => { - const cancellationToken = createTestCancellationToken(); - const promises = [ + const cancellationToken: ICancellationToken & { cancel: (reason: string) => void } = + createTestCancellationToken(); + + const promises: Promise[] = [ createDelayedPromise(10, "first"), createDelayedRejectingPromise(20, "Test error"), createDelayedPromise(15, "third"), @@ -118,37 +129,46 @@ describe("Promise Utils", () => { describe("processSequentiallyWithCancellation", () => { it("should process all items sequentially when no cancellation token is provided", async () => { - const items = ["a", "b", "c"]; - const processor = async (item: string) => { - await new Promise(resolve => setTimeout(resolve, 10)); + const items: string[] = ["a", "b", "c"]; + + const processor: (item: string) => Promise = async (item: string): Promise => { + await new Promise((resolve: (value: unknown) => void) => setTimeout(resolve, 10)); + return item.toUpperCase(); }; - const result = await processSequentiallyWithCancellation(items, processor); + const result: string[] = await processSequentiallyWithCancellation(items, processor); expect(result).to.deep.equal(["A", "B", "C"]); }); it("should process all items when cancellation token is not cancelled", async () => { - const cancellationToken = createTestCancellationToken(); - const items = ["a", "b", "c"]; - const processor = async (item: string) => { - await new Promise(resolve => setTimeout(resolve, 10)); + const cancellationToken: ICancellationToken & { cancel: (reason: string) => void } = + createTestCancellationToken(); + + const items: string[] = ["a", "b", "c"]; + + const processor: (item: string) => Promise = async (item: string): Promise => { + await new Promise((resolve: (value: unknown) => void) => setTimeout(resolve, 10)); + return item.toUpperCase(); }; - const result = await processSequentiallyWithCancellation(items, processor, cancellationToken); + const result: string[] = await processSequentiallyWithCancellation(items, processor, cancellationToken); expect(result).to.deep.equal(["A", "B", "C"]); expect(cancellationToken.isCancelled()).to.be.false; }); it("should stop processing when cancellation token is cancelled", async () => { - const cancellationToken = createTestCancellationToken(); - const items = ["a", "b", "c", "d", "e"]; + const cancellationToken: ICancellationToken & { cancel: (reason: string) => void } = + createTestCancellationToken(); + + const items: string[] = ["a", "b", "c", "d", "e"]; const processedItems: string[] = []; - const processor = async (item: string) => { + const processor: (item: string) => Promise = async (item: string): Promise => { processedItems.push(item); - await new Promise(resolve => setTimeout(resolve, 20)); + await new Promise((resolve: (value: unknown) => void) => setTimeout(resolve, 20)); + return item.toUpperCase(); }; @@ -169,11 +189,15 @@ describe("Promise Utils", () => { }); it("should reject immediately if cancellation token is already cancelled", async () => { - const cancellationToken = createTestCancellationToken(); + const cancellationToken: ICancellationToken & { cancel: (reason: string) => void } = + createTestCancellationToken(); + cancellationToken.cancel("Pre-cancelled"); - const items = ["a", "b", "c"]; - const processor = async (item: string) => item.toUpperCase(); + const items: string[] = ["a", "b", "c"]; + + const processor: (item: string) => Promise = (item: string): Promise => + Promise.resolve(item.toUpperCase()); try { await processSequentiallyWithCancellation(items, processor, cancellationToken); @@ -184,22 +208,30 @@ describe("Promise Utils", () => { }); it("should handle empty arrays", async () => { - const cancellationToken = createTestCancellationToken(); + const cancellationToken: ICancellationToken & { cancel: (reason: string) => void } = + createTestCancellationToken(); + const items: string[] = []; - const processor = async (item: string) => item.toUpperCase(); - const result = await processSequentiallyWithCancellation(items, processor, cancellationToken); + const processor: (item: string) => Promise = (item: string): Promise => + Promise.resolve(item.toUpperCase()); + + const result: string[] = await processSequentiallyWithCancellation(items, processor, cancellationToken); expect(result).to.deep.equal([]); }); it("should handle processor errors normally when not cancelled", async () => { - const cancellationToken = createTestCancellationToken(); - const items = ["a", "b", "c"]; - const processor = async (item: string) => { + const cancellationToken: ICancellationToken & { cancel: (reason: string) => void } = + createTestCancellationToken(); + + const items: string[] = ["a", "b", "c"]; + + const processor: (item: string) => Promise = (item: string): Promise => { if (item === "b") { throw new Error("Test processor error"); } - return item.toUpperCase(); + + return Promise.resolve(item.toUpperCase()); }; try { @@ -211,13 +243,16 @@ describe("Promise Utils", () => { }); it("should check cancellation before each item", async () => { - const cancellationToken = createTestCancellationToken(); - const items = ["a", "b", "c"]; - let processorCallCount = 0; + const cancellationToken: ICancellationToken & { cancel: (reason: string) => void } = + createTestCancellationToken(); - const processor = async (item: string) => { - processorCallCount++; - return item.toUpperCase(); + const items: string[] = ["a", "b", "c"]; + let processorCallCount: number = 0; + + const processor: (item: string) => Promise = (item: string): Promise => { + processorCallCount += 1; + + return Promise.resolve(item.toUpperCase()); }; // Cancel before any processing @@ -235,14 +270,17 @@ describe("Promise Utils", () => { describe("cancellation timing behavior", () => { it("should demonstrate different cancellation points in sequential processing", async () => { - const cancellationToken = createTestCancellationToken(); - const items = [1, 2, 3, 4, 5]; + const cancellationToken: ICancellationToken & { cancel: (reason: string) => void } = + createTestCancellationToken(); + + const items: number[] = [1, 2, 3, 4, 5]; const processedItems: number[] = []; - const processor = async (item: number) => { + const processor: (item: number) => Promise = async (item: number): Promise => { processedItems.push(item); // Each item takes 30ms to process - await new Promise(resolve => setTimeout(resolve, 30)); + await new Promise((resolve: (value: unknown) => void) => setTimeout(resolve, 30)); + return item * 2; }; @@ -256,6 +294,7 @@ describe("Promise Utils", () => { assert.fail("Expected processing to be cancelled"); } catch (error: any) { expect(error.message).to.contain("cancelled"); + console.log( `Processed ${processedItems.length} items before cancellation: [${processedItems.join(", ")}]`, ); @@ -265,7 +304,7 @@ describe("Promise Utils", () => { expect(processedItems.length).to.be.lessThan(items.length); // Should have processed items in order - for (let i = 0; i < processedItems.length; i++) { + for (let i: number = 0; i < processedItems.length; i = i + 1) { expect(processedItems[i]).to.equal(i + 1); } } diff --git a/src/test/validation/asyncValidation.test.ts b/src/test/validation/asyncValidation.test.ts index a448de14..f5816dc0 100644 --- a/src/test/validation/asyncValidation.test.ts +++ b/src/test/validation/asyncValidation.test.ts @@ -2,32 +2,34 @@ // Licensed under the MIT license. import "mocha"; -import { expect } from "chai"; -import { ICancellationToken, ResultUtils } from "@microsoft/powerquery-parser"; import * as fs from "fs"; import * as path from "path"; +import { expect } from "chai"; + +import { CommonError, ICancellationToken, Result, ResultUtils } from "@microsoft/powerquery-parser"; + +import * as ValidateTestUtils from "../testUtils/validationTestUtils"; import { - ValidationSettings, AnalysisSettings, + TypeStrategy, validate, ValidateOk, - TypeStrategy, + ValidationSettings, } from "../../powerquery-language-services"; import { TestConstants, TestUtils } from ".."; -import * as ValidateTestUtils from "../testUtils/validationTestUtils"; function createCancellationToken(): ICancellationToken { - let isCancelled = false; + let isCancelled: boolean = false; return { - isCancelled: () => isCancelled, - throwIfCancelled: () => { + isCancelled: (): boolean => isCancelled, + throwIfCancelled: (): void => { if (isCancelled) { throw new Error("Operation was cancelled"); } }, - cancel: (_reason: string) => { + cancel: (_reason: string): void => { isCancelled = true; }, }; @@ -52,9 +54,21 @@ describe("Async Validation", () => { before(() => { // Load the large section documents - const cleanFilePath = path.join(__dirname, "..", "files", "LargeSectionDocument.pq"); - const diagnosticsFilePath = path.join(__dirname, "..", "files", "LargeSectionDocument_WithDiagnostics.pq"); - const parserErrorFilePath = path.join(__dirname, "..", "files", "LargeSectionDocument_WithParserError.pq"); + const cleanFilePath: string = path.join(__dirname, "..", "files", "LargeSectionDocument.pq"); + + const diagnosticsFilePath: string = path.join( + __dirname, + "..", + "files", + "LargeSectionDocument_WithDiagnostics.pq", + ); + + const parserErrorFilePath: string = path.join( + __dirname, + "..", + "files", + "LargeSectionDocument_WithParserError.pq", + ); largeSectionDocumentText = fs.readFileSync(cleanFilePath, "utf8"); largeSectionDocumentWithDiagnosticsText = fs.readFileSync(diagnosticsFilePath, "utf8"); @@ -67,7 +81,8 @@ describe("Async Validation", () => { cancellationToken: undefined, // No cancellation for this test }; - const startTime = Date.now(); + const startTime: number = Date.now(); + try { const result: ValidateOk = await ValidateTestUtils.assertValidate({ text: largeSectionDocumentText, @@ -75,8 +90,8 @@ describe("Async Validation", () => { validationSettings, }); - const endTime = Date.now(); - const duration = endTime - startTime; + const endTime: number = Date.now(); + const duration: number = endTime - startTime; // Validation should complete successfully expect(result).to.not.be.undefined; @@ -96,12 +111,12 @@ describe("Async Validation", () => { console.log( `Clean large document validation took ${duration}ms with ${result.diagnostics.length} diagnostics`, ); - } catch (error: any) { - const endTime = Date.now(); - const duration = endTime - startTime; + } catch (error: unknown) { + const endTime: number = Date.now(); + const duration: number = endTime - startTime; console.error(`❌ Validation failed after ${duration}ms`); - console.error(`Error: ${error.message}`); + console.error(`Error: ${error instanceof Error ? error.message : String(error)}`); throw error; // Re-throw to fail the test } @@ -113,7 +128,8 @@ describe("Async Validation", () => { cancellationToken: undefined, // No cancellation for this test }; - const startTime = Date.now(); + const startTime: number = Date.now(); + try { const result: ValidateOk = await ValidateTestUtils.assertValidate({ text: largeSectionDocumentWithDiagnosticsText, @@ -121,8 +137,8 @@ describe("Async Validation", () => { validationSettings, }); - const endTime = Date.now(); - const duration = endTime - startTime; + const endTime: number = Date.now(); + const duration: number = endTime - startTime; // Validation should complete successfully expect(result).to.not.be.undefined; @@ -135,33 +151,35 @@ describe("Async Validation", () => { console.log( `Document with diagnostics validation took ${duration}ms, found ${result.diagnostics.length} diagnostics`, ); - } catch (error: any) { - const endTime = Date.now(); - const duration = endTime - startTime; + } catch (error: unknown) { + const endTime: number = Date.now(); + const duration: number = endTime - startTime; console.error(`❌ Validation failed after ${duration}ms`); - console.error(`Error: ${error.message}`); + console.error(`Error: ${error instanceof Error ? error.message : String(error)}`); throw error; // Re-throw to fail the test } }).timeout(60000); it("should handle document with parser errors", async () => { - const cancellationToken = createCancellationToken(); + const cancellationToken: ICancellationToken = createCancellationToken(); + const validationSettings: ValidationSettings = { ...baseValidationSettings, cancellationToken, }; - const startTime = Date.now(); + const startTime: number = Date.now(); + const result: ValidateOk = await ValidateTestUtils.assertValidate({ text: largeSectionDocumentWithParserErrorText, analysisSettings, validationSettings, }); - const endTime = Date.now(); - const duration = endTime - startTime; + const endTime: number = Date.now(); + const duration: number = endTime - startTime; // Validation should complete but have syntax errors expect(result).to.not.be.undefined; @@ -180,14 +198,15 @@ describe("Async Validation", () => { }).timeout(60000); it("should respect cancellation token when cancelled during validation", async () => { - const cancellationToken = createCancellationToken(); + const cancellationToken: ICancellationToken = createCancellationToken(); + const validationSettings: ValidationSettings = { ...baseValidationSettings, cancellationToken, }; // Start validation with the diagnostics file (takes longer) - const validationPromise = validate( + const validationPromise: Promise> = validate( TestUtils.mockDocument(largeSectionDocumentWithDiagnosticsText), analysisSettings, validationSettings, @@ -198,30 +217,33 @@ describe("Async Validation", () => { cancellationToken.cancel("Test cancellation"); }, 500); // Cancel after 500ms - const result = await validationPromise; + const result: Result = await validationPromise; - if (ResultUtils.isOk(result)) { - // If validation completed successfully, it finished before cancellation - expect(result.value).to.not.be.undefined; - console.log("Validation completed before cancellation could take effect"); - } else { - // Expect cancellation error - expect(result.error.message).to.contain("cancelled"); - console.log("Validation was successfully cancelled"); - } + ValidateTestUtils.assertValidationSuccessOrCancelled( + result, + () => { + // If validation completed successfully, it finished before cancellation + console.log("Validation completed before cancellation could take effect"); + }, + () => { + // Validation was successfully cancelled + console.log("Validation was successfully cancelled"); + }, + ); }).timeout(30000); // 30 second timeout it("should handle cancellation gracefully at different stages", async () => { - const delays = [100, 250, 500, 1000]; // Different cancellation timings + const delays: number[] = [100, 250, 500, 1000]; // Different cancellation timings for (const delay of delays) { - const cancellationToken = createCancellationToken(); + const cancellationToken: ICancellationToken = createCancellationToken(); + const validationSettings: ValidationSettings = { ...baseValidationSettings, cancellationToken, }; - const validationPromise = validate( + const validationPromise: Promise> = validate( TestUtils.mockDocument(largeSectionDocumentWithDiagnosticsText), analysisSettings, validationSettings, @@ -232,16 +254,20 @@ describe("Async Validation", () => { cancellationToken.cancel("Test cancellation"); }, delay); - const result = await validationPromise; - - if (ResultUtils.isOk(result)) { - // If validation completes despite cancellation, - // it might have finished before cancellation occurred - console.log(`Validation completed before cancellation at ${delay}ms`); - } else { - expect(result.error.message).to.contain("cancelled"); - console.log(`Successfully cancelled validation at ${delay}ms`); - } + // eslint-disable-next-line no-await-in-loop + const result: Result = await validationPromise; + + ValidateTestUtils.assertValidationSuccessOrCancelled( + result, + () => { + // If validation completes despite cancellation, + // it might have finished before cancellation occurred + console.log(`Validation completed before cancellation at ${delay}ms`); + }, + () => { + console.log(`Successfully cancelled validation at ${delay}ms`); + }, + ); } }).timeout(60000); @@ -249,7 +275,8 @@ describe("Async Validation", () => { // Test that cancelled validation is faster than completed validation // First, measure time for completed validation - const startComplete = Date.now(); + const startComplete: number = Date.now(); + const completedValidationSettings: ValidationSettings = { ...baseValidationSettings, cancellationToken: undefined, // No cancellation @@ -260,11 +287,13 @@ describe("Async Validation", () => { analysisSettings, validationSettings: completedValidationSettings, }); - const completeDuration = Date.now() - startComplete; + + const completeDuration: number = Date.now() - startComplete; // Then, measure time for cancelled validation - const startCancelled = Date.now(); - const cancellationToken = createCancellationToken(); + const startCancelled: number = Date.now(); + const cancellationToken: ICancellationToken = createCancellationToken(); + const cancelledValidationSettings: ValidationSettings = { ...baseValidationSettings, cancellationToken, @@ -279,9 +308,10 @@ describe("Async Validation", () => { Math.min(completeDuration * 0.2, 300), // Cancel at 20% of completion time, max 300ms ); - let cancellationDuration = 0; - let wasCancelled = false; - const result = await validate( + let cancellationDuration: number = 0; + let wasCancelled: boolean = false; + + const result: Result = await validate( TestUtils.mockDocument(largeSectionDocumentWithDiagnosticsText), analysisSettings, cancelledValidationSettings, @@ -290,13 +320,16 @@ describe("Async Validation", () => { if (ResultUtils.isOk(result)) { // If we get here, validation completed before cancellation cancellationDuration = Date.now() - startCancelled; + console.log( `Cancellation test: validation completed in ${cancellationDuration}ms before cancellation could occur`, ); } else { cancellationDuration = Date.now() - startCancelled; wasCancelled = true; - expect(result.error.message).to.contain("cancelled"); + + ValidateTestUtils.assertValidationCancelled(result); + console.log(`Cancellation test: validation was cancelled after ${cancellationDuration}ms`); } @@ -312,6 +345,7 @@ describe("Async Validation", () => { completeDuration, `Cancelled validation (${cancellationDuration}ms) should be faster than complete validation (${completeDuration}ms)`, ); + console.log(`✓ Cancellation provided ${completeDuration - cancellationDuration}ms performance benefit`); } else { // If validation completed before cancellation, that's also valuable information @@ -329,7 +363,7 @@ describe("Async Validation", () => { cancellationToken: undefined, }; - const result = await ValidateTestUtils.assertValidate({ + const result: ValidateOk = await ValidateTestUtils.assertValidate({ text: "let x = 1 in x", analysisSettings, validationSettings, @@ -339,13 +373,14 @@ describe("Async Validation", () => { }); it("should not throw when cancellation token is not cancelled", async () => { - const cancellationToken = createCancellationToken(); + const cancellationToken: ICancellationToken = createCancellationToken(); + const validationSettings: ValidationSettings = { ...baseValidationSettings, cancellationToken, }; - const result = await ValidateTestUtils.assertValidate({ + const result: ValidateOk = await ValidateTestUtils.assertValidate({ text: "let x = 1 in x", analysisSettings, validationSettings, @@ -356,7 +391,7 @@ describe("Async Validation", () => { }); it("should throw immediately when cancellation token is already cancelled", async () => { - const cancellationToken = createCancellationToken(); + const cancellationToken: ICancellationToken = createCancellationToken(); cancellationToken.cancel("Pre-cancelled for testing"); // Cancel before starting const validationSettings: ValidationSettings = { @@ -364,17 +399,16 @@ describe("Async Validation", () => { cancellationToken, }; - const result = await validate( + const result: Result = await validate( TestUtils.mockDocument("let x = 1 in x"), analysisSettings, validationSettings, ); - expect(ResultUtils.isError(result), "Expected validation to return error due to pre-cancelled token").to.be - .true; - if (ResultUtils.isError(result)) { - expect(result.error.message).to.contain("cancelled"); - } + ValidateTestUtils.assertValidationCancelled( + result, + "Expected validation to return error due to pre-cancelled token", + ); }); }); @@ -386,8 +420,9 @@ describe("Async Validation", () => { // 2. Separate performance benchmarking tools // 3. CI/CD performance tracking over time - const smallDocument = "let x = 1 in x"; - const mediumDocument = ` + const smallDocument: string = "let x = 1 in x"; + + const mediumDocument: string = ` let func1 = (param1 as text, param2 as number) => param1 & Text.From(param2), func2 = (data as table) => Table.RowCount(data), @@ -398,19 +433,22 @@ describe("Async Validation", () => { `; it("should have reasonable performance for small documents", async () => { - const cancellationToken = createCancellationToken(); + const cancellationToken: ICancellationToken = createCancellationToken(); + const validationSettings: ValidationSettings = { ...baseValidationSettings, cancellationToken, }; - const startTime = Date.now(); - const result = await ValidateTestUtils.assertValidate({ + const startTime: number = Date.now(); + + const result: ValidateOk = await ValidateTestUtils.assertValidate({ text: smallDocument, analysisSettings, validationSettings, }); - const duration = Date.now() - startTime; + + const duration: number = Date.now() - startTime; expect(result).to.not.be.undefined; // Log performance for manual observation (no assertions on timing) @@ -418,19 +456,22 @@ describe("Async Validation", () => { }); it("should have reasonable performance for medium documents", async () => { - const cancellationToken = createCancellationToken(); + const cancellationToken: ICancellationToken = createCancellationToken(); + const validationSettings: ValidationSettings = { ...baseValidationSettings, cancellationToken, }; - const startTime = Date.now(); - const result = await ValidateTestUtils.assertValidate({ + const startTime: number = Date.now(); + + const result: ValidateOk = await ValidateTestUtils.assertValidate({ text: mediumDocument, analysisSettings, validationSettings, }); - const duration = Date.now() - startTime; + + const duration: number = Date.now() - startTime; expect(result).to.not.be.undefined; // Log performance for manual observation (no assertions on timing) @@ -440,7 +481,8 @@ describe("Async Validation", () => { describe("Async validation with different validation settings", () => { it("should respect cancellation with all validation checks enabled", async () => { - const cancellationToken = createCancellationToken(); + const cancellationToken: ICancellationToken = createCancellationToken(); + const validationSettings: ValidationSettings = { ...baseValidationSettings, cancellationToken, @@ -449,7 +491,7 @@ describe("Async Validation", () => { checkForDuplicateIdentifiers: true, }; - const validationPromise = validate( + const validationPromise: Promise> = validate( TestUtils.mockDocument("let x = unknownFunc(1, 2) in x"), analysisSettings, validationSettings, @@ -457,17 +499,22 @@ describe("Async Validation", () => { setTimeout(() => cancellationToken.cancel("Test cancellation"), 100); - const result = await validationPromise; + const result: Result = await validationPromise; - if (ResultUtils.isOk(result)) { - console.log("Validation completed before cancellation"); - } else { - expect(result.error.message).to.contain("cancelled"); - } + ValidateTestUtils.assertValidationSuccessOrCancelled( + result, + () => { + console.log("Validation completed before cancellation"); + }, + () => { + // Validation was cancelled - no additional action needed + }, + ); }); it("should respect cancellation with only specific checks enabled", async () => { - const cancellationToken = createCancellationToken(); + const cancellationToken: ICancellationToken = createCancellationToken(); + const validationSettings: ValidationSettings = { ...baseValidationSettings, cancellationToken, @@ -476,7 +523,7 @@ describe("Async Validation", () => { checkForDuplicateIdentifiers: false, }; - const validationPromise = validate( + const validationPromise: Promise> = validate( TestUtils.mockDocument("let x = unknownVariable in x"), analysisSettings, validationSettings, @@ -484,13 +531,17 @@ describe("Async Validation", () => { setTimeout(() => cancellationToken.cancel("Test cancellation"), 100); - const result = await validationPromise; + const result: Result = await validationPromise; - if (ResultUtils.isOk(result)) { - console.log("Validation completed before cancellation"); - } else { - expect(result.error.message).to.contain("cancelled"); - } + ValidateTestUtils.assertValidationSuccessOrCancelled( + result, + () => { + console.log("Validation completed before cancellation"); + }, + () => { + // Validation was cancelled - no additional action needed + }, + ); }); }); }); From 243a6d9c5c4041756d11807b77ee7c5d4865738a Mon Sep 17 00:00:00 2001 From: Matt Masson Date: Tue, 9 Sep 2025 13:52:35 -0400 Subject: [PATCH 19/42] remove unused --- .../utils/promiseUtils.ts | 51 --------- src/test/utils/promiseUtils.test.ts | 105 +----------------- 2 files changed, 1 insertion(+), 155 deletions(-) diff --git a/src/powerquery-language-services/utils/promiseUtils.ts b/src/powerquery-language-services/utils/promiseUtils.ts index f29d3efe..0e0ba323 100644 --- a/src/powerquery-language-services/utils/promiseUtils.ts +++ b/src/powerquery-language-services/utils/promiseUtils.ts @@ -3,57 +3,6 @@ import { ICancellationToken } from "@microsoft/powerquery-parser"; -/** - * Promise.all with cancellation support. Useful when you want parallel execution - * but need the ability to cancel if needed. - */ -export function promiseAllWithCancellation( - promises: Promise[], - cancellationToken?: ICancellationToken, -): Promise { - if (cancellationToken) { - cancellationToken.throwIfCancelled(); - - return new Promise((resolve: (value: T[]) => void, reject: (reason?: any) => void) => { - let isFinished: boolean = false; - - const cancellationCheck: () => void = (): void => { - if (isFinished) return; - - try { - cancellationToken.throwIfCancelled(); - } catch (error) { - isFinished = true; - reject(error); - - return; - } - - // Check again after a short delay - setTimeout(cancellationCheck, 10); - }; - - // Start cancellation checking - cancellationCheck(); - - // Start the actual Promise.all - // eslint-disable-next-line promise/prefer-await-to-then - Promise.all(promises).then( - (result: T[]) => { - isFinished = true; - resolve(result); - }, - (error: any) => { - isFinished = true; - reject(error); - }, - ); - }); - } - - return Promise.all(promises); -} - /** * Sequential processing with cancellation support - often better than Promise.all * for cancellation scenarios because we can stop between each operation. diff --git a/src/test/utils/promiseUtils.test.ts b/src/test/utils/promiseUtils.test.ts index 174e99bf..57052e7d 100644 --- a/src/test/utils/promiseUtils.test.ts +++ b/src/test/utils/promiseUtils.test.ts @@ -5,10 +5,7 @@ import "mocha"; import { assert, expect } from "chai"; import { ICancellationToken } from "@microsoft/powerquery-parser"; -import { - processSequentiallyWithCancellation, - promiseAllWithCancellation, -} from "../../powerquery-language-services/utils/promiseUtils"; +import { processSequentiallyWithCancellation } from "../../powerquery-language-services/utils/promiseUtils"; function createTestCancellationToken(): ICancellationToken & { cancel: (reason: string) => void } { let isCancelled: boolean = false; @@ -26,107 +23,7 @@ function createTestCancellationToken(): ICancellationToken & { cancel: (reason: }; } -function createDelayedPromise(delayMs: number, value: string): Promise { - return new Promise((resolve: (value: string) => void) => { - setTimeout(() => resolve(value), delayMs); - }); -} - -function createDelayedRejectingPromise(delayMs: number, errorMessage: string): Promise { - return new Promise((_: (value: string) => void, reject: (reason: Error) => void) => { - setTimeout(() => reject(new Error(errorMessage)), delayMs); - }); -} - describe("Promise Utils", () => { - describe("promiseAllWithCancellation", () => { - it("should resolve all promises when no cancellation token is provided", async () => { - const promises: Promise[] = [ - createDelayedPromise(10, "first"), - createDelayedPromise(20, "second"), - createDelayedPromise(15, "third"), - ]; - - const result: string[] = await promiseAllWithCancellation(promises); - expect(result).to.deep.equal(["first", "second", "third"]); - }); - - it("should resolve all promises when cancellation token is not cancelled", async () => { - const cancellationToken: ICancellationToken & { cancel: (reason: string) => void } = - createTestCancellationToken(); - - const promises: Promise[] = [ - createDelayedPromise(10, "first"), - createDelayedPromise(20, "second"), - createDelayedPromise(15, "third"), - ]; - - const result: string[] = await promiseAllWithCancellation(promises, cancellationToken); - expect(result).to.deep.equal(["first", "second", "third"]); - expect(cancellationToken.isCancelled()).to.be.false; - }); - - it("should reject immediately if cancellation token is already cancelled", async () => { - const cancellationToken: ICancellationToken & { cancel: (reason: string) => void } = - createTestCancellationToken(); - - cancellationToken.cancel("Pre-cancelled"); - - const promises: Promise[] = [ - createDelayedPromise(100, "first"), - createDelayedPromise(200, "second"), - ]; - - try { - await promiseAllWithCancellation(promises, cancellationToken); - assert.fail("Expected promise to be rejected due to pre-cancelled token"); - } catch (error: any) { - expect(error.message).to.contain("cancelled"); - } - }); - - it("should reject when cancellation token is cancelled during execution", async () => { - const cancellationToken: ICancellationToken & { cancel: (reason: string) => void } = - createTestCancellationToken(); - - const promises: Promise[] = [ - createDelayedPromise(100, "first"), - createDelayedPromise(200, "second"), - createDelayedPromise(300, "third"), - ]; - - // Cancel after 50ms - setTimeout(() => { - cancellationToken.cancel("Cancelled during execution"); - }, 50); - - try { - await promiseAllWithCancellation(promises, cancellationToken); - assert.fail("Expected promise to be rejected due to cancellation"); - } catch (error: any) { - expect(error.message).to.contain("cancelled"); - } - }); - - it("should handle promise rejections normally when not cancelled", async () => { - const cancellationToken: ICancellationToken & { cancel: (reason: string) => void } = - createTestCancellationToken(); - - const promises: Promise[] = [ - createDelayedPromise(10, "first"), - createDelayedRejectingPromise(20, "Test error"), - createDelayedPromise(15, "third"), - ]; - - try { - await promiseAllWithCancellation(promises, cancellationToken); - assert.fail("Expected promise to be rejected due to promise rejection"); - } catch (error: any) { - expect(error.message).to.equal("Test error"); - } - }); - }); - describe("processSequentiallyWithCancellation", () => { it("should process all items sequentially when no cancellation token is provided", async () => { const items: string[] = ["a", "b", "c"]; From aac60a67711c232e7341d14913f270bfaa3003d2 Mon Sep 17 00:00:00 2001 From: Matt Masson Date: Tue, 9 Sep 2025 14:03:22 -0400 Subject: [PATCH 20/42] remove throwIfCancelled() from synchronous code paths --- .../validate/validateFunctionExpression.ts | 2 -- .../validate/validateInvokeExpression.ts | 4 ---- .../validate/validateUnknownIdentifiers.ts | 2 -- 3 files changed, 8 deletions(-) diff --git a/src/powerquery-language-services/validate/validateFunctionExpression.ts b/src/powerquery-language-services/validate/validateFunctionExpression.ts index 48927098..0b7a35ae 100644 --- a/src/powerquery-language-services/validate/validateFunctionExpression.ts +++ b/src/powerquery-language-services/validate/validateFunctionExpression.ts @@ -82,8 +82,6 @@ function validateNoDuplicateParameter( const parameterNames: Map = new Map(); for (const parameter of NodeIdMapIterator.iterFunctionExpressionParameterNames(nodeIdMapCollection, fnExpression)) { - validationSettings.cancellationToken?.throwIfCancelled(); - const existingNames: Ast.Identifier[] = parameterNames.get(parameter.literal) ?? []; existingNames.push(parameter); parameterNames.set(parameter.literal, existingNames); diff --git a/src/powerquery-language-services/validate/validateInvokeExpression.ts b/src/powerquery-language-services/validate/validateInvokeExpression.ts index c879e1d9..6a99b44f 100644 --- a/src/powerquery-language-services/validate/validateInvokeExpression.ts +++ b/src/powerquery-language-services/validate/validateInvokeExpression.ts @@ -44,8 +44,6 @@ export async function validateInvokeExpression( const inspectionTasks: Promise[] = []; for (const nodeId of invokeExpressionIds) { - validationSettings.cancellationToken?.throwIfCancelled(); - inspectionTasks.push(Inspection.tryInvokeExpression(updatedSettings, nodeIdMapCollection, nodeId, typeCache)); } @@ -63,8 +61,6 @@ export async function validateInvokeExpression( const diagnosticTasks: Promise>[] = []; for (const triedInvokeExpression of inspections) { - validationSettings.cancellationToken?.throwIfCancelled(); - if (ResultUtils.isOk(triedInvokeExpression)) { diagnosticTasks.push( invokeExpressionToDiagnostics(updatedSettings, nodeIdMapCollection, triedInvokeExpression.value), diff --git a/src/powerquery-language-services/validate/validateUnknownIdentifiers.ts b/src/powerquery-language-services/validate/validateUnknownIdentifiers.ts index ed08264a..e7eaebf1 100644 --- a/src/powerquery-language-services/validate/validateUnknownIdentifiers.ts +++ b/src/powerquery-language-services/validate/validateUnknownIdentifiers.ts @@ -130,8 +130,6 @@ function findUnknownIdentifiers( const unknownIdentifiers: UnknownIdentifier[] = []; for (const identifierWithNodeScope of identifiersWithNodeScope) { - validationSettings.cancellationToken?.throwIfCancelled(); - if (ResultUtils.isError(identifierWithNodeScope.triedNodeScope)) { continue; } From f9c1ff019114fba58a51e75913e43e26059f491c Mon Sep 17 00:00:00 2001 From: Matt Masson Date: Tue, 9 Sep 2025 14:09:57 -0400 Subject: [PATCH 21/42] improve logic --- .../validate/validateInvokeExpression.ts | 31 +++++++------------ 1 file changed, 11 insertions(+), 20 deletions(-) diff --git a/src/powerquery-language-services/validate/validateInvokeExpression.ts b/src/powerquery-language-services/validate/validateInvokeExpression.ts index 6a99b44f..e39f5398 100644 --- a/src/powerquery-language-services/validate/validateInvokeExpression.ts +++ b/src/powerquery-language-services/validate/validateInvokeExpression.ts @@ -12,6 +12,7 @@ import { Inspection, PositionUtils } from ".."; import { Localization, LocalizationUtils } from "../localization"; import { DiagnosticErrorCode } from "../diagnosticErrorCode"; import { ILocalizationTemplates } from "../localization/templates"; +import { processSequentiallyWithCancellation } from "../utils/promiseUtils"; import { ValidationSettings } from "./validationSettings"; import { ValidationTraceConstant } from "../trace"; @@ -47,16 +48,11 @@ export async function validateInvokeExpression( inspectionTasks.push(Inspection.tryInvokeExpression(updatedSettings, nodeIdMapCollection, nodeId, typeCache)); } - // Process inspections with cancellation support - const inspections: Inspection.TriedInvokeExpression[] = []; - - for (const task of inspectionTasks) { - validationSettings.cancellationToken?.throwIfCancelled(); - - // eslint-disable-next-line no-await-in-loop - const inspection: Inspection.TriedInvokeExpression = await task; - inspections.push(inspection); - } + const inspections: Inspection.TriedInvokeExpression[] = await processSequentiallyWithCancellation( + inspectionTasks, + (task: Promise) => task, + validationSettings.cancellationToken, + ); const diagnosticTasks: Promise>[] = []; @@ -72,16 +68,11 @@ export async function validateInvokeExpression( } } - // Process diagnostics with cancellation support - const diagnostics: ReadonlyArray[] = []; - - for (const task of diagnosticTasks) { - validationSettings.cancellationToken?.throwIfCancelled(); - - // eslint-disable-next-line no-await-in-loop - const diagnostic: ReadonlyArray = await task; - diagnostics.push(diagnostic); - } + const diagnostics: ReadonlyArray[] = await processSequentiallyWithCancellation( + diagnosticTasks, + (task: Promise>) => task, + validationSettings.cancellationToken, + ); trace.exit(); From f567c836c957c9a450054cdd13afdeeb968677eb Mon Sep 17 00:00:00 2001 From: Matt Masson Date: Tue, 9 Sep 2025 14:16:51 -0400 Subject: [PATCH 22/42] use expensive validation --- src/test/validation/asyncValidation.test.ts | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/src/test/validation/asyncValidation.test.ts b/src/test/validation/asyncValidation.test.ts index f5816dc0..abebd031 100644 --- a/src/test/validation/asyncValidation.test.ts +++ b/src/test/validation/asyncValidation.test.ts @@ -10,13 +10,7 @@ import { CommonError, ICancellationToken, Result, ResultUtils } from "@microsoft import * as ValidateTestUtils from "../testUtils/validationTestUtils"; -import { - AnalysisSettings, - TypeStrategy, - validate, - ValidateOk, - ValidationSettings, -} from "../../powerquery-language-services"; +import { AnalysisSettings, validate, ValidateOk, ValidationSettings } from "../../powerquery-language-services"; import { TestConstants, TestUtils } from ".."; function createCancellationToken(): ICancellationToken { @@ -39,12 +33,8 @@ describe("Async Validation", () => { const analysisSettings: AnalysisSettings = TestConstants.SimpleLibraryAnalysisSettings; const baseValidationSettings: ValidationSettings = { - // TODO: Using Standard library results in an error - "expected a 'Each' scope item with key '_'" - // https://github.com/microsoft/powerquery-language-services/issues/255 - // ...TestConstants.StandardLibraryValidateAllSettings, - ...TestConstants.SimpleLibraryValidateAllSettings, + ...TestConstants.StandardLibraryValidateAllSettings, isWorkspaceCacheAllowed: false, - typeStrategy: TypeStrategy.Extended, }; describe("Large document validation", () => { From 9bfa8d136fcef31224a8e870ff59c18f08975def Mon Sep 17 00:00:00 2001 From: Matt Masson Date: Wed, 10 Sep 2025 09:09:15 -0400 Subject: [PATCH 23/42] instructions will be moved --- .copilot-instructions.md | 58 ---------------------------------------- 1 file changed, 58 deletions(-) delete mode 100644 .copilot-instructions.md diff --git a/.copilot-instructions.md b/.copilot-instructions.md deleted file mode 100644 index c0b33ea6..00000000 --- a/.copilot-instructions.md +++ /dev/null @@ -1,58 +0,0 @@ -# PowerQuery Language Services Local Instructions - -## Project Overview - -This package provides intellisense functionality for the Power Query / M language. It is consumed through: - -- Applications using `monaco-editor` -- VS Code Language Server Protocol extension - -## Current Development Focus - -- Improving async processing of validation code path -- Enhancing cancellation token support for large file validation -- Addressing performance issues with large M documents - -## Key Architecture Points - -### Validation System - -- Main validation logic in `src\powerquery-language-services\validate\validate.ts` -- ValidationSettings includes cancellationToken support -- Current implementation has synchronous bottlenecks preventing effective cancellation -- Performance degrades significantly with large files (30+ seconds for complex documents) - -### Testing Patterns - -- Validation tests located in `src\test\validation\` -- Common utilities in `src\test\testUtils\validationTestUtils.ts` -- Test files for validation in `src\test\files\` -- Follow existing mocha patterns and style conventions - -### Critical Files for Async Validation Work - -- `src\powerquery-language-services\validate\validate.ts` - Main validation logic -- `src\powerquery-language-services\validate\validationSettings.ts` - Settings interface -- `src\powerquery-language-services\analysis\` - Analysis utilities used by validation -- `src\powerquery-language-services\inspection\` - Type inspection system - -## Development Guidelines - -- Maintain backward compatibility -- Ensure cancellation is graceful and doesn't leave inconsistent state -- Follow existing code patterns and style -- Add comprehensive tests for async behavior -- Use .copilot-current-task.md for task-specific tracking - -## Common Issues - -- Large M documents (like Kusto.pq example) take 30+ seconds to validate -- Cancellation tokens not effectively checked during validation processing -- Synchronous operations block proper async flow - -## Testing Strategy - -- Create complex test documents that demonstrate performance issues -- Test cancellation behavior with long-running validation -- Ensure existing validation functionality remains intact -- Measure performance improvements after async enhancements From 18fe93b40032b10aecf13879ed74c17549bede66 Mon Sep 17 00:00:00 2001 From: Matt Masson Date: Wed, 10 Sep 2025 16:47:17 -0400 Subject: [PATCH 24/42] refactor --- .../{utils => }/promiseUtils.ts | 0 src/powerquery-language-services/validate/validate.ts | 10 +++++----- .../validate/validateDuplicateIdentifiers.ts | 5 +++-- .../validate/validateInvokeExpression.ts | 7 ++++--- src/test/utils/promiseUtils.test.ts | 2 +- 5 files changed, 13 insertions(+), 11 deletions(-) rename src/powerquery-language-services/{utils => }/promiseUtils.ts (100%) diff --git a/src/powerquery-language-services/utils/promiseUtils.ts b/src/powerquery-language-services/promiseUtils.ts similarity index 100% rename from src/powerquery-language-services/utils/promiseUtils.ts rename to src/powerquery-language-services/promiseUtils.ts diff --git a/src/powerquery-language-services/validate/validate.ts b/src/powerquery-language-services/validate/validate.ts index c10cb046..9baa7018 100644 --- a/src/powerquery-language-services/validate/validate.ts +++ b/src/powerquery-language-services/validate/validate.ts @@ -1,14 +1,15 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. +import { CommonError, Result, ResultUtils } from "@microsoft/powerquery-parser"; import { NodeIdMap, ParseError, ParseState } from "@microsoft/powerquery-parser/lib/powerquery-parser/parser"; import { Diagnostic } from "vscode-languageserver-types"; import { TextDocument } from "vscode-languageserver-textdocument"; import { Trace } from "@microsoft/powerquery-parser/lib/powerquery-parser/common/trace"; +import * as PromiseUtils from "../promiseUtils"; + import { Analysis, AnalysisSettings, AnalysisUtils } from "../analysis"; -import { CommonError, Result, ResultUtils } from "@microsoft/powerquery-parser"; -import { processSequentiallyWithCancellation } from "../utils/promiseUtils"; import { TypeCache } from "../inspection"; import { validateDuplicateIdentifiers } from "./validateDuplicateIdentifiers"; import { validateFunctionExpression } from "./validateFunctionExpression"; @@ -42,6 +43,7 @@ export function validate( validationSettings.cancellationToken?.throwIfCancelled(); const parseError: ParseError.ParseError | undefined = ResultUtils.assertOk(await analysis.getParseError()); + validationSettings.cancellationToken?.throwIfCancelled(); if (parseState === undefined) { trace.exit(); @@ -49,8 +51,6 @@ export function validate( return undefined; } - validationSettings.cancellationToken?.throwIfCancelled(); - const nodeIdMapCollection: NodeIdMap.Collection = parseState.contextState.nodeIdMapCollection; const typeCache: TypeCache = analysis.getTypeCache(); @@ -90,7 +90,7 @@ export function validate( } // Execute all validation operations sequentially with cancellation support - const allDiagnostics: Diagnostic[][] = await processSequentiallyWithCancellation( + const allDiagnostics: Diagnostic[][] = await PromiseUtils.processSequentiallyWithCancellation( validationOperations, (operation: () => Promise) => operation(), validationSettings.cancellationToken, diff --git a/src/powerquery-language-services/validate/validateDuplicateIdentifiers.ts b/src/powerquery-language-services/validate/validateDuplicateIdentifiers.ts index 1ee87db5..1b2e1381 100644 --- a/src/powerquery-language-services/validate/validateDuplicateIdentifiers.ts +++ b/src/powerquery-language-services/validate/validateDuplicateIdentifiers.ts @@ -14,10 +14,11 @@ import { ICancellationToken } from "@microsoft/powerquery-parser"; import { TextDocument } from "vscode-languageserver-textdocument"; import { Trace } from "@microsoft/powerquery-parser/lib/powerquery-parser/common/trace"; +import * as PromiseUtils from "../promiseUtils"; + import { Localization, LocalizationUtils } from "../localization"; import { DiagnosticErrorCode } from "../diagnosticErrorCode"; import { PositionUtils } from ".."; -import { processSequentiallyWithCancellation } from "../utils/promiseUtils"; import { ValidationSettings } from "./validationSettings"; import { ValidationTraceConstant } from "../trace"; @@ -83,7 +84,7 @@ export async function validateDuplicateIdentifiers( ]; // Process all validation functions sequentially with cancellation support - const diagnosticArrays: ReadonlyArray[] = await processSequentiallyWithCancellation( + const diagnosticArrays: ReadonlyArray[] = await PromiseUtils.processSequentiallyWithCancellation( validationFunctions, (validationFunction: () => Promise>) => validationFunction(), cancellationToken, diff --git a/src/powerquery-language-services/validate/validateInvokeExpression.ts b/src/powerquery-language-services/validate/validateInvokeExpression.ts index e39f5398..17c29e13 100644 --- a/src/powerquery-language-services/validate/validateInvokeExpression.ts +++ b/src/powerquery-language-services/validate/validateInvokeExpression.ts @@ -8,11 +8,12 @@ import { NodeIdMap, NodeIdMapUtils, TXorNode } from "@microsoft/powerquery-parse import { Trace, TraceConstant } from "@microsoft/powerquery-parser/lib/powerquery-parser/common/trace"; import type { Range } from "vscode-languageserver-textdocument"; +import * as PromiseUtils from "../promiseUtils"; + import { Inspection, PositionUtils } from ".."; import { Localization, LocalizationUtils } from "../localization"; import { DiagnosticErrorCode } from "../diagnosticErrorCode"; import { ILocalizationTemplates } from "../localization/templates"; -import { processSequentiallyWithCancellation } from "../utils/promiseUtils"; import { ValidationSettings } from "./validationSettings"; import { ValidationTraceConstant } from "../trace"; @@ -48,7 +49,7 @@ export async function validateInvokeExpression( inspectionTasks.push(Inspection.tryInvokeExpression(updatedSettings, nodeIdMapCollection, nodeId, typeCache)); } - const inspections: Inspection.TriedInvokeExpression[] = await processSequentiallyWithCancellation( + const inspections: Inspection.TriedInvokeExpression[] = await PromiseUtils.processSequentiallyWithCancellation( inspectionTasks, (task: Promise) => task, validationSettings.cancellationToken, @@ -68,7 +69,7 @@ export async function validateInvokeExpression( } } - const diagnostics: ReadonlyArray[] = await processSequentiallyWithCancellation( + const diagnostics: ReadonlyArray[] = await PromiseUtils.processSequentiallyWithCancellation( diagnosticTasks, (task: Promise>) => task, validationSettings.cancellationToken, diff --git a/src/test/utils/promiseUtils.test.ts b/src/test/utils/promiseUtils.test.ts index 57052e7d..c9786ac7 100644 --- a/src/test/utils/promiseUtils.test.ts +++ b/src/test/utils/promiseUtils.test.ts @@ -5,7 +5,7 @@ import "mocha"; import { assert, expect } from "chai"; import { ICancellationToken } from "@microsoft/powerquery-parser"; -import { processSequentiallyWithCancellation } from "../../powerquery-language-services/utils/promiseUtils"; +import { processSequentiallyWithCancellation } from "../../powerquery-language-services/promiseUtils"; function createTestCancellationToken(): ICancellationToken & { cancel: (reason: string) => void } { let isCancelled: boolean = false; From 274c3d05b54fb2d7de696732cd3b5f5cfc1a46b1 Mon Sep 17 00:00:00 2001 From: Matt Masson Date: Thu, 11 Sep 2025 12:24:52 -0400 Subject: [PATCH 25/42] cleanup --- .gitignore | 3 --- 1 file changed, 3 deletions(-) diff --git a/.gitignore b/.gitignore index 2867dabd..4137dd7f 100644 --- a/.gitignore +++ b/.gitignore @@ -48,9 +48,6 @@ Generated\ Files/ TestResult.xml nunit-*.xml -# Task-specific temporary files -.copilot-current-task.md - # Build Results of an ATL Project [Dd]ebugPS/ [Rr]eleasePS/ From 2f6de7eb42138f81e8edfa259bde11a5acae24d0 Mon Sep 17 00:00:00 2001 From: Matt Masson Date: Thu, 11 Sep 2025 12:32:07 -0400 Subject: [PATCH 26/42] don't reuse build window --- .vscode/tasks.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.vscode/tasks.json b/.vscode/tasks.json index a9156837..d74ef8ce 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -11,6 +11,10 @@ "group": { "kind": "build", "isDefault": true + }, + "presentation": { + "showReuseMessage": false, + "close": true } } ] From 40f82654a81c25199c7a0d8aa05c33e0684f4903 Mon Sep 17 00:00:00 2001 From: Matt Masson Date: Thu, 11 Sep 2025 12:47:32 -0400 Subject: [PATCH 27/42] add yield helper --- src/powerquery-language-services/promiseUtils.ts | 9 +++++++++ .../validate/validateDuplicateIdentifiers.ts | 9 ++++----- .../validate/validateFunctionExpression.ts | 6 ++++++ 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/powerquery-language-services/promiseUtils.ts b/src/powerquery-language-services/promiseUtils.ts index 0e0ba323..61d67bb7 100644 --- a/src/powerquery-language-services/promiseUtils.ts +++ b/src/powerquery-language-services/promiseUtils.ts @@ -23,3 +23,12 @@ export async function processSequentiallyWithCancellation( return results; } + +export async function yieldForCancellation(cancellationToken?: ICancellationToken): Promise { + if (cancellationToken) { + await Promise.resolve(); + cancellationToken?.throwIfCancelled(); + } + + return Promise.resolve(); +} diff --git a/src/powerquery-language-services/validate/validateDuplicateIdentifiers.ts b/src/powerquery-language-services/validate/validateDuplicateIdentifiers.ts index 1b2e1381..a61f747d 100644 --- a/src/powerquery-language-services/validate/validateDuplicateIdentifiers.ts +++ b/src/powerquery-language-services/validate/validateDuplicateIdentifiers.ts @@ -257,13 +257,12 @@ async function validateDuplicateIdentifiersForKeyValuePair( const result: Diagnostic[] = []; + // Yield control to allow for cancellation. + // If we need more cancellability, we can move this into the loop and yield every N iterations. + await PromiseUtils.yieldForCancellation(cancellationToken); + for (const collection of nodeIdCollections) { for (const nodeId of collection) { - cancellationToken?.throwIfCancelled(); - // Yield control to allow for cancellation - // eslint-disable-next-line no-await-in-loop - await Promise.resolve(); - const node: TXorNode = NodeIdMapUtils.assertXor(nodeIdMapCollection, nodeId); const duplicateFieldsByKey: Map = new Map(); const knownFieldByKey: Map = new Map(); diff --git a/src/powerquery-language-services/validate/validateFunctionExpression.ts b/src/powerquery-language-services/validate/validateFunctionExpression.ts index 0b7a35ae..a27d5a18 100644 --- a/src/powerquery-language-services/validate/validateFunctionExpression.ts +++ b/src/powerquery-language-services/validate/validateFunctionExpression.ts @@ -12,6 +12,8 @@ import { Ast } from "@microsoft/powerquery-parser/lib/powerquery-parser/language import { Range } from "vscode-languageserver-textdocument"; import { Trace } from "@microsoft/powerquery-parser/lib/powerquery-parser/common/trace"; +import * as PromiseUtils from "../promiseUtils"; + import { Localization, LocalizationUtils } from "../localization"; import { DiagnosticErrorCode } from "../diagnosticErrorCode"; import { ILocalizationTemplates } from "../localization/templates"; @@ -45,6 +47,10 @@ export async function validateFunctionExpression( return []; } + // Yield control to allow for cancellation. + // If we need more cancellability, we can move this into the loop and yield every N iterations. + await PromiseUtils.yieldForCancellation(validationSettings.cancellationToken); + const diagnostics: Diagnostic[][] = []; for (const nodeId of fnExpressionIds) { From 84d53351c10e9c4811e9794d5a5d98c43ec1b461 Mon Sep 17 00:00:00 2001 From: Matt Masson Date: Thu, 11 Sep 2025 12:58:14 -0400 Subject: [PATCH 28/42] remove conditional check --- src/powerquery-language-services/promiseUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/powerquery-language-services/promiseUtils.ts b/src/powerquery-language-services/promiseUtils.ts index 61d67bb7..7ad03739 100644 --- a/src/powerquery-language-services/promiseUtils.ts +++ b/src/powerquery-language-services/promiseUtils.ts @@ -27,7 +27,7 @@ export async function processSequentiallyWithCancellation( export async function yieldForCancellation(cancellationToken?: ICancellationToken): Promise { if (cancellationToken) { await Promise.resolve(); - cancellationToken?.throwIfCancelled(); + cancellationToken.throwIfCancelled(); } return Promise.resolve(); From b8dbb6a9cac71a8449408235555c70ed738814e5 Mon Sep 17 00:00:00 2001 From: Matt Masson Date: Thu, 11 Sep 2025 13:21:49 -0400 Subject: [PATCH 29/42] try this --- .vscode/tasks.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.vscode/tasks.json b/.vscode/tasks.json index d74ef8ce..9ef871d7 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -13,8 +13,7 @@ "isDefault": true }, "presentation": { - "showReuseMessage": false, - "close": true + "clear": true } } ] From b438cc5684bb23830020078a41dde1036d7ea470 Mon Sep 17 00:00:00 2001 From: Matt Masson Date: Thu, 11 Sep 2025 13:57:50 -0400 Subject: [PATCH 30/42] use common code --- src/test/utils/promiseUtils.test.ts | 326 ++++++++++++++++------------ 1 file changed, 187 insertions(+), 139 deletions(-) diff --git a/src/test/utils/promiseUtils.test.ts b/src/test/utils/promiseUtils.test.ts index c9786ac7..368a9631 100644 --- a/src/test/utils/promiseUtils.test.ts +++ b/src/test/utils/promiseUtils.test.ts @@ -7,6 +7,56 @@ import { ICancellationToken } from "@microsoft/powerquery-parser"; import { processSequentiallyWithCancellation } from "../../powerquery-language-services/promiseUtils"; +function isErrorWithMessage(error: unknown): error is { message: string } { + return ( + typeof error === "object" && + error !== null && + "message" in error && + typeof (error as { message: unknown }).message === "string" + ); +} + +/** + * Helper function to test that an async operation throws an error with a specific message + */ +async function expectAsyncError( + operation: () => Promise, + expectedMessage: string, + failureMessage?: string, +): Promise { + try { + await operation(); + assert.fail(failureMessage ?? `Expected operation to throw an error containing "${expectedMessage}"`); + } catch (error: unknown) { + if (isErrorWithMessage(error)) { + expect(error.message).to.contain(expectedMessage); + } else { + assert.fail("Caught error is not an object with a message property"); + } + } +} + +/** + * Helper function to test cancellation behavior with timeout + */ +async function expectCancellationAfterTimeout( + operation: () => Promise, + cancellationToken: ICancellationToken & { cancel: (reason: string) => void }, + timeoutMs: number, + cancellationReason: string = "Test cancellation", + additionalAssertions?: () => void, +): Promise { + // Cancel after the specified timeout + setTimeout(() => { + cancellationToken.cancel(cancellationReason); + }, timeoutMs); + + await expectAsyncError(operation, "cancelled", "Expected processing to be cancelled"); + + // Run any additional assertions + additionalAssertions?.(); +} + function createTestCancellationToken(): ICancellationToken & { cancel: (reason: string) => void } { let isCancelled: boolean = false; @@ -23,32 +73,81 @@ function createTestCancellationToken(): ICancellationToken & { cancel: (reason: }; } +/** + * Interface for test setup return values + */ +interface TestSetup { + readonly items: string[]; + readonly processor: (item: string) => Promise; + readonly cancellationToken: ICancellationToken & { cancel: (reason: string) => void }; + readonly processedItems: string[]; + readonly processorCallCount: () => number; +} + +/** + * Creates common test setup for string processing tests + */ +function testSetup(options?: { + items?: string[]; + delayMs?: number; + trackProcessed?: boolean; + errorOnItem?: string; + countCalls?: boolean; +}): TestSetup { + const items: string[] = options?.items ?? ["a", "b", "c"]; + const delayMs: number = options?.delayMs ?? 0; + const trackProcessed: boolean = options?.trackProcessed ?? false; + const errorOnItem: string | undefined = options?.errorOnItem; + const countCalls: boolean = options?.countCalls ?? false; + + const cancellationToken: ICancellationToken & { cancel: (reason: string) => void } = createTestCancellationToken(); + const processedItems: string[] = []; + let processorCallCount: number = 0; + + const processor: (item: string) => Promise = async (item: string): Promise => { + if (countCalls) { + processorCallCount += 1; + } + + if (trackProcessed) { + processedItems.push(item); + } + + if (errorOnItem && item === errorOnItem) { + throw new Error("Test processor error"); + } + + if (delayMs > 0) { + await new Promise((resolve: (value: unknown) => void) => setTimeout(resolve, delayMs)); + } + + return item.toUpperCase(); + }; + + return { + items, + processor, + cancellationToken, + processedItems, + processorCallCount: (): number => processorCallCount, + }; +} + describe("Promise Utils", () => { describe("processSequentiallyWithCancellation", () => { it("should process all items sequentially when no cancellation token is provided", async () => { - const items: string[] = ["a", "b", "c"]; - - const processor: (item: string) => Promise = async (item: string): Promise => { - await new Promise((resolve: (value: unknown) => void) => setTimeout(resolve, 10)); - - return item.toUpperCase(); - }; + const { items, processor }: TestSetup = testSetup({ + delayMs: 10, + }); const result: string[] = await processSequentiallyWithCancellation(items, processor); expect(result).to.deep.equal(["A", "B", "C"]); }); it("should process all items when cancellation token is not cancelled", async () => { - const cancellationToken: ICancellationToken & { cancel: (reason: string) => void } = - createTestCancellationToken(); - - const items: string[] = ["a", "b", "c"]; - - const processor: (item: string) => Promise = async (item: string): Promise => { - await new Promise((resolve: (value: unknown) => void) => setTimeout(resolve, 10)); - - return item.toUpperCase(); - }; + const { items, processor, cancellationToken }: TestSetup = testSetup({ + delayMs: 10, + }); const result: string[] = await processSequentiallyWithCancellation(items, processor, cancellationToken); expect(result).to.deep.equal(["A", "B", "C"]); @@ -56,155 +155,104 @@ describe("Promise Utils", () => { }); it("should stop processing when cancellation token is cancelled", async () => { - const cancellationToken: ICancellationToken & { cancel: (reason: string) => void } = - createTestCancellationToken(); - - const items: string[] = ["a", "b", "c", "d", "e"]; - const processedItems: string[] = []; - - const processor: (item: string) => Promise = async (item: string): Promise => { - processedItems.push(item); - await new Promise((resolve: (value: unknown) => void) => setTimeout(resolve, 20)); - - return item.toUpperCase(); - }; - - // Cancel after processing should have started - setTimeout(() => { - cancellationToken.cancel("Cancelled during processing"); - }, 35); // Should allow first item and start of second - - try { - await processSequentiallyWithCancellation(items, processor, cancellationToken); - assert.fail("Expected processing to be cancelled"); - } catch (error: any) { - expect(error.message).to.contain("cancelled"); - // Should have processed at least the first item, but not all - expect(processedItems.length).to.be.greaterThan(0); - expect(processedItems.length).to.be.lessThan(items.length); - } + const { items, processor, cancellationToken, processedItems }: TestSetup = testSetup({ + items: ["a", "b", "c", "d", "e"], + delayMs: 20, + trackProcessed: true, + }); + + await expectCancellationAfterTimeout( + () => processSequentiallyWithCancellation(items, processor, cancellationToken), + cancellationToken, + 35, // Should allow first item and start of second + "Cancelled during processing", + () => { + // Should have processed at least the first item, but not all + expect(processedItems.length).to.be.greaterThan(0); + expect(processedItems.length).to.be.lessThan(items.length); + }, + ); }); it("should reject immediately if cancellation token is already cancelled", async () => { - const cancellationToken: ICancellationToken & { cancel: (reason: string) => void } = - createTestCancellationToken(); + const { items, processor, cancellationToken }: TestSetup = testSetup(); cancellationToken.cancel("Pre-cancelled"); - const items: string[] = ["a", "b", "c"]; - - const processor: (item: string) => Promise = (item: string): Promise => - Promise.resolve(item.toUpperCase()); - - try { - await processSequentiallyWithCancellation(items, processor, cancellationToken); - assert.fail("Expected processing to be rejected due to pre-cancelled token"); - } catch (error: any) { - expect(error.message).to.contain("cancelled"); - } + await expectAsyncError( + () => processSequentiallyWithCancellation(items, processor, cancellationToken), + "cancelled", + "Expected processing to be rejected due to pre-cancelled token", + ); }); it("should handle empty arrays", async () => { - const cancellationToken: ICancellationToken & { cancel: (reason: string) => void } = - createTestCancellationToken(); - - const items: string[] = []; - - const processor: (item: string) => Promise = (item: string): Promise => - Promise.resolve(item.toUpperCase()); + const { items, processor, cancellationToken }: TestSetup = testSetup({ + items: [], + }); const result: string[] = await processSequentiallyWithCancellation(items, processor, cancellationToken); expect(result).to.deep.equal([]); }); it("should handle processor errors normally when not cancelled", async () => { - const cancellationToken: ICancellationToken & { cancel: (reason: string) => void } = - createTestCancellationToken(); - - const items: string[] = ["a", "b", "c"]; - - const processor: (item: string) => Promise = (item: string): Promise => { - if (item === "b") { - throw new Error("Test processor error"); - } - - return Promise.resolve(item.toUpperCase()); - }; - - try { - await processSequentiallyWithCancellation(items, processor, cancellationToken); - assert.fail("Expected processing to be rejected due to processor error"); - } catch (error: any) { - expect(error.message).to.equal("Test processor error"); - } + const { items, processor, cancellationToken }: TestSetup = testSetup({ + errorOnItem: "b", + }); + + await expectAsyncError( + () => processSequentiallyWithCancellation(items, processor, cancellationToken), + "Test processor error", + "Expected processing to be rejected due to processor error", + ); }); it("should check cancellation before each item", async () => { - const cancellationToken: ICancellationToken & { cancel: (reason: string) => void } = - createTestCancellationToken(); - - const items: string[] = ["a", "b", "c"]; - let processorCallCount: number = 0; - - const processor: (item: string) => Promise = (item: string): Promise => { - processorCallCount += 1; - - return Promise.resolve(item.toUpperCase()); - }; + const { items, processor, cancellationToken, processorCallCount }: TestSetup = testSetup({ + countCalls: true, + }); // Cancel before any processing cancellationToken.cancel("Pre-cancelled"); - try { - await processSequentiallyWithCancellation(items, processor, cancellationToken); - assert.fail("Expected processing to be cancelled"); - } catch (error: any) { - expect(error.message).to.contain("cancelled"); - expect(processorCallCount).to.equal(0, "Processor should not be called when pre-cancelled"); - } + await expectAsyncError( + () => processSequentiallyWithCancellation(items, processor, cancellationToken), + "cancelled", + "Expected processing to be cancelled", + ); + + expect(processorCallCount()).to.equal(0, "Processor should not be called when pre-cancelled"); }); }); describe("cancellation timing behavior", () => { it("should demonstrate different cancellation points in sequential processing", async () => { - const cancellationToken: ICancellationToken & { cancel: (reason: string) => void } = - createTestCancellationToken(); - - const items: number[] = [1, 2, 3, 4, 5]; - const processedItems: number[] = []; - - const processor: (item: number) => Promise = async (item: number): Promise => { - processedItems.push(item); - // Each item takes 30ms to process - await new Promise((resolve: (value: unknown) => void) => setTimeout(resolve, 30)); - - return item * 2; - }; - - // Cancel after 75ms (should process ~2-3 items) - setTimeout(() => { - cancellationToken.cancel("Timed cancellation"); - }, 75); - - try { - await processSequentiallyWithCancellation(items, processor, cancellationToken); - assert.fail("Expected processing to be cancelled"); - } catch (error: any) { - expect(error.message).to.contain("cancelled"); - - console.log( - `Processed ${processedItems.length} items before cancellation: [${processedItems.join(", ")}]`, - ); - - // Should have processed some but not all items - expect(processedItems.length).to.be.greaterThan(0); - expect(processedItems.length).to.be.lessThan(items.length); - - // Should have processed items in order - for (let i: number = 0; i < processedItems.length; i = i + 1) { - expect(processedItems[i]).to.equal(i + 1); - } - } + const { items, processor, cancellationToken, processedItems }: TestSetup = testSetup({ + items: ["first", "second", "third", "fourth", "fifth"], + delayMs: 30, + trackProcessed: true, + }); + + await expectCancellationAfterTimeout( + () => processSequentiallyWithCancellation(items, processor, cancellationToken), + cancellationToken, + 75, // Cancel after 75ms (should process ~2-3 items) + "Timed cancellation", + () => { + console.log( + `Processed ${processedItems.length} items before cancellation: [${processedItems.join(", ")}]`, + ); + + // Should have processed some but not all items + expect(processedItems.length).to.be.greaterThan(0); + expect(processedItems.length).to.be.lessThan(items.length); + + // Should have processed items in order + for (let i: number = 0; i < processedItems.length; i = i + 1) { + expect(processedItems[i]).to.equal(items[i]); + } + }, + ); }); }); }); From 89559b2930f9f29ec858fd4457fa000395c5013e Mon Sep 17 00:00:00 2001 From: Matt Masson Date: Thu, 11 Sep 2025 14:39:10 -0400 Subject: [PATCH 31/42] add timeout --- src/test/validation/asyncValidation.test.ts | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/test/validation/asyncValidation.test.ts b/src/test/validation/asyncValidation.test.ts index abebd031..e39c045c 100644 --- a/src/test/validation/asyncValidation.test.ts +++ b/src/test/validation/asyncValidation.test.ts @@ -13,6 +13,8 @@ import * as ValidateTestUtils from "../testUtils/validationTestUtils"; import { AnalysisSettings, validate, ValidateOk, ValidationSettings } from "../../powerquery-language-services"; import { TestConstants, TestUtils } from ".."; +const TEST_TIMEOUT_MS: number = 60000; + function createCancellationToken(): ICancellationToken { let isCancelled: boolean = false; @@ -88,9 +90,6 @@ describe("Async Validation", () => { expect(result.diagnostics).to.be.an("array"); expect(result.hasSyntaxError).to.be.false; - // Should have no parse errors since this is the clean file - expect(result.hasSyntaxError).to.be.false; - // Should have significantly fewer diagnostic errors than the diagnostics file expect(result.diagnostics.length).to.be.lessThan( 100, @@ -110,7 +109,7 @@ describe("Async Validation", () => { throw error; // Re-throw to fail the test } - }).timeout(60000); // 60 second timeout + }).timeout(TEST_TIMEOUT_MS); // 60 second timeout it("should validate document with diagnostic errors", async () => { const validationSettings: ValidationSettings = { @@ -150,7 +149,7 @@ describe("Async Validation", () => { throw error; // Re-throw to fail the test } - }).timeout(60000); + }).timeout(TEST_TIMEOUT_MS); it("should handle document with parser errors", async () => { const cancellationToken: ICancellationToken = createCancellationToken(); @@ -185,7 +184,7 @@ describe("Async Validation", () => { console.log( `Document with parser errors validation took ${duration}ms, found ${result.diagnostics.length} diagnostics`, ); - }).timeout(60000); + }).timeout(TEST_TIMEOUT_MS); it("should respect cancellation token when cancelled during validation", async () => { const cancellationToken: ICancellationToken = createCancellationToken(); @@ -259,7 +258,7 @@ describe("Async Validation", () => { }, ); } - }).timeout(60000); + }).timeout(TEST_TIMEOUT_MS); it("should demonstrate performance benefit of cancellation", async () => { // Test that cancelled validation is faster than completed validation @@ -343,7 +342,7 @@ describe("Async Validation", () => { `ℹ Validation completed too quickly (${cancellationDuration}ms) for cancellation to take effect`, ); } - }).timeout(60000); + }).timeout(TEST_TIMEOUT_MS); }); describe("Cancellation token behavior", () => { From fbedbb583d5affbd4a108fedad1d2e097d198521 Mon Sep 17 00:00:00 2001 From: Matt Masson Date: Thu, 11 Sep 2025 15:19:32 -0400 Subject: [PATCH 32/42] move cancellation token to test utils --- src/test/testUtils/asyncTestUtils.ts | 26 +++++++++++++++ src/test/testUtils/index.ts | 1 + src/test/utils/promiseUtils.test.ts | 24 +++----------- src/test/validation/asyncValidation.test.ts | 36 ++++++--------------- 4 files changed, 42 insertions(+), 45 deletions(-) create mode 100644 src/test/testUtils/asyncTestUtils.ts diff --git a/src/test/testUtils/asyncTestUtils.ts b/src/test/testUtils/asyncTestUtils.ts new file mode 100644 index 00000000..95b00bce --- /dev/null +++ b/src/test/testUtils/asyncTestUtils.ts @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import { ICancellationToken } from "@microsoft/powerquery-parser"; + +/** + * Creates a test cancellation token that can be cancelled manually. + * This is useful for testing cancellation behavior in async operations. + * + * @returns A cancellation token with a cancel method for testing + */ +export function createTestCancellationToken(): ICancellationToken { + let isCancelled: boolean = false; + + return { + isCancelled: (): boolean => isCancelled, + throwIfCancelled: (): void => { + if (isCancelled) { + throw new Error("Operation was cancelled"); + } + }, + cancel: (_reason: string): void => { + isCancelled = true; + }, + }; +} diff --git a/src/test/testUtils/index.ts b/src/test/testUtils/index.ts index 8d0c80f8..b054fe3a 100644 --- a/src/test/testUtils/index.ts +++ b/src/test/testUtils/index.ts @@ -4,6 +4,7 @@ export * from "./abridgedTestUtils"; export * from "./analysisTestUtils"; export * from "./assertEqualTestUtils"; +export * from "./asyncTestUtils"; export * from "./autocompleteTestUtils"; export * from "./inspectionTestUtils"; export * from "./parseTestUtils"; diff --git a/src/test/utils/promiseUtils.test.ts b/src/test/utils/promiseUtils.test.ts index 368a9631..b932b209 100644 --- a/src/test/utils/promiseUtils.test.ts +++ b/src/test/utils/promiseUtils.test.ts @@ -5,6 +5,7 @@ import "mocha"; import { assert, expect } from "chai"; import { ICancellationToken } from "@microsoft/powerquery-parser"; +import * as TestUtils from "../testUtils"; import { processSequentiallyWithCancellation } from "../../powerquery-language-services/promiseUtils"; function isErrorWithMessage(error: unknown): error is { message: string } { @@ -41,7 +42,7 @@ async function expectAsyncError( */ async function expectCancellationAfterTimeout( operation: () => Promise, - cancellationToken: ICancellationToken & { cancel: (reason: string) => void }, + cancellationToken: ICancellationToken, timeoutMs: number, cancellationReason: string = "Test cancellation", additionalAssertions?: () => void, @@ -57,29 +58,13 @@ async function expectCancellationAfterTimeout( additionalAssertions?.(); } -function createTestCancellationToken(): ICancellationToken & { cancel: (reason: string) => void } { - let isCancelled: boolean = false; - - return { - isCancelled: (): boolean => isCancelled, - throwIfCancelled: (): void => { - if (isCancelled) { - throw new Error("Operation was cancelled"); - } - }, - cancel: (_reason: string): void => { - isCancelled = true; - }, - }; -} - /** * Interface for test setup return values */ interface TestSetup { readonly items: string[]; readonly processor: (item: string) => Promise; - readonly cancellationToken: ICancellationToken & { cancel: (reason: string) => void }; + readonly cancellationToken: ICancellationToken; readonly processedItems: string[]; readonly processorCallCount: () => number; } @@ -100,7 +85,8 @@ function testSetup(options?: { const errorOnItem: string | undefined = options?.errorOnItem; const countCalls: boolean = options?.countCalls ?? false; - const cancellationToken: ICancellationToken & { cancel: (reason: string) => void } = createTestCancellationToken(); + const cancellationToken: ICancellationToken = TestUtils.createTestCancellationToken(); + const processedItems: string[] = []; let processorCallCount: number = 0; diff --git a/src/test/validation/asyncValidation.test.ts b/src/test/validation/asyncValidation.test.ts index e39c045c..2d1339b4 100644 --- a/src/test/validation/asyncValidation.test.ts +++ b/src/test/validation/asyncValidation.test.ts @@ -15,22 +15,6 @@ import { TestConstants, TestUtils } from ".."; const TEST_TIMEOUT_MS: number = 60000; -function createCancellationToken(): ICancellationToken { - let isCancelled: boolean = false; - - return { - isCancelled: (): boolean => isCancelled, - throwIfCancelled: (): void => { - if (isCancelled) { - throw new Error("Operation was cancelled"); - } - }, - cancel: (_reason: string): void => { - isCancelled = true; - }, - }; -} - describe("Async Validation", () => { const analysisSettings: AnalysisSettings = TestConstants.SimpleLibraryAnalysisSettings; @@ -152,7 +136,7 @@ describe("Async Validation", () => { }).timeout(TEST_TIMEOUT_MS); it("should handle document with parser errors", async () => { - const cancellationToken: ICancellationToken = createCancellationToken(); + const cancellationToken: ICancellationToken = TestUtils.createTestCancellationToken(); const validationSettings: ValidationSettings = { ...baseValidationSettings, @@ -187,7 +171,7 @@ describe("Async Validation", () => { }).timeout(TEST_TIMEOUT_MS); it("should respect cancellation token when cancelled during validation", async () => { - const cancellationToken: ICancellationToken = createCancellationToken(); + const cancellationToken: ICancellationToken = TestUtils.createTestCancellationToken(); const validationSettings: ValidationSettings = { ...baseValidationSettings, @@ -225,7 +209,7 @@ describe("Async Validation", () => { const delays: number[] = [100, 250, 500, 1000]; // Different cancellation timings for (const delay of delays) { - const cancellationToken: ICancellationToken = createCancellationToken(); + const cancellationToken: ICancellationToken = TestUtils.createTestCancellationToken(); const validationSettings: ValidationSettings = { ...baseValidationSettings, @@ -281,7 +265,7 @@ describe("Async Validation", () => { // Then, measure time for cancelled validation const startCancelled: number = Date.now(); - const cancellationToken: ICancellationToken = createCancellationToken(); + const cancellationToken: ICancellationToken = TestUtils.createTestCancellationToken(); const cancelledValidationSettings: ValidationSettings = { ...baseValidationSettings, @@ -362,7 +346,7 @@ describe("Async Validation", () => { }); it("should not throw when cancellation token is not cancelled", async () => { - const cancellationToken: ICancellationToken = createCancellationToken(); + const cancellationToken: ICancellationToken = TestUtils.createTestCancellationToken(); const validationSettings: ValidationSettings = { ...baseValidationSettings, @@ -380,7 +364,7 @@ describe("Async Validation", () => { }); it("should throw immediately when cancellation token is already cancelled", async () => { - const cancellationToken: ICancellationToken = createCancellationToken(); + const cancellationToken: ICancellationToken = TestUtils.createTestCancellationToken(); cancellationToken.cancel("Pre-cancelled for testing"); // Cancel before starting const validationSettings: ValidationSettings = { @@ -422,7 +406,7 @@ describe("Async Validation", () => { `; it("should have reasonable performance for small documents", async () => { - const cancellationToken: ICancellationToken = createCancellationToken(); + const cancellationToken: ICancellationToken = TestUtils.createTestCancellationToken(); const validationSettings: ValidationSettings = { ...baseValidationSettings, @@ -445,7 +429,7 @@ describe("Async Validation", () => { }); it("should have reasonable performance for medium documents", async () => { - const cancellationToken: ICancellationToken = createCancellationToken(); + const cancellationToken: ICancellationToken = TestUtils.createTestCancellationToken(); const validationSettings: ValidationSettings = { ...baseValidationSettings, @@ -470,7 +454,7 @@ describe("Async Validation", () => { describe("Async validation with different validation settings", () => { it("should respect cancellation with all validation checks enabled", async () => { - const cancellationToken: ICancellationToken = createCancellationToken(); + const cancellationToken: ICancellationToken = TestUtils.createTestCancellationToken(); const validationSettings: ValidationSettings = { ...baseValidationSettings, @@ -502,7 +486,7 @@ describe("Async Validation", () => { }); it("should respect cancellation with only specific checks enabled", async () => { - const cancellationToken: ICancellationToken = createCancellationToken(); + const cancellationToken: ICancellationToken = TestUtils.createTestCancellationToken(); const validationSettings: ValidationSettings = { ...baseValidationSettings, From 14d2c2463b7528150e25312e530478740befaa2e Mon Sep 17 00:00:00 2001 From: Matt Masson Date: Thu, 11 Sep 2025 17:01:15 -0400 Subject: [PATCH 33/42] redo tests --- src/test/files/LargeSectionDocument.pq | 94 ------ .../LargeSectionDocument_WithParserError.pq | 166 ----------- src/test/testUtils/asyncTestUtils.ts | 41 ++- src/test/validation/asyncValidation.test.ts | 274 ++++++------------ 4 files changed, 119 insertions(+), 456 deletions(-) delete mode 100644 src/test/files/LargeSectionDocument.pq delete mode 100644 src/test/files/LargeSectionDocument_WithParserError.pq diff --git a/src/test/files/LargeSectionDocument.pq b/src/test/files/LargeSectionDocument.pq deleted file mode 100644 index 13c01076..00000000 --- a/src/test/files/LargeSectionDocument.pq +++ /dev/null @@ -1,94 +0,0 @@ -[Version = "1.0.0"] -section LargeSectionDocument; - -// This is a complex M section document designed to test validation performance -// It contains many functions and expressions - but NO parsing or diagnostic errors - -// Constants and basic values -BaseUrl = "https://api.example.com/v1/"; -MaxRetries = 5; -DefaultTimeout = 30; - -// Complex mathematical functions -MathematicalOperations = [ - // Calculate moving average - CalculateMovingAverage = (values as list, windowSize as number) as list => - let - ValuesCount = List.Count(values), - MovingAverages = List.Transform( - List.Numbers(0, ValuesCount - windowSize + 1), - (index) => - let - WindowValues = List.Range(values, index, windowSize), - Average = List.Average(WindowValues) - in - Average - ) - in - MovingAverages, - - // Calculate standard deviation - CalculateStandardDeviation = (values as list) as number => - let - Count = List.Count(values), - Mean = List.Average(values), - SquaredDifferences = List.Transform(values, each Number.Power(_ - Mean, 2)), - Variance = List.Sum(SquaredDifferences) / (Count - 1), - StandardDeviation = Number.Sqrt(Variance) - in - StandardDeviation -]; - -// Main data processing functions -shared LargeSectionDocument.CalculateStatistics = (data as list) as record => - [ - MovingAverage = MathematicalOperations[CalculateMovingAverage](data, 5), - StandardDeviation = MathematicalOperations[CalculateStandardDeviation](data), - Mean = List.Average(data), - Median = List.Median(data), - Min = List.Min(data), - Max = List.Max(data) - ]; - -// Additional complex functions to increase processing time -shared ComplexFunction1 = (param1 as text, param2 as number, param3 as logical) as table => - let - Source = #table({"Column1", "Column2", "Column3"}, {{param1, param2, param3}}), - Step1 = Table.AddColumn(Source, "NewCol", each Text.Length([Column1]) + [Column2]), - Step2 = Table.TransformColumns(Step1, {{"NewCol", each _ * 2}}), - Step3 = Table.SelectRows(Step2, each [Column3] = param3), - Result = Table.Sort(Step3, {{"NewCol", Order.Descending}}) - in - Result; - -shared ComplexFunction2 = (data as table, config as record) as table => - let - ColumnNames = Table.ColumnNames(data), - FirstColumn = Table.Column(data, ColumnNames{0}), - SecondColumn = if List.Count(ColumnNames) > 1 then Table.Column(data, ColumnNames{1}) else {}, - Processed1 = List.Transform(FirstColumn, each Text.From(_)), - Processed2 = List.Transform(SecondColumn, each Number.From(_)), - CombinedTable = #table({"Proc1", "Proc2"}, List.Zip({Processed1, Processed2})), - Aggregated = Table.Group(CombinedTable, {"Proc1"}, {{"Proc2Sum", each List.Sum([Proc2]), type number}}), - Final = Table.AddColumn(Aggregated, "Calculated", each [Proc1] & " = " & Text.From([Proc2Sum])) - in - Final; - -shared ComplexFunction3 = (inputList as list, operations as list) as list => - let - ProcessOperation = (current as list, operation as record) as list => - let - OperationType = Record.Field(operation, "Type"), - Parameter = Record.Field(operation, "Parameter") - in - if OperationType = "transform" then - List.Transform(current, each _ + Parameter) - else if OperationType = "filter" then - List.Select(current, each _ > Parameter) - else if OperationType = "sort" then - List.Sort(current) - else - current, - Result = List.Accumulate(operations, inputList, ProcessOperation) - in - Result; diff --git a/src/test/files/LargeSectionDocument_WithParserError.pq b/src/test/files/LargeSectionDocument_WithParserError.pq deleted file mode 100644 index cf63c726..00000000 --- a/src/test/files/LargeSectionDocument_WithParserError.pq +++ /dev/null @@ -1,166 +0,0 @@ -[Version = "1.0.0"] -section LargeSectionDocument_WithParserError; - -// This is a complex M section document designed to test validation performance -// It contains parsing errors AND diagnostic errors - -// Constants and basic values -BaseUrl = "https://api.example.com/v1/"; -MaxRetries = 5; -DefaultTimeout = 30; - -// Complex functions with parsing errors -shared ComplexFunction1 = (param1 as text, param2 as number, param3 as logical) as table => - let - Source = UnknownTableFunction(param1, param2), - Step1 = Table.AddColumn(Source, "NewCol", each UnknownScalarFunction([Column1], [Column2])), - // Parsing error: missing closing parenthesis - Step2 = Table.TransformColumns(Step1, {{"NewCol", each UnknownTransformFunction(_)}), - Step3 = Table.SelectRows(Step2, each UnknownFilterFunction([NewCol], param3)), - Result = Table.Sort(Step3, {{"NewCol", UnknownOrderFunction}}) - in - Result; - -shared ComplexFunction2 = (data as table, config as record) as table => - let - Column1 = Table.Column(data, UnknownColumnName1), - Column2 = Table.Column(data, UnknownColumnName2), - Processed1 = List.Transform(Column1, each UnknownProcessor1(_)), - Processed2 = List.Transform(Column2, each UnknownProcessor2(_)), - CombinedTable = #table({"Proc1", "Proc2"}, List.Zip({Processed1, Processed2})), - // Parsing error: incorrect list syntax - Aggregated = Table.Group(CombinedTable, {"Proc1"}, [{"Proc2Sum", each List.Sum([Proc2]), type number}]), - Final = Table.AddColumn(Aggregated, "Calculated", each UnknownCalculation([Proc1], [Proc2Sum])) - in - Final; - -shared ComplexFunction3 = (inputList as list, operations as list) as list => - let - ProcessOperation = (current as list, operation as record) as list => - let - OperationType = Record.Field(operation, "Type"), - Parameter = Record.Field(operation, "Parameter") - in - if OperationType = "unknown1" then - List.Transform(current, each UnknownFunction1(_, Parameter)) - else if OperationType = "unknown2" then - List.Select(current, each UnknownFunction2(_, Parameter)) - else if OperationType = "unknown3" then - // Parsing error: malformed function call - List.Sort(current each UnknownFunction3(_, Parameter)) - else - current, - Result = List.Accumulate(operations, inputList, ProcessOperation) - in - Result; - -shared ComplexFunction4 = (textData as text, patterns as list) as record => - let - ExtractPattern = (text as text, pattern as record) as any => - let - PatternType = Record.Field(pattern, "Type"), - PatternValue = Record.Field(pattern, "Value") - in - if PatternType = "unknown" then - UnknownExtractor(textData, PatternValue) - else if PatternType = "custom" then - CustomUnknownExtractor(textData, PatternValue) - else - null, - Results = List.Transform(patterns, each ExtractPattern(textData, _)), - Summary = [ - TotalMatches = List.Count(List.Select(Results, each _ <> null)), - FirstMatch = UnknownFirstFunction(Results), - LastMatch = UnknownLastFunction(Results), - // Parsing error: incorrect record syntax - ProcessedResults = List.Transform(Results, each UnknownResultProcessor(_)) - - in - Summary; - -shared ComplexFunction5 = (numericData as list, analysisType as text) as record => - let - BasicStats = [ - Sum = List.Sum(numericData), - Average = List.Average(numericData), - Count = List.Count(numericData) - ], - AdvancedStats = - if analysisType = "advanced" then - [ - StandardDev = UnknownStandardDeviation(numericData), - Variance = UnknownVariance(numericData), - Median = UnknownMedian(numericData), - Quartiles = UnknownQuartiles(numericData), - Outliers = UnknownOutlierDetection(numericData) - ] - else - [], - CombinedStats = BasicStats & AdvancedStats, - ProcessedStats = Record.TransformFields( - CombinedStats, - { - {"Sum", each UnknownSumProcessor(_)}, - {"Average", each UnknownAverageProcessor(_)}, - // Parsing error: missing closing braces - {"Count", each UnknownCountProcessor(_) - } - ) - in - ProcessedStats; - -// Additional functions with both parsing and diagnostic errors -shared MachineLearningPipeline = (trainingData as table, features as list, target as text) as record => - let - // Data preprocessing with unknown functions - CleanedData = UnknownDataCleaner(trainingData), - FeatureMatrix = UnknownFeatureExtractor(CleanedData, features), - NormalizedFeatures = UnknownNormalizer(FeatureMatrix), - EncodedFeatures = UnknownCategoricalEncoder(NormalizedFeatures), - - // Feature selection with unknown functions - SelectedFeatures = UnknownFeatureSelector(EncodedFeatures, target), - ImportanceScores = UnknownFeatureImportance(SelectedFeatures, target), - - // Model training with unknown functions - TrainTestSplit = UnknownTrainTestSplitter(SelectedFeatures, 0.8), - TrainingSet = TrainTestSplit[Training], - TestingSet = TrainTestSplit[Testing], - - // Parsing error: incorrect list syntax - Models = [ - LinearRegression = UnknownLinearRegression(TrainingSet, target), - RandomForest = UnknownRandomForest(TrainingSet, target) - XGBoost = UnknownXGBoost(TrainingSet, target), - NeuralNetwork = UnknownNeuralNetwork(TrainingSet, target), - SVM = UnknownSVM(TrainingSet, target) - ], - - // Model evaluation with unknown functions - Predictions = Record.TransformFields( - Models, - { - "LinearRegression", each UnknownPredict(_, TestingSet), - "RandomForest", each UnknownPredict(_, TestingSet), - "XGBoost", each UnknownPredict(_, TestingSet), - "NeuralNetwork", each UnknownPredict(_, TestingSet), - "SVM", each UnknownPredict(_, TestingSet) - } - ), - - BestModel = UnknownModelSelector(Metrics), - Hyperparameters = UnknownHyperparameterTuner(BestModel, TrainingSet), - FinalModel = UnknownModelTrainer(BestModel, Hyperparameters, TrainingSet) - in - [ - Data = [ - Original = trainingData, - Cleaned = CleanedData, - Features = SelectedFeatures, - Training = TrainingSet, - Testing = TestingSet - ], - Models = Models, - FinalModel = FinalModel, - FeatureImportance = ImportanceScores - ]; diff --git a/src/test/testUtils/asyncTestUtils.ts b/src/test/testUtils/asyncTestUtils.ts index 95b00bce..f2267400 100644 --- a/src/test/testUtils/asyncTestUtils.ts +++ b/src/test/testUtils/asyncTestUtils.ts @@ -3,18 +3,53 @@ import { ICancellationToken } from "@microsoft/powerquery-parser"; +export interface TestCancellationTokenOptions { + /** + * Number of calls to isCancelled/throwIfCancelled before triggering cancellation. + * If not specified, token will only be cancelled via manual cancel() call. + */ + cancelAfterCount?: number; + + /** + * Delay in milliseconds before setting cancelled state (via setTimeout). + * Defaults to 0ms to simulate async cancellation behavior. + */ + asyncDelayMs?: number; +} + /** - * Creates a test cancellation token that can be cancelled manually. + * Creates a test cancellation token that can be cancelled manually or after a specified number of calls. * This is useful for testing cancellation behavior in async operations. * + * @param options Configuration for the cancellation token behavior * @returns A cancellation token with a cancel method for testing */ -export function createTestCancellationToken(): ICancellationToken { +export function createTestCancellationToken(options?: TestCancellationTokenOptions): ICancellationToken { let isCancelled: boolean = false; + let callCount: number = 0; + const cancelAfterCount: number | undefined = options?.cancelAfterCount; + const asyncDelayMs: number = options?.asyncDelayMs ?? 0; + + const checkAndTriggerCancellation: () => void = (): void => { + callCount += 1; + + if (cancelAfterCount !== undefined && callCount >= cancelAfterCount && !isCancelled) { + // Use setTimeout to simulate async cancellation behavior + setTimeout(() => { + isCancelled = true; + }, asyncDelayMs); + } + }; return { - isCancelled: (): boolean => isCancelled, + isCancelled: (): boolean => { + checkAndTriggerCancellation(); + + return isCancelled; + }, throwIfCancelled: (): void => { + checkAndTriggerCancellation(); + if (isCancelled) { throw new Error("Operation was cancelled"); } diff --git a/src/test/validation/asyncValidation.test.ts b/src/test/validation/asyncValidation.test.ts index 2d1339b4..7f43fcff 100644 --- a/src/test/validation/asyncValidation.test.ts +++ b/src/test/validation/asyncValidation.test.ts @@ -24,14 +24,10 @@ describe("Async Validation", () => { }; describe("Large document validation", () => { - let largeSectionDocumentText: string; let largeSectionDocumentWithDiagnosticsText: string; - let largeSectionDocumentWithParserErrorText: string; before(() => { - // Load the large section documents - const cleanFilePath: string = path.join(__dirname, "..", "files", "LargeSectionDocument.pq"); - + // Load the large section document with diagnostics const diagnosticsFilePath: string = path.join( __dirname, "..", @@ -39,206 +35,138 @@ describe("Async Validation", () => { "LargeSectionDocument_WithDiagnostics.pq", ); - const parserErrorFilePath: string = path.join( - __dirname, - "..", - "files", - "LargeSectionDocument_WithParserError.pq", - ); - - largeSectionDocumentText = fs.readFileSync(cleanFilePath, "utf8"); largeSectionDocumentWithDiagnosticsText = fs.readFileSync(diagnosticsFilePath, "utf8"); - largeSectionDocumentWithParserErrorText = fs.readFileSync(parserErrorFilePath, "utf8"); }); - it("should validate clean large document successfully without cancellation", async () => { + it("should validate document with diagnostic errors without cancellation", async () => { const validationSettings: ValidationSettings = { ...baseValidationSettings, cancellationToken: undefined, // No cancellation for this test }; - const startTime: number = Date.now(); - - try { - const result: ValidateOk = await ValidateTestUtils.assertValidate({ - text: largeSectionDocumentText, - analysisSettings, - validationSettings, - }); - - const endTime: number = Date.now(); - const duration: number = endTime - startTime; - - // Validation should complete successfully - expect(result).to.not.be.undefined; - expect(result.diagnostics).to.be.an("array"); - expect(result.hasSyntaxError).to.be.false; - - // Should have significantly fewer diagnostic errors than the diagnostics file - expect(result.diagnostics.length).to.be.lessThan( - 100, - "Clean document should have fewer diagnostic errors", - ); + const result: ValidateOk = await ValidateTestUtils.assertValidate({ + text: largeSectionDocumentWithDiagnosticsText, + analysisSettings, + validationSettings, + }); - // Log performance for manual observation (no assertions on timing) - console.log( - `Clean large document validation took ${duration}ms with ${result.diagnostics.length} diagnostics`, - ); - } catch (error: unknown) { - const endTime: number = Date.now(); - const duration: number = endTime - startTime; + // Validation should complete successfully + expect(result).to.not.be.undefined; + expect(result.diagnostics).to.be.an("array"); + expect(result.hasSyntaxError).to.be.false; - console.error(`❌ Validation failed after ${duration}ms`); - console.error(`Error: ${error instanceof Error ? error.message : String(error)}`); + // Should have diagnostic errors due to unknown identifiers + expect(result.diagnostics.length).to.be.greaterThan(0, "Document with diagnostics should have errors"); + }).timeout(TEST_TIMEOUT_MS); - throw error; // Re-throw to fail the test - } - }).timeout(TEST_TIMEOUT_MS); // 60 second timeout + it("should respect cancellation token when cancelled after few cancellation checks", async () => { + const cancellationToken: ICancellationToken = TestUtils.createTestCancellationToken({ + cancelAfterCount: 10, // Cancel after 10 calls to isCancelled/throwIfCancelled + }); - it("should validate document with diagnostic errors", async () => { const validationSettings: ValidationSettings = { ...baseValidationSettings, - cancellationToken: undefined, // No cancellation for this test + cancellationToken, }; - const startTime: number = Date.now(); - - try { - const result: ValidateOk = await ValidateTestUtils.assertValidate({ - text: largeSectionDocumentWithDiagnosticsText, - analysisSettings, - validationSettings, - }); - - const endTime: number = Date.now(); - const duration: number = endTime - startTime; - - // Validation should complete successfully - expect(result).to.not.be.undefined; - expect(result.diagnostics).to.be.an("array"); - expect(result.hasSyntaxError).to.be.false; - - // Should have diagnostic errors due to unknown identifiers - expect(result.diagnostics.length).to.be.greaterThan(0, "Document with diagnostics should have errors"); - - console.log( - `Document with diagnostics validation took ${duration}ms, found ${result.diagnostics.length} diagnostics`, - ); - } catch (error: unknown) { - const endTime: number = Date.now(); - const duration: number = endTime - startTime; - - console.error(`❌ Validation failed after ${duration}ms`); - console.error(`Error: ${error instanceof Error ? error.message : String(error)}`); + const result: Result = await validate( + TestUtils.mockDocument(largeSectionDocumentWithDiagnosticsText), + analysisSettings, + validationSettings, + ); - throw error; // Re-throw to fail the test - } + ValidateTestUtils.assertValidationSuccessOrCancelled( + result, + () => { + // If validation completed successfully, it finished before reaching cancellation threshold + }, + () => { + // Validation was successfully cancelled + }, + ); }).timeout(TEST_TIMEOUT_MS); - it("should handle document with parser errors", async () => { - const cancellationToken: ICancellationToken = TestUtils.createTestCancellationToken(); + it("should respect cancellation token when cancelled after moderate cancellation checks", async () => { + const cancellationToken: ICancellationToken = TestUtils.createTestCancellationToken({ + cancelAfterCount: 100, // Cancel after 100 calls to isCancelled/throwIfCancelled + }); const validationSettings: ValidationSettings = { ...baseValidationSettings, cancellationToken, }; - const startTime: number = Date.now(); - - const result: ValidateOk = await ValidateTestUtils.assertValidate({ - text: largeSectionDocumentWithParserErrorText, + const result: Result = await validate( + TestUtils.mockDocument(largeSectionDocumentWithDiagnosticsText), analysisSettings, validationSettings, - }); - - const endTime: number = Date.now(); - const duration: number = endTime - startTime; - - // Validation should complete but have syntax errors - expect(result).to.not.be.undefined; - expect(result.diagnostics).to.be.an("array"); - expect(result.hasSyntaxError).to.be.true; - - // Should have diagnostic errors due to parser errors - expect(result.diagnostics.length).to.be.greaterThan( - 0, - "Document with parser errors should have diagnostics", ); - console.log( - `Document with parser errors validation took ${duration}ms, found ${result.diagnostics.length} diagnostics`, + ValidateTestUtils.assertValidationSuccessOrCancelled( + result, + () => { + // Validation completed before reaching cancellation threshold + }, + () => { + // Validation was successfully cancelled + }, ); }).timeout(TEST_TIMEOUT_MS); - it("should respect cancellation token when cancelled during validation", async () => { - const cancellationToken: ICancellationToken = TestUtils.createTestCancellationToken(); + it("should respect cancellation token when cancelled after many cancellation checks", async () => { + const cancellationToken: ICancellationToken = TestUtils.createTestCancellationToken({ + cancelAfterCount: 1000, // Cancel after 1000 calls to isCancelled/throwIfCancelled + }); const validationSettings: ValidationSettings = { ...baseValidationSettings, cancellationToken, }; - // Start validation with the diagnostics file (takes longer) - const validationPromise: Promise> = validate( + const result: Result = await validate( TestUtils.mockDocument(largeSectionDocumentWithDiagnosticsText), analysisSettings, validationSettings, ); - // Cancel after a short delay - setTimeout(() => { - cancellationToken.cancel("Test cancellation"); - }, 500); // Cancel after 500ms - - const result: Result = await validationPromise; - ValidateTestUtils.assertValidationSuccessOrCancelled( result, () => { - // If validation completed successfully, it finished before cancellation - console.log("Validation completed before cancellation could take effect"); + // Validation completed before reaching cancellation threshold }, () => { // Validation was successfully cancelled - console.log("Validation was successfully cancelled"); }, ); - }).timeout(30000); // 30 second timeout + }).timeout(TEST_TIMEOUT_MS); - it("should handle cancellation gracefully at different stages", async () => { - const delays: number[] = [100, 250, 500, 1000]; // Different cancellation timings + it("should handle cancellation gracefully at different thresholds", async () => { + const thresholds: number[] = [5, 25, 50, 200]; // Different cancellation thresholds - for (const delay of delays) { - const cancellationToken: ICancellationToken = TestUtils.createTestCancellationToken(); + for (const threshold of thresholds) { + const cancellationToken: ICancellationToken = TestUtils.createTestCancellationToken({ + cancelAfterCount: threshold, + }); const validationSettings: ValidationSettings = { ...baseValidationSettings, cancellationToken, }; - const validationPromise: Promise> = validate( + // eslint-disable-next-line no-await-in-loop + const result: Result = await validate( TestUtils.mockDocument(largeSectionDocumentWithDiagnosticsText), analysisSettings, validationSettings, ); - // Cancel after the specified delay - setTimeout(() => { - cancellationToken.cancel("Test cancellation"); - }, delay); - - // eslint-disable-next-line no-await-in-loop - const result: Result = await validationPromise; - ValidateTestUtils.assertValidationSuccessOrCancelled( result, () => { - // If validation completes despite cancellation, - // it might have finished before cancellation occurred - console.log(`Validation completed before cancellation at ${delay}ms`); + // Validation completed before reaching cancellation threshold }, () => { - console.log(`Successfully cancelled validation at ${delay}ms`); + // Validation was successfully cancelled }, ); } @@ -263,24 +191,18 @@ describe("Async Validation", () => { const completeDuration: number = Date.now() - startComplete; - // Then, measure time for cancelled validation + // Then, measure time for cancelled validation with early threshold const startCancelled: number = Date.now(); - const cancellationToken: ICancellationToken = TestUtils.createTestCancellationToken(); + + const cancellationToken: ICancellationToken = TestUtils.createTestCancellationToken({ + cancelAfterCount: 50, // Cancel after 50 calls + }); const cancelledValidationSettings: ValidationSettings = { ...baseValidationSettings, cancellationToken, }; - // Cancel after enough time to start heavy work but before completion - // Use the same timing as the successful cancellation test - setTimeout( - () => { - cancellationToken.cancel("Performance test cancellation"); - }, - Math.min(completeDuration * 0.2, 300), // Cancel at 20% of completion time, max 300ms - ); - let cancellationDuration: number = 0; let wasCancelled: boolean = false; @@ -293,24 +215,13 @@ describe("Async Validation", () => { if (ResultUtils.isOk(result)) { // If we get here, validation completed before cancellation cancellationDuration = Date.now() - startCancelled; - - console.log( - `Cancellation test: validation completed in ${cancellationDuration}ms before cancellation could occur`, - ); } else { cancellationDuration = Date.now() - startCancelled; wasCancelled = true; ValidateTestUtils.assertValidationCancelled(result); - - console.log(`Cancellation test: validation was cancelled after ${cancellationDuration}ms`); } - // Log the performance comparison - console.log( - `Performance comparison: Complete=${completeDuration}ms, Cancelled=${cancellationDuration}ms, WasCancelled=${wasCancelled}`, - ); - // Verify that cancellation provided a performance benefit if (wasCancelled) { // If validation was actually cancelled, it should be significantly faster @@ -318,13 +229,6 @@ describe("Async Validation", () => { completeDuration, `Cancelled validation (${cancellationDuration}ms) should be faster than complete validation (${completeDuration}ms)`, ); - - console.log(`✓ Cancellation provided ${completeDuration - cancellationDuration}ms performance benefit`); - } else { - // If validation completed before cancellation, that's also valuable information - console.log( - `ℹ Validation completed too quickly (${cancellationDuration}ms) for cancellation to take effect`, - ); } }).timeout(TEST_TIMEOUT_MS); }); @@ -413,19 +317,13 @@ describe("Async Validation", () => { cancellationToken, }; - const startTime: number = Date.now(); - const result: ValidateOk = await ValidateTestUtils.assertValidate({ text: smallDocument, analysisSettings, validationSettings, }); - const duration: number = Date.now() - startTime; - expect(result).to.not.be.undefined; - // Log performance for manual observation (no assertions on timing) - console.log(`Small document validation took ${duration}ms`); }); it("should have reasonable performance for medium documents", async () => { @@ -436,25 +334,21 @@ describe("Async Validation", () => { cancellationToken, }; - const startTime: number = Date.now(); - const result: ValidateOk = await ValidateTestUtils.assertValidate({ text: mediumDocument, analysisSettings, validationSettings, }); - const duration: number = Date.now() - startTime; - expect(result).to.not.be.undefined; - // Log performance for manual observation (no assertions on timing) - console.log(`Medium document validation took ${duration}ms`); }); }); describe("Async validation with different validation settings", () => { it("should respect cancellation with all validation checks enabled", async () => { - const cancellationToken: ICancellationToken = TestUtils.createTestCancellationToken(); + const cancellationToken: ICancellationToken = TestUtils.createTestCancellationToken({ + cancelAfterCount: 20, // Cancel after 20 calls to test early cancellation with all checks + }); const validationSettings: ValidationSettings = { ...baseValidationSettings, @@ -464,29 +358,27 @@ describe("Async Validation", () => { checkForDuplicateIdentifiers: true, }; - const validationPromise: Promise> = validate( + const result: Result = await validate( TestUtils.mockDocument("let x = unknownFunc(1, 2) in x"), analysisSettings, validationSettings, ); - setTimeout(() => cancellationToken.cancel("Test cancellation"), 100); - - const result: Result = await validationPromise; - ValidateTestUtils.assertValidationSuccessOrCancelled( result, () => { - console.log("Validation completed before cancellation"); + // Validation completed before reaching cancellation threshold }, () => { - // Validation was cancelled - no additional action needed + // Validation was cancelled }, ); }); it("should respect cancellation with only specific checks enabled", async () => { - const cancellationToken: ICancellationToken = TestUtils.createTestCancellationToken(); + const cancellationToken: ICancellationToken = TestUtils.createTestCancellationToken({ + cancelAfterCount: 30, // Cancel after 30 calls to test with fewer validation checks + }); const validationSettings: ValidationSettings = { ...baseValidationSettings, @@ -496,23 +388,19 @@ describe("Async Validation", () => { checkForDuplicateIdentifiers: false, }; - const validationPromise: Promise> = validate( + const result: Result = await validate( TestUtils.mockDocument("let x = unknownVariable in x"), analysisSettings, validationSettings, ); - setTimeout(() => cancellationToken.cancel("Test cancellation"), 100); - - const result: Result = await validationPromise; - ValidateTestUtils.assertValidationSuccessOrCancelled( result, () => { - console.log("Validation completed before cancellation"); + // Validation completed before reaching cancellation threshold }, () => { - // Validation was cancelled - no additional action needed + // Validation was cancelled }, ); }); From 0412ece96abc448086b445445d15495630568e79 Mon Sep 17 00:00:00 2001 From: Matt Masson Date: Thu, 11 Sep 2025 17:02:47 -0400 Subject: [PATCH 34/42] large doc only --- src/test/validation/asyncValidation.test.ts | 55 --------------------- 1 file changed, 55 deletions(-) diff --git a/src/test/validation/asyncValidation.test.ts b/src/test/validation/asyncValidation.test.ts index 7f43fcff..8d2af004 100644 --- a/src/test/validation/asyncValidation.test.ts +++ b/src/test/validation/asyncValidation.test.ts @@ -289,61 +289,6 @@ describe("Async Validation", () => { }); }); - describe("Performance with different document sizes", () => { - // Note: These tests validate functional correctness and log performance metrics - // without asserting on timing to avoid flaky tests across different environments. - // Performance can be monitored through: - // 1. Console output during test runs - // 2. Separate performance benchmarking tools - // 3. CI/CD performance tracking over time - - const smallDocument: string = "let x = 1 in x"; - - const mediumDocument: string = ` - let - func1 = (param1 as text, param2 as number) => param1 & Text.From(param2), - func2 = (data as table) => Table.RowCount(data), - func3 = (list as list) => List.Sum(list), - result = func1("Hello", func2(#table({"Col1"}, {{"Value1"}, {"Value2"}, {"Value3"}}))) - in - result - `; - - it("should have reasonable performance for small documents", async () => { - const cancellationToken: ICancellationToken = TestUtils.createTestCancellationToken(); - - const validationSettings: ValidationSettings = { - ...baseValidationSettings, - cancellationToken, - }; - - const result: ValidateOk = await ValidateTestUtils.assertValidate({ - text: smallDocument, - analysisSettings, - validationSettings, - }); - - expect(result).to.not.be.undefined; - }); - - it("should have reasonable performance for medium documents", async () => { - const cancellationToken: ICancellationToken = TestUtils.createTestCancellationToken(); - - const validationSettings: ValidationSettings = { - ...baseValidationSettings, - cancellationToken, - }; - - const result: ValidateOk = await ValidateTestUtils.assertValidate({ - text: mediumDocument, - analysisSettings, - validationSettings, - }); - - expect(result).to.not.be.undefined; - }); - }); - describe("Async validation with different validation settings", () => { it("should respect cancellation with all validation checks enabled", async () => { const cancellationToken: ICancellationToken = TestUtils.createTestCancellationToken({ From 2e4a5b844fddada5a12e00eb68c895e4bceaf6d0 Mon Sep 17 00:00:00 2001 From: Matt Masson Date: Thu, 11 Sep 2025 18:29:35 -0400 Subject: [PATCH 35/42] fix async cancellation --- .../promiseUtils.ts | 13 +- src/test/testUtils/asyncTestUtils.ts | 60 +++- src/test/validation/asyncValidation.test.ts | 260 +++++++++++------- 3 files changed, 230 insertions(+), 103 deletions(-) diff --git a/src/powerquery-language-services/promiseUtils.ts b/src/powerquery-language-services/promiseUtils.ts index 7ad03739..7ae4c108 100644 --- a/src/powerquery-language-services/promiseUtils.ts +++ b/src/powerquery-language-services/promiseUtils.ts @@ -2,10 +2,12 @@ // Licensed under the MIT license. import { ICancellationToken } from "@microsoft/powerquery-parser"; +import { setImmediate } from "timers"; /** * Sequential processing with cancellation support - often better than Promise.all * for cancellation scenarios because we can stop between each operation. + * Also yields control before each operation to allow cancellation tokens to take effect. */ export async function processSequentiallyWithCancellation( items: T[], @@ -15,7 +17,10 @@ export async function processSequentiallyWithCancellation( const results: R[] = []; for (const item of items) { - cancellationToken?.throwIfCancelled(); + // Yield control to allow async cancellation tokens to take effect + // eslint-disable-next-line no-await-in-loop + await yieldForCancellation(cancellationToken); + // eslint-disable-next-line no-await-in-loop const result: R = await processor(item); results.push(result); @@ -26,8 +31,14 @@ export async function processSequentiallyWithCancellation( export async function yieldForCancellation(cancellationToken?: ICancellationToken): Promise { if (cancellationToken) { + // First yield to microtasks (handles synchronous cancellation) await Promise.resolve(); cancellationToken.throwIfCancelled(); + + // Additional yield for setImmediate-based cancellation tokens + // This ensures we yield to the timer queue where setImmediate callbacks execute + await new Promise((resolve: () => void) => setImmediate(resolve)); + cancellationToken.throwIfCancelled(); } return Promise.resolve(); diff --git a/src/test/testUtils/asyncTestUtils.ts b/src/test/testUtils/asyncTestUtils.ts index f2267400..adb557b4 100644 --- a/src/test/testUtils/asyncTestUtils.ts +++ b/src/test/testUtils/asyncTestUtils.ts @@ -2,6 +2,7 @@ // Licensed under the MIT license. import { ICancellationToken } from "@microsoft/powerquery-parser"; +import { setImmediate } from "timers"; export interface TestCancellationTokenOptions { /** @@ -11,10 +12,18 @@ export interface TestCancellationTokenOptions { cancelAfterCount?: number; /** - * Delay in milliseconds before setting cancelled state (via setTimeout). - * Defaults to 0ms to simulate async cancellation behavior. + * Delay before setting cancelled state. + * - undefined: Synchronous cancellation (for deterministic tests) + * - 0: Use setImmediate for immediate async cancellation (better for async validation) + * - >0: Use setTimeout with the specified delay in milliseconds */ asyncDelayMs?: number; + + /** + * Test identifier for logging purposes. + * When provided, will log the final call count when the test completes. + */ + testId?: string; } /** @@ -24,20 +33,54 @@ export interface TestCancellationTokenOptions { * @param options Configuration for the cancellation token behavior * @returns A cancellation token with a cancel method for testing */ -export function createTestCancellationToken(options?: TestCancellationTokenOptions): ICancellationToken { +export function createTestCancellationToken( + options?: TestCancellationTokenOptions, +): ICancellationToken & { getCallCount: () => number } { let isCancelled: boolean = false; let callCount: number = 0; + let cancellationScheduled: boolean = false; // Prevent multiple async cancellations const cancelAfterCount: number | undefined = options?.cancelAfterCount; - const asyncDelayMs: number = options?.asyncDelayMs ?? 0; + const asyncDelayMs: number | undefined = options?.asyncDelayMs; + const testId: string | undefined = options?.testId; const checkAndTriggerCancellation: () => void = (): void => { callCount += 1; - if (cancelAfterCount !== undefined && callCount >= cancelAfterCount && !isCancelled) { - // Use setTimeout to simulate async cancellation behavior - setTimeout(() => { + if (cancelAfterCount !== undefined && callCount >= cancelAfterCount && !isCancelled && !cancellationScheduled) { + cancellationScheduled = true; // Mark that we've scheduled cancellation + + if (asyncDelayMs === undefined) { + // Synchronous cancellation for deterministic tests isCancelled = true; - }, asyncDelayMs); + + if (testId) { + console.log( + `${testId}: Cancellation triggered at call ${callCount} (threshold: ${cancelAfterCount}) [synchronous]`, + ); + } + } else if (asyncDelayMs === 0) { + // Use setImmediate for immediate async cancellation (better for async validation) + setImmediate(() => { + isCancelled = true; + + if (testId) { + console.log( + `${testId}: Cancellation triggered at call ${callCount} (threshold: ${cancelAfterCount}) [setImmediate]`, + ); + } + }); + } else { + // Use setTimeout with the specified delay + setTimeout(() => { + isCancelled = true; + + if (testId) { + console.log( + `${testId}: Cancellation triggered at call ${callCount} (threshold: ${cancelAfterCount}) [setTimeout:${asyncDelayMs}ms]`, + ); + } + }, asyncDelayMs); + } } }; @@ -57,5 +100,6 @@ export function createTestCancellationToken(options?: TestCancellationTokenOptio cancel: (_reason: string): void => { isCancelled = true; }, + getCallCount: (): number => callCount, }; } diff --git a/src/test/validation/asyncValidation.test.ts b/src/test/validation/asyncValidation.test.ts index 8d2af004..0a345ddc 100644 --- a/src/test/validation/asyncValidation.test.ts +++ b/src/test/validation/asyncValidation.test.ts @@ -23,21 +23,21 @@ describe("Async Validation", () => { isWorkspaceCacheAllowed: false, }; - describe("Large document validation", () => { - let largeSectionDocumentWithDiagnosticsText: string; - - before(() => { - // Load the large section document with diagnostics - const diagnosticsFilePath: string = path.join( - __dirname, - "..", - "files", - "LargeSectionDocument_WithDiagnostics.pq", - ); - - largeSectionDocumentWithDiagnosticsText = fs.readFileSync(diagnosticsFilePath, "utf8"); - }); + let largeSectionDocumentWithDiagnosticsText: string; + + before(() => { + // Load the large section document with diagnostics for all tests + const diagnosticsFilePath: string = path.join( + __dirname, + "..", + "files", + "LargeSectionDocument_WithDiagnostics.pq", + ); + + largeSectionDocumentWithDiagnosticsText = fs.readFileSync(diagnosticsFilePath, "utf8"); + }); + describe("Large document validation", () => { it("should validate document with diagnostic errors without cancellation", async () => { const validationSettings: ValidationSettings = { ...baseValidationSettings, @@ -60,9 +60,12 @@ describe("Async Validation", () => { }).timeout(TEST_TIMEOUT_MS); it("should respect cancellation token when cancelled after few cancellation checks", async () => { - const cancellationToken: ICancellationToken = TestUtils.createTestCancellationToken({ - cancelAfterCount: 10, // Cancel after 10 calls to isCancelled/throwIfCancelled - }); + const cancellationToken: ICancellationToken & { getCallCount: () => number } = + TestUtils.createTestCancellationToken({ + cancelAfterCount: 3, // Cancel after 3 calls to isCancelled/throwIfCancelled + asyncDelayMs: 0, // Immediate cancellation for deterministic testing + testId: "Few cancellation checks", + }); const validationSettings: ValidationSettings = { ...baseValidationSettings, @@ -75,21 +78,21 @@ describe("Async Validation", () => { validationSettings, ); - ValidateTestUtils.assertValidationSuccessOrCancelled( + console.log(`Test completed - Total cancellation calls: ${cancellationToken.getCallCount()}`); + + // This test expects cancellation to occur with immediate cancellation + ValidateTestUtils.assertValidationCancelled( result, - () => { - // If validation completed successfully, it finished before reaching cancellation threshold - }, - () => { - // Validation was successfully cancelled - }, + "Expected validation to be cancelled after 3 cancellation checks with immediate cancellation", ); }).timeout(TEST_TIMEOUT_MS); it("should respect cancellation token when cancelled after moderate cancellation checks", async () => { - const cancellationToken: ICancellationToken = TestUtils.createTestCancellationToken({ - cancelAfterCount: 100, // Cancel after 100 calls to isCancelled/throwIfCancelled - }); + const cancellationToken: ICancellationToken & { getCallCount: () => number } = + TestUtils.createTestCancellationToken({ + cancelAfterCount: 100, // Cancel after 100 calls to isCancelled/throwIfCancelled + testId: "Moderate cancellation checks", + }); const validationSettings: ValidationSettings = { ...baseValidationSettings, @@ -102,6 +105,8 @@ describe("Async Validation", () => { validationSettings, ); + console.log(`Test completed - Total cancellation calls: ${cancellationToken.getCallCount()}`); + ValidateTestUtils.assertValidationSuccessOrCancelled( result, () => { @@ -114,9 +119,11 @@ describe("Async Validation", () => { }).timeout(TEST_TIMEOUT_MS); it("should respect cancellation token when cancelled after many cancellation checks", async () => { - const cancellationToken: ICancellationToken = TestUtils.createTestCancellationToken({ - cancelAfterCount: 1000, // Cancel after 1000 calls to isCancelled/throwIfCancelled - }); + const cancellationToken: ICancellationToken & { getCallCount: () => number } = + TestUtils.createTestCancellationToken({ + cancelAfterCount: 1000, // Cancel after 1000 calls to isCancelled/throwIfCancelled + testId: "Many cancellation checks", + }); const validationSettings: ValidationSettings = { ...baseValidationSettings, @@ -129,6 +136,8 @@ describe("Async Validation", () => { validationSettings, ); + console.log(`Test completed - Total cancellation calls: ${cancellationToken.getCallCount()}`); + ValidateTestUtils.assertValidationSuccessOrCancelled( result, () => { @@ -144,31 +153,69 @@ describe("Async Validation", () => { const thresholds: number[] = [5, 25, 50, 200]; // Different cancellation thresholds for (const threshold of thresholds) { - const cancellationToken: ICancellationToken = TestUtils.createTestCancellationToken({ - cancelAfterCount: threshold, - }); - - const validationSettings: ValidationSettings = { - ...baseValidationSettings, - cancellationToken, - }; - - // eslint-disable-next-line no-await-in-loop - const result: Result = await validate( - TestUtils.mockDocument(largeSectionDocumentWithDiagnosticsText), - analysisSettings, - validationSettings, - ); - - ValidateTestUtils.assertValidationSuccessOrCancelled( - result, - () => { - // Validation completed before reaching cancellation threshold - }, - () => { - // Validation was successfully cancelled - }, - ); + // Low thresholds should definitely trigger cancellation with synchronous cancellation + if (threshold <= 25) { + const cancellationToken: ICancellationToken & { getCallCount: () => number } = + TestUtils.createTestCancellationToken({ + cancelAfterCount: threshold, + asyncDelayMs: undefined, // Synchronous cancellation for deterministic testing + testId: `Threshold ${threshold}`, + }); + + const validationSettings: ValidationSettings = { + ...baseValidationSettings, + cancellationToken, + }; + + // eslint-disable-next-line no-await-in-loop + const result: Result = await validate( + TestUtils.mockDocument(largeSectionDocumentWithDiagnosticsText), + analysisSettings, + validationSettings, + ); + + console.log( + `Threshold ${threshold} test completed - Total cancellation calls: ${cancellationToken.getCallCount()}`, + ); + + ValidateTestUtils.assertValidationCancelled( + result, + `Expected validation to be cancelled with low threshold of ${threshold} calls`, + ); + } else { + const cancellationToken: ICancellationToken & { getCallCount: () => number } = + TestUtils.createTestCancellationToken({ + cancelAfterCount: threshold, + testId: `Threshold ${threshold}`, + }); + + const validationSettings: ValidationSettings = { + ...baseValidationSettings, + cancellationToken, + }; + + // eslint-disable-next-line no-await-in-loop + const result: Result = await validate( + TestUtils.mockDocument(largeSectionDocumentWithDiagnosticsText), + analysisSettings, + validationSettings, + ); + + console.log( + `Threshold ${threshold} test completed - Total cancellation calls: ${cancellationToken.getCallCount()}`, + ); + + // Higher thresholds might complete before cancellation occurs + ValidateTestUtils.assertValidationSuccessOrCancelled( + result, + () => { + // Validation completed before reaching cancellation threshold + }, + () => { + // Validation was successfully cancelled + }, + ); + } } }).timeout(TEST_TIMEOUT_MS); @@ -194,9 +241,12 @@ describe("Async Validation", () => { // Then, measure time for cancelled validation with early threshold const startCancelled: number = Date.now(); - const cancellationToken: ICancellationToken = TestUtils.createTestCancellationToken({ - cancelAfterCount: 50, // Cancel after 50 calls - }); + const cancellationToken: ICancellationToken & { getCallCount: () => number } = + TestUtils.createTestCancellationToken({ + cancelAfterCount: 10, // Cancel after 10 calls + asyncDelayMs: 0, // Immediate cancellation for deterministic testing + testId: "Performance benefit test", + }); const cancelledValidationSettings: ValidationSettings = { ...baseValidationSettings, @@ -212,9 +262,13 @@ describe("Async Validation", () => { cancelledValidationSettings, ); + console.log(`Performance test completed - Total cancellation calls: ${cancellationToken.getCallCount()}`); + if (ResultUtils.isOk(result)) { - // If we get here, validation completed before cancellation - cancellationDuration = Date.now() - startCancelled; + // This test expects cancellation to occur, so fail if it doesn't + throw new Error( + `Expected validation to be cancelled with threshold of 10 calls, but it completed successfully in ${Date.now() - startCancelled}ms`, + ); } else { cancellationDuration = Date.now() - startCancelled; wasCancelled = true; @@ -223,13 +277,12 @@ describe("Async Validation", () => { } // Verify that cancellation provided a performance benefit - if (wasCancelled) { - // If validation was actually cancelled, it should be significantly faster - expect(cancellationDuration).to.be.lessThan( - completeDuration, - `Cancelled validation (${cancellationDuration}ms) should be faster than complete validation (${completeDuration}ms)`, - ); - } + expect(wasCancelled).to.be.true; + + expect(cancellationDuration).to.be.lessThan( + completeDuration, + `Cancelled validation (${cancellationDuration}ms) should be faster than complete validation (${completeDuration}ms)`, + ); }).timeout(TEST_TIMEOUT_MS); }); @@ -241,7 +294,7 @@ describe("Async Validation", () => { }; const result: ValidateOk = await ValidateTestUtils.assertValidate({ - text: "let x = 1 in x", + text: largeSectionDocumentWithDiagnosticsText, analysisSettings, validationSettings, }); @@ -250,7 +303,10 @@ describe("Async Validation", () => { }); it("should not throw when cancellation token is not cancelled", async () => { - const cancellationToken: ICancellationToken = TestUtils.createTestCancellationToken(); + const cancellationToken: ICancellationToken & { getCallCount: () => number } = + TestUtils.createTestCancellationToken({ + testId: "No cancellation test", + }); const validationSettings: ValidationSettings = { ...baseValidationSettings, @@ -258,17 +314,25 @@ describe("Async Validation", () => { }; const result: ValidateOk = await ValidateTestUtils.assertValidate({ - text: "let x = 1 in x", + text: largeSectionDocumentWithDiagnosticsText, analysisSettings, validationSettings, }); + console.log( + `No cancellation test completed - Total cancellation calls: ${cancellationToken.getCallCount()}`, + ); + expect(result).to.not.be.undefined; expect(cancellationToken.isCancelled()).to.be.false; }); it("should throw immediately when cancellation token is already cancelled", async () => { - const cancellationToken: ICancellationToken = TestUtils.createTestCancellationToken(); + const cancellationToken: ICancellationToken & { getCallCount: () => number } = + TestUtils.createTestCancellationToken({ + testId: "Pre-cancelled test", + }); + cancellationToken.cancel("Pre-cancelled for testing"); // Cancel before starting const validationSettings: ValidationSettings = { @@ -277,11 +341,13 @@ describe("Async Validation", () => { }; const result: Result = await validate( - TestUtils.mockDocument("let x = 1 in x"), + TestUtils.mockDocument(largeSectionDocumentWithDiagnosticsText), analysisSettings, validationSettings, ); + console.log(`Pre-cancelled test completed - Total cancellation calls: ${cancellationToken.getCallCount()}`); + ValidateTestUtils.assertValidationCancelled( result, "Expected validation to return error due to pre-cancelled token", @@ -291,9 +357,12 @@ describe("Async Validation", () => { describe("Async validation with different validation settings", () => { it("should respect cancellation with all validation checks enabled", async () => { - const cancellationToken: ICancellationToken = TestUtils.createTestCancellationToken({ - cancelAfterCount: 20, // Cancel after 20 calls to test early cancellation with all checks - }); + const cancellationToken: ICancellationToken & { getCallCount: () => number } = + TestUtils.createTestCancellationToken({ + cancelAfterCount: 5, // Cancel after 5 calls to test early cancellation with all checks + asyncDelayMs: 0, // Immediate cancellation for deterministic testing + testId: "All validation checks", + }); const validationSettings: ValidationSettings = { ...baseValidationSettings, @@ -304,26 +373,29 @@ describe("Async Validation", () => { }; const result: Result = await validate( - TestUtils.mockDocument("let x = unknownFunc(1, 2) in x"), + TestUtils.mockDocument(largeSectionDocumentWithDiagnosticsText), analysisSettings, validationSettings, ); - ValidateTestUtils.assertValidationSuccessOrCancelled( + console.log( + `All validation checks test completed - Total cancellation calls: ${cancellationToken.getCallCount()}`, + ); + + // This test expects cancellation to occur with a low threshold + ValidateTestUtils.assertValidationCancelled( result, - () => { - // Validation completed before reaching cancellation threshold - }, - () => { - // Validation was cancelled - }, + "Expected validation to be cancelled after 5 cancellation checks with all validation enabled", ); }); it("should respect cancellation with only specific checks enabled", async () => { - const cancellationToken: ICancellationToken = TestUtils.createTestCancellationToken({ - cancelAfterCount: 30, // Cancel after 30 calls to test with fewer validation checks - }); + const cancellationToken: ICancellationToken & { getCallCount: () => number } = + TestUtils.createTestCancellationToken({ + cancelAfterCount: 5, // Cancel after 5 calls to test with fewer validation checks + asyncDelayMs: undefined, // Synchronous cancellation for deterministic testing + testId: "Specific validation checks", + }); const validationSettings: ValidationSettings = { ...baseValidationSettings, @@ -334,19 +406,19 @@ describe("Async Validation", () => { }; const result: Result = await validate( - TestUtils.mockDocument("let x = unknownVariable in x"), + TestUtils.mockDocument(largeSectionDocumentWithDiagnosticsText), analysisSettings, validationSettings, ); - ValidateTestUtils.assertValidationSuccessOrCancelled( + console.log( + `Specific validation checks test completed - Total cancellation calls: ${cancellationToken.getCallCount()}`, + ); + + // This test expects cancellation to occur with a low threshold + ValidateTestUtils.assertValidationCancelled( result, - () => { - // Validation completed before reaching cancellation threshold - }, - () => { - // Validation was cancelled - }, + "Expected validation to be cancelled after 5 cancellation checks with specific validation enabled", ); }); }); From 2a06ecd1abcf80f3e27d5a9fc737c522664db380 Mon Sep 17 00:00:00 2001 From: Matt Masson Date: Thu, 11 Sep 2025 18:34:12 -0400 Subject: [PATCH 36/42] change timeouts --- src/test/validation/asyncValidation.test.ts | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/test/validation/asyncValidation.test.ts b/src/test/validation/asyncValidation.test.ts index 0a345ddc..a2c9de38 100644 --- a/src/test/validation/asyncValidation.test.ts +++ b/src/test/validation/asyncValidation.test.ts @@ -13,7 +13,8 @@ import * as ValidateTestUtils from "../testUtils/validationTestUtils"; import { AnalysisSettings, validate, ValidateOk, ValidationSettings } from "../../powerquery-language-services"; import { TestConstants, TestUtils } from ".."; -const TEST_TIMEOUT_MS: number = 60000; +const TEST_TIMEOUT_MS: number = 5000; +const TEST_SLOW_MS: number = 1000; describe("Async Validation", () => { const analysisSettings: AnalysisSettings = TestConstants.SimpleLibraryAnalysisSettings; @@ -57,7 +58,7 @@ describe("Async Validation", () => { // Should have diagnostic errors due to unknown identifiers expect(result.diagnostics.length).to.be.greaterThan(0, "Document with diagnostics should have errors"); - }).timeout(TEST_TIMEOUT_MS); + }); it("should respect cancellation token when cancelled after few cancellation checks", async () => { const cancellationToken: ICancellationToken & { getCallCount: () => number } = @@ -85,7 +86,7 @@ describe("Async Validation", () => { result, "Expected validation to be cancelled after 3 cancellation checks with immediate cancellation", ); - }).timeout(TEST_TIMEOUT_MS); + }); it("should respect cancellation token when cancelled after moderate cancellation checks", async () => { const cancellationToken: ICancellationToken & { getCallCount: () => number } = @@ -116,7 +117,7 @@ describe("Async Validation", () => { // Validation was successfully cancelled }, ); - }).timeout(TEST_TIMEOUT_MS); + }); it("should respect cancellation token when cancelled after many cancellation checks", async () => { const cancellationToken: ICancellationToken & { getCallCount: () => number } = @@ -147,7 +148,7 @@ describe("Async Validation", () => { // Validation was successfully cancelled }, ); - }).timeout(TEST_TIMEOUT_MS); + }); it("should handle cancellation gracefully at different thresholds", async () => { const thresholds: number[] = [5, 25, 50, 200]; // Different cancellation thresholds @@ -217,7 +218,7 @@ describe("Async Validation", () => { ); } } - }).timeout(TEST_TIMEOUT_MS); + }); it("should demonstrate performance benefit of cancellation", async () => { // Test that cancelled validation is faster than completed validation @@ -283,7 +284,7 @@ describe("Async Validation", () => { completeDuration, `Cancelled validation (${cancellationDuration}ms) should be faster than complete validation (${completeDuration}ms)`, ); - }).timeout(TEST_TIMEOUT_MS); + }); }); describe("Cancellation token behavior", () => { @@ -422,4 +423,6 @@ describe("Async Validation", () => { ); }); }); -}); +}) + .timeout(TEST_TIMEOUT_MS) + .slow(TEST_SLOW_MS); From f798a9230314f352cbbce07b139ba733d6189dec Mon Sep 17 00:00:00 2001 From: Matt Masson Date: Thu, 11 Sep 2025 18:47:56 -0400 Subject: [PATCH 37/42] improve --- src/test/testUtils/asyncTestUtils.ts | 40 ++++---- src/test/validation/asyncValidation.test.ts | 106 ++++++++++---------- 2 files changed, 75 insertions(+), 71 deletions(-) diff --git a/src/test/testUtils/asyncTestUtils.ts b/src/test/testUtils/asyncTestUtils.ts index adb557b4..c2675d6f 100644 --- a/src/test/testUtils/asyncTestUtils.ts +++ b/src/test/testUtils/asyncTestUtils.ts @@ -4,6 +4,16 @@ import { ICancellationToken } from "@microsoft/powerquery-parser"; import { setImmediate } from "timers"; +/** + * Test cancellation token interface that extends ICancellationToken with additional test methods. + */ +export interface ITestCancellationToken extends ICancellationToken { + /** + * Gets the number of times isCancelled or throwIfCancelled has been called. + */ + getCallCount(): number; +} + export interface TestCancellationTokenOptions { /** * Number of calls to isCancelled/throwIfCancelled before triggering cancellation. @@ -20,10 +30,10 @@ export interface TestCancellationTokenOptions { asyncDelayMs?: number; /** - * Test identifier for logging purposes. - * When provided, will log the final call count when the test completes. + * Optional logging function. + * When provided, will be called with trace messages during cancellation. */ - testId?: string; + log?: (message: string) => void; } /** @@ -33,15 +43,13 @@ export interface TestCancellationTokenOptions { * @param options Configuration for the cancellation token behavior * @returns A cancellation token with a cancel method for testing */ -export function createTestCancellationToken( - options?: TestCancellationTokenOptions, -): ICancellationToken & { getCallCount: () => number } { +export function createTestCancellationToken(options?: TestCancellationTokenOptions): ITestCancellationToken { let isCancelled: boolean = false; let callCount: number = 0; let cancellationScheduled: boolean = false; // Prevent multiple async cancellations const cancelAfterCount: number | undefined = options?.cancelAfterCount; const asyncDelayMs: number | undefined = options?.asyncDelayMs; - const testId: string | undefined = options?.testId; + const log: ((message: string) => void) | undefined = options?.log; const checkAndTriggerCancellation: () => void = (): void => { callCount += 1; @@ -53,19 +61,17 @@ export function createTestCancellationToken( // Synchronous cancellation for deterministic tests isCancelled = true; - if (testId) { - console.log( - `${testId}: Cancellation triggered at call ${callCount} (threshold: ${cancelAfterCount}) [synchronous]`, - ); + if (log) { + log(`Cancellation triggered at call ${callCount} (threshold: ${cancelAfterCount}) [synchronous]`); } } else if (asyncDelayMs === 0) { // Use setImmediate for immediate async cancellation (better for async validation) setImmediate(() => { isCancelled = true; - if (testId) { - console.log( - `${testId}: Cancellation triggered at call ${callCount} (threshold: ${cancelAfterCount}) [setImmediate]`, + if (log) { + log( + `Cancellation triggered at call ${callCount} (threshold: ${cancelAfterCount}) [setImmediate]`, ); } }); @@ -74,9 +80,9 @@ export function createTestCancellationToken( setTimeout(() => { isCancelled = true; - if (testId) { - console.log( - `${testId}: Cancellation triggered at call ${callCount} (threshold: ${cancelAfterCount}) [setTimeout:${asyncDelayMs}ms]`, + if (log) { + log( + `Cancellation triggered at call ${callCount} (threshold: ${cancelAfterCount}) [setTimeout:${asyncDelayMs}ms]`, ); } }, asyncDelayMs); diff --git a/src/test/validation/asyncValidation.test.ts b/src/test/validation/asyncValidation.test.ts index a2c9de38..d8b5282e 100644 --- a/src/test/validation/asyncValidation.test.ts +++ b/src/test/validation/asyncValidation.test.ts @@ -6,17 +6,25 @@ import * as fs from "fs"; import * as path from "path"; import { expect } from "chai"; -import { CommonError, ICancellationToken, Result, ResultUtils } from "@microsoft/powerquery-parser"; +import { CommonError, Result, ResultUtils } from "@microsoft/powerquery-parser"; import * as ValidateTestUtils from "../testUtils/validationTestUtils"; import { AnalysisSettings, validate, ValidateOk, ValidationSettings } from "../../powerquery-language-services"; import { TestConstants, TestUtils } from ".."; +import { ITestCancellationToken } from "../testUtils/asyncTestUtils"; const TEST_TIMEOUT_MS: number = 5000; const TEST_SLOW_MS: number = 1000; describe("Async Validation", () => { + // Set to true to enable console logging in tests for debugging + const enableConsoleTrace: boolean = false; + + const consoleLogger: ((message: string) => void) | undefined = enableConsoleTrace + ? (message: string): void => console.log(message) + : undefined; + const analysisSettings: AnalysisSettings = TestConstants.SimpleLibraryAnalysisSettings; const baseValidationSettings: ValidationSettings = { @@ -61,12 +69,11 @@ describe("Async Validation", () => { }); it("should respect cancellation token when cancelled after few cancellation checks", async () => { - const cancellationToken: ICancellationToken & { getCallCount: () => number } = - TestUtils.createTestCancellationToken({ - cancelAfterCount: 3, // Cancel after 3 calls to isCancelled/throwIfCancelled - asyncDelayMs: 0, // Immediate cancellation for deterministic testing - testId: "Few cancellation checks", - }); + const cancellationToken: ITestCancellationToken = TestUtils.createTestCancellationToken({ + cancelAfterCount: 3, // Cancel after 3 calls to isCancelled/throwIfCancelled + asyncDelayMs: 0, // Immediate cancellation for deterministic testing + log: consoleLogger, + }); const validationSettings: ValidationSettings = { ...baseValidationSettings, @@ -89,11 +96,10 @@ describe("Async Validation", () => { }); it("should respect cancellation token when cancelled after moderate cancellation checks", async () => { - const cancellationToken: ICancellationToken & { getCallCount: () => number } = - TestUtils.createTestCancellationToken({ - cancelAfterCount: 100, // Cancel after 100 calls to isCancelled/throwIfCancelled - testId: "Moderate cancellation checks", - }); + const cancellationToken: ITestCancellationToken = TestUtils.createTestCancellationToken({ + cancelAfterCount: 100, // Cancel after 100 calls to isCancelled/throwIfCancelled + log: consoleLogger, + }); const validationSettings: ValidationSettings = { ...baseValidationSettings, @@ -120,11 +126,10 @@ describe("Async Validation", () => { }); it("should respect cancellation token when cancelled after many cancellation checks", async () => { - const cancellationToken: ICancellationToken & { getCallCount: () => number } = - TestUtils.createTestCancellationToken({ - cancelAfterCount: 1000, // Cancel after 1000 calls to isCancelled/throwIfCancelled - testId: "Many cancellation checks", - }); + const cancellationToken: ITestCancellationToken = TestUtils.createTestCancellationToken({ + cancelAfterCount: 1000, // Cancel after 1000 calls to isCancelled/throwIfCancelled + log: consoleLogger, + }); const validationSettings: ValidationSettings = { ...baseValidationSettings, @@ -156,12 +161,11 @@ describe("Async Validation", () => { for (const threshold of thresholds) { // Low thresholds should definitely trigger cancellation with synchronous cancellation if (threshold <= 25) { - const cancellationToken: ICancellationToken & { getCallCount: () => number } = - TestUtils.createTestCancellationToken({ - cancelAfterCount: threshold, - asyncDelayMs: undefined, // Synchronous cancellation for deterministic testing - testId: `Threshold ${threshold}`, - }); + const cancellationToken: ITestCancellationToken = TestUtils.createTestCancellationToken({ + cancelAfterCount: threshold, + asyncDelayMs: undefined, // Synchronous cancellation for deterministic testing + log: consoleLogger, + }); const validationSettings: ValidationSettings = { ...baseValidationSettings, @@ -184,11 +188,10 @@ describe("Async Validation", () => { `Expected validation to be cancelled with low threshold of ${threshold} calls`, ); } else { - const cancellationToken: ICancellationToken & { getCallCount: () => number } = - TestUtils.createTestCancellationToken({ - cancelAfterCount: threshold, - testId: `Threshold ${threshold}`, - }); + const cancellationToken: ITestCancellationToken = TestUtils.createTestCancellationToken({ + cancelAfterCount: threshold, + log: consoleLogger, + }); const validationSettings: ValidationSettings = { ...baseValidationSettings, @@ -242,12 +245,11 @@ describe("Async Validation", () => { // Then, measure time for cancelled validation with early threshold const startCancelled: number = Date.now(); - const cancellationToken: ICancellationToken & { getCallCount: () => number } = - TestUtils.createTestCancellationToken({ - cancelAfterCount: 10, // Cancel after 10 calls - asyncDelayMs: 0, // Immediate cancellation for deterministic testing - testId: "Performance benefit test", - }); + const cancellationToken: ITestCancellationToken = TestUtils.createTestCancellationToken({ + cancelAfterCount: 10, // Cancel after 10 calls + asyncDelayMs: 0, // Immediate cancellation for deterministic testing + log: consoleLogger, + }); const cancelledValidationSettings: ValidationSettings = { ...baseValidationSettings, @@ -304,10 +306,9 @@ describe("Async Validation", () => { }); it("should not throw when cancellation token is not cancelled", async () => { - const cancellationToken: ICancellationToken & { getCallCount: () => number } = - TestUtils.createTestCancellationToken({ - testId: "No cancellation test", - }); + const cancellationToken: ITestCancellationToken = TestUtils.createTestCancellationToken({ + log: consoleLogger, + }); const validationSettings: ValidationSettings = { ...baseValidationSettings, @@ -329,10 +330,9 @@ describe("Async Validation", () => { }); it("should throw immediately when cancellation token is already cancelled", async () => { - const cancellationToken: ICancellationToken & { getCallCount: () => number } = - TestUtils.createTestCancellationToken({ - testId: "Pre-cancelled test", - }); + const cancellationToken: ITestCancellationToken = TestUtils.createTestCancellationToken({ + log: consoleLogger, + }); cancellationToken.cancel("Pre-cancelled for testing"); // Cancel before starting @@ -358,12 +358,11 @@ describe("Async Validation", () => { describe("Async validation with different validation settings", () => { it("should respect cancellation with all validation checks enabled", async () => { - const cancellationToken: ICancellationToken & { getCallCount: () => number } = - TestUtils.createTestCancellationToken({ - cancelAfterCount: 5, // Cancel after 5 calls to test early cancellation with all checks - asyncDelayMs: 0, // Immediate cancellation for deterministic testing - testId: "All validation checks", - }); + const cancellationToken: ITestCancellationToken = TestUtils.createTestCancellationToken({ + cancelAfterCount: 5, // Cancel after 5 calls to test early cancellation with all checks + asyncDelayMs: 0, // Immediate cancellation for deterministic testing + log: consoleLogger, + }); const validationSettings: ValidationSettings = { ...baseValidationSettings, @@ -391,12 +390,11 @@ describe("Async Validation", () => { }); it("should respect cancellation with only specific checks enabled", async () => { - const cancellationToken: ICancellationToken & { getCallCount: () => number } = - TestUtils.createTestCancellationToken({ - cancelAfterCount: 5, // Cancel after 5 calls to test with fewer validation checks - asyncDelayMs: undefined, // Synchronous cancellation for deterministic testing - testId: "Specific validation checks", - }); + const cancellationToken: ITestCancellationToken = TestUtils.createTestCancellationToken({ + cancelAfterCount: 5, // Cancel after 5 calls to test with fewer validation checks + asyncDelayMs: undefined, // Synchronous cancellation for deterministic testing + log: consoleLogger, + }); const validationSettings: ValidationSettings = { ...baseValidationSettings, From 278dc3b54775872a191708695801dd7ad61a6a17 Mon Sep 17 00:00:00 2001 From: Matt Masson Date: Thu, 11 Sep 2025 18:50:46 -0400 Subject: [PATCH 38/42] adjust comment --- src/test/testUtils/asyncTestUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/testUtils/asyncTestUtils.ts b/src/test/testUtils/asyncTestUtils.ts index c2675d6f..eb6f4a4c 100644 --- a/src/test/testUtils/asyncTestUtils.ts +++ b/src/test/testUtils/asyncTestUtils.ts @@ -65,7 +65,7 @@ export function createTestCancellationToken(options?: TestCancellationTokenOptio log(`Cancellation triggered at call ${callCount} (threshold: ${cancelAfterCount}) [synchronous]`); } } else if (asyncDelayMs === 0) { - // Use setImmediate for immediate async cancellation (better for async validation) + // Use setImmediate for immediate async cancellation setImmediate(() => { isCancelled = true; From 077995819d10cf67e3fc2132f558746c93861b1b Mon Sep 17 00:00:00 2001 From: Matt Masson Date: Thu, 11 Sep 2025 18:52:27 -0400 Subject: [PATCH 39/42] cleanup comment --- .../validate/validateDuplicateIdentifiers.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/powerquery-language-services/validate/validateDuplicateIdentifiers.ts b/src/powerquery-language-services/validate/validateDuplicateIdentifiers.ts index a61f747d..88781e01 100644 --- a/src/powerquery-language-services/validate/validateDuplicateIdentifiers.ts +++ b/src/powerquery-language-services/validate/validateDuplicateIdentifiers.ts @@ -257,7 +257,6 @@ async function validateDuplicateIdentifiersForKeyValuePair( const result: Diagnostic[] = []; - // Yield control to allow for cancellation. // If we need more cancellability, we can move this into the loop and yield every N iterations. await PromiseUtils.yieldForCancellation(cancellationToken); From 996ebabbe606bea6ce2b4705de61e13ef4bb2a3a Mon Sep 17 00:00:00 2001 From: Matt Masson Date: Thu, 11 Sep 2025 19:02:17 -0400 Subject: [PATCH 40/42] test cleanup --- src/test/validation/asyncValidation.test.ts | 110 ++++++++++---------- 1 file changed, 54 insertions(+), 56 deletions(-) diff --git a/src/test/validation/asyncValidation.test.ts b/src/test/validation/asyncValidation.test.ts index d8b5282e..b2479b7a 100644 --- a/src/test/validation/asyncValidation.test.ts +++ b/src/test/validation/asyncValidation.test.ts @@ -19,7 +19,7 @@ const TEST_SLOW_MS: number = 1000; describe("Async Validation", () => { // Set to true to enable console logging in tests for debugging - const enableConsoleTrace: boolean = false; + const enableConsoleTrace: boolean = true; const consoleLogger: ((message: string) => void) | undefined = enableConsoleTrace ? (message: string): void => console.log(message) @@ -68,10 +68,10 @@ describe("Async Validation", () => { expect(result.diagnostics.length).to.be.greaterThan(0, "Document with diagnostics should have errors"); }); - it("should respect cancellation token when cancelled after few cancellation checks", async () => { + it("should respect cancellation token when cancelled after 3 cancellation checks", async () => { const cancellationToken: ITestCancellationToken = TestUtils.createTestCancellationToken({ - cancelAfterCount: 3, // Cancel after 3 calls to isCancelled/throwIfCancelled - asyncDelayMs: 0, // Immediate cancellation for deterministic testing + cancelAfterCount: 3, + asyncDelayMs: 0, log: consoleLogger, }); @@ -86,7 +86,9 @@ describe("Async Validation", () => { validationSettings, ); - console.log(`Test completed - Total cancellation calls: ${cancellationToken.getCallCount()}`); + if (consoleLogger) { + consoleLogger(`Test completed - Total cancellation calls: ${cancellationToken.getCallCount()}`); + } // This test expects cancellation to occur with immediate cancellation ValidateTestUtils.assertValidationCancelled( @@ -95,7 +97,7 @@ describe("Async Validation", () => { ); }); - it("should respect cancellation token when cancelled after moderate cancellation checks", async () => { + it("should respect cancellation token when cancelled after 100 cancellation checks", async () => { const cancellationToken: ITestCancellationToken = TestUtils.createTestCancellationToken({ cancelAfterCount: 100, // Cancel after 100 calls to isCancelled/throwIfCancelled log: consoleLogger, @@ -112,17 +114,11 @@ describe("Async Validation", () => { validationSettings, ); - console.log(`Test completed - Total cancellation calls: ${cancellationToken.getCallCount()}`); + if (consoleLogger) { + consoleLogger(`Test completed - Total cancellation calls: ${cancellationToken.getCallCount()}`); + } - ValidateTestUtils.assertValidationSuccessOrCancelled( - result, - () => { - // Validation completed before reaching cancellation threshold - }, - () => { - // Validation was successfully cancelled - }, - ); + ValidateTestUtils.assertValidationCancelled(result); }); it("should respect cancellation token when cancelled after many cancellation checks", async () => { @@ -142,28 +138,21 @@ describe("Async Validation", () => { validationSettings, ); - console.log(`Test completed - Total cancellation calls: ${cancellationToken.getCallCount()}`); + if (consoleLogger) { + consoleLogger(`Test completed - Total cancellation calls: ${cancellationToken.getCallCount()}`); + } - ValidateTestUtils.assertValidationSuccessOrCancelled( - result, - () => { - // Validation completed before reaching cancellation threshold - }, - () => { - // Validation was successfully cancelled - }, - ); + ValidateTestUtils.assertValidationCancelled(result); }); it("should handle cancellation gracefully at different thresholds", async () => { const thresholds: number[] = [5, 25, 50, 200]; // Different cancellation thresholds for (const threshold of thresholds) { - // Low thresholds should definitely trigger cancellation with synchronous cancellation if (threshold <= 25) { const cancellationToken: ITestCancellationToken = TestUtils.createTestCancellationToken({ cancelAfterCount: threshold, - asyncDelayMs: undefined, // Synchronous cancellation for deterministic testing + asyncDelayMs: 0, log: consoleLogger, }); @@ -179,9 +168,11 @@ describe("Async Validation", () => { validationSettings, ); - console.log( - `Threshold ${threshold} test completed - Total cancellation calls: ${cancellationToken.getCallCount()}`, - ); + if (consoleLogger) { + consoleLogger( + `Threshold ${threshold} test completed - Total cancellation calls: ${cancellationToken.getCallCount()}`, + ); + } ValidateTestUtils.assertValidationCancelled( result, @@ -191,6 +182,7 @@ describe("Async Validation", () => { const cancellationToken: ITestCancellationToken = TestUtils.createTestCancellationToken({ cancelAfterCount: threshold, log: consoleLogger, + asyncDelayMs: 0, }); const validationSettings: ValidationSettings = { @@ -205,20 +197,13 @@ describe("Async Validation", () => { validationSettings, ); - console.log( - `Threshold ${threshold} test completed - Total cancellation calls: ${cancellationToken.getCallCount()}`, - ); + if (consoleLogger) { + consoleLogger( + `Threshold ${threshold} test completed - Total cancellation calls: ${cancellationToken.getCallCount()}`, + ); + } - // Higher thresholds might complete before cancellation occurs - ValidateTestUtils.assertValidationSuccessOrCancelled( - result, - () => { - // Validation completed before reaching cancellation threshold - }, - () => { - // Validation was successfully cancelled - }, - ); + ValidateTestUtils.assertValidationCancelled(result); } } }); @@ -265,7 +250,11 @@ describe("Async Validation", () => { cancelledValidationSettings, ); - console.log(`Performance test completed - Total cancellation calls: ${cancellationToken.getCallCount()}`); + if (consoleLogger) { + consoleLogger( + `Performance test completed - Total cancellation calls: ${cancellationToken.getCallCount()}`, + ); + } if (ResultUtils.isOk(result)) { // This test expects cancellation to occur, so fail if it doesn't @@ -321,9 +310,11 @@ describe("Async Validation", () => { validationSettings, }); - console.log( - `No cancellation test completed - Total cancellation calls: ${cancellationToken.getCallCount()}`, - ); + if (consoleLogger) { + consoleLogger( + `No cancellation test completed - Total cancellation calls: ${cancellationToken.getCallCount()}`, + ); + } expect(result).to.not.be.undefined; expect(cancellationToken.isCancelled()).to.be.false; @@ -347,7 +338,11 @@ describe("Async Validation", () => { validationSettings, ); - console.log(`Pre-cancelled test completed - Total cancellation calls: ${cancellationToken.getCallCount()}`); + if (consoleLogger) { + consoleLogger( + `Pre-cancelled test completed - Total cancellation calls: ${cancellationToken.getCallCount()}`, + ); + } ValidateTestUtils.assertValidationCancelled( result, @@ -378,9 +373,11 @@ describe("Async Validation", () => { validationSettings, ); - console.log( - `All validation checks test completed - Total cancellation calls: ${cancellationToken.getCallCount()}`, - ); + if (consoleLogger) { + consoleLogger( + `All validation checks test completed - Total cancellation calls: ${cancellationToken.getCallCount()}`, + ); + } // This test expects cancellation to occur with a low threshold ValidateTestUtils.assertValidationCancelled( @@ -410,11 +407,12 @@ describe("Async Validation", () => { validationSettings, ); - console.log( - `Specific validation checks test completed - Total cancellation calls: ${cancellationToken.getCallCount()}`, - ); + if (consoleLogger) { + consoleLogger( + `Specific validation checks test completed - Total cancellation calls: ${cancellationToken.getCallCount()}`, + ); + } - // This test expects cancellation to occur with a low threshold ValidateTestUtils.assertValidationCancelled( result, "Expected validation to be cancelled after 5 cancellation checks with specific validation enabled", From 291192a261b15c307187355c1c32f94067c2290e Mon Sep 17 00:00:00 2001 From: Matt Masson Date: Thu, 11 Sep 2025 19:02:42 -0400 Subject: [PATCH 41/42] add todo --- src/test/validation/asyncValidation.test.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/test/validation/asyncValidation.test.ts b/src/test/validation/asyncValidation.test.ts index b2479b7a..5e63a6cc 100644 --- a/src/test/validation/asyncValidation.test.ts +++ b/src/test/validation/asyncValidation.test.ts @@ -17,6 +17,10 @@ import { ITestCancellationToken } from "../testUtils/asyncTestUtils"; const TEST_TIMEOUT_MS: number = 5000; const TEST_SLOW_MS: number = 1000; +// ******************* +// TODO: consolidate test code and cleanup test cases +// ******************* + describe("Async Validation", () => { // Set to true to enable console logging in tests for debugging const enableConsoleTrace: boolean = true; From 8da8df51a7a0460a8dc11f59462a8cbc87e1f3cf Mon Sep 17 00:00:00 2001 From: Matt Masson Date: Fri, 12 Sep 2025 10:29:14 -0400 Subject: [PATCH 42/42] remove the throwIfCancelled statements that were added to invokeExpression --- .../inspection/invokeExpression/invokeExpression.ts | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/powerquery-language-services/inspection/invokeExpression/invokeExpression.ts b/src/powerquery-language-services/inspection/invokeExpression/invokeExpression.ts index 60a39726..05df093c 100644 --- a/src/powerquery-language-services/inspection/invokeExpression/invokeExpression.ts +++ b/src/powerquery-language-services/inspection/invokeExpression/invokeExpression.ts @@ -65,8 +65,6 @@ async function inspectInvokeExpression( correlationId, ); - settings.cancellationToken?.throwIfCancelled(); - const invokeExpressionXorNode: TXorNode | undefined = NodeIdMapUtils.xor(nodeIdMapCollection, invokeExpressionId); if (invokeExpressionXorNode === undefined) { @@ -112,8 +110,6 @@ async function inspectInvokeExpression( const givenArguments: ReadonlyArray = iterableArguments.slice(0, givenArgumentTypes.length); - settings.cancellationToken?.throwIfCancelled(); - const [numMinExpectedArguments, numMaxExpectedArguments]: [number, number] = getNumExpectedArguments(functionType); @@ -170,8 +166,6 @@ async function getIsNameInLocalScope( initialCorrelationId: trace.id, }; - settings.cancellationToken?.throwIfCancelled(); - // Try to find out if the identifier is a local or external name. if (name !== undefined) { // Seed local scope @@ -217,10 +211,9 @@ async function getArgumentTypes( const result: Type.TPowerQueryType[] = []; for (const xorNode of argXorNodes) { - settings.cancellationToken?.throwIfCancelled(); - // eslint-disable-next-line no-await-in-loop const triedArgType: TriedType = await tryType(settings, nodeIdMapCollection, xorNode.node.id, typeCache); + settings.cancellationToken?.throwIfCancelled(); if (ResultUtils.isError(triedArgType)) { throw triedArgType;