From 71644ef236650b50eb4f9b1ee4276b787220e83e Mon Sep 17 00:00:00 2001 From: Gordon Smith Date: Wed, 25 Mar 2026 12:10:35 +0000 Subject: [PATCH] fix: enhance WIT validation and binding generation with source path support - Updated `generateBindingsFromWasm` to accept an optional source path for improved context during binding generation. - Modified `validateWitSyntaxDetailedFromWasm` to include source path for detailed validation. - Introduced recursive file collection for WIT files in `wasmUtils.ts` to support local imports. - Added tests for local import resolution and validation scenarios. - Updated WIT interface definitions to reflect new parameters for validation and binding functions. - Bumped dependencies in `Cargo.lock` and `Cargo.toml` for compatibility with new features. Fixes Packages split across multiple files fail Fixes #121 Signed-off-by: Gordon Smith --- package-lock.json | 461 +++++++++++++-------------- package.json | 29 +- src/extension.ts | 7 +- src/validator.ts | 2 +- src/wasmUtils.ts | 160 +++++++++- tests/bindings-generation.test.ts | 8 +- tests/grammar/issue-121/wit/a.wit | 11 + tests/grammar/issue-121/wit/b.wit | 3 + tests/import-resolution.test.ts | 82 +++++ tests/wasmUtils.test.ts | 45 +-- wit-bindgen-wasm/Cargo.lock | 24 +- wit-bindgen-wasm/Cargo.toml | 2 +- wit-bindgen-wasm/src/lib.rs | 455 +++++++++++++++++++++----- wit-bindgen-wasm/wit/wit-bindgen.wit | 20 +- 14 files changed, 932 insertions(+), 377 deletions(-) create mode 100644 tests/grammar/issue-121/wit/a.wit create mode 100644 tests/grammar/issue-121/wit/b.wit create mode 100644 tests/import-resolution.test.ts diff --git a/package-lock.json b/package-lock.json index 1134b47..e88ee23 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,27 +9,27 @@ "version": "0.3.30", "license": "Apache-2.0 WITH LLVM-exception", "devDependencies": { - "@bytecodealliance/jco": "1.17.1", + "@bytecodealliance/jco": "1.17.5", "@eslint/css": "1.0.0", "@eslint/js": "10.0.1", "@types/node": "25.5.0", "@types/vscode": "1.99.0", - "@typescript-eslint/parser": "8.57.1", - "@vitest/coverage-v8": "4.1.0", - "@vitest/ui": "4.1.0", + "@typescript-eslint/parser": "8.57.2", + "@vitest/coverage-v8": "4.1.1", + "@vitest/ui": "4.1.1", "@vscode/vsce": "3.7.1", "esbuild": "0.27.4", "esbuild-node-externals": "1.20.1", - "eslint": "10.0.3", + "eslint": "10.1.0", "eslint-plugin-prettier": "5.5.5", "globals": "17.4.0", "npm-run-all": "4.1.5", - "ovsx": "0.10.9", + "ovsx": "0.10.10", "prettier": "3.8.1", "rimraf": "6.1.3", - "typescript": "5.9.3", - "typescript-eslint": "8.57.1", - "vitest": "4.1.0", + "typescript": "6.0.2", + "typescript-eslint": "8.57.2", + "vitest": "4.1.1", "vscode-tmgrammar-test": "0.1.3", "wit-bindgen-wasm": "file:wit-bindgen-wasm/pkg" }, @@ -324,9 +324,9 @@ } }, "node_modules/@bytecodealliance/jco": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/@bytecodealliance/jco/-/jco-1.17.1.tgz", - "integrity": "sha512-MtKnukB58U2j2bgLrMvead5MNcEWy4nvwPYLwxZHfclcfksQaDyaQL/NcJxknSYXUrGRHpLWjVNaXMjVAYLhDQ==", + "version": "1.17.5", + "resolved": "https://registry.npmjs.org/@bytecodealliance/jco/-/jco-1.17.5.tgz", + "integrity": "sha512-fBclNYSg7vcjQYjrwX2+GYf1CAigyPgtv7WivxvVvH92gmORWQFuFA1pS1flijUp+6hV34eztTAsg6QKRPCMGQ==", "dev": true, "license": "(Apache-2.0 WITH LLVM-exception)", "dependencies": { @@ -1791,16 +1791,6 @@ "node": ">=20.0.0" } }, - "node_modules/@oxc-project/runtime": { - "version": "0.115.0", - "resolved": "https://registry.npmjs.org/@oxc-project/runtime/-/runtime-0.115.0.tgz", - "integrity": "sha512-Rg8Wlt5dCbXhQnsXPrkOjL1DTSvXLgb2R/KYfnf1/K+R0k6UMLEmbQXPM+kwrWqSmWA2t0B1EtHy2/3zikQpvQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, "node_modules/@oxc-project/types": { "version": "0.76.0", "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.76.0.tgz", @@ -1832,9 +1822,9 @@ "license": "MIT" }, "node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.0-rc.9", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.9.tgz", - "integrity": "sha512-lcJL0bN5hpgJfSIz/8PIf02irmyL43P+j1pTCfbD1DbLkmGRuFIA4DD3B3ZOvGqG0XiVvRznbKtN0COQVaKUTg==", + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.11.tgz", + "integrity": "sha512-SJ+/g+xNnOh6NqYxD0V3uVN4W3VfnrGsC9/hoglicgTNfABFG9JjISvkkU0dNY84MNHLWyOgxP9v9Y9pX4S7+A==", "cpu": [ "arm64" ], @@ -1849,9 +1839,9 @@ } }, "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.0-rc.9", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.9.tgz", - "integrity": "sha512-J7Zk3kLYFsLtuH6U+F4pS2sYVzac0qkjcO5QxHS7OS7yZu2LRs+IXo+uvJ/mvpyUljDJ3LROZPoQfgBIpCMhdQ==", + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.11.tgz", + "integrity": "sha512-7WQgR8SfOPwmDZGFkThUvsmd/nwAWv91oCO4I5LS7RKrssPZmOt7jONN0cW17ydGC1n/+puol1IpoieKqQidmg==", "cpu": [ "arm64" ], @@ -1866,9 +1856,9 @@ } }, "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.0-rc.9", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.9.tgz", - "integrity": "sha512-iwtmmghy8nhfRGeNAIltcNXzD0QMNaaA5U/NyZc1Ia4bxrzFByNMDoppoC+hl7cDiUq5/1CnFthpT9n+UtfFyg==", + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.11.tgz", + "integrity": "sha512-39Ks6UvIHq4rEogIfQBoBRusj0Q0nPVWIvqmwBLaT6aqQGIakHdESBVOPRRLacy4WwUPIx4ZKzfZ9PMW+IeyUQ==", "cpu": [ "x64" ], @@ -1883,9 +1873,9 @@ } }, "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.0-rc.9", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.9.tgz", - "integrity": "sha512-DLFYI78SCiZr5VvdEplsVC2Vx53lnA4/Ga5C65iyldMVaErr86aiqCoNBLl92PXPfDtUYjUh+xFFor40ueNs4Q==", + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.11.tgz", + "integrity": "sha512-jfsm0ZHfhiqrvWjJAmzsqiIFPz5e7mAoCOPBNTcNgkiid/LaFKiq92+0ojH+nmJmKYkre4t71BWXUZDNp7vsag==", "cpu": [ "x64" ], @@ -1900,9 +1890,9 @@ } }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.0-rc.9", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.9.tgz", - "integrity": "sha512-CsjTmTwd0Hri6iTw/DRMK7kOZ7FwAkrO4h8YWKoX/kcj833e4coqo2wzIFywtch/8Eb5enQ/lwLM7w6JX1W5RQ==", + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.11.tgz", + "integrity": "sha512-zjQaUtSyq1nVe3nxmlSCuR96T1LPlpvmJ0SZy0WJFEsV4kFbXcq2u68L4E6O0XeFj4aex9bEauqjW8UQBeAvfQ==", "cpu": [ "arm" ], @@ -1917,9 +1907,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.0-rc.9", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.9.tgz", - "integrity": "sha512-2x9O2JbSPxpxMDhP9Z74mahAStibTlrBMW0520+epJH5sac7/LwZW5Bmg/E6CXuEF53JJFW509uP+lSedaUNxg==", + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.11.tgz", + "integrity": "sha512-WMW1yE6IOnehTcFE9eipFkm3XN63zypWlrJQ2iF7NrQ9b2LDRjumFoOGJE8RJJTJCTBAdmLMnJ8uVitACUUo1Q==", "cpu": [ "arm64" ], @@ -1934,9 +1924,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.0-rc.9", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.9.tgz", - "integrity": "sha512-JA1QRW31ogheAIRhIg9tjMfsYbglXXYGNPLdPEYrwFxdbkQCAzvpSCSHCDWNl4hTtrol8WeboCSEpjdZK8qrCg==", + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.11.tgz", + "integrity": "sha512-jfndI9tsfm4APzjNt6QdBkYwre5lRPUgHeDHoI7ydKUuJvz3lZeCfMsI56BZj+7BYqiKsJm7cfd/6KYV7ubrBg==", "cpu": [ "arm64" ], @@ -1951,9 +1941,9 @@ } }, "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.0.0-rc.9", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.9.tgz", - "integrity": "sha512-aOKU9dJheda8Kj8Y3w9gnt9QFOO+qKPAl8SWd7JPHP+Cu0EuDAE5wokQubLzIDQWg2myXq2XhTpOVS07qqvT+w==", + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.11.tgz", + "integrity": "sha512-ZlFgw46NOAGMgcdvdYwAGu2Q+SLFA9LzbJLW+iyMOJyhj5wk6P3KEE9Gct4xWwSzFoPI7JCdYmYMzVtlgQ+zfw==", "cpu": [ "ppc64" ], @@ -1968,9 +1958,9 @@ } }, "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.0.0-rc.9", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.9.tgz", - "integrity": "sha512-OalO94fqj7IWRn3VdXWty75jC5dk4C197AWEuMhIpvVv2lw9fiPhud0+bW2ctCxb3YoBZor71QHbY+9/WToadA==", + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.11.tgz", + "integrity": "sha512-hIOYmuT6ofM4K04XAZd3OzMySEO4K0/nc9+jmNcxNAxRi6c5UWpqfw3KMFV4MVFWL+jQsSh+bGw2VqmaPMTLyw==", "cpu": [ "s390x" ], @@ -1985,9 +1975,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.0-rc.9", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.9.tgz", - "integrity": "sha512-cVEl1vZtBsBZna3YMjGXNvnYYrOJ7RzuWvZU0ffvJUexWkukMaDuGhUXn0rjnV0ptzGVkvc+vW9Yqy6h8YX4pg==", + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.11.tgz", + "integrity": "sha512-qXBQQO9OvkjjQPLdUVr7Nr2t3QTZI7s4KZtfw7HzBgjbmAPSFwSv4rmET9lLSgq3rH/ndA3ngv3Qb8l2njoPNA==", "cpu": [ "x64" ], @@ -2002,9 +1992,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.0-rc.9", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.9.tgz", - "integrity": "sha512-UzYnKCIIc4heAKgI4PZ3dfBGUZefGCJ1TPDuLHoCzgrMYPb5Rv6TLFuYtyM4rWyHM7hymNdsg5ik2C+UD9VDbA==", + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.11.tgz", + "integrity": "sha512-/tpFfoSTzUkH9LPY+cYbqZBDyyX62w5fICq9qzsHLL8uTI6BHip3Q9Uzft0wylk/i8OOwKik8OxW+QAhDmzwmg==", "cpu": [ "x64" ], @@ -2019,9 +2009,9 @@ } }, "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.0-rc.9", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.9.tgz", - "integrity": "sha512-+6zoiF+RRyf5cdlFQP7nm58mq7+/2PFaY2DNQeD4B87N36JzfF/l9mdBkkmTvSYcYPE8tMh/o3cRlsx1ldLfog==", + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.11.tgz", + "integrity": "sha512-mcp3Rio2w72IvdZG0oQ4bM2c2oumtwHfUfKncUM6zGgz0KgPz4YmDPQfnXEiY5t3+KD/i8HG2rOB/LxdmieK2g==", "cpu": [ "arm64" ], @@ -2036,9 +2026,9 @@ } }, "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.0-rc.9", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.9.tgz", - "integrity": "sha512-rgFN6sA/dyebil3YTlL2evvi/M+ivhfnyxec7AccTpRPccno/rPoNlqybEZQBkcbZu8Hy+eqNJCqfBR8P7Pg8g==", + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.11.tgz", + "integrity": "sha512-LXk5Hii1Ph9asuGRjBuz8TUxdc1lWzB7nyfdoRgI0WGPZKmCxvlKk8KfYysqtr4MfGElu/f/pEQRh8fcEgkrWw==", "cpu": [ "wasm32" ], @@ -2070,9 +2060,9 @@ } }, "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.0-rc.9", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.9.tgz", - "integrity": "sha512-lHVNUG/8nlF1IQk1C0Ci574qKYyty2goMiPlRqkC5R+3LkXDkL5Dhx8ytbxq35m+pkHVIvIxviD+TWLdfeuadA==", + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.11.tgz", + "integrity": "sha512-dDwf5otnx0XgRY1yqxOC4ITizcdzS/8cQ3goOWv3jFAo4F+xQYni+hnMuO6+LssHHdJW7+OCVL3CoU4ycnh35Q==", "cpu": [ "arm64" ], @@ -2087,9 +2077,9 @@ } }, "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.0-rc.9", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.9.tgz", - "integrity": "sha512-G0oA4+w1iY5AGi5HcDTxWsoxF509hrFIPB2rduV5aDqS9FtDg1CAfa7V34qImbjfhIcA8C+RekocJZA96EarwQ==", + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.11.tgz", + "integrity": "sha512-LN4/skhSggybX71ews7dAj6r2geaMJfm3kMbK2KhFMg9B10AZXnKoLCVVgzhMHL0S+aKtr4p8QbAW8k+w95bAA==", "cpu": [ "x64" ], @@ -2104,9 +2094,9 @@ } }, "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-rc.9", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.9.tgz", - "integrity": "sha512-w6oiRWgEBl04QkFZgmW+jnU1EC9b57Oihi2ot3HNWIQRqgHp5PnYDia5iZ5FF7rpa4EQdiqMDXjlqKGXBhsoXw==", + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.11.tgz", + "integrity": "sha512-xQO9vbwBecJRv9EUcQ/y0dzSTJgA7Q6UVN7xp6B81+tBGSLVAK03yJ9NkJaUA7JFD91kbjxRSC/mDnmvXzbHoQ==", "dev": true, "license": "MIT" }, @@ -2470,17 +2460,17 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.57.1.tgz", - "integrity": "sha512-Gn3aqnvNl4NGc6x3/Bqk1AOn0thyTU9bqDRhiRnUWezgvr2OnhYCWCgC8zXXRVqBsIL1pSDt7T9nJUe0oM0kDQ==", + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.57.2.tgz", + "integrity": "sha512-NZZgp0Fm2IkD+La5PR81sd+g+8oS6JwJje+aRWsDocxHkjyRw0J5L5ZTlN3LI1LlOcGL7ph3eaIUmTXMIjLk0w==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.57.1", - "@typescript-eslint/type-utils": "8.57.1", - "@typescript-eslint/utils": "8.57.1", - "@typescript-eslint/visitor-keys": "8.57.1", + "@typescript-eslint/scope-manager": "8.57.2", + "@typescript-eslint/type-utils": "8.57.2", + "@typescript-eslint/utils": "8.57.2", + "@typescript-eslint/visitor-keys": "8.57.2", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.4.0" @@ -2493,7 +2483,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.57.1", + "@typescript-eslint/parser": "^8.57.2", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } @@ -2509,16 +2499,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.57.1.tgz", - "integrity": "sha512-k4eNDan0EIMTT/dUKc/g+rsJ6wcHYhNPdY19VoX/EOtaAG8DLtKCykhrUnuHPYvinn5jhAPgD2Qw9hXBwrahsw==", + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.57.2.tgz", + "integrity": "sha512-30ScMRHIAD33JJQkgfGW1t8CURZtjc2JpTrq5n2HFhOefbAhb7ucc7xJwdWcrEtqUIYJ73Nybpsggii6GtAHjA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.57.1", - "@typescript-eslint/types": "8.57.1", - "@typescript-eslint/typescript-estree": "8.57.1", - "@typescript-eslint/visitor-keys": "8.57.1", + "@typescript-eslint/scope-manager": "8.57.2", + "@typescript-eslint/types": "8.57.2", + "@typescript-eslint/typescript-estree": "8.57.2", + "@typescript-eslint/visitor-keys": "8.57.2", "debug": "^4.4.3" }, "engines": { @@ -2534,14 +2524,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.57.1.tgz", - "integrity": "sha512-vx1F37BRO1OftsYlmG9xay1TqnjNVlqALymwWVuYTdo18XuKxtBpCj1QlzNIEHlvlB27osvXFWptYiEWsVdYsg==", + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.57.2.tgz", + "integrity": "sha512-FuH0wipFywXRTHf+bTTjNyuNQQsQC3qh/dYzaM4I4W0jrCqjCVuUh99+xd9KamUfmCGPvbO8NDngo/vsnNVqgw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.57.1", - "@typescript-eslint/types": "^8.57.1", + "@typescript-eslint/tsconfig-utils": "^8.57.2", + "@typescript-eslint/types": "^8.57.2", "debug": "^4.4.3" }, "engines": { @@ -2556,14 +2546,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.57.1.tgz", - "integrity": "sha512-hs/QcpCwlwT2L5S+3fT6gp0PabyGk4Q0Rv2doJXA0435/OpnSR3VRgvrp8Xdoc3UAYSg9cyUjTeFXZEPg/3OKg==", + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.57.2.tgz", + "integrity": "sha512-snZKH+W4WbWkrBqj4gUNRIGb/jipDW3qMqVJ4C9rzdFc+wLwruxk+2a5D+uoFcKPAqyqEnSb4l2ULuZf95eSkw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.57.1", - "@typescript-eslint/visitor-keys": "8.57.1" + "@typescript-eslint/types": "8.57.2", + "@typescript-eslint/visitor-keys": "8.57.2" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2574,9 +2564,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.57.1.tgz", - "integrity": "sha512-0lgOZB8cl19fHO4eI46YUx2EceQqhgkPSuCGLlGi79L2jwYY1cxeYc1Nae8Aw1xjgW3PKVDLlr3YJ6Bxx8HkWg==", + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.57.2.tgz", + "integrity": "sha512-3Lm5DSM+DCowsUOJC+YqHHnKEfFh5CoGkj5Z31NQSNF4l5wdOwqGn99wmwN/LImhfY3KJnmordBq/4+VDe2eKw==", "dev": true, "license": "MIT", "engines": { @@ -2591,15 +2581,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.57.1.tgz", - "integrity": "sha512-+Bwwm0ScukFdyoJsh2u6pp4S9ktegF98pYUU0hkphOOqdMB+1sNQhIz8y5E9+4pOioZijrkfNO/HUJVAFFfPKA==", + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.57.2.tgz", + "integrity": "sha512-Co6ZCShm6kIbAM/s+oYVpKFfW7LBc6FXoPXjTRQ449PPNBY8U0KZXuevz5IFuuUj2H9ss40atTaf9dlGLzbWZg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.57.1", - "@typescript-eslint/typescript-estree": "8.57.1", - "@typescript-eslint/utils": "8.57.1", + "@typescript-eslint/types": "8.57.2", + "@typescript-eslint/typescript-estree": "8.57.2", + "@typescript-eslint/utils": "8.57.2", "debug": "^4.4.3", "ts-api-utils": "^2.4.0" }, @@ -2616,9 +2606,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.57.1.tgz", - "integrity": "sha512-S29BOBPJSFUiblEl6RzPPjJt6w25A6XsBqRVDt53tA/tlL8q7ceQNZHTjPeONt/3S7KRI4quk+yP9jK2WjBiPQ==", + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.57.2.tgz", + "integrity": "sha512-/iZM6FnM4tnx9csuTxspMW4BOSegshwX5oBDznJ7S4WggL7Vczz5d2W11ecc4vRrQMQHXRSxzrCsyG5EsPPTbA==", "dev": true, "license": "MIT", "engines": { @@ -2630,16 +2620,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.57.1.tgz", - "integrity": "sha512-ybe2hS9G6pXpqGtPli9Gx9quNV0TWLOmh58ADlmZe9DguLq0tiAKVjirSbtM1szG6+QH6rVXyU6GTLQbWnMY+g==", + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.57.2.tgz", + "integrity": "sha512-2MKM+I6g8tJxfSmFKOnHv2t8Sk3T6rF20A1Puk0svLK+uVapDZB/4pfAeB7nE83uAZrU6OxW+HmOd5wHVdXwXA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.57.1", - "@typescript-eslint/tsconfig-utils": "8.57.1", - "@typescript-eslint/types": "8.57.1", - "@typescript-eslint/visitor-keys": "8.57.1", + "@typescript-eslint/project-service": "8.57.2", + "@typescript-eslint/tsconfig-utils": "8.57.2", + "@typescript-eslint/types": "8.57.2", + "@typescript-eslint/visitor-keys": "8.57.2", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", @@ -2658,16 +2648,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.57.1.tgz", - "integrity": "sha512-XUNSJ/lEVFttPMMoDVA2r2bwrl8/oPx8cURtczkSEswY5T3AeLmCy+EKWQNdL4u0MmAHOjcWrqJp2cdvgjn8dQ==", + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.57.2.tgz", + "integrity": "sha512-krRIbvPK1ju1WBKIefiX+bngPs+odIQUtR7kymzPfo1POVw3jlF+nLkmexdSSd4UCbDcQn+wMBATOOmpBbqgKg==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.57.1", - "@typescript-eslint/types": "8.57.1", - "@typescript-eslint/typescript-estree": "8.57.1" + "@typescript-eslint/scope-manager": "8.57.2", + "@typescript-eslint/types": "8.57.2", + "@typescript-eslint/typescript-estree": "8.57.2" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2682,13 +2672,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.57.1.tgz", - "integrity": "sha512-YWnmJkXbofiz9KbnbbwuA2rpGkFPLbAIetcCNO6mJ8gdhdZ/v7WDXsoGFAJuM6ikUFKTlSQnjWnVO4ux+UzS6A==", + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.57.2.tgz", + "integrity": "sha512-zhahknjobV2FiD6Ee9iLbS7OV9zi10rG26odsQdfBO/hjSzUQbkIYgda+iNKK1zNiW2ey+Lf8MU5btN17V3dUw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.57.1", + "@typescript-eslint/types": "8.57.2", "eslint-visitor-keys": "^5.0.0" }, "engines": { @@ -2715,14 +2705,14 @@ } }, "node_modules/@vitest/coverage-v8": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.0.tgz", - "integrity": "sha512-nDWulKeik2bL2Va/Wl4x7DLuTKAXa906iRFooIRPR+huHkcvp9QDkPQ2RJdmjOFrqOqvNfoSQLF68deE3xC3CQ==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.1.tgz", + "integrity": "sha512-nZ4RWwGCoGOQRMmU/Q9wlUY540RVRxJZ9lxFsFfy0QV7Zmo5VVBhB6Sl9Xa0KIp2iIs3zWfPlo9LcY1iqbpzCw==", "dev": true, "license": "MIT", "dependencies": { "@bcoe/v8-coverage": "^1.0.2", - "@vitest/utils": "4.1.0", + "@vitest/utils": "4.1.1", "ast-v8-to-istanbul": "^1.0.0", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", @@ -2736,8 +2726,8 @@ "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "@vitest/browser": "4.1.0", - "vitest": "4.1.0" + "@vitest/browser": "4.1.1", + "vitest": "4.1.1" }, "peerDependenciesMeta": { "@vitest/browser": { @@ -2746,16 +2736,16 @@ } }, "node_modules/@vitest/expect": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.0.tgz", - "integrity": "sha512-EIxG7k4wlWweuCLG9Y5InKFwpMEOyrMb6ZJ1ihYu02LVj/bzUwn2VMU+13PinsjRW75XnITeFrQBMH5+dLvCDA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.1.tgz", + "integrity": "sha512-xAV0fqBTk44Rn6SjJReEQkHP3RrqbJo6JQ4zZ7/uVOiJZRarBtblzrOfFIZeYUrukp2YD6snZG6IBqhOoHTm+A==", "dev": true, "license": "MIT", "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", - "@vitest/spy": "4.1.0", - "@vitest/utils": "4.1.0", + "@vitest/spy": "4.1.1", + "@vitest/utils": "4.1.1", "chai": "^6.2.2", "tinyrainbow": "^3.0.3" }, @@ -2764,13 +2754,13 @@ } }, "node_modules/@vitest/mocker": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.0.tgz", - "integrity": "sha512-evxREh+Hork43+Y4IOhTo+h5lGmVRyjqI739Rz4RlUPqwrkFFDF6EMvOOYjTx4E8Tl6gyCLRL8Mu7Ry12a13Tw==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.1.tgz", + "integrity": "sha512-h3BOylsfsCLPeceuCPAAJ+BvNwSENgJa4hXoXu4im0bs9Lyp4URc4JYK4pWLZ4pG/UQn7AT92K6IByi6rE6g3A==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "4.1.0", + "@vitest/spy": "4.1.1", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, @@ -2779,7 +2769,7 @@ }, "peerDependencies": { "msw": "^2.4.9", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0-0" + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { "msw": { @@ -2791,9 +2781,9 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.0.tgz", - "integrity": "sha512-3RZLZlh88Ib0J7NQTRATfc/3ZPOnSUn2uDBUoGNn5T36+bALixmzphN26OUD3LRXWkJu4H0s5vvUeqBiw+kS0A==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.1.tgz", + "integrity": "sha512-GM+TEQN5WhOygr1lp7skeVjdLPqqWMHsfzXrcHAqZJi/lIVh63H0kaRCY8MDhNWikx19zBUK8ceaLB7X5AH9NQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2804,13 +2794,13 @@ } }, "node_modules/@vitest/runner": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.0.tgz", - "integrity": "sha512-Duvx2OzQ7d6OjchL+trw+aSrb9idh7pnNfxrklo14p3zmNL4qPCDeIJAK+eBKYjkIwG96Bc6vYuxhqDXQOWpoQ==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.1.tgz", + "integrity": "sha512-f7+FPy75vN91QGWsITueq0gedwUZy1fLtHOCMeQpjs8jTekAHeKP80zfDEnhrleviLHzVSDXIWuCIOFn3D3f8A==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "4.1.0", + "@vitest/utils": "4.1.1", "pathe": "^2.0.3" }, "funding": { @@ -2818,14 +2808,14 @@ } }, "node_modules/@vitest/snapshot": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.0.tgz", - "integrity": "sha512-0Vy9euT1kgsnj1CHttwi9i9o+4rRLEaPRSOJ5gyv579GJkNpgJK+B4HSv/rAWixx2wdAFci1X4CEPjiu2bXIMg==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.1.tgz", + "integrity": "sha512-kMVSgcegWV2FibXEx9p9WIKgje58lcTbXgnJixfcg15iK8nzCXhmalL0ZLtTWLW9PH1+1NEDShiFFedB3tEgWg==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.0", - "@vitest/utils": "4.1.0", + "@vitest/pretty-format": "4.1.1", + "@vitest/utils": "4.1.1", "magic-string": "^0.30.21", "pathe": "^2.0.3" }, @@ -2834,9 +2824,9 @@ } }, "node_modules/@vitest/spy": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.0.tgz", - "integrity": "sha512-pz77k+PgNpyMDv2FV6qmk5ZVau6c3R8HC8v342T2xlFxQKTrSeYw9waIJG8KgV9fFwAtTu4ceRzMivPTH6wSxw==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.1.tgz", + "integrity": "sha512-6Ti/KT5OVaiupdIZEuZN7l3CZcR0cxnxt70Z0//3CtwgObwA6jZhmVBA3yrXSVN3gmwjgd7oDNLlsXz526gpRA==", "dev": true, "license": "MIT", "funding": { @@ -2844,13 +2834,13 @@ } }, "node_modules/@vitest/ui": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-4.1.0.tgz", - "integrity": "sha512-sTSDtVM1GOevRGsCNhp1mBUHKo9Qlc55+HCreFT4fe99AHxl1QQNXSL3uj4Pkjh5yEuWZIx8E2tVC94nnBZECQ==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-4.1.1.tgz", + "integrity": "sha512-k0qNVLmCISxoGWvdhOeynlZVrfjx7Xjp95kIptN0fZYyONCgVcKIPn53MpFZ7S+fO6YdKNhgIfl0nu92Q0CCOg==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "4.1.0", + "@vitest/utils": "4.1.1", "fflate": "^0.8.2", "flatted": "3.4.0", "pathe": "^2.0.3", @@ -2862,17 +2852,17 @@ "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "vitest": "4.1.0" + "vitest": "4.1.1" } }, "node_modules/@vitest/utils": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.0.tgz", - "integrity": "sha512-XfPXT6a8TZY3dcGY8EdwsBulFCIw+BeeX0RZn2x/BtiY/75YGh8FeWGG8QISN/WhaqSrE2OrlDgtF8q5uhOTmw==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.1.tgz", + "integrity": "sha512-cNxAlaB3sHoCdL6pj6yyUXv9Gry1NHNg0kFTXdvSIZXLHsqKH7chiWOkwJ5s5+d/oMwcoG9T0bKU38JZWKusrQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.0", + "@vitest/pretty-format": "4.1.1", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.0.3" }, @@ -4399,16 +4389,16 @@ } }, "node_modules/eslint": { - "version": "10.0.3", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.0.3.tgz", - "integrity": "sha512-COV33RzXZkqhG9P2rZCFl9ZmJ7WL+gQSCRzE7RhkbclbQPtLAWReL7ysA0Sh4c8Im2U9ynybdR56PV0XcKvqaQ==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.1.0.tgz", + "integrity": "sha512-S9jlY/ELKEUwwQnqWDO+f+m6sercqOPSqXM5Go94l7DOmxHVDgmSFGWEzeE/gwgTAr0W103BWt0QLe/7mabIvA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", "@eslint/config-array": "^0.23.3", - "@eslint/config-helpers": "^0.5.2", + "@eslint/config-helpers": "^0.5.3", "@eslint/core": "^1.1.1", "@eslint/plugin-kit": "^0.6.1", "@humanfs/node": "^0.16.6", @@ -4421,7 +4411,7 @@ "escape-string-regexp": "^4.0.0", "eslint-scope": "^9.1.2", "eslint-visitor-keys": "^5.0.1", - "espree": "^11.1.1", + "espree": "^11.2.0", "esquery": "^1.7.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -7429,9 +7419,9 @@ } }, "node_modules/ovsx": { - "version": "0.10.9", - "resolved": "https://registry.npmjs.org/ovsx/-/ovsx-0.10.9.tgz", - "integrity": "sha512-gY6912U50YzzNdAEFr9IxAqu59pKySXZzJUxzHRzi3/h/fWFdDDFCCXyjik6VL4TmiVKeor1Yv/cg7I3KfOUuQ==", + "version": "0.10.10", + "resolved": "https://registry.npmjs.org/ovsx/-/ovsx-0.10.10.tgz", + "integrity": "sha512-/X5J4VLKPUGGaMynW9hgvsGg9jmwsK/3RhODeA2yzdeDbb8PUSNcg5GQ9aPDJW/znlqNvAwQcXAyE+Cq0RRvAQ==", "dev": true, "license": "EPL-2.0", "dependencies": { @@ -8187,14 +8177,14 @@ } }, "node_modules/rolldown": { - "version": "1.0.0-rc.9", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.9.tgz", - "integrity": "sha512-9EbgWge7ZH+yqb4d2EnELAntgPTWbfL8ajiTW+SyhJEC4qhBbkCKbqFV4Ge4zmu5ziQuVbWxb/XwLZ+RIO7E8Q==", + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.11.tgz", + "integrity": "sha512-NRjoKMusSjfRbSYiH3VSumlkgFe7kYAa3pzVOsVYVFY3zb5d7nS+a3KGQ7hJKXuYWbzJKPVQ9Wxq2UvyK+ENpw==", "dev": true, "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.115.0", - "@rolldown/pluginutils": "1.0.0-rc.9" + "@oxc-project/types": "=0.122.0", + "@rolldown/pluginutils": "1.0.0-rc.11" }, "bin": { "rolldown": "bin/cli.mjs" @@ -8203,27 +8193,27 @@ "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.0.0-rc.9", - "@rolldown/binding-darwin-arm64": "1.0.0-rc.9", - "@rolldown/binding-darwin-x64": "1.0.0-rc.9", - "@rolldown/binding-freebsd-x64": "1.0.0-rc.9", - "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.9", - "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.9", - "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.9", - "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.9", - "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.9", - "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.9", - "@rolldown/binding-linux-x64-musl": "1.0.0-rc.9", - "@rolldown/binding-openharmony-arm64": "1.0.0-rc.9", - "@rolldown/binding-wasm32-wasi": "1.0.0-rc.9", - "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.9", - "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.9" + "@rolldown/binding-android-arm64": "1.0.0-rc.11", + "@rolldown/binding-darwin-arm64": "1.0.0-rc.11", + "@rolldown/binding-darwin-x64": "1.0.0-rc.11", + "@rolldown/binding-freebsd-x64": "1.0.0-rc.11", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.11", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.11", + "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.11", + "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.11", + "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.11", + "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.11", + "@rolldown/binding-linux-x64-musl": "1.0.0-rc.11", + "@rolldown/binding-openharmony-arm64": "1.0.0-rc.11", + "@rolldown/binding-wasm32-wasi": "1.0.0-rc.11", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.11", + "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.11" } }, "node_modules/rolldown/node_modules/@oxc-project/types": { - "version": "0.115.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.115.0.tgz", - "integrity": "sha512-4n91DKnebUS4yjUHl2g3/b2T+IUdCfmoZGhmwsovZCDaJSs+QkVAM+0AqqTxHSsHfeiMuueT75cZaZcT/m0pSw==", + "version": "0.122.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.122.0.tgz", + "integrity": "sha512-oLAl5kBpV4w69UtFZ9xqcmTi+GENWOcPF7FCrczTiBbmC0ibXxCwyvZGbO39rCVEuLGAZM84DH0pUIyyv/YJzA==", "dev": true, "license": "MIT", "funding": { @@ -9355,9 +9345,9 @@ } }, "node_modules/ts-api-utils": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", - "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", "dev": true, "license": "MIT", "engines": { @@ -9515,9 +9505,9 @@ } }, "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.2.tgz", + "integrity": "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==", "dev": true, "license": "Apache-2.0", "bin": { @@ -9529,16 +9519,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.57.1.tgz", - "integrity": "sha512-fLvZWf+cAGw3tqMCYzGIU6yR8K+Y9NT2z23RwOjlNFF2HwSB3KhdEFI5lSBv8tNmFkkBShSjsCjzx1vahZfISA==", + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.57.2.tgz", + "integrity": "sha512-VEPQ0iPgWO/sBaZOU1xo4nuNdODVOajPnTIbog2GKYr31nIlZ0fWPoCQgGfF3ETyBl1vn63F/p50Um9Z4J8O8A==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.57.1", - "@typescript-eslint/parser": "8.57.1", - "@typescript-eslint/typescript-estree": "8.57.1", - "@typescript-eslint/utils": "8.57.1" + "@typescript-eslint/eslint-plugin": "8.57.2", + "@typescript-eslint/parser": "8.57.2", + "@typescript-eslint/typescript-estree": "8.57.2", + "@typescript-eslint/utils": "8.57.2" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -9685,17 +9675,16 @@ } }, "node_modules/vite": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.0.tgz", - "integrity": "sha512-fPGaRNj9Zytaf8LEiBhY7Z6ijnFKdzU/+mL8EFBaKr7Vw1/FWcTBAMW0wLPJAGMPX38ZPVCVgLceWiEqeoqL2Q==", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.2.tgz", + "integrity": "sha512-1gFhNi+bHhRE/qKZOJXACm6tX4bA3Isy9KuKF15AgSRuRazNBOJfdDemPBU16/mpMxApDPrWvZ08DcLPEoRnuA==", "dev": true, "license": "MIT", "dependencies": { - "@oxc-project/runtime": "0.115.0", "lightningcss": "^1.32.0", "picomatch": "^4.0.3", "postcss": "^8.5.8", - "rolldown": "1.0.0-rc.9", + "rolldown": "1.0.0-rc.11", "tinyglobby": "^0.2.15" }, "bin": { @@ -9712,7 +9701,7 @@ }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.0.0-alpha.31", + "@vitejs/devtools": "^0.1.0", "esbuild": "^0.27.0", "jiti": ">=1.21.0", "less": "^4.0.0", @@ -9764,9 +9753,9 @@ } }, "node_modules/vite/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { @@ -9777,19 +9766,19 @@ } }, "node_modules/vitest": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.0.tgz", - "integrity": "sha512-YbDrMF9jM2Lqc++2530UourxZHmkKLxrs4+mYhEwqWS97WJ7wOYEkcr+QfRgJ3PW9wz3odRijLZjHEaRLTNbqw==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.1.tgz", + "integrity": "sha512-yF+o4POL41rpAzj5KVILUxm1GCjKnELvaqmU9TLLUbMfDzuN0UpUR9uaDs+mCtjPe+uYPksXDRLQGGPvj1cTmA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/expect": "4.1.0", - "@vitest/mocker": "4.1.0", - "@vitest/pretty-format": "4.1.0", - "@vitest/runner": "4.1.0", - "@vitest/snapshot": "4.1.0", - "@vitest/spy": "4.1.0", - "@vitest/utils": "4.1.0", + "@vitest/expect": "4.1.1", + "@vitest/mocker": "4.1.1", + "@vitest/pretty-format": "4.1.1", + "@vitest/runner": "4.1.1", + "@vitest/snapshot": "4.1.1", + "@vitest/spy": "4.1.1", + "@vitest/utils": "4.1.1", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", @@ -9801,7 +9790,7 @@ "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.0.3", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0-0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", "why-is-node-running": "^2.3.0" }, "bin": { @@ -9817,13 +9806,13 @@ "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.1.0", - "@vitest/browser-preview": "4.1.0", - "@vitest/browser-webdriverio": "4.1.0", - "@vitest/ui": "4.1.0", + "@vitest/browser-playwright": "4.1.1", + "@vitest/browser-preview": "4.1.1", + "@vitest/browser-webdriverio": "4.1.1", + "@vitest/ui": "4.1.1", "happy-dom": "*", "jsdom": "*", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0-0" + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { "@edge-runtime/vm": { @@ -9866,9 +9855,9 @@ "license": "MIT" }, "node_modules/vitest/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { diff --git a/package.json b/package.json index 8e68ec9..798e6ac 100644 --- a/package.json +++ b/package.json @@ -75,30 +75,41 @@ }, "dependencies": {}, "devDependencies": { - "@bytecodealliance/jco": "1.17.1", + "@bytecodealliance/jco": "1.17.5", "@eslint/css": "1.0.0", "@eslint/js": "10.0.1", "@types/node": "25.5.0", "@types/vscode": "1.99.0", - "@typescript-eslint/parser": "8.57.1", - "@vitest/coverage-v8": "4.1.0", - "@vitest/ui": "4.1.0", + "@typescript-eslint/parser": "8.57.2", + "@vitest/coverage-v8": "4.1.1", + "@vitest/ui": "4.1.1", "@vscode/vsce": "3.7.1", "esbuild": "0.27.4", "esbuild-node-externals": "1.20.1", - "eslint": "10.0.3", + "eslint": "10.1.0", "eslint-plugin-prettier": "5.5.5", "globals": "17.4.0", "npm-run-all": "4.1.5", - "ovsx": "0.10.9", + "ovsx": "0.10.10", "prettier": "3.8.1", "rimraf": "6.1.3", - "typescript": "5.9.3", - "typescript-eslint": "8.57.1", - "vitest": "4.1.0", + "typescript": "6.0.2", + "typescript-eslint": "8.57.2", + "vitest": "4.1.1", "vscode-tmgrammar-test": "0.1.3", "wit-bindgen-wasm": "file:wit-bindgen-wasm/pkg" }, + "overrides": { + "typescript-eslint": { + "typescript": "$typescript" + }, + "@typescript-eslint/eslint-plugin": { + "typescript": "$typescript" + }, + "@typescript-eslint/parser": { + "typescript": "$typescript" + } + }, "contributes": { "colors": [ { diff --git a/src/extension.ts b/src/extension.ts index 2e97b15..45d5bda 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -596,7 +596,12 @@ export function activate(context: vscode.ExtensionContext) { const outputPath = outputUri[0].fsPath; - const bindingFiles = await generateBindingsFromWasm(witContent, language); + const bindingFiles = await generateBindingsFromWasm( + witContent, + language, + undefined, + diagDoc?.uri.fsPath ?? targetUri.fsPath + ); const fileEntries = Object.entries(bindingFiles); const errorFile = fileEntries.find(([filename]) => filename === "error.txt"); diff --git a/src/validator.ts b/src/validator.ts index b898de8..bce8b5d 100644 --- a/src/validator.ts +++ b/src/validator.ts @@ -34,7 +34,7 @@ export class WitSyntaxValidator { public async validate(path: string, content: string): Promise | null> { try { // Use the enhanced WASM-based WIT validation with detailed error reporting - const validationResult = await validateWitSyntaxDetailedFromWasm(content); + const validationResult = await validateWitSyntaxDetailedFromWasm(content, path); if (validationResult.valid) { return null; diff --git a/src/wasmUtils.ts b/src/wasmUtils.ts index 88ceae4..5784802 100644 --- a/src/wasmUtils.ts +++ b/src/wasmUtils.ts @@ -1,3 +1,7 @@ +import { readdir, readFile } from "node:fs/promises"; +import type { Dirent } from "node:fs"; +import path from "node:path"; + /** * Cached reference to the witValidator interface from the WASM component module. * The jco-transpiled module self-initializes via top-level await, @@ -5,6 +9,129 @@ */ let witValidatorApi: typeof import("wit-bindgen-wasm").witValidator | null = null; +interface PreparedSourceContext { + sourcePath?: string; + sourceFilesJson?: string; +} + +function normalizeSourcePath(sourcePath?: string): string | undefined { + const trimmedPath = sourcePath?.trim(); + if (!trimmedPath) { + return undefined; + } + + return path.resolve(trimmedPath); +} + +function isErrnoException(error: unknown): error is NodeJS.ErrnoException { + return error instanceof Error && "code" in error; +} + +async function readDirectoryEntries(directoryPath: string): Promise> { + try { + return await readdir(directoryPath, { encoding: "utf8", withFileTypes: true }); + } catch (error: unknown) { + if (isErrnoException(error) && error.code === "ENOENT") { + return []; + } + + throw error; + } +} + +async function collectWitFilePathsRecursively(directoryPath: string, filePaths: Array): Promise { + const entries = await readDirectoryEntries(directoryPath); + for (const entry of entries) { + const entryPath = path.join(directoryPath, entry.name); + if (entry.isDirectory()) { + await collectWitFilePathsRecursively(entryPath, filePaths); + continue; + } + + if (entry.isFile() && entry.name.toLowerCase().endsWith(".wit")) { + filePaths.push(entryPath); + } + } +} + +async function readWitFilesWithConcurrency(filePaths: Array, target: Record): Promise { + if (filePaths.length === 0) { + return; + } + + const maxConcurrency = 8; + let currentIndex = 0; + + const worker = async (): Promise => { + while (true) { + const index = currentIndex; + if (index >= filePaths.length) { + return; + } + + currentIndex += 1; + const filePath = filePaths[index]; + try { + const contents = await readFile(filePath, "utf8"); + target[filePath] = contents; + } catch (error: unknown) { + if (isErrnoException(error) && (error.code === "ENOENT" || error.code === "EACCES")) { + // Skip files that are missing or not accessible without failing the whole operation. + continue; + } + + throw error; + } + } + }; + + const workerCount = Math.min(maxConcurrency, filePaths.length); + const workers: Array> = []; + for (let i = 0; i < workerCount; i += 1) { + workers.push(worker()); + } + + await Promise.all(workers); +} + +async function collectWitContext(sourceDirectory: string): Promise> { + const sourceFiles: Record = {}; + const filePaths: Array = []; + + const entries = await readDirectoryEntries(sourceDirectory); + for (const entry of entries) { + const entryPath = path.join(sourceDirectory, entry.name); + if (entry.isDirectory()) { + if (entry.name === "deps") { + await collectWitFilePathsRecursively(entryPath, filePaths); + } + continue; + } + + if (entry.isFile() && entry.name.toLowerCase().endsWith(".wit")) { + filePaths.push(entryPath); + } + } + + await readWitFilesWithConcurrency(filePaths, sourceFiles); + return sourceFiles; +} + +async function prepareSourceContext(content: string, sourcePath?: string): Promise { + const normalizedSourcePath = normalizeSourcePath(sourcePath); + if (!normalizedSourcePath) { + return {}; + } + + const sourceFiles = await collectWitContext(path.dirname(normalizedSourcePath)); + sourceFiles[normalizedSourcePath] = content; + + return { + sourcePath: normalizedSourcePath, + sourceFilesJson: JSON.stringify(sourceFiles), + }; +} + /** * Initialize the WASM module by dynamically importing it. * The jco-transpiled component handles WASM loading internally. @@ -68,9 +195,10 @@ export async function isWitFileExtensionFromWasm(filename: string): Promise { +export async function validateWitSyntaxFromWasm(content: string, sourcePath?: string): Promise { const api = await getApi(); - return api.validateWitSyntax(content); + const preparedSource = await prepareSourceContext(content, sourcePath); + return api.validateWitSyntax(content, preparedSource.sourcePath, preparedSource.sourceFilesJson); } /** @@ -122,11 +250,19 @@ export async function extractInterfacesFromWasm(content: string): Promise> { const api = await getApi(); - const jsonResult = api.generateBindings(content, language, worldName); - return JSON.parse(jsonResult); + const preparedSource = await prepareSourceContext(content, sourcePath); + const jsonResult = api.generateBindings( + content, + language, + worldName, + preparedSource.sourcePath, + preparedSource.sourceFilesJson + ); + return JSON.parse(jsonResult) as Record; } /** @@ -143,8 +279,16 @@ export interface WitValidationResult { * @param content - The WIT content to validate * @returns Promise that resolves to detailed validation results */ -export async function validateWitSyntaxDetailedFromWasm(content: string): Promise { +export async function validateWitSyntaxDetailedFromWasm( + content: string, + sourcePath?: string +): Promise { const api = await getApi(); - const resultJson = api.validateWitSyntaxDetailed(content); - return JSON.parse(resultJson); + const preparedSource = await prepareSourceContext(content, sourcePath); + const resultJson = api.validateWitSyntaxDetailed( + content, + preparedSource.sourcePath, + preparedSource.sourceFilesJson + ); + return JSON.parse(resultJson) as WitValidationResult; } diff --git a/tests/bindings-generation.test.ts b/tests/bindings-generation.test.ts index f86b369..892a151 100644 --- a/tests/bindings-generation.test.ts +++ b/tests/bindings-generation.test.ts @@ -28,7 +28,7 @@ describe("Bindings Generation for All Languages", () => { supportedLanguages.forEach(({ lang, extension, expectedContent, minLength }) => { it(`should generate actual code stubs for ${lang}`, () => { - const resultJson = witValidator.generateBindings(TEST_WIT, lang, undefined); + const resultJson = witValidator.generateBindings(TEST_WIT, lang, undefined, undefined, undefined); const result = JSON.parse(resultJson); // Verify files were generated @@ -66,7 +66,7 @@ describe("Bindings Generation for All Languages", () => { }); it(`should not generate only README files for ${lang}`, () => { - const resultJson = witValidator.generateBindings(TEST_WIT, lang, undefined); + const resultJson = witValidator.generateBindings(TEST_WIT, lang, undefined, undefined, undefined); const result = JSON.parse(resultJson); // Verify that not all files are README files @@ -90,7 +90,7 @@ describe("Bindings Generation for All Languages", () => { const results: Record> = {}; for (const { lang } of supportedLanguages) { - const resultJson = witValidator.generateBindings(TEST_WIT, lang, undefined); + const resultJson = witValidator.generateBindings(TEST_WIT, lang, undefined, undefined, undefined); results[lang] = JSON.parse(resultJson); } @@ -149,7 +149,7 @@ world app { `; for (const { lang, extension } of supportedLanguages) { - const resultJson = witValidator.generateBindings(complexWit, lang, undefined); + const resultJson = witValidator.generateBindings(complexWit, lang, undefined, undefined, undefined); const result = JSON.parse(resultJson); expect(Object.keys(result).length).toBeGreaterThan(0); diff --git a/tests/grammar/issue-121/wit/a.wit b/tests/grammar/issue-121/wit/a.wit new file mode 100644 index 0000000..17ab45b --- /dev/null +++ b/tests/grammar/issue-121/wit/a.wit @@ -0,0 +1,11 @@ +package local:demo; + +world my-world { + import host; + + export another-interface; +} + +interface host { + // ... +} diff --git a/tests/grammar/issue-121/wit/b.wit b/tests/grammar/issue-121/wit/b.wit new file mode 100644 index 0000000..e6d754d --- /dev/null +++ b/tests/grammar/issue-121/wit/b.wit @@ -0,0 +1,3 @@ +interface another-interface { + // ... +} diff --git a/tests/import-resolution.test.ts b/tests/import-resolution.test.ts new file mode 100644 index 0000000..20f0159 --- /dev/null +++ b/tests/import-resolution.test.ts @@ -0,0 +1,82 @@ +import { mkdtemp, mkdir, readFile, rm, writeFile } from "node:fs/promises"; +import os from "node:os"; +import path from "node:path"; +import { afterEach, describe, expect, it } from "vitest"; + +import { generateBindingsFromWasm, validateWitSyntaxDetailedFromWasm } from "../src/wasmUtils.js"; + +describe("local import resolution", () => { + const tempDirectories: string[] = []; + + afterEach(async () => { + await Promise.all(tempDirectories.map((directory) => rm(directory, { recursive: true, force: true }))); + tempDirectories.length = 0; + }); + + async function createIssue121Fixture(): Promise<{ content: string; sourcePath: string }> { + const fixtureRoot = await mkdtemp(path.join(os.tmpdir(), "wit-idl-imports-")); + tempDirectories.push(fixtureRoot); + + const witDirectory = path.join(fixtureRoot, "wit"); + await mkdir(witDirectory, { recursive: true }); + + const sourcePath = path.join(witDirectory, "a.wit"); + await writeFile( + sourcePath, + `package local:demo; + +world my-world { + import host; + + export another-interface; +} + +interface host { + ping: func(); +} +`, + "utf8" + ); + await writeFile( + path.join(witDirectory, "b.wit"), + `interface another-interface { + pong: func(); +} +`, + "utf8" + ); + + return { + content: await readFile(sourcePath, "utf8"), + sourcePath, + }; + } + + it("validates WIT with sibling imports when given a source path", async () => { + const fixture = await createIssue121Fixture(); + + const result = await validateWitSyntaxDetailedFromWasm(fixture.content, fixture.sourcePath); + + expect(result).toEqual({ valid: true }); + }); + + it("uses unsaved editor content while still resolving sibling imports", async () => { + const fixture = await createIssue121Fixture(); + const invalidContent = `${fixture.content}\nthis is not valid wit\n`; + + const result = await validateWitSyntaxDetailedFromWasm(invalidContent, fixture.sourcePath); + + expect(result.valid).toBe(false); + expect(result.error).toContain(fixture.sourcePath); + }); + + it("generates bindings from in-memory content while resolving sibling imports", async () => { + const fixture = await createIssue121Fixture(); + const updatedContent = fixture.content.replace("world my-world", "world staged-world"); + + const files = await generateBindingsFromWasm(updatedContent, "rust", "staged-world", fixture.sourcePath); + + expect(files["error.txt"]).toBeUndefined(); + expect(Object.keys(files).some((filename) => filename.endsWith(".rs"))).toBe(true); + }); +}); diff --git a/tests/wasmUtils.test.ts b/tests/wasmUtils.test.ts index 6b47a68..b5b3b2b 100644 --- a/tests/wasmUtils.test.ts +++ b/tests/wasmUtils.test.ts @@ -4,35 +4,23 @@ import { describe, it, expect, vi, beforeEach } from "vitest"; vi.mock("wit-bindgen-wasm", () => { return { witValidator: { - version(): string { - return "0.42.0-mock"; - }, - validateWitSyntax(content: string): boolean { + version: vi.fn((): string => "0.42.0-mock"), + validateWitSyntax: vi.fn((content: string): boolean => { return content.includes("package"); - }, - validateWitSyntaxDetailed(content: string): string { + }), + validateWitSyntaxDetailed: vi.fn((content: string): string => { if (content.includes("package")) { return JSON.stringify({ valid: true }); } return JSON.stringify({ valid: false, error: "expected package", errorType: "parsing" }); - }, - extractInterfaces(): string { - return "my-interface, another-interface"; - }, - generateBindings(): string { - return JSON.stringify({ "lib.rs": "// generated" }); - }, + }), + extractInterfaces: vi.fn((): string => "my-interface, another-interface"), + generateBindings: vi.fn((): string => JSON.stringify({ "lib.rs": "// generated" })), extractWitFromComponent: vi.fn().mockReturnValue("package test:component;"), extractCoreWasmFromComponent: vi.fn().mockReturnValue(JSON.stringify({ "module-0.wasm": "\x00asm" })), - hasWorldDefinition(content: string): boolean { - return content.includes("world"); - }, - isWitFileExtension(filename: string): boolean { - return filename.toLowerCase().endsWith(".wit"); - }, - getWitBindgenVersion(): string { - return "0.42.0-mock"; - }, + hasWorldDefinition: vi.fn((content: string): boolean => content.includes("world")), + isWitFileExtension: vi.fn((filename: string): boolean => filename.toLowerCase().endsWith(".wit")), + getWitBindgenVersion: vi.fn((): string => "0.42.0-mock"), }, }; }); @@ -105,6 +93,7 @@ describe("wasmUtils", () => { it("should return true for valid WIT content", async () => { const result = await validateWitSyntaxFromWasm("package foo:bar;"); expect(result).toBe(true); + expect(witValidator.validateWitSyntax).toHaveBeenCalledWith("package foo:bar;", undefined, undefined); }); it("should return false for invalid WIT content", async () => { @@ -117,6 +106,11 @@ describe("wasmUtils", () => { it("should return valid result for valid content", async () => { const result = await validateWitSyntaxDetailedFromWasm("package foo:bar;"); expect(result.valid).toBe(true); + expect(witValidator.validateWitSyntaxDetailed).toHaveBeenCalledWith( + "package foo:bar;", + undefined, + undefined + ); }); it("should return error details for invalid content", async () => { @@ -140,6 +134,13 @@ describe("wasmUtils", () => { expect(result).toBeDefined(); expect(typeof result).toBe("object"); expect(result["lib.rs"]).toBe("// generated"); + expect(witValidator.generateBindings).toHaveBeenCalledWith( + "package foo:bar; world w {}", + "rust", + undefined, + undefined, + undefined + ); }); it("should accept optional world name", async () => { diff --git a/wit-bindgen-wasm/Cargo.lock b/wit-bindgen-wasm/Cargo.lock index 63c004e..3869835 100644 --- a/wit-bindgen-wasm/Cargo.lock +++ b/wit-bindgen-wasm/Cargo.lock @@ -63,9 +63,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "leb128fmt" @@ -174,9 +174,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "1.0.4" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" +checksum = "876ac351060d4f882bb1032b6369eb0aef79ad9df1ea8bc404874d8cc3d0cd98" dependencies = [ "serde_core", ] @@ -194,9 +194,9 @@ dependencies = [ [[package]] name = "toml" -version = "1.0.7+spec-1.1.0" +version = "1.1.0+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd28d57d8a6f6e458bc0b8784f8fdcc4b99a437936056fa122cb234f18656a96" +checksum = "f8195ca05e4eb728f4ba94f3e3291661320af739c4e43779cbdfae82ab239fcc" dependencies = [ "indexmap", "serde_core", @@ -209,27 +209,27 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "1.0.1+spec-1.1.0" +version = "1.1.0+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b320e741db58cac564e26c607d3cc1fdc4a88fd36c879568c07856ed83ff3e9" +checksum = "97251a7c317e03ad83774a8752a7e81fb6067740609f75ea2b585b569a59198f" dependencies = [ "serde_core", ] [[package]] name = "toml_parser" -version = "1.0.10+spec-1.1.0" +version = "1.1.0+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7df25b4befd31c4816df190124375d5a20c6b6921e2cad937316de3fccd63420" +checksum = "2334f11ee363607eb04df9b8fc8a13ca1715a72ba8662a26ac285c98aabb4011" dependencies = [ "winnow", ] [[package]] name = "toml_writer" -version = "1.0.7+spec-1.1.0" +version = "1.1.0+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17aaa1c6e3dc22b1da4b6bba97d066e354c7945cac2f7852d4e4e7ca7a6b56d" +checksum = "d282ade6016312faf3e41e57ebbba0c073e4056dab1232ab1cb624199648f8ed" [[package]] name = "unicase" diff --git a/wit-bindgen-wasm/Cargo.toml b/wit-bindgen-wasm/Cargo.toml index 17e46dc..11df599 100644 --- a/wit-bindgen-wasm/Cargo.toml +++ b/wit-bindgen-wasm/Cargo.toml @@ -25,7 +25,7 @@ wit-component = "0.245" anyhow = "1.0.102" [build-dependencies] -toml = "1.0.7" +toml = "1.1.0" [profile.release] opt-level = "s" diff --git a/wit-bindgen-wasm/src/lib.rs b/wit-bindgen-wasm/src/lib.rs index 6ea3f9c..b719e77 100644 --- a/wit-bindgen-wasm/src/lib.rs +++ b/wit-bindgen-wasm/src/lib.rs @@ -1,5 +1,5 @@ use std::collections::HashMap; -use std::path::Path; +use std::path::{Path, PathBuf}; use anyhow::Context; use wasmparser::{Parser, Payload}; @@ -13,7 +13,7 @@ use wit_bindgen_markdown as markdown; use wit_bindgen_moonbit as moonbit; use wit_bindgen_rust as rust; use wit_component as wcomp; -use wit_parser::{PackageId, Resolve}; +use wit_parser::{PackageId, Resolve, SourceMap}; // Generate component model bindings from our WIT file wit_bindgen::generate!({ @@ -27,24 +27,101 @@ fn bytes_to_latin1_string(bytes: &[u8]) -> String { bytes.iter().map(|&b| b as char).collect() } +fn normalize_source_path(source_path: Option<&str>) -> Option { + source_path.and_then(|value| { + let trimmed = value.trim(); + if trimmed.is_empty() { + None + } else { + Some(PathBuf::from(trimmed)) + } + }) +} + +fn normalize_optional_text(value: Option<&str>) -> Option<&str> { + value.and_then(|entry| { + let trimmed = entry.trim(); + if trimmed.is_empty() { + None + } else { + Some(trimmed) + } + }) +} + +fn source_label(source_path: Option<&str>) -> String { + normalize_source_path(source_path) + .map(|path| path.display().to_string()) + .unwrap_or_else(|| "inline content".to_string()) +} + +fn load_resolve( + content: &str, + source_path: Option<&str>, + source_files_json: Option<&str>, + operation: &str, +) -> anyhow::Result<(Resolve, PackageId)> { + let mut resolve = Resolve::default(); + let package_id = match normalize_optional_text(source_files_json) { + Some(source_files_json) => { + let source_files = + serde_json::from_str::>(source_files_json) + .with_context(|| format!("{operation}: invalid source files payload"))?; + let mut source_map = SourceMap::default(); + for (file_path, file_contents) in source_files { + source_map.push_str(&file_path, file_contents); + } + let unresolved_group = source_map + .parse() + .with_context(|| format!("{operation} from {}", source_label(source_path)))?; + resolve + .push_group(unresolved_group) + .with_context(|| format!("{operation} from {}", source_label(source_path)))? + } + None => { + let main_path = normalize_source_path(source_path) + .unwrap_or_else(|| PathBuf::from("inline.wit")) + .display() + .to_string(); + resolve + .push_source(&main_path, content) + .with_context(|| format!("{operation} from {}", source_label(source_path)))? + } + }; + + Ok((resolve, package_id)) +} + struct WitBindgenComponent; export!(WitBindgenComponent); impl exports::wit_bindgen::wasm::wit_validator::Guest for WitBindgenComponent { - fn validate_wit_syntax(content: String) -> bool { + fn validate_wit_syntax( + content: String, + source_path: Option, + source_files_json: Option, + ) -> bool { let trimmed = content.trim(); if trimmed.is_empty() { return false; } - let inline_path = Path::new("inline.wit"); - let mut resolve = Resolve::default(); - resolve.push_str(inline_path, trimmed).is_ok() + load_resolve( + trimmed, + source_path.as_deref(), + source_files_json.as_deref(), + "Failed to validate WIT syntax", + ) + .is_ok() } - fn validate_wit_syntax_detailed(content: String) -> String { + fn validate_wit_syntax_detailed( + content: String, + source_path: Option, + source_files_json: Option, + ) -> String { let trimmed = content.trim(); if trimmed.is_empty() { @@ -56,15 +133,18 @@ impl exports::wit_bindgen::wasm::wit_validator::Guest for WitBindgenComponent { .to_string(); } - let inline_path = Path::new("inline.wit"); - let mut resolve = Resolve::default(); - match resolve.push_str(inline_path, trimmed) { - Ok(_package_id) => serde_json::json!({ + match load_resolve( + trimmed, + source_path.as_deref(), + source_files_json.as_deref(), + "Failed to validate WIT syntax", + ) { + Ok((_resolve, _package_id)) => serde_json::json!({ "valid": true }) .to_string(), Err(e) => { - let error_message = e.to_string(); + let error_message = format!("{e:#}"); if error_message.contains("package not found") || error_message.contains("interface not found") @@ -122,15 +202,56 @@ impl exports::wit_bindgen::wasm::wit_validator::Guest for WitBindgenComponent { env!("WIT_BINDGEN_CORE_VERSION").to_string() } - fn generate_bindings(content: String, language: String, world_name: Option) -> String { + fn generate_bindings( + content: String, + language: String, + world_name: Option, + source_path: Option, + source_files_json: Option, + ) -> String { let files = match language.to_lowercase().as_str() { - "rust" => generate_rust_bindings(&content, world_name), - "c" => generate_c_bindings(&content, world_name), - "cpp" | "c++" => generate_cpp_bindings(&content, world_name), - "csharp" | "c#" => generate_csharp_bindings(&content, world_name), - "go" => generate_go_bindings(&content, world_name), - "moonbit" => generate_moonbit_bindings(&content, world_name), - "markdown" | "md" => generate_markdown_bindings(&content, world_name), + "rust" => generate_rust_bindings( + &content, + world_name, + source_path.as_deref(), + source_files_json.as_deref(), + ), + "c" => generate_c_bindings( + &content, + world_name, + source_path.as_deref(), + source_files_json.as_deref(), + ), + "cpp" | "c++" => generate_cpp_bindings( + &content, + world_name, + source_path.as_deref(), + source_files_json.as_deref(), + ), + "csharp" | "c#" => generate_csharp_bindings( + &content, + world_name, + source_path.as_deref(), + source_files_json.as_deref(), + ), + "go" => generate_go_bindings( + &content, + world_name, + source_path.as_deref(), + source_files_json.as_deref(), + ), + "moonbit" => generate_moonbit_bindings( + &content, + world_name, + source_path.as_deref(), + source_files_json.as_deref(), + ), + "markdown" | "md" => generate_markdown_bindings( + &content, + world_name, + source_path.as_deref(), + source_files_json.as_deref(), + ), _ => { let mut error_files = HashMap::new(); error_files.insert( @@ -233,14 +354,24 @@ fn extract_core_wasm_impl(bytes: &[u8]) -> anyhow::Result) -> HashMap { - match generate_rust_with_wit_bindgen(content, world_name.as_deref()) { +fn generate_rust_bindings( + content: &str, + world_name: Option, + source_path: Option<&str>, + source_files_json: Option<&str>, +) -> HashMap { + match generate_rust_with_wit_bindgen( + content, + world_name.as_deref(), + source_path, + source_files_json, + ) { Ok(files) => files, Err(e) => { let mut error_files = HashMap::new(); error_files.insert( "error.txt".to_string(), - format!("Rust binding generation failed: {}", e), + format!("Rust binding generation failed: {e:#}"), ); error_files } @@ -250,12 +381,15 @@ fn generate_rust_bindings(content: &str, world_name: Option) -> HashMap< fn generate_rust_with_wit_bindgen( content: &str, world_name: Option<&str>, + source_path: Option<&str>, + source_files_json: Option<&str>, ) -> Result, anyhow::Error> { - let inline_path = Path::new("inline.wit"); - let mut resolve = Resolve::default(); - let package_id = resolve - .push_str(inline_path, content) - .with_context(|| "Failed to parse WIT content for Rust binding generation")?; + let (mut resolve, package_id) = load_resolve( + content, + source_path, + source_files_json, + "Failed to parse WIT content for Rust binding generation", + )?; let world_id = resolve.select_world(&[package_id], world_name)?; @@ -274,14 +408,24 @@ fn generate_rust_with_wit_bindgen( Ok(result) } -fn generate_c_bindings(content: &str, world_name: Option) -> HashMap { - match generate_c_with_wit_bindgen(content, world_name.as_deref()) { +fn generate_c_bindings( + content: &str, + world_name: Option, + source_path: Option<&str>, + source_files_json: Option<&str>, +) -> HashMap { + match generate_c_with_wit_bindgen( + content, + world_name.as_deref(), + source_path, + source_files_json, + ) { Ok(files) => files, Err(e) => { let mut error_files = HashMap::new(); error_files.insert( "error.txt".to_string(), - format!("C binding generation failed: {}", e), + format!("C binding generation failed: {e:#}"), ); error_files } @@ -291,12 +435,15 @@ fn generate_c_bindings(content: &str, world_name: Option) -> HashMap, + source_path: Option<&str>, + source_files_json: Option<&str>, ) -> Result, anyhow::Error> { - let inline_path = Path::new("inline.wit"); - let mut resolve = Resolve::default(); - let package_id = resolve - .push_str(inline_path, content) - .with_context(|| "Failed to parse WIT content for C binding generation")?; + let (mut resolve, package_id) = load_resolve( + content, + source_path, + source_files_json, + "Failed to parse WIT content for C binding generation", + )?; let world_id = resolve.select_world(&[package_id], world_name)?; @@ -312,14 +459,24 @@ fn generate_c_with_wit_bindgen( Ok(result) } -fn generate_cpp_bindings(content: &str, world_name: Option) -> HashMap { - match generate_cpp_with_wit_bindgen(content, world_name.as_deref()) { +fn generate_cpp_bindings( + content: &str, + world_name: Option, + source_path: Option<&str>, + source_files_json: Option<&str>, +) -> HashMap { + match generate_cpp_with_wit_bindgen( + content, + world_name.as_deref(), + source_path, + source_files_json, + ) { Ok(files) => files, Err(e) => { let mut error_files = HashMap::new(); error_files.insert( "error.txt".to_string(), - format!("C++ binding generation failed: {}", e), + format!("C++ binding generation failed: {e:#}"), ); error_files } @@ -329,12 +486,15 @@ fn generate_cpp_bindings(content: &str, world_name: Option) -> HashMap, + source_path: Option<&str>, + source_files_json: Option<&str>, ) -> Result, anyhow::Error> { - let inline_path = Path::new("inline.wit"); - let mut resolve = Resolve::default(); - let package_id = resolve - .push_str(inline_path, content) - .with_context(|| "Failed to parse WIT content for C++ binding generation")?; + let (mut resolve, package_id) = load_resolve( + content, + source_path, + source_files_json, + "Failed to parse WIT content for C++ binding generation", + )?; let world_id = resolve.select_world(&[package_id], world_name)?; @@ -350,14 +510,24 @@ fn generate_cpp_with_wit_bindgen( Ok(result) } -fn generate_csharp_bindings(content: &str, world_name: Option) -> HashMap { - match generate_csharp_with_wit_bindgen(content, world_name.as_deref()) { +fn generate_csharp_bindings( + content: &str, + world_name: Option, + source_path: Option<&str>, + source_files_json: Option<&str>, +) -> HashMap { + match generate_csharp_with_wit_bindgen( + content, + world_name.as_deref(), + source_path, + source_files_json, + ) { Ok(files) => files, Err(e) => { let mut error_files = HashMap::new(); error_files.insert( "error.txt".to_string(), - format!("C# binding generation failed: {}", e), + format!("C# binding generation failed: {e:#}"), ); error_files } @@ -367,12 +537,15 @@ fn generate_csharp_bindings(content: &str, world_name: Option) -> HashMa fn generate_csharp_with_wit_bindgen( content: &str, world_name: Option<&str>, + source_path: Option<&str>, + source_files_json: Option<&str>, ) -> Result, anyhow::Error> { - let inline_path = Path::new("inline.wit"); - let mut resolve = Resolve::default(); - let package_id = resolve - .push_str(inline_path, content) - .with_context(|| "Failed to parse WIT content for C# binding generation")?; + let (mut resolve, package_id) = load_resolve( + content, + source_path, + source_files_json, + "Failed to parse WIT content for C# binding generation", + )?; let world_id = resolve.select_world(&[package_id], world_name)?; @@ -388,14 +561,24 @@ fn generate_csharp_with_wit_bindgen( Ok(result) } -fn generate_go_bindings(content: &str, world_name: Option) -> HashMap { - match generate_go_with_wit_bindgen(content, world_name.as_deref()) { +fn generate_go_bindings( + content: &str, + world_name: Option, + source_path: Option<&str>, + source_files_json: Option<&str>, +) -> HashMap { + match generate_go_with_wit_bindgen( + content, + world_name.as_deref(), + source_path, + source_files_json, + ) { Ok(files) => files, Err(e) => { let mut error_files = HashMap::new(); error_files.insert( "error.txt".to_string(), - format!("Go binding generation failed: {}", e), + format!("Go binding generation failed: {e:#}"), ); error_files } @@ -405,12 +588,15 @@ fn generate_go_bindings(content: &str, world_name: Option) -> HashMap, + source_path: Option<&str>, + source_files_json: Option<&str>, ) -> Result, anyhow::Error> { - let inline_path = Path::new("inline.wit"); - let mut resolve = Resolve::default(); - let package_id = resolve - .push_str(inline_path, content) - .with_context(|| "Failed to parse WIT content for Go binding generation")?; + let (mut resolve, package_id) = load_resolve( + content, + source_path, + source_files_json, + "Failed to parse WIT content for Go binding generation", + )?; let world_id = resolve.select_world(&[package_id], world_name)?; @@ -426,14 +612,24 @@ fn generate_go_with_wit_bindgen( Ok(result) } -fn generate_moonbit_bindings(content: &str, world_name: Option) -> HashMap { - match generate_moonbit_with_wit_bindgen(content, world_name.as_deref()) { +fn generate_moonbit_bindings( + content: &str, + world_name: Option, + source_path: Option<&str>, + source_files_json: Option<&str>, +) -> HashMap { + match generate_moonbit_with_wit_bindgen( + content, + world_name.as_deref(), + source_path, + source_files_json, + ) { Ok(files) => files, Err(e) => { let mut error_files = HashMap::new(); error_files.insert( "error.txt".to_string(), - format!("MoonBit binding generation failed: {}", e), + format!("MoonBit binding generation failed: {e:#}"), ); error_files } @@ -443,12 +639,15 @@ fn generate_moonbit_bindings(content: &str, world_name: Option) -> HashM fn generate_moonbit_with_wit_bindgen( content: &str, world_name: Option<&str>, + source_path: Option<&str>, + source_files_json: Option<&str>, ) -> Result, anyhow::Error> { - let inline_path = Path::new("inline.wit"); - let mut resolve = Resolve::default(); - let package_id = resolve - .push_str(inline_path, content) - .with_context(|| "Failed to parse WIT content for MoonBit binding generation")?; + let (mut resolve, package_id) = load_resolve( + content, + source_path, + source_files_json, + "Failed to parse WIT content for MoonBit binding generation", + )?; let world_id = resolve.select_world(&[package_id], world_name)?; @@ -467,14 +666,21 @@ fn generate_moonbit_with_wit_bindgen( fn generate_markdown_bindings( content: &str, world_name: Option, + source_path: Option<&str>, + source_files_json: Option<&str>, ) -> HashMap { - match generate_markdown_with_wit_bindgen(content, world_name.as_deref()) { + match generate_markdown_with_wit_bindgen( + content, + world_name.as_deref(), + source_path, + source_files_json, + ) { Ok(files) => files, Err(e) => { let mut error_files = HashMap::new(); error_files.insert( "error.txt".to_string(), - format!("Markdown generation failed: {}", e), + format!("Markdown generation failed: {e:#}"), ); error_files } @@ -484,12 +690,15 @@ fn generate_markdown_bindings( fn generate_markdown_with_wit_bindgen( content: &str, world_name: Option<&str>, + source_path: Option<&str>, + source_files_json: Option<&str>, ) -> Result, anyhow::Error> { - let inline_path = Path::new("inline.wit"); - let mut resolve = Resolve::default(); - let package_id = resolve - .push_str(inline_path, content) - .with_context(|| "Failed to parse WIT content for Markdown generation")?; + let (mut resolve, package_id) = load_resolve( + content, + source_path, + source_files_json, + "Failed to parse WIT content for Markdown generation", + )?; let world_id = resolve.select_world(&[package_id], world_name)?; @@ -507,9 +716,52 @@ fn generate_markdown_with_wit_bindgen( #[cfg(test)] mod tests { + use std::time::{SystemTime, UNIX_EPOCH}; + use crate::exports::wit_bindgen::wasm::wit_validator::Guest; use crate::WitBindgenComponent; + fn create_import_fixture() -> (String, String, String) { + let suffix = SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("system clock should be after unix epoch") + .as_nanos(); + let main_path = std::env::temp_dir() + .join(format!("wit-bindgen-wasm-imports-{suffix}")) + .join("a.wit"); + let imported_path = main_path.with_file_name("b.wit"); + + let main_content = r#"package local:demo; + +world my-world { + import host; + + export another-interface; +} + +interface host { + ping: func(); +} +"# + .to_string(); + let imported_content = r#"interface another-interface { + pong: func(); +} +"# + .to_string(); + let source_files_json = serde_json::to_string(&HashMap::from([ + (main_path.display().to_string(), main_content.clone()), + (imported_path.display().to_string(), imported_content), + ])) + .expect("source files json should serialize"); + + ( + main_path.display().to_string(), + main_content, + source_files_json, + ) + } + #[test] fn test_validate_wit_syntax_with_undefined_type() { let invalid_content = r#"package foo:foo; @@ -530,7 +782,8 @@ world foo { export foo: func() -> tuple; }"#; - let result = WitBindgenComponent::validate_wit_syntax(invalid_content.to_string()); + let result = + WitBindgenComponent::validate_wit_syntax(invalid_content.to_string(), None, None); assert!(!result, "Should detect undefined type4 as invalid"); } @@ -541,16 +794,18 @@ world foo { world foo { }"#; - let result = WitBindgenComponent::validate_wit_syntax(valid_content.to_string()); + let result = + WitBindgenComponent::validate_wit_syntax(valid_content.to_string(), None, None); assert!(result, "Should validate correct WIT syntax as valid"); } #[test] fn test_validate_wit_syntax_with_empty_content() { - let result = WitBindgenComponent::validate_wit_syntax("".to_string()); + let result = WitBindgenComponent::validate_wit_syntax("".to_string(), None, None); assert!(!result, "Empty content should be invalid"); - let result = WitBindgenComponent::validate_wit_syntax(" \n \t ".to_string()); + let result = + WitBindgenComponent::validate_wit_syntax(" \n \t ".to_string(), None, None); assert!(!result, "Whitespace-only content should be invalid"); } @@ -567,7 +822,47 @@ world test-world { export test; }"#; - let result = WitBindgenComponent::validate_wit_syntax(valid_content.to_string()); + let result = + WitBindgenComponent::validate_wit_syntax(valid_content.to_string(), None, None); assert!(result, "Should validate sized list syntax as valid"); } + + #[test] + fn test_validate_wit_syntax_with_local_imports() { + let (main_path, content, source_files_json) = create_import_fixture(); + + let result = WitBindgenComponent::validate_wit_syntax( + content, + Some(main_path), + Some(source_files_json), + ); + assert!( + result, + "Should resolve imported WIT files from the local folder" + ); + } + + #[test] + fn test_generate_bindings_with_local_imports() { + let (main_path, content, source_files_json) = create_import_fixture(); + + let result_json = WitBindgenComponent::generate_bindings( + content, + "rust".to_string(), + Some("my-world".to_string()), + Some(main_path), + Some(source_files_json), + ); + + let files: std::collections::HashMap = + serde_json::from_str(&result_json).expect("binding generation should return json"); + assert!( + !files.contains_key("error.txt"), + "Binding generation should succeed with local imports: {files:?}" + ); + assert!( + files.keys().any(|filename| filename.ends_with(".rs")), + "Rust bindings should include a .rs file" + ); + } } diff --git a/wit-bindgen-wasm/wit/wit-bindgen.wit b/wit-bindgen-wasm/wit/wit-bindgen.wit index cb28b9f..6d819c4 100644 --- a/wit-bindgen-wasm/wit/wit-bindgen.wit +++ b/wit-bindgen-wasm/wit/wit-bindgen.wit @@ -13,11 +13,19 @@ package wit-bindgen:wasm@0.1.0; interface wit-validator { /// Validate WIT syntax in the given content /// Returns true if the content appears to be valid WIT syntax - validate-wit-syntax: func(content: string) -> bool; + validate-wit-syntax: func( + content: string, + source-path: option, + source-files-json: option + ) -> bool; /// Validate WIT syntax and return detailed error information /// Returns JSON string with validation results and error details - validate-wit-syntax-detailed: func(content: string) -> string; + validate-wit-syntax-detailed: func( + content: string, + source-path: option, + source-files-json: option + ) -> string; /// Extract interface names from WIT content /// Returns a comma-separated list of interface names @@ -37,7 +45,13 @@ interface wit-validator { /// to preserve binary data integrity. This ensures that binary files (like .wasm, .o) /// and text files can be round-tripped without corruption. Consumers must decode /// file content using Latin1 encoding when writing to disk. - generate-bindings: func(content: string, language: string, world-name: option) -> string; + generate-bindings: func( + content: string, + language: string, + world-name: option, + source-path: option, + source-files-json: option + ) -> string; /// Extract WIT text from a WebAssembly component (bytes). /// Returns the WIT text as a plain string on success, or an empty string on failure.