diff --git a/.gitignore b/.gitignore index 9e7e900d..dfe85986 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ node_modules coverage .DS_Store dist -.vscode \ No newline at end of file +.vscode +.claude \ No newline at end of file diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 00000000..58085c20 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,7 @@ +{ + "useTabs": true, + "singleQuote": true, + "trailingComma": "all", + "semi": false, + "printWidth": 140 +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 57fa46ab..ce2c12aa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,10 +14,14 @@ }, "devDependencies": { "@codecov/vite-plugin": "^1.9.0", + "@types/css-tree": "^2.3.11", "c8": "^10.1.3", + "prettier": "^3.6.2", + "typescript": "^5.9.3", "uvu": "^0.5.6", "vite": "^6.3.4", - "vite-plugin-dts": "^4.5.0" + "vite-plugin-dts": "^4.5.0", + "vitest": "^4.0.8" }, "engines": { "node": ">=18.0.0" @@ -740,9 +744,9 @@ } }, "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, "license": "MIT" }, @@ -896,6 +900,7 @@ "integrity": "sha512-1LFfa/qnMQvEOAdzlQymH0ulepxbxnCYAKJZfMci/5XJyIHWgEYnDmgnKakbTh7CH2tFQ5O60oYDvns4i9RAIg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@octokit/auth-token": "^4.0.0", "@octokit/graphql": "^7.1.0", @@ -1496,6 +1501,13 @@ "string-argv": "~0.3.1" } }, + "node_modules/@standard-schema/spec": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", + "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/argparse": { "version": "1.0.38", "resolved": "https://registry.npmjs.org/@types/argparse/-/argparse-1.0.38.tgz", @@ -1503,6 +1515,31 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/css-tree": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/@types/css-tree/-/css-tree-2.3.11.tgz", + "integrity": "sha512-aEokibJOI77uIlqoBOkVbaQGC9zII0A+JH1kcTNKW2CwyYWD8KM6qdo+4c77wD3wZOQfJuNWAr9M4hdk+YhDIg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", @@ -1517,6 +1554,127 @@ "dev": true, "license": "MIT" }, + "node_modules/@vitest/expect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.8.tgz", + "integrity": "sha512-Rv0eabdP/xjAHQGr8cjBm+NnLHNoL268lMDK85w2aAGLFoVKLd8QGnVon5lLtkXQCoYaNL0wg04EGnyKkkKhPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.0.8", + "@vitest/utils": "4.0.8", + "chai": "^6.2.0", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.8.tgz", + "integrity": "sha512-9FRM3MZCedXH3+pIh+ME5Up2NBBHDq0wqwhOKkN4VnvCiKbVxddqH9mSGPZeawjd12pCOGnl+lo/ZGHt0/dQSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.0.8", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/mocker/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.8.tgz", + "integrity": "sha512-qRrjdRkINi9DaZHAimV+8ia9Gq6LeGz2CgIEmMLz3sBDYV53EsnLZbJMR1q84z1HZCMsf7s0orDgZn7ScXsZKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.8.tgz", + "integrity": "sha512-mdY8Sf1gsM8hKJUQfiPT3pn1n8RF4QBcJYFslgWh41JTfrK1cbqY8whpGCFzBl45LN028g0njLCYm0d7XxSaQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.0.8", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.8.tgz", + "integrity": "sha512-Nar9OTU03KGiubrIOFhcfHg8FYaRaNT+bh5VUlNz8stFhCZPNrJvmZkhsr1jtaYvuefYFwK2Hwrq026u4uPWCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.0.8", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.8.tgz", + "integrity": "sha512-nvGVqUunyCgZH7kmo+Ord4WgZ7lN0sOULYXUOYuHr55dvg9YvMz3izfB189Pgp28w0vWFbEEfNc/c3VTrqrXeA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.8.tgz", + "integrity": "sha512-pdk2phO5NDvEFfUTxcTP8RFYjVj/kfLSPIN5ebP2Mu9kcIMeAQTbknqcFEyBcC4z2pJlJI9aS5UQjcYfhmKAow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.0.8", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/@volar/language-core": { "version": "2.4.11", "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.11.tgz", @@ -1720,6 +1878,16 @@ "sprintf-js": "~1.0.2" } }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -1843,6 +2011,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/chai": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.0.tgz", + "integrity": "sha512-aUTnJc/JipRzJrNADXVvpVqi6CO0dn3nx4EVPxijri+fj3LUUDyZQOgVeW54Ob3Y1Xh9Iz8f+CgaCl8v0mn9bA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -1952,9 +2130,9 @@ "license": "MIT" }, "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.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, "license": "MIT", "dependencies": { @@ -2019,6 +2197,13 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/es-module-lexer": { + "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" + }, "node_modules/esbuild": { "version": "0.25.1", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.1.tgz", @@ -2075,6 +2260,16 @@ "dev": true, "license": "MIT" }, + "node_modules/expect-type": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.2.tgz", + "integrity": "sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -2083,11 +2278,14 @@ "license": "MIT" }, "node_modules/fdir": { - "version": "6.4.4", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", - "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, "peerDependencies": { "picomatch": "^3 || ^4" }, @@ -2418,13 +2616,13 @@ "license": "ISC" }, "node_modules/magic-string": { - "version": "0.30.17", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", - "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0" + "@jridgewell/sourcemap-codec": "^1.5.5" } }, "node_modules/mdn-data": { @@ -2595,9 +2793,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", "engines": { @@ -2648,6 +2846,22 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/prettier": { + "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.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -2704,6 +2918,7 @@ "integrity": "sha512-C5VvvgCCyfyotVITIAv+4efVytl5F7wt+/I2i9q9GZcEXW9BP52YYOXC58igUi+LFZVHukErIIqQSWwv/M3WRw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/estree": "1.0.7" }, @@ -2785,6 +3000,13 @@ "node": ">=8" } }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, "node_modules/signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", @@ -2823,6 +3045,20 @@ "dev": true, "license": "BSD-3-Clause" }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, "node_modules/string-argv": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", @@ -2945,15 +3181,29 @@ "node": ">=18" } }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, "node_modules/tinyglobby": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz", - "integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==", + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", "dev": true, "license": "MIT", "dependencies": { - "fdir": "^6.4.4", - "picomatch": "^4.0.2" + "fdir": "^6.5.0", + "picomatch": "^4.0.3" }, "engines": { "node": ">=12.0.0" @@ -2962,6 +3212,16 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, + "node_modules/tinyrainbow": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz", + "integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/tunnel": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", @@ -2973,9 +3233,9 @@ } }, "node_modules/typescript": { - "version": "5.7.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", - "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", "peer": true, @@ -3093,6 +3353,7 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", "dev": true, + "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", @@ -3189,6 +3450,84 @@ } } }, + "node_modules/vitest": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.8.tgz", + "integrity": "sha512-urzu3NCEV0Qa0Y2PwvBtRgmNtxhj5t5ULw7cuKhIHh3OrkKTLlut0lnBOv9qe5OvbkMH2g38G7KPDCTpIytBVg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.0.8", + "@vitest/mocker": "4.0.8", + "@vitest/pretty-format": "4.0.8", + "@vitest/runner": "4.0.8", + "@vitest/snapshot": "4.0.8", + "@vitest/spy": "4.0.8", + "@vitest/utils": "4.0.8", + "debug": "^4.4.3", + "es-module-lexer": "^1.7.0", + "expect-type": "^1.2.2", + "magic-string": "^0.30.21", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^3.10.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.0.3", + "vite": "^6.0.0 || ^7.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/debug": "^4.1.12", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.0.8", + "@vitest/browser-preview": "4.0.8", + "@vitest/browser-webdriverio": "4.0.8", + "@vitest/ui": "4.0.8", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/debug": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, "node_modules/vscode-uri": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", @@ -3219,6 +3558,23 @@ "node": ">= 8" } }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -3707,9 +4063,9 @@ "dev": true }, "@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 }, "@jridgewell/trace-mapping": { @@ -3828,6 +4184,7 @@ "resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.2.0.tgz", "integrity": "sha512-1LFfa/qnMQvEOAdzlQymH0ulepxbxnCYAKJZfMci/5XJyIHWgEYnDmgnKakbTh7CH2tFQ5O60oYDvns4i9RAIg==", "dev": true, + "peer": true, "requires": { "@octokit/auth-token": "^4.0.0", "@octokit/graphql": "^7.1.0", @@ -4198,12 +4555,40 @@ "string-argv": "~0.3.1" } }, + "@standard-schema/spec": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", + "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==", + "dev": true + }, "@types/argparse": { "version": "1.0.38", "resolved": "https://registry.npmjs.org/@types/argparse/-/argparse-1.0.38.tgz", "integrity": "sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA==", "dev": true }, + "@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "requires": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "@types/css-tree": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/@types/css-tree/-/css-tree-2.3.11.tgz", + "integrity": "sha512-aEokibJOI77uIlqoBOkVbaQGC9zII0A+JH1kcTNKW2CwyYWD8KM6qdo+4c77wD3wZOQfJuNWAr9M4hdk+YhDIg==", + "dev": true + }, + "@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true + }, "@types/estree": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", @@ -4216,6 +4601,88 @@ "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", "dev": true }, + "@vitest/expect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.8.tgz", + "integrity": "sha512-Rv0eabdP/xjAHQGr8cjBm+NnLHNoL268lMDK85w2aAGLFoVKLd8QGnVon5lLtkXQCoYaNL0wg04EGnyKkkKhPA==", + "dev": true, + "requires": { + "@standard-schema/spec": "^1.0.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.0.8", + "@vitest/utils": "4.0.8", + "chai": "^6.2.0", + "tinyrainbow": "^3.0.3" + } + }, + "@vitest/mocker": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.8.tgz", + "integrity": "sha512-9FRM3MZCedXH3+pIh+ME5Up2NBBHDq0wqwhOKkN4VnvCiKbVxddqH9mSGPZeawjd12pCOGnl+lo/ZGHt0/dQSg==", + "dev": true, + "requires": { + "@vitest/spy": "4.0.8", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "dependencies": { + "estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "requires": { + "@types/estree": "^1.0.0" + } + } + } + }, + "@vitest/pretty-format": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.8.tgz", + "integrity": "sha512-qRrjdRkINi9DaZHAimV+8ia9Gq6LeGz2CgIEmMLz3sBDYV53EsnLZbJMR1q84z1HZCMsf7s0orDgZn7ScXsZKg==", + "dev": true, + "requires": { + "tinyrainbow": "^3.0.3" + } + }, + "@vitest/runner": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.8.tgz", + "integrity": "sha512-mdY8Sf1gsM8hKJUQfiPT3pn1n8RF4QBcJYFslgWh41JTfrK1cbqY8whpGCFzBl45LN028g0njLCYm0d7XxSaQQ==", + "dev": true, + "requires": { + "@vitest/utils": "4.0.8", + "pathe": "^2.0.3" + } + }, + "@vitest/snapshot": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.8.tgz", + "integrity": "sha512-Nar9OTU03KGiubrIOFhcfHg8FYaRaNT+bh5VUlNz8stFhCZPNrJvmZkhsr1jtaYvuefYFwK2Hwrq026u4uPWCw==", + "dev": true, + "requires": { + "@vitest/pretty-format": "4.0.8", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + } + }, + "@vitest/spy": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.8.tgz", + "integrity": "sha512-nvGVqUunyCgZH7kmo+Ord4WgZ7lN0sOULYXUOYuHr55dvg9YvMz3izfB189Pgp28w0vWFbEEfNc/c3VTrqrXeA==", + "dev": true + }, + "@vitest/utils": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.8.tgz", + "integrity": "sha512-pdk2phO5NDvEFfUTxcTP8RFYjVj/kfLSPIN5ebP2Mu9kcIMeAQTbknqcFEyBcC4z2pJlJI9aS5UQjcYfhmKAow==", + "dev": true, + "requires": { + "@vitest/pretty-format": "4.0.8", + "tinyrainbow": "^3.0.3" + } + }, "@volar/language-core": { "version": "2.4.11", "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.11.tgz", @@ -4361,6 +4828,12 @@ "sprintf-js": "~1.0.2" } }, + "assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true + }, "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -4440,6 +4913,12 @@ } } }, + "chai": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.0.tgz", + "integrity": "sha512-aUTnJc/JipRzJrNADXVvpVqi6CO0dn3nx4EVPxijri+fj3LUUDyZQOgVeW54Ob3Y1Xh9Iz8f+CgaCl8v0mn9bA==", + "dev": true + }, "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -4521,9 +5000,9 @@ "dev": true }, "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.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, "requires": { "ms": "^2.1.3" @@ -4561,6 +5040,12 @@ "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", "dev": true }, + "es-module-lexer": { + "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 + }, "esbuild": { "version": "0.25.1", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.1.tgz", @@ -4604,6 +5089,12 @@ "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", "dev": true }, + "expect-type": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.2.tgz", + "integrity": "sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==", + "dev": true + }, "fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -4611,9 +5102,9 @@ "dev": true }, "fdir": { - "version": "6.4.4", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", - "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, "requires": {} }, @@ -4834,12 +5325,12 @@ "dev": true }, "magic-string": { - "version": "0.30.17", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", - "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", "dev": true, "requires": { - "@jridgewell/sourcemap-codec": "^1.5.0" + "@jridgewell/sourcemap-codec": "^1.5.5" } }, "mdn-data": { @@ -4958,9 +5449,9 @@ "dev": true }, "picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true }, "pkg-types": { @@ -4985,6 +5476,12 @@ "source-map-js": "^1.2.1" } }, + "prettier": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", + "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", + "dev": true + }, "punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -5019,6 +5516,7 @@ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.40.1.tgz", "integrity": "sha512-C5VvvgCCyfyotVITIAv+4efVytl5F7wt+/I2i9q9GZcEXW9BP52YYOXC58igUi+LFZVHukErIIqQSWwv/M3WRw==", "dev": true, + "peer": true, "requires": { "@rollup/rollup-android-arm-eabi": "4.40.1", "@rollup/rollup-android-arm64": "4.40.1", @@ -5072,6 +5570,12 @@ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true }, + "siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true + }, "signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", @@ -5095,6 +5599,18 @@ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "dev": true }, + "stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true + }, + "std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true + }, "string-argv": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", @@ -5173,16 +5689,34 @@ "minimatch": "^9.0.4" } }, + "tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true + }, + "tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true + }, "tinyglobby": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz", - "integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==", + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", "dev": true, "requires": { - "fdir": "^6.4.4", - "picomatch": "^4.0.2" + "fdir": "^6.5.0", + "picomatch": "^4.0.3" } }, + "tinyrainbow": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz", + "integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==", + "dev": true + }, "tunnel": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", @@ -5190,9 +5724,9 @@ "dev": true }, "typescript": { - "version": "5.7.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", - "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "peer": true }, @@ -5278,6 +5812,7 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", "dev": true, + "peer": true, "requires": { "esbuild": "^0.25.0", "fdir": "^6.4.4", @@ -5305,6 +5840,34 @@ "magic-string": "^0.30.17" } }, + "vitest": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.8.tgz", + "integrity": "sha512-urzu3NCEV0Qa0Y2PwvBtRgmNtxhj5t5ULw7cuKhIHh3OrkKTLlut0lnBOv9qe5OvbkMH2g38G7KPDCTpIytBVg==", + "dev": true, + "requires": { + "@vitest/expect": "4.0.8", + "@vitest/mocker": "4.0.8", + "@vitest/pretty-format": "4.0.8", + "@vitest/runner": "4.0.8", + "@vitest/snapshot": "4.0.8", + "@vitest/spy": "4.0.8", + "@vitest/utils": "4.0.8", + "debug": "^4.4.3", + "es-module-lexer": "^1.7.0", + "expect-type": "^1.2.2", + "magic-string": "^0.30.21", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^3.10.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.0.3", + "vite": "^6.0.0 || ^7.0.0", + "why-is-node-running": "^2.3.0" + } + }, "vscode-uri": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", @@ -5326,6 +5889,16 @@ "isexe": "^2.0.0" } }, + "why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "requires": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + } + }, "wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", diff --git a/package.json b/package.json index f24b3404..f1ff7916 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,8 @@ "node": ">=18.0.0" }, "scripts": { - "test": "uvu", + "test": "vitest", + "check": "tsc --noEmit", "build": "vite build" }, "keywords": [ @@ -50,9 +51,13 @@ }, "devDependencies": { "@codecov/vite-plugin": "^1.9.0", + "@types/css-tree": "^2.3.11", "c8": "^10.1.3", + "prettier": "^3.6.2", + "typescript": "^5.9.3", "uvu": "^0.5.6", "vite": "^6.3.4", - "vite-plugin-dts": "^4.5.0" + "vite-plugin-dts": "^4.5.0", + "vitest": "^4.0.8" } -} \ No newline at end of file +} diff --git a/src/aggregate-collection.js b/src/aggregate-collection.js deleted file mode 100644 index 190cae8a..00000000 --- a/src/aggregate-collection.js +++ /dev/null @@ -1,99 +0,0 @@ -/** - * Find the mode (most occurring value) in an array of Numbers - * Takes the mean/average of multiple values if multiple values occur the same amount of times. - * - * @see https://github.com/angus-c/just/blob/684af9ca0c7808bc78543ec89379b1fdfce502b1/packages/array-mode/index.js - * @param {Array} arr - Array to find the mode value for - * @returns {Number} mode - The `mode` value of `arr` - */ -function Mode(arr) { - let frequencies = new Map() - let maxOccurrences = -1 - let maxOccurenceCount = 0 - let sum = 0 - let len = arr.length - - for (let i = 0; i < len; i++) { - let element = arr[i] - let updatedCount = (frequencies.get(element) || 0) + 1 - frequencies.set(element, updatedCount) - - if (updatedCount > maxOccurrences) { - maxOccurrences = updatedCount - maxOccurenceCount = 0 - sum = 0 - } - - if (updatedCount >= maxOccurrences) { - maxOccurenceCount++ - sum += element - } - } - - return sum / maxOccurenceCount -} - -class AggregateCollection { - constructor() { - /** @type number[] */ - this._items = [] - this._sum = 0 - } - - /** - * Add a new Integer at the end of this AggregateCollection - * @param {number} item - The item to add - */ - push(item) { - this._items.push(item) - this._sum += item - } - - size() { - return this._items.length - } - - aggregate() { - let len = this._items.length - - if (len === 0) { - return { - min: 0, - max: 0, - mean: 0, - mode: 0, - range: 0, - sum: 0, - } - } - - // TODO: can we avoid this sort()? It's slow - /** @type Number[] */ - let sorted = this._items.slice().sort((a, b) => a - b) - let min = sorted[0] - let max = sorted[len - 1] - - let mode = Mode(sorted) - let sum = this._sum - - return { - min, - max, - mean: sum / len, - mode, - range: max - min, - sum: sum, - } - } - - /** - * @returns {number[]} All _items in this collection - */ - toArray() { - return this._items - } -} - -export { - AggregateCollection -} \ No newline at end of file diff --git a/src/aggregate-collection.test.js b/src/aggregate-collection.test.js deleted file mode 100644 index 3cfdce8e..00000000 --- a/src/aggregate-collection.test.js +++ /dev/null @@ -1,45 +0,0 @@ -import { suite } from 'uvu'; -import * as assert from 'uvu/assert'; -import { AggregateCollection } from './aggregate-collection.js' - -const CollectionSuite = suite('AggregateCollection') - -CollectionSuite('aggregates correctly', () => { - const fixture = new AggregateCollection() - fixture.push(1) - fixture.push(2) - fixture.push(25) - fixture.push(3) - fixture.push(4) - fixture.push(4) - const actual = fixture.aggregate() - const expected = { - max: 25, - min: 1, - range: 24, - mean: 39 / 6, - mode: 4, - sum: 39, - } - - assert.equal(actual, expected) - assert.equal(fixture.toArray(), [1, 2, 25, 3, 4, 4]) -}) - -CollectionSuite('handles collections without values', () => { - const fixture = new AggregateCollection() - const aggregate = fixture.aggregate() - const items = fixture.toArray() - - assert.equal(aggregate, { - max: 0, - min: 0, - range: 0, - mean: 0, - mode: 0, - sum: 0, - }) - assert.equal(items, []) -}) - -CollectionSuite.run() \ No newline at end of file diff --git a/src/aggregate-collection.test.ts b/src/aggregate-collection.test.ts new file mode 100644 index 00000000..cedc8da5 --- /dev/null +++ b/src/aggregate-collection.test.ts @@ -0,0 +1,40 @@ +import { test, expect } from 'vitest' +import { AggregateCollection } from './aggregate-collection.js' + +test('aggregates correctly', () => { + const fixture = new AggregateCollection() + fixture.push(1) + fixture.push(2) + fixture.push(25) + fixture.push(3) + fixture.push(4) + fixture.push(4) + const actual = fixture.aggregate() + const expected = { + max: 25, + min: 1, + range: 24, + mean: 39 / 6, + mode: 4, + sum: 39, + } + + expect(actual).toEqual(expected) + expect(fixture.toArray()).toEqual([1, 2, 25, 3, 4, 4]) +}) + +test('handles collections without values', () => { + const fixture = new AggregateCollection() + const aggregate = fixture.aggregate() + const items = fixture.toArray() + + expect(aggregate).toEqual({ + max: 0, + min: 0, + range: 0, + mean: 0, + mode: 0, + sum: 0, + }) + expect(items).toEqual([]) +}) diff --git a/src/aggregate-collection.ts b/src/aggregate-collection.ts new file mode 100644 index 00000000..be303a03 --- /dev/null +++ b/src/aggregate-collection.ts @@ -0,0 +1,94 @@ +/** + * Find the mode (most occurring value) in an array of Numbers + * Takes the mean/average of multiple values if multiple values occur the same amount of times. + * + * @see https://github.com/angus-c/just/blob/684af9ca0c7808bc78543ec89379b1fdfce502b1/packages/array-mode/index.js + * @param arr - Array to find the mode value for + * @returns mode - The `mode` value of `arr` + */ +function Mode(arr: unknown[]): number { + let frequencies = new Map() + let maxOccurrences = -1 + let maxOccurenceCount = 0 + let sum = 0 + let len = arr.length + + for (let i = 0; i < len; i++) { + let element = arr[i] + let updatedCount = (frequencies.get(element) || 0) + 1 + frequencies.set(element, updatedCount) + + if (updatedCount > maxOccurrences) { + maxOccurrences = updatedCount + maxOccurenceCount = 0 + sum = 0 + } + + if (updatedCount >= maxOccurrences) { + maxOccurenceCount++ + // @ts-expect-error TODO: fix this + sum += element + } + } + + return sum / maxOccurenceCount +} + +export class AggregateCollection { + #items: number[] + #sum: number + + constructor() { + this.#items = [] + this.#sum = 0 + } + + /** + * Add a new Integer at the end of this AggregateCollection + * @param item - The item to add + */ + push(item: number) { + this.#items.push(item) + this.#sum += item + } + + size() { + return this.#items.length + } + + aggregate() { + let len = this.#items.length + + if (len === 0) { + return { + min: 0, + max: 0, + mean: 0, + mode: 0, + range: 0, + sum: 0, + } + } + + // TODO: can we avoid this sort()? It's slow + let sorted = this.#items.slice().sort((a, b) => a - b) + let min = sorted[0]! + let max = sorted[len - 1]! + + let mode = Mode(sorted) + let sum = this.#sum + + return { + min, + max, + mean: sum / len, + mode, + range: max - min, + sum: sum, + } + } + + toArray() { + return this.#items + } +} diff --git a/src/atrules/atrules.js b/src/atrules/atrules.js deleted file mode 100644 index 45d0e876..00000000 --- a/src/atrules/atrules.js +++ /dev/null @@ -1,94 +0,0 @@ -import { strEquals, startsWith, endsWith } from '../string-utils.js' -import walk from 'css-tree/walker' -import { - Identifier, - MediaQuery, - Declaration, -} from '../css-tree-node-types.js' - -/** - * Check whether node.property === property and node.value === value, - * but case-insensitive and fast. - * @param {import('css-tree').Declaration} node - * @param {string} property - The CSS property to compare with (case-insensitive) - * @param {string} value - The identifier/keyword value to compare with - * @returns true if declaratioNode is the given property: value, false otherwise - */ -function isPropertyValue(node, property, value) { - let firstChild = node.value.children.first - return strEquals(property, node.property) - && firstChild.type === Identifier - && strEquals(value, firstChild.name) -} - -/** - * Check if an @supports atRule is a browserhack - * @param {import('css-tree').AtrulePrelude} prelude - * @returns true if the atrule is a browserhack - */ -export function isSupportsBrowserhack(prelude) { - let returnValue = false - - walk(prelude, function (node) { - if (node.type === Declaration) { - if ( - isPropertyValue(node, '-webkit-appearance', 'none') - || isPropertyValue(node, '-moz-appearance', 'meterbar') - ) { - returnValue = true - return this.break - } - } - }) - - return returnValue -} - -/** - * Check if a @media atRule is a browserhack - * @param {import('css-tree').AtrulePrelude} prelude - * @returns true if the atrule is a browserhack - */ -export function isMediaBrowserhack(prelude) { - let returnValue = false - - walk(prelude, function (node) { - let name = node.name - let value = node.value - - if (node.type === MediaQuery && node.mediaType !== null) { - // Note: CSSTree adds a trailing space to \\9 - if (startsWith('\\0', node.mediaType) || endsWith('\\9 ', node.mediaType)) { - returnValue = true - return this.break - } - } else if (node.type === 'Feature' && node.kind === 'media') { - if (value && value.unit && value.unit === '\\0') { - returnValue = true - return this.break - } - else if (strEquals('-moz-images-in-menus', name) - || strEquals('min--moz-device-pixel-ratio', name) - || strEquals('-ms-high-contrast', name) - ) { - returnValue = true - return this.break - } - else if (strEquals('min-resolution', name) - && value && strEquals('.001', value.value) - && strEquals('dpcm', value.unit) - ) { - returnValue = true - return this.break - } - else if (strEquals('-webkit-min-device-pixel-ratio', name)) { - if (value && value.value && (strEquals('0', value.value) || strEquals('10000', value.value))) { - returnValue = true - return this.break - } - } - } - }) - - return returnValue -} \ No newline at end of file diff --git a/src/atrules/atrules.test.js b/src/atrules/atrules.test.js deleted file mode 100644 index 86e4f5a9..00000000 --- a/src/atrules/atrules.test.js +++ /dev/null @@ -1,1003 +0,0 @@ -import { suite } from 'uvu' -import * as assert from 'uvu/assert' -import { analyze } from '../index.js' - -const AtRules = suite('at-rules') - -AtRules('counts total atrules', () => { - let css = ` - @import url(test.css); - - @layer a, b, c; - - @media all { - a {} - } - - @supports (display: grid) { - a {} - } - ` - let actual = analyze(css).atrules - assert.is(actual.total, 4) - assert.is(actual.totalUnique, 4) - assert.equal(actual.unique, { - 'import': 1, - 'layer': 1, - 'media': 1, - 'supports': 1, - }) - assert.equal(actual.uniquenessRatio, 4 / 4) -}) - -AtRules('calculates complexity', () => { - let css = ` - /* 1 */ - @import url(test.css); - - /* 1 */ - @layer a, b, c; - /* 2 */ - @layer {} - - /* 1 */ - @supports (display: grid) {} - /* 2 */ - @supports (-webkit-appearance: none) {} - - /* 1 */ - @media all {} - /* 2 */ - @media (min-resolution: .001dpcm) {} - - /* 1 */ - @font-face { - font-family: Arial; - } - - /* 1 */ - @keyframes one {} - /* 2 */ - @-webkit-keyframes one {} - ` - let actual = analyze(css).atrules.complexity - assert.equal(actual, { - min: 1, - max: 2, - mean: 14 / 10, - mode: 1, - range: 1, - sum: 14, - }) -}) - -AtRules('finds @layer', () => { - // Fixture is pretty much a straight copy from all code examples from - // https://css-tricks.com/css-cascade-layers/ - const fixture = ` - @import url('test.css') layer; - @import url('test.css') layer(); - @import url('test.css') layer(test); - @import url('test.css') layer(test.abc); - - /* establish a layer order up-front, from lowest to highest priority */ - @layer reset, defaults, patterns, components, utilities, overrides; - - /* add styles to layers */ - @layer utilities { - /* high layer priority, despite low specificity */ - [data-color='brand'] { - color: var(--brand, rebeccapurple); - } - } - - @layer defaults { - /* higher specificity, but lower layer priority */ - a:any-link { color: maroon; } - } - - /* un-layered styles have the highest priority */ - a { - color: mediumvioletred; - } - - @layer layer-1 { a { color: red; } } - @layer layer-2 { a { color: orange; } } - @layer layer-3 { - @layer sub-layer-1 { a { color: yellow; } } - @layer sub-layer-2 { a { color: green; } } - /* un-nested */ a { color: blue; } - @layer { - anonymous {} - } - } - /* un-layered */ a { color: indigo; } - - @layer reset, defaults, framework; - @layer components, defaults, framework, reset, utilities; - - @layer one { - /* sorting the sub-layers */ - @layer two, three; - - /* styles ... */ - @layer three { /* styles ... */ } - @layer two { /* styles ... */ } - } - - /* sorting nested layers directly */ - @layer one.two, one.three; - - /* adding to nested layers directly */ - @layer one.three { /* ... */ } - @layer one.two { /* ... */ } - - @layer reset.type, default.type, reset.media, default.media; - - @layer - reset, - default, - themes, - patterns, - layouts, - components, - utilities; - - @layer components { - @layer defaults, structures, themes, utilities; - } - - /* Anonymous layer (no prelude/layer name) */ - @layer { - test { - color: green; - } - } - ` - const actual = analyze(fixture).atrules.layer - const expected = { - total: 50, - totalUnique: 28, - unique: { - "test": 1, - "test.abc": 1, - "defaults": 5, - "layer-1": 1, - "layer-2": 1, - "layer-3": 1, - "sub-layer-1": 1, - "sub-layer-2": 1, - "reset": 4, - "framework": 2, - "components": 4, - "utilities": 5, - "one": 1, - "two": 2, - "three": 2, - "one.two": 2, - "one.three": 2, - "reset.type": 1, - "default.type": 1, - "reset.media": 1, - "default.media": 1, - "default": 1, - "themes": 2, - "patterns": 2, - "layouts": 1, - "structures": 1, - "overrides": 1, - "": 2, - }, - uniquenessRatio: 28 / 50 - } - - assert.equal(actual, expected) -}) - -AtRules('finds @font-face', () => { - const fixture = ` - @font-face { - font-family: Arial; - src: url("https://url-to-arial.woff"); - } - - @font-face { - font-display: swap; - font-family: Test; - font-stretch: condensed; - font-style: italic; - font-weight: 700; - font-variant: no-common-ligatures proportional-nums; - font-feature-settings: "liga" 0; - font-variation-settings: "xhgt" 0.7; - src: local("Input Mono"); - unicode-range: U+0025-00FF; - } - - @font-face { - font-family: 'Input Mono'; - src: local('Input Mono') url("https://url-to-input-mono.woff"); - } - - @font-face { - font-family: MyHelvetica; - src: local("Helvetica Neue Bold"), local("HelveticaNeue-Bold"), url(MgOpenModernaBold.ttf); - font-weight: bold; - } - - /* Duplicate @font-face in Media Query */ - @media (min-width: 1000px) { - @font-face { - font-family: 'Input Mono'; - src: local('Input Mono') url("https://url-to-input-mono.woff"); - } - }` - const actual = analyze(fixture).atrules.fontface - const expected = { - total: 5, - totalUnique: 5, - unique: [ - { - "font-family": "Arial", - "src": "url(\"https://url-to-arial.woff\")" - }, - { - "font-display": `swap`, - "font-family": `Test`, - "font-stretch": `condensed`, - "font-style": `italic`, - "font-weight": `700`, - "font-variant": `no-common-ligatures proportional-nums`, - "font-feature-settings": `"liga" 0`, - "font-variation-settings": `"xhgt" 0.7`, - "src": `local("Input Mono")`, - "unicode-range": `U+0025-00FF`, - }, - { - "font-family": "'Input Mono'", - "src": "local('Input Mono') url(\"https://url-to-input-mono.woff\")" - }, - { - 'font-family': 'MyHelvetica', - 'src': 'local("Helvetica Neue Bold"), local("HelveticaNeue-Bold"), url(MgOpenModernaBold.ttf)', - 'font-weight': 'bold', - }, - { - "font-family": "'Input Mono'", - "src": "local('Input Mono') url(\"https://url-to-input-mono.woff\")" - } - ], - uniquenessRatio: 1 - } - - assert.equal(actual, expected) -}) - -AtRules('finds @font-face', () => { - const fixture = ` - @font-face { - font-family: Arial; - src: url("https://url-to-arial.woff"); - } - - @font-face { - font-display: swap; - font-family: Test; - font-stretch: condensed; - font-style: italic; - font-weight: 700; - font-variant: no-common-ligatures proportional-nums; - font-feature-settings: "liga" 0; - font-variation-settings: "xhgt" 0.7; - src: local("Input Mono"); - unicode-range: U+0025-00FF; - } - - @font-face { - font-family: 'Input Mono'; - src: local('Input Mono') url("https://url-to-input-mono.woff"); - } - - @font-face { - font-family: MyHelvetica; - src: local("Helvetica Neue Bold"), local("HelveticaNeue-Bold"), url(MgOpenModernaBold.ttf); - font-weight: bold; - } - - /* Duplicate @font-face in Media Query */ - @media (min-width: 1000px) { - @font-face { - font-family: 'Input Mono'; - src: local('Input Mono') url("https://url-to-input-mono.woff"); - } - }` - const actual = analyze(fixture, { - useLocations: true - }).atrules.fontface.uniqueWithLocations - const expected = { - 5: [{ - line: 2, - column: 5, - offset: 5, - length: 89, - }], - 100: [{ - line: 7, - column: 5, - offset: 100, - length: 357, - }], - 463: [{ - line: 20, - column: 5, - offset: 463, - length: 121, - }], - 590: [{ - line: 25, - column: 5, - offset: 590, - length: 173, - }], - 850: [{ - line: 33, - column: 7, - offset: 850, - length: 127, - }], - } - - assert.equal(actual, expected) -}) - -AtRules('handles @font-face encoding issues (GH-307)', () => { - // Actual CSS once found in a