diff --git a/.publish/ai/package.json b/.publish/ai/package.json index 853c59c0..0d642cbc 100644 --- a/.publish/ai/package.json +++ b/.publish/ai/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/ai", - "version": "0.8.7", + "version": "0.8.8", "type": "module", "description": "Zero-dependency Claude (Anthropic) client for WrNexus apps.", "license": "MIT", diff --git a/.publish/authz/package.json b/.publish/authz/package.json index 11b35f1d..122e944e 100644 --- a/.publish/authz/package.json +++ b/.publish/authz/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/authz", - "version": "0.8.7", + "version": "0.8.8", "type": "module", "description": "@wrnexus/authz — part of the WrNexus framework.", "license": "MIT", @@ -41,8 +41,8 @@ } }, "dependencies": { - "@wrnexus/core": "^0.8.7", - "@wrnexus/db": "^0.8.7" + "@wrnexus/core": "^0.8.8", + "@wrnexus/db": "^0.8.8" }, "files": [ "dist", diff --git a/.publish/cli/package.json b/.publish/cli/package.json index 94fb3985..49898fc4 100644 --- a/.publish/cli/package.json +++ b/.publish/cli/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/cli", - "version": "0.8.7", + "version": "0.8.8", "type": "module", "description": "@wrnexus/cli — part of the WrNexus framework.", "license": "MIT", @@ -44,23 +44,23 @@ "wrnexus": "./dist/index.js" }, "dependencies": { - "@wrnexus/core": "^0.8.7", - "@wrnexus/router": "^0.8.7", - "@wrnexus/csr": "^0.8.7", - "@wrnexus/compiler": "^0.8.7", - "@wrnexus/styles": "^0.8.7", - "@wrnexus/dev-server": "^0.8.7", - "@wrnexus/ui": "^0.8.7", - "@wrnexus/validation": "^0.8.7", - "@wrnexus/i18n": "^0.8.7", - "@wrnexus/mcp": "^0.8.7", - "@wrnexus/playground": "^0.8.7", - "@wrnexus/db": "^0.8.7", - "@wrnexus/authz": "^0.8.7", - "@wrnexus/plugin": "^0.8.7", - "@wrnexus/syntax": "^0.8.7", - "@wrnexus/typecheck": "^0.8.7", - "@wrnexus/security": "^0.8.7", + "@wrnexus/core": "^0.8.8", + "@wrnexus/router": "^0.8.8", + "@wrnexus/csr": "^0.8.8", + "@wrnexus/compiler": "^0.8.8", + "@wrnexus/styles": "^0.8.8", + "@wrnexus/dev-server": "^0.8.8", + "@wrnexus/ui": "^0.8.8", + "@wrnexus/validation": "^0.8.8", + "@wrnexus/i18n": "^0.8.8", + "@wrnexus/mcp": "^0.8.8", + "@wrnexus/playground": "^0.8.8", + "@wrnexus/db": "^0.8.8", + "@wrnexus/authz": "^0.8.8", + "@wrnexus/plugin": "^0.8.8", + "@wrnexus/syntax": "^0.8.8", + "@wrnexus/typecheck": "^0.8.8", + "@wrnexus/security": "^0.8.8", "selfsigned": "^5.5.0" }, "files": [ diff --git a/.publish/compiler/package.json b/.publish/compiler/package.json index bc08ffb0..0ed98c56 100644 --- a/.publish/compiler/package.json +++ b/.publish/compiler/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/compiler", - "version": "0.8.7", + "version": "0.8.8", "type": "module", "description": "@wrnexus/compiler — part of the WrNexus framework.", "license": "MIT", @@ -37,10 +37,10 @@ } }, "dependencies": { - "@wrnexus/csr": "^0.8.7", - "@wrnexus/syntax": "^0.8.7", - "@wrnexus/store": "^0.8.7", - "@wrnexus/validation": "^0.8.7" + "@wrnexus/csr": "^0.8.8", + "@wrnexus/syntax": "^0.8.8", + "@wrnexus/store": "^0.8.8", + "@wrnexus/validation": "^0.8.8" }, "files": [ "dist", diff --git a/.publish/core/package.json b/.publish/core/package.json index ae8bd6a7..9442ed72 100644 --- a/.publish/core/package.json +++ b/.publish/core/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/core", - "version": "0.8.7", + "version": "0.8.8", "type": "module", "description": "@wrnexus/core — part of the WrNexus framework.", "license": "MIT", diff --git a/.publish/csr/package.json b/.publish/csr/package.json index f762a049..20662018 100644 --- a/.publish/csr/package.json +++ b/.publish/csr/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/csr", - "version": "0.8.7", + "version": "0.8.8", "type": "module", "description": "@wrnexus/csr — part of the WrNexus framework.", "license": "MIT", @@ -37,7 +37,7 @@ } }, "dependencies": { - "@wrnexus/core": "^0.8.7" + "@wrnexus/core": "^0.8.8" }, "files": [ "dist", diff --git a/.publish/db/package.json b/.publish/db/package.json index 78debab7..e94a52b7 100644 --- a/.publish/db/package.json +++ b/.publish/db/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/db", - "version": "0.8.7", + "version": "0.8.8", "type": "module", "description": "Typed database drivers, migrations, instrumentation, repositories, pagination, and transaction helpers.", "license": "MIT", diff --git a/.publish/dev-server/package.json b/.publish/dev-server/package.json index 079da063..e44bc699 100644 --- a/.publish/dev-server/package.json +++ b/.publish/dev-server/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/dev-server", - "version": "0.8.7", + "version": "0.8.8", "type": "module", "description": "@wrnexus/dev-server — part of the WrNexus framework.", "license": "MIT", @@ -41,27 +41,27 @@ } }, "dependencies": { - "@wrnexus/authz": "^0.8.7", - "@wrnexus/rpc": "^0.8.7", - "@wrnexus/core": "^0.8.7", - "@wrnexus/dev-toolbar": "^0.8.7", - "@wrnexus/router": "^0.8.7", - "@wrnexus/ssr": "^0.8.7", - "@wrnexus/csr": "^0.8.7", - "@wrnexus/compiler": "^0.8.7", - "@wrnexus/styles": "^0.8.7", - "@wrnexus/ui": "^0.8.7", - "@wrnexus/validation": "^0.8.7", - "@wrnexus/i18n": "^0.8.7", - "@wrnexus/db": "^0.8.7", - "@wrnexus/pubsub": "^0.8.7", - "@wrnexus/uploader": "^0.8.7", - "@wrnexus/plugin": "^0.8.7", - "@wrnexus/store": "^0.8.7", - "@wrnexus/security": "^0.8.7", - "@wrnexus/observability": "^0.8.7", - "@wrnexus/cache": "^0.8.7", - "@wrnexus/pwa": "^0.8.7" + "@wrnexus/authz": "^0.8.8", + "@wrnexus/rpc": "^0.8.8", + "@wrnexus/core": "^0.8.8", + "@wrnexus/dev-toolbar": "^0.8.8", + "@wrnexus/router": "^0.8.8", + "@wrnexus/ssr": "^0.8.8", + "@wrnexus/csr": "^0.8.8", + "@wrnexus/compiler": "^0.8.8", + "@wrnexus/styles": "^0.8.8", + "@wrnexus/ui": "^0.8.8", + "@wrnexus/validation": "^0.8.8", + "@wrnexus/i18n": "^0.8.8", + "@wrnexus/db": "^0.8.8", + "@wrnexus/pubsub": "^0.8.8", + "@wrnexus/uploader": "^0.8.8", + "@wrnexus/plugin": "^0.8.8", + "@wrnexus/store": "^0.8.8", + "@wrnexus/security": "^0.8.8", + "@wrnexus/observability": "^0.8.8", + "@wrnexus/cache": "^0.8.8", + "@wrnexus/pwa": "^0.8.8" }, "files": [ "dist", diff --git a/.publish/dev-toolbar/package.json b/.publish/dev-toolbar/package.json index d1e32791..a50940cd 100644 --- a/.publish/dev-toolbar/package.json +++ b/.publish/dev-toolbar/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/dev-toolbar", - "version": "0.8.7", + "version": "0.8.8", "type": "module", "description": "@wrnexus/dev-toolbar — part of the WrNexus framework.", "license": "MIT", diff --git a/.publish/encryption/package.json b/.publish/encryption/package.json index 49f760df..457f8fcf 100644 --- a/.publish/encryption/package.json +++ b/.publish/encryption/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/encryption", - "version": "0.8.7", + "version": "0.8.8", "type": "module", "description": "Authenticated encryption, key rotation, hashing, and optional application-layer encrypted HTTP envelopes.", "license": "MIT", @@ -37,7 +37,7 @@ } }, "dependencies": { - "@wrnexus/core": "^0.8.7" + "@wrnexus/core": "^0.8.8" }, "files": [ "dist", diff --git a/.publish/helpers/package.json b/.publish/helpers/package.json index 9bead21f..d8b263aa 100644 --- a/.publish/helpers/package.json +++ b/.publish/helpers/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/helpers", - "version": "0.8.7", + "version": "0.8.8", "type": "module", "description": "Safe convenience helpers for WrNexus request contexts and common application flows.", "license": "MIT", @@ -37,7 +37,7 @@ } }, "dependencies": { - "@wrnexus/core": "^0.8.7" + "@wrnexus/core": "^0.8.8" }, "files": [ "dist", diff --git a/.publish/i18n/package.json b/.publish/i18n/package.json index 232f8559..2b75181e 100644 --- a/.publish/i18n/package.json +++ b/.publish/i18n/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/i18n", - "version": "0.8.7", + "version": "0.8.8", "type": "module", "description": "Locale loading, fallback resolution, SSR/browser translations, formatters, and WRNexusJS language components.", "license": "MIT", @@ -42,9 +42,9 @@ "./components/*": "./components/*" }, "dependencies": { - "@wrnexus/core": "^0.8.7", - "@wrnexus/plugin": "^0.8.7", - "@wrnexus/ui": "^0.8.7" + "@wrnexus/core": "^0.8.8", + "@wrnexus/plugin": "^0.8.8", + "@wrnexus/ui": "^0.8.8" }, "wrnexus": { "plugin": { diff --git a/.publish/jwt/package.json b/.publish/jwt/package.json index 8ed00761..03c9c13b 100644 --- a/.publish/jwt/package.json +++ b/.publish/jwt/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/jwt", - "version": "0.8.7", + "version": "0.8.8", "type": "module", "description": "HS256 JSON Web Tokens, key rotation, access/refresh helpers, scopes, cookies, and auth middleware.", "license": "MIT", @@ -37,7 +37,7 @@ } }, "dependencies": { - "@wrnexus/core": "^0.8.7" + "@wrnexus/core": "^0.8.8" }, "files": [ "dist", diff --git a/.publish/mobile/package.json b/.publish/mobile/package.json index c37ccec8..6b3f5e15 100644 --- a/.publish/mobile/package.json +++ b/.publish/mobile/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/mobile", - "version": "0.8.7", + "version": "0.8.8", "type": "module", "description": "@wrnexus/mobile — part of the WrNexus framework.", "license": "MIT", @@ -37,7 +37,7 @@ } }, "dependencies": { - "@wrnexus/native": "^0.8.7" + "@wrnexus/native": "^0.8.8" }, "files": [ "dist", diff --git a/.publish/native/package.json b/.publish/native/package.json index 9c3c043c..2c4e78c3 100644 --- a/.publish/native/package.json +++ b/.publish/native/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/native", - "version": "0.8.7", + "version": "0.8.8", "type": "module", "description": "@wrnexus/native — part of the WrNexus framework.", "license": "MIT", diff --git a/.publish/oauth/package.json b/.publish/oauth/package.json index 50c959f3..94b3d556 100644 --- a/.publish/oauth/package.json +++ b/.publish/oauth/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/oauth", - "version": "0.8.7", + "version": "0.8.8", "type": "module", "description": "@wrnexus/oauth — part of the WrNexus framework.", "license": "MIT", @@ -37,7 +37,7 @@ } }, "dependencies": { - "@wrnexus/jwt": "^0.8.7" + "@wrnexus/jwt": "^0.8.8" }, "files": [ "dist", diff --git a/.publish/plugin/package.json b/.publish/plugin/package.json index 59468e4f..0aa05a84 100644 --- a/.publish/plugin/package.json +++ b/.publish/plugin/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/plugin", - "version": "0.8.7", + "version": "0.8.8", "type": "module", "description": "@wrnexus/plugin — part of the WrNexus framework.", "license": "MIT", @@ -49,7 +49,7 @@ } }, "dependencies": { - "@wrnexus/syntax": "^0.8.7" + "@wrnexus/syntax": "^0.8.8" }, "files": [ "dist", diff --git a/.publish/pubsub/package.json b/.publish/pubsub/package.json index c420b2ec..b5967a03 100644 --- a/.publish/pubsub/package.json +++ b/.publish/pubsub/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/pubsub", - "version": "0.8.7", + "version": "0.8.8", "type": "module", "description": "@wrnexus/pubsub — part of the WrNexus framework.", "license": "MIT", @@ -45,8 +45,8 @@ } }, "dependencies": { - "@wrnexus/core": "^0.8.7", - "@wrnexus/rpc": "^0.8.7" + "@wrnexus/core": "^0.8.8", + "@wrnexus/rpc": "^0.8.8" }, "files": [ "dist", diff --git a/.publish/queue/package.json b/.publish/queue/package.json index 76ff188a..08b4acdd 100644 --- a/.publish/queue/package.json +++ b/.publish/queue/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/queue", - "version": "0.8.7", + "version": "0.8.8", "type": "module", "description": "@wrnexus/queue — part of the WrNexus framework.", "license": "MIT", @@ -37,8 +37,8 @@ } }, "dependencies": { - "@wrnexus/core": "^0.8.7", - "@wrnexus/rpc": "^0.8.7" + "@wrnexus/core": "^0.8.8", + "@wrnexus/rpc": "^0.8.8" }, "files": [ "dist", diff --git a/.publish/reactive/package.json b/.publish/reactive/package.json index 4d965463..9a058be4 100644 --- a/.publish/reactive/package.json +++ b/.publish/reactive/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/reactive", - "version": "0.8.7", + "version": "0.8.8", "type": "module", "description": "@wrnexus/reactive — part of the WrNexus framework.", "license": "MIT", diff --git a/.publish/router/package.json b/.publish/router/package.json index dfab14e8..b3ec1ba9 100644 --- a/.publish/router/package.json +++ b/.publish/router/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/router", - "version": "0.8.7", + "version": "0.8.8", "type": "module", "description": "@wrnexus/router — part of the WrNexus framework.", "license": "MIT", @@ -37,8 +37,8 @@ } }, "dependencies": { - "@wrnexus/compiler": "^0.8.7", - "@wrnexus/core": "^0.8.7" + "@wrnexus/compiler": "^0.8.8", + "@wrnexus/core": "^0.8.8" }, "files": [ "dist", diff --git a/.publish/ssr/package.json b/.publish/ssr/package.json index d3c109c1..ab735ec4 100644 --- a/.publish/ssr/package.json +++ b/.publish/ssr/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/ssr", - "version": "0.8.7", + "version": "0.8.8", "type": "module", "description": "@wrnexus/ssr — part of the WrNexus framework.", "license": "MIT", @@ -45,9 +45,9 @@ } }, "dependencies": { - "@wrnexus/core": "^0.8.7", - "@wrnexus/store": "^0.8.7", - "@wrnexus/security": "^0.8.7" + "@wrnexus/core": "^0.8.8", + "@wrnexus/store": "^0.8.8", + "@wrnexus/security": "^0.8.8" }, "files": [ "dist", diff --git a/.publish/styles/package.json b/.publish/styles/package.json index 4c7b4cb1..c7edfa0a 100644 --- a/.publish/styles/package.json +++ b/.publish/styles/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/styles", - "version": "0.8.7", + "version": "0.8.8", "type": "module", "description": "@wrnexus/styles — part of the WrNexus framework.", "license": "MIT", @@ -37,9 +37,9 @@ } }, "dependencies": { - "@wrnexus/uploader": "^0.8.7", - "@wrnexus/core": "^0.8.7", - "@wrnexus/plugin": "^0.8.7" + "@wrnexus/uploader": "^0.8.8", + "@wrnexus/core": "^0.8.8", + "@wrnexus/plugin": "^0.8.8" }, "files": [ "dist", diff --git a/.publish/syntax/package.json b/.publish/syntax/package.json index 84dc4cba..225fffd1 100644 --- a/.publish/syntax/package.json +++ b/.publish/syntax/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/syntax", - "version": "0.8.7", + "version": "0.8.8", "type": "module", "description": "@wrnexus/syntax — part of the WrNexus framework.", "license": "MIT", diff --git a/.publish/test/package.json b/.publish/test/package.json index 172f1f15..60270cec 100644 --- a/.publish/test/package.json +++ b/.publish/test/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/test", - "version": "0.8.7", + "version": "0.8.8", "type": "module", "description": "@wrnexus/test — part of the WrNexus framework.", "license": "MIT", diff --git a/.publish/tracking/package.json b/.publish/tracking/package.json index 57ba30bb..f2ab3286 100644 --- a/.publish/tracking/package.json +++ b/.publish/tracking/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/tracking", - "version": "0.8.7", + "version": "0.8.8", "type": "module", "description": "@wrnexus/tracking — part of the WrNexus framework.", "license": "MIT", diff --git a/.publish/ui/package.json b/.publish/ui/package.json index 2cc67677..f4e2361f 100644 --- a/.publish/ui/package.json +++ b/.publish/ui/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/ui", - "version": "0.8.7", + "version": "0.8.8", "type": "module", "description": "@wrnexus/ui — part of the WrNexus framework.", "license": "MIT", @@ -46,7 +46,7 @@ "./ui.css": "./ui.css" }, "dependencies": { - "@wrnexus/core": "^0.8.7" + "@wrnexus/core": "^0.8.8" }, "files": [ "dist", diff --git a/.publish/uploader/package.json b/.publish/uploader/package.json index ed54f4e1..67e77713 100644 --- a/.publish/uploader/package.json +++ b/.publish/uploader/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/uploader", - "version": "0.8.7", + "version": "0.8.8", "type": "module", "description": "Secure upload drivers, policies, client runtime, helper functions, and reusable upload components.", "license": "MIT", @@ -42,9 +42,9 @@ "./components/*": "./components/*" }, "dependencies": { - "@wrnexus/core": "^0.8.7", - "@wrnexus/plugin": "^0.8.7", - "@wrnexus/ui": "^0.8.7" + "@wrnexus/core": "^0.8.8", + "@wrnexus/plugin": "^0.8.8", + "@wrnexus/ui": "^0.8.8" }, "wrnexus": { "plugin": { diff --git a/.publish/validation/package.json b/.publish/validation/package.json index a6388259..ed052e97 100644 --- a/.publish/validation/package.json +++ b/.publish/validation/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/validation", - "version": "0.8.7", + "version": "0.8.8", "type": "module", "description": "Shared server/browser schemas, validation helpers, form runtime, and reusable error components.", "license": "MIT", @@ -42,9 +42,9 @@ "./components/*": "./components/*" }, "dependencies": { - "@wrnexus/core": "^0.8.7", - "@wrnexus/plugin": "^0.8.7", - "@wrnexus/ui": "^0.8.7" + "@wrnexus/core": "^0.8.8", + "@wrnexus/plugin": "^0.8.8", + "@wrnexus/ui": "^0.8.8" }, "wrnexus": { "plugin": { diff --git a/CHANGELOG.md b/CHANGELOG.md index bd03958c..6a3e7460 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 0.8.8 + +- Added the framework request context to `.wrn` language-server type environments. +- Kept workspace type declarations inside runnable apps during updates. +- Regenerated lint-safe application declarations before update verification. + All notable framework changes are recorded here. Every release must also include an idempotent entry in the CLI migration registry. diff --git a/bun.lock b/bun.lock index 9090615b..e4702699 100644 --- a/bun.lock +++ b/bun.lock @@ -208,11 +208,11 @@ }, "packages/ai": { "name": "@wrnexus/ai", - "version": "0.8.7", + "version": "0.8.8", }, "packages/auth": { "name": "@wrnexus/auth", - "version": "0.8.7", + "version": "0.8.8", "dependencies": { "@wrnexus/authz": "workspace:*", "@wrnexus/captcha": "workspace:*", @@ -234,7 +234,7 @@ }, "packages/authz": { "name": "@wrnexus/authz", - "version": "0.8.7", + "version": "0.8.8", "dependencies": { "@wrnexus/core": "workspace:*", "@wrnexus/db": "workspace:*", @@ -242,18 +242,18 @@ }, "packages/benchmark": { "name": "@wrnexus/benchmark", - "version": "0.8.7", + "version": "0.8.8", }, "packages/cache": { "name": "@wrnexus/cache", - "version": "0.8.7", + "version": "0.8.8", "dependencies": { "@wrnexus/core": "workspace:*", }, }, "packages/captcha": { "name": "@wrnexus/captcha", - "version": "0.8.7", + "version": "0.8.8", "dependencies": { "@wrnexus/core": "workspace:*", "@wrnexus/plugin": "workspace:*", @@ -268,7 +268,7 @@ }, "packages/cli": { "name": "@wrnexus/cli", - "version": "0.8.7", + "version": "0.8.8", "bin": { "wrnexus": "src/index.ts", }, @@ -295,7 +295,7 @@ }, "packages/compiler": { "name": "@wrnexus/compiler", - "version": "0.8.7", + "version": "0.8.8", "dependencies": { "@wrnexus/csr": "workspace:*", "@wrnexus/store": "workspace:*", @@ -305,7 +305,7 @@ }, "packages/content": { "name": "@wrnexus/content", - "version": "0.8.7", + "version": "0.8.8", "devDependencies": { "@types/bun": "^1.3.14", "typescript": "^5.9.2", @@ -313,18 +313,18 @@ }, "packages/core": { "name": "@wrnexus/core", - "version": "0.8.7", + "version": "0.8.8", }, "packages/csr": { "name": "@wrnexus/csr", - "version": "0.8.7", + "version": "0.8.8", "dependencies": { "@wrnexus/core": "workspace:*", }, }, "packages/db": { "name": "@wrnexus/db", - "version": "0.8.7", + "version": "0.8.8", "devDependencies": { "@types/bun": "^1.3.14", "typescript": "^5.9.2", @@ -332,7 +332,7 @@ }, "packages/dev-server": { "name": "@wrnexus/dev-server", - "version": "0.8.7", + "version": "0.8.8", "dependencies": { "@wrnexus/authz": "workspace:*", "@wrnexus/cache": "workspace:*", @@ -359,7 +359,7 @@ }, "packages/dev-toolbar": { "name": "@wrnexus/dev-toolbar", - "version": "0.8.7", + "version": "0.8.8", "devDependencies": { "@types/bun": "^1.3.14", "typescript": "^5.9.2", @@ -367,7 +367,7 @@ }, "packages/encryption": { "name": "@wrnexus/encryption", - "version": "0.8.7", + "version": "0.8.8", "dependencies": { "@wrnexus/core": "workspace:*", }, @@ -378,21 +378,21 @@ }, "packages/graphql": { "name": "@wrnexus/graphql", - "version": "0.8.7", + "version": "0.8.8", "dependencies": { "@wrnexus/plugin": "workspace:*", }, }, "packages/helpers": { "name": "@wrnexus/helpers", - "version": "0.8.7", + "version": "0.8.8", "dependencies": { "@wrnexus/core": "workspace:*", }, }, "packages/i18n": { "name": "@wrnexus/i18n", - "version": "0.8.7", + "version": "0.8.8", "dependencies": { "@wrnexus/core": "workspace:*", "@wrnexus/plugin": "workspace:*", @@ -406,7 +406,7 @@ }, "packages/identity": { "name": "@wrnexus/identity", - "version": "0.8.7", + "version": "0.8.8", "dependencies": { "@wrnexus/auth": "workspace:*", "@wrnexus/authz": "workspace:*", @@ -415,7 +415,7 @@ }, "packages/image": { "name": "@wrnexus/image", - "version": "0.8.7", + "version": "0.8.8", "dependencies": { "@wrnexus/plugin": "workspace:*", "@wrnexus/security": "workspace:*", @@ -435,7 +435,7 @@ }, "packages/jwt": { "name": "@wrnexus/jwt", - "version": "0.8.7", + "version": "0.8.8", "dependencies": { "@wrnexus/core": "workspace:*", }, @@ -446,7 +446,7 @@ }, "packages/language-server": { "name": "@wrnexus/language-server", - "version": "0.8.7", + "version": "0.8.8", "bin": { "wrnexus-language-server": "src/server.ts", }, @@ -457,39 +457,39 @@ }, "packages/mcp": { "name": "@wrnexus/mcp", - "version": "0.8.7", + "version": "0.8.8", "bin": { "wrnexus-mcp": "src/stdio.ts", }, }, "packages/mobile": { "name": "@wrnexus/mobile", - "version": "0.8.7", + "version": "0.8.8", "dependencies": { "@wrnexus/native": "workspace:*", }, }, "packages/native": { "name": "@wrnexus/native", - "version": "0.8.7", + "version": "0.8.8", }, "packages/oauth": { "name": "@wrnexus/oauth", - "version": "0.8.7", + "version": "0.8.8", "dependencies": { "@wrnexus/jwt": "workspace:*", }, }, "packages/observability": { "name": "@wrnexus/observability", - "version": "0.8.7", + "version": "0.8.8", "dependencies": { "@wrnexus/core": "workspace:*", }, }, "packages/playground": { "name": "@wrnexus/playground", - "version": "0.8.7", + "version": "0.8.8", "dependencies": { "@wrnexus/compiler": "workspace:*", "@wrnexus/syntax": "workspace:*", @@ -497,14 +497,14 @@ }, "packages/plugin": { "name": "@wrnexus/plugin", - "version": "0.8.7", + "version": "0.8.8", "dependencies": { "@wrnexus/syntax": "workspace:*", }, }, "packages/pubsub": { "name": "@wrnexus/pubsub", - "version": "0.8.7", + "version": "0.8.8", "dependencies": { "@wrnexus/core": "workspace:*", "@wrnexus/rpc": "workspace:*", @@ -512,7 +512,7 @@ }, "packages/pwa": { "name": "@wrnexus/pwa", - "version": "0.8.7", + "version": "0.8.8", "devDependencies": { "@types/bun": "^1.3.14", "typescript": "^5.9.2", @@ -520,7 +520,7 @@ }, "packages/queue": { "name": "@wrnexus/queue", - "version": "0.8.7", + "version": "0.8.8", "dependencies": { "@wrnexus/core": "workspace:*", "@wrnexus/rpc": "workspace:*", @@ -528,11 +528,11 @@ }, "packages/reactive": { "name": "@wrnexus/reactive", - "version": "0.8.7", + "version": "0.8.8", }, "packages/realtime": { "name": "@wrnexus/realtime", - "version": "0.8.7", + "version": "0.8.8", "dependencies": { "@wrnexus/core": "workspace:*", "@wrnexus/plugin": "workspace:*", @@ -546,7 +546,7 @@ }, "packages/router": { "name": "@wrnexus/router", - "version": "0.8.7", + "version": "0.8.8", "dependencies": { "@wrnexus/compiler": "workspace:*", "@wrnexus/core": "workspace:*", @@ -554,7 +554,7 @@ }, "packages/rpc": { "name": "@wrnexus/rpc", - "version": "0.8.7", + "version": "0.8.8", "dependencies": { "@wrnexus/core": "workspace:*", "@wrnexus/helpers": "workspace:*", @@ -568,14 +568,14 @@ }, "packages/security": { "name": "@wrnexus/security", - "version": "0.8.7", + "version": "0.8.8", "dependencies": { "@wrnexus/core": "workspace:*", }, }, "packages/ssr": { "name": "@wrnexus/ssr", - "version": "0.8.7", + "version": "0.8.8", "dependencies": { "@wrnexus/core": "workspace:*", "@wrnexus/security": "workspace:*", @@ -584,11 +584,11 @@ }, "packages/store": { "name": "@wrnexus/store", - "version": "0.8.7", + "version": "0.8.8", }, "packages/styles": { "name": "@wrnexus/styles", - "version": "0.8.7", + "version": "0.8.8", "dependencies": { "@wrnexus/core": "workspace:*", "@wrnexus/plugin": "workspace:*", @@ -597,19 +597,19 @@ }, "packages/syntax": { "name": "@wrnexus/syntax", - "version": "0.8.7", + "version": "0.8.8", }, "packages/test": { "name": "@wrnexus/test", - "version": "0.8.7", + "version": "0.8.8", }, "packages/tracking": { "name": "@wrnexus/tracking", - "version": "0.8.7", + "version": "0.8.8", }, "packages/typecheck": { "name": "@wrnexus/typecheck", - "version": "0.8.7", + "version": "0.8.8", "dependencies": { "@wrnexus/syntax": "workspace:*", "typescript": "^5.5.0", @@ -617,14 +617,14 @@ }, "packages/ui": { "name": "@wrnexus/ui", - "version": "0.8.7", + "version": "0.8.8", "dependencies": { "@wrnexus/core": "workspace:*", }, }, "packages/uploader": { "name": "@wrnexus/uploader", - "version": "0.8.7", + "version": "0.8.8", "dependencies": { "@wrnexus/core": "workspace:*", "@wrnexus/plugin": "workspace:*", @@ -638,7 +638,7 @@ }, "packages/validation": { "name": "@wrnexus/validation", - "version": "0.8.7", + "version": "0.8.8", "dependencies": { "@wrnexus/core": "workspace:*", "@wrnexus/plugin": "workspace:*", diff --git a/docs/public-api-0.8.json b/docs/public-api-0.8.json index 9a5c135c..d8640777 100644 --- a/docs/public-api-0.8.json +++ b/docs/public-api-0.8.json @@ -1830,10 +1830,13 @@ "htmlToWrn", "offsetAt", "positionAt", + "semanticTokens", + "semanticTokensLegend", "symbolLocations", "virtualTypeScriptDocument", "wordAt", - "workspaceCompletionItems" + "workspaceCompletionItems", + "workspaceSymbolLocations" ], "./server": [] }, diff --git a/editors/vscode/.vscodeignore b/editors/vscode/.vscodeignore index dbfc5b14..420cd496 100644 --- a/editors/vscode/.vscodeignore +++ b/editors/vscode/.vscodeignore @@ -10,3 +10,5 @@ package-lock.json bun.lock ../../** node_modules/** +src/*.js +src/compiler.cjs diff --git a/editors/vscode/package-lock.json b/editors/vscode/package-lock.json index 7a3b6f70..db4b026b 100644 --- a/editors/vscode/package-lock.json +++ b/editors/vscode/package-lock.json @@ -1,12 +1,12 @@ { "name": "wrnexus", - "version": "0.8.7", + "version": "0.8.8", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "wrnexus", - "version": "0.8.7", + "version": "0.8.8", "license": "SEE LICENSE IN LICENSE", "dependencies": { "vscode-languageclient": "^10.1.0" diff --git a/editors/vscode/package.json b/editors/vscode/package.json index 07f7623a..9a45466f 100644 --- a/editors/vscode/package.json +++ b/editors/vscode/package.json @@ -2,7 +2,7 @@ "name": "wrnexus", "displayName": "WRNexus Language Support", "description": "Complete WRNexus v0.8.3 language support for typed imports, props, state, outputs, runtime functions, stores, diagnostics, formatting, navigation, and migration assistance.", - "version": "0.8.7", + "version": "0.8.8", "publisher": "wrnexus", "private": true, "license": "SEE LICENSE IN LICENSE", @@ -275,9 +275,9 @@ "validate": "node test/validate.mjs && node ../../scripts/build-editor-compiler.mjs --check && node ../../scripts/build-editor-language-server.mjs --check && node ../../scripts/build-editor-extension.mjs --check", "check": "bun run build && bun run test && bun run validate", "vscode:prepublish": "bun run check", - "package": "vsce package --no-dependencies", - "publish": "vsce publish --no-dependencies", - "publish:azure": "vsce publish --no-dependencies --azure-credential" + "package": "vsce package --no-dependencies --no-rewrite-relative-links", + "publish": "vsce publish --no-dependencies --no-rewrite-relative-links", + "publish:azure": "vsce publish --no-dependencies --no-rewrite-relative-links --azure-credential" }, "devDependencies": { "@vscode/vsce": "^3.9.2" diff --git a/editors/vscode/src/extension.bundle.cjs b/editors/vscode/src/extension.bundle.cjs index fd1fb06f..40eff1a8 100644 --- a/editors/vscode/src/extension.bundle.cjs +++ b/editors/vscode/src/extension.bundle.cjs @@ -1,4 +1,4 @@ -// WRN editor extension source hash: d5eb04a05be154cd682a6dd2efa7bda4a977dbd2d3ac4d110995e9a25488c3b2 +// WRN editor extension source hash: b455291b3d69fabfba7d10889ff628f90579432d07cd871f9e39c336e6c8851f // WRN editor extension generator hash: 456d1d614e44e5fb1f19b784176c09cf2ade9b64ef73a17934c2698150b62728 "use strict"; var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports); @@ -22701,9474 +22701,12 @@ var require_main5 = __commonJS((exports2) => { } }); -// editors/vscode/src/compiler.cjs -var require_compiler = __commonJS((exports2, module2) => { - var __nodeRequire = require; - var __path = __nodeRequire("node:path"); - var __modules = { - "packages/compiler/src/analysis.ts": function(module3, exports3, require2, __filename2, __dirname2) { - Object.defineProperty(exports3, "__esModule", { value: true }); - exports3.optimizeAst = optimizeAst; - exports3.analyzeOptimizations = analyzeOptimizations; - exports3.analyzeRuntimeRequirements = analyzeRuntimeRequirements; - function identifiers(value) { - return new Set(value.match(/[A-Za-z_$][\w$]*/g) ?? []); - } - function literalBoolean(expression) { - if (expression === null) - return true; - const value = expression.trim(); - if (value === "true") - return true; - if (value === "false" || value === "null" || value === "undefined" || value === "0" || value === "''" || value === '""') - return false; - if (/^-?(?:[1-9]\d*|0?\.\d+)$/.test(value) || /^(['"]).+\1$/.test(value)) - return true; - return; - } - function optimizeNodes(nodes, report) { - const output = []; - for (const node of nodes) { - if (node.type === "element") - output.push({ - ...node, - attrs: node.attrs.map((attribute) => ({ ...attribute })), - children: optimizeNodes(node.children, report) - }); - else if (node.type === "each") - output.push({ - ...node, - body: optimizeNodes(node.body, report), - empty: optimizeNodes(node.empty, report) - }); - else if (node.type === "if") { - let selected; - let dynamic = false; - for (const branch of node.branches) { - const value = literalBoolean(branch.cond); - if (value === undefined) { - dynamic = true; - break; - } - report.eliminated++; - if (value) { - selected = branch.body; - break; - } - } - if (dynamic) - output.push({ - ...node, - branches: node.branches.map((branch) => ({ - ...branch, - body: optimizeNodes(branch.body, report) - })) - }); - else if (selected) - output.push(...optimizeNodes(selected, report)); - } else - output.push({ ...node }); - } - return output; - } - function optimizeAst(ast) { - const report = { eliminated: 0 }; - return { - ast: { ...ast, view: optimizeNodes(ast.view, report) }, - eliminatedBranches: report.eliminated - }; - } - function analyzeOptimizations(ast) { - const used = new Set; - let staticNodes = 0; - let reactiveRegions = 0; - const componentNames = new Set; - const staticClasses = new Set; - const visit = (nodes) => { - for (const node of nodes) { - if (node.type === "text") { - const refs = identifiers(node.value); - refs.forEach((name) => used.add(name)); - if (node.value.includes("{")) - reactiveRegions++; - else - staticNodes++; - } else if (node.type === "element") { - if (/^[A-Z]/.test(node.tag)) - componentNames.add(node.tag); - let reactive = false; - for (const attribute of node.attrs) { - identifiers(attribute.value).forEach((name) => used.add(name)); - reactive ||= attribute.event || attribute.value.includes("{"); - if (attribute.name === "class" && !attribute.value.includes("{")) { - for (const name of attribute.value.split(/\s+/)) - if (name) - staticClasses.add(name); - } - } - if (reactive) - reactiveRegions++; - else - staticNodes++; - visit(node.children); - } else if (node.type === "each") { - identifiers(`${node.list} ${node.key ?? ""}`).forEach((name) => used.add(name)); - reactiveRegions++; - visit(node.body); - visit(node.empty); - } else { - for (const branch of node.branches) { - identifiers(branch.cond ?? "").forEach((name) => used.add(name)); - visit(branch.body); - } - reactiveRegions++; - } - } - }; - visit(ast.view); - const handlerReferences = new Set(used); - const executable = [ - ...ast.runtimeFunctions.map((fn) => fn.body), - ...ast.functions, - ...ast.effects.map((effect) => effect.body), - ...ast.watches.map((watch) => watch.body), - ...ast.actions.map((action) => action.body) - ].join(` -`); - identifiers(executable).forEach((name) => used.add(name)); - const localCss = new Set(ast.styles.flatMap((style) => [...style.matchAll(/\.([_a-zA-Z][\w-]*)/g)].map((match) => match[1]))); - const optimized = optimizeAst(ast); - const assignmentCounts = ast.runtimeFunctions.map((fn) => ast.states.filter((state) => new RegExp(`\\b${state.name}\\s*(?:[+*/-]?=|\\+\\+|--)`).test(fn.body)).length); - return { - staticNodes, - reactiveRegions, - eliminatedBranches: optimized.eliminatedBranches, - unusedState: ast.states.filter((state) => !used.has(state.name)).map((state) => state.name), - unusedHandlers: ast.runtimeFunctions.filter((fn) => fn.runtime !== "server" && !handlerReferences.has(fn.name)).map((fn) => fn.name), - constantProps: ast.props.filter((prop) => /^(?:-?\d+(?:\.\d+)?|true|false|null|(['"]).*\1)$/.test(prop.default.trim())).map((prop) => prop.name), - unusedLocalCssClasses: [...localCss].filter((name) => !staticClasses.has(name)).sort(), - batchableStateUpdates: assignmentCounts.filter((count) => count > 1).reduce((sum, count) => sum + count - 1, 0), - memoizableComponents: [...componentNames].sort(), - preloadDependencies: ast.structuredImports.filter((entry) => !entry.typeOnly && !entry.source.startsWith("node:")).map((entry) => entry.source), - serverOnlyModules: ast.structuredImports.filter((entry) => entry.source.startsWith("node:") || ast.runtime === "server").map((entry) => entry.source) - }; - } - function hasEvent(nodes) { - for (const node of nodes) { - if (node.type === "element") { - if (node.attrs.some((attribute) => attribute.event)) - return true; - if (hasEvent(node.children)) - return true; - } else if (node.type === "each") { - if (hasEvent(node.body) || hasEvent(node.empty)) - return true; - } else if (node.type === "if") { - if (node.branches.some((branch) => hasEvent(branch.body))) - return true; - } - } - return false; - } - function analyzeRuntimeRequirements(ast) { - const reasons = []; - const clientFunctions = ast.runtimeFunctions.some((fn) => fn.runtime !== "server"); - const clientState = ast.states.some((state) => state.runtime !== "server"); - const interactive = clientFunctions || clientState || ast.effects.length > 0 || ast.watches.length > 0 || hasEvent(ast.view); - if (interactive) - reasons.push("client interactivity"); - const requestData = ast.loads.length > 0 || ast.actions.length > 0 || ast.dataApis.length > 0 || ast.apis.length > 0 || ast.realtimes.length > 0 || ast.runtimeFunctions.some((fn) => fn.runtime === "server") || ast.states.some((state) => state.runtime === "server"); - if (requestData) - reasons.push("server/request data"); - const authenticated = /^(?:required|true)$/i.test(ast.security.auth ?? ""); - if (authenticated) - reasons.push("authentication required"); - const streaming = /^(?:true|required)$/i.test(ast.security.streaming ?? ""); - if (streaming) - reasons.push("streaming enabled"); - let kind; - if (streaming) - kind = "streaming-ssr"; - else if (authenticated) - kind = "authenticated-ssr"; - else if (requestData && interactive) - kind = "dynamic"; - else if (requestData) - kind = "request-ssr"; - else if (interactive) - kind = "static-interactive"; - else - kind = "static"; - if (ast.renderMode === "static") { - kind = "static"; - reasons.push("explicit static rendering"); - } else if (ast.renderMode === "server") { - kind = requestData ? "request-ssr" : "static"; - reasons.push("explicit server rendering"); - } else if (ast.renderMode === "client") { - kind = "static-interactive"; - reasons.push("explicit client rendering"); - } else if (ast.renderMode === "partial-static") { - kind = "streaming-ssr"; - reasons.push("partial-static shell with streamed dynamic regions"); - } - const clientDisabled = ast.renderMode === "static" || ast.renderMode === "server"; - const serverDisabled = ast.renderMode === "client"; - return { - kind, - canPrerender: kind === "static" || kind === "static-interactive", - needsClientRuntime: !clientDisabled && (interactive || ast.renderMode === "client") && ast.hydrate !== "none" && ast.runtime !== "server", - needsServerRuntime: !serverDisabled && (requestData || authenticated || streaming || ast.renderMode === "server" || ["server", "edge", "worker", "service-worker"].includes(ast.runtime ?? "")), - hydrationStrategy: clientDisabled ? null : interactive ? ast.hydrate ?? "load" : null, - reasons, - optimization: analyzeOptimizations(ast), - cachePolicy: { ...ast.cache ?? {} }, - requiredPermission: ast.security.permission ?? null - }; - } - }, - "packages/compiler/src/cache.ts": function(module3, exports3, require2, __filename2, __dirname2) { - Object.defineProperty(exports3, "__esModule", { value: true }); - exports3.DependencyGraph = undefined; - exports3.compilationKey = compilationKey; - exports3.createCompilationCache = createCompilationCache; - const node_crypto_1 = require2("node:crypto"); - const syntax_1 = require2("@wrnexus/syntax"); - const codegen_ts_1 = require2("./codegen.js"); - function compileSource(source, filePath) { - const richDiagnostics = (0, syntax_1.diagnose)(source, { file: filePath, accessibility: true }); - const errors = richDiagnostics.filter((diagnostic) => diagnostic.severity === "error"); - if (errors.length > 0) { - throw new syntax_1.ParseError(errors.map((diagnostic) => diagnostic.message).join(` -`), errors[0].code); - } - const ast = (0, syntax_1.parse)(source); - return { - code: `// compiled from .wrn -${(0, codegen_ts_1.generate)(ast)}`, - ast, - diagnostics: richDiagnostics.map((diagnostic) => `${diagnostic.code}: ${diagnostic.message}`), - richDiagnostics - }; - } - function compilationKey(source, file = "", salt = "") { - return (0, node_crypto_1.createHash)("sha256").update(file).update("\x00").update(salt).update("\x00").update(source).digest("hex"); - } - function createCompilationCache(options = {}) { - const maxEntries = options.maxEntries ?? 500; - if (!Number.isInteger(maxEntries) || maxEntries < 1) - throw new RangeError("maxEntries must be positive"); - const now = options.now ?? Date.now; - const entries = new Map; - let hits = 0; - let misses = 0; - function touch(key, value) { - entries.delete(key); - entries.set(key, value); - while (entries.size > maxEntries) - entries.delete(entries.keys().next().value); - } - return { - compile(source, file = "", salt = "") { - const key = compilationKey(source, file, salt); - const existing = entries.get(key); - if (existing) { - hits++; - touch(key, existing); - return existing; - } - misses++; - const result = compileSource(source, file); - const entry = { - ...result, - key, - file, - sourceHash: (0, node_crypto_1.createHash)("sha256").update(source).digest("hex"), - createdAt: now() - }; - touch(key, entry); - return entry; - }, - get(key) { - const entry = entries.get(key); - if (entry) - touch(key, entry); - return entry; - }, - invalidate(file) { - let removed = 0; - for (const [key, entry] of entries) { - if (!file || entry.file === file) { - entries.delete(key); - removed++; - } - } - return removed; - }, - clear() { - entries.clear(); - }, - size: () => entries.size, - stats: () => ({ hits, misses, entries: entries.size }) - }; - } - - class DependencyGraph { - #dependencies = new Map; - #dependents = new Map; - set(file, dependencies) { - this.remove(file); - const values = new Set(dependencies); - this.#dependencies.set(file, values); - for (const dependency of values) { - const set = this.#dependents.get(dependency) ?? new Set; - set.add(file); - this.#dependents.set(dependency, set); - } - } - remove(file) { - for (const dependency of this.#dependencies.get(file) ?? []) { - const set = this.#dependents.get(dependency); - set?.delete(file); - if (set?.size === 0) - this.#dependents.delete(dependency); - } - this.#dependencies.delete(file); - } - dependencies(file) { - return [...this.#dependencies.get(file) ?? []].sort(); - } - dependents(file) { - return [...this.#dependents.get(file) ?? []].sort(); - } - affected(file) { - const found = new Set; - const queue = [file]; - while (queue.length) { - const current = queue.shift(); - for (const dependent of this.#dependents.get(current) ?? []) { - if (found.has(dependent)) - continue; - found.add(dependent); - queue.push(dependent); - } - } - return [...found].sort(); - } - } - exports3.DependencyGraph = DependencyGraph; - }, - "packages/compiler/src/client-codegen.ts": function(module3, exports3, require2, __filename2, __dirname2) { - Object.defineProperty(exports3, "__esModule", { value: true }); - exports3.browserModuleRequired = browserModuleRequired; - exports3.generateBrowserModule = generateBrowserModule; - const syntax_1 = require2("@wrnexus/syntax"); - const RESERVED_BINDINGS = new Set([ - "await", - "break", - "case", - "catch", - "class", - "const", - "continue", - "debugger", - "default", - "delete", - "do", - "else", - "enum", - "export", - "extends", - "false", - "finally", - "for", - "function", - "if", - "implements", - "import", - "in", - "instanceof", - "interface", - "let", - "new", - "null", - "package", - "private", - "protected", - "public", - "return", - "static", - "super", - "switch", - "this", - "throw", - "true", - "try", - "typeof", - "var", - "void", - "while", - "with", - "yield" - ]); - const RUNTIME_BINDINGS = new Set([ - "context", - "state", - "output", - "server", - "props", - "refs", - "event", - "payload" - ]); - function safeIdentifier(name) { - return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) && !RESERVED_BINDINGS.has(name); - } - function identifierReferenced(source, name) { - const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return new RegExp(`(?:^|[^A-Za-z0-9_$])${escaped}(?![A-Za-z0-9_$])`).test(source); - } - function viewReferenceSource(nodes) { - return nodes.flatMap((node) => { - if (node.type === "text") - return [node.value]; - if (node.type === "each") { - return [ - node.list, - node.key ?? "", - ...viewReferenceSource(node.body), - ...viewReferenceSource(node.empty) - ]; - } - if (node.type === "if") { - return node.branches.flatMap((branch) => [ - branch.cond ?? "", - ...viewReferenceSource(branch.body) - ]); - } - return [...node.attrs.map((attr) => attr.value), ...viewReferenceSource(node.children)]; - }); - } - function browserReferenceSource(ast, functions) { - return [ - ...functions.flatMap((fn) => [fn.source, fn.body]), - ...ast.computed.map((entry) => entry.expr), - ...ast.effects, - ast.lifecycle.mount ?? "", - ast.lifecycle.update ?? "", - ast.lifecycle.unmount ?? "", - ...viewReferenceSource(ast.view) - ].join(` -`); - } - function renderSelectedImport(entry, source) { - if (entry.typeOnly) - return null; - const isStore = entry.source.endsWith(".wrn") && /(?:^|\/)stores?\//.test(entry.source); - if (entry.source.endsWith(".wrn") && !isStore) - return null; - const defaultImport = entry.defaultImport && (isStore || identifierReferenced(source, entry.defaultImport)) ? entry.defaultImport : undefined; - const namespaceImport = entry.namespaceImport && (isStore || identifierReferenced(source, entry.namespaceImport)) ? entry.namespaceImport : undefined; - const namedImports = entry.namedImports.filter((item) => !item.typeOnly && (isStore || identifierReferenced(source, item.local))); - const bindings = [ - ...defaultImport ? [defaultImport] : [], - ...namespaceImport ? [namespaceImport] : [], - ...namedImports.map((item) => item.local) - ].filter(safeIdentifier); - const hasBindings = Boolean(defaultImport || namespaceImport || namedImports.length); - const sideEffectOnly = !entry.defaultImport && !entry.namespaceImport && entry.namedImports.length === 0; - if (!hasBindings && !sideEffectOnly) - return null; - if (sideEffectOnly) - return { code: entry.raw, bindings: [] }; - const clauses = []; - if (defaultImport) - clauses.push(defaultImport); - if (namespaceImport) - clauses.push(`* as ${namespaceImport}`); - if (namedImports.length) { - clauses.push(`{ ${namedImports.map((item) => item.imported === item.local ? item.imported : `${item.imported} as ${item.local}`).join(", ")} }`); - } - return { - code: `import ${clauses.join(", ")} from ${JSON.stringify(entry.source)};`, - bindings - }; - } - function selectedBrowserImports(ast, functions) { - const referenceSource = browserReferenceSource(ast, functions); - return ast.structuredImports.map((entry) => renderSelectedImport(entry, referenceSource)).filter((entry) => entry !== null); - } - function browserModuleRequired(ast) { - const functions = ast.runtimeFunctions.filter((fn) => ["legacy", "client", "shared"].includes(fn.runtime)); - return functions.length > 0 || selectedBrowserImports(ast, functions).length > 0; - } - function _functionEntry(ast, fn, availableFunctions) { - const parameterNames = new Set(fn.parameters.map((parameter) => parameter.name)); - const declaredLocals = new Set; - for (const match of fn.body.matchAll(/\b(?:var|let|const)\s+([A-Za-z_$][\w$]*)|\bfunction\s+([A-Za-z_$][\w$]*)/g)) { - const name = match[1] ?? match[2]; - if (name) - declaredLocals.add(name); - } - const stateNames = ast.states.filter((state) => state.runtime !== "server" && safeIdentifier(state.name) && !RUNTIME_BINDINGS.has(state.name) && !parameterNames.has(state.name) && !declaredLocals.has(state.name)).map((state) => state.name); - const stateSet = new Set(stateNames); - const propNames = ast.props.filter((prop) => safeIdentifier(prop.name) && !RUNTIME_BINDINGS.has(prop.name) && !parameterNames.has(prop.name) && !stateSet.has(prop.name) && !declaredLocals.has(prop.name)).map((prop) => prop.name); - const functionAliases = availableFunctions.filter((name) => safeIdentifier(name) && !RUNTIME_BINDINGS.has(name) && !parameterNames.has(name) && !stateSet.has(name) && !propNames.includes(name) && !declaredLocals.has(name)); - const parameters = fn.parameters.map((parameter) => parameter.name).join(", "); - const initialStateSnapshot = stateNames.length ? `const __wrnexusInitialState = { ${stateNames.map((name) => `${JSON.stringify(name)}: context.state.${name}`).join(", ")} };` : ""; - const stateAliases = stateNames.length ? `let { ${stateNames.join(", ")} } = context.state;` : ""; - const propAliases = propNames.length ? `const { ${propNames.join(", ")} } = context.props;` : ""; - const syncStateToContext = stateNames.map((name) => `context.state.${name} = ${name};`).join(" "); - const syncStateFromContext = stateNames.map((name) => `${name} = context.state.${name};`).join(" "); - const peerAliases = !stateNames.length ? functionAliases.map((name) => `const ${name} = (...__wrnexusPeerArgs) => context.functions[${JSON.stringify(name)}](...__wrnexusPeerArgs);`).join(` -`) : `const __wrnexusFlush = () => { ${syncStateToContext} }; - const __wrnexusRestore = () => { ${syncStateFromContext} }; - const __wrnexusPeer = (name, args) => { - __wrnexusFlush(); - let result; - try { - result = context.functions[name](...args); - } catch (error) { - __wrnexusRestore(); - throw error; - } - if (result && typeof result.then === "function") { - return Promise.resolve(result).finally(__wrnexusRestore); - } - __wrnexusRestore(); - return result; - }; - ${functionAliases.map((name) => `const ${name} = (...__wrnexusPeerArgs) => __wrnexusPeer(${JSON.stringify(name)}, __wrnexusPeerArgs);`).join(` -`)}`; - const copyBack = stateNames.map((name) => `if (!Object.is(${name}, __wrnexusInitialState[${JSON.stringify(name)}])) context.state.${name} = ${name};`).join(` -`); - const commitBinding = stateNames.length ? `const __wrnexusCommit = () => { ${copyBack} }; - ${!parameterNames.has("commit") && !declaredLocals.has("commit") && !functionAliases.includes("commit") ? "const commit = __wrnexusCommit;" : ""} - ${!parameterNames.has("setTimeout") && !declaredLocals.has("setTimeout") && !functionAliases.includes("setTimeout") ? `const setTimeout = (callback, delay, ...args) => globalThis.setTimeout(() => { - try { return callback(...args); } finally { __wrnexusCommit(); } - }, delay);` : ""}` : ""; - const body = (0, syntax_1.eraseFunctionTypes)(fn.body); - const runtimeBindings = [ - !parameterNames.has("output") ? "const output = context.output;" : "", - !parameterNames.has("server") ? "const server = context.server;" : "", - !parameterNames.has("props") ? "const props = context.props;" : "", - !parameterNames.has("refs") ? "const refs = context.refs;" : "" - ].filter(Boolean).join(` - `); - return `${JSON.stringify(fn.name)}: ${fn.async ? "async " : ""}function(context${parameters ? `, ${parameters}` : ""}) { - ${initialStateSnapshot} - ${stateAliases} - ${propAliases} - ${commitBinding} - ${peerAliases} - ${runtimeBindings} - try { - ${body} - } finally { - ${stateNames.length ? "__wrnexusCommit();" : ""} - } - }`; - } - function generateBrowserModule(ast) { - const functions = ast.runtimeFunctions.filter((fn) => ["legacy", "client", "shared"].includes(fn.runtime)); - const functionNames = functions.map((fn) => fn.name); - const state = ast.states.filter((entry) => entry.runtime !== "server").map((entry) => entry.name); - const selectedImports = selectedBrowserImports(ast, functions); - const imports = selectedImports.map((entry) => entry.code).join(` -`); - const importedBindings = [...new Set(selectedImports.flatMap((entry) => entry.bindings))]; - const sharedState = state.filter((name) => safeIdentifier(name) && !RUNTIME_BINDINGS.has(name)); - const sharedProps = ast.props.map((entry) => entry.name).filter((name) => safeIdentifier(name) && !RUNTIME_BINDINGS.has(name) && !sharedState.includes(name)); - const callableAliases = functionNames.filter((name) => safeIdentifier(name) && !RUNTIME_BINDINGS.has(name) && !sharedState.includes(name) && !sharedProps.includes(name)); - const sharedCommit = sharedState.map((name) => `context.state.${name} = ${name};`).join(" "); - const sharedRestore = [ - ...sharedState.map((name) => `${name} = context.state.${name};`), - ...sharedProps.map((name) => `${name} = context.props.${name};`) - ].join(" "); - const hasAuthoredCommit = callableAliases.includes("commit"); - const hasAuthoredSetTimeout = callableAliases.includes("setTimeout"); - const implementations = functions.map((fn) => { - const parameters = fn.parameters.map((parameter) => parameter.name).join(", "); - return `${JSON.stringify(fn.name)}: ${fn.async ? "async " : ""}function(${parameters}) { - try { ${(0, syntax_1.eraseFunctionTypes)(fn.body)} } finally { __wrnexusCommit(); } - }`; - }).join(`, -`); - return `// generated WRNexusJS browser module for ${ast.name} -${imports} -export const __wrnexusClientFunctions = { -${functions.map((fn) => ` ${JSON.stringify(fn.name)}: (context, ...args) => __wrnexusBindings(context)[${JSON.stringify(fn.name)}](...args)`).join(`, -`)} -}; -export const __wrnexusClientState = ${JSON.stringify(state)}; -export const __wrnexusOutputs = ${JSON.stringify(ast.outputs)}; -export const __wrnexusImportedBindings = { ${importedBindings.join(", ")} }; -function __wrnexusCreateClientFunctions(context) { - ${sharedState.length ? `let { ${sharedState.join(", ")} } = context.state;` : ""} - ${sharedProps.length ? `let { ${sharedProps.join(", ")} } = context.props;` : ""} - const output = context.output; - const server = context.server; - const props = context.props; - const refs = context.refs; - const __wrnexusCommit = () => { ${sharedCommit} }; - const __wrnexusRestore = () => { ${sharedRestore} }; - ${!hasAuthoredCommit ? "const commit = __wrnexusCommit;" : ""} - ${!hasAuthoredSetTimeout ? `const setTimeout = (callback, delay, ...args) => globalThis.setTimeout(() => { - try { return callback(...args); } finally { __wrnexusCommit(); } - }, delay);` : ""} - const implementations = { - ${implementations} - }; - ${callableAliases.map((name) => `const ${name} = (...args) => implementations[${JSON.stringify(name)}](...args);`).join(` - `)} - const functions = {}; - for (const name of Object.keys(implementations)) { - functions[name] = (...args) => { - __wrnexusRestore(); - return implementations[name](...args); - }; - } - return functions; -} -function __wrnexusBindings(context) { - return context.__wrnexusBoundFunctions || - (context.__wrnexusBoundFunctions = __wrnexusCreateClientFunctions(context)); -} -export function bindClientScope(context) { - return __wrnexusBindings(context); -} -`; - } - }, - "packages/compiler/src/codegen.ts": function(module3, exports3, require2, __filename2, __dirname2) { - Object.defineProperty(exports3, "__esModule", { value: true }); - exports3.generate = generate; - exports3.parseForExpr = parseForExpr; - const node_buffer_1 = require2("node:buffer"); - const parser_ts_1 = require2("./parser.js"); - const types_ts_1 = require2("./types.js"); - const syntax_1 = require2("@wrnexus/syntax"); - const store_codegen_ts_1 = require2("./store-codegen.js"); - const analysis_ts_1 = require2("./analysis.js"); - const client_codegen_ts_1 = require2("./client-codegen.js"); - function isComponentTag(tag) { - return /^[A-Z][A-Za-z0-9_$]*$/.test(tag); - } - const HTML_BOOLEAN_ATTRIBUTES = new Set([ - "allowfullscreen", - "async", - "autofocus", - "autoplay", - "checked", - "controls", - "default", - "defer", - "disabled", - "formnovalidate", - "hidden", - "inert", - "ismap", - "itemscope", - "loop", - "multiple", - "muted", - "nomodule", - "novalidate", - "open", - "playsinline", - "readonly", - "required", - "reversed", - "selected" - ]); - function isHtmlBooleanAttribute(name) { - return HTML_BOOLEAN_ATTRIBUTES.has(name.toLowerCase()); - } - const URL_ATTRIBUTES = new Set([ - "href", - "src", - "action", - "formaction", - "poster", - "cite", - "background", - "xlink:href" - ]); - function stripAsciiControlAndSpace(value) { - let result = ""; - for (const character of value) { - if (character.charCodeAt(0) > 32) - result += character; - } - return result; - } - function sanitizeUrlAttribute(value) { - const compact = stripAsciiControlAndSpace(value.trim()); - const lower = compact.toLowerCase(); - if (/^(?:javascript|vbscript|file):/.test(lower)) - return "about:blank"; - if (/^data:(?!image\/(?:png|gif|jpeg|webp|avif);)/.test(lower)) - return "about:blank"; - return value; - } - function safeAttributeValue(name, value) { - if (!URL_ATTRIBUTES.has(name.toLowerCase()) || value.includes("{")) - return value; - return sanitizeUrlAttribute(value); - } - function attrEscape(value) { - return value.replace(/&/g, "&").replace(/"/g, """).replace(//g, ">"); - } - function templateEscape(html) { - return html.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${"); - } - function styleEscape(css) { - return css.replace(/<\/style/gi, "<\\/style"); - } - function attrValue(attrs, name) { - return attrs.find((attr) => !attr.event && attr.name === name)?.value; - } - function renderAttr(attr) { - if (attr.event) - return ` ${eventAttribute(attr.name)}="${attrEscape(attr.value)}"`; - switch (attr.name) { - case "api": - case "ssrGet": - case "ssrText": - case "csrGet": - case "csrText": - return ""; - default: - return attr.boolean ? ` ${attr.name}` : ` ${attr.name}="${attrEscape(safeAttributeValue(attr.name, attr.value))}"`; - } - } - function eventAttribute(name) { - if (name.startsWith("window:")) { - return `data-on-window-${name.slice("window:".length)}`; - } - if (name.startsWith("document:")) { - return `data-on-document-${name.slice("document:".length)}`; - } - if (name.startsWith("browser-")) { - return `data-on-wrnexus-browser-${name.slice(8)}`; - } - if (name.startsWith("mobile-")) { - return `data-on-wrnexus-mobile-${name.slice(7)}`; - } - return `data-on-${name}`; - } - function componentEventAttribute(name) { - if (name.startsWith("window:") || name.startsWith("document:") || name.startsWith("browser-") || name.startsWith("mobile-")) { - return eventAttribute(name); - } - return `data-wrn-out-${name}`; - } - function reactiveAttrValue(raw, reactive) { - let found = false; - const value = raw.replace(/\{([^{}]+)\}/g, (whole, inner) => { - const expr = inner.trim(); - if (!exprRefsState(expr, reactive.stateNames)) - return whole; - found = true; - try { - const result = new Function("with(this){return (" + expr + ");}").call(reactive.scope); - return result == null ? "" : String(result); - } catch { - return whole; - } - }); - return found ? value : null; - } - function renderAttrs(attrs, csrId, reactive = null, dynamicExpressions) { - let bindIndex = 0; - const rendered = attrs.map((attr) => { - const base = renderAttr(attr); - if (!reactive || attr.event || attr.boolean || !base || !attr.value.includes("{")) - return base; - const expression = wholeAttributeExpression(attr.value); - if (expression && exprRefsState(expression, reactive.runtimeStateNames) && dynamicExpressions) { - dynamicExpressions.push(`\${__wrnexusPropAttr(${expression})}`); - const sentinel = `\x00WRNEACH${dynamicExpressions.length - 1}\x00`; - const marker2 = JSON.stringify([attr.name, attr.value]); - return ` ${attr.name}="${sentinel}" data-wrn-bind-${bindIndex++}="${attrEscape(marker2)}"`; - } - const initial = reactiveAttrValue(attr.value, reactive); - if (initial === null) - return base; - const marker = JSON.stringify([attr.name, attr.value]); - return ` ${attr.name}="${attrEscape(URL_ATTRIBUTES.has(attr.name.toLowerCase()) ? sanitizeUrlAttribute(initial) : initial)}" data-wrn-bind-${bindIndex++}="${attrEscape(marker)}"`; - }).join(""); - return csrId ? `${rendered} data-wrnexus-csr="${attrEscape(csrId)}"` : rendered; - } - function substituteTMarkers(text) { - return text.replace(/\{t:([^{}]+)\}/g, (_m, key) => ``); - } - function htmlTextEscape(value) { - return value.replace(/[&<>]/g, (c) => c === "&" ? "&" : c === "<" ? "<" : ">"); - } - function evalStateSeeds(states) { - const scope = {}; - for (const s of states) { - try { - scope[s.name] = new Function("with(this){return (" + s.expr + ");}").call(scope); - } catch { - scope[s.name] = undefined; - } - } - return scope; - } - function substituteReactiveText(raw, reactive, dynamicExpressions) { - const text = substituteTMarkers(raw); - if (!reactive || reactive.stateNames.size === 0) - return text; - return text.replace(/\{([^{}]+)\}/g, (whole, inner) => { - const expr = inner.trim(); - if (expr.startsWith("t:") || !exprRefsState(expr, reactive.stateNames)) - return whole; - if (exprRefsState(expr, reactive.runtimeStateNames) && dynamicExpressions) { - dynamicExpressions.push(`\${__wrnexusEscapeHtml(${expr})}`); - const sentinel = `\x00WRNEACH${dynamicExpressions.length - 1}\x00`; - return `${sentinel}`; - } - let value; - try { - value = new Function("with(this){return (" + expr + ");}").call(reactive.scope); - } catch { - return whole; - } - const baked = htmlTextEscape(value == null ? "" : String(value)); - return `${baked}`; - }); - } - function bakeLoopText(raw) { - let out = ""; - let last = 0; - let m; - const re = /\{([^{}]+)\}/g; - while (m = re.exec(raw)) { - out += escLit(raw.slice(last, m.index)); - const expr = m[1].trim(); - if (expr.startsWith("t:")) { - out += escLit(``); - } else { - out += "${__wrnexusEscapeHtml(" + expr + ")}"; - } - last = m.index + m[0].length; - } - return out + escLit(raw.slice(last)); - } - function bakeLoopAttr(raw, typed = false) { - const wholeExpression = wholeAttributeExpression(raw); - if (typed && wholeExpression) - return "${__wrnexusPropAttr(" + wholeExpression + ")}"; - if (!raw.includes("{")) - return escLit(attrEscape(raw)); - let out = ""; - let last = 0; - let m; - const re = /\{([^{}]+)\}/g; - while (m = re.exec(raw)) { - out += escLit(attrEscape(raw.slice(last, m.index))); - out += "${__wrnexusEscapeHtml(" + m[1].trim() + ")}"; - last = m.index + m[0].length; - } - return out + escLit(attrEscape(raw.slice(last))); - } - function renderLoopBody(node) { - if (node.type === "text") { - return bakeLoopText(node.value); - } - if (node.type === "each") { - return compileEachExpr(node); - } - if (node.type === "if") { - return compileIfExpr(node); - } - const componentTag = isComponentTag(node.tag); - const attrs = node.attrs.filter((attr) => attr.name !== "data-component").map((attr) => { - const name = attr.event ? componentTag ? componentEventAttribute(attr.name) : eventAttribute(attr.name) : attr.name; - if (attr.boolean) { - return escLit(` ${name}`); - } - return escLit(` ${name}="`) + bakeLoopAttr(attr.value, componentTag) + escLit(`"`); - }).join(""); - const inner = node.children.map(renderLoopBody).join(""); - if (node.tag === "Static") - return inner; - if (node.tag === "Dynamic") - return escLit('') + inner + escLit(""); - if (node.tag === "KeepAlive") { - const key = node.attrs.find((attribute) => attribute.name === "key")?.value ?? "default"; - return escLit('
`) + inner + escLit("
"); - } - if (node.tag === "Portal") { - const target = node.attrs.find((attribute) => attribute.name === "to")?.value ?? "body"; - return escLit('
') + inner + escLit("
"); - } - if (node.tag === "Transition") { - const name = node.attrs.find((attribute) => attribute.name === "name")?.value ?? "wrn-transition"; - return escLit('
') + inner + escLit("
"); - } - if (node.tag === "Component") { - const selected = node.attrs.find((attribute) => attribute.name === "is")?.value ?? ""; - return escLit('
') + inner + escLit("
"); - } - if (componentTag) { - return escLit(`
") + inner + escLit("
"); - } - if (parser_ts_1.VOID_ELEMENTS.has(node.tag.toLowerCase())) { - return escLit(`<${node.tag}`) + attrs + escLit(">"); - } - return escLit(`<${node.tag}`) + attrs + escLit(">") + inner + escLit(``); - } - function compileEachExpr(node) { - const item = node.item; - const index = node.index ?? "__wi"; - const body = node.body.map(renderLoopBody).join(""); - const empty = node.empty.map(renderLoopBody).join(""); - return "${(() => { const __wl = Array.isArray(" + node.list + ") ? (" + node.list + ") : []; return __wl.length ? __wl.map((" + item + ", " + index + ") => `" + body + '`).join("") : `' + empty + "`; })()}"; - } - function compileIfExpr(node) { - let expr = "``"; - for (let k = node.branches.length - 1;k >= 0; k--) { - const b = node.branches[k]; - const bodySrc = "`" + b.body.map(renderLoopBody).join("") + "`"; - expr = b.cond === null ? bodySrc : "(" + b.cond + ") ? " + bodySrc + " : " + expr; - } - return "${" + expr + "}"; - } - function collectControlExprs(nodes, out = []) { - for (const node of nodes) { - if (node.type === "text") - continue; - if (node.type === "each") { - out.push(node.list); - collectControlExprs(node.body, out); - collectControlExprs(node.empty, out); - } else if (node.type === "if") { - for (const b of node.branches) { - if (b.cond) - out.push(b.cond); - collectControlExprs(b.body, out); - } - } else if (node.type === "element") { - collectControlExprs(node.children, out); - } - } - return out; - } - function renderNode(node, ssrBindings, csrBindings, apiBindings, loops, reactive = null) { - if (node.type === "text") - return substituteReactiveText(node.value, reactive, loops); - if (node.type === "each" || node.type === "if") { - loops.push(node.type === "each" ? compileEachExpr(node) : compileIfExpr(node)); - return `\x00WRNEACH${loops.length - 1}\x00`; - } - if (node.tag === "Static" || node.tag === "Dynamic") { - const inner2 = node.children.map((child) => renderNode(child, ssrBindings, csrBindings, apiBindings, loops, reactive)).join(""); - return node.tag === "Static" ? inner2 : `${inner2}`; - } - if (node.tag === "Portal" || node.tag === "Transition" || node.tag === "Component") { - const inner2 = node.children.map((child) => renderNode(child, ssrBindings, csrBindings, apiBindings, loops, reactive)).join(""); - const attribute = node.tag === "Portal" ? "data-wrn-portal" : node.tag === "Transition" ? "data-wrn-transition" : "data-wrn-dynamic-component"; - const source = node.tag === "Portal" ? "to" : node.tag === "Transition" ? "name" : "is"; - const fallback = node.tag === "Portal" ? "body" : node.tag === "Transition" ? "wrn-transition" : ""; - const original = node.attrs.find((item) => item.name === source); - const rendered = original ? renderAttrs([{ ...original, name: attribute }], undefined, reactive, loops) : ` ${attribute}="${attrEscape(fallback)}"`; - return `${inner2}`; - } - if (node.tag === "Async") { - const source = attrValue(node.attrs, "source") ?? "data"; - const retries = attrValue(node.attrs, "retries") ?? "2"; - const tags = attrValue(node.attrs, "tags") ?? source; - const serverResolved = attrValue(node.attrs, "data-wrn-async-server") === "true"; - const branchElement = (name) => node.children.find((child) => child.type === "element" && child.tag === name); - const branch = (name) => { - const element = branchElement(name); - return (element?.children ?? []).map((child) => renderNode(child, ssrBindings, csrBindings, apiBindings, loops, reactive)).join(""); - }; - const loading = branch("Loading"); - const success = branch("Success"); - const error = branch("Error"); - const successElement = branchElement("Success"); - const errorElement = branchElement("Error"); - const identifier = (value, fallback) => value && isSafeGeneratedIdentifier(value) ? value : fallback; - const successAlias = identifier(successElement ? attrValue(successElement.attrs, "data") : undefined, identifier(source, "data")); - const errorAlias = identifier(errorElement ? attrValue(errorElement.attrs, "error") : undefined, "error"); - const nested = (value) => value.replace(/\\/g, "\\\\").replace(/`/g, "\\`"); - const scoped = (value, alias, expression) => { - const index = loops.push(`\${(() => { const ${alias} = ${expression}; return \`${nested(value)}\`; })()}`) - 1; - return `\x00WRNEACH${index}\x00`; - }; - const successTemplate = scoped(success, successAlias, `(ctx[${JSON.stringify(source)}] ?? {})`); - const errorTemplate = scoped(error, errorAlias, `{ message: "" }`); - let initial = loading; - if (serverResolved) { - const sourcePattern = successAlias.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - const serverSuccess = success.replace(new RegExp(`\\{\\s*(${sourcePattern}(?:\\.[A-Za-z_$][\\w$]*)*)\\s*\\}`, "g"), (_whole, expression) => `\${__wrnexusEscapeHtml(${expression})}`); - const index = loops.push(`\${ctx[${JSON.stringify(source)}] !== undefined ? (() => { const ${successAlias} = ctx[${JSON.stringify(source)}]; return \`${nested(serverSuccess)}\`; })() : \`${nested(loading)}\`}`) - 1; - initial = `\x00WRNEACH${index}\x00`; - } - return `
${initial}
`; - } - if (node.tag === "KeepAlive") { - const key = attrValue(node.attrs, "key") ?? "default"; - const inner2 = node.children.map((child) => renderNode(child, ssrBindings, csrBindings, apiBindings, loops, reactive)).join(""); - return `
${inner2}
`; - } - if (isComponentTag(node.tag)) { - return renderPageComponentInvocation(node, ssrBindings, csrBindings, apiBindings, loops, reactive); - } - const apiName = attrValue(node.attrs, "api"); - const apiBinding = apiName ? apiBindings.get(apiName) : undefined; - if (apiName && !apiBinding) { - throw new Error(`Unknown .wrn api binding "${apiName}"`); - } - const ssrGet = attrValue(node.attrs, "ssrGet"); - const ssrText = attrValue(node.attrs, "ssrText"); - const csrGet = attrValue(node.attrs, "csrGet"); - const csrText = attrValue(node.attrs, "csrText"); - const csrId = apiBinding?.mode === "client" ? csrMarker(csrBindings, renderBinding(apiBinding)) : csrGet && csrText ? csrMarker(csrBindings, { - method: "GET", - path: apiRoutePath(csrGet), - body: expressionBody(csrText), - helpers: "" - }) : undefined; - if (parser_ts_1.VOID_ELEMENTS.has(node.tag.toLowerCase())) { - return `<${node.tag}${renderAttrs(node.attrs, csrId, reactive, loops)}>`; - } - const inner = apiBinding?.mode === "ssr" ? ssrMarker(ssrBindings, renderBinding(apiBinding)) : ssrGet && ssrText ? ssrMarker(ssrBindings, { - method: "GET", - path: apiRoutePath(ssrGet), - body: expressionBody(ssrText), - helpers: "" - }) : node.children.map((child) => renderNode(child, ssrBindings, csrBindings, apiBindings, loops, reactive)).join(""); - return `<${node.tag}${renderAttrs(node.attrs, csrId, reactive, loops)}>${inner}`; - } - function renderPageComponentInvocation(node, ssrBindings, csrBindings, apiBindings, loops, reactive) { - const attrs = node.attrs.filter((attr) => attr.name !== "data-component").map((attr) => renderPageComponentAttr(attr, loops)).join(""); - const inner = node.children.map((child) => renderNode(child, ssrBindings, csrBindings, apiBindings, loops, reactive)).join(""); - return `
${inner}
`; - } - function renderNestedComponentInvocation(node, ctx) { - let bindIndex = 0; - const attrs = node.attrs.filter((attr) => attr.name !== "data-component").map((attr) => { - const spread = /^\{\.\.\.([A-Za-z_$][\w$]*)\}$/.exec(attr.name); - if (spread) { - return `\${__wireSpreadAttrs(${ctx.resolveExpr(spread[1])})}`; - } - if (attr.event) { - return escLit(` ${componentEventAttribute(attr.name)}="`) + escLit(attrEscape(attr.value)) + escLit(`"`); - } - if (attr.boolean) { - return ` ${attr.name}`; - } - const wholeExpression = wholeAttributeExpression(attr.value); - const compiledValue = wholeExpression ? `\${__wireProp(${ctx.resolveExpr(wholeExpression)})}` : compileAttrValue(attr.value, ctx); - const rendered = ` ${attr.name}="${compiledValue}"`; - if (!attr.value.includes("{") || !exprRefsState(attr.value, ctx.stateNames) && !exprRefsState(attr.value, ctx.propNames)) { - return rendered; - } - const marker = attrEscape(JSON.stringify([attr.name, attr.value])); - return rendered + ` data-wrn-prop-bind-${bindIndex++}="${escLit(marker)}"`; - }).join(""); - const loops = loopVarsOf(node); - const childCtx = loops.length > 0 ? { - ...ctx, - forwardRestAttrs: false, - loopVars: new Set([...ctx.loopVars ?? [], ...loops]) - } : { ...ctx, forwardRestAttrs: false }; - const inner = node.children.map((child) => renderComponentNode(child, childCtx)).join(""); - return `
${inner}
`; - } - function ssrMarker(bindings, binding) { - const marker = ``; - bindings.push({ marker, ...binding }); - return marker; - } - function csrMarker(bindings, binding) { - const id = String(bindings.length); - bindings.push({ id, ...binding }); - return id; - } - function renderBinding(binding) { - return { - method: binding.method, - path: binding.path, - body: binding.body, - helpers: binding.helpers - }; - } - function hasClientBehavior(nodes) { - return nodes.some((node) => { - if (node.type === "text") - return /\{(?!t:)[^{}]+\}/.test(node.value); - if (node.type === "each") { - return hasClientBehavior(node.body) || hasClientBehavior(node.empty); - } - if (node.type === "if") { - return node.branches.some((branch) => hasClientBehavior(branch.body)); - } - return node.attrs.some((attr) => attr.event || attr.name === "csrGet" || attr.name === "csrText") || hasClientBehavior(node.children); - }); - } - function apiRoutePath(path) { - const trimmed = path.trim(); - if (!trimmed.startsWith("/")) { - throw new Error(`.wrn API paths must start with "/": ${path}`); - } - if (trimmed.includes("\x00") || trimmed.includes("\\") || /(^|\/)\.\.(\/|$)/.test(trimmed)) { - throw new Error(`Unsafe .wrn API path: ${path}`); - } - if (trimmed === "/api" || trimmed.startsWith("/api/")) - return trimmed; - return `/api${trimmed}`; - } - function expressionBody(expr) { - return `return (${expr});`; - } - function dataBody(source) { - const trimmed = source.trim(); - if (!trimmed) - return "return undefined;"; - return /\breturn\b/.test(trimmed) ? trimmed : expressionBody(trimmed); - } - function modeHelpers(ast, mode, sharedHelpers) { - return [ - sharedHelpers, - ...ast.modeFunctions.filter((block) => block.mode === mode).map((block) => block.body.trim()).filter(Boolean) - ].filter(Boolean).join(` - -`); - } - function apiBindingMap(ast, sharedHelpers) { - const bindings = new Map; - for (const block of ast.dataApis) { - if (bindings.has(block.name)) { - throw new Error(`Duplicate .wrn api binding "${block.name}"`); - } - bindings.set(block.name, { - mode: block.mode, - method: block.method, - path: apiRoutePath(block.path), - body: dataBody(block.body), - helpers: modeHelpers(ast, block.mode, sharedHelpers) - }); - } - return bindings; - } - function ssrRuntimeSource() { - return `const __wrnexusHtmlEscapes = { "&": "&", "<": "<", ">": ">", "\\"": """, "'": "'" }; -function __wrnexusEscapeHtml(value: unknown): string { - return String(value).replace(/[&<>"']/g, (ch) => __wrnexusHtmlEscapes[ch] ?? ch); -} - -type __WrnexusContext = import("@wrnexus/core").Context & { - __wrnexusCallApi?: (path: string, method: string) => Promise; - localStorage?: unknown; -}; - -function __wrnexusEvalData(data: unknown, body: string, helpers = "", ctx: __WrnexusContext): unknown { - const adapters = { - cookies: ctx.cookies, - session: ctx.session, - localStorage: ctx.localStorage, - }; - return new Function("$data", "$adapters", "const cookies = $adapters.cookies;\\nconst session = $adapters.session;\\nconst localStorage = $adapters.localStorage;\\nwith ($data ?? {}) {\\n" + helpers + "\\n" + body + "\\n}")(data, adapters); -} - -function __wrnexusPropAttr( - value: unknown, -): string { - const serialized = - value !== null && - typeof value === "object" - ? JSON.stringify(value) - : String(value == null ? "" : value); - - return serialized.replace( - /[&<>"]/g, - (character) => - character === "&" - ? "&" - : character === "<" - ? "<" - : character === ">" - ? ">" - : """, - ); -} - -async function __wrnexusCallApi(path: string, method: string, ctx: __WrnexusContext): Promise { - if (typeof ctx.__wrnexusCallApi === "function") { - return await ctx.__wrnexusCallApi(path, method); - } - - const url = new URL(path, ctx.req.url); - const res = await fetch(new Request(url, { method, headers: ctx.req.headers })); - if (!res.ok) { - throw new Error(".wrn data API request failed with status " + res.status); - } - - const type = res.headers.get("content-type") || ""; - return type.includes("application/json") ? await res.json() : await res.text(); -} - -async function __wrnexusRenderSsrBindings(html: string, ctx: __WrnexusContext): Promise { - for (const binding of __wrnexusSsrBindings) { - const data = await __wrnexusCallApi(binding.path, binding.method, ctx); - const value = __wrnexusEvalData(data, binding.body, binding.helpers, ctx); - html = html.replace(binding.marker, __wrnexusEscapeHtml(value)); - } - return html; -}`; - } - function stableHash(value) { - let hash = 2166136261; - for (let index = 0;index < value.length; index++) { - hash ^= value.charCodeAt(index); - hash = Math.imul(hash, 16777619); - } - return (hash >>> 0).toString(36); - } - function hydrationId(ast) { - const shape = JSON.stringify({ - kind: ast.kind, - name: ast.name, - props: ast.props.map((entry) => entry.name), - events: ast.events.map((entry) => entry.name), - states: ast.states.map((entry) => entry.name), - computed: ast.computed.map((entry) => entry.name), - view: ast.view - }); - return `${ast.name}:${stableHash(shape)}`; - } - function localStyleId(ast) { - return `wrn-${ast.kind}-${stableHash(`${ast.kind}:${ast.name}`)}`; - } - function localStyleTag(ast, styles) { - if (!styles.length) - return ""; - const id = localStyleId(ast); - const css = styles.map(styleEscape).join(` -`); - return ``; - } - function localStyleExport(ast, styles) { - if (!styles.length) - return null; - return `export const __wrnexusStyles = ${JSON.stringify([ - { - id: localStyleId(ast), - owner: ast.name, - kind: ast.kind, - css: styles.join(` -`) - } - ], null, 2)};`; - } - function isStoreImportSource(source) { - return /(?:^|\/)stores?\//.test(source) || source.startsWith("@wrnexus/store"); - } - function importedStoreBindings(ast) { - return ast.structuredImports.filter((entry) => entry.defaultImport && entry.source.endsWith(".wrn") && isStoreImportSource(entry.source)).map((entry) => ({ - local: entry.defaultImport, - internal: `__wrnexusStoreDefinition_${entry.defaultImport}` - })); - } - function generatedImports(ast) { - const stores = new Map(importedStoreBindings(ast).map((entry) => [entry.local, entry.internal])); - return ast.structuredImports.map((entry) => { - if (!entry.defaultImport) - return entry.raw; - const internal = stores.get(entry.defaultImport); - return internal ? entry.raw.replace(new RegExp(`^(\\s*import\\s+(?:type\\s+)?)(?:${entry.defaultImport.replace(/[.*+?^${}()|[\\]\\]/g, "\\$&")})(\\s+from\\s+)`), `$1${internal}$2`) : entry.raw; - }); - } - function isSafeGeneratedIdentifier(name) { - return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name); - } - function generateSsrStateAliases(stateNames) { - const names = [...new Set(stateNames)].filter(isSafeGeneratedIdentifier); - if (!names.length) { - return ""; - } - return `const { ${names.join(", ")} } = __state; -`; - } - function orderPageStates(entries) { - if (entries.length < 2) - return entries; - const byName = new Map(entries.map((entry) => [entry.name, entry])); - const visiting = new Set; - const visited = new Set; - const ordered = []; - const visit = (name) => { - if (visited.has(name)) - return; - if (visiting.has(name)) { - throw new Error(`WRN-STATE-CYCLE: state value '${name}' has a dependency cycle.`); - } - const entry = byName.get(name); - if (!entry) - return; - visiting.add(name); - for (const dependency of byName.keys()) { - if (dependency !== name && new RegExp(`\\b${dependency.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`).test(entry.expr)) { - visit(dependency); - } - } - visiting.delete(name); - visited.add(name); - ordered.push(entry); - }; - for (const entry of entries) - visit(entry.name); - return ordered; - } - function generateSsrStateInitializer(entries) { - const ordered = orderPageStates(entries); - const declarations = ordered.filter((entry) => isSafeGeneratedIdentifier(entry.name)).map((entry) => `const ${entry.name} = (() => { try { return (${entry.expr}); } catch { return undefined; } })();`).join(" "); - const values = entries.map((entry) => { - if (isSafeGeneratedIdentifier(entry.name)) { - return `${JSON.stringify(entry.name)}: ${entry.name}`; - } - return `${JSON.stringify(entry.name)}: (() => { try { return (${entry.expr}); } catch { return undefined; } })()`; - }).join(", "); - return `(() => { ${declarations} return { ${values} }; })()`; - } - function orderPageComputed(entries) { - if (entries.length < 2) - return entries; - const byName = new Map(entries.map((entry) => [entry.name, entry])); - const visiting = new Set; - const visited = new Set; - const ordered = []; - const visit = (name) => { - if (visited.has(name)) - return; - if (visiting.has(name)) { - throw new Error(`WRN-COMPUTED-CYCLE: computed value '${name}' has a dependency cycle.`); - } - const entry = byName.get(name); - if (!entry) - return; - visiting.add(name); - for (const dependency of byName.keys()) { - if (dependency !== name && new RegExp(`\\b${dependency.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`).test(entry.expr)) { - visit(dependency); - } - } - visiting.delete(name); - visited.add(name); - ordered.push(entry); - }; - for (const entry of entries) - visit(entry.name); - return ordered; - } - function generateSsrComputedAliases(entries) { - return orderPageComputed(entries).filter((entry) => isSafeGeneratedIdentifier(entry.name)).map((entry) => `const ${entry.name} = (() => { try { return (${entry.expr}); } catch { return undefined; } })();`).join(` -`); - } - function runtimeComputedNames(states, computed, runtimeRoots = []) { - const entries = [...states, ...computed]; - const runtime = new Set([ - ...runtimeRoots, - ...entries.filter((entry) => /\bctx\b/.test(entry.expr)).map((entry) => entry.name) - ]); - let changed = true; - while (changed) { - changed = false; - for (const entry of entries) { - if (runtime.has(entry.name)) - continue; - if ([...runtime].some((name) => new RegExp(`\\b${name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`).test(entry.expr))) { - runtime.add(entry.name); - changed = true; - } - } - } - return runtime; - } - function hydrationAttribute(ast) { - const strategy = ["static", "server"].includes(ast.renderMode ?? "") ? "none" : ast.hydrate ?? "load"; - const hasBrowserModule = (0, client_codegen_ts_1.browserModuleRequired)(ast); - const moduleAttribute = hasBrowserModule ? ' data-wrn-client-module="__WRNEXUS_CLIENT_MODULE__"' : ""; - return ` data-wrn-hydration="${attrEscape(hydrationId(ast))}" data-wrn-hydrate="${attrEscape(strategy)}" data-wrn-runtime="${attrEscape(ast.runtime ?? "universal")}"${moduleAttribute}`; - } - function targetFunctions(ast, target) { - const runtimes = target === "browser" ? ["legacy", "client", "shared"] : ["legacy", "server", "shared"]; - return ast.functions.map((body) => (0, syntax_1.stripRuntimeFunctionModifiers)(body, [...runtimes])).map((body) => body.trim()).filter(Boolean).join(` - -`); - } - function publicOutputNames(ast) { - return [ - ...new Set([ - ...ast.outputs.map((output) => output.name), - ...ast.events.map((event) => event.name) - ]) - ]; - } - function prepareActionForms(nodes, actions) { - for (const node of nodes) { - if (node.type === "text") - continue; - if (node.type === "each") { - prepareActionForms(node.body, actions); - prepareActionForms(node.empty, actions); - continue; - } - if (node.type === "if") { - node.branches.forEach((branch) => prepareActionForms(branch.body, actions)); - continue; - } - prepareActionForms(node.children, actions); - if (node.tag.toLowerCase() !== "form") - continue; - const submit = node.attrs.find((attr) => attr.event && attr.name === "submit"); - if (!submit || !actions.has(submit.value.trim())) - continue; - const name = submit.value.trim(); - node.attrs = node.attrs.filter((attr) => attr !== submit); - if (!node.attrs.some((attr) => !attr.event && attr.name === "method")) { - node.attrs.push({ name: "method", value: "post", event: false }); - } - node.attrs.push({ name: "data-wrn-action", value: name, event: false }); - node.children.unshift({ - type: "element", - tag: "input", - attrs: [ - { name: "type", value: "hidden", event: false }, - { name: "name", value: "_wrnexus_action", event: false }, - { name: "value", value: name, event: false } - ], - children: [] - }); - } - } - function markServerAsyncBoundaries(nodes, serverLoads) { - for (const node of nodes) { - if (node.type === "text") - continue; - if (node.type === "each") { - markServerAsyncBoundaries(node.body, serverLoads); - markServerAsyncBoundaries(node.empty, serverLoads); - continue; - } - if (node.type === "if") { - node.branches.forEach((branch) => markServerAsyncBoundaries(branch.body, serverLoads)); - continue; - } - if (node.tag === "Async") { - const source = attrValue(node.attrs, "source") ?? "data"; - if (serverLoads.has(source) && !node.attrs.some((attribute) => attribute.name === "data-wrn-async-server")) { - node.attrs.push({ name: "data-wrn-async-server", value: "true", event: false }); - } - } - markServerAsyncBoundaries(node.children, serverLoads); - } - } - function generate(ast) { - ast = (0, analysis_ts_1.optimizeAst)(ast).ast; - if (ast.kind === "global-store" || ast.kind === "page-store") - return (0, store_codegen_ts_1.generateStoreModule)(ast); - if (ast.kind === "component" || ast.kind === "layout") { - return generateComponent(ast); - } - const out = []; - prepareActionForms(ast.view, new Set(ast.actions.map((action) => action.name))); - markServerAsyncBoundaries(ast.view, new Set(ast.loads.filter((load) => load.mode === "server" && !load.deferred && load.name).map((load) => load.name))); - if (ast.actions.length > 0) { - out.push(`import { createActionClient } from "@wrnexus/csr"; -import type { InferSchema } from "@wrnexus/validation";`); - } - if (ast.imports.length > 0) - out.push(generatedImports(ast).join(` -`)); - const ssrBindings = []; - const csrBindings = []; - const helpers = targetFunctions(ast, "server"); - const apiBindings = apiBindingMap(ast, helpers); - const typeSource = ast.types.map((body2) => body2.trim()).filter(Boolean).join(` - -`); - if (typeSource) - out.push(typeSource); - if (helpers) { - out.push(`// --- .wrn functions --- -${helpers}`); - } - out.push(`export const meta = ${JSON.stringify({ title: ast.name, ...ast.seo }, null, 2)};`); - if (ast.layout) - out.push(`export const layout = ${ast.layoutIsSymbol ? ast.layout : JSON.stringify(ast.layout)};`); - out.push(`export const __wrnexusRuntime = ${JSON.stringify(ast.runtime ?? "universal")};`); - out.push(`export const __wrnexusRender = ${JSON.stringify(ast.renderMode ?? "hybrid")};`); - out.push(`export const __wrnexusHydrate = ${JSON.stringify(["static", "server"].includes(ast.renderMode ?? "") ? "none" : ast.hydrate ?? "load")};`); - out.push(`export const __wrnexusHydrationId = ${JSON.stringify(hydrationId(ast))};`); - if (Object.keys(ast.cache ?? {}).length > 0) - out.push(`export const __wrnexusCache = ${JSON.stringify(ast.cache, null, 2)};`); - if (Object.keys(ast.security).length > 0) { - out.push(`export const __wrnexusSecurity = ${JSON.stringify(ast.security, null, 2)};`); - } - if (Object.keys(ast.navigation).length > 0) { - out.push(`export const __wrnexusNavigation = ${JSON.stringify(ast.navigation, null, 2)};`); - } - const orderedStates = orderPageStates(ast.states); - const browserStates = ast.states.filter((state) => state.runtime !== "server"); - const seedScope = evalStateSeeds(orderedStates); - const orderedComputed = orderPageComputed(ast.computed); - for (const entry of orderedComputed) { - try { - seedScope[entry.name] = new Function("with(this){return (" + entry.expr + ");}").call(seedScope); - } catch { - seedScope[entry.name] = undefined; - } - } - const reactiveNames = [ - ...browserStates.map((entry) => entry.name), - ...ast.computed.map((entry) => entry.name) - ]; - const storeBindings = importedStoreBindings(ast); - const runtimeRoots = [ - ...storeBindings.map((entry) => entry.local), - ...ast.loads.filter((load) => load.mode === "server" && !load.deferred && load.name).map((load) => load.name) - ]; - const runtimeStateNames = runtimeComputedNames(orderedStates, orderedComputed, runtimeRoots); - const reactive = reactiveNames.length > 0 ? { stateNames: new Set(reactiveNames), runtimeStateNames, scope: seedScope } : null; - const loops = []; - let html = ast.view.map((node) => renderNode(node, ssrBindings, csrBindings, apiBindings, loops, reactive)).join(""); - const styles = ast.styles.map((body2) => body2.trim()).filter(Boolean); - const pageBehavior = ast.runtime === "server" ? null : componentBehavior(ast); - const needsClientRuntime = ast.runtime !== "server" && (browserStates.length > 0 || ast.computed.length > 0 || hasClientBehavior(ast.view) || pageBehavior !== null); - if (needsClientRuntime) { - const scopePlaceholder = "__WRNEXUS_DYNAMIC_SCOPE__"; - html = `
${html}
`; - } - const pageStyleTag = localStyleTag(ast, styles); - if (pageStyleTag) { - html = `${pageStyleTag}${html}`; - } - if (ast.renderMode === "client") { - const clientRoot = hydrationId(ast); - html = `
`; - } - const pageStyleExport = localStyleExport(ast, styles); - if (pageStyleExport) - out.push(pageStyleExport); - if (csrBindings.length > 0) { - out.push(`export const __wrnexusCsr = ${JSON.stringify(csrBindings, null, 2)};`); - } - if (pageBehavior) { - out.push(`export const __wrnexusBehavior = ${JSON.stringify(pageBehavior, null, 2)};`); - } - let body = templateEscape(html); - let staticShellBody; - if (ast.renderMode === "partial-static") { - const shellHtml = html.replace(/]*>[\s\S]*?<\/wrn-dynamic-region>/gi, ''); - staticShellBody = templateEscape(shellHtml); - } - const dynamicStateInitializer = generateSsrStateInitializer(orderedStates); - const stateType = ast.states.length > 0 ? `{ ${ast.states.map((state) => `${JSON.stringify(state.name)}: ${state.valueType ?? "unknown"}`).join("; ")} }` : "Record"; - const hydrationStateNames = JSON.stringify(browserStates.map((state) => state.name)); - const ssrStateAliases = generateSsrStateAliases(ast.states.map((state) => state.name)); - const ssrComputedAliases = generateSsrComputedAliases(orderedComputed); - const storeDeclarations = storeBindings.map((entry) => ` const ${entry.local} = await ctx.__wrnexusUseStore(${entry.internal});`).join(` -`); - const serverLoadAliases = ast.loads.filter((load) => load.mode === "server" && !load.deferred && load.name).map((load) => ` const ${load.name} = ctx[${JSON.stringify(load.name)}];`).join(` -`); - for (let idx = loops.length - 1;idx >= 0; idx--) { - const code = loops[idx]; - body = body.replaceAll(`\x00WRNEACH${idx}\x00`, code); - if (staticShellBody?.includes(`\x00WRNEACH${idx}\x00`)) { - staticShellBody = staticShellBody.replaceAll(`\x00WRNEACH${idx}\x00`, code); - } - } - const loopConsts = []; - if (loops.length > 0) { - const lists = collectControlExprs(ast.view); - for (const [name, binding] of apiBindings) { - if (binding.mode !== "ssr") - continue; - if (!lists.some((expr) => new RegExp(`\\b${name}\\b`).test(expr))) - continue; - loopConsts.push(` const ${name} = __wrnexusEvalData(await __wrnexusCallApi(${JSON.stringify(binding.path)}, ${JSON.stringify(binding.method)}, ctx), ${JSON.stringify(binding.body)}, ${JSON.stringify(binding.helpers)}, ctx);`); - } - } - const needsSsrRuntime = ssrBindings.length > 0 || loops.length > 0 || runtimeStateNames.size > 0; - if (needsSsrRuntime) { - out.push(ssrRuntimeSource()); - out.push(`const __wrnexusSsrBindings = ${JSON.stringify(ssrBindings, null, 2)};`); - const decls = loopConsts.length > 0 ? loopConsts.join(` -`) + ` -` : ""; - out.push(`export default async function ${ast.name}(ctx: __WrnexusContext) { - ${storeDeclarations} - ${serverLoadAliases} - ${decls} - const __state: ${stateType} = ${dynamicStateInitializer}; - ${ssrStateAliases} - ${ssrComputedAliases} - const __hydrationState = Object.fromEntries(${hydrationStateNames}.map((key) => [key, Reflect.get(__state, key)])); - const __scopeValue = Object.entries(__hydrationState) - .map(([key, value]) => { - let encoded: string; - try { - encoded = value === undefined ? "undefined" : JSON.stringify(value); - } catch { - encoded = JSON.stringify(String(value)); - } - return key + ": " + encoded; - }) - .join(", ") - .replace(/&/g, "&") - .replace(/"/g, """) - .replace(//g, ">"); - - const html = \`${body}\`.replace( - "__WRNEXUS_DYNAMIC_SCOPE__", - __scopeValue, - ); - - return await __wrnexusRenderSsrBindings(html, ctx); - }`); - } else { - out.push(`export default ${storeBindings.length > 0 ? "async " : ""}function ${ast.name}(ctx: import("@wrnexus/core").Context) { - ${storeDeclarations} - ${serverLoadAliases} - const __state: ${stateType} = ${dynamicStateInitializer}; - ${ssrStateAliases} - ${ssrComputedAliases} - const __hydrationState = Object.fromEntries(${hydrationStateNames}.map((key) => [key, Reflect.get(__state, key)])); - const __scopeValue = Object.entries(__hydrationState) - .map(([key, value]) => { - let encoded: string; - try { - encoded = value === undefined ? "undefined" : JSON.stringify(value); - } catch { - encoded = JSON.stringify(String(value)); - } - return key + ": " + encoded; - }) - .join(", ") - .replace(/&/g, "&") - .replace(/"/g, """) - .replace(//g, ">"); - - return \`${body}\`.replace( - "__WRNEXUS_DYNAMIC_SCOPE__", - __scopeValue, - ); - }`); - } - if (staticShellBody !== undefined) { - out.push(`export async function __wrnexusBuildStaticShell(ctx: import("@wrnexus/core").Context = {} as import("@wrnexus/core").Context) { - ${storeDeclarations} - ${serverLoadAliases} - ${loopConsts.length > 0 ? loopConsts.join(` -`) : ""} - const __state: ${stateType} = ${dynamicStateInitializer}; - ${ssrStateAliases} - ${ssrComputedAliases} - const __hydrationState = Object.fromEntries(${hydrationStateNames}.map((key) => [key, Reflect.get(__state, key)])); - const __scopeValue = Object.entries(__hydrationState) - .map(([key, value]) => { - let encoded: string; - try { - encoded = value === undefined ? "undefined" : JSON.stringify(value); - } catch { - encoded = JSON.stringify(String(value)); - } - return key + ": " + encoded; - }) - .join(", ") - .replace(/&/g, "&") - .replace(/"/g, """) - .replace(//g, ">"); - return \`${staticShellBody}\`.replace("__WRNEXUS_DYNAMIC_SCOPE__", __scopeValue); - }`); - } - if (ast.loads.length > 0) { - const serverLoads = ast.loads.filter((entry) => entry.mode === "server" && !entry.deferred); - const publicClientLoads = ast.loads.filter((entry) => entry.mode === "client" || entry.deferred); - const namedByName = new Map(ast.loads.filter((entry) => entry.name).map((entry) => [entry.name, entry])); - const clientNames = new Set(publicClientLoads.flatMap((entry) => entry.name ? [entry.name] : [])); - const includeDependencies = (name) => { - for (const dependency of namedByName.get(name)?.dependsOn ?? []) { - if (clientNames.has(dependency)) - continue; - clientNames.add(dependency); - includeDependencies(dependency); - } - }; - for (const name of [...clientNames]) - includeDependencies(name); - const clientLoads = ast.loads.filter((entry) => !entry.name || clientNames.has(entry.name)); - const renderLoads = (exportName, execution, exposed) => { - const declarations = execution.filter((entry) => entry.name).map((entry) => { - const dependencies = (entry.dependsOn ?? []).map((dependency) => `const ${dependency} = await __load_${dependency}();`).join(` -`); - return ` let __promise_${entry.name}: Promise | undefined; - const __load_${entry.name} = () => (__promise_${entry.name} ??= (async () => { - ${dependencies} - ${entry.body} - })());`; - }).join(` -`); - const visible = exposed.filter((entry) => entry.name); - return `export async function ${exportName}(ctx: import("@wrnexus/core").Context) { -${exposed.filter((entry) => !entry.name).map((entry) => entry.body).join(` -`)} -${declarations} -${visible.length ? ` const __values = await Promise.all([${visible.map((entry) => `__load_${entry.name}()`).join(", ")}]); - return { ${visible.map((entry, index) => `${JSON.stringify(entry.name)}: __values[${index}]`).join(", ")} };` : ""} -}`; - }; - if (serverLoads.length > 0) - out.push(renderLoads("__wrnexusLoad", serverLoads, serverLoads)); - if (publicClientLoads.length > 0) - out.push(renderLoads("__wrnexusClientLoad", clientLoads, publicClientLoads)); - } - if (ast.actions.length > 0) { - for (const action of ast.actions) { - if (!action.schema) { - out.push(`export async function ${action.name}(${action.args.join(", ")}) {${action.body}}`); - continue; - } - out.push(`export async function ${action.name}(input: InferSchema, ctx: import("@wrnexus/core").Context) { - const invalidate = (...tags: string[]) => { - const bucket = (ctx.locals.__wrnexusInvalidatedTags ??= []); - bucket.push(...tags.flat()); - }; -${action.body} -}`); - } - out.push(`export const __wrnexusActions = { ${ast.actions.map((action) => `${action.name}: { run: ${action.name}, schema: ${action.schema ?? "undefined"} }`).join(", ")} };`); - out.push(`export const __wrnexusActionClients = { -${ast.actions.map((action) => ` ${action.name}: createActionClient<${action.schema ? `InferSchema` : "Record"}, Awaited>>("", ${JSON.stringify(action.name)}),`).join(` -`)} -};`); - } - if (ast.apis.length > 0) { - ast.apis.forEach((api, index) => { - const name = `__wrnexusApi_${api.method}_${index}`; - out.push(`// ${api.method} ${apiRoutePath(api.path)} -const ${name} = async (ctx: import("@wrnexus/core").Context) => {${api.body}};`); - }); - const entries = ast.apis.map((api, index) => ` ${JSON.stringify(`${api.method} ${apiRoutePath(api.path)}`)}: __wrnexusApi_${api.method}_${index},`); - out.push(`export const __wrnexusApi = { -${entries.join(` -`)} -};`); - const exported = new Set; - ast.apis.forEach((api, index) => { - if (exported.has(api.method)) - return; - exported.add(api.method); - out.push(`export const ${api.method} = __wrnexusApi_${api.method}_${index};`); - }); - } - if (ast.realtimes.length > 0) { - const handlers = ast.realtimes.flatMap((rt) => rt.handlers.map((h) => { - const params = ["ws", ...h.args].join(", "); - return ` ${h.event}(${params}: Event) {${h.body}},`; - })); - out.push(`export const websocket = { -${handlers.join(` -`)} -};`); - } - return out.join(` - -`) + ` -`; - } - function parseForExpr(value) { - const m = /^\s*([A-Za-z_$][\w$]*)\s*(?:,\s*([A-Za-z_$][\w$]*)\s*)?\s+in\s+([\s\S]+?)(?:\s+key\s+([\s\S]+?))?\s*$/.exec(value); - if (!m) - return null; - return { item: m[1], index: m[2], list: m[3].trim(), key: m[4]?.trim() }; - } - function loopVarsOf(node) { - if (node.type !== "element") - return []; - const attr = node.attrs.find((a) => !a.event && a.name === "data-for"); - if (!attr) - return []; - const parsed = parseForExpr(attr.value); - return parsed ? [parsed.item, ...parsed.index ? [parsed.index] : []] : []; - } - const JS_RESERVED = new Set([ - "class", - "for", - "default", - "function", - "return", - "if", - "else", - "new", - "delete", - "typeof", - "in", - "instanceof", - "void", - "do", - "while", - "switch", - "case", - "break", - "continue", - "this", - "super", - "import", - "export", - "extends", - "var", - "let", - "const", - "null", - "true", - "false", - "try", - "catch", - "finally", - "throw", - "yield", - "await", - "enum", - "with", - "debugger", - "implements", - "interface", - "package", - "private", - "protected", - "public", - "static" - ]); - function safeRef(name) { - return JS_RESERVED.has(name) ? `__p_${name}` : name; - } - function escLit(s) { - return s.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${"); - } - function componentBehavior(ast) { - const functions = (0, types_ts_1.eraseFunctionTypes)(targetFunctions(ast, "browser")); - const computed = ast.computed.map((entry) => ({ name: entry.name, expr: entry.expr.trim() })); - const effects = ast.effects.map((entry) => entry.body.trim()).filter(Boolean); - const lifecycle = { - ...ast.lifecycle.mount?.trim() ? { mount: ast.lifecycle.mount.trim() } : {}, - ...ast.lifecycle.update?.trim() ? { update: ast.lifecycle.update.trim() } : {}, - ...ast.lifecycle.unmount?.trim() ? { unmount: ast.lifecycle.unmount.trim() } : {} - }; - const watches = ast.watches.map((watch) => ({ - state: watch.state, - body: watch.body.trim() - })); - if (!functions && ast.outputs.length === 0 && computed.length === 0 && effects.length === 0 && Object.keys(lifecycle).length === 0 && watches.length === 0) { - return null; - } - return { - functions, - outputs: ast.outputs, - computed, - effects, - lifecycle, - watches - }; - } - function behaviorAttribute(behavior) { - if (!behavior) { - return ""; - } - const encoded = node_buffer_1.Buffer.from(JSON.stringify(behavior), "utf8").toString("base64"); - return ` data-wrn-behavior="${encoded}"`; - } - const INTERP_RE = /\{([^{}]+)\}/g; - function exprRefsState(expr, stateNames) { - for (const name of stateNames) { - if (new RegExp(`\\b${name}\\b`).test(expr)) - return true; - } - return false; - } - function exprRefsComponentReactiveValue(expr, ctx) { - return exprRefsState(expr, ctx.stateNames) || exprRefsState(expr, ctx.propNames) || exprRefsState(expr, ctx.functionNames); - } - function viewHasEvents(nodes) { - return nodes.some((node) => { - if (node.type === "text") - return false; - if (node.type === "each") { - return viewHasEvents(node.body) || viewHasEvents(node.empty); - } - if (node.type === "if") { - return node.branches.some((branch) => viewHasEvents(branch.body)); - } - return node.attrs.some((attr) => attr.event) || viewHasEvents(node.children); - }); - } - function viewHasServerEach(nodes) { - return nodes.some((node) => { - if (node.type === "text") - return false; - if (node.type === "each") - return true; - if (node.type === "if") { - return node.branches.some((branch) => viewHasServerEach(branch.body)); - } - return viewHasServerEach(node.children); - }); - } - function viewHasRestAttributeSpread(nodes) { - return nodes.some((node) => { - if (node.type === "text") - return false; - if (node.type === "each") { - return viewHasRestAttributeSpread(node.body) || viewHasRestAttributeSpread(node.empty); - } - if (node.type === "if") { - return node.branches.some((branch) => viewHasRestAttributeSpread(branch.body)); - } - return node.attrs.some((attr) => /^\{\.\.\.([A-Za-z_$][\w$]*)\}$/.test(attr.name)) || viewHasRestAttributeSpread(node.children); - }); - } - function compileText(raw, ctx) { - let out = ""; - let last = 0; - let m; - INTERP_RE.lastIndex = 0; - while (m = INTERP_RE.exec(raw)) { - out += escLit(raw.slice(last, m.index)); - const expr = m[1].trim(); - if (expr.startsWith("t:")) { - out += escLit(``); - } else if (ctx.loopVars && exprRefsState(expr, ctx.loopVars)) { - out += escLit(`{${expr}}`); - } else if (expr === "content") { - out += `\${__wireRaw(${ctx.resolveExpr(expr)})}`; - } else if (exprRefsComponentReactiveValue(expr, ctx)) { - out += escLit(``) + `\${__wireHtml(${ctx.resolveExpr(expr)})}` + escLit(``); - } else { - out += `\${__wireHtml(${ctx.resolveExpr(expr)})}`; - } - last = m.index + m[0].length; - } - return out + escLit(raw.slice(last)); - } - function compileAttrValue(raw, ctx) { - if (!raw.includes("{")) - return escLit(attrEscape(raw)); - let out = ""; - let last = 0; - let m; - INTERP_RE.lastIndex = 0; - while (m = INTERP_RE.exec(raw)) { - out += escLit(attrEscape(raw.slice(last, m.index))); - const expr = m[1].trim(); - if (ctx.loopVars && exprRefsState(expr, ctx.loopVars)) { - out += escLit(`{${expr}}`); - } else { - out += `\${__wireAttr(${ctx.resolveExpr(expr)})}`; - } - last = m.index + m[0].length; - } - return out + escLit(attrEscape(raw.slice(last))); - } - function renderComponentIfNode(node, ctx) { - let expression = "``"; - for (let index = node.branches.length - 1;index >= 0; index--) { - const branch = node.branches[index]; - const body = branch.body.map((child) => renderComponentNode(child, ctx)).join(""); - const bodyExpression = "`" + body + "`"; - expression = branch.cond === null ? bodyExpression : `(${ctx.resolveExpr(branch.cond)}) ? ${bodyExpression} : ${expression}`; - } - return "${" + expression + "}"; - } - function renderComponentEachNode(node, ctx) { - const item = node.item; - const index = node.index ?? "__wi"; - const list = ctx.resolveExpr(node.list); - const childCtx = { - ...ctx, - serverLocals: new Set([...ctx.serverLocals ?? [], item, index]) - }; - const body = node.body.map((child) => renderComponentNode(child, childCtx)).join(""); - const empty = node.empty.map((child) => renderComponentNode(child, ctx)).join(""); - return "${(() => { const __wl = Array.isArray(" + list + ") ? (" + list + ") : []; return __wl.length ? __wl.map((" + item + ", " + index + ") => `" + body + '`).join("") : `' + empty + "`; })()}"; - } - function serverLoopLocalsAttribute(ctx) { - const locals = [...ctx.serverLocals ?? []]; - if (locals.length === 0) { - return ""; - } - const entries = locals.map((name) => `${JSON.stringify(name)}: ${name}`).join(", "); - return ` data-wrn-loop-locals="\${__wrnexusEncodeLoopLocals({ ${entries} })}"`; - } - function unwrapDirectiveExpression(raw) { - const value = raw.trim(); - if (!value.startsWith("{") || !value.endsWith("}")) { - return value; - } - let depth = 0; - let quote = null; - let escaped = false; - for (let index = 0;index < value.length; index++) { - const char = value[index]; - if (escaped) { - escaped = false; - continue; - } - if (quote) { - if (char === "\\") { - escaped = true; - } else if (char === quote) { - quote = null; - } - continue; - } - if (char === '"' || char === "'" || char === "`") { - quote = char; - continue; - } - if (char === "{") - depth++; - if (char === "}") - depth--; - if (depth === 0 && index < value.length - 1) { - return value; - } - } - return depth === 0 ? value.slice(1, -1).trim() : value; - } - function renderComponentNode(node, ctx) { - if (node.type === "text") - return compileText(node.value, ctx); - if (node.type === "each") { - return renderComponentEachNode(node, ctx); - } - if (node.type === "if") { - return renderComponentIfNode(node, ctx); - } - if (node.tag === "Static" || node.tag === "Dynamic") { - const inner2 = node.children.map((child) => renderComponentNode(child, ctx)).join(""); - return node.tag === "Static" ? inner2 : `${inner2}`; - } - if (node.tag === "KeepAlive") { - const key = node.attrs.find((attribute) => attribute.name === "key")?.value ?? "default"; - const inner2 = node.children.map((child) => renderComponentNode(child, ctx)).join(""); - return `
${inner2}
`; - } - if (node.tag === "Portal" || node.tag === "Transition" || node.tag === "Component") { - const inner2 = node.children.map((child) => renderComponentNode(child, ctx)).join(""); - const attribute = node.tag === "Portal" ? "data-wrn-portal" : node.tag === "Transition" ? "data-wrn-transition" : "data-wrn-dynamic-component"; - const source = node.tag === "Portal" ? "to" : node.tag === "Transition" ? "name" : "is"; - const fallback = node.tag === "Portal" ? "body" : node.tag === "Transition" ? "wrn-transition" : ""; - const raw = node.attrs.find((item) => item.name === source)?.value ?? fallback; - return `
${inner2}
`; - } - if (isComponentTag(node.tag)) { - return renderNestedComponentInvocation(node, ctx); - } - const loopVariables = loopVarsOf(node); - const elementContext = { - ...ctx, - forwardRestAttrs: false, - ...loopVariables.length > 0 ? { loopVars: new Set([...ctx.loopVars ?? [], ...loopVariables]) } : {} - }; - let bindIndex = 0; - const staticClasses = []; - const conditionalClasses = []; - for (const attr of node.attrs) { - if (!attr.event && attr.name === "class") { - staticClasses.push(attr.value); - } - if (!attr.event && attr.name.startsWith("class:")) { - conditionalClasses.push({ - className: attr.name.slice("class:".length), - expression: unwrapDirectiveExpression(attr.value) - }); - } - } - const isExplicitComponentMount = node.attrs.some((attribute) => attribute.name === "data-component"); - const attrs = node.attrs.filter((a) => a.name !== "class" && !a.name.startsWith("class:")).map((a) => { - const spread = /^\{\.\.\.([A-Za-z_$][\w$]*)\}$/.exec(a.name); - if (spread) { - return `\${__wireSpreadAttrs(${elementContext.resolveExpr(spread[1])})}`; - } - if (a.event) { - return ` ${eventAttribute(a.name)}="${escLit(attrEscape(a.value))}"`; - } - if (a.boolean) { - return ` ${a.name}`; - } - if (isHtmlBooleanAttribute(a.name)) { - const expression = wholeAttributeExpression(a.value); - if (expression) { - const referencesState2 = exprRefsComponentReactiveValue(a.value, ctx); - const referencesLoopVariable2 = elementContext.loopVars ? exprRefsState(a.value, elementContext.loopVars) : false; - const referencesServerLocal2 = ctx.serverLocals ? exprRefsState(a.value, ctx.serverLocals) : false; - const marker2 = referencesState2 || referencesLoopVariable2 || referencesServerLocal2 ? ` data-wrn-bind-${bindIndex++}="${escLit(attrEscape(JSON.stringify([a.name, a.value])))}"` : ""; - if (referencesLoopVariable2) { - return ` data-wrn-bind-${bindIndex++}="${escLit(attrEscape(JSON.stringify([a.name, a.value])))}"`; - } - return `\${__wireBooleanAttr(${JSON.stringify(a.name)}, ${elementContext.resolveExpr(expression)})}${marker2}`; - } - if (a.value === "false") - return ""; - if (a.value === "true" || a.value === "") - return ` ${a.name}`; - } - const wholeExpression = wholeAttributeExpression(a.value); - if (a.name === "data-show" && wholeExpression) { - return ` data-show="${escLit(attrEscape(wholeExpression))}"`; - } - const compiledValue = isExplicitComponentMount && wholeExpression ? `\${__wireProp(${elementContext.resolveExpr(wholeExpression)})}` : compileAttrValue(a.value, elementContext); - const rendered = ` ${a.name}="${compiledValue}"`; - const referencesState = exprRefsComponentReactiveValue(a.value, ctx); - const referencesLoopVariable = elementContext.loopVars ? exprRefsState(a.value, elementContext.loopVars) : false; - const referencesServerLocal = ctx.serverLocals ? exprRefsState(a.value, ctx.serverLocals) : false; - if (!a.value.includes("{") || !referencesState && !referencesLoopVariable && !referencesServerLocal) { - return rendered; - } - const marker = attrEscape(JSON.stringify([a.name, a.value])); - return `${rendered} data-wrn-bind-${bindIndex++}="${escLit(marker)}"`; - }).join(""); - const initialConditionalClasses = conditionalClasses.map(({ className, expression }) => { - const referencesLoopVariable = elementContext.loopVars ? exprRefsState(expression, elementContext.loopVars) : false; - if (referencesLoopVariable) { - return ""; - } - return `\${(${ctx.resolveExpr(expression)}) ? ${JSON.stringify(` ${className}`)} : ""}`; - }).join(""); - const staticClassValue = staticClasses.join(" "); - const classReferencesState = exprRefsComponentReactiveValue(staticClassValue, ctx); - const classReferencesLoopVariable = elementContext.loopVars ? exprRefsState(staticClassValue, elementContext.loopVars) : false; - const classReferencesServerLocal = ctx.serverLocals ? exprRefsState(staticClassValue, ctx.serverLocals) : false; - const classHasReactiveExpression = staticClassValue.includes("{") && (classReferencesState || classReferencesLoopVariable || classReferencesServerLocal); - const classAttribute = staticClasses.length > 0 || conditionalClasses.length > 0 ? ` class="${compileAttrValue(staticClassValue, elementContext)}${initialConditionalClasses}"` : ""; - const classReactiveBinding = classHasReactiveExpression ? ` data-wrn-bind-class="${escLit(attrEscape(JSON.stringify(["class", staticClassValue])))}"` : ""; - const classBindings = conditionalClasses.map(({ className, expression }, index) => { - const marker = attrEscape(JSON.stringify([className, expression])); - return ` data-wrn-class-${index}="${escLit(marker)}"`; - }).join(""); - const loopLocalsAttribute = serverLoopLocalsAttribute(ctx); - const allAttrs = `${loopLocalsAttribute}` + `${ctx.forwardRestAttrs ? "${__wireSpreadAttrs(__attrs)}" : ""}` + `${ctx.eventNames?.length ? ` data-wrn-events="${attrEscape(ctx.eventNames.join(","))}"` : ""}` + `${classAttribute}` + `${classReactiveBinding}` + `${classBindings}` + `${attrs}`; - if (parser_ts_1.VOID_ELEMENTS.has(node.tag.toLowerCase())) { - return `<${node.tag}${allAttrs}>`; - } - const inner = node.children.map((child) => renderComponentNode(child, elementContext)).join(""); - return `<${node.tag}${allAttrs}>${inner}`; - } - function generateComponent(ast) { - const out = []; - if (ast.imports.length > 0) - out.push(generatedImports(ast).join(` -`)); - const hasServerEach = viewHasServerEach(ast.view); - const effectiveProps = ast.kind === "layout" && !ast.props.some((prop) => prop.name === "content") ? [ - { - name: "content", - default: '""', - valueType: "string", - required: false - }, - ...ast.props - ] : ast.props; - const browserStates = ast.states.filter((state) => state.runtime !== "server"); - const stateNames = new Set([ - ...browserStates.map((entry) => entry.name), - ...ast.computed.map((entry) => entry.name) - ]); - const nameRefs = new Map; - for (const p of effectiveProps) { - nameRefs.set(p.name, safeRef(p.name)); - } - if (!nameRefs.has("attrs")) { - nameRefs.set("attrs", "__attrs"); - } - for (const s of ast.states) - nameRefs.set(s.name, safeRef(s.name)); - for (const entry of ast.computed) - nameRefs.set(entry.name, safeRef(entry.name)); - const resolveExpr = (expr) => { - let result = expr; - for (const [name, ref] of nameRefs) { - if (name !== ref) - result = result.replace(new RegExp(`\\b${name}\\b`, "g"), ref); - } - return result; - }; - const ctx = { - stateNames, - propNames: new Set(effectiveProps.map((entry) => entry.name)), - functionNames: new Set(ast.runtimeFunctions.filter((fn) => fn.runtime !== "server").map((fn) => fn.name)), - resolveExpr, - eventNames: publicOutputNames(ast) - }; - const serverFunctions = targetFunctions(ast, "server"); - const hasExplicitRestSpread = viewHasRestAttributeSpread(ast.view); - const rootElementIndex = ast.view.findIndex((node) => node.type === "element"); - const automaticallyForwardRootAttrs = !hasExplicitRestSpread && !effectiveProps.some((prop) => prop.name === "attrs") && rootElementIndex >= 0; - const viewCode = ast.view.map((node, index) => renderComponentNode(node, automaticallyForwardRootAttrs && index === rootElementIndex ? { ...ctx, forwardRestAttrs: true } : ctx)).join(""); - const styles = ast.styles.map((body) => body.trim()).filter(Boolean); - const styleTag = escLit(localStyleTag(ast, styles)); - const behavior = componentBehavior(ast); - const needsScope = ast.runtime !== "server" && (effectiveProps.length > 0 || browserStates.length > 0 || ast.computed.length > 0 || viewHasEvents(ast.view) || behavior !== null); - if (hasServerEach || needsScope) { - out.push(`import { Buffer as __WrnexusBuffer } from "node:buffer";`); - } - const scopeKeys = [ - ...effectiveProps.map((prop) => prop.name), - ...browserStates.map((state) => state.name) - ]; - const behaviorAttr = behaviorAttribute(behavior); - const decls = []; - for (const prop of effectiveProps) { - if (prop.required) { - decls.push(` if (__p[${JSON.stringify(prop.name)}] === undefined) throw new TypeError(${JSON.stringify(`${ast.name} requires prop '${prop.name}' (${prop.valueType ?? "unknown"})`)});`); - } - decls.push(` const ${nameRefs.get(prop.name)}: ${prop.valueType ?? "unknown"} = __coerce(__p[${JSON.stringify(prop.name)}], (${resolveExpr(prop.default)}), ${JSON.stringify((0, types_ts_1.runtimeTypeOf)(prop.valueType))}, ${JSON.stringify(prop.name)}) as ${prop.valueType ?? "unknown"};`); - } - if (!effectiveProps.some((prop) => prop.name === "attrs")) { - decls.push(` const __attrs = __restProps(__p, new Set(${JSON.stringify(effectiveProps.map((prop) => prop.name))}));`); - } - for (const state of ast.states) { - decls.push(` let ${nameRefs.get(state.name)}${state.valueType ? `: ${state.valueType}` : ""} = (${resolveExpr(state.expr)});`); - } - for (const entry of ast.computed) { - decls.push(` const ${nameRefs.get(entry.name)} = (${resolveExpr(entry.expr)});`); - } - const returnExpr = needsScope ? "`" + styleTag + `
` + viewCode + "
`" : "`" + styleTag + viewCode + "`"; - const scopeLine = needsScope && scopeKeys.length > 0 ? ` const __scopeState = { ${scopeKeys.map((key) => `${JSON.stringify(key)}: ${nameRefs.get(key)}`).join(", ")} }; - const __scope = __wrnexusScopeDecl(__scopeState); - const __scopePayload = __WrnexusBuffer.from(JSON.stringify(__scopeState), "utf8").toString("base64"); -` : needsScope ? ` const __scopeState = {}; - const __scope = ""; - const __scopePayload = __WrnexusBuffer.from("{}", "utf8").toString("base64"); -` : ""; - if (ast.kind === "layout") { - out.push(`export const __wrnexusLayout = ${JSON.stringify(ast.name)};`); - } else { - out.push(`export const __wrnexusComponent = ${JSON.stringify(ast.name)};`); - } - out.push(`export const __wrnexusRuntime = ${JSON.stringify(ast.runtime ?? "universal")};`); - out.push(`export const __wrnexusRender = ${JSON.stringify(ast.renderMode ?? "hybrid")};`); - out.push(`export const __wrnexusHydrate = ${JSON.stringify(["static", "server"].includes(ast.renderMode ?? "") ? "none" : ast.hydrate ?? "load")};`); - out.push(`export const __wrnexusHydrationId = ${JSON.stringify(hydrationId(ast))};`); - if (Object.keys(ast.cache ?? {}).length > 0) - out.push(`export const __wrnexusCache = ${JSON.stringify(ast.cache, null, 2)};`); - if (Object.keys(ast.security).length > 0) { - out.push(`export const __wrnexusSecurity = ${JSON.stringify(ast.security, null, 2)};`); - } - if (Object.keys(ast.navigation).length > 0) { - out.push(`export const __wrnexusNavigation = ${JSON.stringify(ast.navigation, null, 2)};`); - } - const componentStyleExport = localStyleExport(ast, styles); - if (componentStyleExport) - out.push(componentStyleExport); - if (behavior) { - out.push(`export const __wrnexusBehavior = ${JSON.stringify(behavior, null, 2)};`); - } - const typeSource = ast.types.map((body) => body.trim()).filter(Boolean).join(` - -`); - if (typeSource) - out.push(typeSource); - if (effectiveProps.length > 0) { - out.push(`export interface ${ast.name}Props { - [attribute: string]: unknown; -${effectiveProps.map((prop) => ` ${JSON.stringify(prop.name)}${prop.required ? "" : "?"}: ${prop.valueType ?? "unknown"};`).join(` -`)} -}`); - } - if (ast.outputs.length > 0) { - out.push(`export interface ${ast.name}Outputs { -${ast.outputs.map((output) => ` ${JSON.stringify(output.name)}(${output.payload ? `${output.payload.name}${output.payload.optional ? "?" : ""}: ${output.payload.valueType}` : ""}): void;`).join(` -`)} -}`); - } - out.push(`function __coerce(v: unknown, def: unknown, declared: string = "unknown", propName: string = "prop"): unknown { - if (v === undefined || v === null) { - return def; - } - - if (declared === "number" || typeof def === "number") { - const parsed = Number(v); - if (!Number.isFinite(parsed)) throw new TypeError("Expected a finite number prop"); - return parsed; - } - - if (declared === "boolean" || typeof def === "boolean") { - if (v === true || v === "" || v === "true" || v === 1 || v === "1") return true; - if (v === false || v === "false" || v === 0 || v === "0") return false; - throw new TypeError("Expected a boolean prop"); - } - - if (declared === "array" || Array.isArray(def)) { - if (Array.isArray(v)) { - return v; - } - - if (typeof v === "string") { - try { - const parsed = JSON.parse(v); - if (!Array.isArray(parsed)) throw new TypeError("Expected an array prop '" + propName + "'"); - return parsed; - } catch { - throw new TypeError("Expected an array prop '" + propName + "'"); - } - } - - throw new TypeError("Expected an array prop '" + propName + "'"); - } - - if (declared === "object" || (def !== null && typeof def === "object")) { - if ( - v !== null && - typeof v === "object" && - !Array.isArray(v) - ) { - return v; - } - - if (typeof v === "string") { - try { - const parsed = JSON.parse(v); - - if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) { - throw new TypeError("Expected an object prop '" + propName + "'"); - } - return parsed; - } catch { - throw new TypeError("Expected an object prop '" + propName + "'"); - } - } - - throw new TypeError("Expected an object prop '" + propName + "'"); - } - - if (declared === "bigint") return BigInt(v as string | number | bigint | boolean); - if (declared === "function" && typeof v !== "function") { - throw new TypeError("Expected a function prop"); - } - return declared === "unknown" && def === undefined ? v : String(v); -} - -function __restProps( - props: Record, - declared: Set, -): Record { - return Object.fromEntries( - Object.entries(props).filter(([name]) => !declared.has(name)), - ); -} - -function __wireHtml(v: unknown): string { - return String(v == null ? "" : v).replace( - /[&<>]/g, - (c) => - c === "&" - ? "&" - : c === "<" - ? "<" - : ">", - ); -} - -function __wireAttr(v: unknown): string { - return String(v == null ? "" : v).replace( - /[&<>"]/g, - (c) => - c === "&" - ? "&" - : c === "<" - ? "<" - : c === ">" - ? ">" - : """, - ); -} - -function __wireBooleanAttr(name: string, value: unknown): string { - return value === true || - value === "true" || - value === "" || - value === 1 || - value === "1" || - value === name - ? " " + name - : ""; -} - -function __wireSpreadAttrs(value: unknown): string { - if (value === null || typeof value !== "object" || Array.isArray(value)) return ""; - - const booleanAttributes = new Set(${JSON.stringify([...HTML_BOOLEAN_ATTRIBUTES])}); - const attributes: string[] = []; - - for (const [name, raw] of Object.entries(value)) { - const lowerName = name.toLowerCase(); - if ( - !/^[A-Za-z_:][A-Za-z0-9_.:-]*$/.test(name) || - lowerName.startsWith("on") || - lowerName === "style" || - lowerName === "slot" || - lowerName === "data-component" || - // Internal markers must not leak through a spread. Parent-owned output - // and prop bindings ride from the mount onto the - // rendered child root so the mounting scope can bind them there. - (lowerName.startsWith("data-wrn") && - !lowerName.startsWith("data-wrn-out-") && - !lowerName.startsWith("data-wrn-prop-bind-")) - ) { - continue; - } - - if (booleanAttributes.has(lowerName)) { - attributes.push(__wireBooleanAttr(name, raw)); - continue; - } - - if (raw === false || raw === null || raw === undefined) continue; - attributes.push(" " + name + '="' + __wireAttr(raw) + '"'); - } - - return attributes.join(""); -} - -function __wireProp(v: unknown): string { - const value = - v !== null && typeof v === "object" - ? JSON.stringify(v) - : String(v == null ? "" : v); - - return __wireAttr(value); -} - -function __wireRaw(v: unknown): string { - return String(v == null ? "" : v); -}`); - if (hasServerEach) { - out.push(`function __wrnexusEncodeLoopLocals(value: Record): string { - return __WrnexusBuffer.from(JSON.stringify(value), "utf8").toString("base64"); -}`); - } - if (needsScope) { - out.push(`function __wrnexusSerializeScopeValue(value: unknown): string { - if (value === undefined) { - return "undefined"; - } - - if (value === null) { - return "null"; - } - - if (typeof value === "number") { - return Number.isFinite(value) - ? String(value) - : "null"; - } - - if (typeof value === "boolean") { - return value ? "true" : "false"; - } - - if (typeof value === "string") { - return JSON.stringify(value); - } - - try { - const serialized = JSON.stringify(value); - - return serialized === undefined - ? "undefined" - : serialized; - } catch { - return "null"; - } - } - - function __wrnexusScopeDecl(obj: Record): string { - return Object.keys(obj) - .map( - (key) => - key + - ": " + - __wrnexusSerializeScopeValue( - obj[key], - ), - ) - .join(", ") - .replace(/&/g, "&") - .replace(/"/g, """) - .replace(//g, ">"); - }`); - } - const serverFunctionSource = serverFunctions ? `${serverFunctions} -` : ""; - out.push(`export function render(props: ${effectiveProps.length > 0 ? `${ast.name}Props` : "Record"} = {} as ${effectiveProps.length > 0 ? `${ast.name}Props` : "Record"}): string { -` + ` const __p = props || {}; -` + (decls.length > 0 ? decls.join(` -`) + ` -` : "") + serverFunctionSource + scopeLine + ` return ${returnExpr}; -` + `}`); - out.push(`export default { name: ${JSON.stringify(ast.name)}, kind: ${JSON.stringify(ast.kind)}, render };`); - return out.join(` - -`) + ` -`; - } - function __wireRaw(v) { - return String(v == null ? "" : v); - } - function wholeAttributeExpression(value) { - const match = /^\s*\{([\s\S]+)\}\s*$/.exec(value); - return match?.[1]?.trim() || null; - } - function __wireHtml(v) { - return String(v == null ? "" : v).replace(/[&<>]/g, (c) => c === "&" ? "&" : c === "<" ? "<" : ">"); - } - function __wireAttr(v) { - return String(v == null ? "" : v).replace(/[&<>"]/g, (c) => c === "&" ? "&" : c === "<" ? "<" : c === ">" ? ">" : """); - } - function __wireProp(v) { - const value = v !== null && typeof v === "object" ? JSON.stringify(v) : String(v == null ? "" : v); - return __wireAttr(value); - } - function renderPageComponentAttr(attr, dynamicExpressions) { - if (attr.event) { - return ` ${componentEventAttribute(attr.name)}="${attrEscape(attr.value)}"`; - } - if (attr.boolean) { - return ` ${attr.name}`; - } - const expression = wholeAttributeExpression(attr.value); - if (!expression) { - return ` ${attr.name}="${attrEscape(safeAttributeValue(attr.name, attr.value))}"`; - } - dynamicExpressions.push(`\${__wrnexusPropAttr(${expression})}`); - const marker = `\x00WRNEACH${dynamicExpressions.length - 1}\x00`; - return ` ${attr.name}="${marker}"`; - } - }, - "packages/compiler/src/component-contract.ts": function(module3, exports3, require2, __filename2, __dirname2) { - Object.defineProperty(exports3, "__esModule", { value: true }); - exports3.createComponentContract = createComponentContract; - function unionOptions(type) { - if (!type || !type.includes("|")) - return; - const values = type.split("|").map((part) => part.trim()).filter((part) => /^(?:"[^"]*"|'[^']*')$/.test(part)).map((part) => part.slice(1, -1)); - return values.length ? values : undefined; - } - function createComponentContract(ast) { - return { - name: ast.name, - kind: ast.kind, - props: ast.props.map((prop) => ({ - name: prop.name, - type: prop.valueType ?? "unknown", - required: prop.required, - ...prop.default !== "undefined" ? { default: prop.default } : {}, - ...unionOptions(prop.valueType) ? { options: unionOptions(prop.valueType) } : {} - })), - outputs: ast.outputs.map((output) => ({ - name: output.name, - ...output.payload ? { payloadName: output.payload.name, payloadType: output.payload.valueType } : {} - })), - functions: ast.runtimeFunctions.map((fn) => ({ - name: fn.name, - runtime: fn.runtime, - async: fn.async, - parameters: fn.parameters.map((param) => ({ - name: param.name, - type: param.valueType ?? "unknown", - optional: param.optional - })), - returnType: fn.returnType ?? (fn.async ? "Promise" : "unknown") - })), - states: ast.states.map((state) => ({ - name: state.name, - runtime: state.runtime, - type: state.valueType ?? "unknown", - initializer: state.expr - })), - computed: ast.computed.map((entry) => ({ - name: entry.name, - type: entry.valueType ?? "unknown", - expression: entry.expr - })), - imports: ast.structuredImports.map((entry) => ({ - source: entry.source, - typeOnly: entry.typeOnly, - ...entry.defaultImport ? { defaultImport: entry.defaultImport } : {}, - namedImports: entry.namedImports.map((named) => named.local) - })) - }; - } - }, - "packages/compiler/src/import-resolver.ts": function(module3, exports3, require2, __filename2, __dirname2) { - Object.defineProperty(exports3, "__esModule", { value: true }); - exports3.resolveWrnImport = resolveWrnImport; - exports3.resolveWrnImports = resolveWrnImports; - const node_fs_1 = require2("node:fs"); - const node_path_1 = require2("node:path"); - function candidates(path) { - return (0, node_path_1.extname)(path) ? [path] : [ - path, - `${path}.wrn`, - `${path}.ts`, - `${path}.d.ts`, - (0, node_path_1.join)(path, "index.wrn"), - (0, node_path_1.join)(path, "index.ts") - ]; - } - function resolveWrnImport(declaration, importer, options) { - const source = declaration.source; - const aliases = { - "@": "./app", - ...options.aliases ?? {} - }; - const alias = Object.keys(aliases).filter((key) => key.length > 0 && (source === key || source.startsWith(`${key}/`))).sort((left, right) => right.length - left.length)[0]; - if (!source.startsWith(".") && !alias) - return { declaration, resolved: source }; - const base = alias ? (0, node_path_1.resolve)(options.appRoot, aliases[alias], source === alias ? "" : source.slice(alias.length + 1)) : (0, node_path_1.resolve)((0, node_path_1.dirname)(importer), source); - const found = candidates(base).find((candidate) => { - if (!(0, node_fs_1.existsSync)(candidate)) - return false; - try { - return (0, node_fs_1.statSync)(candidate).isFile(); - } catch { - return false; - } - }); - if (found) - return { declaration, resolved: (0, node_fs_1.realpathSync)(found) }; - const severity = (options.mode ?? "compatible") === "explicit" ? "error" : "warning"; - return { - declaration, - diagnostic: { - code: "WRN-IMPORT-NOT-FOUND", - message: `Cannot resolve import '${source}' from ${importer}`, - severity - } - }; - } - function resolveWrnImports(declarations, importer, options) { - return declarations.map((declaration) => resolveWrnImport(declaration, importer, options)); - } - }, - "packages/compiler/src/index.ts": function(module3, exports3, require2, __filename2, __dirname2) { - Object.defineProperty(exports3, "__esModule", { value: true }); - exports3.DependencyGraph = exports3.createCompilationCache = exports3.compilationKey = exports3.runtimeTypeOf = exports3.inferredRuntimeType = exports3.eraseFunctionTypes = exports3.LexError = exports3.Lexer = exports3.NativeCompileError = exports3.generateNative = exports3.runtimeCapabilities = exports3.analyzeRuntimeImports = exports3.optimizeAst = exports3.analyzeRuntimeRequirements = exports3.analyzeOptimizations = exports3.createWrnSourceMap = exports3.resolveWrnImports = exports3.resolveWrnImport = exports3.createComponentContract = exports3.generateStoreModule = exports3.generateStoreBrowserModule = exports3.generateDeclarations = exports3.rpcManifest = exports3.generateServerFunctionsModule = exports3.generateBrowserModule = exports3.generateTargets = exports3.generate = exports3.ParseError = exports3.parse = exports3.formatDiagnostic = exports3.diagnosticFromError = exports3.diagnose = exports3.assertValidAst = exports3.formatWrn = undefined; - exports3.compileNativeWireFile = compileNativeWireFile; - exports3.compileWireFile = compileWireFile; - exports3.compile = compile; - const syntax_1 = require2("@wrnexus/syntax"); - var syntax_2 = require2("@wrnexus/syntax"); - Object.defineProperty(exports3, "formatWrn", { enumerable: true, get: function() { - return syntax_2.formatWrn; - } }); - const codegen_ts_1 = require2("./codegen.js"); - const native_codegen_ts_1 = require2("./native-codegen.js"); - var syntax_3 = require2("@wrnexus/syntax"); - Object.defineProperty(exports3, "assertValidAst", { enumerable: true, get: function() { - return syntax_3.assertValidAst; - } }); - Object.defineProperty(exports3, "diagnose", { enumerable: true, get: function() { - return syntax_3.diagnose; - } }); - Object.defineProperty(exports3, "diagnosticFromError", { enumerable: true, get: function() { - return syntax_3.diagnosticFromError; - } }); - Object.defineProperty(exports3, "formatDiagnostic", { enumerable: true, get: function() { - return syntax_3.formatDiagnostic; - } }); - Object.defineProperty(exports3, "parse", { enumerable: true, get: function() { - return syntax_3.parse; - } }); - Object.defineProperty(exports3, "ParseError", { enumerable: true, get: function() { - return syntax_3.ParseError; - } }); - var codegen_ts_2 = require2("./codegen.js"); - Object.defineProperty(exports3, "generate", { enumerable: true, get: function() { - return codegen_ts_2.generate; - } }); - var targets_ts_1 = require2("./targets.js"); - Object.defineProperty(exports3, "generateTargets", { enumerable: true, get: function() { - return targets_ts_1.generateTargets; - } }); - var client_codegen_ts_1 = require2("./client-codegen.js"); - Object.defineProperty(exports3, "generateBrowserModule", { enumerable: true, get: function() { - return client_codegen_ts_1.generateBrowserModule; - } }); - var server_codegen_ts_1 = require2("./server-codegen.js"); - Object.defineProperty(exports3, "generateServerFunctionsModule", { enumerable: true, get: function() { - return server_codegen_ts_1.generateServerFunctionsModule; - } }); - Object.defineProperty(exports3, "rpcManifest", { enumerable: true, get: function() { - return server_codegen_ts_1.rpcManifest; - } }); - var type_codegen_ts_1 = require2("./type-codegen.js"); - Object.defineProperty(exports3, "generateDeclarations", { enumerable: true, get: function() { - return type_codegen_ts_1.generateDeclarations; - } }); - var store_codegen_ts_1 = require2("./store-codegen.js"); - Object.defineProperty(exports3, "generateStoreBrowserModule", { enumerable: true, get: function() { - return store_codegen_ts_1.generateStoreBrowserModule; - } }); - Object.defineProperty(exports3, "generateStoreModule", { enumerable: true, get: function() { - return store_codegen_ts_1.generateStoreModule; - } }); - var component_contract_ts_1 = require2("./component-contract.js"); - Object.defineProperty(exports3, "createComponentContract", { enumerable: true, get: function() { - return component_contract_ts_1.createComponentContract; - } }); - var import_resolver_ts_1 = require2("./import-resolver.js"); - Object.defineProperty(exports3, "resolveWrnImport", { enumerable: true, get: function() { - return import_resolver_ts_1.resolveWrnImport; - } }); - Object.defineProperty(exports3, "resolveWrnImports", { enumerable: true, get: function() { - return import_resolver_ts_1.resolveWrnImports; - } }); - var source_map_ts_1 = require2("./source-map.js"); - Object.defineProperty(exports3, "createWrnSourceMap", { enumerable: true, get: function() { - return source_map_ts_1.createWrnSourceMap; - } }); - var analysis_ts_1 = require2("./analysis.js"); - Object.defineProperty(exports3, "analyzeOptimizations", { enumerable: true, get: function() { - return analysis_ts_1.analyzeOptimizations; - } }); - Object.defineProperty(exports3, "analyzeRuntimeRequirements", { enumerable: true, get: function() { - return analysis_ts_1.analyzeRuntimeRequirements; - } }); - Object.defineProperty(exports3, "optimizeAst", { enumerable: true, get: function() { - return analysis_ts_1.optimizeAst; - } }); - var runtime_capabilities_ts_1 = require2("./runtime-capabilities.js"); - Object.defineProperty(exports3, "analyzeRuntimeImports", { enumerable: true, get: function() { - return runtime_capabilities_ts_1.analyzeRuntimeImports; - } }); - Object.defineProperty(exports3, "runtimeCapabilities", { enumerable: true, get: function() { - return runtime_capabilities_ts_1.runtimeCapabilities; - } }); - var native_codegen_ts_2 = require2("./native-codegen.js"); - Object.defineProperty(exports3, "generateNative", { enumerable: true, get: function() { - return native_codegen_ts_2.generateNative; - } }); - Object.defineProperty(exports3, "NativeCompileError", { enumerable: true, get: function() { - return native_codegen_ts_2.NativeCompileError; - } }); - var syntax_4 = require2("@wrnexus/syntax"); - Object.defineProperty(exports3, "Lexer", { enumerable: true, get: function() { - return syntax_4.Lexer; - } }); - Object.defineProperty(exports3, "LexError", { enumerable: true, get: function() { - return syntax_4.LexError; - } }); - var syntax_5 = require2("@wrnexus/syntax"); - Object.defineProperty(exports3, "eraseFunctionTypes", { enumerable: true, get: function() { - return syntax_5.eraseFunctionTypes; - } }); - Object.defineProperty(exports3, "inferredRuntimeType", { enumerable: true, get: function() { - return syntax_5.inferredRuntimeType; - } }); - Object.defineProperty(exports3, "runtimeTypeOf", { enumerable: true, get: function() { - return syntax_5.runtimeTypeOf; - } }); - function compileNativeWireFile(source) { - const ast = (0, syntax_1.parse)(source); - (0, syntax_1.assertValidAst)(ast); - return (0, native_codegen_ts_1.generateNative)(ast); - } - function compileWireFile(source, filePath = "") { - try { - const ast = (0, syntax_1.parse)(source); - (0, syntax_1.assertValidAst)(ast, { file: filePath, accessibility: true }); - return `// compiled from .wrn -${(0, codegen_ts_1.generate)(ast)}`; - } catch (error) { - const diagnostic = (0, syntax_1.diagnosticFromError)(source, error, { file: filePath }); - throw new Error(`Failed to parse ${filePath}: - -${(0, syntax_1.formatDiagnostic)(source, diagnostic)}`, { - cause: error - }); - } - } - function compile(source, filePath = "") { - const richDiagnostics = (0, syntax_1.diagnose)(source, { file: filePath, accessibility: true }); - const errors = richDiagnostics.filter((diagnostic) => diagnostic.severity === "error"); - if (errors.length > 0) { - throw new syntax_1.ParseError(errors.map((diagnostic) => diagnostic.message).join(` -`), errors[0].code); - } - const ast = (0, syntax_1.parse)(source); - return { - code: `// compiled from .wrn -${(0, codegen_ts_1.generate)(ast)}`, - ast, - diagnostics: richDiagnostics.map((diagnostic) => `${diagnostic.code}: ${diagnostic.message}`), - richDiagnostics - }; - } - var cache_ts_1 = require2("./cache.js"); - Object.defineProperty(exports3, "compilationKey", { enumerable: true, get: function() { - return cache_ts_1.compilationKey; - } }); - Object.defineProperty(exports3, "createCompilationCache", { enumerable: true, get: function() { - return cache_ts_1.createCompilationCache; - } }); - Object.defineProperty(exports3, "DependencyGraph", { enumerable: true, get: function() { - return cache_ts_1.DependencyGraph; - } }); - }, - "packages/compiler/src/native-codegen.ts": function(module3, exports3, require2, __filename2, __dirname2) { - Object.defineProperty(exports3, "__esModule", { value: true }); - exports3.NativeCompileError = undefined; - exports3.generateNative = generateNative; - - class NativeCompileError extends Error { - constructor(message) { - super(message); - this.name = "NativeCompileError"; - } - } - exports3.NativeCompileError = NativeCompileError; - const tagMap = { - div: "View", - main: "View", - section: "View", - article: "View", - nav: "View", - header: "View", - footer: "View", - aside: "View", - form: "View", - ul: "View", - ol: "View", - li: "View", - p: "Text", - span: "Text", - strong: "Text", - em: "Text", - small: "Text", - label: "Text", - h1: "Text", - h2: "Text", - h3: "Text", - h4: "Text", - h5: "Text", - h6: "Text", - button: "Pressable", - a: "Pressable", - input: "TextInput", - textarea: "TextInput", - img: "Image", - view: "View", - text: "Text", - pressable: "Pressable", - textinput: "TextInput", - image: "Image", - scrollview: "ScrollView", - safeareaview: "SafeAreaView", - flatlist: "FlatList", - activityindicator: "ActivityIndicator" - }; - const attrMap = { - class: "style", - className: "style", - src: "source", - alt: "accessibilityLabel", - placeholder: "placeholder", - disabled: "disabled", - value: "value", - href: "__href", - "aria-label": "accessibilityLabel" - }; - function expression(value) { - const exact = /^\{([\s\S]+)\}$/.exec(value.trim()); - return exact?.[1]?.trim() ?? null; - } - function textJsx(value) { - const pieces = []; - let last = 0; - for (const match of value.matchAll(/\{([^{}]+)\}/g)) { - if (match.index > last) - pieces.push(value.slice(last, match.index)); - const expr = match[1].trim(); - pieces.push(expr.startsWith("t:") ? `{${JSON.stringify(expr.slice(2).trim())}}` : `{${expr}}`); - last = match.index + match[0].length; - } - pieces.push(value.slice(last)); - return pieces.join("").replace(/([<>])/g, (char) => char === "<" ? "<" : ">"); - } - function eventBody(value, states) { - let body = expression(value) ?? value; - for (const state of states) { - const cap = state[0].toUpperCase() + state.slice(1); - body = body.replace(new RegExp(`\\b${state}\\+\\+`, "g"), `set${cap}(value => value + 1)`).replace(new RegExp(`\\b${state}--`, "g"), `set${cap}(value => value - 1)`).replace(new RegExp(`\\b${state}\\s*=\\s*([^;]+)`, "g"), `set${cap}($1)`); - } - return `() => { ${body} }`; - } - function renderAttrs(attrs, states) { - return attrs.map((attr) => { - if (attr.event) { - if (attr.name.startsWith("browser-")) - return ""; - const eventName = attr.name.startsWith("mobile-") ? attr.name.slice(7) : attr.name; - const event = eventName === "click" || eventName === "press" ? "onPress" : eventName === "input" || eventName === "change" ? "onChangeText" : `on${eventName[0].toUpperCase()}${eventName.slice(1)}`; - return ` ${event}={${eventBody(attr.value, states)}}`; - } - if (attr.name === "data-native-browser" || attr.name.startsWith("data-native-on-browser-")) - return ""; - if (attr.name === "data-native-options" || attr.name === "data-native-only" || attr.name === "data-native-requires" || attr.name === "data-native-unsupported") - return ""; - if (attr.name === "data-native-mobile") { - throw new NativeCompileError(`Declarative native capability "${attr.value}" currently targets browser/Capacitor pages. In Expo output, call the installed Expo package from an @mobile-event handler.`); - } - const name = attrMap[attr.name] ?? attr.name; - if (name === "__href") - return ` onPress={() => router.push(${JSON.stringify(attr.value)})}`; - if (name === "source") { - const expr2 = expression(attr.value); - return ` source={${expr2 ? `{ uri: ${expr2} }` : `{ uri: ${JSON.stringify(attr.value)} }`}}`; - } - if (name === "style" && attr.name !== "style") { - return ` style={[${attr.value.split(/\s+/).filter(Boolean).map((value) => `styles[${JSON.stringify(value)}]`).join(", ")} ]}`; - } - if (name === "style") { - const inlineExpression = expression(attr.value); - if (inlineExpression) - return ` style={${inlineExpression}}`; - throw new NativeCompileError('Inline CSS strings are not portable to native; use class="name" and a page style block'); - } - if (attr.boolean) - return ` ${name}`; - const expr = expression(attr.value); - return expr ? ` ${name}={${expr}}` : ` ${name}=${JSON.stringify(attr.value)}`; - }).join(""); - } - function renderNode(node, states, key) { - if (node.type === "text") - return textJsx(node.value); - if (node.type === "each") { - const params = node.index ? `${node.item}, ${node.index}` : `${node.item}, __index`; - const body = node.body.map((child, index) => renderNode(child, states, index === 0 ? node.index ?? "__index" : undefined)).join(""); - const empty = node.empty.map((child) => renderNode(child, states)).join(""); - return `{(${node.list})?.length ? (${node.list}).map((${params}) => <>${body}) : <>${empty}}`; - } - if (node.type === "if") { - const result = node.branches.reduceRight((fallback, branch) => branch.cond === null ? `<>${branch.body.map((child) => renderNode(child, states)).join("")}` : `(${branch.cond}) ? <>${branch.body.map((child) => renderNode(child, states)).join("")} : ${fallback}`, "null"); - return `{${result}}`; - } - const nativeOnly = node.attrs.find((attr) => !attr.event && attr.name === "data-native-only")?.value; - if (nativeOnly === "browser" || nativeOnly === "web") - return ""; - const nativeTag = tagMap[node.tag.toLowerCase()] ?? (/^[A-Z]/.test(node.tag) ? node.tag : undefined); - if (!nativeTag) - throw new NativeCompileError(`HTML element <${node.tag}> has no native equivalent`); - const attrs = renderAttrs(node.attrs, states) + (key ? ` key={${key}}` : ""); - if (nativeTag === "TextInput" || nativeTag === "Image" || nativeTag === "ActivityIndicator") - return `<${nativeTag}${attrs} />`; - const children = node.children.map((child) => { - if (child.type !== "text") - return renderNode(child, states); - if (!child.value.trim()) - return ""; - const text = textJsx(child.value); - return nativeTag === "Text" ? text : `${text}`; - }).join(""); - return `<${nativeTag}${attrs}>${children}`; - } - function nativeStyles(blocks) { - const entries = []; - for (const block of blocks) { - for (const match of block.matchAll(/\.([A-Za-z_][\w-]*)\s*\{([^}]*)\}/g)) { - const props = []; - for (const declaration of match[2].split(";")) { - const colon = declaration.indexOf(":"); - if (colon < 0) - continue; - const name = declaration.slice(0, colon).trim().replace(/-([a-z])/g, (_, c) => c.toUpperCase()); - let value = declaration.slice(colon + 1).trim(); - if (/^-?\d+(?:\.\d+)?px$/.test(value)) - value = Number(value.slice(0, -2)); - props.push(`${JSON.stringify(name)}: ${typeof value === "number" ? value : JSON.stringify(value)}`); - } - entries.push(`${JSON.stringify(match[1])}: { ${props.join(", ")} }`); - } - } - return `const styles = StyleSheet.create({ ${entries.join(`, -`)} });`; - } - function generateNative(ast) { - if (ast.kind !== "page") - throw new NativeCompileError("Native route compilation currently accepts page files only"); - if (ast.dataApis.length) - throw new NativeCompileError("Data API blocks are not yet portable to native screens; fetch through the generated native backend helper"); - const states = new Set(ast.states.map((state) => state.name)); - const hooks = ast.states.map((state) => { - const cap = state.name[0].toUpperCase() + state.name.slice(1); - return ` const [${state.name}, set${cap}] = useState${state.valueType ? `<${state.valueType}>` : ""}(${state.expr});`; - }).join(` -`); - const body = ast.view.map((node) => renderNode(node, states)).join(""); - const typeSource = ast.types.map((block) => block.trim()).filter(Boolean).join(` - -`); - return `// generated from .wrn for Expo/React Native -import React, { useState } from "react"; -import { ActivityIndicator, FlatList, Image, Pressable, SafeAreaView, ScrollView, StyleSheet, Text, TextInput, View } from "react-native"; -import { useRouter } from "expo-router"; -${ast.imports.join(` -`)} - -${typeSource} - -export default function ${ast.name}() { - const router = useRouter(); -${hooks} - return <>${body}; -} - -${nativeStyles(ast.styles)} -`; - } - }, - "packages/compiler/src/parser.ts": function(module3, exports3, require2, __filename2, __dirname2) { - var __createBinding = this && this.__createBinding || (Object.create ? function(o, m, k, k2) { - if (k2 === undefined) - k2 = k; - var desc = Object.getOwnPropertyDescriptor(m, k); - if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { - desc = { enumerable: true, get: function() { - return m[k]; - } }; - } - Object.defineProperty(o, k2, desc); - } : function(o, m, k, k2) { - if (k2 === undefined) - k2 = k; - o[k2] = m[k]; - }); - var __exportStar = this && this.__exportStar || function(m, exports4) { - for (var p in m) - if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports4, p)) - __createBinding(exports4, m, p); - }; - Object.defineProperty(exports3, "__esModule", { value: true }); - __exportStar(require2("@wrnexus/syntax/parser"), exports3); - }, - "packages/compiler/src/runtime-capabilities.ts": function(module3, exports3, require2, __filename2, __dirname2) { - Object.defineProperty(exports3, "__esModule", { value: true }); - exports3.runtimeCapabilities = runtimeCapabilities; - exports3.analyzeRuntimeImports = analyzeRuntimeImports; - const CAPABILITIES = { - bun: new Set([ - "filesystem", - "tcp", - "process", - "websocket", - "crypto", - "streams", - "background-tasks" - ]), - node: new Set([ - "filesystem", - "tcp", - "process", - "websocket", - "crypto", - "streams", - "background-tasks" - ]), - edge: new Set(["websocket", "crypto", "streams", "background-tasks"]), - worker: new Set(["websocket", "crypto", "streams", "background-tasks"]), - "service-worker": new Set(["crypto", "streams", "background-tasks"]), - browser: new Set(["websocket", "crypto", "streams"]) - }; - const MODULE_CAPABILITIES = [ - [/^(?:node:)?(?:fs|path|os)(?:\/|$)/, "filesystem"], - [/^(?:node:)?(?:net|tls|dgram|http2)(?:\/|$)/, "tcp"], - [/^(?:node:)?(?:child_process|cluster|worker_threads)(?:\/|$)/, "process"] - ]; - function runtimeCapabilities(runtime) { - return CAPABILITIES[runtime]; - } - function analyzeRuntimeImports(source, runtime) { - const modules = [ - ...source.matchAll(/\b(?:import\s+(?:[\s\S]*?\s+from\s+)?|require\s*\()\s*["']([^"']+)["']/g) - ].map((match) => match[1]); - const available = runtimeCapabilities(runtime); - return modules.flatMap((module4) => { - const requirement = MODULE_CAPABILITIES.find(([pattern]) => pattern.test(module4)); - if (!requirement || available.has(requirement[1])) - return []; - return [ - { - code: "WRN-RUNTIME-CAPABILITY", - runtime, - module: module4, - capability: requirement[1], - message: `Module '${module4}' requires ${requirement[1]}, which is unavailable in the ${runtime} runtime.` - } - ]; - }); - } - }, - "packages/compiler/src/server-codegen.ts": function(module3, exports3, require2, __filename2, __dirname2) { - Object.defineProperty(exports3, "__esModule", { value: true }); - exports3.remotelyReferencedServerFunctions = remotelyReferencedServerFunctions; - exports3.rpcManifest = rpcManifest; - exports3.generateServerFunctionsModule = generateServerFunctionsModule; - const syntax_1 = require2("@wrnexus/syntax"); - function stableId(value) { - let hash = 2166136261; - for (let index = 0;index < value.length; index++) { - hash ^= value.charCodeAt(index); - hash = Math.imul(hash, 16777619); - } - return `wrn_${(hash >>> 0).toString(36)}`; - } - function remotelyReferencedServerFunctions(ast) { - const browserSources = ast.runtimeFunctions.filter((fn) => ["legacy", "client", "shared"].includes(fn.runtime)).map((fn) => fn.body); - for (const [hook, body] of Object.entries(ast.storeLifecycle)) { - if (hook !== "serverInit" && body) - browserSources.push(body); - } - const names = new Set; - const call = /\bserver\.([A-Za-z_$][\w$]*)\s*\(/g; - for (const source of browserSources) { - for (const match of source.matchAll(call)) - names.add(match[1]); - } - return names; - } - function rpcManifest(ast) { - const exposed = remotelyReferencedServerFunctions(ast); - return ast.runtimeFunctions.filter((fn) => fn.runtime === "server" && exposed.has(fn.name)).map((fn) => ({ - id: stableId(`${ast.name}:${fn.name}`), - component: ast.name, - function: fn.name, - parameters: fn.parameters.map((param) => ({ - name: param.name, - type: param.valueType ?? "unknown", - optional: param.optional - })), - returnType: fn.returnType ?? (fn.async ? "Promise" : "unknown") - })); - } - function generateServerFunctionsModule(ast) { - const source = ast.functions.map((body) => (0, syntax_1.stripRuntimeFunctionModifiers)(body, ["legacy", "server", "shared"])).filter(Boolean).join(` - -`); - const names = ast.runtimeFunctions.filter((fn) => ["legacy", "server", "shared"].includes(fn.runtime)).map((fn) => fn.name); - const manifest = rpcManifest(ast); - return `// generated WRNexusJS server module for ${ast.name} -${source} - -export const __wrnexusServerFunctions = { ${[...new Set(names)].join(", ")} }; -export const __wrnexusRpcManifest = ${JSON.stringify(manifest, null, 2)}; -`; - } - }, - "packages/compiler/src/source-map.ts": function(module3, exports3, require2, __filename2, __dirname2) { - Object.defineProperty(exports3, "__esModule", { value: true }); - exports3.createWrnSourceMap = createWrnSourceMap; - function createWrnSourceMap(source, generated) { - const sourceLines = source.split(/\r?\n/).length; - const generatedLines = generated.split(/\r?\n/).length; - const mappings = Array.from({ length: Math.min(sourceLines, generatedLines) }, (_, index) => ({ - generatedLine: index + 1, - sourceLine: index + 1, - sourceColumn: 1, - kind: "line" - })); - return { version: 1, source, generated, mappings }; - } - }, - "packages/compiler/src/store-codegen.ts": function(module3, exports3, require2, __filename2, __dirname2) { - Object.defineProperty(exports3, "__esModule", { value: true }); - exports3.generateStoreModule = generateStoreModule; - exports3.generateStoreBrowserModule = generateStoreBrowserModule; - const syntax_1 = require2("@wrnexus/syntax"); - const type_codegen_ts_1 = require2("./type-codegen.js"); - const server_codegen_ts_1 = require2("./server-codegen.js"); - const RESERVED_BINDINGS = new Set([ - "await", - "break", - "case", - "catch", - "class", - "const", - "continue", - "debugger", - "default", - "delete", - "do", - "else", - "enum", - "export", - "extends", - "false", - "finally", - "for", - "function", - "if", - "implements", - "import", - "in", - "instanceof", - "interface", - "let", - "new", - "null", - "package", - "private", - "protected", - "public", - "return", - "static", - "super", - "switch", - "this", - "throw", - "true", - "try", - "typeof", - "var", - "void", - "while", - "with", - "yield" - ]); - function safeBinding(name) { - return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) && !RESERVED_BINDINGS.has(name); - } - function stateObject(ast, runtime) { - const entries = ast.states.filter((state) => state.runtime === runtime).map((state) => `${JSON.stringify(state.name)}: (${state.expr})`); - return `{ ${entries.join(", ")} }`; - } - function actionSource(fn, stateNames, eraseTypes = false) { - const parameterNames = new Set(fn.parameters.map((param) => param.name)); - const params = fn.parameters.map((param) => param.name).join(", "); - const aliases = stateNames.filter((name) => safeBinding(name) && !parameterNames.has(name)); - const aliasSource = aliases.length ? `let { ${aliases.join(", ")} } = context.state;` : ""; - const runtimeAliases = ["server"].filter((name) => !parameterNames.has(name)).map((name) => `const ${name} = context.${name};`).join(` -`); - const copyBack = aliases.map((name) => `context.state.${name} = ${name};`).join(` -`); - const body = eraseTypes ? (0, syntax_1.eraseFunctionTypes)(fn.body) : fn.body; - return `{ runtime: ${JSON.stringify(fn.runtime)}, handler: ${fn.async ? "async " : ""}function(context${params ? `, ${params}` : ""}) { ${runtimeAliases} -${aliasSource} -try { ${body} } finally { ${copyBack} } } }`; - } - function persistedCallback(source, functionName) { - if (!source?.trim()) - return; - const body = (0, syntax_1.eraseFunctionTypes)(source); - if (functionName === "migrate") { - return `(value, fromVersion, toVersion) => { -${body} -if (typeof migrate === "function") return migrate(value, fromVersion, toVersion); -return value; -}`; - } - return `(value) => { -${body} -if (typeof validate === "function") return validate(value); -return value && typeof value === "object" && !Array.isArray(value) ? value : null; -}`; - } - function persistenceSource(ast) { - if (!ast.persist) - return "undefined"; - const migrate = persistedCallback(ast.persist.migrations, "migrate"); - const validate = persistedCallback(ast.persist.validation, "validate"); - return `{ storage: ${JSON.stringify(ast.persist.storage)}, include: ${JSON.stringify(ast.persist.include)}, version: ${ast.persist.version}${migrate ? `, migrate: ${migrate}` : ""}${validate ? `, validate: ${validate}` : ""} }`; - } - function lifecycleSource(ast, stateNames, browser) { - return Object.entries(ast.storeLifecycle).filter(([name]) => !browser || name !== "serverInit").map(([name, body]) => { - const aliases = stateNames.filter(safeBinding); - const aliasSource = aliases.length ? `let { ${aliases.join(", ")} } = context.state;` : ""; - const runtimeAliases = browser ? "const server = context.server;" : ""; - const copyBack = aliases.map((state) => `context.state.${state} = ${state};`).join(` -`); - const emittedBody = browser ? (0, syntax_1.eraseFunctionTypes)(body) : body; - return `${name}: async (context) => { ${runtimeAliases} ${aliasSource} try { ${emittedBody} } finally { ${copyBack} } }`; - }).join(`, -`); - } - function generateStoreModule(ast) { - if (ast.kind !== "global-store" && ast.kind !== "page-store") { - throw new Error("generateStoreModule requires a store AST"); - } - const stateNames = ast.states.map((state) => state.name); - const safeStateNames = stateNames.filter(safeBinding); - const computed = ast.computed.map((entry) => `${JSON.stringify(entry.name)}: (state) => { ${safeStateNames.length ? `const { ${safeStateNames.join(", ")} } = state;` : ""} return (${entry.expr}); }`).join(`, -`); - const actionGroups = new Map; - for (const fn of ast.runtimeFunctions) { - const group = actionGroups.get(fn.name) ?? []; - group.push(fn); - actionGroups.set(fn.name, group); - } - const actions = Array.from(actionGroups, ([name, functions]) => `${JSON.stringify(name)}: [${functions.map((fn) => actionSource(fn, stateNames)).join(", ")}]`).join(`, -`); - const persistence = persistenceSource(ast); - const lifecycle = lifecycleSource(ast, stateNames, false); - const manifest = (0, server_codegen_ts_1.rpcManifest)(ast); - const remoteFunctions = manifest.map((entry) => entry.function); - const rpcWrappers = remoteFunctions.map((name) => `${JSON.stringify(name)}: async (...received) => { - const rpcContext = received.pop(); - if (!rpcContext || !rpcContext.request) throw new Error("WRN-RPC-CONTEXT: request context is required"); - let container = __wrnexusRpcContainers.get(rpcContext.request); - if (!container) { - const url = new URL(rpcContext.request.url); - container = createRequestStoreContainer(rpcContext.request, url.pathname + url.search); - __wrnexusRpcContainers.set(rpcContext.request, container); - } - const store = await container.use(${ast.name}); - const action = store.actions[${JSON.stringify(name)}]; - if (typeof action !== "function") throw new Error(${JSON.stringify(`WRN-RPC-FUNCTION: store action '${name}' is unavailable on the server`)}); - return action(...received); - }`).join(`, -`); - return `${ast.imports.join(` -`)} -import { defineStore } from "@wrnexus/store"; -import { createRequestStoreContainer } from "@wrnexus/store/server"; - -${ast.types.join(` - -`)} - -export const ${ast.name} = defineStore({ - name: ${JSON.stringify(ast.name)}, - kind: ${JSON.stringify(ast.storeKind)}, - createSharedState: () => (${stateObject(ast, "shared")}), - createClientState: () => (${stateObject(ast, "client")}), - createServerState: () => (${stateObject(ast, "server")}), - computed: { ${computed} }, - actions: { ${actions} }, - persist: ${persistence}, - lifecycle: { ${lifecycle} }, -}); - -export default ${ast.name}; - -const __wrnexusRpcContainers = new WeakMap(); -export const __wrnexusServerFunctions = { -${rpcWrappers} -}; -export const __wrnexusRpcManifest = ${JSON.stringify(manifest, null, 2)}; - -${(0, type_codegen_ts_1.generateDeclarations)(ast)} -`; - } - function generateStoreBrowserModule(ast) { - if (ast.kind !== "global-store" && ast.kind !== "page-store") { - throw new Error("generateStoreBrowserModule requires a store AST"); - } - const browserStates = ast.states.filter((state) => state.runtime !== "server"); - const stateNames = browserStates.map((state) => state.name); - const safeStateNames = stateNames.filter(safeBinding); - const initialState = `{ ${browserStates.map((state) => `${JSON.stringify(state.name)}: (${state.expr})`).join(", ")} }`; - const computed = ast.computed.map((entry) => `${JSON.stringify(entry.name)}: (state) => { ${safeStateNames.length ? `const { ${safeStateNames.join(", ")} } = state;` : ""} return (${entry.expr}); }`).join(`, -`); - const groups = new Map; - for (const fn of ast.runtimeFunctions.filter((entry) => ["client", "shared", "legacy"].includes(entry.runtime))) { - const group = groups.get(fn.name) ?? []; - group.push(fn); - groups.set(fn.name, group); - } - const actions = Array.from(groups, ([name, functions]) => `${JSON.stringify(name)}: [${functions.map((fn) => actionSource(fn, stateNames, true)).join(", ")}]`).join(`, -`); - const persistence = persistenceSource(ast); - const lifecycleEntries = lifecycleSource(ast, stateNames, true); - return `// generated WRNexusJS browser store module for ${ast.name} -const __root = globalThis; -const __registry = __root.__wrnexusStoreRegistry || (__root.__wrnexusStoreRegistry = new Map()); -const __hydrationNode = typeof document !== "undefined" ? document.querySelector("script[data-wrnexus-store-hydration]") : null; -let __hydration = {}; -try { __hydration = __hydrationNode ? JSON.parse(__hydrationNode.textContent || "{}") : {}; } catch (_) {} -function __clone(value) { try { return structuredClone(value); } catch (_) { return JSON.parse(JSON.stringify(value)); } } -function __storage(kind) { if (typeof window === "undefined") return null; return kind === "local" ? window.localStorage : kind === "session" ? window.sessionStorage : null; } -function __diagnostic(code, message, details) { - const detail = { code, message, store: ${JSON.stringify(ast.name)}, details }; - try { __root.dispatchEvent && __root.dispatchEvent(new CustomEvent("wrnexus:store-diagnostic", { detail })); } catch (_) {} - if (typeof console !== "undefined" && console.warn) console.warn("[wrnexus:store] " + code + ": " + message, details || ""); -} -function __csrfToken() { - if (typeof document === "undefined") return undefined; - const match = /(?:^|;\\s*)wire-csrf=([^;]+)/.exec(document.cookie || ""); - return match ? decodeURIComponent(match[1]) : undefined; -} -async function __callServerFunction(storeName, functionName, args, options) { - options = options || {}; - const csrf = options.csrfToken || __csrfToken(); - const traceId = options.traceId || (globalThis.crypto && crypto.randomUUID ? crypto.randomUUID() : String(Date.now())); - const response = await fetch(options.endpoint || "/__wrnexus/rpc", { - method: "POST", - credentials: "same-origin", - signal: options.signal, - headers: Object.assign({ "content-type": "application/json", "x-request-id": traceId }, csrf ? { "x-csrf-token": csrf } : {}, options.headers || {}), - body: JSON.stringify({ component: storeName, function: functionName, args: args }), - }); - const payload = await response.json().catch(function () { return null; }); - if (!response.ok || !payload || !payload.ok) { - const error = new Error(payload && payload.error && payload.error.message || "Server call failed (" + response.status + ")"); - error.code = payload && payload.error && payload.error.code || "WRN-RPC-FAILED"; - error.status = response.status; - error.details = payload && payload.error && payload.error.details; - error.traceId = payload && payload.error && payload.error.traceId || traceId; - throw error; - } - return payload.value; -} -function __compatible(expected, value) { - if (expected === null || value === null) return expected === value || expected === null; - if (Array.isArray(expected)) return Array.isArray(value); - return typeof expected === typeof value; -} -function __create(definition) { - const routeId = typeof location !== "undefined" ? location.pathname + location.search : "default"; - const key = definition.kind === "page" ? definition.name + "@" + routeId : definition.name; - if (__registry.has(key)) return __registry.get(key); - let currentDefinition = definition; - const listeners = new Set(); - const initial = currentDefinition.createState(); - let restored = null; - if (currentDefinition.persist) { - try { - const storage = __storage(currentDefinition.persist.storage); - const rawValue = storage && storage.getItem("wrnexus:store:" + currentDefinition.name); - const parsed = rawValue ? JSON.parse(rawValue) : null; - if (parsed) { - let candidate = parsed.state; - const fromVersion = Number(parsed.version || 0); - if (fromVersion !== currentDefinition.persist.version) { - if (typeof currentDefinition.persist.migrate === "function") candidate = currentDefinition.persist.migrate(candidate, fromVersion, currentDefinition.persist.version); - else { __diagnostic("WRN-PERSIST-VERSION", "Persisted state version cannot be restored without a migration.", { fromVersion, toVersion: currentDefinition.persist.version }); candidate = null; } - } - if (candidate && typeof currentDefinition.persist.validate === "function") candidate = currentDefinition.persist.validate(candidate); - if (candidate && typeof candidate === "object" && !Array.isArray(candidate)) { - restored = {}; - currentDefinition.persist.include.forEach(function (name) { if (Object.prototype.hasOwnProperty.call(candidate, name)) restored[name] = candidate[name]; }); - } else if (candidate != null) { - __diagnostic("WRN-PERSIST-INVALID", "Persisted state failed validation and was reset.", candidate); - } - } - } catch (error) { __diagnostic("WRN-PERSIST-RESTORE", "Persisted state could not be restored and was reset.", error); } - } - const raw = Object.assign({}, initial, restored || {}, __hydration[currentDefinition.name] || {}); - let mutable = false; - let actionName = "direct"; - function persistState() { - if (!currentDefinition.persist) return; - try { - const picked = {}; - currentDefinition.persist.include.forEach(function (name) { picked[name] = raw[name]; }); - const storage = __storage(currentDefinition.persist.storage); - if (storage) storage.setItem("wrnexus:store:" + currentDefinition.name, JSON.stringify({ version: currentDefinition.persist.version, state: picked })); - } catch (error) { __diagnostic("WRN-PERSIST-WRITE", "Persisted state could not be written.", error); } - } - const state = new Proxy(raw, { - set(target, property, value) { - if (!mutable) throw new TypeError("WRN-STORE-READONLY: " + currentDefinition.name + "." + String(property) + " must be changed by a store action."); - if (Object.is(target[property], value)) return true; - target[property] = value; - persistState(); - listeners.forEach(function (listener) { listener(instance.snapshot(), { store: currentDefinition.name, action: actionName, changed: [String(property)] }); }); - return true; - }, - deleteProperty(target, property) { - if (!mutable) throw new TypeError("WRN-STORE-READONLY: store state is readonly outside actions"); - return Reflect.deleteProperty(target, property); - }, - }); - const actions = {}; - const server = new Proxy({}, { get: function (_target, property) { return function () { return __callServerFunction(currentDefinition.name, String(property), Array.prototype.slice.call(arguments)); }; } }); - function installActions() { - Object.keys(actions).forEach(function (name) { delete actions[name]; }); - Object.entries(currentDefinition.actions || {}).forEach(function (pair) { - const name = pair[0], candidates = pair[1]; - const selected = candidates.find(function (entry) { return entry.runtime === "client"; }) || candidates.find(function (entry) { return entry.runtime === "shared"; }) || candidates.find(function (entry) { return entry.runtime === "legacy"; }); - if (!selected) return; - actions[name] = async function () { - const args = Array.prototype.slice.call(arguments); - const previousMutable = mutable, previousAction = actionName; - mutable = true; actionName = name; - try { return await selected.handler({ state, snapshot: function () { return __clone(state); }, reset: function () { return instance.reset(); }, runtime: "client", routeId, server }, ...args); } - finally { mutable = previousMutable; actionName = previousAction; } - }; - }); - } - installActions(); - const core = { - name: currentDefinition.name, - kind: currentDefinition.kind, - state, - actions, - whenReady: Promise.resolve(), - reset() { - mutable = true; actionName = "$reset"; - try { - const next = currentDefinition.createState(); - Object.keys(raw).forEach(function (name) { if (!(name in next)) delete raw[name]; }); - Object.assign(raw, next); persistState(); - listeners.forEach(function (listener) { listener(instance.snapshot(), { store: currentDefinition.name, action: "$reset", changed: Object.keys(next) }); }); - } finally { mutable = false; actionName = "direct"; } - }, - snapshot() { return Object.freeze(__clone(raw)); }, - subscribe(listener) { listeners.add(listener); return function () { listeners.delete(listener); }; }, - async dispose() { - mutable = true; actionName = "$dispose"; - try { await currentDefinition.lifecycle && currentDefinition.lifecycle.dispose && currentDefinition.lifecycle.dispose({ state, runtime: "client", routeId, server }); } - finally { mutable = false; actionName = "direct"; listeners.clear(); __registry.delete(key); } - }, - async __hotUpdate(nextDefinition) { - const previous = __clone(raw); - const nextShape = nextDefinition.createState(); - const preserved = [], reset = [], added = [], removed = []; - Object.keys(previous).forEach(function (name) { - if (!(name in nextShape)) { removed.push(name); return; } - if (__compatible(nextShape[name], previous[name])) { nextShape[name] = previous[name]; preserved.push(name); } - else reset.push(name); - }); - Object.keys(nextShape).forEach(function (name) { if (!(name in previous)) added.push(name); }); - currentDefinition = nextDefinition; - mutable = true; actionName = "$hmr"; - try { - Object.keys(raw).forEach(function (name) { delete raw[name]; }); - Object.assign(raw, nextShape); - installActions(); persistState(); - } finally { mutable = false; actionName = "direct"; } - const result = { store: currentDefinition.name, preserved, reset, added, removed }; - listeners.forEach(function (listener) { listener(instance.snapshot(), { store: currentDefinition.name, action: "$hmr", changed: added.concat(reset, removed) }); }); - try { __root.dispatchEvent && __root.dispatchEvent(new CustomEvent("wrnexus:store-hmr", { detail: result })); } catch (_) {} - return result; - }, - }; - const instance = new Proxy(core, { - get(target, property, receiver) { - if (Reflect.has(target, property)) return Reflect.get(target, property, receiver); - if (property in actions) return actions[property]; - if (property in (currentDefinition.computed || {})) return currentDefinition.computed[property](state); - return state[property]; - }, - set() { throw new TypeError("WRN-STORE-READONLY: store state is readonly outside actions"); }, - }); - __registry.set(key, instance); - const hydrationSource = __hydration[currentDefinition.name]; - const init = async function () { - const run = async function (name, hook) { - if (!hook) return; - mutable = true; actionName = name; - try { await hook({ state, runtime: "client", routeId, server }); } - finally { mutable = false; actionName = "direct"; } - }; - await run("$clientInit", currentDefinition.lifecycle && currentDefinition.lifecycle.clientInit); - if (hydrationSource) await run("$hydrate", currentDefinition.lifecycle && currentDefinition.lifecycle.hydrate); - }; - core.whenReady = init(); - return instance; -} -if (!__root.__wrnexusApplyStoreHotUpdate) { - __root.__wrnexusApplyStoreHotUpdate = async function (name, definition) { - const results = []; - for (const item of Array.from(__registry.values())) if (item.name === name && typeof item.__hotUpdate === "function") results.push(await item.__hotUpdate(definition)); - return results; - }; -} -if (!__root.__wrnexusStoreContainer) { - __root.__wrnexusStoreContainer = { - async disposePageStores() { for (const item of Array.from(__registry.values())) if (item.kind === "page") await item.dispose(); }, - async hotUpdate(name, definition) { return __root.__wrnexusApplyStoreHotUpdate(name, definition); }, - inspect() { return Array.from(__registry.values()).map(function (item) { return { name: item.name, kind: item.kind, state: item.snapshot() }; }); }, - }; -} -export const ${ast.name}Definition = { - name: ${JSON.stringify(ast.name)}, - kind: ${JSON.stringify(ast.storeKind)}, - createState: () => (${initialState}), - computed: { ${computed} }, - actions: { ${actions} }, - persist: ${persistence}, - lifecycle: { ${lifecycleEntries} }, -}; -export const ${ast.name} = __create(${ast.name}Definition); -export default ${ast.name}; -`; - } - }, - "packages/compiler/src/targets.ts": function(module3, exports3, require2, __filename2, __dirname2) { - Object.defineProperty(exports3, "__esModule", { value: true }); - exports3.generateTargets = generateTargets; - const component_contract_ts_1 = require2("./component-contract.js"); - const client_codegen_ts_1 = require2("./client-codegen.js"); - const server_codegen_ts_1 = require2("./server-codegen.js"); - const type_codegen_ts_1 = require2("./type-codegen.js"); - const store_codegen_ts_1 = require2("./store-codegen.js"); - function generateTargets(ast) { - return { - server: ast.kind === "global-store" || ast.kind === "page-store" ? (0, store_codegen_ts_1.generateStoreModule)(ast) : (0, server_codegen_ts_1.generateServerFunctionsModule)(ast), - browser: ast.kind === "global-store" || ast.kind === "page-store" ? (0, store_codegen_ts_1.generateStoreBrowserModule)(ast) : (0, client_codegen_ts_1.generateBrowserModule)(ast), - declarations: (0, type_codegen_ts_1.generateDeclarations)(ast), - contract: (0, component_contract_ts_1.createComponentContract)(ast), - rpc: (0, server_codegen_ts_1.rpcManifest)(ast) - }; - } - }, - "packages/compiler/src/tokenizer.ts": function(module3, exports3, require2, __filename2, __dirname2) { - var __createBinding = this && this.__createBinding || (Object.create ? function(o, m, k, k2) { - if (k2 === undefined) - k2 = k; - var desc = Object.getOwnPropertyDescriptor(m, k); - if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { - desc = { enumerable: true, get: function() { - return m[k]; - } }; - } - Object.defineProperty(o, k2, desc); - } : function(o, m, k, k2) { - if (k2 === undefined) - k2 = k; - o[k2] = m[k]; - }); - var __exportStar = this && this.__exportStar || function(m, exports4) { - for (var p in m) - if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports4, p)) - __createBinding(exports4, m, p); - }; - Object.defineProperty(exports3, "__esModule", { value: true }); - __exportStar(require2("@wrnexus/syntax/tokenizer"), exports3); - }, - "packages/compiler/src/type-codegen.ts": function(module3, exports3, require2, __filename2, __dirname2) { - Object.defineProperty(exports3, "__esModule", { value: true }); - exports3.generateDeclarations = generateDeclarations; - function member(name) { - return /^[A-Za-z_$][\w$]*$/.test(name) ? name : JSON.stringify(name); - } - function params(astParams) { - return astParams.map((param) => `${member(param.name)}${param.optional ? "?" : ""}: ${param.valueType ?? "unknown"}`).join(", "); - } - function generateDeclarations(ast) { - const inline = ast.types.map((body) => body.trim()).filter(Boolean).join(` - -`); - if (ast.kind === "global-store" || ast.kind === "page-store") { - const state = ast.states.filter((entry) => entry.runtime !== "server").map((entry) => ` readonly ${member(entry.name)}: ${entry.valueType ?? "unknown"};`).join(` -`); - const computed = ast.computed.map((entry) => ` readonly ${member(entry.name)}: ${entry.valueType ?? "unknown"};`).join(` -`); - const actions = ast.runtimeFunctions.filter((fn) => fn.runtime !== "server").map((fn) => ` ${member(fn.name)}(${params(fn.parameters)}): ${fn.returnType ?? (fn.async ? "Promise" : "unknown")};`).join(` -`); - return `${inline ? `${inline} - -` : ""}export interface ${ast.name}State { -${state} -} - -export interface ${ast.name}Computed { -${computed} -} - -export interface ${ast.name}Actions { -${actions} -} - -export interface ${ast.name}Instance extends ${ast.name}State, ${ast.name}Computed, ${ast.name}Actions { - reset(): void; - snapshot(): Readonly<${ast.name}State>; -} - -declare const store: ${ast.name}Instance; -export default store; -`; - } - const props = ast.props.map((prop) => ` readonly ${member(prop.name)}${prop.required ? "" : "?"}: ${prop.valueType ?? "unknown"};`).join(` -`); - const outputs = ast.outputs.map((output) => ` ${member(output.name)}(${output.payload ? `${member(output.payload.name)}${output.payload.optional ? "?" : ""}: ${output.payload.valueType}` : ""}): void;`).join(` -`); - const clientFunctions = ast.runtimeFunctions.filter((fn) => fn.runtime === "client" || fn.runtime === "shared" || fn.runtime === "legacy").map((fn) => ` ${member(fn.name)}(${params(fn.parameters)}): ${fn.returnType ?? (fn.async ? "Promise" : "unknown")};`).join(` -`); - const serverFunctions = ast.runtimeFunctions.filter((fn) => fn.runtime === "server").map((fn) => ` ${member(fn.name)}(${params(fn.parameters)}): Promise>;`).join(` -`); - return `${inline ? `${inline} - -` : ""}export interface ${ast.name}Props { -${props} -} - -export interface ${ast.name}Outputs { -${outputs} -} - -export interface ${ast.name}ClientFunctions { -${clientFunctions} -} - -export interface ${ast.name}ServerCalls { -${serverFunctions} -} -`; - } - }, - "packages/compiler/src/types.ts": function(module3, exports3, require2, __filename2, __dirname2) { - var __createBinding = this && this.__createBinding || (Object.create ? function(o, m, k, k2) { - if (k2 === undefined) - k2 = k; - var desc = Object.getOwnPropertyDescriptor(m, k); - if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { - desc = { enumerable: true, get: function() { - return m[k]; - } }; - } - Object.defineProperty(o, k2, desc); - } : function(o, m, k, k2) { - if (k2 === undefined) - k2 = k; - o[k2] = m[k]; - }); - var __exportStar = this && this.__exportStar || function(m, exports4) { - for (var p in m) - if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports4, p)) - __createBinding(exports4, m, p); - }; - Object.defineProperty(exports3, "__esModule", { value: true }); - __exportStar(require2("@wrnexus/syntax/types"), exports3); - }, - "packages/syntax/src/diagnostics.ts": function(module3, exports3, require2, __filename2, __dirname2) { - Object.defineProperty(exports3, "__esModule", { value: true }); - exports3.containsReadonlyPropMutation = containsReadonlyPropMutation; - exports3.positionAt = positionAt; - exports3.classifyParseError = classifyParseError; - exports3.diagnosticFromError = diagnosticFromError; - exports3.diagnose = diagnose; - exports3.assertValidAst = assertValidAst; - exports3.isHydrationStrategy = isHydrationStrategy; - exports3.isRuntimeTarget = isRuntimeTarget; - exports3.formatDiagnostic = formatDiagnostic; - const parser_ts_1 = require2("./parser.js"); - const spec_ts_1 = require2("./spec.js"); - function stripAsciiControlAndSpace(value) { - let result = ""; - for (const character of value) { - if (character.charCodeAt(0) > 32) - result += character; - } - return result; - } - function maskJavaScriptTrivia(source) { - let result = ""; - let index = 0; - let quote = null; - let lineComment = false; - let blockComment = false; - while (index < source.length) { - const char = source[index]; - const next = source[index + 1]; - if (lineComment) { - if (char === ` -`) { - lineComment = false; - result += ` -`; - } else - result += " "; - index++; - continue; - } - if (blockComment) { - if (char === "*" && next === "/") { - result += " "; - index += 2; - blockComment = false; - } else { - result += char === ` -` ? ` -` : " "; - index++; - } - continue; - } - if (quote) { - if (char === "\\") { - result += " "; - index += Math.min(2, source.length - index); - } else if (char === quote) { - result += " "; - index++; - quote = null; - } else { - result += char === ` -` ? ` -` : " "; - index++; - } - continue; - } - if (char === "/" && next === "/") { - result += " "; - index += 2; - lineComment = true; - continue; - } - if (char === "/" && next === "*") { - result += " "; - index += 2; - blockComment = true; - continue; - } - if (char === "'" || char === '"' || char === "`") { - quote = char; - result += " "; - index++; - continue; - } - result += char; - index++; - } - return result; - } - function containsReadonlyPropMutation(body, propName, parameterNames) { - const code = maskJavaScriptTrivia(body); - const escaped = propName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - const operator = String.raw`(?:\+\+|--|(?:\*\*|&&|\|\||\?\?|[+\-*/%&|^])?=(?!=|>))`; - if (new RegExp(String.raw`\bprops\.${escaped}\s*${operator}`).test(code)) - return true; - if (parameterNames.has(propName)) - return false; - if (new RegExp(String.raw`\b(?:const|let|var)\s+${escaped}\b`).test(code)) - return false; - return new RegExp(String.raw`(?:^|[^\w$.])${escaped}\s*${operator}`, "m").test(code); - } - function positionAt(source, offset) { - const safe = Math.max(0, Math.min(offset, source.length)); - const before = source.slice(0, safe); - const lines = before.split(/\r?\n/); - return { offset: safe, line: lines.length, column: (lines.at(-1)?.length ?? 0) + 1 }; - } - function offsetFromMessage(message) { - const match = /offset\s+(\d+)/i.exec(message); - return match ? Number(match[1]) : undefined; - } - function classifyParseError(message) { - if (/Expected 'page', 'component', or 'layout'/.test(message)) - return spec_ts_1.WRN_DIAGNOSTIC_CODES.root; - if (/Unknown (?:page|component|layout|ssr|client) member/.test(message)) { - return spec_ts_1.WRN_DIAGNOSTIC_CODES.member; - } - if (/prop initializer|Expected eq/.test(message)) - return spec_ts_1.WRN_DIAGNOSTIC_CODES.propInitializer; - if (/State '.+' requires an initializer/.test(message)) { - return spec_ts_1.WRN_DIAGNOSTIC_CODES.stateInitializer; - } - if (/Cannot watch undeclared state/.test(message)) - return spec_ts_1.WRN_DIAGNOSTIC_CODES.watchUndeclared; - return spec_ts_1.WRN_DIAGNOSTIC_CODES.parse; - } - function diagnosticFromError(source, error, options = {}) { - const message = error instanceof Error ? error.message : String(error); - const offset = error instanceof parser_ts_1.ParseError && error.offset !== undefined ? error.offset : offsetFromMessage(message); - return { - code: error instanceof parser_ts_1.ParseError ? error.code : classifyParseError(message), - severity: "error", - message, - file: options.file, - ...offset === undefined ? {} : { position: positionAt(source, offset) } - }; - } - function walk(nodes, visit) { - for (const node of nodes) { - visit(node); - if (node.type === "element") - walk(node.children, visit); - else if (node.type === "each") { - walk(node.body, visit); - walk(node.empty, visit); - } else if (node.type === "if") { - for (const branch of node.branches) - walk(branch.body, visit); - } - } - } - function astDiagnostics(ast, options) { - const diagnostics = []; - const seen = new Map; - for (const [kind, declarations] of [ - ["prop", ast.props], - ["state", ast.states], - ["computed", ast.computed] - ]) { - for (const declaration of declarations) { - const previous = seen.get(declaration.name); - if (previous) { - diagnostics.push({ - code: spec_ts_1.WRN_DIAGNOSTIC_CODES.duplicateSymbol, - severity: "error", - message: `Duplicate symbol '${declaration.name}' (${previous} and ${kind}).`, - hint: "Rename one declaration so every prop, state, and computed value is unique.", - file: options.file - }); - } else { - seen.set(declaration.name, kind); - } - } - } - if (ast.hydrate && !isHydrationStrategy(ast.hydrate)) { - diagnostics.push({ - code: spec_ts_1.WRN_DIAGNOSTIC_CODES.invalidHydration, - severity: "error", - message: `Unknown hydration strategy '${ast.hydrate}'.`, - hint: "Use load, idle, visible, interaction, none, or media:.", - file: options.file - }); - } - if (ast.runtime && !isRuntimeTarget(ast.runtime)) { - diagnostics.push({ - code: spec_ts_1.WRN_DIAGNOSTIC_CODES.invalidRuntime, - severity: "error", - message: `Unknown runtime target '${ast.runtime}'.`, - hint: "Use server, client, or universal.", - file: options.file - }); - } - let interactive = ast.states.length > 0 || ast.effects.length > 0 || ast.watches.length > 0; - const urlAttributes = new Set([ - "href", - "src", - "action", - "formaction", - "poster", - "cite", - "background", - "xlink:href" - ]); - walk(ast.view, (node) => { - if (node.type !== "element") - return; - if (node.attrs.some((attribute) => attribute.event)) - interactive = true; - const tag = node.tag.toLowerCase(); - for (const attribute of node.attrs) { - if (attribute.event || attribute.boolean || !urlAttributes.has(attribute.name.toLowerCase())) - continue; - if (attribute.value.includes("{")) - continue; - const value = stripAsciiControlAndSpace(attribute.value.trim()).toLowerCase(); - if (/^(?:javascript|vbscript|file):/.test(value) || /^data:(?!image\/(?:png|gif|jpeg|webp|avif);)/.test(value)) { - diagnostics.push({ - code: "WRN-SEC-UNSAFE-URL", - severity: "error", - message: `Unsafe URL protocol in ${attribute.name} on <${node.tag}>.`, - hint: "Use a relative URL, https:, mailto:, tel:, or a framework-validated URL helper.", - file: options.file - }); - } - } - if (tag === "a") { - const target = node.attrs.find((attribute) => attribute.name === "target")?.value; - const rel = node.attrs.find((attribute) => attribute.name === "rel")?.value ?? ""; - if (target === "_blank" && !/\bnoopener\b/i.test(rel)) { - diagnostics.push({ - code: "WRN-SEC-BLANK-REL", - severity: "warning", - message: "A target=_blank link should include rel=noopener.", - hint: 'Add rel="noopener noreferrer".', - file: options.file - }); - } - } - if (!options.accessibility) - return; - if (tag === "img") { - if (!node.attrs.some((attribute) => attribute.name === "alt")) { - diagnostics.push({ - code: spec_ts_1.WRN_DIAGNOSTIC_CODES.accessibility, - severity: "warning", - message: "Image is missing an alt attribute.", - hint: 'Add alt text, or alt="" for a decorative image.', - file: options.file - }); - } - const hasWidth = node.attrs.some((attribute) => attribute.name === "width"); - const hasHeight = node.attrs.some((attribute) => attribute.name === "height"); - if (!hasWidth || !hasHeight) { - diagnostics.push({ - code: "WRN-PERF-IMAGE-DIMENSIONS", - severity: "warning", - message: "Image width and height are required to prevent layout shifts.", - hint: "Declare intrinsic width and height, or use @wrnexus/image.", - file: options.file - }); - } - } - }); - if (ast.runtime === "server" && interactive) { - diagnostics.push({ - code: spec_ts_1.WRN_DIAGNOSTIC_CODES.serverInteractive, - severity: "error", - message: "A server-only WRN root cannot contain client state, effects, watches, or event handlers.", - hint: 'Use runtime = "universal" or remove interactive behavior.', - file: options.file - }); - } - const outputs = new Set(ast.outputs.map((output) => output.name)); - for (const fn of ast.runtimeFunctions) { - for (const call of fn.body.matchAll(/\boutput\.([A-Za-z_$][\w$]*)\s*\(/g)) { - const outputName = call[1]; - if (fn.runtime === "server") { - diagnostics.push({ - code: "WRN-OUTPUT-SERVER-CALL", - severity: "error", - message: `Server function '${fn.name}' cannot call output.${outputName}().`, - hint: "Return a typed value to the browser and call the output from a client function.", - file: options.file - }); - } else if (!outputs.has(outputName)) { - diagnostics.push({ - code: "WRN-OUTPUT-UNKNOWN", - severity: "error", - message: `Unknown output '${outputName}' called from '${fn.name}'.`, - hint: `Declare ${outputName}(payload) inside outputs { ... }.`, - file: options.file - }); - } - } - if (fn.runtime === "client" && /\b(?:process|Bun|Deno|__dirname|require)\b/.test(fn.body)) { - diagnostics.push({ - code: "WRN-CLIENT-SERVER-API", - severity: "error", - message: `Client function '${fn.name}' references a server-only API.`, - hint: "Move that operation into a server function and call it through server.name(...).", - file: options.file - }); - } - if (fn.runtime === "server" && /\b(?:window|document|localStorage|sessionStorage|navigator)\b/.test(fn.body)) { - diagnostics.push({ - code: "WRN-SERVER-BROWSER-API", - severity: "error", - message: `Server function '${fn.name}' references a browser-only API.`, - hint: "Move that code into a client function.", - file: options.file - }); - } - if (fn.runtime !== "server" && /\b(?:eval\s*\(|new\s+Function\s*\(|document\.write\s*\(|\.innerHTML\s*=|\.outerHTML\s*=|insertAdjacentHTML\s*\()/.test(fn.body)) { - diagnostics.push({ - code: "WRN-SEC-DOM-SINK", - severity: "error", - message: `Client function '${fn.name}' uses an unsafe dynamic-code or HTML sink.`, - hint: "Use compiled templates, textContent, typed outputs, or a reviewed TrustedHTML sanitizer.", - file: options.file - }); - } - if (fn.runtime !== "server" && /\b(?:setTimeout|setInterval)\s*\(\s*["'`]/.test(fn.body)) { - diagnostics.push({ - code: "WRN-SEC-STRING-TIMER", - severity: "error", - message: `Client function '${fn.name}' passes a string to a timer.`, - hint: "Pass a function instead of executable text.", - file: options.file - }); - } - const parameterNames = new Set(fn.parameters.map((parameter) => parameter.name)); - for (const prop of ast.props) { - if (containsReadonlyPropMutation(fn.body, prop.name, parameterNames)) { - diagnostics.push({ - code: "WRN-PROP-READONLY", - severity: "error", - message: `Function '${fn.name}' attempts to mutate readonly prop '${prop.name}'.`, - hint: "Copy the prop into state before mutating it.", - file: options.file - }); - } - } - } - for (const state of ast.states) { - if (state.runtime === "shared" && /^(?:new\s+(?:Map|Set|WeakMap|WeakSet)|(?:async\s+)?function\b|.*=>)/.test(state.expr.trim())) { - diagnostics.push({ - code: "WRN-STATE-NON-SERIALIZABLE", - severity: "error", - message: `Shared state '${state.name}' is not safely serializable.`, - hint: "Use JSON-compatible data or move the value into client/server state.", - file: options.file - }); - } - if (state.runtime !== "server" && /\b(?:process\.env|Bun\.env|Deno\.env|ctx\.env|import\.meta\.env)\b/.test(state.expr)) { - diagnostics.push({ - code: "WRN-SEC-SERVER-SECRET-SOURCE", - severity: "error", - message: `Browser-visible state '${state.name}' reads from a server environment source.`, - hint: "Move environment-backed values into server state and return only an explicitly safe result.", - file: options.file - }); - } - } - if (ast.persist) { - const stateNames = new Set(ast.states.filter((state) => state.runtime !== "server").map((state) => state.name)); - for (const name of ast.persist.include) - if (!stateNames.has(name)) - diagnostics.push({ - code: "WRN-PERSIST-UNKNOWN-FIELD", - severity: "error", - message: `Persist include references unknown or server-only state '${name}'.`, - hint: "Persist only declared shared/client state fields.", - file: options.file - }); - for (const name of ast.persist.include) - if (/token|password|secret|otp|api.?key/i.test(name)) - diagnostics.push({ - code: "WRN-PERSIST-SENSITIVE", - severity: "error", - message: `Sensitive field '${name}' cannot be persisted.`, - hint: "Remove secrets, tokens, passwords, OTPs, and API keys from persistence.", - file: options.file - }); - } - return diagnostics; - } - function diagnose(source, options = {}) { - try { - return astDiagnostics((0, parser_ts_1.parse)(source), options); - } catch (error) { - return [diagnosticFromError(source, error, options)]; - } - } - function assertValidAst(ast, options = {}) { - const errors = astDiagnostics(ast, options).filter((diagnostic) => diagnostic.severity === "error"); - if (!errors.length) - return; - const first = errors[0]; - throw new parser_ts_1.ParseError(first.message, first.code); - } - function isHydrationStrategy(value) { - return spec_ts_1.WRN_HYDRATION_STRATEGIES.includes(value) || value.startsWith("media:") && value.length > "media:".length; - } - function isRuntimeTarget(value) { - return spec_ts_1.WRN_RUNTIME_TARGETS.includes(value); - } - function formatDiagnostic(source, diagnostic) { - const location = diagnostic.position ? `${diagnostic.file ?? ""}:${diagnostic.position.line}:${diagnostic.position.column}` : diagnostic.file ?? ""; - const lines = [ - `${diagnostic.code} ${diagnostic.severity.toUpperCase()}`, - "", - diagnostic.message, - "", - location - ]; - if (diagnostic.position) { - const sourceLine = source.split(/\r?\n/)[diagnostic.position.line - 1] ?? ""; - lines.push("", sourceLine, `${" ".repeat(Math.max(0, diagnostic.position.column - 1))}^`); - } - if (diagnostic.hint) - lines.push("", `Hint: ${diagnostic.hint}`); - return lines.join(` -`); - } - }, - "packages/syntax/src/formatter.ts": function(module3, exports3, require2, __filename2, __dirname2) { - Object.defineProperty(exports3, "__esModule", { value: true }); - exports3.formatWrn = formatWrn; - const VOID_ELEMENTS = new Set([ - "area", - "base", - "br", - "col", - "embed", - "hr", - "img", - "input", - "link", - "meta", - "param", - "source", - "track", - "wbr" - ]); - function splitPropDeclarations(value) { - const declarations = []; - let start = 0; - let index = 0; - let quote = null; - let escaped = false; - let square = 0; - let brace = 0; - let paren = 0; - let segmentHasColon = false; - let segmentHasEquals = false; - const isIdentifierStart = (character) => /[A-Za-z_]/.test(character || ""); - const isIdentifierPart = (character) => /[A-Za-z0-9_]/.test(character || ""); - const beginsDeclaration = (position) => { - let cursor = position; - while (cursor < value.length && /[ \t]/.test(value[cursor])) - cursor += 1; - if (value.slice(cursor).startsWith("@event")) { - cursor += "@event".length; - if (!/\s/.test(value[cursor] || "")) - return false; - while (cursor < value.length && /\s/.test(value[cursor])) - cursor += 1; - if (!isIdentifierStart(value[cursor])) - return false; - cursor += 1; - while (cursor < value.length && isIdentifierPart(value[cursor])) - cursor += 1; - while (cursor < value.length && /[ \t]/.test(value[cursor])) - cursor += 1; - return value[cursor] === "=" ? "=" : null; - } - if (!isIdentifierStart(value[cursor])) - return false; - cursor += 1; - while (cursor < value.length && isIdentifierPart(value[cursor])) - cursor += 1; - if (value[cursor] === "?") - cursor += 1; - while (cursor < value.length && /[ \t]/.test(value[cursor])) - cursor += 1; - return value[cursor] === "=" || value[cursor] === ":" ? value[cursor] : null; - }; - while (index < value.length) { - const character = value[index]; - if (quote !== null) { - if (escaped) - escaped = false; - else if (character === "\\") - escaped = true; - else if (character === quote) - quote = null; - index += 1; - continue; - } - if (character === '"' || character === "'" || character === "`") { - quote = character; - index += 1; - continue; - } - if (character === "[") - square += 1; - else if (character === "]" && square > 0) - square -= 1; - else if (character === "{") - brace += 1; - else if (character === "}" && brace > 0) - brace -= 1; - else if (character === "(") - paren += 1; - else if (character === ")" && paren > 0) - paren -= 1; - const topLevel = square === 0 && brace === 0 && paren === 0; - if (topLevel && character === ":") - segmentHasColon = true; - if (topLevel && character === "=") - segmentHasEquals = true; - const candidateDelimiter = topLevel && /\s/.test(character) ? beginsDeclaration(index) : null; - const beginsNext = candidateDelimiter === ":" || candidateDelimiter === "=" && (segmentHasEquals || !segmentHasColon); - if (topLevel && /\s/.test(character) && value.slice(start, index).trim() !== "@event" && beginsNext) { - const declaration2 = value.slice(start, index).trim(); - if (declaration2) - declarations.push(declaration2); - while (index < value.length && /\s/.test(value[index])) - index += 1; - start = index; - segmentHasColon = false; - segmentHasEquals = false; - continue; - } - index += 1; - } - const declaration = value.slice(start).trim(); - if (declaration) - declarations.push(declaration); - return declarations; - } - function splitOutputDeclarations(value) { - const declarations = []; - let start = 0; - let paren = 0; - let angle = 0; - let square = 0; - let quote = null; - let escaped = false; - const startsOutput = (position) => { - let cursor = position; - while (cursor < value.length && /\s/.test(value[cursor])) - cursor += 1; - if (!/[A-Za-z_$]/.test(value[cursor] || "")) - return false; - cursor += 1; - while (cursor < value.length && /[A-Za-z0-9_$]/.test(value[cursor] || "")) - cursor += 1; - while (cursor < value.length && /\s/.test(value[cursor])) - cursor += 1; - return value[cursor] === "("; - }; - for (let index = 0;index < value.length; index += 1) { - const character = value[index]; - if (quote !== null) { - if (escaped) - escaped = false; - else if (character === "\\") - escaped = true; - else if (character === quote) - quote = null; - continue; - } - if (character === '"' || character === "'" || character === "`") { - quote = character; - continue; - } - if (character === "(") - paren += 1; - else if (character === ")" && paren > 0) - paren -= 1; - else if (character === "[") - square += 1; - else if (character === "]" && square > 0) - square -= 1; - else if (character === "<") - angle += 1; - else if (character === ">" && angle > 0) - angle -= 1; - if (paren === 0 && square === 0 && angle === 0 && /\s/.test(character) && startsOutput(index)) { - const declaration = value.slice(start, index).trim(); - if (declaration) - declarations.push(declaration); - while (index < value.length && /\s/.test(value[index])) - index += 1; - start = index; - index -= 1; - } - } - const finalDeclaration = value.slice(start).trim(); - if (finalDeclaration) - declarations.push(finalDeclaration); - return declarations; - } - function formatInlineDeclarationBlock(value, unit, depth) { - const match = /^(props|state|computed|outputs)\s*\{([\s\S]*)\}$/.exec(value.trim()); - if (!match) - return null; - const declarations = match[1] === "outputs" ? splitOutputDeclarations(match[2].trim()) : splitPropDeclarations(match[2].trim()); - return [ - `${unit.repeat(depth)}${match[1]} {`, - ...declarations.map((declaration) => `${unit.repeat(depth + 1)}${declaration}`), - `${unit.repeat(depth)}}` - ]; - } - function formatInlinePropsBlock(value, unit, depth) { - const match = /^props\s*\{([\s\S]*)\}$/.exec(value.trim()); - if (!match) - return null; - const declarations = splitPropDeclarations(match[1].trim()); - if (declarations.length === 0) { - return [`${unit.repeat(depth)}props {`, `${unit.repeat(depth)}}`]; - } - return [ - `${unit.repeat(depth)}props {`, - ...declarations.map((declaration) => `${unit.repeat(depth + 1)}${declaration}`), - `${unit.repeat(depth)}}` - ]; - } - function findOpeningTagEnd(value) { - let quote = null; - let escaped = false; - for (let index = 0;index < value.length; index += 1) { - const character = value[index]; - if (quote !== null) { - if (escaped) { - escaped = false; - continue; - } - if (character === "\\") { - escaped = true; - continue; - } - if (character === quote) { - quote = null; - } - continue; - } - if (character === '"' || character === "'") { - quote = character; - continue; - } - if (character === ">") { - return index; - } - } - return -1; - } - function parseAttributes(value) { - const attributes = []; - let index = 0; - while (index < value.length) { - while (index < value.length && /\s/.test(value[index])) - index += 1; - if (index >= value.length) - break; - const start = index; - while (index < value.length && !/[\s=]/.test(value[index])) - index += 1; - while (index < value.length && /\s/.test(value[index])) - index += 1; - if (value[index] === "=") { - index += 1; - while (index < value.length && /\s/.test(value[index])) - index += 1; - const quote = value[index]; - if (quote === '"' || quote === "'") { - index += 1; - let escaped = false; - while (index < value.length) { - const character = value[index++]; - if (escaped) - escaped = false; - else if (character === "\\") - escaped = true; - else if (character === quote) - break; - } - } else if (value[index] === "{") { - let depth = 0; - let expressionQuote = null; - let escaped = false; - while (index < value.length) { - const character = value[index++]; - if (expressionQuote !== null) { - if (escaped) - escaped = false; - else if (character === "\\") - escaped = true; - else if (character === expressionQuote) - expressionQuote = null; - continue; - } - if (character === '"' || character === "'" || character === "`") { - expressionQuote = character; - } else if (character === "{") { - depth += 1; - } else if (character === "}" && --depth === 0) { - break; - } - } - } else { - while (index < value.length && !/\s/.test(value[index])) - index += 1; - } - } - const attribute = value.slice(start, index).trim(); - if (attribute) - attributes.push(attribute); - } - return attributes; - } - function parseStructuredAttribute(attribute) { - const match = /^([^\s=]+)\s*=\s*\{([\s\S]*)\}$/.exec(attribute); - if (!match) - return null; - const expression = match[2].trim(); - if (!expression.startsWith("[") && !expression.startsWith("{")) - return null; - try { - return { - name: match[1], - value: JSON.parse(expression) - }; - } catch { - return null; - } - } - function formatAttribute(attribute, indentation, unit) { - const structured = parseStructuredAttribute(attribute); - if (!structured) - return [`${indentation}${attribute}`]; - const jsonLines = JSON.stringify(structured.value, null, unit).split(` -`); - if (jsonLines.length === 1) { - return [`${indentation}${structured.name}={${jsonLines[0]}}`]; - } - return [ - `${indentation}${structured.name}={${jsonLines[0]}`, - ...jsonLines.slice(1, -1).map((line) => `${indentation}${line}`), - `${indentation}${jsonLines.at(-1)}}` - ]; - } - function escapeRegExp(value) { - return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - } - function parseOpeningTag(value) { - const endIndex = findOpeningTagEnd(value); - if (endIndex === -1) { - return null; - } - const openingPart = value.slice(0, endIndex + 1); - const remainder = value.slice(endIndex + 1).trim(); - const match = /^<([A-Za-z][\w$:.-]*)([\s\S]*?)(\/?)>$/.exec(openingPart); - if (!match) { - return null; - } - const tagName = match[1]; - const attributes = parseAttributes(match[2].trim()); - const selfClosing = match[3] === "/"; - const escapedTagName = escapeRegExp(tagName); - const immediateClosing = new RegExp(`^<\\/${escapedTagName}\\s*>`, "i").test(remainder); - const trailingClosingMatch = new RegExp(`^([\\s\\S]*?)<\\/${escapedTagName}\\s*>$`, "i").exec(remainder); - const trailingClosing = trailingClosingMatch !== null; - const inlineContent = trailingClosing ? trailingClosingMatch[1].trim() : ""; - const inlineClosing = trailingClosing && inlineContent.length === 0; - const closesInRemainder = immediateClosing || trailingClosing; - return { - tagName, - attributes, - selfClosing, - inlineClosing, - immediateClosing, - trailingClosing, - inlineContent, - closesInRemainder, - remainder - }; - } - function formatOpeningTag(value, unit, depth, printWidth = 100, multilineAttributes = true) { - const parsed = parseOpeningTag(value); - if (!parsed) { - return { - lines: [`${unit.repeat(depth)}${value.trim()}`], - opensElement: false - }; - } - const baseIndent = unit.repeat(depth); - const childIndent = unit.repeat(depth + 1); - const normalizedOpening = `<${parsed.tagName}` + `${parsed.attributes.length ? ` ${parsed.attributes.join(" ")}` : ""}` + `${parsed.selfClosing ? " /" : ""}>`; - const normalizedSingleLine = `${normalizedOpening}${parsed.remainder}`; - const shouldBreak = value.includes(` -`) || multilineAttributes && parsed.attributes.length > 0 || baseIndent.length + normalizedSingleLine.length > printWidth; - const opensElement = !parsed.selfClosing && !parsed.closesInRemainder && !VOID_ELEMENTS.has(parsed.tagName.toLowerCase()); - if (!shouldBreak) { - return { - lines: [`${baseIndent}${normalizedSingleLine}`], - opensElement - }; - } - if (parsed.attributes.length === 0 && !parsed.selfClosing) { - const lines2 = [`${baseIndent}<${parsed.tagName}>`]; - if (parsed.trailingClosing) { - if (parsed.inlineContent) - lines2.push(`${childIndent}${parsed.inlineContent}`); - lines2.push(`${baseIndent}`); - } else if (parsed.remainder) { - lines2.push(`${childIndent}${parsed.remainder}`); - } - return { lines: lines2, opensElement }; - } - const lines = [ - `${baseIndent}<${parsed.tagName}`, - ...parsed.attributes.flatMap((attribute) => formatAttribute(attribute, childIndent, unit)) - ]; - if (parsed.selfClosing) { - lines.push(`${baseIndent}/>`); - return { - lines, - opensElement - }; - } - lines.push(`${baseIndent}>`); - if (parsed.trailingClosing) { - if (parsed.inlineContent) { - lines.push(`${childIndent}${parsed.inlineContent}`); - } - lines.push(`${baseIndent}`); - } else if (parsed.remainder) { - lines.push(`${parsed.immediateClosing ? baseIndent : childIndent}${parsed.remainder}`); - } - return { - lines, - opensElement - }; - } - function isMultilineOpeningTagStart(value) { - if (!value.startsWith("<")) { - return false; - } - if (value.startsWith("/.test(value); - } - function isBalancedInlineHtmlFragment(value) { - if (!value.startsWith("<") || value.startsWith("", tagStart + 4); - if (commentEnd === -1) - return false; - index = commentEnd + 3; - continue; - } - const relativeEnd = findOpeningTagEnd(value.slice(tagStart)); - if (relativeEnd === -1) - return false; - const tag = value.slice(tagStart, tagStart + relativeEnd + 1); - const match = /^<\/?([A-Za-z][\w$:.-]*)[\s\S]*?>$/.exec(tag); - if (!match) { - index = tagStart + 1; - continue; - } - tagCount += 1; - const tagName = match[1]; - const normalizedName = /^[a-z]/.test(tagName) ? tagName.toLowerCase() : tagName; - const closing = tag.startsWith("$/.test(tag); - const voidElement = VOID_ELEMENTS.has(tagName.toLowerCase()); - if (closing) { - if (stack.at(-1) !== normalizedName) - return false; - stack.pop(); - } else { - if (stack.length === 0) - rootCount += 1; - else - hasNestedElement = true; - if (!selfClosing && !voidElement) - stack.push(normalizedName); - } - index = tagStart + relativeEnd + 1; - } - return tagCount >= 2 && stack.length === 0 && (rootCount > 1 || hasNestedElement || hasOutsideText); - } - function isControlBlockOpen(value) { - return /^\{#(?:if|each)\b[\s\S]*\}$/.test(value); - } - function isControlBlockMiddle(value) { - return /^\{:(?:else(?:\s+if\b[\s\S]*)?|empty)\}$/.test(value); - } - function isControlBlockClose(value) { - return /^\{\/(?:if|each)\}$/.test(value); - } - function countLeadingClosingBraces(value) { - let index = 0; - let count = 0; - while (index < value.length) { - while (index < value.length && /\s/.test(value[index])) { - index += 1; - } - if (value[index] !== "}" && value[index] !== "]") { - break; - } - count += 1; - index += 1; - } - return count; - } - function countStructuralBraces(value) { - let openings = 0; - let closings = 0; - let quote = null; - let escaped = false; - let htmlComment = false; - for (let index = 0;index < value.length; index += 1) { - if (!quote && !htmlComment && value.startsWith("", index)) { - htmlComment = false; - index += 2; - continue; - } - if (htmlComment) { - continue; - } - const character = value[index]; - if (quote !== null) { - if (escaped) { - escaped = false; - continue; - } - if (character === "\\") { - escaped = true; - continue; - } - if (character === quote) { - quote = null; - } - continue; - } - if (character === '"' || character === "'" || character === "`") { - quote = character; - continue; - } - if (character === "{") { - openings += 1; - } else if (character === "}") { - closings += 1; - } else if (character === "[") { - openings += 1; - } else if (character === "]") { - closings += 1; - } - } - return { - openings, - closings - }; - } - function collectOpeningTag(inputLines, startIndex) { - const collected = [inputLines[startIndex].trim()]; - let index = startIndex; - while (index + 1 < inputLines.length) { - const joined = collected.join(" "); - if (findOpeningTagEnd(joined) !== -1) { - break; - } - index += 1; - collected.push(inputLines[index].trim()); - } - return { - value: collected.join(` -`), - endIndex: index - }; - } - function isPreservedRawBlockStart(value) { - return /)/i.test(value) && !/<\/pre\s*>/i.test(value.slice(0, value.search(/)/i))); - } - function hasPreservedRawBlockEnd(value) { - return /<\/pre\s*>/i.test(value); - } - function transformOutsidePreservedRawBlocks(lines, transformLine) { - const output = []; - let preserving = false; - for (const line of lines) { - if (preserving) { - output.push(line); - if (hasPreservedRawBlockEnd(line)) - preserving = false; - continue; - } - if (isPreservedRawBlockStart(line)) { - output.push(line); - preserving = !hasPreservedRawBlockEnd(line); - continue; - } - output.push(...transformLine(line)); - } - return output; - } - function collectPreservedRawBlock(lines, startIndex) { - const collected = [lines[startIndex]]; - let index = startIndex; - while (!hasPreservedRawBlockEnd(collected.at(-1) || "") && index + 1 < lines.length) { - index += 1; - collected.push(lines[index]); - } - return { lines: collected, endIndex: index }; - } - function expandInlineControlBlocks(lines) { - const marker = /(\{#(?:if|each)\b[^}]*\}|\{:(?:else(?:\s+if\b[^}]*)?|empty)\}|\{\/(?:if|each)\})/g; - return transformOutsidePreservedRawBlocks(lines, (line) => { - if (!marker.test(line)) - return [line]; - marker.lastIndex = 0; - const indentation = line.match(/^\s*/)?.[0] ?? ""; - const segments = line.split(marker).map((segment) => segment.trim()).filter(Boolean); - return segments.map((segment) => `${indentation}${segment}`); - }); - } - function expandStructuredStateDeclarations(lines, unit) { - return transformOutsidePreservedRawBlocks(lines, (line) => { - const match = /^(\s*state\s+[A-Za-z_$][\w$]*\s*=\s*)([\\[{][\s\S]*)$/.exec(line); - if (!match) - return [line]; - try { - const parsed = JSON.parse(match[2].trim()); - const jsonLines = JSON.stringify(parsed, null, unit).split(` -`); - if (jsonLines.length === 1) - return [`${match[1]}${jsonLines[0]}`]; - const leading = match[1].match(/^\s*/)?.[0] ?? ""; - return [ - `${match[1]}${jsonLines[0]}`, - ...jsonLines.slice(1).map((jsonLine) => `${leading}${jsonLine}`) - ]; - } catch { - return [line]; - } - }); - } - function formatWrnPass(source, options = {}) { - const unit = options.insertSpaces === false ? "\t" : " ".repeat(options.tabSize ?? 4); - const printWidth = options.printWidth ?? 100; - const multilineAttributes = options.multilineAttributes !== false; - let codeDepth = 0; - let htmlDepth = 0; - let controlDepth = 0; - let index = 0; - const sourceLines = source.replace(/\r\n/g, ` -`).split(` -`); - const inputLines = expandInlineControlBlocks(expandStructuredStateDeclarations(sourceLines, unit)); - const output = []; - let previousWasBlank = false; - while (index < inputLines.length) { - const originalLine = inputLines[index]; - let value = originalLine.trim(); - if (value === "") { - if (!previousWasBlank && output.length > 0) { - output.push(""); - } - previousWasBlank = true; - index += 1; - continue; - } - previousWasBlank = false; - if (/^import\b/.test(value)) { - const importLines = [value]; - while (!/(?:\bfrom\s+)?["'][^"']+["']\s*;?$/.test(importLines[importLines.length - 1]) && index + 1 < inputLines.length) { - index += 1; - importLines.push(inputLines[index].trim()); - } - output.push(importLines[0], ...importLines.slice(1).map((line) => `${unit}${line}`)); - index += 1; - continue; - } - if (isPreservedRawBlockStart(value)) { - const collected = collectPreservedRawBlock(inputLines, index); - const depth2 = codeDepth + htmlDepth + controlDepth; - output.push(`${unit.repeat(depth2)}${collected.lines[0].trimStart()}`); - output.push(...collected.lines.slice(1)); - index = collected.endIndex + 1; - continue; - } - if (isMultilineOpeningTagStart(value)) { - const collected = collectOpeningTag(inputLines, index); - value = collected.value; - index = collected.endIndex; - } - const inlineDeclaration = formatInlineDeclarationBlock(value, unit, codeDepth + htmlDepth); - const inlineProps = inlineDeclaration ?? formatInlinePropsBlock(value, unit, codeDepth + htmlDepth); - if (inlineProps) { - output.push(...inlineProps); - index += 1; - continue; - } - const leadingClosingBraces = countLeadingClosingBraces(value); - const closesControlBlock = isControlBlockClose(value); - const continuesControlBlock = isControlBlockMiddle(value); - const lineControlDepth = closesControlBlock || continuesControlBlock ? Math.max(0, controlDepth - 1) : controlDepth; - const lineCodeDepth = Math.max(0, codeDepth - leadingClosingBraces); - let lineHtmlDepth = htmlDepth; - if (isClosingTag(value)) { - lineHtmlDepth = Math.max(0, htmlDepth - 1); - } - const depth = lineCodeDepth + lineHtmlDepth + lineControlDepth; - const inlineFragmentFits = unit.repeat(depth).length + value.length <= printWidth; - if (isBalancedInlineHtmlFragment(value) && inlineFragmentFits) { - output.push(`${unit.repeat(depth)}${value}`); - } else if (value.startsWith("<") && !value.startsWith(" 0 && output[output.length - 1] === "") { - output.pop(); - } - return `${output.join(` -`)} -`; - } - function formatWrn(source, options = {}) { - let current = source; - const seen = new Set; - for (let pass = 0;pass < 8; pass++) { - const formatted = formatWrnPass(current, options); - if (formatted === current) - return formatted; - if (seen.has(formatted)) - return [...seen, formatted].sort()[0]; - seen.add(current); - current = formatted; - } - return current; - } - }, - "packages/syntax/src/index.ts": function(module3, exports3, require2, __filename2, __dirname2) { - var __createBinding = this && this.__createBinding || (Object.create ? function(o, m, k, k2) { - if (k2 === undefined) - k2 = k; - var desc = Object.getOwnPropertyDescriptor(m, k); - if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { - desc = { enumerable: true, get: function() { - return m[k]; - } }; - } - Object.defineProperty(o, k2, desc); - } : function(o, m, k, k2) { - if (k2 === undefined) - k2 = k; - o[k2] = m[k]; - }); - var __exportStar = this && this.__exportStar || function(m, exports4) { - for (var p in m) - if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports4, p)) - __createBinding(exports4, m, p); - }; - Object.defineProperty(exports3, "__esModule", { value: true }); - exports3.stripRuntimeFunctionModifiers = exports3.parseStructuredImports = exports3.parseStoreLifecycle = exports3.parseStateDeclarations = exports3.parseRuntimeFunctions = exports3.parsePersist = exports3.parseOutputs = exports3.parseComputedDeclarations = exports3.supportsSyntaxFeature = exports3.diagnosticSummary = exports3.sliceSource = exports3.createSourceRange = exports3.WRN_SYNTAX_FEATURES = exports3.WRN_SYNTAX_VERSION = exports3.positionAt = exports3.isRuntimeTarget = exports3.isHydrationStrategy = exports3.formatDiagnostic = exports3.diagnosticFromError = exports3.diagnose = exports3.containsReadonlyPropMutation = exports3.classifyParseError = exports3.assertValidAst = exports3.validateTypedInitializer = exports3.runtimeTypeOf = exports3.inferredRuntimeType = exports3.eraseFunctionTypes = exports3.VOID_ELEMENTS = exports3.ParseError = exports3.parseHtmlView = exports3.parse = exports3.formatWrn = exports3.LexError = exports3.Lexer = undefined; - var tokenizer_ts_1 = require2("./tokenizer.js"); - Object.defineProperty(exports3, "Lexer", { enumerable: true, get: function() { - return tokenizer_ts_1.Lexer; - } }); - Object.defineProperty(exports3, "LexError", { enumerable: true, get: function() { - return tokenizer_ts_1.LexError; - } }); - var formatter_ts_1 = require2("./formatter.js"); - Object.defineProperty(exports3, "formatWrn", { enumerable: true, get: function() { - return formatter_ts_1.formatWrn; - } }); - var parser_ts_1 = require2("./parser.js"); - Object.defineProperty(exports3, "parse", { enumerable: true, get: function() { - return parser_ts_1.parse; - } }); - Object.defineProperty(exports3, "parseHtmlView", { enumerable: true, get: function() { - return parser_ts_1.parseHtmlView; - } }); - Object.defineProperty(exports3, "ParseError", { enumerable: true, get: function() { - return parser_ts_1.ParseError; - } }); - Object.defineProperty(exports3, "VOID_ELEMENTS", { enumerable: true, get: function() { - return parser_ts_1.VOID_ELEMENTS; - } }); - var types_ts_1 = require2("./types.js"); - Object.defineProperty(exports3, "eraseFunctionTypes", { enumerable: true, get: function() { - return types_ts_1.eraseFunctionTypes; - } }); - Object.defineProperty(exports3, "inferredRuntimeType", { enumerable: true, get: function() { - return types_ts_1.inferredRuntimeType; - } }); - Object.defineProperty(exports3, "runtimeTypeOf", { enumerable: true, get: function() { - return types_ts_1.runtimeTypeOf; - } }); - Object.defineProperty(exports3, "validateTypedInitializer", { enumerable: true, get: function() { - return types_ts_1.validateTypedInitializer; - } }); - var diagnostics_ts_1 = require2("./diagnostics.js"); - Object.defineProperty(exports3, "assertValidAst", { enumerable: true, get: function() { - return diagnostics_ts_1.assertValidAst; - } }); - Object.defineProperty(exports3, "classifyParseError", { enumerable: true, get: function() { - return diagnostics_ts_1.classifyParseError; - } }); - Object.defineProperty(exports3, "containsReadonlyPropMutation", { enumerable: true, get: function() { - return diagnostics_ts_1.containsReadonlyPropMutation; - } }); - Object.defineProperty(exports3, "diagnose", { enumerable: true, get: function() { - return diagnostics_ts_1.diagnose; - } }); - Object.defineProperty(exports3, "diagnosticFromError", { enumerable: true, get: function() { - return diagnostics_ts_1.diagnosticFromError; - } }); - Object.defineProperty(exports3, "formatDiagnostic", { enumerable: true, get: function() { - return diagnostics_ts_1.formatDiagnostic; - } }); - Object.defineProperty(exports3, "isHydrationStrategy", { enumerable: true, get: function() { - return diagnostics_ts_1.isHydrationStrategy; - } }); - Object.defineProperty(exports3, "isRuntimeTarget", { enumerable: true, get: function() { - return diagnostics_ts_1.isRuntimeTarget; - } }); - Object.defineProperty(exports3, "positionAt", { enumerable: true, get: function() { - return diagnostics_ts_1.positionAt; - } }); - __exportStar(require2("./spec.js"), exports3); - var versioning_ts_1 = require2("./versioning.js"); - Object.defineProperty(exports3, "WRN_SYNTAX_VERSION", { enumerable: true, get: function() { - return versioning_ts_1.WRN_SYNTAX_VERSION; - } }); - Object.defineProperty(exports3, "WRN_SYNTAX_FEATURES", { enumerable: true, get: function() { - return versioning_ts_1.WRN_SYNTAX_FEATURES; - } }); - Object.defineProperty(exports3, "createSourceRange", { enumerable: true, get: function() { - return versioning_ts_1.createSourceRange; - } }); - Object.defineProperty(exports3, "sliceSource", { enumerable: true, get: function() { - return versioning_ts_1.sliceSource; - } }); - Object.defineProperty(exports3, "diagnosticSummary", { enumerable: true, get: function() { - return versioning_ts_1.diagnosticSummary; - } }); - Object.defineProperty(exports3, "supportsSyntaxFeature", { enumerable: true, get: function() { - return versioning_ts_1.supportsSyntaxFeature; - } }); - var v060_ts_1 = require2("./v060.js"); - Object.defineProperty(exports3, "parseComputedDeclarations", { enumerable: true, get: function() { - return v060_ts_1.parseComputedDeclarations; - } }); - Object.defineProperty(exports3, "parseOutputs", { enumerable: true, get: function() { - return v060_ts_1.parseOutputs; - } }); - Object.defineProperty(exports3, "parsePersist", { enumerable: true, get: function() { - return v060_ts_1.parsePersist; - } }); - Object.defineProperty(exports3, "parseRuntimeFunctions", { enumerable: true, get: function() { - return v060_ts_1.parseRuntimeFunctions; - } }); - Object.defineProperty(exports3, "parseStateDeclarations", { enumerable: true, get: function() { - return v060_ts_1.parseStateDeclarations; - } }); - Object.defineProperty(exports3, "parseStoreLifecycle", { enumerable: true, get: function() { - return v060_ts_1.parseStoreLifecycle; - } }); - Object.defineProperty(exports3, "parseStructuredImports", { enumerable: true, get: function() { - return v060_ts_1.parseStructuredImports; - } }); - Object.defineProperty(exports3, "stripRuntimeFunctionModifiers", { enumerable: true, get: function() { - return v060_ts_1.stripRuntimeFunctionModifiers; - } }); - }, - "packages/syntax/src/parser.ts": function(module3, exports3, require2, __filename2, __dirname2) { - Object.defineProperty(exports3, "__esModule", { value: true }); - exports3.ParseError = exports3.VOID_ELEMENTS = undefined; - exports3.parse = parse; - exports3.parseHtmlView = parseHtmlView; - const spec_ts_1 = require2("./spec.js"); - const tokenizer_ts_1 = require2("./tokenizer.js"); - const types_ts_1 = require2("./types.js"); - const v060_ts_1 = require2("./v060.js"); - exports3.VOID_ELEMENTS = new Set([ - "area", - "base", - "br", - "col", - "embed", - "hr", - "img", - "input", - "link", - "meta", - "param", - "source", - "track", - "wbr" - ]); - - class ParseError extends Error { - code; - offset; - constructor(message, code = "WRN-PARSE-001") { - super(message); - this.name = "ParseError"; - this.code = code; - const match = /offset\s+(\d+)/i.exec(message); - this.offset = match ? Number(match[1]) : undefined; - } - } - exports3.ParseError = ParseError; - function parseSeoBlock(body) { - const out = {}; - const pair = /([A-Za-z][A-Za-z0-9_-]*)\s*=\s*(?:"((?:\\.|[^"\\])*)"|'((?:\\.|[^'\\])*)'|([^\n;]+))/g; - for (const match of body.matchAll(pair)) { - const key = match[1]; - const rawValue = match[2] ?? match[3] ?? match[4] ?? ""; - out[key] = unescapeSeoValue(rawValue.trim()); - } - return out; - } - function unescapeSeoValue(value) { - return value.replace(/\\(["'\\nrt])/g, (_match, ch) => { - if (ch === "n") - return ` -`; - if (ch === "r") - return "\r"; - if (ch === "t") - return "\t"; - return ch; - }); - } - function parse(source) { - const lx = new tokenizer_ts_1.Lexer(source); - const imports = []; - const importPattern = /import\s+(?:type\s+)?(?:[\s\S]*?\s+from\s+)?["'][^"'\r\n]+["']\s*;?/y; - while (true) { - while (/\s/u.test(source[lx.pos] ?? "")) - lx.pos++; - if (source.startsWith("//", lx.pos)) { - while (lx.pos < source.length && source[lx.pos] !== ` -`) - lx.pos++; - continue; - } - importPattern.lastIndex = lx.pos; - const statement = importPattern.exec(source); - if (!statement) - break; - imports.push(statement[0].trim()); - lx.pos = importPattern.lastIndex; - } - const expect = (type) => { - const t = lx.next(); - if (t.type !== type) { - throw new ParseError(`Expected ${type} but got '${t.value || t.type}' at offset ${t.pos}`); - } - return t; - }; - const expectKeyword = (kw) => { - const t = lx.next(); - if (t.type !== "ident" || t.value !== kw) { - throw new ParseError(`Expected '${kw}' but got '${t.value || t.type}' at offset ${t.pos}`); - } - }; - try { - const opener = lx.next(); - if (opener.type !== "ident" || !["page", "component", "layout", "global"].includes(opener.value)) { - throw new ParseError(`Expected 'page', 'component', 'layout', 'global store', or 'page store' but got '${opener.value || opener.type}' at offset ${opener.pos}`); - } - let kind; - let storeKind; - let name; - if (opener.value === "global") { - expectKeyword("store"); - kind = "global-store"; - storeKind = "global"; - name = expect("ident").value; - } else if (opener.value === "page" && lx.peek().type === "ident" && lx.peek().value === "store") { - lx.next(); - kind = "page-store"; - storeKind = "page"; - name = expect("ident").value; - } else { - kind = opener.value; - name = expect("ident").value; - } - expect("lbrace"); - let layout; - let layoutIsSymbol = false; - let runtime; - let renderMode; - let hydrate; - const props = []; - const events = []; - const outputs = []; - const types = []; - const states = []; - const computed = []; - const effects = []; - const loads = []; - const actions = []; - const security = {}; - const cache = {}; - const navigation = {}; - const seo = {}; - const view = []; - const styles = []; - const functions = []; - const runtimeFunctions = []; - const dataApis = []; - const modeFunctions = []; - const lifecycle = {}; - let storeLifecycle = {}; - let persist; - const watches = []; - const apis = []; - const realtimes = []; - while (lx.peek().type !== "rbrace") { - const kw = lx.peek(); - if (kw.type === "eof") - throw new ParseError(`Unexpected end of input inside ${kind}`); - if (kw.type !== "ident") { - throw new ParseError(`Expected a ${kind} member keyword at offset ${kw.pos}`); - } - switch (kw.value) { - case "layout": { - lx.next(); - expect("eq"); - const layoutToken = lx.next(); - if (layoutToken.type !== "string" && layoutToken.type !== "ident") { - throw new ParseError(`Expected a layout string or imported symbol at offset ${layoutToken.pos}`); - } - layout = layoutToken.value; - layoutIsSymbol = layoutToken.type === "ident"; - break; - } - case "runtime": { - lx.next(); - expect("eq"); - const value = expect("string").value; - if (!spec_ts_1.WRN_RUNTIME_TARGETS.includes(value)) { - throw new ParseError(`Unknown runtime target '${value}' at offset ${kw.pos}`, "WRN-RUNTIME-TARGET"); - } - runtime = value; - break; - } - case "render": { - lx.next(); - expect("eq"); - const value = expect("string").value; - if (!["static", "server", "hybrid", "client", "partial-static"].includes(value)) - throw new ParseError(`Unknown render mode '${value}' at offset ${kw.pos}`, "WRN-RENDER-MODE"); - renderMode = value; - break; - } - case "hydrate": { - lx.next(); - expect("eq"); - const value = expect("string").value; - hydrate = value === "never" ? "none" : value; - break; - } - case "props": { - lx.next(); - expect("lbrace"); - while (true) { - if (lx.startsWithBlockComment()) { - throw new ParseError("Block comments are not allowed inside props {}; use // line comments instead", "WRN-PROPS-BLOCK-COMMENT"); - } - if (lx.peek().type === "rbrace") - break; - const t = lx.peek(); - if (t.type === "eof") - throw new ParseError("Unexpected end of input inside props"); - if (t.type === "at") { - lx.next(); - const declarationKind = expect("ident"); - if (declarationKind.value !== "event") { - throw new ParseError(`Expected '@event' but got '@${declarationKind.value}' at offset ${declarationKind.pos}`); - } - const eventName = expect("ident").value; - expect("eq"); - const marker = lx.readPropInitializer(); - if (marker !== "function") { - throw new ParseError(`Event '${eventName}' must be declared as '@event ${eventName} = function'`); - } - events.push({ name: eventName }); - continue; - } - if (t.type !== "ident") { - throw new ParseError(`Expected a prop name at offset ${t.pos}`); - } - const pName = expect("ident").value; - const optional = lx.peek().type === "question" ? (lx.next(), true) : false; - let valueType; - let hasDefault = false; - if (lx.peek().type === "colon") { - lx.next(); - const annotation = lx.readTypeAnnotation(); - valueType = annotation.type; - hasDefault = annotation.hasDefault; - } else { - expect("eq"); - hasDefault = true; - } - const defaultValue = hasDefault ? lx.readPropInitializer() : "undefined"; - props.push({ - name: pName, - valueType, - required: !hasDefault && !optional, - default: defaultValue - }); - } - expect("rbrace"); - break; - } - case "state": { - lx.next(); - if (lx.peek().type === "lbrace") { - const grouped = (0, v060_ts_1.parseStateDeclarations)(lx.readBalancedBraces(), "shared"); - states.push(...grouped); - break; - } - const sName = expect("ident").value; - let valueType; - if (lx.peek().type === "colon") { - lx.next(); - const annotation = lx.readTypeAnnotation(); - valueType = annotation.type; - if (!annotation.hasDefault) - throw new ParseError(`State '${sName}' requires an initializer`); - } else { - expect("eq"); - } - states.push({ - name: sName, - valueType, - expr: lx.readPropInitializer(), - runtime: "shared" - }); - break; - } - case "computed": { - lx.next(); - if (lx.peek().type === "lbrace") { - computed.push(...(0, v060_ts_1.parseComputedDeclarations)(lx.readBalancedBraces())); - } else { - const cName = expect("ident").value; - let valueType; - if (lx.peek().type === "colon") { - lx.next(); - const annotation = lx.readTypeAnnotation(); - valueType = annotation.type; - if (!annotation.hasDefault) - throw new ParseError(`Computed '${cName}' requires an expression`); - } else - expect("eq"); - computed.push({ name: cName, valueType, expr: lx.readPropInitializer() }); - } - break; - } - case "outputs": { - lx.next(); - try { - outputs.push(...(0, v060_ts_1.parseOutputs)(lx.readBalancedBraces())); - } catch (error) { - throw new ParseError(error instanceof Error ? error.message : String(error), "WRN-OUTPUT-DECLARATION"); - } - break; - } - case "effect": { - lx.next(); - effects.push({ body: lx.readBalancedBraces() }); - break; - } - case "types": { - lx.next(); - types.push(lx.readBalancedBraces()); - break; - } - case "view": { - lx.next(); - expect("lbrace"); - const { nodes, endPos } = parseHtmlView(lx.src, lx.pos); - view.push(...nodes); - lx.pos = endPos; - expect("rbrace"); - break; - } - case "seo": { - lx.next(); - Object.assign(seo, parseSeoBlock(lx.readBalancedBraces())); - break; - } - case "security": { - lx.next(); - Object.assign(security, parseSeoBlock(lx.readBalancedBraces())); - break; - } - case "navigation": { - lx.next(); - Object.assign(navigation, parseSeoBlock(lx.readBalancedBraces())); - break; - } - case "cache": { - lx.next(); - Object.assign(cache, parseSeoBlock(lx.readBalancedBraces())); - break; - } - case "load": { - lx.next(); - const first = expect("ident"); - const mode = first.value === "client" ? "client" : "server"; - const name2 = first.value === "server" || first.value === "client" ? lx.peek().type === "ident" ? expect("ident").value : undefined : first.value; - const dependsOn = []; - let deferred = false; - while (lx.peek().type === "ident") { - if (lx.peek().value === "defer") { - lx.next(); - deferred = true; - continue; - } - if (lx.peek().value !== "after") - break; - lx.next(); - dependsOn.push(expect("ident").value); - while (lx.peek().type === "comma") { - lx.next(); - dependsOn.push(expect("ident").value); - } - } - loads.push({ - mode, - name: name2, - ...dependsOn.length ? { dependsOn } : {}, - ...deferred ? { deferred: true } : {}, - body: lx.readBalancedBraces() - }); - break; - } - case "action": { - lx.next(); - const actionName = expect("ident").value; - const args = []; - if (lx.peek().type === "lparen") { - lx.next(); - while (lx.peek().type !== "rparen") { - args.push(expect("ident").value); - if (lx.peek().type === "comma") - lx.next(); - } - expect("rparen"); - } - let schema; - if (lx.peek().type === "ident" && lx.peek().value === "using") { - lx.next(); - schema = expect("ident").value; - } - actions.push({ name: actionName, args, schema, body: lx.readBalancedBraces() }); - break; - } - case "api": { - lx.next(); - const method = expect("ident").value.toUpperCase(); - const path = lx.readPath(); - const body = lx.readBalancedBraces(); - apis.push({ method, path, body }); - break; - } - case "ssr": - case "client": - case "server": { - const rawMode = kw.value; - const mode = rawMode === "client" ? "client" : "ssr"; - lx.next(); - if ((rawMode === "client" || rawMode === "server") && lx.peek().type === "ident" && lx.peek().value === "state") { - lx.next(); - if (lx.peek().type !== "lbrace") - throw new ParseError(`Expected a grouped ${rawMode} state block`); - states.push(...(0, v060_ts_1.parseStateDeclarations)(lx.readBalancedBraces(), rawMode)); - break; - } - if (mode === "client" && lx.peek().type === "eq") { - lx.next(); - hydrate = expect("string").value; - break; - } - expect("lbrace"); - while (lx.peek().type !== "rbrace") { - const member = lx.peek(); - if (member.type === "eof") { - throw new ParseError(`Unexpected end of input inside ${mode} block`); - } - if (member.type !== "ident") { - throw new ParseError(`Expected a ${mode} member keyword at offset ${member.pos}`); - } - switch (member.value) { - case "api": { - lx.next(); - const name2 = expect("ident").value; - const method = expect("ident").value.toUpperCase(); - const path = lx.readPath(); - const body = lx.readBalancedBraces(); - dataApis.push({ mode, name: name2, method, path, body }); - break; - } - case "functions": { - lx.next(); - modeFunctions.push({ mode, body: lx.readBalancedBraces() }); - break; - } - default: - throw new ParseError(`Unknown ${mode} member '${member.value}' at offset ${member.pos}`); - } - } - expect("rbrace"); - break; - } - case "shared": { - lx.next(); - const member = expect("ident"); - if (member.value !== "state") - throw new ParseError(`Expected 'state' after shared at offset ${member.pos}`); - states.push(...(0, v060_ts_1.parseStateDeclarations)(lx.readBalancedBraces(), "shared")); - break; - } - case "realtime": { - lx.next(); - const rName = expect("ident").value; - expect("lbrace"); - const handlers = []; - while (lx.peek().type !== "rbrace") { - expectKeyword("on"); - const event = expect("ident").value; - expect("lparen"); - const args = []; - while (lx.peek().type !== "rparen") { - args.push(expect("ident").value); - if (lx.peek().type === "comma") - lx.next(); - } - expect("rparen"); - handlers.push({ event, args, body: lx.readBalancedBraces() }); - } - expect("rbrace"); - realtimes.push({ name: rName, handlers }); - break; - } - case "style": { - lx.next(); - styles.push(lx.readBalancedBraces()); - break; - } - case "lifecycle": { - lx.next(); - const body = lx.readBalancedBraces(); - if (kind === "global-store" || kind === "page-store") { - storeLifecycle = (0, v060_ts_1.parseStoreLifecycle)(body); - const allowedStoreHooks = new Set(["serverInit", "clientInit", "hydrate", "dispose"]); - const hookLexer = new tokenizer_ts_1.Lexer(body); - while (hookLexer.peek().type !== "eof") { - const token = hookLexer.next(); - if (token.type !== "ident") { - throw new ParseError(`Expected a lifecycle hook at offset ${token.pos}`); - } - if (!allowedStoreHooks.has(token.value)) { - throw new ParseError(`Unknown store lifecycle hook '${token.value}'`); - } - hookLexer.readBalancedBraces(); - } - } else { - const allowedComponentHooks = new Set([ - "mount", - "update", - "unmount", - "clientInit", - "dispose" - ]); - const hookLexer = new tokenizer_ts_1.Lexer(body); - while (hookLexer.peek().type !== "eof") { - const token = hookLexer.next(); - if (token.type !== "ident") { - throw new ParseError(`Expected a lifecycle hook at offset ${token.pos}`); - } - if (!allowedComponentHooks.has(token.value)) { - throw new ParseError(`Unknown lifecycle hook '${token.value}'`); - } - const hookBody = hookLexer.readBalancedBraces(); - const hook = token.value === "clientInit" ? "mount" : token.value === "dispose" ? "unmount" : token.value; - if (lifecycle[hook] !== undefined) { - throw new ParseError(`Duplicate lifecycle hook '${hook}'`); - } - lifecycle[hook] = hookBody; - } - } - break; - } - case "watch": { - lx.next(); - const stateName = expect("ident").value; - const body = lx.readBalancedBraces(); - watches.push({ - state: stateName, - body - }); - break; - } - case "functions": { - lx.next(); - const body = lx.readBalancedBraces(); - functions.push(body); - try { - runtimeFunctions.push(...(0, v060_ts_1.parseRuntimeFunctions)(body)); - } catch (error) { - throw new ParseError(error instanceof Error ? error.message : String(error), "WRN-FUNCTION-DECLARATION"); - } - break; - } - case "persist": { - lx.next(); - persist = (0, v060_ts_1.parsePersist)(lx.readBalancedBraces()); - break; - } - default: - throw new ParseError(`Unknown page member '${kw.value}' at offset ${kw.pos}`); - } - } - expect("rbrace"); - const declaredStates = new Set(states.map((state) => state.name)); - if (declaredStates.has("page")) { - throw new ParseError("State name 'page' collides with the WRN 'page' keyword; choose another state name", "WRN-STATE-RESERVED-NAME"); - } - for (const watcher of watches) { - if (!declaredStates.has(watcher.state)) { - throw new ParseError(`Cannot watch undeclared state '${watcher.state}'`); - } - } - for (const prop of props) { - const problem = (0, types_ts_1.validateTypedInitializer)(`Prop '${prop.name}'`, prop.valueType, prop.default); - if (problem) - throw new ParseError(problem); - } - for (const state of states) { - const problem = (0, types_ts_1.validateTypedInitializer)(`State '${state.name}'`, state.valueType, state.expr); - if (problem) - throw new ParseError(problem); - } - const symbols = new Set; - for (const declaration of [...props, ...states, ...computed]) { - if (symbols.has(declaration.name)) { - throw new ParseError(`Duplicate symbol '${declaration.name}'`, "WRN-SYMBOL-DUPLICATE"); - } - symbols.add(declaration.name); - } - const outputNames = new Set; - for (const output of outputs) { - if (outputNames.has(output.name)) - throw new ParseError(`Duplicate output '${output.name}'`, "WRN-OUTPUT-DUPLICATE"); - outputNames.add(output.name); - } - const functionKeys = new Set; - for (const fn of runtimeFunctions) { - const key = `${fn.runtime}:${fn.name}`; - if (functionKeys.has(key)) - throw new ParseError(`Duplicate ${fn.runtime} function '${fn.name}'`, "WRN-FUNCTION-DUPLICATE"); - functionKeys.add(key); - } - const namedLoads = new Map(loads.filter((load) => load.name).map((load) => [load.name, load])); - for (const load of namedLoads.values()) { - for (const dependency of load.dependsOn ?? []) { - const dependencyLoad = namedLoads.get(dependency); - if (!dependencyLoad) - throw new ParseError(`Load '${load.name}' depends on unknown load '${dependency}'`, "WRN-LOAD-DEPENDENCY"); - if (load.mode === "server" && !load.deferred && (dependencyLoad.mode !== "server" || dependencyLoad.deferred)) - throw new ParseError(`Server load '${load.name}' cannot depend on deferred/client load '${dependency}'`, "WRN-LOAD-PHASE"); - } - } - const visiting = new Set; - const visited = new Set; - const visitLoad = (name2) => { - if (visiting.has(name2)) - throw new ParseError(`Load dependency cycle includes '${name2}'`, "WRN-LOAD-CYCLE"); - if (visited.has(name2)) - return; - visiting.add(name2); - for (const dependency of namedLoads.get(name2)?.dependsOn ?? []) - visitLoad(dependency); - visiting.delete(name2); - visited.add(name2); - }; - for (const name2 of namedLoads.keys()) - visitLoad(name2); - return { - type: "page", - imports, - structuredImports: (0, v060_ts_1.parseStructuredImports)(imports), - kind, - storeKind, - name, - layout, - layoutIsSymbol, - runtime, - renderMode, - hydrate, - cache, - props, - events, - outputs, - types, - states, - computed, - effects, - loads, - actions, - security, - navigation, - seo, - view, - styles, - functions, - runtimeFunctions, - dataApis, - modeFunctions, - lifecycle, - storeLifecycle, - persist, - watches, - apis, - realtimes - }; - } catch (err) { - if (err instanceof tokenizer_ts_1.LexError) - throw new ParseError(err.message); - throw err; - } - } - function parseHtmlView(src, pos) { - let i = pos; - const isNameStart = (c) => /[A-Za-z_]/.test(c); - const isTagNamePart = (c) => /[A-Za-z0-9_$:.-]/.test(c); - const isWs = (c) => c === " " || c === "\t" || c === ` -` || c === "\r"; - const fail = (msg) => { - throw new ParseError(`${msg} at offset ${i}`); - }; - const skipWs = () => { - while (i < src.length && isWs(src[i])) - i++; - }; - const readInterpolation = () => { - const start = i; - let depth = 0; - let quote = null; - for (;i < src.length; i++) { - const char = src[i]; - if (quote) { - if (char === "\\" && i + 1 < src.length) { - i++; - continue; - } - if (char === quote) - quote = null; - continue; - } - if (char === '"' || char === "'" || char === "`") { - quote = char; - continue; - } - if (char === "{") - depth++; - else if (char === "}" && --depth === 0) { - i++; - return src.slice(start, i); - } - } - return fail("Unterminated `{` interpolation in view"); - }; - const readQuoted = () => { - const quote = src[i]; - if (quote !== '"' && quote !== "'") - return fail("Expected a quoted attribute value"); - i++; - const start = i; - while (i < src.length && src[i] !== quote) - i++; - if (i >= src.length) - return fail("Unterminated attribute value"); - const value = src.slice(start, i); - i++; - return value; - }; - const readTagName = () => { - if (i >= src.length || !isNameStart(src[i])) { - return fail("Expected a tag name"); - } - const start = i++; - while (i < src.length && isTagNamePart(src[i])) { - i++; - } - return src.slice(start, i); - }; - const readAttributeName = () => { - if (i >= src.length) { - return fail("Expected an attribute name"); - } - const start = i; - while (i < src.length) { - const char = src[i]; - const next = src[i + 1]; - if (char === "=" || char === ">" || char === '"' || char === "'" || char === " " || char === "\t" || char === ` -` || char === "\r" || char === "/" && next === ">") { - break; - } - i++; - } - if (i === start) { - return fail("Expected an attribute name"); - } - return src.slice(start, i); - }; - const parseTag = () => { - i++; - const tag = readTagName(); - const attrs = []; - for (;; ) { - skipWs(); - const c = src[i]; - if (c === undefined) - return fail(`Unterminated <${tag}> tag`); - if (c === ">") { - i++; - break; - } - if (c === "/" && src[i + 1] === ">") { - i += 2; - return { type: "element", tag, attrs, children: [] }; - } - if (c === "@") { - i++; - const name2 = readAttributeName(); - skipWs(); - if (src[i] !== "=") - return fail(`Expected '=' after @${name2}`); - i++; - skipWs(); - attrs.push({ name: name2, value: readQuoted(), event: true }); - continue; - } - const name = readAttributeName(); - skipWs(); - if (src[i] === "=") { - i++; - skipWs(); - const value = src[i] === '"' || src[i] === "'" ? readQuoted() : src[i] === "{" ? readInterpolation() : fail(`Expected a quoted value or {...} expression after '${name}='`); - attrs.push({ name, value, event: false }); - } else { - attrs.push({ name, value: "", event: false, boolean: true }); - } - } - if (exports3.VOID_ELEMENTS.has(tag.toLowerCase())) { - return { type: "element", tag, attrs, children: [] }; - } - const children = parseNodeList("element"); - if (src[i] !== "<" || src[i + 1] !== "/") - return fail(`Expected `); - i += 2; - skipWs(); - const close = readTagName(); - if (close !== tag) - return fail(`Mismatched , expected `); - skipWs(); - if (src[i] !== ">") - return fail(`Expected '>' to close `); - i++; - return { type: "element", tag, attrs, children }; - }; - const EACH_HEADER = /^\{#each\s+([\s\S]+?)\s+as\s+([A-Za-z_$][\w$]*)\s*(?:,\s*([A-Za-z_$][\w$]*))?(?:\s+key\s+([\s\S]+?))?\s*\}$/; - function parseEach() { - const header = readInterpolation(); - const m = EACH_HEADER.exec(header); - if (!m) - return fail(`Invalid {#each …} header: ${header}`); - const list = m[1].trim(); - const item = m[2]; - const index = m[3]; - const key = m[4]?.trim(); - const body = parseNodeList("each"); - let empty = []; - if (src.startsWith("{:empty}", i)) { - i += "{:empty}".length; - empty = parseNodeList("each"); - } - if (!src.startsWith("{/each}", i)) - return fail("Expected `{/each}` to close `{#each}`"); - i += "{/each}".length; - return { type: "each", list, item, index, key, body, empty }; - } - function parseIf() { - const header = readInterpolation(); - const m = /^\{#if\s+([\s\S]+?)\s*\}$/.exec(header); - if (!m) - return fail(`Invalid {#if …} header: ${header}`); - const branches = [ - { cond: m[1].trim(), body: parseNodeList("if") } - ]; - for (;; ) { - if (src.startsWith("{:else if", i)) { - const h = readInterpolation(); - const mm = /^\{:else if\s+([\s\S]+?)\s*\}$/.exec(h); - if (!mm) - return fail(`Invalid {:else if …}: ${h}`); - branches.push({ cond: mm[1].trim(), body: parseNodeList("if") }); - continue; - } - if (src.startsWith("{:else}", i)) { - i += "{:else}".length; - branches.push({ cond: null, body: parseNodeList("if") }); - continue; - } - break; - } - if (!src.startsWith("{/if}", i)) - return fail("Expected `{/if}` to close `{#if}`"); - i += "{/if}".length; - return { type: "if", branches }; - } - function parseNodeList(mode) { - const nodes2 = []; - let text = ""; - const flush = () => { - if (text.length > 0) { - nodes2.push({ type: "text", value: text }); - text = ""; - } - }; - for (;; ) { - if (i >= src.length) { - return mode === "root" ? fail("Unexpected end of view (missing `}`)") : fail("Unclosed block"); - } - const c = src[i]; - if (c === "<") { - const next = src[i + 1]; - if (next === "/") { - flush(); - break; - } - if (src.startsWith("", i + 4); - i = end === -1 ? src.length : end + 3; - continue; - } - if (next !== undefined && (isNameStart(next) || next === "!")) { - flush(); - nodes2.push(parseTag()); - continue; - } - text += c; - i++; - continue; - } - if (c === "{") { - if (src.startsWith("{#each", i)) { - flush(); - nodes2.push(parseEach()); - continue; - } - if (src.startsWith("{#if", i)) { - flush(); - nodes2.push(parseIf()); - continue; - } - if (mode === "each" && (src.startsWith("{:empty}", i) || src.startsWith("{/each}", i))) { - flush(); - break; - } - if (mode === "if" && (src.startsWith("{:else", i) || src.startsWith("{/if}", i))) { - flush(); - break; - } - text += readInterpolation(); - continue; - } - if (c === "}" && mode === "root") { - flush(); - break; - } - text += c; - i++; - } - return nodes2; - } - const nodes = parseNodeList("root"); - return { nodes, endPos: i }; - } - }, - "packages/syntax/src/spec.ts": function(module3, exports3, require2, __filename2, __dirname2) { - Object.defineProperty(exports3, "__esModule", { value: true }); - exports3.WRN_DIAGNOSTIC_CODES = exports3.WRN_RUNTIME_TARGETS = exports3.WRN_HYDRATION_STRATEGIES = exports3.WRN_ROOT_MEMBERS = exports3.WRN_ROOT_KINDS = exports3.WRN_LANGUAGE_VERSION = undefined; - exports3.WRN_LANGUAGE_VERSION = "0.6"; - exports3.WRN_ROOT_KINDS = [ - "page", - "component", - "layout", - "global-store", - "page-store" - ]; - exports3.WRN_ROOT_MEMBERS = [ - "layout", - "runtime", - "hydrate", - "client", - "types", - "props", - "outputs", - "state", - "shared", - "server", - "computed", - "effect", - "watch", - "lifecycle", - "view", - "seo", - "security", - "load", - "action", - "api", - "ssr", - "realtime", - "style", - "functions", - "persist" - ]; - exports3.WRN_HYDRATION_STRATEGIES = ["load", "idle", "visible", "interaction", "none"]; - exports3.WRN_RUNTIME_TARGETS = [ - "server", - "client", - "universal", - "edge", - "worker", - "service-worker" - ]; - exports3.WRN_DIAGNOSTIC_CODES = { - parse: "WRN-PARSE-001", - root: "WRN-PARSE-ROOT", - member: "WRN-PARSE-MEMBER", - propInitializer: "WRN-PROP-INITIALIZER", - stateInitializer: "WRN-STATE-INITIALIZER", - watchUndeclared: "WRN-WATCH-UNDECLARED", - duplicateSymbol: "WRN-SYMBOL-DUPLICATE", - invalidHydration: "WRN-HYDRATE-STRATEGY", - invalidRuntime: "WRN-RUNTIME-TARGET", - serverInteractive: "WRN-RUNTIME-SERVER-INTERACTIVE", - accessibility: "WRN-A11Y-001", - import: "WRN-IMPORT-001", - function: "WRN-FUNCTION-001", - client: "WRN-CLIENT-001", - server: "WRN-SERVER-001", - output: "WRN-OUTPUT-001", - type: "WRN-TYPE-001", - state: "WRN-STATE-001", - component: "WRN-COMPONENT-001", - template: "WRN-TEMPLATE-001", - store: "WRN-STORE-001", - persist: "WRN-PERSIST-001", - rpc: "WRN-RPC-001", - hydration: "WRN-HYDRATION-001", - migration: "WRN-MIGRATION-001" - }; - }, - "packages/syntax/src/tokenizer.ts": function(module3, exports3, require2, __filename2, __dirname2) { - Object.defineProperty(exports3, "__esModule", { value: true }); - exports3.Lexer = exports3.LexError = undefined; - - class LexError extends Error { - } - exports3.LexError = LexError; - const isWs = (c) => c === " " || c === "\t" || c === ` -` || c === "\r"; - const isIdentStart = (c) => /[A-Za-z_]/.test(c); - const isIdentPart = (c) => /[A-Za-z0-9_]/.test(c); - - class Lexer { - src; - pos = 0; - constructor(src) { - this.src = src; - } - skipTrivia() { - const { src } = this; - while (this.pos < src.length) { - const c = src[this.pos]; - if (isWs(c)) { - this.pos++; - continue; - } - if (c === "/" && src[this.pos + 1] === "/") { - while (this.pos < src.length && src[this.pos] !== ` -`) - this.pos++; - continue; - } - break; - } - } - startsWithBlockComment() { - this.skipTrivia(); - return this.src[this.pos] === "/" && this.src[this.pos + 1] === "*"; - } - next() { - this.skipTrivia(); - const { src } = this; - const pos = this.pos; - if (pos >= src.length) - return { type: "eof", value: "", pos }; - const c = src[pos]; - switch (c) { - case "{": - this.pos++; - return { type: "lbrace", value: c, pos }; - case "}": - this.pos++; - return { type: "rbrace", value: c, pos }; - case "(": - this.pos++; - return { type: "lparen", value: c, pos }; - case ")": - this.pos++; - return { type: "rparen", value: c, pos }; - case "@": - this.pos++; - return { type: "at", value: c, pos }; - case "=": - this.pos++; - return { type: "eq", value: c, pos }; - case ":": - this.pos++; - return { type: "colon", value: c, pos }; - case ",": - this.pos++; - return { type: "comma", value: c, pos }; - case "?": - this.pos++; - return { type: "question", value: c, pos }; - case '"': - case "'": - return this.readString(c, pos); - } - if (isIdentStart(c)) { - let v = ""; - while (this.pos < src.length && isIdentPart(src[this.pos])) - v += src[this.pos++]; - return { type: "ident", value: v, pos }; - } - throw new LexError(`Unexpected character '${c}' at offset ${pos} (line ${this.lineAt(pos)})`); - } - peek() { - const save = this.pos; - const t = this.next(); - this.pos = save; - return t; - } - readString(quote, pos) { - const { src } = this; - let v = ""; - this.pos++; - while (this.pos < src.length) { - const c = src[this.pos++]; - if (c === "\\") { - const n = src[this.pos++]; - v += n === "n" ? ` -` : n === "t" ? "\t" : n; - continue; - } - if (c === quote) - return { type: "string", value: v, pos }; - v += c; - } - throw new LexError(`Unterminated string at offset ${pos}`); - } - readPath() { - this.skipTrivia(); - const { src } = this; - let v = ""; - while (this.pos < src.length && !isWs(src[this.pos]) && src[this.pos] !== "{") { - v += src[this.pos++]; - } - if (!v) - throw new LexError(`Expected a path at offset ${this.pos}`); - return v; - } - readPropInitializer() { - const { src } = this; - while (this.pos < src.length && (src[this.pos] === " " || src[this.pos] === "\t")) { - this.pos++; - } - const start = this.pos; - let square = 0; - let brace = 0; - let paren = 0; - let angle = 0; - let quote = null; - const atTopLevel = () => square === 0 && brace === 0 && paren === 0 && angle === 0; - while (this.pos < src.length) { - const c = src[this.pos]; - if (quote) { - this.pos++; - if (c === "\\" && this.pos < src.length) { - this.pos++; - } else if (c === quote) { - quote = null; - } - continue; - } - if (c === '"' || c === "'" || c === "`") { - quote = c; - this.pos++; - continue; - } - if (atTopLevel()) { - if (c === ` -` || c === "\r" || c === "}") - break; - if (c === " " || c === "\t") { - let look = this.pos; - while (look < src.length && (src[look] === " " || src[look] === "\t")) - look++; - const rest = src.slice(look); - if (/^[A-Za-z_][A-Za-z0-9_]*(?:\s*:[^=\r\n{}]+)?\s*=/.test(rest)) - break; - } - } - if (c === "[") - square++; - else if (c === "]" && square > 0) - square--; - else if (c === "{") - brace++; - else if (c === "}" && brace > 0) - brace--; - else if (c === "(") - paren++; - else if (c === ")" && paren > 0) - paren--; - else if (c === "<") - angle++; - else if (c === ">" && angle > 0) - angle--; - this.pos++; - } - const value = src.slice(start, this.pos).trim(); - if (!value) - throw new LexError(`Expected a prop initializer at offset ${start}`); - return value; - } - readToLineEnd() { - const { src } = this; - let v = ""; - while (this.pos < src.length && src[this.pos] !== ` -`) - v += src[this.pos++]; - return v.trim(); - } - readTypeAnnotation() { - const { src } = this; - let value = ""; - let angle = 0; - let square = 0; - let brace = 0; - let paren = 0; - let quote = null; - while (this.pos < src.length) { - const c = src[this.pos]; - if (quote) { - value += c; - this.pos++; - if (c === "\\" && this.pos < src.length) - value += src[this.pos++]; - else if (c === quote) - quote = null; - continue; - } - if (c === '"' || c === "'" || c === "`") { - quote = c; - value += c; - this.pos++; - continue; - } - if (c === "}" && angle === 0 && square === 0 && brace === 0 && paren === 0) - break; - if (c === "<") - angle++; - else if (c === ">" && angle > 0) - angle--; - else if (c === "[") - square++; - else if (c === "]" && square > 0) - square--; - else if (c === "{") - brace++; - else if (c === "}" && brace > 0) - brace--; - else if (c === "(") - paren++; - else if (c === ")" && paren > 0) - paren--; - if (angle === 0 && square === 0 && brace === 0 && paren === 0) { - if (c === " " || c === "\t") { - let look = this.pos; - while (look < src.length && (src[look] === " " || src[look] === "\t")) - look++; - if (/^[A-Za-z_][A-Za-z0-9_]*\??\s*:/.test(src.slice(look))) - break; - } - if (c === "=") { - this.pos++; - const type2 = value.trim(); - if (!type2) - throw new LexError(`Expected a type annotation at offset ${this.pos}`); - return { type: type2, hasDefault: true }; - } - if (c === ` -` || c === "\r") - break; - } - value += c; - this.pos++; - } - const type = value.trim(); - if (!type) - throw new LexError(`Expected a type annotation at offset ${this.pos}`); - return { type, hasDefault: false }; - } - readBalancedBraces() { - this.skipTrivia(); - const { src } = this; - if (src[this.pos] !== "{") { - throw new LexError(`Expected '{' at offset ${this.pos}`); - } - const start = this.pos + 1; - let depth = 0; - let i = this.pos; - let str = null; - let atLineStart = false; - for (;i < src.length; i++) { - const c = src[i]; - if (str) { - if (c === "\\") { - i++; - continue; - } - if (c === str) - str = null; - continue; - } - if (c === ` -`) { - atLineStart = true; - continue; - } - if (c === "/" && src[i + 1] === "*") { - const close = src.indexOf("*/", i + 2); - if (close === -1) - break; - i = close + 1; - atLineStart = false; - continue; - } - if (atLineStart && c === "/" && src[i + 1] === "/") { - const newline = src.indexOf(` -`, i + 2); - if (newline === -1) - break; - i = newline - 1; - continue; - } - if (c !== " " && c !== "\t" && c !== "\r") - atLineStart = false; - if (c === '"' || c === "'" || c === "`") { - str = c; - continue; - } - if (c === "{") - depth++; - else if (c === "}") { - depth--; - if (depth === 0) { - this.pos = i + 1; - return src.slice(start, i); - } - } - } - throw new LexError(`Unbalanced braces starting at offset ${this.pos}`); - } - lineAt(pos) { - let line = 1; - for (let i = 0;i < pos && i < this.src.length; i++) { - if (this.src[i] === ` -`) - line++; - } - return line; - } - } - exports3.Lexer = Lexer; - }, - "packages/syntax/src/types.ts": function(module3, exports3, require2, __filename2, __dirname2) { - Object.defineProperty(exports3, "__esModule", { value: true }); - exports3.runtimeTypeOf = runtimeTypeOf; - exports3.inferredRuntimeType = inferredRuntimeType; - exports3.validateTypedInitializer = validateTypedInitializer; - exports3.eraseFunctionTypes = eraseFunctionTypes; - function runtimeTypeOf(annotation) { - if (!annotation) - return "unknown"; - const type = annotation.trim().replace(/^readonly\s+/, ""); - const unionParts = type.split("|").map((part) => part.trim()); - const concreteParts = unionParts.filter((part) => !/^(?:null|undefined)$/.test(part)); - if (/^(?:string|String)(?:\s*\|\s*(?:null|undefined))*$/.test(type) || concreteParts.length > 0 && concreteParts.every((part) => /^(?:"[^"]*"|'[^']*')$/.test(part))) - return "string"; - if (/^(?:number|Number)(?:\s*\|\s*(?:null|undefined))*$/.test(type) || concreteParts.length > 0 && concreteParts.every((part) => /^-?(?:\d+\.?\d*|\.\d+)$/.test(part))) - return "number"; - if (/^(?:boolean|Boolean)(?:\s*\|\s*(?:null|undefined))*$/.test(type) || concreteParts.length > 0 && concreteParts.every((part) => /^(?:true|false)$/.test(part))) - return "boolean"; - if (/^bigint(?:\s*\|\s*(?:null|undefined))*$/.test(type)) - return "bigint"; - if (/^(?:Array\s*<|ReadonlyArray\s*<|.+\[\])/.test(type) || /^\[/.test(type)) - return "array"; - if (/^(?:Record\s*<|object\b|\{)/.test(type)) - return "object"; - if (/=>|^(?:Function|\([^)]*\)\s*=>)/.test(type)) - return "function"; - return "unknown"; - } - function inferredRuntimeType(expression) { - const value = expression.trim(); - if (/^["'`]/.test(value)) - return "string"; - if (/^-?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?$/i.test(value)) - return "number"; - if (/^(?:true|false)$/.test(value)) - return "boolean"; - if (/^-?\d+n$/.test(value)) - return "bigint"; - if (value.startsWith("[")) - return "array"; - if (value.startsWith("{") || /^new\s+(?:Map|Set|Date)\b/.test(value)) - return "object"; - if (/^(?:async\s+)?(?:function\b|\([^)]*\)\s*=>|[A-Za-z_$][\w$]*\s*=>)/.test(value)) { - return "function"; - } - return "unknown"; - } - function validateTypedInitializer(name, annotation, expression) { - if (!annotation || expression.trim() === "undefined" || expression.trim() === "null") - return null; - const expected = runtimeTypeOf(annotation); - const actual = inferredRuntimeType(expression); - if (expected === "unknown" || actual === "unknown" || expected === actual) - return null; - return `${name} is declared as ${annotation}, but its initializer is ${actual}`; - } - function eraseFunctionTypes(source) { - return source.replace(/(\b(?:async\s+)?function\s+[A-Za-z_$][\w$]*\s*\()([^)]*)(\)\s*)(?::\s*([^{}=>]+)\s*)?(\{)/g, (_whole, open, params, close, _returnType, brace) => { - const plainParams = params.split(",").map((param) => param.replace(/([A-Za-z_$][\w$]*)(\?)?\s*:\s*([^=]+?)(?=\s*=|$)/, "$1").trim()).join(", "); - return `${open}${plainParams}${close}${brace}`; - }); - } - }, - "packages/syntax/src/v060.ts": function(module3, exports3, require2, __filename2, __dirname2) { - Object.defineProperty(exports3, "__esModule", { value: true }); - exports3.parseRuntimeFunctions = parseRuntimeFunctions; - exports3.stripRuntimeFunctionModifiers = stripRuntimeFunctionModifiers; - exports3.parseOutputs = parseOutputs; - exports3.parseStructuredImports = parseStructuredImports; - exports3.parseStateDeclarations = parseStateDeclarations; - exports3.parseComputedDeclarations = parseComputedDeclarations; - exports3.parsePersist = parsePersist; - exports3.parseStoreLifecycle = parseStoreLifecycle; - const tokenizer_ts_1 = require2("./tokenizer.js"); - function splitTopLevel(input, separator = ",") { - const parts = []; - let start = 0; - let quote = null; - let angle = 0; - let square = 0; - let brace = 0; - let paren = 0; - for (let i = 0;i < input.length; i++) { - const c = input[i]; - if (quote) { - if (c === "\\") - i++; - else if (c === quote) - quote = null; - continue; - } - if (c === '"' || c === "'" || c === "`") { - quote = c; - continue; - } - if (c === "<") - angle++; - else if (c === ">" && angle > 0) - angle--; - else if (c === "[") - square++; - else if (c === "]" && square > 0) - square--; - else if (c === "{") - brace++; - else if (c === "}" && brace > 0) - brace--; - else if (c === "(") - paren++; - else if (c === ")" && paren > 0) - paren--; - else if (c === separator && angle === 0 && square === 0 && brace === 0 && paren === 0) { - parts.push(input.slice(start, i).trim()); - start = i + 1; - } - } - const tail = input.slice(start).trim(); - if (tail) - parts.push(tail); - return parts; - } - function findTopLevelChar(input, wanted) { - let quote = null; - let angle = 0; - let square = 0; - let brace = 0; - let paren = 0; - for (let i = 0;i < input.length; i++) { - const c = input[i]; - if (quote) { - if (c === "\\") - i++; - else if (c === quote) - quote = null; - continue; - } - if (c === '"' || c === "'" || c === "`") { - quote = c; - continue; - } - if (c === "<") - angle++; - else if (c === ">" && angle > 0) - angle--; - else if (c === "[") - square++; - else if (c === "]" && square > 0) - square--; - else if (c === "{") - brace++; - else if (c === "}" && brace > 0) - brace--; - else if (c === "(") - paren++; - else if (c === ")" && paren > 0) - paren--; - if (c === wanted && angle === 0 && square === 0 && brace === 0 && paren === 0) - return i; - } - return -1; - } - function parseParameters(source) { - return splitTopLevel(source).filter(Boolean).map((entry) => { - const eq = findTopLevelChar(entry, "="); - const declaration = (eq >= 0 ? entry.slice(0, eq) : entry).trim(); - const defaultValue = eq >= 0 ? entry.slice(eq + 1).trim() : undefined; - const colon = findTopLevelChar(declaration, ":"); - const rawName = (colon >= 0 ? declaration.slice(0, colon) : declaration).trim(); - const optional = rawName.endsWith("?"); - const name = optional ? rawName.slice(0, -1).trim() : rawName; - const valueType = colon >= 0 ? declaration.slice(colon + 1).trim() : undefined; - return { - name, - optional, - ...valueType ? { valueType } : {}, - ...defaultValue ? { default: defaultValue } : {} - }; - }); - } - function skipTrivia(source, start) { - let i = start; - while (i < source.length) { - if (/\s/.test(source[i])) { - i++; - continue; - } - if (source.startsWith("//", i)) { - const end = source.indexOf(` -`, i + 2); - i = end < 0 ? source.length : end + 1; - continue; - } - if (source.startsWith("/*", i)) { - const end = source.indexOf("*/", i + 2); - i = end < 0 ? source.length : end + 2; - continue; - } - break; - } - return i; - } - function readWord(source, start) { - const match = /^[A-Za-z_$][\w$]*/.exec(source.slice(start)); - return match ? { word: match[0], end: start + match[0].length } : null; - } - function readBalanced(source, start, open, close) { - if (source[start] !== open) - throw new Error(`Expected '${open}' at offset ${start}`); - let depth = 0; - let quote = null; - for (let i = start;i < source.length; i++) { - const c = source[i]; - if (quote) { - if (c === "\\") - i++; - else if (c === quote) - quote = null; - continue; - } - if (c === '"' || c === "'" || c === "`") { - quote = c; - continue; - } - if (c === open) - depth++; - else if (c === close && --depth === 0) - return { inner: source.slice(start + 1, i), end: i + 1 }; - } - throw new Error(`Unbalanced '${open}${close}' starting at offset ${start}`); - } - function parseRuntimeFunctions(source) { - const declarations = []; - let i = 0; - while (i < source.length) { - i = skipTrivia(source, i); - const start = i; - let token = readWord(source, i); - if (!token) { - i++; - continue; - } - let runtime = "legacy"; - if (["client", "server", "shared"].includes(token.word)) { - runtime = token.word; - i = skipTrivia(source, token.end); - token = readWord(source, i); - if (!token) - continue; - } - let isAsync = false; - if (token.word === "async") { - isAsync = true; - i = skipTrivia(source, token.end); - token = readWord(source, i); - if (!token) - continue; - } - if (token.word !== "function") { - i = token.end; - continue; - } - i = skipTrivia(source, token.end); - const nameToken = readWord(source, i); - if (!nameToken) - throw new Error(`Expected function name at offset ${i}`); - const name = nameToken.word; - i = skipTrivia(source, nameToken.end); - const params = readBalanced(source, i, "(", ")"); - i = skipTrivia(source, params.end); - let returnType; - if (source[i] === ":") { - i++; - const typeStart = i; - let quote = null; - let angle = 0; - let square = 0; - let paren = 0; - while (i < source.length) { - const c = source[i]; - if (quote) { - if (c === "\\") - i++; - else if (c === quote) - quote = null; - i++; - continue; - } - if (c === '"' || c === "'" || c === "`") - quote = c; - else if (c === "<") - angle++; - else if (c === ">" && angle > 0) - angle--; - else if (c === "[") - square++; - else if (c === "]" && square > 0) - square--; - else if (c === "(") - paren++; - else if (c === ")" && paren > 0) - paren--; - else if (c === "{" && angle === 0 && square === 0 && paren === 0) - break; - i++; - } - returnType = source.slice(typeStart, i).trim(); - } - i = skipTrivia(source, i); - const body = readBalanced(source, i, "{", "}"); - i = body.end; - declarations.push({ - name, - runtime, - async: isAsync, - parameters: parseParameters(params.inner), - ...returnType ? { returnType } : {}, - body: body.inner, - source: source.slice(start, body.end).trim() - }); - } - return declarations; - } - function stripRuntimeFunctionModifiers(source, include) { - const allowed = new Set(include); - return parseRuntimeFunctions(source).filter((entry) => allowed.has(entry.runtime)).map((entry) => { - const params = entry.parameters.map((param) => `${param.name}${param.optional ? "?" : ""}${param.valueType ? `: ${param.valueType}` : ""}${param.default ? ` = ${param.default}` : ""}`).join(", "); - return `${entry.async ? "async " : ""}function ${entry.name}(${params})${entry.returnType ? `: ${entry.returnType}` : ""} {${entry.body}}`; - }).join(` - -`); - } - function parseOutputs(source) { - const out = []; - let i = 0; - while (i < source.length) { - i = skipTrivia(source, i); - if (i >= source.length) - break; - const nameToken = readWord(source, i); - if (!nameToken) - throw new Error(`Expected output name at offset ${i}`); - i = skipTrivia(source, nameToken.end); - const args = readBalanced(source, i, "(", ")"); - i = args.end; - const parameters = parseParameters(args.inner); - if (parameters.length > 1) - throw new Error(`Output '${nameToken.word}' accepts zero or one payload`); - const payload = parameters[0]; - if (payload && !payload.valueType) - throw new Error(`Output '${nameToken.word}' payload requires a type`); - out.push({ - name: nameToken.word, - ...payload ? { - payload: { - name: payload.name, - valueType: payload.valueType, - optional: payload.optional - } - } : {} - }); - } - return out; - } - function parseStructuredImports(imports) { - return imports.map((raw) => { - const sourceMatch = /\sfrom\s+["']([^"']+)["']|^import\s+["']([^"']+)["']/.exec(raw); - const source = sourceMatch?.[1] ?? sourceMatch?.[2] ?? ""; - const typeOnly = /^import\s+type\b/.test(raw); - const clause = raw.replace(/^import\s+(?:type\s+)?/, "").replace(/\s+from\s+["'][^"']+["']\s*;?$/, "").trim(); - const declaration = { source, typeOnly, namedImports: [], raw }; - if (!clause || clause.startsWith('"') || clause.startsWith("'")) - return declaration; - if (clause.startsWith("*")) { - declaration.namespaceImport = /\*\s+as\s+([A-Za-z_$][\w$]*)/.exec(clause)?.[1]; - return declaration; - } - let rest = clause; - if (!rest.startsWith("{")) { - const comma = findTopLevelChar(rest, ","); - declaration.defaultImport = (comma < 0 ? rest : rest.slice(0, comma)).trim(); - rest = comma < 0 ? "" : rest.slice(comma + 1).trim(); - } - const named = /^\{([\s\S]*)\}$/.exec(rest)?.[1]; - if (named !== undefined) { - declaration.namedImports = splitTopLevel(named).map((item) => { - const localTypeOnly = /^type\s+/.test(item); - const cleaned = item.replace(/^type\s+/, "").trim(); - const [imported, local] = cleaned.split(/\s+as\s+/); - return { - imported: imported.trim(), - local: (local ?? imported).trim(), - typeOnly: typeOnly || localTypeOnly - }; - }); - } - return declaration; - }); - } - function parseStateDeclarations(source, runtime) { - const lx = new tokenizer_ts_1.Lexer(source); - const out = []; - while (lx.peek().type !== "eof") { - const nameToken = lx.next(); - if (nameToken.type !== "ident") - throw new tokenizer_ts_1.LexError(`Expected a state name at offset ${nameToken.pos}`); - let valueType; - if (lx.peek().type === "colon") { - lx.next(); - const annotation = lx.readTypeAnnotation(); - valueType = annotation.type; - if (!annotation.hasDefault) - throw new tokenizer_ts_1.LexError(`State '${nameToken.value}' requires an initializer`); - } else { - const eq = lx.next(); - if (eq.type !== "eq") - throw new tokenizer_ts_1.LexError(`Expected '=' after state '${nameToken.value}' at offset ${eq.pos}`); - } - out.push({ - name: nameToken.value, - ...valueType ? { valueType } : {}, - expr: lx.readPropInitializer(), - runtime - }); - } - return out; - } - function parseComputedDeclarations(source) { - const lx = new tokenizer_ts_1.Lexer(source); - const out = []; - while (lx.peek().type !== "eof") { - const nameToken = lx.next(); - if (nameToken.type !== "ident") - throw new tokenizer_ts_1.LexError(`Expected a computed name at offset ${nameToken.pos}`); - let valueType; - if (lx.peek().type === "colon") { - lx.next(); - const annotation = lx.readTypeAnnotation(); - valueType = annotation.type; - if (!annotation.hasDefault) - throw new tokenizer_ts_1.LexError(`Computed '${nameToken.value}' requires an expression`); - } else { - const eq = lx.next(); - if (eq.type !== "eq") - throw new tokenizer_ts_1.LexError(`Expected '=' after computed '${nameToken.value}' at offset ${eq.pos}`); - } - out.push({ - name: nameToken.value, - ...valueType ? { valueType } : {}, - expr: lx.readPropInitializer() - }); - } - return out; - } - function nestedBlock(source, name) { - const match = new RegExp(`\\b${name}\\s*\\{`).exec(source); - if (!match) - return; - const brace = source.indexOf("{", match.index); - return readBalanced(source, brace, "{", "}").inner.trim() || undefined; - } - function parsePersist(source) { - const storage = /\bstorage\s*=\s*["'](memory|session|local)["']/.exec(source)?.[1]; - const includeRaw = /\binclude\s*=\s*\[([\s\S]*?)\]/.exec(source)?.[1] ?? ""; - const include = Array.from(includeRaw.matchAll(/["']([^"']+)["']/g), (match) => match[1]); - const version = Number(/\bversion\s*=\s*(\d+)/.exec(source)?.[1] ?? "1"); - const migrations = nestedBlock(source, "migrations"); - const validation = nestedBlock(source, "validate"); - return { - storage: storage ?? "memory", - include, - version, - ...migrations ? { migrations } : {}, - ...validation ? { validation } : {} - }; - } - function parseStoreLifecycle(source) { - const out = {}; - for (const hook of ["serverInit", "clientInit", "hydrate", "dispose"]) { - const start = new RegExp(`\\b${hook}\\s*\\{`).exec(source); - if (!start) - continue; - const brace = source.indexOf("{", start.index); - out[hook] = readBalanced(source, brace, "{", "}").inner; - } - return out; - } - }, - "packages/syntax/src/versioning.ts": function(module3, exports3, require2, __filename2, __dirname2) { - Object.defineProperty(exports3, "__esModule", { value: true }); - exports3.WRN_SYNTAX_FEATURES = exports3.WRN_SYNTAX_VERSION = undefined; - exports3.createSourceRange = createSourceRange; - exports3.sliceSource = sliceSource; - exports3.diagnosticSummary = diagnosticSummary; - exports3.supportsSyntaxFeature = supportsSyntaxFeature; - exports3.WRN_SYNTAX_VERSION = "0.4"; - exports3.WRN_SYNTAX_FEATURES = Object.freeze({ - "typed-declarations": true, - layouts: true, - "server-client-blocks": true, - effects: true, - watch: true, - lifecycle: true, - "embedded-api": true, - realtime: true, - "runtime-markers": true - }); - function createSourceRange(start, end) { - if (!Number.isInteger(start) || !Number.isInteger(end) || start < 0 || end < start) { - throw new RangeError(`Invalid source range ${start}..${end}`); - } - return { start, end }; - } - function sliceSource(source, range) { - return source.slice(range.start, range.end); - } - function diagnosticSummary(diagnostics) { - const summary = { errors: 0, warnings: 0, info: 0, codes: {} }; - for (const diagnostic of diagnostics) { - if (diagnostic.severity === "error") - summary.errors++; - else if (diagnostic.severity === "warning") - summary.warnings++; - else - summary.info++; - summary.codes[diagnostic.code] = (summary.codes[diagnostic.code] ?? 0) + 1; - } - return summary; - } - function supportsSyntaxFeature(feature) { - return Object.prototype.hasOwnProperty.call(exports3.WRN_SYNTAX_FEATURES, feature); - } - } - }; - var __aliases = { - "@wrnexus/syntax": "packages/syntax/src/index.ts", - "@wrnexus/syntax/parser": "packages/syntax/src/parser.ts", - "@wrnexus/syntax/tokenizer": "packages/syntax/src/tokenizer.ts", - "@wrnexus/syntax/types": "packages/syntax/src/types.ts", - "@wrnexus/syntax/diagnostics": "packages/syntax/src/diagnostics.ts", - "@wrnexus/syntax/spec": "packages/syntax/src/spec.ts" - }; - var __cache = Object.create(null); - function __normalize(id) { - const normalized = id.split("\\").join("/"); - return normalized.startsWith("./") ? normalized.slice(2) : normalized; - } - function __resolve(request, parent) { - if (__aliases[request]) - return __aliases[request]; - if (!request.startsWith(".")) - return null; - const base = __normalize(__path.posix.join(__path.posix.dirname(parent), request)); - const candidates = [ - base, - base.endsWith(".js") ? base.slice(0, -3) + ".ts" : base, - base.endsWith(".ts") ? base : base + ".ts", - (base.endsWith("/") ? base.slice(0, -1) : base) + "/index.ts" - ]; - for (const candidate of candidates) { - if (__modules[candidate]) - return candidate; - } - return null; - } - function __load(id) { - if (__cache[id]) - return __cache[id].exports; - const factory = __modules[id]; - if (!factory) - throw new Error("WRN editor compiler module not found: " + id); - const module3 = { exports: {} }; - __cache[id] = module3; - const localRequire = (request) => { - const resolved = __resolve(request, id); - return resolved ? __load(resolved) : __nodeRequire(request); - }; - factory(module3, module3.exports, localRequire, id, __path.posix.dirname(id)); - return module3.exports; - } - module2.exports = __load("packages/compiler/src/index.ts"); -}); - -// editors/vscode/src/formatter.js -var require_formatter = __commonJS((exports2, module2) => { - var { formatWrn } = require_compiler(); - module2.exports = { formatWrn }; -}); - -// editors/vscode/src/completion.js -var require_completion2 = __commonJS((exports2, module2) => { - var vscode = require("vscode"); - var BLOCK_COMPLETIONS = [ - { - label: "import", - detail: "WRN server module import", - documentation: "Import a package or module before the page, component, or layout declaration for server rendering.", - snippet: 'import { ${1:appUrl} } from "${2:@wrnexus/helpers}";\n\n$0' - }, - { - label: "page", - detail: "WRN page", - documentation: "Create a WRN page declaration.", - snippet: [ - "page ${1:PageName} {", - ' layout = "${2:default}"', - "", - " view {", - " $0", - " }", - "}" - ].join(` -`) - }, - { - label: "component", - detail: "WRN component", - documentation: "Create a reusable WRN component with state, functions, lifecycle hooks, watchers, and a view.", - snippet: [ - "component ${1:ComponentName} {", - " state ${2:ready} = false", - "", - " functions {", - " function ${3:initialize}() {", - " $0", - " }", - " }", - "", - " lifecycle {", - " mount {", - " ${3:initialize}()", - " }", - " }", - "", - " view {", - "
", - " }", - "}" - ].join(` -`) - }, - { - label: "layout", - detail: "WRN layout", - documentation: "Create a reusable WRN layout.", - snippet: [ - "layout ${1:LayoutName} {", - " view {", - "
", - " {content}", - "
", - " }", - "}" - ].join(` -`) - }, - { - label: "types", - detail: "TypeScript type declarations block", - documentation: "Declare interfaces and type aliases used by props, state, and functions.", - snippet: [ - "types {", - " interface ${1:Item} {", - " ${2:id}: ${3:string}", - " }", - "}" - ].join(` -`) - }, - { - label: "props", - detail: "Component or layout props block", - documentation: "Declare values accepted by a component or layout.", - snippet: ["props {", ' ${1:title}: ${2:string} = "${3:Title}"', "}"].join(` -`) - }, - { - label: "seo", - detail: "SEO metadata block", - documentation: "Declare title, description, and other page metadata.", - snippet: [ - "seo {", - ' title = "${1:Page title}"', - ' description = "${2:Page description}"', - "}" - ].join(` -`) - }, - { - label: "view", - detail: "WRN view block", - documentation: "Declare the HTML view rendered by the page, component, or layout.", - snippet: ["view {", " $0", "}"].join(` -`) - }, - { - label: "state", - detail: "Reactive state declaration", - documentation: "Declare reactive state owned by the current component, layout, or page.", - snippet: 'state ${1:name}: ${2:string} = ${3:"value"}' - }, - { - label: "runtime", - detail: "WRN execution target", - documentation: "Choose whether this declaration executes on the server, client, or both.", - snippet: 'runtime = "${1|universal,server,client|}"' - }, - { - label: "hydrate", - detail: "WRN hydration strategy", - documentation: "Choose when client interactivity is initialized for this declaration.", - snippet: 'hydrate = "${1|load,idle,visible,interaction,none|}"' - }, - { - label: "computed", - detail: "Derived reactive values block", - documentation: "Declare values that are recomputed only when their reactive dependencies change.", - snippet: ["computed {", " ${1:displayName} = ${2:firstName + ' ' + lastName}", "}"].join(` -`) - }, - { - label: "effect", - detail: "Reactive side-effect block", - documentation: "Run browser-side code whenever its referenced reactive values change.", - snippet: ["effect {", " ${1:console.log(value)}", "}"].join(` -`) - }, - { - label: "security", - detail: "Route security metadata block", - documentation: "Declare authentication, authorization, CSRF, and rate-limit policy metadata.", - snippet: [ - "security {", - ' auth = "${1|required,optional,public|}"', - ' csrf = "${2:true}"', - "}" - ].join(` -`) - }, - { - label: "load", - detail: "Typed data-loading block", - documentation: "Load data on the server or client with an explicit execution boundary.", - snippet: ["load ${1|server,client|} {", " ${2:return {}}", "}"].join(` -`) - }, - { - label: "action", - detail: "Named server action", - documentation: "Declare a callable mutation with an explicit name and arguments.", - snippet: ["action ${1:save}(${2:input}) {", " $0", "}"].join(` -`) - }, - { - label: "functions", - detail: "Browser component functions block", - documentation: "Declare functions that can be called from events, lifecycle hooks, and watchers.", - snippet: [ - "functions {", - " function ${1:handler}(${2:value}: ${3:unknown}): ${4:void} {", - " $0", - " }", - "}" - ].join(` -`) - }, - { - label: "function", - detail: "WRN component function", - documentation: "Declare a browser-side function inside a functions block.", - snippet: ["function ${1:name}(${2:value}: ${3:unknown}): ${4:void} {", " $0", "}"].join(` -`) - }, - { - label: "lifecycle", - detail: "Component lifecycle block", - documentation: "Declare mount, update, and unmount hooks for a component or layout.", - snippet: [ - "lifecycle {", - " mount {", - " $1", - " }", - "", - " update {", - " $2", - " }", - "", - " unmount {", - " $0", - " }", - "}" - ].join(` -`) - }, - { - label: "mount", - detail: "Lifecycle mount hook", - documentation: "Runs once after the component is connected and hydrated.", - snippet: ["mount {", " $0", "}"].join(` -`) - }, - { - label: "update", - detail: "Lifecycle update hook", - documentation: "Runs once after a batch of reactive state changes.", - snippet: ["update {", " $0", "}"].join(` -`) - }, - { - label: "unmount", - detail: "Lifecycle unmount hook", - documentation: "Runs before the component is removed. Use it to remove global listeners and release resources.", - snippet: ["unmount {", " $0", "}"].join(` -`) - }, - { - label: "watch", - detail: "Reactive state watcher", - documentation: "Run code when one declared state value changes. The watcher receives `value` and `previous`.", - snippet: ["watch ${1:stateName} {", " console.log(value, previous)", " $0", "}"].join(` -`) - }, - { - label: "style", - detail: "Scoped style block", - documentation: "Declare styles for the current WRN declaration.", - snippet: ["style {", " $0", "}"].join(` -`) - }, - { - label: "import type", - detail: "WRN v0.6 TypeScript type import", - snippet: 'import type { ${1:PublicUser} } from "${2:@/types/user.ts}"\n\n$0' - }, - { - label: "outputs", - detail: "Typed callable component outputs", - snippet: [ - "outputs {", - " ${1:confirm}(payload: ${2:ConfirmPayload})", - " ${3:cancel}()", - "}" - ].join(` -`) - }, - { - label: "client function", - detail: "Browser-only typed function", - snippet: ["client ${1|,async |}function ${2:name}(${3}): ${4:void} {", " $0", "}"].join(` -`) - }, - { - label: "server function", - detail: "Server-only typed function", - snippet: ["server ${1|,async |}function ${2:name}(${3}): ${4:void} {", " $0", "}"].join(` -`) - }, - { - label: "shared function", - detail: "Server-and-browser typed function", - snippet: ["shared function ${1:name}(${2}): ${3:void} {", " $0", "}"].join(` -`) - }, - { - label: "state block", - detail: "Grouped typed reactive state", - snippet: ["state {", ' ${1:name}: ${2:string} = ${3:""}', "}"].join(` -`) - }, - { - label: "client state", - detail: "Browser-only state block", - snippet: ["client state {", " ${1:menuOpen}: boolean = false", "}"].join(` -`) - }, - { - label: "server state", - detail: "Server-only state block", - snippet: ["server state {", " ${1:sessionId}: string | null = null", "}"].join(` -`) - }, - { - label: "global store", - detail: "Request-scoped global store", - snippet: [ - "global store ${1:UserStore} {", - " state {", - " ${2:user}: ${3:PublicUser | null} = null", - " }", - "", - " computed {", - " ${4:authenticated}: boolean = ${2:user} !== null", - " }", - "}" - ].join(` -`) - }, - { - label: "page store", - detail: "Route-scoped page store", - snippet: [ - "page store ${1:PageStore} {", - " state {", - " ${2:loading}: boolean = false", - " }", - "}" - ].join(` -`) - }, - { - label: "persist", - detail: "Include-only store persistence", - snippet: [ - "persist {", - ' storage = "${1|memory,session,local|}"', - ' include = ["${2:field}"]', - " version = ${3:1}", - "}" - ].join(` -`) - } - ]; - var ATTRIBUTE_COMPLETIONS = [ - ["@click", "Click event handler", '@click="${1:handler()}"'], - ["@change", "Change event handler", '@change="${1:handler()}"'], - ["@input", "Input event handler", '@input="${1:handler()}"'], - ["@submit", "Submit event handler", '@submit="${1:handler()}"'], - ["@focus", "Focus event handler", '@focus="${1:handler()}"'], - ["@blur", "Blur event handler", '@blur="${1:handler()}"'], - ["@keydown", "Keyboard key-down event handler", '@keydown="${1:handler()}"'], - ["@keyup", "Keyboard key-up event handler", '@keyup="${1:handler()}"'], - ["@mouseenter", "Pointer enter event handler", '@mouseenter="${1:handler()}"'], - ["@mouseleave", "Pointer leave event handler", '@mouseleave="${1:handler()}"'], - ["@window:scroll", "Window scroll event handler", '@window:scroll="${1:handler()}"'], - ["@window:resize", "Window resize event handler", '@window:resize="${1:handler()}"'], - ["@window:keydown", "Window key-down event handler", '@window:keydown="${1:handler()}"'], - ["@document:click", "Document click event handler", '@document:click="${1:handler()}"'], - [ - "@document:visibilitychange", - "Document visibility-change event handler", - '@document:visibilitychange="${1:handler()}"' - ], - ["data-show", "Conditional visibility", 'data-show="${1:condition}"'], - ["data-for", "Reactive loop", 'data-for="${1:item} in ${2:items}"'], - ["class:", "Conditional CSS class", 'class:${1:border-indigo-500}="${2:condition}"'] - ]; - var CONTEXT_COMPLETIONS = [ - ["ctx.params", "Dynamic route parameters"], - ["ctx.query", "URL query parameters"], - ["ctx.request", "Current Request object"], - ["ctx.user", "Authenticated user"], - ["ctx.session", "Current session"], - ["ctx.locals", "Request-local data"], - ["ctx.tenant", "Resolved tenant context"], - ["ctx.tracer", "Request tracing interface"] - ]; - var WATCH_VALUE_COMPLETIONS = [ - ["value", "Current watcher state value"], - ["previous", "Previous watcher state value"] - ]; - function completionKindFor(label) { - if (label.startsWith("@")) { - return vscode.CompletionItemKind.Event; - } - if (label.startsWith("class:") || label.startsWith("data-")) { - return vscode.CompletionItemKind.Property; - } - if (label === "function" || label === "functions") { - return vscode.CompletionItemKind.Function; - } - return vscode.CompletionItemKind.Keyword; - } - function createCompletion(label, detail, snippet, documentation) { - const item = new vscode.CompletionItem(label, completionKindFor(label)); - item.detail = detail; - item.insertText = new vscode.SnippetString(snippet || label); - item.documentation = new vscode.MarkdownString(documentation || detail); - return item; - } - function createVariableCompletion(label, detail, insertion = label) { - const item = new vscode.CompletionItem(label, vscode.CompletionItemKind.Variable); - item.detail = detail; - item.insertText = insertion; - return item; - } - function createFunctionCompletion(name, parameters = []) { - const item = new vscode.CompletionItem(name, vscode.CompletionItemKind.Function); - item.detail = `WRN component function${parameters.length > 0 ? ` (${parameters.join(", ")})` : ""}`; - item.documentation = new vscode.MarkdownString(`Call the browser function \`${name}\` declared in the current \`functions { ... }\` block.`); - const placeholders = parameters.map((parameter, index) => `\${${index + 1}:${parameter}}`); - item.insertText = new vscode.SnippetString(`${name}(${placeholders.join(", ")})`); - return item; - } - function getCurrentOpeningTag(document, position) { - const textBeforeCursor = document.getText(new vscode.Range(new vscode.Position(position.line, 0), position)); - const lastOpen = textBeforeCursor.lastIndexOf("<"); - const lastClose = textBeforeCursor.lastIndexOf(">"); - if (lastOpen > lastClose) { - return textBeforeCursor.slice(lastOpen); - } - return null; - } - function extractRouteParams(document) { - const fileName = document.fileName.replace(/\\/g, "/"); - const matches = [...fileName.matchAll(/\[([A-Za-z_$][\w$]*)\]/g)]; - return matches.map((match) => match[1]); - } - function extractStates(document) { - const source = document.getText(); - const matches = [...source.matchAll(/^\s*state\s+([A-Za-z_$][\w$]*)(?:\s*:\s*[^=\r\n]+)?\s*=/gm)]; - return [...new Set(matches.map((match) => match[1]))]; - } - function extractProps(document) { - const source = document.getText(); - const propsBlockPattern = /\bprops\s*\{([\s\S]*?)\}/g; - const props = new Set; - let blockMatch; - while ((blockMatch = propsBlockPattern.exec(source)) !== null) { - const body = blockMatch[1]; - for (const match of body.matchAll(/^\s*([A-Za-z_$][\w$]*)(?:\s*:\s*[^=\r\n]+)?(?:\s*=|\s*$)/gm)) { - props.add(match[1]); - } - } - return [...props]; - } - function extractFunctions(document) { - const source = document.getText(); - const functions = []; - const seen = new Set; - const pattern = /(?:^|\s)(?:async\s+)?function\s+([A-Za-z_$][\w$]*)\s*\(([^)]*)\)/g; - let match; - while ((match = pattern.exec(source)) !== null) { - const name = match[1]; - if (seen.has(name)) { - continue; - } - seen.add(name); - const parameters = match[2].split(",").map((parameter) => parameter.trim()).filter(Boolean).map((parameter) => parameter.replace(/=.*$/, "").replace(/\??\s*:\s*[\s\S]+$/, "").trim()); - functions.push({ - name, - parameters - }); - } - return functions; - } - function sourceBeforePosition(document, position) { - return document.getText(new vscode.Range(new vscode.Position(0, 0), position)); - } - function isInsideNamedBlock(document, position, blockName) { - const source = sourceBeforePosition(document, position); - const tokenPattern = new RegExp(`\\b${blockName}\\s*\\{|\\{|\\}`, "g"); - const stack = []; - let match; - while ((match = tokenPattern.exec(source)) !== null) { - const token = match[0]; - if (token.startsWith(blockName)) { - stack.push(blockName); - continue; - } - if (token === "{") { - stack.push(null); - continue; - } - if (token === "}") { - stack.pop(); - } - } - return stack.includes(blockName); - } - function isInsideLifecycle(document, position) { - return isInsideNamedBlock(document, position, "lifecycle"); - } - function isInsideFunctions(document, position) { - return isInsideNamedBlock(document, position, "functions"); - } - function isInsideWatch(document, position) { - const source = sourceBeforePosition(document, position); - const watchMatches = [...source.matchAll(/\bwatch\s+[A-Za-z_$][\w$]*\s*\{/g)]; - if (watchMatches.length === 0) { - return false; - } - const latest = watchMatches[watchMatches.length - 1]; - const tail = source.slice(latest.index); - let depth = 0; - for (const character of tail) { - if (character === "{") { - depth += 1; - } else if (character === "}") { - depth -= 1; - } - } - return depth > 0; - } - function isAfterWatchKeyword(document, position) { - const linePrefix = document.lineAt(position.line).text.slice(0, position.character); - return /^\s*watch\s+[A-Za-z0-9_$]*$/.test(linePrefix); - } - function addBlockCompletions(items, document, position) { - const insideLifecycle = isInsideLifecycle(document, position); - const insideFunctions = isInsideFunctions(document, position); - for (const completion of BLOCK_COMPLETIONS) { - if (insideLifecycle && !["mount", "update", "unmount"].includes(completion.label)) { - continue; - } - if (insideFunctions && completion.label !== "function") { - continue; - } - if (!insideLifecycle && ["mount", "update", "unmount"].includes(completion.label)) { - continue; - } - if (!insideFunctions && completion.label === "function") { - continue; - } - items.push(createCompletion(completion.label, completion.detail, completion.snippet, completion.documentation)); - } - } - function addStateCompletions(items, document, position) { - const states = extractStates(document); - const afterWatch = isAfterWatchKeyword(document, position); - for (const state of states) { - const item = createVariableCompletion(state, afterWatch ? "Declared WRN state available for watching" : "WRN reactive state"); - if (afterWatch) { - item.sortText = `0-${state}`; - } - items.push(item); - } - } - function addFunctionCompletions(items, document) { - for (const fn of extractFunctions(document)) { - items.push(createFunctionCompletion(fn.name, fn.parameters)); - } - } - function provideCompletionItems(document, position) { - const items = []; - const linePrefix = document.lineAt(position.line).text.slice(0, position.character); - const openingTag = getCurrentOpeningTag(document, position); - if (openingTag !== null) { - for (const [label, detail, snippet] of ATTRIBUTE_COMPLETIONS) { - items.push(createCompletion(label, detail, snippet)); - } - } else if (isAfterWatchKeyword(document, position)) { - addStateCompletions(items, document, position); - } else { - addBlockCompletions(items, document, position); - } - if (linePrefix.includes("ctx.") || linePrefix.includes("ctx.params.")) { - for (const [label, detail] of CONTEXT_COMPLETIONS) { - items.push(createCompletion(label, detail, label)); - } - } - for (const param of extractRouteParams(document)) { - items.push(createVariableCompletion(param, `Route parameter from [${param}].wrn`)); - items.push(createVariableCompletion(`ctx.params.${param}`, `Route parameter from [${param}].wrn`)); - } - if (!isAfterWatchKeyword(document, position)) { - addStateCompletions(items, document, position); - } - for (const prop of extractProps(document)) { - items.push(createVariableCompletion(prop, "WRN component or layout prop")); - } - addFunctionCompletions(items, document); - if (isInsideWatch(document, position)) { - for (const [label, detail] of WATCH_VALUE_COMPLETIONS) { - items.push(createVariableCompletion(label, detail)); - } - } - return items; - } - function registerCompletionProvider(context) { - const provider = vscode.languages.registerCompletionItemProvider({ - language: "wrn" - }, { - provideCompletionItems - }, "@", ":", ".", "<", " ", "("); - context.subscriptions.push(provider); - } - module2.exports = { - extractFunctions, - extractProps, - extractRouteParams, - extractStates, - provideCompletionItems, - registerCompletionProvider - }; -}); - -// editors/vscode/src/definition.js -var require_definition2 = __commonJS((exports2, module2) => { - var vscode = require("vscode"); - var COMPONENT_DECLARATION = /^\s*(component|layout)\s+([A-Za-z_$][\w$]*)\s*\{/gm; - function getTagAtPosition(document, position) { - const range = document.getWordRangeAtPosition(position, /[A-Za-z_$][\w$]*/); - if (!range) { - return null; - } - const name = document.getText(range); - if (!/^[A-Z][A-Za-z0-9_$]*$/.test(name)) { - return null; - } - const line = document.lineAt(position.line).text; - const offset = document.offsetAt(position); - const lineStart = document.offsetAt(new vscode.Position(position.line, 0)); - const characterOffset = offset - lineStart; - const before = line.slice(0, characterOffset); - const after = line.slice(characterOffset); - const insideTag = before.lastIndexOf("<") > before.lastIndexOf(">") && after.includes(">"); - if (!insideTag) { - return null; - } - return { - name, - range - }; - } - async function findDeclaration(name) { - const files = await vscode.workspace.findFiles("**/*.wrn", "**/{node_modules,dist,.wrnexus,.git}/**"); - const matches = []; - for (const uri of files) { - let document; - try { - document = await vscode.workspace.openTextDocument(uri); - } catch { - continue; - } - const source = document.getText(); - COMPONENT_DECLARATION.lastIndex = 0; - let match; - while ((match = COMPONENT_DECLARATION.exec(source)) !== null) { - const declarationName = match[2]; - if (declarationName !== name) { - continue; - } - const nameOffset = match.index + match[0].indexOf(declarationName); - const start = document.positionAt(nameOffset); - const end = document.positionAt(nameOffset + declarationName.length); - matches.push(new vscode.Location(uri, new vscode.Range(start, end))); - } - } - return matches; - } - async function provideDefinition(document, position) { - const tag = getTagAtPosition(document, position); - if (!tag) { - return null; - } - const matches = await findDeclaration(tag.name); - if (matches.length === 0) { - return null; - } - return matches.length === 1 ? matches[0] : matches; - } - function registerDefinitionProvider(context) { - const disposable = vscode.languages.registerDefinitionProvider({ - language: "wrn" - }, { - provideDefinition - }); - context.subscriptions.push(disposable); - } - module2.exports = { - findDeclaration, - getTagAtPosition, - provideDefinition, - registerDefinitionProvider - }; -}); - -// editors/vscode/src/component-metadata.js -var require_component_metadata = __commonJS((exports2, module2) => { - var COMPONENT_DECLARATION = /\b(component|layout)\s+([A-Za-z_$][\w$]*)\s*\{/; - function findMatchingBrace(source, openingBrace) { - let depth = 0; - let quote = null; - let escaped = false; - for (let index = openingBrace;index < source.length; index += 1) { - const character = source[index]; - if (quote) { - if (escaped) - escaped = false; - else if (character === "\\") - escaped = true; - else if (character === quote) - quote = null; - continue; - } - if (character === '"' || character === "'" || character === "`") - quote = character; - else if (character === "{") - depth += 1; - else if (character === "}" && --depth === 0) - return index; - } - return -1; - } - function inferType(defaultValue) { - const value = defaultValue.trim(); - if (value === "undefined") - return "unknown"; - if (/^(?:true|false)$/.test(value)) - return "boolean"; - if (/^-?(?:\d+\.?\d*|\.\d+)$/.test(value)) - return "number"; - if (/^["'`]/.test(value)) - return "string"; - if (value.startsWith("[")) - return "array"; - if (value.startsWith("{")) - return "object"; - if (value === "null") - return "null"; - return "unknown"; - } - function runtimeType(type) { - const value = String(type || "").trim().replace(/^readonly\s+/, ""); - const unionParts = value.split("|").map((part) => part.trim()); - const concreteParts = unionParts.filter((part) => !/^(?:null|undefined)$/.test(part)); - if (/^(?:string|String)(?:\s*\|\s*(?:null|undefined))*$/.test(value) || concreteParts.length > 0 && concreteParts.every((part) => /^(?:"[^"]*"|'[^']*')$/.test(part))) - return "string"; - if (/^(?:number|Number)(?:\s*\|\s*(?:null|undefined))*$/.test(value) || concreteParts.length > 0 && concreteParts.every((part) => /^-?(?:\d+\.?\d*|\.\d+)$/.test(part))) - return "number"; - if (/^(?:boolean|Boolean)(?:\s*\|\s*(?:null|undefined))*$/.test(value) || concreteParts.length > 0 && concreteParts.every((part) => /^(?:true|false)$/.test(part))) - return "boolean"; - if (/^bigint(?:\s*\|\s*(?:null|undefined))*$/.test(value)) - return "bigint"; - if (/^(?:Array\s*<|ReadonlyArray\s*<|.+\[\])/.test(value) || /^\[/.test(value)) - return "array"; - if (/^(?:Record\s*<|object\b|\{)/.test(value)) - return "object"; - if (/=>|^Function$/.test(value)) - return "function"; - return value || "unknown"; - } - function stringLiteral(value) { - const match = /^(?:"([\s\S]*)"|'([\s\S]*)'|`([\s\S]*)`)$/.exec(value.trim()); - return match ? match[1] ?? match[2] ?? match[3] : null; - } - function declaredOptions(type) { - const value = String(type || "").trim(); - if (!value) - return []; - const parts = value.split("|").map((part) => part.trim()); - if (!parts.every((part) => /^(?:"[^"]*"|'[^']*')$/.test(part))) - return []; - return parts.map((part) => part.slice(1, -1)); - } - function inferOptions(source, propName) { - const escaped = propName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - const options = new Set; - const comparisons = new RegExp(`\\b${escaped}\\s*(?:===|!==|==|!=)\\s*(["'])(.*?)\\1`, "g"); - let match; - while ((match = comparisons.exec(source)) !== null) - options.add(match[2]); - return [...options].sort(); - } - function parseComponentMetadata(source, uri = null) { - const declaration = COMPONENT_DECLARATION.exec(source); - if (!declaration) - return null; - const propsKeyword = /\bprops\s*\{/.exec(source.slice(declaration.index)); - const props = []; - const events = []; - if (propsKeyword) { - const start = declaration.index + propsKeyword.index; - const openingBrace = source.indexOf("{", start); - const closingBrace = findMatchingBrace(source, openingBrace); - const bodyEnd = closingBrace === -1 ? source.length : closingBrace; - const body = source.slice(openingBrace + 1, bodyEnd); - const linePattern = /^(?:\s*\/\/\s*@required\s*\r?\n)?\s*([A-Za-z_$][\w$]*)(\?)?(?:\s*:\s*([^=\r\n]+?))?(?:\s*=\s*(.*?))?\s*$/gm; - let propMatch; - while ((propMatch = linePattern.exec(body)) !== null) { - const optional = Boolean(propMatch[2]); - const annotation = propMatch[3] && propMatch[3].trim(); - const hasDefault = propMatch[4] !== undefined; - const defaultValue = hasDefault ? propMatch[4] : "undefined"; - const name = propMatch[1]; - props.push({ - name, - defaultValue, - required: !optional && (!hasDefault || defaultValue.trim() === "undefined" || /^\s*\/\/\s*@required/m.test(propMatch[0])), - type: annotation || inferType(defaultValue), - options: declaredOptions(annotation).length > 0 ? declaredOptions(annotation) : inferOptions(source, name) - }); - } - const eventPattern = /^\s*@event\s+([A-Za-z_$][\w$]*)\s*=\s*function\s*$/gm; - let eventMatch; - while ((eventMatch = eventPattern.exec(body)) !== null) - events.push(eventMatch[1]); - } - const outputsKeyword = /\boutputs\s*\{/.exec(source.slice(declaration.index)); - if (outputsKeyword) { - const start = declaration.index + outputsKeyword.index; - const openingBrace = source.indexOf("{", start); - const closingBrace = findMatchingBrace(source, openingBrace); - const outputBody = source.slice(openingBrace + 1, closingBrace === -1 ? source.length : closingBrace); - const outputPattern = /(?:^|\s)([A-Za-z_$][\w$]*)\s*\(/g; - let outputMatch; - while ((outputMatch = outputPattern.exec(outputBody)) !== null) { - if (!events.includes(outputMatch[1])) - events.push(outputMatch[1]); - } - } - return { kind: declaration[1], name: declaration[2], props, events, uri }; - } - function unwrapAttributeValue(value) { - const trimmed = value.trim(); - if (trimmed.startsWith("{") && trimmed.endsWith("}")) - return trimmed.slice(1, -1).trim(); - return trimmed; - } - function isWrappedExpression(value) { - const trimmed = value.trim(); - return trimmed.startsWith("{") && trimmed.endsWith("}"); - } - function expressionLiteralType(value) { - const expression = value.trim(); - if (/^(?:true|false)$/.test(expression)) - return "boolean"; - if (/^-?(?:\d+\.?\d*|\.\d+)$/.test(expression)) - return "number"; - if (/^(?:"[\s\S]*"|'[\s\S]*'|`[\s\S]*`)$/.test(expression)) - return "string"; - if (expression.startsWith("[")) - return "array"; - if (expression.startsWith("{")) - return "object"; - if (expression === "null") - return "null"; - return null; - } - function attributeValueType(value, symbols = new Map) { - const expression = isWrappedExpression(value); - const unwrapped = unwrapAttributeValue(value); - if (/^[A-Za-z_$][\w$]*$/.test(unwrapped) && symbols.has(unwrapped)) { - return symbols.get(unwrapped); - } - const literal = expressionLiteralType(unwrapped); - if (literal) - return literal; - if (expression) { - if (/^!\s*/.test(unwrapped) || /(?:===|!==|==|!=|<=|>=|<|>|\bin\b|\binstanceof\b)/.test(unwrapped)) { - return "boolean"; - } - return "unknown"; - } - return "string"; - } - function isTypeCompatible(prop, value, symbols = new Map) { - const expected = runtimeType(prop.type); - if (expected === "unknown" || expected === "null") - return true; - const actual = runtimeType(attributeValueType(value, symbols)); - if (actual === "unknown") - return true; - if (expected === actual) - return true; - if (expected === "number" && actual === "string") - return Number.isFinite(Number(value)); - if (expected === "boolean" && actual === "string") - return /^(?:true|false|1|0|yes|no|on|off)?$/i.test(value); - return false; - } - function parseComponentTags(source) { - const tags = []; - const pattern = /<([A-Z][A-Za-z0-9_$]*)(\s[\s\S]*?)?\s*\/?>/g; - let match; - while ((match = pattern.exec(source)) !== null) { - const attributes = []; - const attributeSource = match[2] || ""; - const attributeOffset = match.index + match[0].indexOf(attributeSource); - const attributePattern = /([^\s=/>]+)\s*=\s*(["'])([\s\S]*?)\2/g; - let attributeMatch; - while ((attributeMatch = attributePattern.exec(attributeSource)) !== null) { - const nameStart = attributeOffset + attributeMatch.index; - attributes.push({ - name: attributeMatch[1], - value: attributeMatch[3], - nameStart, - nameEnd: nameStart + attributeMatch[1].length - }); - } - tags.push({ - name: match[1], - start: match.index, - end: match.index + match[0].length, - nameStart: match.index + 1, - nameEnd: match.index + 1 + match[1].length, - attributes - }); - } - return tags; - } - function validateComponentTags(source, components) { - const diagnostics = []; - const own = parseComponentMetadata(source); - const symbols = new Map((own?.props || []).map((prop) => [prop.name, prop.type])); - const statePattern = /^\s*state\s+([A-Za-z_$][\w$]*)(?:\s*:\s*([^=\r\n]+?))?\s*=\s*(.*?)\s*$/gm; - let stateMatch; - while ((stateMatch = statePattern.exec(source)) !== null) { - symbols.set(stateMatch[1], stateMatch[2]?.trim() || inferType(stateMatch[3])); - } - for (const tag of parseComponentTags(source)) { - const component = components.get(tag.name); - if (!component) - continue; - const provided = new Map(tag.attributes.map((attribute) => [attribute.name, attribute])); - const declared = new Map(component.props.map((prop) => [prop.name, prop])); - const declaredEvents = new Set(component.events || []); - for (const prop of component.props) { - if (prop.required && !provided.has(prop.name)) { - diagnostics.push({ - severity: "error", - code: "wrn-missing-component-prop", - message: `<${tag.name}> requires prop \`${prop.name}\` (${prop.type}).`, - start: tag.nameStart, - end: tag.nameEnd - }); - } - } - for (const attribute of tag.attributes) { - if (attribute.name.startsWith("@")) { - const eventName = attribute.name.slice(1); - if (!declaredEvents.has(eventName)) { - diagnostics.push({ - severity: "warning", - code: "wrn-unknown-component-event", - message: `Unknown event \`${eventName}\` on <${tag.name}>.`, - start: attribute.nameStart, - end: attribute.nameEnd - }); - } - continue; - } - const prop = declared.get(attribute.name); - if (!prop) { - diagnostics.push({ - severity: "warning", - code: "wrn-unknown-component-prop", - message: `Unknown prop \`${attribute.name}\` on <${tag.name}>.`, - start: attribute.nameStart, - end: attribute.nameEnd - }); - continue; - } - const actualType = attributeValueType(attribute.value, symbols); - if (!isTypeCompatible(prop, attribute.value, symbols)) { - diagnostics.push({ - severity: "error", - code: "wrn-component-prop-type", - message: `Prop \`${attribute.name}\` on <${tag.name}> expects ${prop.type}, but received ${runtimeType(actualType)}.`, - start: attribute.nameStart, - end: attribute.nameEnd - }); - } - const optionValue = isWrappedExpression(attribute.value) ? stringLiteral(unwrapAttributeValue(attribute.value)) : stringLiteral(attribute.value) ?? attribute.value; - if (optionValue !== null && prop.options.length > 0 && !prop.options.includes(optionValue)) { - diagnostics.push({ - severity: "warning", - code: "wrn-component-prop-option", - message: `Prop \`${attribute.name}\` should be one of: ${prop.options.join(", ")}.`, - start: attribute.nameStart, - end: attribute.nameEnd - }); - } - } - } - return diagnostics; - } - module2.exports = { - attributeValueType, - declaredOptions, - inferType, - runtimeType, - isTypeCompatible, - parseComponentMetadata, - parseComponentTags, - validateComponentTags - }; -}); - -// editors/vscode/src/component-intelligence.js -var require_component_intelligence = __commonJS((exports2, module2) => { - var vscode = require("vscode"); - var path = require("node:path"); - var { - parseComponentMetadata, - parseComponentTags, - validateComponentTags - } = require_component_metadata(); - var catalogPromise = null; - async function readMetadata(uri) { - try { - const document = await vscode.workspace.openTextDocument(uri); - return parseComponentMetadata(document.getText(), uri); - } catch { - return null; - } - } - async function installedUiUris() { - const uris = []; - for (const folder of vscode.workspace.workspaceFolders || []) { - const directory = vscode.Uri.joinPath(folder.uri, "node_modules", "@wrnexus", "ui", "components"); - try { - for (const [name, type] of await vscode.workspace.fs.readDirectory(directory)) { - if (type === vscode.FileType.File && name.endsWith(".wrn")) { - uris.push(vscode.Uri.joinPath(directory, name)); - } - } - } catch {} - } - return uris; - } - async function loadComponentCatalog() { - const localUris = await vscode.workspace.findFiles("**/*.wrn", "**/{node_modules,dist,.wrnexus,.git}/**"); - const uiUris = await installedUiUris(); - const metadata = await Promise.all([...uiUris, ...localUris].map(readMetadata)); - const catalog = new Map; - for (const component of metadata) { - if (component && (component.kind === "component" || component.kind === "layout")) { - catalog.set(component.name, component); - } - } - return catalog; - } - function componentCatalog() { - catalogPromise ||= loadComponentCatalog(); - return catalogPromise; - } - function invalidateCatalog() { - catalogPromise = null; - } - function openingTagAt(source, offset) { - const opening = source.lastIndexOf("<", offset); - const closing = source.lastIndexOf(">", offset); - if (opening <= closing) - return null; - const fragment = source.slice(opening, offset); - const match = /^<([A-Z][A-Za-z0-9_$]*)\b/.exec(fragment); - return match ? { name: match[1], fragment } : null; - } - function componentAlreadyImported(source, name) { - const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return new RegExp(`\\bimport[\\s\\S]*?\\b${escaped}\\b[\\s\\S]*?\\bfrom\\b`).test(source) || new RegExp(`\\b(?:component|layout)\\s+${escaped}\\b`).test(source); - } - function importEditForComponent(document, component) { - if (!component?.uri || componentAlreadyImported(document.getText(), component.name)) - return null; - const target = component.uri.fsPath.replace(/\\/g, "/"); - const current = document.uri.fsPath.replace(/\\/g, "/"); - let statement; - if (target.includes("/node_modules/@wrnexus/ui/components/") || target.includes("/packages/ui/components/")) { - statement = `import { ${component.name} } from "@wrnexus/ui" -`; - } else { - const appMarker = "/app/"; - const appIndex = target.lastIndexOf(appMarker); - if (appIndex !== -1) { - statement = `import ${component.name} from "@/${target.slice(appIndex + appMarker.length)}" -`; - } else { - let relative = path.posix.relative(path.posix.dirname(current), target); - if (!relative.startsWith(".")) - relative = `./${relative}`; - statement = `import ${component.name} from "${relative}" -`; - } - } - return vscode.TextEdit.insert(new vscode.Position(0, 0), statement); - } - function propSnippet(prop) { - if (prop.type === "boolean") - return `${prop.name}="{\${1:false}}"`; - if (prop.type === "number") - return `${prop.name}="{\${1:0}}"`; - if (prop.type === "array") - return `${prop.name}="{[\${1}]}"`; - if (prop.type === "object") - return `${prop.name}="{{ \${1} }}"`; - if (prop.options.length > 0) - return `${prop.name}="\${1|${prop.options.join(",")}|}"`; - return `${prop.name}="\${1}"`; - } - function propDocumentation(prop) { - const lines = [ - `**${prop.name}** — \`${prop.type}\` ${prop.required ? "**required**" : "optional"}` - ]; - if (!prop.required) - lines.push(`Default: \`${prop.defaultValue}\``); - if (prop.options.length > 0) - lines.push(`Allowed: ${prop.options.map((v) => `\`${v}\``).join(", ")}`); - return lines.join(` - -`); - } - async function provideComponentCompletions(document, position) { - const source = document.getText(); - const offset = document.offsetAt(position); - const tag = openingTagAt(source, offset); - const catalog = await componentCatalog(); - const items = []; - if (tag) { - const component = catalog.get(tag.name); - if (!component) - return items; - const used = new Set([...tag.fragment.matchAll(/\s([^\s=/>]+)\s*=/g)].map((m) => m[1])); - for (const prop of component.props) { - if (used.has(prop.name)) - continue; - const item = new vscode.CompletionItem(prop.name, vscode.CompletionItemKind.Property); - item.detail = `${prop.required ? "required" : "optional"} · ${prop.type}`; - item.documentation = new vscode.MarkdownString(propDocumentation(prop)); - item.insertText = new vscode.SnippetString(propSnippet(prop)); - item.sortText = `${prop.required ? "0" : "1"}-${prop.name}`; - items.push(item); - } - return items; - } - const prefix = source.slice(Math.max(0, offset - 80), offset); - if (!/<[A-Za-z0-9_$]*$/.test(prefix)) - return items; - for (const component of catalog.values()) { - const item = new vscode.CompletionItem(component.name, vscode.CompletionItemKind.Class); - const required = component.props.filter((prop) => prop.required); - item.detail = `WRN ${component.kind} · ${component.props.length} props`; - item.documentation = new vscode.MarkdownString(componentMarkdown(component)); - item.insertText = new vscode.SnippetString(`${component.name}${required.map((prop, index) => ` ${prop.name}="\${${index + 1}}"`).join("")} />`); - const importEdit = importEditForComponent(document, component); - if (importEdit) - item.additionalTextEdits = [importEdit]; - items.push(item); - } - return items; - } - function componentMarkdown(component) { - const lines = [ - `### <${component.name}>`, - "", - "| Prop | Type | Required | Default / options |", - "|---|---|---:|---|" - ]; - for (const prop of component.props) { - const detail = prop.options.length > 0 ? prop.options.join(" \\| ") : prop.required ? "—" : prop.defaultValue; - lines.push(`| \`${prop.name}\` | \`${prop.type}\` | ${prop.required ? "yes" : "no"} | ${detail} |`); - } - return lines.join(` -`); - } - async function provideComponentHover(document, position) { - const source = document.getText(); - const offset = document.offsetAt(position); - const catalog = await componentCatalog(); - const tag = parseComponentTags(source).find((candidate) => offset >= candidate.start && offset <= candidate.end); - if (!tag) - return null; - const component = catalog.get(tag.name); - if (!component) - return null; - const attribute = tag.attributes.find((candidate) => offset >= candidate.nameStart && offset <= candidate.nameEnd); - const markdown = attribute ? propDocumentation(component.props.find((prop) => prop.name === attribute.name) || { - name: attribute.name, - type: "unknown", - required: false, - defaultValue: "unknown", - options: [] - }) : componentMarkdown(component); - return new vscode.Hover(new vscode.MarkdownString(markdown)); - } - function registerComponentIntelligence(context) { - const diagnostics = vscode.languages.createDiagnosticCollection("wrnexus-components"); - const update = async (document) => { - if (document.languageId !== "wrn") - return; - const catalog = await componentCatalog(); - const results = validateComponentTags(document.getText(), catalog).map((result) => { - const diagnostic = new vscode.Diagnostic(new vscode.Range(document.positionAt(result.start), document.positionAt(result.end)), result.message, result.severity === "error" ? vscode.DiagnosticSeverity.Error : vscode.DiagnosticSeverity.Warning); - diagnostic.source = "WRNexus Components"; - diagnostic.code = result.code; - return diagnostic; - }); - diagnostics.set(document.uri, results); - }; - const watchers = (vscode.workspace.workspaceFolders || []).map((folder) => vscode.workspace.createFileSystemWatcher(new vscode.RelativePattern(folder, "app/**/*.wrn"))); - const refreshOpenDocuments = () => { - invalidateCatalog(); - for (const document of vscode.workspace.textDocuments) - update(document); - }; - context.subscriptions.push(diagnostics, ...watchers, ...watchers.flatMap((watcher) => [ - watcher.onDidCreate(refreshOpenDocuments), - watcher.onDidChange(refreshOpenDocuments), - watcher.onDidDelete(refreshOpenDocuments) - ]), vscode.workspace.onDidOpenTextDocument(update), vscode.workspace.onDidChangeTextDocument((event) => void update(event.document)), vscode.workspace.onDidSaveTextDocument((document) => { - invalidateCatalog(); - update(document); - }), vscode.workspace.onDidCloseTextDocument((document) => diagnostics.delete(document.uri)), vscode.languages.registerCompletionItemProvider({ language: "wrn" }, { provideCompletionItems: provideComponentCompletions }, "<", " "), vscode.languages.registerHoverProvider({ language: "wrn" }, { provideHover: provideComponentHover })); - for (const document of vscode.workspace.textDocuments) - update(document); - } - module2.exports = { - componentAlreadyImported, - componentMarkdown, - importEditForComponent, - openingTagAt, - propDocumentation, - propSnippet, - registerComponentIntelligence - }; -}); - -// editors/vscode/src/diagnostics.js -var require_diagnostics = __commonJS((exports2, module2) => { - var vscode = require("vscode"); - var COLLECTION_NAME = "wrnexus"; - var WRN_LANGUAGE_ID = "wrn"; - var TOP_LEVEL_PATTERN = /^\s*((?:global|page)\s+store|page|component|layout)\s+([A-Za-z_$][\w$]*)\s*\{/; - var VALID_TOP_LEVEL_KINDS = new Set([ - "page", - "component", - "layout", - "global store", - "page store" - ]); - var VALID_LIFECYCLE_HOOKS = new Set(["mount", "update", "unmount"]); - var ROOT_MEMBER_NAMES = [ - "layout", - "runtime", - "hydrate", - "client", - "types", - "props", - "outputs", - "state", - "persist", - "computed", - "effect", - "watch", - "lifecycle", - "view", - "seo", - "security", - "load", - "action", - "api", - "ssr", - "realtime", - "style", - "functions" - ]; - var VALID_MEMBERS = { - page: new Set(ROOT_MEMBER_NAMES), - component: new Set(ROOT_MEMBER_NAMES), - layout: new Set(ROOT_MEMBER_NAMES), - "global store": new Set(ROOT_MEMBER_NAMES), - "page store": new Set(ROOT_MEMBER_NAMES) - }; - function createDiagnostic(document, startOffset, endOffset, message, severity = vscode.DiagnosticSeverity.Error, code) { - const diagnostic = new vscode.Diagnostic(new vscode.Range(document.positionAt(startOffset), document.positionAt(endOffset)), message, severity); - diagnostic.source = "WRNexus"; - if (code) { - diagnostic.code = code; - } - return diagnostic; - } - function lineDiagnostic(document, lineNumber, message, severity = vscode.DiagnosticSeverity.Error, code) { - const line = document.lineAt(lineNumber); - const diagnostic = new vscode.Diagnostic(line.range, message, severity); - diagnostic.source = "WRNexus"; - if (code) { - diagnostic.code = code; - } - return diagnostic; - } - function maskLeadingTrivia(source) { - const masked = [...source]; - let offset = 0; - const importPattern = /import\s+(?:type\s+)?(?:[\s\S]*?\s+from\s+)?["'][^"'\r\n]+["']\s*;?/y; - while (offset < source.length) { - if (/\s/u.test(source[offset])) { - offset += 1; - continue; - } - importPattern.lastIndex = offset; - const importStatement = importPattern.exec(source); - if (importStatement) { - const end = importPattern.lastIndex; - while (offset < end) { - if (source[offset] !== ` -` && source[offset] !== "\r") - masked[offset] = " "; - offset += 1; - } - continue; - } - if (!source.startsWith("//", offset)) - break; - while (offset < source.length && source[offset] !== ` -`) { - masked[offset] = " "; - offset += 1; - } - } - return masked.join(""); - } - function findTopLevelDeclaration(document, source) { - const sourceWithoutLeadingTrivia = maskLeadingTrivia(source); - const match = TOP_LEVEL_PATTERN.exec(sourceWithoutLeadingTrivia); - if (!match) { - const firstMeaningfulLine = sourceWithoutLeadingTrivia.split(/\r?\n/).findIndex((line) => line.trim().length > 0); - return { - diagnostic: lineDiagnostic(document, Math.max(0, firstMeaningfulLine), "A .wrn file must start with `page`, `component`, or `layout`.", vscode.DiagnosticSeverity.Error, "wrn-invalid-root") - }; - } - return { - kind: match[1], - name: match[2], - match - }; - } - function validateBalancedCharacters(document, source) { - const diagnostics = []; - const stack = []; - let quote = null; - let escaped = false; - const pairs = { - "}": "{", - "]": "[", - ")": "(" - }; - for (let index = 0;index < source.length; index += 1) { - const character = source[index]; - if (quote !== null) { - if (escaped) { - escaped = false; - continue; - } - if (character === "\\") { - escaped = true; - continue; - } - if (character === quote) { - quote = null; - } - continue; - } - if (source.startsWith("//", index)) { - const lineEnd = source.indexOf(` -`, index + 2); - index = lineEnd === -1 ? source.length : lineEnd; - continue; - } - if (character === "'" && /[\p{L}\p{N}]/u.test(source[index - 1] ?? "") && /[\p{L}\p{N}]/u.test(source[index + 1] ?? "")) { - continue; - } - if (character === '"' || character === "'") { - quote = character; - continue; - } - if (source.startsWith("", index + 4); - if (commentEnd === -1) { - diagnostics.push(createDiagnostic(document, index, Math.min(source.length, index + 4), "Unclosed HTML comment.", vscode.DiagnosticSeverity.Error, "wrn-unclosed-comment")); - break; - } - index = commentEnd + 2; - continue; - } - if (character === "{" || character === "[" || character === "(") { - stack.push({ - character, - offset: index - }); - continue; - } - if (character === "}" || character === "]" || character === ")") { - const expectedOpening = pairs[character]; - const opening = stack.pop(); - if (!opening || opening.character !== expectedOpening) { - diagnostics.push(createDiagnostic(document, index, index + 1, `Unexpected \`${character}\`.`, vscode.DiagnosticSeverity.Error, "wrn-unexpected-closing")); - } - } - } - for (const opening of stack) { - const expectedClosing = opening.character === "{" ? "}" : opening.character === "[" ? "]" : ")"; - diagnostics.push(createDiagnostic(document, opening.offset, opening.offset + 1, `Missing closing \`${expectedClosing}\`.`, vscode.DiagnosticSeverity.Error, "wrn-missing-closing")); - } - if (quote !== null) { - diagnostics.push(createDiagnostic(document, Math.max(0, source.length - 1), source.length, `Unclosed ${quote === '"' ? "double" : "single"} quote.`, vscode.DiagnosticSeverity.Error, "wrn-unclosed-string")); - } - return diagnostics; - } - function maskHtmlComments(source) { - const masked = [...source]; - let index = 0; - while (index < source.length) { - if (!source.startsWith("", index + 4); - const end = commentEnd === -1 ? source.length : commentEnd + 3; - for (let cursor = index;cursor < end; cursor += 1) { - if (source[cursor] !== ` -` && source[cursor] !== "\r") - masked[cursor] = " "; - } - index = end; - } - return masked.join(""); - } - function maskTemplateExpressions(source) { - const masked = [...source]; - let index = 0; - while (index < source.length) { - if (source[index] !== "{") { - index += 1; - continue; - } - const start = index; - let depth = 0; - let quote = null; - let escaped = false; - while (index < source.length) { - const character = source[index]; - if (quote !== null) { - if (escaped) - escaped = false; - else if (character === "\\") - escaped = true; - else if (character === quote) - quote = null; - index += 1; - continue; - } - if (character === '"' || character === "'" || character === "`") { - quote = character; - index += 1; - continue; - } - if (character === "{") - depth += 1; - else if (character === "}") { - depth -= 1; - index += 1; - if (depth === 0) - break; - continue; - } - index += 1; - } - for (let cursor = start;cursor < index; cursor += 1) { - if (source[cursor] !== ` -` && source[cursor] !== "\r") - masked[cursor] = " "; - } - } - return masked.join(""); - } - function findViewRanges(source) { - const ranges = []; - const pattern = /\bview\s*\{/g; - let match; - while ((match = pattern.exec(source)) !== null) { - const openingBrace = source.indexOf("{", match.index); - const closingBrace = findMatchingBrace(source, openingBrace); - if (closingBrace === -1) - break; - ranges.push({ start: openingBrace + 1, end: closingBrace }); - pattern.lastIndex = closingBrace + 1; - } - return ranges; - } - function validateHtmlTags(document, source) { - const diagnostics = []; - const voidElements = new Set([ - "area", - "base", - "br", - "col", - "embed", - "hr", - "img", - "input", - "link", - "meta", - "param", - "source", - "track", - "wbr" - ]); - for (const range of findViewRanges(source)) { - const fragment = source.slice(range.start, range.end); - const cleaned = maskTemplateExpressions(maskHtmlComments(fragment)); - const stack = []; - const tagPattern = /<\/?([A-Za-z][A-Za-z0-9_$:.-]*)(?:\s[\s\S]*?)?\/?>/g; - let match; - while ((match = tagPattern.exec(cleaned)) !== null) { - const completeTag = match[0]; - const tagName = match[1]; - const normalizedName = /^[a-z]/.test(tagName) ? tagName.toLowerCase() : tagName; - const lowerTag = tagName.toLowerCase(); - const absoluteStart = range.start + match.index; - const isClosing = completeTag.startsWith("$/.test(completeTag); - const isVoid = voidElements.has(lowerTag); - if (isClosing) { - const matchingIndex = stack.findLastIndex((item) => item.normalizedName === normalizedName); - if (matchingIndex === -1) { - diagnostics.push(createDiagnostic(document, absoluteStart, absoluteStart + completeTag.length, `Unexpected closing tag .`, vscode.DiagnosticSeverity.Error, "wrn-unexpected-html-close")); - continue; - } - const last = stack.at(-1); - if (last.normalizedName !== normalizedName) { - diagnostics.push(createDiagnostic(document, absoluteStart, absoluteStart + completeTag.length, `Mismatched closing tag . Expected .`, vscode.DiagnosticSeverity.Error, "wrn-mismatched-html-tag")); - } - stack.splice(matchingIndex); - continue; - } - if (!isSelfClosing && !isVoid) { - stack.push({ - tagName, - normalizedName, - offset: absoluteStart, - length: completeTag.length - }); - } - } - for (const tag of stack) { - diagnostics.push(createDiagnostic(document, tag.offset, tag.offset + tag.length, `Missing closing tag .`, vscode.DiagnosticSeverity.Error, "wrn-missing-html-close")); - } - } - return diagnostics; - } - function getRootBodyRange(source, rootMatch) { - const openingBrace = rootMatch.index + rootMatch[0].lastIndexOf("{"); - const closingBrace = findMatchingBrace(source, openingBrace); - return { - start: openingBrace + 1, - end: closingBrace === -1 ? source.length : closingBrace - }; - } - function findMatchingBrace(source, openingBrace) { - let depth = 0; - let quote = null; - let escaped = false; - for (let index = openingBrace;index < source.length; index += 1) { - const character = source[index]; - if (quote !== null) { - if (escaped) { - escaped = false; - continue; - } - if (character === "\\") { - escaped = true; - continue; - } - if (character === quote) { - quote = null; - } - continue; - } - if (character === '"' || character === "'") { - quote = character; - continue; - } - if (source.startsWith("", index + 4); - if (commentEnd === -1) { - return -1; - } - index = commentEnd + 2; - continue; - } - if (character === "{") { - depth += 1; - continue; - } - if (character === "}") { - depth -= 1; - if (depth === 0) { - return index; - } - } - } - return -1; - } - function skipWhitespace(source, index, end) { - while (index < end && /\s/.test(source[index])) { - index += 1; - } - return index; - } - function readIdentifier(source, index, end) { - if (index >= end || !/[A-Za-z_$]/.test(source[index])) { - return null; - } - const start = index; - index += 1; - while (index < end && /[A-Za-z0-9_$-]/.test(source[index])) { - index += 1; - } - return { - name: source.slice(start, index), - start, - end: index - }; - } - function findRootMembers(source, bodyStart, bodyEnd) { - const members = []; - let index = bodyStart; - let depth = 0; - let quote = null; - let escaped = false; - const skipLine = () => { - while (index < bodyEnd && source[index] !== ` -` && source[index] !== "\r") { - index += 1; - } - }; - while (index < bodyEnd) { - const character = source[index]; - if (quote !== null) { - if (escaped) { - escaped = false; - index += 1; - continue; - } - if (character === "\\") { - escaped = true; - index += 1; - continue; - } - if (character === quote) { - quote = null; - } - index += 1; - continue; - } - if (character === '"' || character === "'") { - quote = character; - index += 1; - continue; - } - if (source.startsWith("//", index)) { - skipLine(); - continue; - } - if (source.startsWith("/*", index)) { - const end = source.indexOf("*/", index + 2); - index = end === -1 ? bodyEnd : end + 2; - continue; - } - if (source.startsWith("", index + 4); - index = end === -1 ? bodyEnd : end + 3; - continue; - } - if (character === "{") { - depth += 1; - index += 1; - continue; - } - if (character === "}") { - depth = Math.max(0, depth - 1); - index += 1; - continue; - } - if (depth === 0 && /[A-Za-z_$]/.test(character)) { - const identifier = readIdentifier(source, index, bodyEnd); - if (!identifier) { - index += 1; - continue; - } - index = identifier.end; - members.push({ - name: identifier.name, - start: identifier.start, - end: identifier.end - }); - const assignmentMembers = new Set(["layout", "runtime", "hydrate", "state"]); - if (assignmentMembers.has(identifier.name)) { - skipLine(); - continue; - } - if (identifier.name === "client") { - const next = skipWhitespace(source, index, bodyEnd); - if (source[next] === "=") { - skipLine(); - continue; - } - } - const blockMembers = new Set([ - "types", - "props", - "computed", - "effect", - "watch", - "lifecycle", - "view", - "seo", - "security", - "load", - "action", - "api", - "ssr", - "client", - "realtime", - "style", - "functions" - ]); - if (blockMembers.has(identifier.name)) { - let cursor = index; - let parenthesisDepth = 0; - let bracketDepth = 0; - let memberQuote = null; - let memberEscaped = false; - while (cursor < bodyEnd) { - const current = source[cursor]; - if (memberQuote !== null) { - if (memberEscaped) { - memberEscaped = false; - } else if (current === "\\") { - memberEscaped = true; - } else if (current === memberQuote) { - memberQuote = null; - } - cursor += 1; - continue; - } - if (current === '"' || current === "'") { - memberQuote = current; - cursor += 1; - continue; - } - if (current === "(") - parenthesisDepth += 1; - if (current === ")") - parenthesisDepth = Math.max(0, parenthesisDepth - 1); - if (current === "[") - bracketDepth += 1; - if (current === "]") - bracketDepth = Math.max(0, bracketDepth - 1); - if (current === "{" && parenthesisDepth === 0 && bracketDepth === 0) { - index = cursor; - break; - } - cursor += 1; - } - if (cursor >= bodyEnd) - index = bodyEnd; - } - continue; - } - index += 1; - } - return members; - } - function findNamedBlocks(source, bodyStart, bodyEnd, blockName) { - const blocks = []; - let index = bodyStart; - while (index < bodyEnd) { - index = skipWhitespace(source, index, bodyEnd); - const identifier = readIdentifier(source, index, bodyEnd); - if (!identifier) { - index += 1; - continue; - } - index = identifier.end; - if (identifier.name !== blockName) { - const possibleBrace = skipWhitespace(source, index, bodyEnd); - if (source[possibleBrace] === "{") { - const end = findMatchingBrace(source, possibleBrace); - index = end === -1 ? bodyEnd : end + 1; - } - continue; - } - const openingBrace = skipWhitespace(source, index, bodyEnd); - if (source[openingBrace] !== "{") { - blocks.push({ - name: blockName, - nameStart: identifier.start, - nameEnd: identifier.end, - openingBrace: -1, - closingBrace: -1 - }); - continue; - } - const closingBrace = findMatchingBrace(source, openingBrace); - blocks.push({ - name: blockName, - nameStart: identifier.start, - nameEnd: identifier.end, - openingBrace, - closingBrace - }); - index = closingBrace === -1 ? bodyEnd : closingBrace + 1; - } - return blocks; - } - function findStateDeclarations(source, bodyStart, bodyEnd) { - const states = new Map; - let index = bodyStart; - let depth = 0; - let quote = null; - let escaped = false; - while (index < bodyEnd) { - const character = source[index]; - if (quote !== null) { - if (escaped) { - escaped = false; - } else if (character === "\\") { - escaped = true; - } else if (character === quote) { - quote = null; - } - index += 1; - continue; - } - if (character === '"' || character === "'") { - quote = character; - index += 1; - continue; - } - if (source.startsWith("", index + 4); - index = end === -1 ? bodyEnd : end + 3; - continue; - } - if (character === "{") { - depth += 1; - index += 1; - continue; - } - if (character === "}") { - depth = Math.max(0, depth - 1); - index += 1; - continue; - } - if (depth === 0 && source.startsWith("state", index) && !/[A-Za-z0-9_$]/.test(source[index - 1] || "") && !/[A-Za-z0-9_$]/.test(source[index + 5] || "")) { - let cursor = skipWhitespace(source, index + 5, bodyEnd); - const state = readIdentifier(source, cursor, bodyEnd); - if (state) { - states.set(state.name, state); - index = state.end; - continue; - } - } - index += 1; - } - return states; - } - function findWatchDeclarations(source, bodyStart, bodyEnd) { - const watches = []; - let index = bodyStart; - let depth = 0; - let quote = null; - let escaped = false; - while (index < bodyEnd) { - const character = source[index]; - if (quote !== null) { - if (escaped) { - escaped = false; - } else if (character === "\\") { - escaped = true; - } else if (character === quote) { - quote = null; - } - index += 1; - continue; - } - if (character === '"' || character === "'") { - quote = character; - index += 1; - continue; - } - if (source.startsWith("", index + 4); - index = end === -1 ? bodyEnd : end + 3; - continue; - } - if (character === "{") { - depth += 1; - index += 1; - continue; - } - if (character === "}") { - depth = Math.max(0, depth - 1); - index += 1; - continue; - } - if (depth === 0 && source.startsWith("watch", index) && !/[A-Za-z0-9_$]/.test(source[index - 1] || "") && !/[A-Za-z0-9_$]/.test(source[index + 5] || "")) { - const watchStart = index; - let cursor = skipWhitespace(source, index + 5, bodyEnd); - const watchedState = readIdentifier(source, cursor, bodyEnd); - if (!watchedState) { - watches.push({ - watchStart, - watchEnd: index + 5, - state: null, - openingBrace: -1, - closingBrace: -1 - }); - index += 5; - continue; - } - cursor = skipWhitespace(source, watchedState.end, bodyEnd); - const openingBrace = source[cursor] === "{" ? cursor : -1; - const closingBrace = openingBrace === -1 ? -1 : findMatchingBrace(source, openingBrace); - watches.push({ - watchStart, - watchEnd: index + 5, - state: watchedState, - openingBrace, - closingBrace - }); - index = closingBrace === -1 ? watchedState.end : closingBrace + 1; - continue; - } - index += 1; - } - return watches; - } - function validateRootMembers(document, source, rootKind, rootMatch) { - const diagnostics = []; - const allowed = VALID_MEMBERS[rootKind]; - if (!allowed) { - return diagnostics; - } - const bodyRange = getRootBodyRange(source, rootMatch); - const members = findRootMembers(source, bodyRange.start, bodyRange.end); - for (const member of members) { - if (allowed.has(member.name)) { - continue; - } - diagnostics.push(createDiagnostic(document, member.start, member.end, `Unknown ${rootKind} member \`${member.name}\`.`, vscode.DiagnosticSeverity.Error, "wrn-unknown-member")); - } - return diagnostics; - } - function validateLifecycleBlocks(document, source, rootKind, rootMatch) { - if (rootKind !== "component" && rootKind !== "layout") { - return []; - } - const diagnostics = []; - const bodyRange = getRootBodyRange(source, rootMatch); - const blocks = findNamedBlocks(source, bodyRange.start, bodyRange.end, "lifecycle"); - if (blocks.length > 1) { - for (const block of blocks.slice(1)) { - diagnostics.push(createDiagnostic(document, block.nameStart, block.nameEnd, "Only one `lifecycle { ... }` block is allowed.", vscode.DiagnosticSeverity.Error, "wrn-duplicate-lifecycle")); - } - } - for (const block of blocks) { - if (block.openingBrace === -1) { - diagnostics.push(createDiagnostic(document, block.nameStart, block.nameEnd, "`lifecycle` must be followed by a block.", vscode.DiagnosticSeverity.Error, "wrn-invalid-lifecycle")); - continue; - } - const lifecycleEnd = block.closingBrace === -1 ? bodyRange.end : block.closingBrace; - let index = block.openingBrace + 1; - const seenHooks = new Set; - while (index < lifecycleEnd) { - index = skipWhitespace(source, index, lifecycleEnd); - if (index >= lifecycleEnd) { - break; - } - const hook = readIdentifier(source, index, lifecycleEnd); - if (!hook) { - index += 1; - continue; - } - index = hook.end; - if (!VALID_LIFECYCLE_HOOKS.has(hook.name)) { - diagnostics.push(createDiagnostic(document, hook.start, hook.end, `Unknown lifecycle hook \`${hook.name}\`. Use \`mount\`, \`update\`, or \`unmount\`.`, vscode.DiagnosticSeverity.Error, "wrn-invalid-lifecycle-hook")); - } else if (seenHooks.has(hook.name)) { - diagnostics.push(createDiagnostic(document, hook.start, hook.end, `Duplicate lifecycle hook \`${hook.name}\`.`, vscode.DiagnosticSeverity.Error, "wrn-duplicate-lifecycle-hook")); - } else { - seenHooks.add(hook.name); - } - const openingBrace = skipWhitespace(source, index, lifecycleEnd); - if (source[openingBrace] !== "{") { - diagnostics.push(createDiagnostic(document, hook.start, hook.end, `Lifecycle hook \`${hook.name}\` must be followed by a block.`, vscode.DiagnosticSeverity.Error, "wrn-invalid-lifecycle-hook")); - index = hook.end; - continue; - } - const closingBrace = findMatchingBrace(source, openingBrace); - index = closingBrace === -1 ? lifecycleEnd : closingBrace + 1; - } - } - return diagnostics; - } - function validateWatchBlocks(document, source, rootKind, rootMatch) { - if (rootKind !== "component" && rootKind !== "layout") { - return []; - } - const diagnostics = []; - const bodyRange = getRootBodyRange(source, rootMatch); - const states = findStateDeclarations(source, bodyRange.start, bodyRange.end); - const watches = findWatchDeclarations(source, bodyRange.start, bodyRange.end); - for (const watch of watches) { - if (!watch.state) { - diagnostics.push(createDiagnostic(document, watch.watchStart, watch.watchEnd, "`watch` must be followed by a declared state name.", vscode.DiagnosticSeverity.Error, "wrn-invalid-watch")); - continue; - } - if (!states.has(watch.state.name)) { - diagnostics.push(createDiagnostic(document, watch.state.start, watch.state.end, `Cannot watch undeclared state \`${watch.state.name}\`.`, vscode.DiagnosticSeverity.Error, "wrn-unknown-watch-state")); - } - if (watch.openingBrace === -1) { - diagnostics.push(createDiagnostic(document, watch.state.start, watch.state.end, `Watcher for \`${watch.state.name}\` must be followed by a block.`, vscode.DiagnosticSeverity.Error, "wrn-invalid-watch")); - } - } - return diagnostics; - } - function validateRequiredView(document, source, rootKind, rootMatch) { - const bodyRange = getRootBodyRange(source, rootMatch); - const body = source.slice(bodyRange.start, bodyRange.end); - if (/\bview\s*\{/.test(body)) { - return []; - } - return [ - createDiagnostic(document, rootMatch.index, rootMatch.index + rootMatch[0].length, `The ${rootKind} \`${rootMatch[2]}\` does not contain a \`view { ... }\` block.`, vscode.DiagnosticSeverity.Warning, "wrn-missing-view") - ]; - } - function validateLayoutUsage(document, source, rootKind, rootMatch) { - const diagnostics = []; - if (rootKind !== "page") { - const bodyRange = getRootBodyRange(source, rootMatch); - const layoutMember = findRootMembers(source, bodyRange.start, bodyRange.end).find((member) => member.name === "layout"); - if (layoutMember) { - diagnostics.push(createDiagnostic(document, layoutMember.start, layoutMember.end, '`layout = "..."` is only valid inside a page.', vscode.DiagnosticSeverity.Error, "wrn-invalid-layout-member")); - } - } - return diagnostics; - } - function validateV060Features(document, source) { - const diagnostics = []; - const duplicateRuntimeFunctions = new Map; - for (const match of source.matchAll(/\b(client|server|shared)\s+(?:async\s+)?function\s+([A-Za-z_$][\w$]*)/g)) { - const key = `${match[1]}:${match[2]}`; - if (duplicateRuntimeFunctions.has(key)) { - diagnostics.push(createDiagnostic(document, match.index, match.index + match[0].length, `Duplicate ${match[1]} function '${match[2]}'.`, vscode.DiagnosticSeverity.Error, "WRN-FUNCTION-DUPLICATE")); - } else - duplicateRuntimeFunctions.set(key, match.index); - } - for (const match of source.matchAll(/\$emit\s*\(/g)) { - diagnostics.push(createDiagnostic(document, match.index, match.index + match[0].length, "Use typed output.name(payload) instead of deprecated $emit().", vscode.DiagnosticSeverity.Warning, "WRN-OUTPUT-LEGACY-EMIT")); - } - for (const match of source.matchAll(/\bevent\.detail\b/g)) { - diagnostics.push(createDiagnostic(document, match.index, match.index + match[0].length, "Component output handlers receive payload directly.", vscode.DiagnosticSeverity.Warning, "WRN-OUTPUT-LEGACY-DETAIL")); - } - for (const match of source.matchAll(/\bserver\s+(?:async\s+)?function\b[\s\S]*?\b(window|document|localStorage|navigator)\b/g)) { - const offset = match.index + match[0].lastIndexOf(match[1]); - diagnostics.push(createDiagnostic(document, offset, offset + match[1].length, `Browser API '${match[1]}' is unavailable in a server function.`, vscode.DiagnosticSeverity.Error, "WRN-SERVER-BROWSER-API")); - } - return diagnostics; - } - function validateDocument(document) { - if (document.languageId !== WRN_LANGUAGE_ID) { - return []; - } - const source = document.getText(); - if (!source.trim()) { - return []; - } - const diagnostics = []; - const declaration = findTopLevelDeclaration(document, source); - if (declaration.diagnostic) { - diagnostics.push(declaration.diagnostic); - diagnostics.push(...validateBalancedCharacters(document, source)); - return diagnostics; - } - if (!VALID_TOP_LEVEL_KINDS.has(declaration.kind)) { - diagnostics.push(createDiagnostic(document, declaration.match.index, declaration.match.index + declaration.match[0].length, `Unsupported WRN declaration \`${declaration.kind}\`.`, vscode.DiagnosticSeverity.Error, "wrn-invalid-kind")); - return diagnostics; - } - diagnostics.push(...validateBalancedCharacters(document, source)); - diagnostics.push(...validateHtmlTags(document, source)); - diagnostics.push(...validateRootMembers(document, source, declaration.kind, declaration.match)); - diagnostics.push(...validateLifecycleBlocks(document, source, declaration.kind, declaration.match)); - diagnostics.push(...validateWatchBlocks(document, source, declaration.kind, declaration.match)); - diagnostics.push(...validateRequiredView(document, source, declaration.kind, declaration.match)); - diagnostics.push(...validateLayoutUsage(document, source, declaration.kind, declaration.match)); - diagnostics.push(...validateV060Features(document, source)); - return diagnostics; - } - function registerDiagnostics(context) { - const collection = vscode.languages.createDiagnosticCollection(COLLECTION_NAME); - const timers = new Map; - const update = (document) => { - if (document.languageId !== WRN_LANGUAGE_ID) { - return; - } - const key = document.uri.toString(); - const previousTimer = timers.get(key); - if (previousTimer) { - clearTimeout(previousTimer); - timers.delete(key); - } - const configuration = vscode.workspace.getConfiguration("wrnexus", document.uri); - if (!configuration.get("diagnostics.enable", true)) { - collection.delete(document.uri); - return; - } - const timer = setTimeout(() => { - timers.delete(key); - collection.set(document.uri, validateDocument(document)); - }, 150); - timers.set(key, timer); - }; - for (const document of vscode.workspace.textDocuments) { - update(document); - } - context.subscriptions.push(collection, vscode.workspace.onDidOpenTextDocument(update), vscode.workspace.onDidChangeTextDocument((event) => { - update(event.document); - }), vscode.workspace.onDidSaveTextDocument(update), vscode.workspace.onDidChangeConfiguration((event) => { - if (!event.affectsConfiguration("wrnexus.diagnostics.enable")) { - return; - } - for (const document of vscode.workspace.textDocuments) { - update(document); - } - }), vscode.workspace.onDidCloseTextDocument((document) => { - const key = document.uri.toString(); - const timer = timers.get(key); - if (timer) { - clearTimeout(timer); - timers.delete(key); - } - collection.delete(document.uri); - }), { - dispose() { - for (const timer of timers.values()) { - clearTimeout(timer); - } - timers.clear(); - } - }); - } - module2.exports = { - findTopLevelDeclaration, - maskLeadingTrivia, - registerDiagnostics, - validateBalancedCharacters, - validateDocument, - validateHtmlTags, - validateLifecycleBlocks, - validateLayoutUsage, - validateRootMembers, - validateWatchBlocks - }; -}); - -// editors/vscode/src/v060-language.js -var require_v060_language = __commonJS((exports2, module2) => { - var vscode = require("vscode"); - var selector = { language: "wrn" }; - var WRN_GLOB = "**/*.wrn"; - var EXCLUDE_GLOB = "**/{node_modules,dist,.wrnexus,.git}/**"; - function escapeRegExp(value) { - return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - } - function identifierAt(document, position) { - const range = document.getWordRangeAtPosition(position, /[A-Za-z_$][A-Za-z0-9_$]*/); - if (!range) - return null; - return { name: document.getText(range), range }; - } - function occurrences(document, name) { - const result = []; - const pattern = new RegExp(`\\b${escapeRegExp(name)}\\b`, "g"); - const source = document.getText(); - let match; - while ((match = pattern.exec(source)) !== null) { - result.push(new vscode.Range(document.positionAt(match.index), document.positionAt(match.index + name.length))); - } - return result; - } - function declarationRanges(document, name) { - const source = document.getText(); - const escaped = escapeRegExp(name); - const patterns = [ - new RegExp(`\\b(?:page|component|layout)\\s+(${escaped})\\b`, "g"), - new RegExp(`\\b(?:global|page)\\s+store\\s+(${escaped})\\b`, "g"), - new RegExp(`\\b(?:client|server|shared)?\\s*(?:async\\s+)?function\\s+(${escaped})\\b`, "g"), - new RegExp(`\\b(?:client|server|shared)?\\s*state\\s+(${escaped})\\b`, "g"), - new RegExp(`\\boutputs\\s*\\{[\\s\\S]*?\\b(${escaped})\\s*\\(`, "g"), - new RegExp(`\\b(?:props|state|computed)\\s*\\{[\\s\\S]*?\\b(${escaped})(?:\\?|\\s)*(?=[:=])`, "g"), - new RegExp(`\\bimport(?:\\s+type)?[\\s\\S]*?\\b(${escaped})\\b[\\s\\S]*?\\bfrom\\b`, "g") - ]; - const ranges = []; - for (const pattern of patterns) { - let match; - while ((match = pattern.exec(source)) !== null) { - const relative = match[0].lastIndexOf(match[1]); - const start = match.index + relative; - ranges.push(new vscode.Range(document.positionAt(start), document.positionAt(start + name.length))); - } - } - return ranges; - } - async function wrnDocuments() { - const uris = await vscode.workspace.findFiles(WRN_GLOB, EXCLUDE_GLOB, 1000); - const open = new Map(vscode.workspace.textDocuments.filter((item) => item.languageId === "wrn").map((item) => [item.uri.toString(), item])); - return Promise.all(uris.map((uri) => open.get(uri.toString()) || vscode.workspace.openTextDocument(uri))); - } - async function provideReferences(document, position, context, token) { - const symbol = identifierAt(document, position); - if (!symbol || token.isCancellationRequested) - return []; - const locations = []; - for (const candidate of await wrnDocuments()) { - if (token.isCancellationRequested) - return locations; - for (const range of occurrences(candidate, symbol.name)) { - if (!context.includeDeclaration && declarationRanges(candidate, symbol.name).some((decl) => decl.isEqual(range))) - continue; - locations.push(new vscode.Location(candidate.uri, range)); - } - } - return locations; - } - async function prepareRename(document, position) { - const symbol = identifierAt(document, position); - if (!symbol) - throw new Error("Place the cursor on a WRN identifier."); - return { range: symbol.range, placeholder: symbol.name }; - } - async function provideRenameEdits(document, position, newName, token) { - if (!/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(newName)) - throw new Error("The new WRN name is not a valid identifier."); - const symbol = identifierAt(document, position); - if (!symbol) - return null; - const edit = new vscode.WorkspaceEdit; - for (const candidate of await wrnDocuments()) { - if (token.isCancellationRequested) - return edit; - for (const range of occurrences(candidate, symbol.name)) - edit.replace(candidate.uri, range, newName); - } - return edit; - } - function importRanges(source) { - const ranges = []; - const pattern = /^import(?:\s+type)?[\s\S]*?(?:;\s*|\n(?=import|\s*(?:page|component|layout|global\s+store|page\s+store)\b))/gm; - let match; - while ((match = pattern.exec(source)) !== null) - ranges.push({ start: match.index, end: match.index + match[0].length, text: match[0].trim() }); - return ranges; - } - function organizeImportsEdit(document) { - const source = document.getText(); - const imports = importRanges(source); - if (imports.length < 2) - return null; - const ordered = [...imports].sort((a, b) => { - const aType = /^import\s+type\b/.test(a.text) ? 0 : 1; - const bType = /^import\s+type\b/.test(b.text) ? 0 : 1; - return aType - bType || a.text.localeCompare(b.text); - }); - const replacement = ordered.map((entry) => entry.text.replace(/;$/, "")).join(` -`) + ` -`; - const start = imports[0].start; - const end = imports.at(-1).end; - if (source.slice(start, end) === replacement) - return null; - return vscode.TextEdit.replace(new vscode.Range(document.positionAt(start), document.positionAt(end)), replacement); - } - function provideCodeActions(document, range, context) { - const actions = []; - const organizeEdit = organizeImportsEdit(document); - if (organizeEdit) { - const action = new vscode.CodeAction("Organize WRN imports", vscode.CodeActionKind.SourceOrganizeImports); - action.edit = new vscode.WorkspaceEdit; - action.edit.set(document.uri, [organizeEdit]); - actions.push(action); - } - for (const diagnostic of context.diagnostics) { - if (diagnostic.code === "WRN-OUTPUT-LEGACY-EMIT") { - const source = document.getText(diagnostic.range); - const match = /\$emit\(\s*["']([A-Za-z_$][\w$]*)["']\s*,?/.exec(source); - if (!match) - continue; - const action = new vscode.CodeAction(`Convert $emit to output.${match[1]}`, vscode.CodeActionKind.QuickFix); - action.diagnostics = [diagnostic]; - action.isPreferred = true; - action.edit = new vscode.WorkspaceEdit; - action.edit.replace(document.uri, diagnostic.range, source.replace(/\$emit\(\s*["'][A-Za-z_$][\w$]*["']\s*,?\s*/, `output.${match[1]}(`)); - actions.push(action); - } - } - return actions; - } - function registerV060LanguageFeatures(context) { - context.subscriptions.push(vscode.languages.registerReferenceProvider(selector, { provideReferences }), vscode.languages.registerRenameProvider(selector, { prepareRename, provideRenameEdits }), vscode.languages.registerCodeActionsProvider(selector, { provideCodeActions }, { - providedCodeActionKinds: [ - vscode.CodeActionKind.QuickFix, - vscode.CodeActionKind.SourceOrganizeImports - ] - })); - } - module2.exports = { - declarationRanges, - importRanges, - organizeImportsEdit, - registerV060LanguageFeatures - }; -}); - // editors/vscode/src/extension.js -var vscode = require("vscode"); var path = require("node:path"); +var vscode = require("vscode"); var { LanguageClient, TransportKind } = require_main5(); -var { formatWrn } = require_formatter(); -var { registerCompletionProvider } = require_completion2(); -var { registerDefinitionProvider } = require_definition2(); -var { registerComponentIntelligence } = require_component_intelligence(); -var { registerDiagnostics } = require_diagnostics(); -var { registerV060LanguageFeatures } = require_v060_language(); -var compiler = null; -try { - compiler = require_compiler(); -} catch (error) { - console.warn("[wrnexus] compiler bundle not found; compiler diagnostics disabled.", error instanceof Error ? error.message : String(error)); -} var WRN_LANGUAGE_ID = "wrn"; -var COMPILER_DIAGNOSTIC_COLLECTION = "wrnexus-compiler"; -function registerFormatter(context) { - const selector = { - language: WRN_LANGUAGE_ID - }; - const provider = vscode.languages.registerDocumentFormattingEditProvider(selector, { - provideDocumentFormattingEdits(document, options, token) { - if (token.isCancellationRequested) { - return []; - } - const configuration = vscode.workspace.getConfiguration("wrnexus", document.uri); - const enabled = configuration.get("format.enable", true); - if (!enabled) { - return []; - } - const source = document.getText(); - if (!source.trim()) { - return []; - } - const printWidth = configuration.get("formatting.printWidth", 100); - const multilineAttributes = configuration.get("formatting.multilineAttributes", true); - try { - const formatted = formatWrn(source, { - tabSize: options.tabSize || 4, - insertSpaces: options.insertSpaces !== false, - printWidth, - multilineAttributes - }); - if (typeof formatted !== "string" || formatted === source) { - return []; - } - const fullRange = new vscode.Range(document.positionAt(0), document.positionAt(source.length)); - return [vscode.TextEdit.replace(fullRange, formatted)]; - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - console.error("[wrnexus] formatting failed:", message); - vscode.window.showErrorMessage(`WRNexus formatting failed: ${message}`); - return []; - } - } - }); - context.subscriptions.push(provider); -} -function toCompilerDiagnostic(document, error) { - const message = error && typeof error === "object" && "message" in error ? String(error.message) : "Failed to compile .wrn file."; - const offsetMatch = /offset\s+(\d+)/i.exec(message); - let range; - if (offsetMatch) { - const requestedOffset = Number(offsetMatch[1]); - const safeOffset = Math.max(0, Math.min(requestedOffset, document.getText().length)); - const start = document.positionAt(safeOffset); - const wordRange = document.getWordRangeAtPosition(start); - range = wordRange || new vscode.Range(start, document.positionAt(Math.min(safeOffset + 1, document.getText().length))); - } else { - const lastLine = document.lineAt(Math.max(0, document.lineCount - 1)); - range = lastLine.range; - } - const diagnostic = new vscode.Diagnostic(range, message, vscode.DiagnosticSeverity.Error); - diagnostic.source = "WRNexus Compiler"; - diagnostic.code = "wrn-compiler-error"; - return diagnostic; -} -function registerCompilerDiagnostics(context) { - const collection = vscode.languages.createDiagnosticCollection(COMPILER_DIAGNOSTIC_COLLECTION); - const timers = new Map; - const run = (document) => { - if (document.languageId !== WRN_LANGUAGE_ID) { - return; - } - const configuration = vscode.workspace.getConfiguration("wrnexus", document.uri); - const enabled = configuration.get("diagnostics.enable", true); - if (!enabled) { - collection.delete(document.uri); - return; - } - if (!compiler || typeof compiler.compileWireFile !== "function") { - collection.delete(document.uri); - return; - } - const source = document.getText(); - if (!source.trim()) { - collection.delete(document.uri); - return; - } - try { - compiler.compileWireFile(source); - collection.set(document.uri, []); - } catch (error) { - collection.set(document.uri, [toCompilerDiagnostic(document, error)]); - } - }; - const schedule = (document) => { - if (document.languageId !== WRN_LANGUAGE_ID) { - return; - } - const key = document.uri.toString(); - const existing = timers.get(key); - if (existing) { - clearTimeout(existing); - } - const timer = setTimeout(() => { - timers.delete(key); - run(document); - }, 250); - timers.set(key, timer); - }; - for (const document of vscode.workspace.textDocuments) { - run(document); - } - context.subscriptions.push(collection, vscode.workspace.onDidOpenTextDocument(run), vscode.workspace.onDidChangeTextDocument((event) => { - schedule(event.document); - }), vscode.workspace.onDidSaveTextDocument(run), vscode.workspace.onDidCloseTextDocument((document) => { - const key = document.uri.toString(); - const timer = timers.get(key); - if (timer) { - clearTimeout(timer); - timers.delete(key); - } - collection.delete(document.uri); - }), vscode.workspace.onDidChangeConfiguration((event) => { - if (!event.affectsConfiguration("wrnexus.diagnostics.enable")) { - return; - } - for (const document of vscode.workspace.textDocuments) { - run(document); - } - }), { - dispose() { - for (const timer of timers.values()) { - clearTimeout(timer); - } - timers.clear(); - } - }); -} -function registerSemanticTokens(context) { - context.subscriptions.push(vscode.languages.registerDocumentSemanticTokensProvider({ - language: WRN_LANGUAGE_ID - }, wrnSemanticTokensProvider, semanticTokenLegend)); -} +var client; async function recoverWrnLanguage(document) { if (!document.fileName.toLowerCase().endsWith(".wrn")) return; @@ -32181,180 +22719,22 @@ async function recoverWrnLanguage(document) { } } async function activate(context) { - for (const document of vscode.workspace.textDocuments) { + for (const document of vscode.workspace.textDocuments) recoverWrnLanguage(document); - } - context.subscriptions.push(vscode.workspace.onDidOpenTextDocument((document) => { - recoverWrnLanguage(document); - })); - const useLanguageServer = vscode.workspace.getConfiguration("wrnexus").get("languageServer.enable", true); - let languageServerStarted = false; - if (useLanguageServer) { - const module2 = path.join(context.extensionPath, "src", "language-server.cjs"); - const serverOptions = { - run: { module: module2, transport: TransportKind.stdio }, - debug: { module: module2, transport: TransportKind.stdio, options: { execArgv: ["--nolazy"] } } - }; - const client = new LanguageClient("wrnexusLanguageServer", "WRNexus Language Server", serverOptions, { - documentSelector: [{ scheme: "file", language: WRN_LANGUAGE_ID }] - }); - try { - await client.start(); - languageServerStarted = true; - context.subscriptions.push({ dispose: () => void client.stop() }); - } catch (error) { - console.warn("[wrnexus] language server failed to start; using built-in providers.", error instanceof Error ? error.message : String(error)); - } - } - if (!languageServerStarted) { - registerDiagnostics(context); - registerCompilerDiagnostics(context); - registerCompletionProvider(context); - registerDefinitionProvider(context); - registerFormatter(context); - } - registerComponentIntelligence(context); - registerSemanticTokens(context); - registerV060LanguageFeatures(context); + context.subscriptions.push(vscode.workspace.onDidOpenTextDocument((document) => void recoverWrnLanguage(document))); + if (!vscode.workspace.getConfiguration("wrnexus").get("languageServer.enable", true)) + return; + const module2 = path.join(context.extensionPath, "src", "language-server.cjs"); + client = new LanguageClient("wrnexusLanguageServer", "WRNexus Language Server", { + run: { module: module2, transport: TransportKind.stdio }, + debug: { module: module2, transport: TransportKind.stdio, options: { execArgv: ["--nolazy"] } } + }, { documentSelector: [{ scheme: "file", language: WRN_LANGUAGE_ID }] }); + await client.start(); } -function deactivate() {} -var semanticTokenLegend = new vscode.SemanticTokensLegend(["variable"], ["declaration", "modification"]); -function escapeRegExp(value) { - return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); +async function deactivate() { + const running = client; + client = undefined; + if (running) + await running.stop(); } -function collectStateVariables(text) { - const states = new Map; - const statePattern = /\bstate\s+([A-Za-z_$][A-Za-z0-9_$]*)\s*(?==|;|\r?$)/gm; - let match; - while ((match = statePattern.exec(text)) !== null) { - const name = match[1]; - if (!name) { - continue; - } - const offset = match.index + match[0].lastIndexOf(name); - const declarations = states.get(name) || new Set; - declarations.add(offset); - states.set(name, declarations); - } - return states; -} -function collectCommentRanges(text) { - const ranges = []; - let index = 0; - let quote = null; - let escaped = false; - while (index < text.length) { - const current = text[index]; - const next = text[index + 1]; - if (quote) { - if (escaped) { - escaped = false; - index++; - continue; - } - if (current === "\\") { - escaped = true; - index++; - continue; - } - if (current === quote) { - quote = null; - } - index++; - continue; - } - if (current === '"' || current === "'" || current === "`") { - quote = current; - index++; - continue; - } - if (current === "/" && next === "/") { - const start = index; - index += 2; - while (index < text.length && text[index] !== ` -`) { - index++; - } - ranges.push({ start, end: index }); - continue; - } - if (current === "/" && next === "*") { - const start = index; - index += 2; - while (index < text.length && !(text[index] === "*" && text[index + 1] === "/")) { - index++; - } - index = Math.min(text.length, index + 2); - ranges.push({ start, end: index }); - continue; - } - index++; - } - return ranges; -} -function isInsideComment(offset, ranges) { - let low = 0; - let high = ranges.length - 1; - while (low <= high) { - const middle = Math.floor((low + high) / 2); - const range = ranges[middle]; - if (!range) { - return false; - } - if (offset < range.start) { - high = middle - 1; - } else if (offset >= range.end) { - low = middle + 1; - } else { - return true; - } - } - return false; -} -function isStateModification(text, offset, length) { - const after = text.slice(offset + length).match(/^\s*(=|\+=|-=|\*=|\/=|%=|\+\+|--)/); - if (after) { - return true; - } - const before = text.slice(Math.max(0, offset - 8), offset).match(/(\+\+|--)\s*$/); - return Boolean(before); -} -var wrnSemanticTokensProvider = { - provideDocumentSemanticTokens(document, token) { - const builder = new vscode.SemanticTokensBuilder(semanticTokenLegend); - const text = document.getText(); - const states = collectStateVariables(text); - const commentRanges = collectCommentRanges(text); - for (const [name, declarationOffsets] of states) { - if (token.isCancellationRequested) { - return builder.build(); - } - const pattern = new RegExp(`(? { - if (document.languageId !== WRN_LANGUAGE_ID) { - return; - } - - const configuration = vscode.workspace.getConfiguration("wrnexus", document.uri); - - const enabled = configuration.get("diagnostics.enable", true); - - if (!enabled) { - collection.delete(document.uri); - return; - } - - if (!compiler || typeof compiler.compileWireFile !== "function") { - collection.delete(document.uri); - return; - } - - const source = document.getText(); - - if (!source.trim()) { - collection.delete(document.uri); - return; - } - - try { - compiler.compileWireFile(source); - - collection.set(document.uri, []); - } catch (error) { - collection.set(document.uri, [toCompilerDiagnostic(document, error)]); - } - }; - - /** - * @param {vscode.TextDocument} document - */ - const schedule = (document) => { - if (document.languageId !== WRN_LANGUAGE_ID) { - return; - } - - const key = document.uri.toString(); - - const existing = timers.get(key); - - if (existing) { - clearTimeout(existing); - } - - const timer = setTimeout(() => { - timers.delete(key); - run(document); - }, 250); - - timers.set(key, timer); - }; - - for (const document of vscode.workspace.textDocuments) { - run(document); - } - - context.subscriptions.push( - collection, - - vscode.workspace.onDidOpenTextDocument(run), - - vscode.workspace.onDidChangeTextDocument((event) => { - schedule(event.document); - }), - - vscode.workspace.onDidSaveTextDocument(run), - - vscode.workspace.onDidCloseTextDocument((document) => { - const key = document.uri.toString(); - - const timer = timers.get(key); - - if (timer) { - clearTimeout(timer); - timers.delete(key); - } - - collection.delete(document.uri); - }), - - vscode.workspace.onDidChangeConfiguration((event) => { - if (!event.affectsConfiguration("wrnexus.diagnostics.enable")) { - return; - } - - for (const document of vscode.workspace.textDocuments) { - run(document); - } - }), - - { - dispose() { - for (const timer of timers.values()) { - clearTimeout(timer); - } - - timers.clear(); - }, - }, - ); -} - -/** - * Register semantic highlighting for WRN state declarations and references. - * - * @param {vscode.ExtensionContext} context - */ -function registerSemanticTokens(context) { - context.subscriptions.push( - vscode.languages.registerDocumentSemanticTokensProvider( - { - language: WRN_LANGUAGE_ID, - }, - wrnSemanticTokensProvider, - semanticTokenLegend, - ), - ); -} - -/** - * Recover files affected by older workspace settings that associated `*.wrn` - * with the obsolete `wire` language id. Normal language contribution matching - * happens before activation; this fallback is intentionally limited to Plain - * Text and the legacy id so explicit third-party associations are respected. - * - * @param {vscode.TextDocument} document - */ +/** @param {vscode.TextDocument} document */ async function recoverWrnLanguage(document) { if (!document.fileName.toLowerCase().endsWith(".wrn")) return; if (!["plaintext", "wire"].includes(document.languageId)) return; - try { await vscode.languages.setTextDocumentLanguage(document, WRN_LANGUAGE_ID); } catch (error) { @@ -326,281 +23,32 @@ async function recoverWrnLanguage(document) { } } -/** - * Activate the WRNexus VS Code extension. - * - * @param {vscode.ExtensionContext} context - */ +/** @param {vscode.ExtensionContext} context */ async function activate(context) { - for (const document of vscode.workspace.textDocuments) { - void recoverWrnLanguage(document); - } - + for (const document of vscode.workspace.textDocuments) void recoverWrnLanguage(document); context.subscriptions.push( - vscode.workspace.onDidOpenTextDocument((document) => { - void recoverWrnLanguage(document); - }), + vscode.workspace.onDidOpenTextDocument((document) => void recoverWrnLanguage(document)), ); - const useLanguageServer = vscode.workspace - .getConfiguration("wrnexus") - .get("languageServer.enable", true); - let languageServerStarted = false; - if (useLanguageServer) { - const module = path.join(context.extensionPath, "src", "language-server.cjs"); - const serverOptions = { + if (!vscode.workspace.getConfiguration("wrnexus").get("languageServer.enable", true)) return; + + const module = path.join(context.extensionPath, "src", "language-server.cjs"); + client = new LanguageClient( + "wrnexusLanguageServer", + "WRNexus Language Server", + { run: { module, transport: TransportKind.stdio }, debug: { module, transport: TransportKind.stdio, options: { execArgv: ["--nolazy"] } }, - }; - const client = new LanguageClient( - "wrnexusLanguageServer", - "WRNexus Language Server", - serverOptions, - { - documentSelector: [{ scheme: "file", language: WRN_LANGUAGE_ID }], - }, - ); - try { - await client.start(); - languageServerStarted = true; - context.subscriptions.push({ dispose: () => void client.stop() }); - } catch (error) { - console.warn( - "[wrnexus] language server failed to start; using built-in providers.", - error instanceof Error ? error.message : String(error), - ); - } - } - if (!languageServerStarted) { - registerDiagnostics(context); - registerCompilerDiagnostics(context); - registerCompletionProvider(context); - registerDefinitionProvider(context); - registerFormatter(context); - } - // Package/local component metadata is not yet part of the LSP workspace - // catalog, so keep prop/event completion, hover, and validation available in - // both LSP and built-in-provider modes. - registerComponentIntelligence(context); - registerSemanticTokens(context); - registerV060LanguageFeatures(context); + }, + { documentSelector: [{ scheme: "file", language: WRN_LANGUAGE_ID }] }, + ); + await client.start(); } -function deactivate() {} - -const semanticTokenLegend = new vscode.SemanticTokensLegend( - ["variable"], - ["declaration", "modification"], -); - -/** - * @param {string} value - * @returns {string} - */ -function escapeRegExp(value) { - return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); +async function deactivate() { + const running = client; + client = undefined; + if (running) await running.stop(); } -/** - * @param {string} text - * @returns {Map>} - */ -function collectStateVariables(text) { - const states = new Map(); - const statePattern = /\bstate\s+([A-Za-z_$][A-Za-z0-9_$]*)\s*(?==|;|\r?$)/gm; - - let match; - - while ((match = statePattern.exec(text)) !== null) { - const name = match[1]; - - if (!name) { - continue; - } - - const offset = match.index + match[0].lastIndexOf(name); - const declarations = states.get(name) || new Set(); - - declarations.add(offset); - states.set(name, declarations); - } - - return states; -} - -/** - * Comments are excluded, but quoted attribute expressions are deliberately - * included because WRN state references commonly appear in values such as - * class:flex="centered" and @click="enabled = !enabled". - * - * @param {string} text - * @returns {Array<{start: number, end: number}>} - */ -function collectCommentRanges(text) { - const ranges = []; - let index = 0; - let quote = null; - let escaped = false; - - while (index < text.length) { - const current = text[index]; - const next = text[index + 1]; - - if (quote) { - if (escaped) { - escaped = false; - index++; - continue; - } - - if (current === "\\") { - escaped = true; - index++; - continue; - } - - if (current === quote) { - quote = null; - } - - index++; - continue; - } - - if (current === '"' || current === "'" || current === "`") { - quote = current; - index++; - continue; - } - - if (current === "/" && next === "/") { - const start = index; - index += 2; - - while (index < text.length && text[index] !== "\n") { - index++; - } - - ranges.push({ start, end: index }); - continue; - } - - if (current === "/" && next === "*") { - const start = index; - index += 2; - - while (index < text.length && !(text[index] === "*" && text[index + 1] === "/")) { - index++; - } - - index = Math.min(text.length, index + 2); - ranges.push({ start, end: index }); - continue; - } - - index++; - } - - return ranges; -} - -/** - * @param {number} offset - * @param {Array<{start: number, end: number}>} ranges - * @returns {boolean} - */ -function isInsideComment(offset, ranges) { - let low = 0; - let high = ranges.length - 1; - - while (low <= high) { - const middle = Math.floor((low + high) / 2); - const range = ranges[middle]; - - if (!range) { - return false; - } - - if (offset < range.start) { - high = middle - 1; - } else if (offset >= range.end) { - low = middle + 1; - } else { - return true; - } - } - - return false; -} - -/** - * @param {string} text - * @param {number} offset - * @param {number} length - * @returns {boolean} - */ -function isStateModification(text, offset, length) { - const after = text.slice(offset + length).match(/^\s*(=|\+=|-=|\*=|\/=|%=|\+\+|--)/); - - if (after) { - return true; - } - - const before = text.slice(Math.max(0, offset - 8), offset).match(/(\+\+|--)\s*$/); - - return Boolean(before); -} - -const wrnSemanticTokensProvider = { - /** - * @param {vscode.TextDocument} document - * @param {vscode.CancellationToken} token - */ - provideDocumentSemanticTokens(document, token) { - const builder = new vscode.SemanticTokensBuilder(semanticTokenLegend); - const text = document.getText(); - const states = collectStateVariables(text); - const commentRanges = collectCommentRanges(text); - - for (const [name, declarationOffsets] of states) { - if (token.isCancellationRequested) { - return builder.build(); - } - - const pattern = new RegExp(`(? import_node_path3.extname(path) === ".wrn").slice(0, limit)) { + let source; + try { + source = import_node_fs3.readFileSync(file, "utf8"); + } catch { + continue; + } + for (const match of source.matchAll(pattern)) { + const before = source.slice(0, match.index).split(/\r?\n/); + const start = { line: before.length - 1, character: before.at(-1)?.length ?? 0 }; + locations.push({ + uri: `file:///${file.replace(/\\/g, "/")}`, + range: { start, end: { line: start.line, character: start.character + name.length } } + }); + } + } + return locations; +} function extractComponentRefactor(document, range, name) { if (!/^[A-Z][A-Za-z0-9_$]*$/.test(name)) throw new Error("Component name must use PascalCase"); @@ -172599,7 +172630,7 @@ function diagnosticRange(diagnostic) { } }; } -function documentDiagnostics(document) { +function documentDiagnostics(document, options = {}) { if (Buffer.byteLength(document.text, "utf8") > 1048576) { return [ { @@ -172618,7 +172649,7 @@ function documentDiagnostics(document) { source: "wrnexus", message: diagnostic.message })); - if (syntax.some((diagnostic) => diagnostic.severity === 1)) + if (syntax.some((diagnostic) => diagnostic.severity === 1) || options.includeTypes === false) return syntax; const types = checkWrnSource(document.text, { filePath: documentPath(document.uri) }).map((diagnostic) => ({ range: { @@ -172688,19 +172719,6 @@ function documentSymbols(document) { }; }); } -function symbolLocations(document, position) { - const selected = wordAt(document.text, position); - if (!selected) - return []; - const pattern = new RegExp(`(? ({ - uri: document.uri, - range: { - start: positionAt2(document.text, match.index), - end: positionAt2(document.text, match.index + selected.word.length) - } - })); -} function definitionLocation(document, position) { const selected = wordAt(document.text, position); if (!selected) @@ -172734,6 +172752,55 @@ ${declaration[0]} function completionItems() { return WRN_COMPLETIONS.map((label) => ({ label, kind: 14, detail: "WRNexus language keyword" })); } +var semanticTokenTypes = ["variable"]; +var semanticTokenModifiers = ["declaration", "modification"]; +var semanticTokensLegend = { + tokenTypes: [...semanticTokenTypes], + tokenModifiers: [...semanticTokenModifiers] +}; +function semanticTokens(document) { + const declarations = new Map; + for (const match of document.text.matchAll(/\bstate\s+([A-Za-z_$][A-Za-z0-9_$]*)\s*(?==|;|\r?$)/gm)) { + const name = match[1]; + const offset = match.index + match[0].lastIndexOf(name); + const offsets = declarations.get(name) ?? new Set; + offsets.add(offset); + declarations.set(name, offsets); + } + const comments = [...document.text.matchAll(/\/\/[^\n]*|\/\*[\s\S]*?\*\//g)].map((match) => ({ + start: match.index, + end: match.index + match[0].length + })); + const tokens = []; + for (const [name, offsets] of declarations) { + const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + for (const match of document.text.matchAll(new RegExp(`(? offset >= range.start && offset < range.end)) + continue; + const position = positionAt2(document.text, offset); + const after = document.text.slice(offset + name.length).match(/^\s*(?:=|\+=|-=|\*=|\/=|%=|\+\+|--)/); + const before = document.text.slice(Math.max(0, offset - 8), offset).match(/(?:\+\+|--)\s*$/); + tokens.push({ + ...position, + length: name.length, + modifiers: offsets.has(offset) ? 1 : after || before ? 2 : 0 + }); + } + } + tokens.sort((a, b) => a.line - b.line || a.character - b.character); + const data = []; + let previousLine = 0; + let previousCharacter = 0; + for (const token of tokens) { + const deltaLine = token.line - previousLine; + const deltaCharacter = deltaLine === 0 ? token.character - previousCharacter : token.character; + data.push(deltaLine, deltaCharacter, token.length, 0, token.modifiers); + previousLine = token.line; + previousCharacter = token.character; + } + return { data }; +} // packages/language-server/src/server.ts var documents = new Map; @@ -172767,20 +172834,20 @@ function internalDiagnostic(document, error) { message: `WRNexus language analysis failed safely: ${message}` }; } -function safeDocumentDiagnostics(document) { +function safeDocumentDiagnostics(document, includeTypes = true) { try { - return documentDiagnostics(document); + return documentDiagnostics(document, { includeTypes }); } catch (error) { process.stderr.write(`[wrnexus-lsp] diagnostics failed for ${document.uri}: ${error instanceof Error ? error.stack ?? error.message : String(error)} `); return [internalDiagnostic(document, error)]; } } -function publish(document) { +function publish(document, includeTypes = true) { send({ jsonrpc: "2.0", method: "textDocument/publishDiagnostics", - params: { uri: document.uri, diagnostics: safeDocumentDiagnostics(document) } + params: { uri: document.uri, diagnostics: safeDocumentDiagnostics(document, includeTypes) } }); } function clearDiagnosticTimer(uri) { @@ -172796,7 +172863,7 @@ function schedulePublish(document) { diagnosticTimers.delete(document.uri); const current = documents.get(document.uri); if (current && current.version === expectedVersion) - publish(current); + publish(current, false); }, DIAGNOSTIC_DEBOUNCE_MS)); } function rememberDocument(document) { @@ -172818,7 +172885,7 @@ async function handle(message) { result(message.id, { serverInfo: { name: "WRNexus Language Server", version: "0.8.3" }, capabilities: { - textDocumentSync: 1, + textDocumentSync: { openClose: true, change: 1, save: true }, documentFormattingProvider: true, completionProvider: { triggerCharacters: ["<", "@", ":", "."] }, hoverProvider: true, @@ -172826,8 +172893,14 @@ async function handle(message) { referencesProvider: true, renameProvider: { prepareProvider: true }, documentSymbolProvider: true, + semanticTokensProvider: { legend: semanticTokensLegend, full: true }, codeActionProvider: { - codeActionKinds: ["quickfix", "refactor.extract", "refactor.rewrite"] + codeActionKinds: [ + "quickfix", + "refactor.extract", + "refactor.rewrite", + "source.organizeImports" + ] }, experimental: { wrnexusVirtualTypeScript: true } } @@ -172842,6 +172915,10 @@ async function handle(message) { result(message.id, null); break; case "exit": + for (const timer of diagnosticTimers.values()) + clearTimeout(timer); + diagnosticTimers.clear(); + documents.clear(); process.exit(0); break; case "textDocument/didOpen": { @@ -172861,6 +172938,15 @@ async function handle(message) { } break; } + case "textDocument/didSave": { + const document = documents.get(params.textDocument.uri); + if (document) { + clearDiagnosticTimer(document.uri); + clearWorkspaceIndexCache(workspaceRoot); + publish(document, true); + } + break; + } case "textDocument/didClose": clearDiagnosticTimer(params.textDocument.uri); documents.delete(params.textDocument.uri); @@ -172890,6 +172976,11 @@ async function handle(message) { result(message.id, document ? documentSymbols(document) : []); break; } + case "textDocument/semanticTokens/full": { + const document = documents.get(params.textDocument.uri); + result(message.id, document ? semanticTokens(document) : { data: [] }); + break; + } case "textDocument/hover": { const document = documents.get(params.textDocument.uri); result(message.id, document ? hover(document, params.position) : null); @@ -172902,7 +172993,8 @@ async function handle(message) { } case "textDocument/references": { const document = documents.get(params.textDocument.uri); - result(message.id, document ? symbolLocations(document, params.position) : []); + const selected = document ? wordAt(document.text, params.position) : null; + result(message.id, selected ? workspaceSymbolLocations(workspaceRoot, selected.word) : []); break; } case "textDocument/prepareRename": { @@ -172912,11 +173004,16 @@ async function handle(message) { } case "textDocument/rename": { const document = documents.get(params.textDocument.uri); - const edits = document ? symbolLocations(document, params.position).map(({ range }) => ({ - range, - newText: params.newName - })) : []; - result(message.id, document ? { changes: { [document.uri]: edits } } : null); + const selected = document ? wordAt(document.text, params.position) : null; + if (!selected || !/^[A-Za-z_$][\w$]*$/.test(params.newName)) { + result(message.id, null); + break; + } + const changes = {}; + for (const location of workspaceSymbolLocations(workspaceRoot, selected.word)) { + (changes[location.uri] ??= []).push({ range: location.range, newText: params.newName }); + } + result(message.id, { changes }); break; } case "textDocument/codeAction": { @@ -172937,6 +173034,70 @@ async function handle(message) { } } })); + const imports = [ + ...document.text.matchAll(/^import(?:\s+type)?[\s\S]*?(?:;\s*|\n(?=import|\s*(?:page|component|layout|global\s+store|page\s+store)\b))/gm) + ].map((match) => ({ + start: match.index, + end: match.index + match[0].length, + text: match[0].trim() + })); + if (imports.length > 1) { + const ordered = [...imports].sort((a, b) => { + const aType = /^import\s+type\b/.test(a.text) ? 0 : 1; + const bType = /^import\s+type\b/.test(b.text) ? 0 : 1; + return aType - bType || a.text.localeCompare(b.text); + }); + const replacement = ordered.map((entry) => entry.text.replace(/;$/, "")).join(` +`) + ` +`; + const start = imports[0].start; + const end = imports.at(-1).end; + if (document.text.slice(start, end) !== replacement) { + actions.push({ + title: "Organize WRN imports", + kind: "source.organizeImports", + edit: { + changes: { + [document.uri]: [ + { + range: { + start: positionAt2(document.text, start), + end: positionAt2(document.text, end) + }, + newText: replacement + } + ] + } + } + }); + } + } + for (const diagnostic of params.context?.diagnostics ?? []) { + if (diagnostic.code !== "WRN-OUTPUT-LEGACY-EMIT") + continue; + const start = offsetAt(document.text, diagnostic.range.start); + const end = offsetAt(document.text, diagnostic.range.end); + const source = document.text.slice(start, end); + const match = /\$emit\(\s*["']([A-Za-z_$][\w$]*)["']\s*,?/.exec(source); + if (!match) + continue; + actions.push({ + title: `Convert $emit to output.${match[1]}`, + kind: "quickfix", + diagnostics: [diagnostic], + isPreferred: true, + edit: { + changes: { + [document.uri]: [ + { + range: diagnostic.range, + newText: source.replace(/\$emit\(\s*["'][A-Za-z_$][\w$]*["']\s*,?\s*/, `output.${match[1]}(`) + } + ] + } + } + }); + } const selected = document.text.slice(document.text.split(/\r?\n/).slice(0, params.range.start.line).reduce((n, line) => n + line.length + 1, 0) + params.range.start.character, document.text.split(/\r?\n/).slice(0, params.range.end.line).reduce((n, line) => n + line.length + 1, 0) + params.range.end.character); if (selected.trim().startsWith("<")) { try { diff --git a/editors/vscode/test/language-server.test.js b/editors/vscode/test/language-server.test.js index 364f35c6..5b03ae8d 100644 --- a/editors/vscode/test/language-server.test.js +++ b/editors/vscode/test/language-server.test.js @@ -68,7 +68,13 @@ test("language server starts under Node and keeps official typed components clea messages.push({ jsonrpc: "2.0", method: "exit", params: {} }); const responses = run(messages); - assert.equal(responses.find((item) => item.id === 1)?.result?.serverInfo?.version, "0.8.3"); + const initialization = responses.find((item) => item.id === 1)?.result; + assert.equal(initialization?.serverInfo?.version, "0.8.3"); + assert.deepEqual(initialization?.capabilities?.textDocumentSync, { + openClose: true, + change: 1, + save: true, + }); const diagnostics = responses.filter((item) => item.method === "textDocument/publishDiagnostics"); assert.equal(diagnostics.length, 2); assert.deepEqual( @@ -77,6 +83,36 @@ test("language server starts under Node and keeps official typed components clea ); }); +test("language server defers expensive type analysis while typing and restores it on save", () => { + const uri = "file:///tmp/wrnexus-save-analysis.wrn"; + const valid = `component SaveAnalysis { + outputs { save(payload: { id: string }) } + functions { client function submit(): void { output.save({ id: "ok" }) } } + view { } +}`; + const invalid = valid.replace('id: "ok"', "id: 123"); + const responses = run([ + { jsonrpc: "2.0", id: 1, method: "initialize", params: { rootPath: root } }, + { jsonrpc: "2.0", method: "initialized", params: {} }, + { + jsonrpc: "2.0", + method: "textDocument/didOpen", + params: { textDocument: { uri, languageId: "wrn", version: 1, text: valid } }, + }, + { + jsonrpc: "2.0", + method: "textDocument/didChange", + params: { textDocument: { uri, version: 2 }, contentChanges: [{ text: invalid }] }, + }, + { jsonrpc: "2.0", method: "textDocument/didSave", params: { textDocument: { uri } } }, + { jsonrpc: "2.0", id: 2, method: "shutdown", params: {} }, + { jsonrpc: "2.0", method: "exit", params: {} }, + ]); + const diagnostics = responses.filter((item) => item.method === "textDocument/publishDiagnostics"); + assert.equal(diagnostics.length, 2); + assert.ok(diagnostics.at(-1).params.diagnostics.some((item) => item.code === "WRN-TYPE-2322")); +}); + test("language server formatter preserves code samples and compact sibling markup", () => { const source = `page Docs { view { diff --git a/package.json b/package.json index 2708bd33..06aa3def 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "wrnexus", - "version": "0.8.7", + "version": "0.8.8", "private": true, "type": "module", "description": "An SSR-first full-stack web framework with server-rendered reactive components. Bun-first, Node-friendly.", diff --git a/packages/ai/package.json b/packages/ai/package.json index a9b4ff0a..d4a3c0c7 100644 --- a/packages/ai/package.json +++ b/packages/ai/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/ai", - "version": "0.8.7", + "version": "0.8.8", "private": true, "type": "module", "description": "Zero-dependency Claude (Anthropic) client for WrNexus apps.", diff --git a/packages/auth/package.json b/packages/auth/package.json index 49692116..bfc8b748 100644 --- a/packages/auth/package.json +++ b/packages/auth/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/auth", - "version": "0.8.7", + "version": "0.8.8", "description": "Complete authentication, account security, MFA, passkeys, recovery, devices, risk, and audit system for WRNexusJS.", "type": "module", "sideEffects": false, diff --git a/packages/authz/package.json b/packages/authz/package.json index 496fc66b..97ddd518 100644 --- a/packages/authz/package.json +++ b/packages/authz/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/authz", - "version": "0.8.7", + "version": "0.8.8", "private": true, "type": "module", "main": "src/index.ts", diff --git a/packages/benchmark/package.json b/packages/benchmark/package.json index 0571bb4b..bc36173a 100644 --- a/packages/benchmark/package.json +++ b/packages/benchmark/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/benchmark", - "version": "0.8.7", + "version": "0.8.8", "type": "module", "description": "Deterministic benchmark runner and performance regression budgets for WRNexusJS.", "main": "src/index.ts", diff --git a/packages/cache/package.json b/packages/cache/package.json index cef198dc..4fdba904 100644 --- a/packages/cache/package.json +++ b/packages/cache/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/cache", - "version": "0.8.7", + "version": "0.8.8", "type": "module", "description": "Tag-aware memory and response caching with stale-while-revalidate for WRNexusJS.", "main": "src/index.ts", diff --git a/packages/captcha/package.json b/packages/captcha/package.json index 93a30a5a..e1fe85f3 100644 --- a/packages/captcha/package.json +++ b/packages/captcha/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/captcha", - "version": "0.8.7", + "version": "0.8.8", "description": "First-class CAPTCHA challenges, providers, verification guards, page gates, and WRNexusJS UI.", "type": "module", "sideEffects": false, diff --git a/packages/cli/package.json b/packages/cli/package.json index 83cc16db..c669062a 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/cli", - "version": "0.8.7", + "version": "0.8.8", "type": "module", "main": "src/index.ts", "exports": { diff --git a/packages/cli/src/types.ts b/packages/cli/src/types.ts index c15f04b7..9a6e27e9 100644 --- a/packages/cli/src/types.ts +++ b/packages/cli/src/types.ts @@ -24,6 +24,12 @@ function literalUnion(values: Iterable): string { return unique.length ? unique.map((value) => JSON.stringify(value)).join(" | ") : "never"; } +function generatedContractMap(name: string, members: string): string { + return members.trim() + ? `interface ${name} {\n${members}\n }` + : `type ${name} = Record;`; +} + function typeImport(fromDirectory: string, file: string): string { const path = relative(fromDirectory, file).replace(/\\/g, "/"); return JSON.stringify(path.startsWith(".") ? path : `./${path}`); @@ -203,24 +209,12 @@ declare namespace WRNexusGenerated { type TranslationKey = ${literalUnion(localeKeys)}; type QueueName = ${literalUnion(queueNames)}; type CacheKey = ${literalUnion(cacheKeys)}; - interface Components { -${componentMap} - } - interface ApiContracts { -${apiContracts} - } - interface MiddlewareContexts { -${middlewareContracts} - } - interface DatabaseQueries { -${databaseContracts} - } - interface RealtimeMessages { -${realtimeContracts} - } - interface QueuePayloads { -${queueContracts} - } + ${generatedContractMap("Components", componentMap)} + ${generatedContractMap("ApiContracts", apiContracts)} + ${generatedContractMap("MiddlewareContexts", middlewareContracts)} + ${generatedContractMap("DatabaseQueries", databaseContracts)} + ${generatedContractMap("RealtimeMessages", realtimeContracts)} + ${generatedContractMap("QueuePayloads", queueContracts)} type ApplicationConfig = ${configFile ? `(typeof import(${typeImport(typeDir, configFile)}))["default"]` : "Record"}; } `; diff --git a/packages/cli/src/update.ts b/packages/cli/src/update.ts index e68d2b87..10f2c484 100644 --- a/packages/cli/src/update.ts +++ b/packages/cli/src/update.ts @@ -33,6 +33,7 @@ import { pathToFileURL } from "node:url"; import { formatWrn, parse } from "@wrnexus/syntax"; import { AI_GUIDE, CLAUDE_MD } from "./ai-guide.ts"; import { inspectProject, type DoctorCheck } from "./doctor.ts"; +import { generateApplicationTypes } from "./types.ts"; /** The version of the CLI currently running (its own package.json). */ function cliVersion(): string { @@ -1783,6 +1784,10 @@ const MIGRATIONS: Migration[] = [ description: "Adds compatible imports, type checking, stores, and legacy behavior configuration.", apply(ctx) { + // Workspace manifests are updated alongside each configured application, + // but they are not themselves runnable apps. Keep app-owned declarations + // under the individual app roots instead of creating `/app`. + if (!existsSync(join(ctx.appRoot, "app", "pages"))) return; updateV060Config(ctx); const types = join(ctx.appRoot, "app", "types"); if (!existsSync(types)) { @@ -2115,6 +2120,17 @@ const MIGRATIONS: Migration[] = [ // select for non-link items as well as navigation items. }, }, + { + version: "0.8.8", + id: "0.8.8-editor-types-and-update-verification", + description: + "Adds typed page context support and regenerates lint-safe application declarations before update verification.", + apply() { + // No source rewrite is required. The 0.8.8 CLI regenerates application + // declarations immediately before verification, repairing stale empty + // interface contracts produced by earlier releases. + }, + }, ]; /** Release tooling uses this to require an explicit migration entry per version. */ @@ -2297,6 +2313,10 @@ export function blockingVerificationChecks(checks: DoctorCheck[]): DoctorCheck[] } function verifyApp(app: UpdatedApp): boolean { + // Generated declarations must reflect the newly installed CLI before the + // application's lint/typecheck scripts inspect them. This also repairs stale + // generated output left by an older framework version. + if (existsSync(join(app.root, "app", "pages"))) generateApplicationTypes(app.root); const health = inspectProject(app.root); const failed = blockingVerificationChecks(health); diff --git a/packages/cli/test/types.test.ts b/packages/cli/test/types.test.ts index f8f635ce..224cb007 100644 --- a/packages/cli/test/types.test.ts +++ b/packages/cli/test/types.test.ts @@ -48,6 +48,9 @@ test("generate types emits application-wide deterministic contracts", () => { expect(output).toContain('"/realtime/chat": RealtimeMessage<'); expect(output).toContain("interface QueuePayloads"); expect(output).toContain('"email": QueuePayload<'); + expect(output).toContain("type MiddlewareContexts = Record"); + expect(output).toContain("type DatabaseQueries = Record"); + expect(output).not.toMatch(/interface\s+\w+\s*\{\s*\}/); }); test("application checker validates every wrn source", () => { diff --git a/packages/cli/test/update.test.ts b/packages/cli/test/update.test.ts index d79c7f71..ad68887f 100644 --- a/packages/cli/test/update.test.ts +++ b/packages/cli/test/update.test.ts @@ -169,6 +169,30 @@ test("update verification skips unavailable scripts", () => { expect(verificationCommands({ check: "bun run lint" })).toEqual([["run", "check"]]); }); +test("0.6 type declarations are created only inside runnable apps", () => { + const workspaceRoot = mkdtempSync(join(tmpdir(), "wrnexus-update-workspace-")); + const appRoot = join(workspaceRoot, "apps", "web"); + mkdirSync(join(appRoot, "app", "pages"), { recursive: true }); + writeFileSync( + join(workspaceRoot, "package.json"), + JSON.stringify({ name: "workspace", wrnexus: { version: "0.5.9" } }), + ); + writeFileSync( + join(appRoot, "package.json"), + JSON.stringify({ name: "web", wrnexus: { version: "0.5.9" } }), + ); + + try { + updateApp(workspaceRoot, "0.6.0", false); + updateApp(appRoot, "0.6.0", false); + + expect(existsSync(join(workspaceRoot, "app", "types", "global.d.ts"))).toBe(false); + expect(existsSync(join(appRoot, "app", "types", "global.d.ts"))).toBe(true); + } finally { + rmSync(workspaceRoot, { recursive: true, force: true }); + } +}); + test("0.3 WRN source normalization is conservative and idempotent", async () => { const { migrateWrnSource } = await import("../src/update.ts"); const source = `component Card { diff --git a/packages/compiler/package.json b/packages/compiler/package.json index bc490e42..c662e582 100644 --- a/packages/compiler/package.json +++ b/packages/compiler/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/compiler", - "version": "0.8.7", + "version": "0.8.8", "type": "module", "main": "src/index.ts", "exports": { diff --git a/packages/content/package.json b/packages/content/package.json index 44864ecc..4bbd9ec3 100644 --- a/packages/content/package.json +++ b/packages/content/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/content", - "version": "0.8.7", + "version": "0.8.8", "type": "module", "description": "Typed Markdown content collections, loaders, indexes, feeds, and preview workflows for WRNexusJS.", "main": "./src/index.ts", diff --git a/packages/core/package.json b/packages/core/package.json index 07ba767c..189898d5 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/core", - "version": "0.8.7", + "version": "0.8.8", "type": "module", "main": "src/index.ts", "exports": { diff --git a/packages/csr/package.json b/packages/csr/package.json index e14ac4ca..790b1ecd 100644 --- a/packages/csr/package.json +++ b/packages/csr/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/csr", - "version": "0.8.7", + "version": "0.8.8", "type": "module", "main": "src/index.ts", "exports": { diff --git a/packages/db/package.json b/packages/db/package.json index aa5cc018..89ab4066 100644 --- a/packages/db/package.json +++ b/packages/db/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/db", - "version": "0.8.7", + "version": "0.8.8", "private": true, "type": "module", "main": "./src/index.ts", diff --git a/packages/dev-server/package.json b/packages/dev-server/package.json index fe868dec..6bf33100 100644 --- a/packages/dev-server/package.json +++ b/packages/dev-server/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/dev-server", - "version": "0.8.7", + "version": "0.8.8", "type": "module", "main": "src/index.ts", "exports": { diff --git a/packages/dev-toolbar/package.json b/packages/dev-toolbar/package.json index 2b939dcf..6be0c223 100644 --- a/packages/dev-toolbar/package.json +++ b/packages/dev-toolbar/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/dev-toolbar", - "version": "0.8.7", + "version": "0.8.8", "private": true, "type": "module", "sideEffects": false, diff --git a/packages/encryption/package.json b/packages/encryption/package.json index e219b29a..54d46f70 100644 --- a/packages/encryption/package.json +++ b/packages/encryption/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/encryption", - "version": "0.8.7", + "version": "0.8.8", "private": true, "type": "module", "main": "./src/index.ts", diff --git a/packages/graphql/package.json b/packages/graphql/package.json index ef7cc130..dbb43db2 100644 --- a/packages/graphql/package.json +++ b/packages/graphql/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/graphql", - "version": "0.8.7", + "version": "0.8.8", "type": "module", "main": "./src/index.ts", "exports": { diff --git a/packages/helpers/package.json b/packages/helpers/package.json index e655c80c..203daded 100644 --- a/packages/helpers/package.json +++ b/packages/helpers/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/helpers", - "version": "0.8.7", + "version": "0.8.8", "private": true, "type": "module", "description": "Safe convenience helpers for WrNexus request contexts and common application flows.", diff --git a/packages/i18n/package.json b/packages/i18n/package.json index 9dc7720f..9b4f7c48 100644 --- a/packages/i18n/package.json +++ b/packages/i18n/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/i18n", - "version": "0.8.7", + "version": "0.8.8", "private": true, "type": "module", "main": "./src/index.ts", diff --git a/packages/identity/package.json b/packages/identity/package.json index 285800ac..27202b89 100644 --- a/packages/identity/package.json +++ b/packages/identity/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/identity", - "version": "0.8.7", + "version": "0.8.8", "type": "module", "description": "Enterprise federation, provisioning, machine identity, and privacy governance for WRNexusJS.", "main": "src/index.ts", diff --git a/packages/image/package.json b/packages/image/package.json index 301f5892..fe39482d 100644 --- a/packages/image/package.json +++ b/packages/image/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/image", - "version": "0.8.7", + "version": "0.8.8", "type": "module", "description": "Responsive image planning, secure remote image policies, and performance auditing for WRNexusJS.", "main": "./src/index.ts", diff --git a/packages/jwt/package.json b/packages/jwt/package.json index 04b2994c..dcac920b 100644 --- a/packages/jwt/package.json +++ b/packages/jwt/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/jwt", - "version": "0.8.7", + "version": "0.8.8", "private": true, "type": "module", "main": "./src/index.ts", diff --git a/packages/language-server/package.json b/packages/language-server/package.json index 9e70c09d..4431e6f8 100644 --- a/packages/language-server/package.json +++ b/packages/language-server/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/language-server", - "version": "0.8.7", + "version": "0.8.8", "type": "module", "description": "Editor-neutral Language Server Protocol implementation for WRNexus .wrn files.", "main": "src/index.ts", diff --git a/packages/language-server/src/index.ts b/packages/language-server/src/index.ts index 16e03e70..8f2f8b9f 100644 --- a/packages/language-server/src/index.ts +++ b/packages/language-server/src/index.ts @@ -75,7 +75,10 @@ function diagnosticRange(diagnostic: WrnDiagnostic): Range { }; } -export function documentDiagnostics(document: TextDocument) { +export function documentDiagnostics( + document: TextDocument, + options: { includeTypes?: boolean } = {}, +) { if (Buffer.byteLength(document.text, "utf8") > 1_048_576) { return [ { @@ -96,7 +99,8 @@ export function documentDiagnostics(document: TextDocument) { message: diagnostic.message, }), ); - if (syntax.some((diagnostic) => diagnostic.severity === 1)) return syntax; + if (syntax.some((diagnostic) => diagnostic.severity === 1) || options.includeTypes === false) + return syntax; const types = checkWrnSource(document.text, { filePath: documentPath(document.uri) }).map( (diagnostic) => ({ range: { @@ -235,4 +239,61 @@ export function hover(document: TextDocument, position: Position) { export function completionItems() { return WRN_COMPLETIONS.map((label) => ({ label, kind: 14, detail: "WRNexus language keyword" })); } + +const semanticTokenTypes = ["variable"] as const; +const semanticTokenModifiers = ["declaration", "modification"] as const; +export const semanticTokensLegend = { + tokenTypes: [...semanticTokenTypes], + tokenModifiers: [...semanticTokenModifiers], +}; + +/** Encode state declarations, reads, and writes using the LSP relative token format. */ +export function semanticTokens(document: TextDocument): { data: number[] } { + const declarations = new Map>(); + for (const match of document.text.matchAll( + /\bstate\s+([A-Za-z_$][A-Za-z0-9_$]*)\s*(?==|;|\r?$)/gm, + )) { + const name = match[1]!; + const offset = match.index! + match[0].lastIndexOf(name); + const offsets = declarations.get(name) ?? new Set(); + offsets.add(offset); + declarations.set(name, offsets); + } + const comments = [...document.text.matchAll(/\/\/[^\n]*|\/\*[\s\S]*?\*\//g)].map((match) => ({ + start: match.index!, + end: match.index! + match[0].length, + })); + const tokens: Array<{ line: number; character: number; length: number; modifiers: number }> = []; + for (const [name, offsets] of declarations) { + const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + for (const match of document.text.matchAll( + new RegExp(`(? offset >= range.start && offset < range.end)) continue; + const position = positionAt(document.text, offset); + const after = document.text + .slice(offset + name.length) + .match(/^\s*(?:=|\+=|-=|\*=|\/=|%=|\+\+|--)/); + const before = document.text.slice(Math.max(0, offset - 8), offset).match(/(?:\+\+|--)\s*$/); + tokens.push({ + ...position, + length: name.length, + modifiers: offsets.has(offset) ? 1 : after || before ? 2 : 0, + }); + } + } + tokens.sort((a, b) => a.line - b.line || a.character - b.character); + const data: number[] = []; + let previousLine = 0; + let previousCharacter = 0; + for (const token of tokens) { + const deltaLine = token.line - previousLine; + const deltaCharacter = deltaLine === 0 ? token.character - previousCharacter : token.character; + data.push(deltaLine, deltaCharacter, token.length, 0, token.modifiers); + previousLine = token.line; + previousCharacter = token.character; + } + return { data }; +} export * from "./workspace.ts"; diff --git a/packages/language-server/src/server.ts b/packages/language-server/src/server.ts index c8b2b6c7..09b0982f 100644 --- a/packages/language-server/src/server.ts +++ b/packages/language-server/src/server.ts @@ -6,10 +6,15 @@ import { documentSymbols, formatDocument, hover, - symbolLocations, + offsetAt, + positionAt, + semanticTokens, + semanticTokensLegend, wordAt, virtualTypeScriptDocument, workspaceCompletionItems, + workspaceSymbolLocations, + clearWorkspaceIndexCache, extractComponentRefactor, htmlToWrn, type TextDocument, @@ -47,9 +52,9 @@ function internalDiagnostic(document: TextDocument, error: unknown) { }; } -function safeDocumentDiagnostics(document: TextDocument) { +function safeDocumentDiagnostics(document: TextDocument, includeTypes = true) { try { - return documentDiagnostics(document); + return documentDiagnostics(document, { includeTypes }); } catch (error) { process.stderr.write( `[wrnexus-lsp] diagnostics failed for ${document.uri}: ${error instanceof Error ? (error.stack ?? error.message) : String(error)}\n`, @@ -58,11 +63,11 @@ function safeDocumentDiagnostics(document: TextDocument) { } } -function publish(document: TextDocument): void { +function publish(document: TextDocument, includeTypes = true): void { send({ jsonrpc: "2.0", method: "textDocument/publishDiagnostics", - params: { uri: document.uri, diagnostics: safeDocumentDiagnostics(document) }, + params: { uri: document.uri, diagnostics: safeDocumentDiagnostics(document, includeTypes) }, }); } @@ -80,7 +85,7 @@ function schedulePublish(document: TextDocument): void { setTimeout(() => { diagnosticTimers.delete(document.uri); const current = documents.get(document.uri); - if (current && current.version === expectedVersion) publish(current); + if (current && current.version === expectedVersion) publish(current, false); }, DIAGNOSTIC_DEBOUNCE_MS), ); } @@ -104,7 +109,7 @@ async function handle(message: JsonRpc): Promise { result(message.id, { serverInfo: { name: "WRNexus Language Server", version: "0.8.3" }, capabilities: { - textDocumentSync: 1, + textDocumentSync: { openClose: true, change: 1, save: true }, documentFormattingProvider: true, completionProvider: { triggerCharacters: ["<", "@", ":", "."] }, hoverProvider: true, @@ -112,8 +117,14 @@ async function handle(message: JsonRpc): Promise { referencesProvider: true, renameProvider: { prepareProvider: true }, documentSymbolProvider: true, + semanticTokensProvider: { legend: semanticTokensLegend, full: true }, codeActionProvider: { - codeActionKinds: ["quickfix", "refactor.extract", "refactor.rewrite"], + codeActionKinds: [ + "quickfix", + "refactor.extract", + "refactor.rewrite", + "source.organizeImports", + ], }, experimental: { wrnexusVirtualTypeScript: true }, }, @@ -127,6 +138,9 @@ async function handle(message: JsonRpc): Promise { result(message.id, null); break; case "exit": + for (const timer of diagnosticTimers.values()) clearTimeout(timer); + diagnosticTimers.clear(); + documents.clear(); process.exit(0); break; case "textDocument/didOpen": { @@ -146,6 +160,15 @@ async function handle(message: JsonRpc): Promise { } break; } + case "textDocument/didSave": { + const document = documents.get(params.textDocument.uri); + if (document) { + clearDiagnosticTimer(document.uri); + clearWorkspaceIndexCache(workspaceRoot); + publish(document, true); + } + break; + } case "textDocument/didClose": clearDiagnosticTimer(params.textDocument.uri); documents.delete(params.textDocument.uri); @@ -180,6 +203,11 @@ async function handle(message: JsonRpc): Promise { result(message.id, document ? documentSymbols(document) : []); break; } + case "textDocument/semanticTokens/full": { + const document = documents.get(params.textDocument.uri); + result(message.id, document ? semanticTokens(document) : { data: [] }); + break; + } case "textDocument/hover": { const document = documents.get(params.textDocument.uri); result(message.id, document ? hover(document, params.position) : null); @@ -192,7 +220,8 @@ async function handle(message: JsonRpc): Promise { } case "textDocument/references": { const document = documents.get(params.textDocument.uri); - result(message.id, document ? symbolLocations(document, params.position) : []); + const selected = document ? wordAt(document.text, params.position) : null; + result(message.id, selected ? workspaceSymbolLocations(workspaceRoot, selected.word) : []); break; } case "textDocument/prepareRename": { @@ -202,13 +231,16 @@ async function handle(message: JsonRpc): Promise { } case "textDocument/rename": { const document = documents.get(params.textDocument.uri); - const edits = document - ? symbolLocations(document, params.position).map(({ range }) => ({ - range, - newText: params.newName, - })) - : []; - result(message.id, document ? { changes: { [document.uri]: edits } } : null); + const selected = document ? wordAt(document.text, params.position) : null; + if (!selected || !/^[A-Za-z_$][\w$]*$/.test(params.newName)) { + result(message.id, null); + break; + } + const changes: Record> = {}; + for (const location of workspaceSymbolLocations(workspaceRoot, selected.word)) { + (changes[location.uri] ??= []).push({ range: location.range, newText: params.newName }); + } + result(message.id, { changes }); break; } case "textDocument/codeAction": { @@ -231,6 +263,71 @@ async function handle(message: JsonRpc): Promise { }, }, })); + const imports = [ + ...document.text.matchAll( + /^import(?:\s+type)?[\s\S]*?(?:;\s*|\n(?=import|\s*(?:page|component|layout|global\s+store|page\s+store)\b))/gm, + ), + ].map((match) => ({ + start: match.index!, + end: match.index! + match[0].length, + text: match[0].trim(), + })); + if (imports.length > 1) { + const ordered = [...imports].sort((a, b) => { + const aType = /^import\s+type\b/.test(a.text) ? 0 : 1; + const bType = /^import\s+type\b/.test(b.text) ? 0 : 1; + return aType - bType || a.text.localeCompare(b.text); + }); + const replacement = ordered.map((entry) => entry.text.replace(/;$/, "")).join("\n") + "\n"; + const start = imports[0]!.start; + const end = imports.at(-1)!.end; + if (document.text.slice(start, end) !== replacement) { + actions.push({ + title: "Organize WRN imports", + kind: "source.organizeImports", + edit: { + changes: { + [document.uri]: [ + { + range: { + start: positionAt(document.text, start), + end: positionAt(document.text, end), + }, + newText: replacement, + }, + ], + }, + }, + }); + } + } + for (const diagnostic of params.context?.diagnostics ?? []) { + if (diagnostic.code !== "WRN-OUTPUT-LEGACY-EMIT") continue; + const start = offsetAt(document.text, diagnostic.range.start); + const end = offsetAt(document.text, diagnostic.range.end); + const source = document.text.slice(start, end); + const match = /\$emit\(\s*["']([A-Za-z_$][\w$]*)["']\s*,?/.exec(source); + if (!match) continue; + actions.push({ + title: `Convert $emit to output.${match[1]}`, + kind: "quickfix", + diagnostics: [diagnostic], + isPreferred: true, + edit: { + changes: { + [document.uri]: [ + { + range: diagnostic.range, + newText: source.replace( + /\$emit\(\s*["'][A-Za-z_$][\w$]*["']\s*,?\s*/, + `output.${match[1]}(`, + ), + }, + ], + }, + }, + }); + } const selected = document.text.slice( document.text .split(/\r?\n/) diff --git a/packages/language-server/src/workspace.ts b/packages/language-server/src/workspace.ts index 35822cfb..d26e591a 100644 --- a/packages/language-server/src/workspace.ts +++ b/packages/language-server/src/workspace.ts @@ -15,7 +15,9 @@ const SKIPPED_DIRECTORIES = new Set([ ]); const MAX_INDEX_FILES = 5_000; const MAX_SOURCE_BYTES = 1_048_576; -const INDEX_TTL_MS = 5_000; +// Completion can be requested repeatedly while the list is visible. Avoid +// rescanning thousands of files on that hot path; document saves invalidate it. +const INDEX_TTL_MS = 5 * 60_000; const MAX_CACHED_ROOTS = 8; const workspaceIndexCache = new Map< string, @@ -168,6 +170,30 @@ export function clearWorkspaceIndexCache(root?: string): void { else workspaceIndexCache.clear(); } +export function workspaceSymbolLocations(root: string, name: string, limit = 1_000) { + if (!/^[A-Za-z_$][\w$]*$/.test(name)) return []; + const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + const pattern = new RegExp(`(? = []; + for (const file of walk(resolve(root), (path) => extname(path) === ".wrn").slice(0, limit)) { + let source: string; + try { + source = readFileSync(file, "utf8"); + } catch { + continue; + } + for (const match of source.matchAll(pattern)) { + const before = source.slice(0, match.index!).split(/\r?\n/); + const start = { line: before.length - 1, character: before.at(-1)?.length ?? 0 }; + locations.push({ + uri: `file:///${file.replace(/\\/g, "/")}`, + range: { start, end: { line: start.line, character: start.character + name.length } }, + }); + } + } + return locations; +} + export function extractComponentRefactor(document: TextDocument, range: Range, name: string) { if (!/^[A-Z][A-Za-z0-9_$]*$/.test(name)) throw new Error("Component name must use PascalCase"); const lines = document.text.split(/\r?\n/); diff --git a/packages/mcp/package.json b/packages/mcp/package.json index c9d88b03..19ba9d6d 100644 --- a/packages/mcp/package.json +++ b/packages/mcp/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/mcp", - "version": "0.8.7", + "version": "0.8.8", "type": "module", "description": "Model Context Protocol server exposing WRNexus application and framework context.", "main": "src/index.ts", diff --git a/packages/mobile/package.json b/packages/mobile/package.json index cc96d83b..e57a8422 100644 --- a/packages/mobile/package.json +++ b/packages/mobile/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/mobile", - "version": "0.8.7", + "version": "0.8.8", "type": "module", "main": "src/index.ts", "exports": { diff --git a/packages/native/package.json b/packages/native/package.json index 927ac4e3..bc4ad7a9 100644 --- a/packages/native/package.json +++ b/packages/native/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/native", - "version": "0.8.7", + "version": "0.8.8", "type": "module", "main": "src/index.ts", "exports": { diff --git a/packages/oauth/package.json b/packages/oauth/package.json index 3a65de17..407d8a3f 100644 --- a/packages/oauth/package.json +++ b/packages/oauth/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/oauth", - "version": "0.8.7", + "version": "0.8.8", "private": true, "type": "module", "main": "src/index.ts", diff --git a/packages/observability/package.json b/packages/observability/package.json index c5239ee9..465e727d 100644 --- a/packages/observability/package.json +++ b/packages/observability/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/observability", - "version": "0.8.7", + "version": "0.8.8", "type": "module", "description": "Metrics, Web Vitals collection, request instrumentation, and exporter adapters for WRNexusJS.", "main": "src/index.ts", diff --git a/packages/playground/package.json b/packages/playground/package.json index a6d812d9..786ae16e 100644 --- a/packages/playground/package.json +++ b/packages/playground/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/playground", - "version": "0.8.7", + "version": "0.8.8", "type": "module", "description": "Secure, shareable WRNexus compiler and UI playground.", "main": "src/index.ts", diff --git a/packages/plugin/package.json b/packages/plugin/package.json index 5d34efd1..34b5875b 100644 --- a/packages/plugin/package.json +++ b/packages/plugin/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/plugin", - "version": "0.8.7", + "version": "0.8.8", "type": "module", "main": "src/index.ts", "exports": { diff --git a/packages/pubsub/package.json b/packages/pubsub/package.json index b5bc7d6a..6e336f36 100644 --- a/packages/pubsub/package.json +++ b/packages/pubsub/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/pubsub", - "version": "0.8.7", + "version": "0.8.8", "private": true, "type": "module", "main": "src/index.ts", diff --git a/packages/pwa/package.json b/packages/pwa/package.json index 2f2be3aa..918fe1ff 100644 --- a/packages/pwa/package.json +++ b/packages/pwa/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/pwa", - "version": "0.8.7", + "version": "0.8.8", "type": "module", "description": "Service workers, manifests, offline queues, background sync, push, and conflict resolution for WRNexusJS.", "main": "./src/index.ts", diff --git a/packages/queue/package.json b/packages/queue/package.json index 5e604032..b17bb8b4 100644 --- a/packages/queue/package.json +++ b/packages/queue/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/queue", - "version": "0.8.7", + "version": "0.8.8", "private": true, "type": "module", "main": "src/index.ts", diff --git a/packages/reactive/package.json b/packages/reactive/package.json index c6d99f09..a18aba7f 100644 --- a/packages/reactive/package.json +++ b/packages/reactive/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/reactive", - "version": "0.8.7", + "version": "0.8.8", "type": "module", "main": "src/index.ts", "exports": { diff --git a/packages/realtime/package.json b/packages/realtime/package.json index 658841fe..643eadb3 100644 --- a/packages/realtime/package.json +++ b/packages/realtime/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/realtime", - "version": "0.8.7", + "version": "0.8.8", "description": "Typed realtime rooms, message helpers, presence utilities, package UI blocks, and WRNexusJS integration.", "type": "module", "sideEffects": false, diff --git a/packages/router/package.json b/packages/router/package.json index c4f900d6..430e232b 100644 --- a/packages/router/package.json +++ b/packages/router/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/router", - "version": "0.8.7", + "version": "0.8.8", "type": "module", "main": "src/index.ts", "exports": { diff --git a/packages/rpc/package.json b/packages/rpc/package.json index cee19e68..7a1128b8 100644 --- a/packages/rpc/package.json +++ b/packages/rpc/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/rpc", - "version": "0.8.7", + "version": "0.8.8", "private": true, "type": "module", "main": "./src/index.ts", diff --git a/packages/security/package.json b/packages/security/package.json index 08d71ef2..dc3d3344 100644 --- a/packages/security/package.json +++ b/packages/security/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/security", - "version": "0.8.7", + "version": "0.8.8", "type": "module", "description": "Security policies, safe serialization, SSRF protection, request limits, and secure cookie helpers for WRNexusJS.", "main": "src/index.ts", diff --git a/packages/ssr/package.json b/packages/ssr/package.json index b864641f..03726f05 100644 --- a/packages/ssr/package.json +++ b/packages/ssr/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/ssr", - "version": "0.8.7", + "version": "0.8.8", "type": "module", "main": "src/index.ts", "exports": { diff --git a/packages/store/package.json b/packages/store/package.json index 5d1ea3f7..4bad0157 100644 --- a/packages/store/package.json +++ b/packages/store/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/store", - "version": "0.8.7", + "version": "0.8.8", "type": "module", "main": "src/index.ts", "exports": { diff --git a/packages/styles/package.json b/packages/styles/package.json index 4475c381..c8b362d9 100644 --- a/packages/styles/package.json +++ b/packages/styles/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/styles", - "version": "0.8.7", + "version": "0.8.8", "type": "module", "main": "src/index.ts", "exports": { diff --git a/packages/syntax/package.json b/packages/syntax/package.json index e0114f99..b76cbf13 100644 --- a/packages/syntax/package.json +++ b/packages/syntax/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/syntax", - "version": "0.8.7", + "version": "0.8.8", "type": "module", "main": "src/index.ts", "exports": { diff --git a/packages/test/package.json b/packages/test/package.json index d9d34554..2bd91981 100644 --- a/packages/test/package.json +++ b/packages/test/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/test", - "version": "0.8.7", + "version": "0.8.8", "private": true, "type": "module", "main": "src/index.ts", diff --git a/packages/tracking/package.json b/packages/tracking/package.json index f8f38734..9f30dd82 100644 --- a/packages/tracking/package.json +++ b/packages/tracking/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/tracking", - "version": "0.8.7", + "version": "0.8.8", "private": true, "type": "module", "main": "src/index.ts", diff --git a/packages/typecheck/package.json b/packages/typecheck/package.json index 083727b0..cb28ec7f 100644 --- a/packages/typecheck/package.json +++ b/packages/typecheck/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/typecheck", - "version": "0.8.7", + "version": "0.8.8", "type": "module", "main": "src/index.ts", "exports": { diff --git a/packages/typecheck/src/index.ts b/packages/typecheck/src/index.ts index 655e773f..aebf6701 100644 --- a/packages/typecheck/src/index.ts +++ b/packages/typecheck/src/index.ts @@ -239,6 +239,10 @@ export function virtualTypeScriptModule( append(retainedImports(ast), ast.imports[0]); append(ast.types.join("\n\n"), ast.types[0]?.trim()); + // Pages, components, and stores are rendered with the framework request + // context in scope. Mirror the compiler-generated function parameter here so + // expressions such as `state user = ctx.user` type-check in the editor. + append('declare const ctx: import("@wrnexus/core").Context;'); for (const prop of ast.props) { // JavaScript keywords such as the conventional `class` component prop are diff --git a/packages/typecheck/test/typecheck.test.ts b/packages/typecheck/test/typecheck.test.ts index 5b9532c1..40f7686f 100644 --- a/packages/typecheck/test/typecheck.test.ts +++ b/packages/typecheck/test/typecheck.test.ts @@ -73,3 +73,15 @@ test("treats omitted runtime parameter types as dynamic JavaScript values", () = ); expect(diagnostics.filter((diagnostic) => diagnostic.code.startsWith("WRN-TYPE-"))).toEqual([]); }); + +test("provides the framework context to page state expressions", () => { + const root = app(); + const diagnostics = checkWrnSource( + `page AccountPage { + state user = ctx.user + view {
} + }`, + { appRoot: root, filePath: join(root, "app", "pages", "account.wrn") }, + ); + expect(diagnostics.some((diagnostic) => diagnostic.code === "WRN-TYPE-2304")).toBe(false); +}); diff --git a/packages/ui/package.json b/packages/ui/package.json index c83dff35..44d79b08 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/ui", - "version": "0.8.7", + "version": "0.8.8", "private": true, "type": "module", "main": "src/index.ts", diff --git a/packages/uploader/package.json b/packages/uploader/package.json index fb3b42b5..f9fa469a 100644 --- a/packages/uploader/package.json +++ b/packages/uploader/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/uploader", - "version": "0.8.7", + "version": "0.8.8", "private": true, "type": "module", "main": "./src/index.ts", diff --git a/packages/validation/package.json b/packages/validation/package.json index 230c30f2..9d559fbe 100644 --- a/packages/validation/package.json +++ b/packages/validation/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/validation", - "version": "0.8.7", + "version": "0.8.8", "private": true, "type": "module", "main": "./src/index.ts", diff --git a/update-package-versions.mjs b/update-package-versions.mjs index c804f4b8..0ef63db5 100644 --- a/update-package-versions.mjs +++ b/update-package-versions.mjs @@ -1,7 +1,7 @@ import { existsSync, readdirSync, readFileSync, writeFileSync } from "node:fs"; import { join } from "node:path"; -const version = "0.8.7"; +const version = "0.8.8"; const dependencyGroups = [ "dependencies", @@ -10,6 +10,11 @@ const dependencyGroups = [ "optionalDependencies", ]; +const rootPackageFile = "package.json"; +const rootPackage = JSON.parse(readFileSync(rootPackageFile, "utf8")); +rootPackage.version = version; +writeFileSync(rootPackageFile, `${JSON.stringify(rootPackage, null, 2)}\n`); + for (const entry of readdirSync("packages", { withFileTypes: true, })) {