release: WRNexusJS 0.8.0
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/ai",
|
"name": "@wrnexus/ai",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Zero-dependency Claude (Anthropic) client for WrNexus apps.",
|
"description": "Zero-dependency Claude (Anthropic) client for WrNexus apps.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/authz",
|
"name": "@wrnexus/authz",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "@wrnexus/authz — part of the WrNexus framework.",
|
"description": "@wrnexus/authz — part of the WrNexus framework.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
+17
-17
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/cli",
|
"name": "@wrnexus/cli",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "@wrnexus/cli — part of the WrNexus framework.",
|
"description": "@wrnexus/cli — part of the WrNexus framework.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -44,22 +44,22 @@
|
|||||||
"wrnexus": "./dist/index.js"
|
"wrnexus": "./dist/index.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wrnexus/core": "^0.8.3",
|
"@wrnexus/core": "^0.8.4",
|
||||||
"@wrnexus/router": "^0.8.3",
|
"@wrnexus/router": "^0.8.4",
|
||||||
"@wrnexus/csr": "^0.8.3",
|
"@wrnexus/csr": "^0.8.4",
|
||||||
"@wrnexus/compiler": "^0.8.3",
|
"@wrnexus/compiler": "^0.8.4",
|
||||||
"@wrnexus/styles": "^0.8.3",
|
"@wrnexus/styles": "^0.8.4",
|
||||||
"@wrnexus/dev-server": "^0.8.3",
|
"@wrnexus/dev-server": "^0.8.4",
|
||||||
"@wrnexus/ui": "^0.8.3",
|
"@wrnexus/ui": "^0.8.4",
|
||||||
"@wrnexus/validation": "^0.8.3",
|
"@wrnexus/validation": "^0.8.4",
|
||||||
"@wrnexus/i18n": "^0.8.3",
|
"@wrnexus/i18n": "^0.8.4",
|
||||||
"@wrnexus/mcp": "^0.8.3",
|
"@wrnexus/mcp": "^0.8.4",
|
||||||
"@wrnexus/playground": "^0.8.3",
|
"@wrnexus/playground": "^0.8.4",
|
||||||
"@wrnexus/db": "^0.8.3",
|
"@wrnexus/db": "^0.8.4",
|
||||||
"@wrnexus/plugin": "^0.8.3",
|
"@wrnexus/plugin": "^0.8.4",
|
||||||
"@wrnexus/syntax": "^0.8.3",
|
"@wrnexus/syntax": "^0.8.4",
|
||||||
"@wrnexus/typecheck": "^0.8.3",
|
"@wrnexus/typecheck": "^0.8.4",
|
||||||
"@wrnexus/security": "^0.8.3",
|
"@wrnexus/security": "^0.8.4",
|
||||||
"selfsigned": "^5.5.0"
|
"selfsigned": "^5.5.0"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/compiler",
|
"name": "@wrnexus/compiler",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "@wrnexus/compiler — part of the WrNexus framework.",
|
"description": "@wrnexus/compiler — part of the WrNexus framework.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -37,10 +37,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wrnexus/csr": "^0.8.3",
|
"@wrnexus/csr": "^0.8.4",
|
||||||
"@wrnexus/syntax": "^0.8.3",
|
"@wrnexus/syntax": "^0.8.4",
|
||||||
"@wrnexus/store": "^0.8.3",
|
"@wrnexus/store": "^0.8.4",
|
||||||
"@wrnexus/validation": "^0.8.3"
|
"@wrnexus/validation": "^0.8.4"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist",
|
"dist",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/core",
|
"name": "@wrnexus/core",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "@wrnexus/core — part of the WrNexus framework.",
|
"description": "@wrnexus/core — part of the WrNexus framework.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/csr",
|
"name": "@wrnexus/csr",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "@wrnexus/csr — part of the WrNexus framework.",
|
"description": "@wrnexus/csr — part of the WrNexus framework.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -37,7 +37,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wrnexus/core": "^0.8.3"
|
"@wrnexus/core": "^0.8.4"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist",
|
"dist",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/db",
|
"name": "@wrnexus/db",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Typed database drivers, migrations, instrumentation, repositories, pagination, and transaction helpers.",
|
"description": "Typed database drivers, migrations, instrumentation, repositories, pagination, and transaction helpers.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/dev-server",
|
"name": "@wrnexus/dev-server",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "@wrnexus/dev-server — part of the WrNexus framework.",
|
"description": "@wrnexus/dev-server — part of the WrNexus framework.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -41,25 +41,25 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wrnexus/core": "^0.8.3",
|
"@wrnexus/core": "^0.8.4",
|
||||||
"@wrnexus/dev-toolbar": "^0.8.3",
|
"@wrnexus/dev-toolbar": "^0.8.4",
|
||||||
"@wrnexus/router": "^0.8.3",
|
"@wrnexus/router": "^0.8.4",
|
||||||
"@wrnexus/ssr": "^0.8.3",
|
"@wrnexus/ssr": "^0.8.4",
|
||||||
"@wrnexus/csr": "^0.8.3",
|
"@wrnexus/csr": "^0.8.4",
|
||||||
"@wrnexus/compiler": "^0.8.3",
|
"@wrnexus/compiler": "^0.8.4",
|
||||||
"@wrnexus/styles": "^0.8.3",
|
"@wrnexus/styles": "^0.8.4",
|
||||||
"@wrnexus/ui": "^0.8.3",
|
"@wrnexus/ui": "^0.8.4",
|
||||||
"@wrnexus/validation": "^0.8.3",
|
"@wrnexus/validation": "^0.8.4",
|
||||||
"@wrnexus/i18n": "^0.8.3",
|
"@wrnexus/i18n": "^0.8.4",
|
||||||
"@wrnexus/db": "^0.8.3",
|
"@wrnexus/db": "^0.8.4",
|
||||||
"@wrnexus/pubsub": "^0.8.3",
|
"@wrnexus/pubsub": "^0.8.4",
|
||||||
"@wrnexus/uploader": "^0.8.3",
|
"@wrnexus/uploader": "^0.8.4",
|
||||||
"@wrnexus/plugin": "^0.8.3",
|
"@wrnexus/plugin": "^0.8.4",
|
||||||
"@wrnexus/store": "^0.8.3",
|
"@wrnexus/store": "^0.8.4",
|
||||||
"@wrnexus/security": "^0.8.3",
|
"@wrnexus/security": "^0.8.4",
|
||||||
"@wrnexus/observability": "^0.8.3",
|
"@wrnexus/observability": "^0.8.4",
|
||||||
"@wrnexus/cache": "^0.8.3",
|
"@wrnexus/cache": "^0.8.4",
|
||||||
"@wrnexus/pwa": "^0.8.3"
|
"@wrnexus/pwa": "^0.8.4"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist",
|
"dist",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/dev-toolbar",
|
"name": "@wrnexus/dev-toolbar",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "@wrnexus/dev-toolbar — part of the WrNexus framework.",
|
"description": "@wrnexus/dev-toolbar — part of the WrNexus framework.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/encryption",
|
"name": "@wrnexus/encryption",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Authenticated encryption, key rotation, hashing, and optional application-layer encrypted HTTP envelopes.",
|
"description": "Authenticated encryption, key rotation, hashing, and optional application-layer encrypted HTTP envelopes.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -37,7 +37,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wrnexus/core": "^0.8.3"
|
"@wrnexus/core": "^0.8.4"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist",
|
"dist",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/helpers",
|
"name": "@wrnexus/helpers",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Safe convenience helpers for WrNexus request contexts and common application flows.",
|
"description": "Safe convenience helpers for WrNexus request contexts and common application flows.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -37,7 +37,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wrnexus/core": "^0.8.3"
|
"@wrnexus/core": "^0.8.4"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist",
|
"dist",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/i18n",
|
"name": "@wrnexus/i18n",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Locale loading, fallback resolution, SSR/browser translations, formatters, and WRNexusJS language components.",
|
"description": "Locale loading, fallback resolution, SSR/browser translations, formatters, and WRNexusJS language components.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -42,9 +42,9 @@
|
|||||||
"./components/*": "./components/*"
|
"./components/*": "./components/*"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wrnexus/core": "^0.8.3",
|
"@wrnexus/core": "^0.8.4",
|
||||||
"@wrnexus/plugin": "^0.8.3",
|
"@wrnexus/plugin": "^0.8.4",
|
||||||
"@wrnexus/ui": "^0.8.3"
|
"@wrnexus/ui": "^0.8.4"
|
||||||
},
|
},
|
||||||
"wrnexus": {
|
"wrnexus": {
|
||||||
"plugin": {
|
"plugin": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/jwt",
|
"name": "@wrnexus/jwt",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "HS256 JSON Web Tokens, key rotation, access/refresh helpers, scopes, cookies, and auth middleware.",
|
"description": "HS256 JSON Web Tokens, key rotation, access/refresh helpers, scopes, cookies, and auth middleware.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -37,7 +37,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wrnexus/core": "^0.8.3"
|
"@wrnexus/core": "^0.8.4"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist",
|
"dist",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/mobile",
|
"name": "@wrnexus/mobile",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "@wrnexus/mobile — part of the WrNexus framework.",
|
"description": "@wrnexus/mobile — part of the WrNexus framework.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -37,7 +37,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wrnexus/native": "^0.8.3"
|
"@wrnexus/native": "^0.8.4"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist",
|
"dist",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/native",
|
"name": "@wrnexus/native",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "@wrnexus/native — part of the WrNexus framework.",
|
"description": "@wrnexus/native — part of the WrNexus framework.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/oauth",
|
"name": "@wrnexus/oauth",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "@wrnexus/oauth — part of the WrNexus framework.",
|
"description": "@wrnexus/oauth — part of the WrNexus framework.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -37,7 +37,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wrnexus/jwt": "^0.8.3"
|
"@wrnexus/jwt": "^0.8.4"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist",
|
"dist",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/plugin",
|
"name": "@wrnexus/plugin",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "@wrnexus/plugin — part of the WrNexus framework.",
|
"description": "@wrnexus/plugin — part of the WrNexus framework.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -49,7 +49,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wrnexus/syntax": "^0.8.3"
|
"@wrnexus/syntax": "^0.8.4"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist",
|
"dist",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/pubsub",
|
"name": "@wrnexus/pubsub",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "@wrnexus/pubsub — part of the WrNexus framework.",
|
"description": "@wrnexus/pubsub — part of the WrNexus framework.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/queue",
|
"name": "@wrnexus/queue",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "@wrnexus/queue — part of the WrNexus framework.",
|
"description": "@wrnexus/queue — part of the WrNexus framework.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -37,7 +37,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wrnexus/core": "^0.8.3"
|
"@wrnexus/core": "^0.8.4"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist",
|
"dist",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/reactive",
|
"name": "@wrnexus/reactive",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "@wrnexus/reactive — part of the WrNexus framework.",
|
"description": "@wrnexus/reactive — part of the WrNexus framework.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/router",
|
"name": "@wrnexus/router",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "@wrnexus/router — part of the WrNexus framework.",
|
"description": "@wrnexus/router — part of the WrNexus framework.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -37,8 +37,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wrnexus/compiler": "^0.8.3",
|
"@wrnexus/compiler": "^0.8.4",
|
||||||
"@wrnexus/core": "^0.8.3"
|
"@wrnexus/core": "^0.8.4"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist",
|
"dist",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/ssr",
|
"name": "@wrnexus/ssr",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "@wrnexus/ssr — part of the WrNexus framework.",
|
"description": "@wrnexus/ssr — part of the WrNexus framework.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -45,9 +45,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wrnexus/core": "^0.8.3",
|
"@wrnexus/core": "^0.8.4",
|
||||||
"@wrnexus/store": "^0.8.3",
|
"@wrnexus/store": "^0.8.4",
|
||||||
"@wrnexus/security": "^0.8.3"
|
"@wrnexus/security": "^0.8.4"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist",
|
"dist",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/styles",
|
"name": "@wrnexus/styles",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "@wrnexus/styles — part of the WrNexus framework.",
|
"description": "@wrnexus/styles — part of the WrNexus framework.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -37,9 +37,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wrnexus/uploader": "^0.8.3",
|
"@wrnexus/uploader": "^0.8.4",
|
||||||
"@wrnexus/core": "^0.8.3",
|
"@wrnexus/core": "^0.8.4",
|
||||||
"@wrnexus/plugin": "^0.8.3"
|
"@wrnexus/plugin": "^0.8.4"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist",
|
"dist",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/syntax",
|
"name": "@wrnexus/syntax",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "@wrnexus/syntax — part of the WrNexus framework.",
|
"description": "@wrnexus/syntax — part of the WrNexus framework.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/test",
|
"name": "@wrnexus/test",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "@wrnexus/test — part of the WrNexus framework.",
|
"description": "@wrnexus/test — part of the WrNexus framework.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/tracking",
|
"name": "@wrnexus/tracking",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "@wrnexus/tracking — part of the WrNexus framework.",
|
"description": "@wrnexus/tracking — part of the WrNexus framework.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/ui",
|
"name": "@wrnexus/ui",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "@wrnexus/ui — part of the WrNexus framework.",
|
"description": "@wrnexus/ui — part of the WrNexus framework.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -42,7 +42,7 @@
|
|||||||
"./ui.css": "./ui.css"
|
"./ui.css": "./ui.css"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wrnexus/core": "^0.8.3"
|
"@wrnexus/core": "^0.8.4"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist",
|
"dist",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/uploader",
|
"name": "@wrnexus/uploader",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Secure upload drivers, policies, client runtime, helper functions, and reusable upload components.",
|
"description": "Secure upload drivers, policies, client runtime, helper functions, and reusable upload components.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -42,9 +42,9 @@
|
|||||||
"./components/*": "./components/*"
|
"./components/*": "./components/*"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wrnexus/core": "^0.8.3",
|
"@wrnexus/core": "^0.8.4",
|
||||||
"@wrnexus/plugin": "^0.8.3",
|
"@wrnexus/plugin": "^0.8.4",
|
||||||
"@wrnexus/ui": "^0.8.3"
|
"@wrnexus/ui": "^0.8.4"
|
||||||
},
|
},
|
||||||
"wrnexus": {
|
"wrnexus": {
|
||||||
"plugin": {
|
"plugin": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/validation",
|
"name": "@wrnexus/validation",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Shared server/browser schemas, validation helpers, form runtime, and reusable error components.",
|
"description": "Shared server/browser schemas, validation helpers, form runtime, and reusable error components.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -42,9 +42,9 @@
|
|||||||
"./components/*": "./components/*"
|
"./components/*": "./components/*"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wrnexus/core": "^0.8.3",
|
"@wrnexus/core": "^0.8.4",
|
||||||
"@wrnexus/plugin": "^0.8.3",
|
"@wrnexus/plugin": "^0.8.4",
|
||||||
"@wrnexus/ui": "^0.8.3"
|
"@wrnexus/ui": "^0.8.4"
|
||||||
},
|
},
|
||||||
"wrnexus": {
|
"wrnexus": {
|
||||||
"plugin": {
|
"plugin": {
|
||||||
|
|||||||
@@ -22,6 +22,24 @@ entry in the CLI migration registry.
|
|||||||
- Made the VS Code embedded compiler freshness check deterministic across TypeScript patch
|
- Made the VS Code embedded compiler freshness check deterministic across TypeScript patch
|
||||||
versions, Windows/Linux line endings, and workspace environments by verifying normalized source
|
versions, Windows/Linux line endings, and workspace environments by verifying normalized source
|
||||||
and generator fingerprints while retaining exact-output checks for the same TypeScript version.
|
and generator fingerprints while retaining exact-output checks for the same TypeScript version.
|
||||||
|
- Fixed the VS Code language-server bundle so it executes under Node instead of exiting after
|
||||||
|
defining an uninvoked Bun CommonJS wrapper, and added request-level crash containment.
|
||||||
|
- Scoped HTML diagnostics to `view` blocks, ignored TypeScript generic syntax and WRN expressions,
|
||||||
|
and preserved JavaScript-looking documentation inside `<pre><code>` examples.
|
||||||
|
- Fixed formatter corruption of preformatted examples, balanced compact sibling markup, long bare
|
||||||
|
tags, and repeated format-on-save indentation drift.
|
||||||
|
- Fixed component prop intelligence for dynamic boolean/union expressions, boolean string literals,
|
||||||
|
literal-union runtime types, and reserved prop names such as `class`.
|
||||||
|
- Prevented bundled editor type checking from publishing TypeScript standard-library path failures or
|
||||||
|
unmapped synthetic virtual-document diagnostics.
|
||||||
|
- Fixed language-server virtual TypeScript inference for untyped dynamic handler parameters and
|
||||||
|
indexed output dispatch, while retaining strict diagnostics for explicitly typed parameters.
|
||||||
|
- Made bundled editor type checking resolve TypeScript standard libraries from the active workspace,
|
||||||
|
and added a regression test that proves semantic diagnostics are actually running.
|
||||||
|
- Fixed final release-gate lint failures by removing the obsolete editor `stripComments` helper and
|
||||||
|
importing Node `Buffer` explicitly in the language-server bundle generator.
|
||||||
|
- Reviewed the UI visual-contract change caused only by corrected `AuthForm.wrn` indentation and
|
||||||
|
regenerated the 0.8 baseline after confirming no rendered component behavior changed.
|
||||||
|
|
||||||
## 0.8.0 - 2026-08-02
|
## 0.8.0 - 2026-08-02
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
diff --git a/package.json b/package.json
|
||||||
|
index f7c6a5f..6ac4583 100644
|
||||||
|
--- a/package.json
|
||||||
|
+++ b/package.json
|
||||||
|
@@ -1,6 +1,6 @@
|
||||||
|
{
|
||||||
|
"name": "wrnexus",
|
||||||
|
- "version": "0.8.3",
|
||||||
|
+ "version": "0.8.4",
|
||||||
|
"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/language-server/src/server.ts b/packages/language-server/src/server.ts
|
||||||
|
index c27baa7..974d966 100644
|
||||||
|
--- a/packages/language-server/src/server.ts
|
||||||
|
+++ b/packages/language-server/src/server.ts
|
||||||
|
@@ -102,7 +102,7 @@ async function handle(message: JsonRpc): Promise<void> {
|
||||||
|
case "initialize":
|
||||||
|
workspaceRoot = params.rootPath ?? rootFromUri(params.rootUri);
|
||||||
|
result(message.id, {
|
||||||
|
- serverInfo: { name: "WRNexus Language Server", version: "0.8.3" },
|
||||||
|
+ serverInfo: { name: "WRNexus Language Server", version: "0.8.4" },
|
||||||
|
capabilities: {
|
||||||
|
textDocumentSync: 1,
|
||||||
|
documentFormattingProvider: true,
|
||||||
|
diff --git a/update-package-versions.mjs b/update-package-versions.mjs
|
||||||
|
index 318b962..8b70170 100644
|
||||||
|
--- a/update-package-versions.mjs
|
||||||
|
+++ b/update-package-versions.mjs
|
||||||
|
@@ -1,7 +1,17 @@
|
||||||
|
import { existsSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||||
|
import { join } from "node:path";
|
||||||
|
+import process from "node:process";
|
||||||
|
|
||||||
|
-const version = "0.8.3";
|
||||||
|
+const version = process.argv[2] ?? "0.8.4";
|
||||||
|
+
|
||||||
|
+if (!/^0\.8\.\d+$/.test(version)) {
|
||||||
|
+ throw new Error(`Expected a 0.8.x version, received ${JSON.stringify(version)}`);
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+const rootPackageFile = "package.json";
|
||||||
|
+const rootPackage = JSON.parse(readFileSync(rootPackageFile, "utf8"));
|
||||||
|
+rootPackage.version = version;
|
||||||
|
+writeFileSync(rootPackageFile, `${JSON.stringify(rootPackage, null, 2)}\n`);
|
||||||
|
|
||||||
|
const dependencyGroups = [
|
||||||
|
"dependencies",
|
||||||
|
@@ -62,6 +72,15 @@ for (const entry of readdirSync("packages", {
|
||||||
|
writeFileSync(packageFile, `${JSON.stringify(packageJson, null, 2)}\n`);
|
||||||
|
}
|
||||||
|
|
||||||
|
+const languageServerFile = join("packages", "language-server", "src", "server.ts");
|
||||||
|
+if (existsSync(languageServerFile)) {
|
||||||
|
+ const source = readFileSync(languageServerFile, "utf8").replace(
|
||||||
|
+ /serverInfo: \{ name: "WRNexus Language Server", version: "[^"]+" \}/,
|
||||||
|
+ `serverInfo: { name: "WRNexus Language Server", version: "${version}" }`,
|
||||||
|
+ );
|
||||||
|
+ writeFileSync(languageServerFile, source);
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
const editorPackageFile = join("editors", "vscode", "package.json");
|
||||||
|
if (existsSync(editorPackageFile)) {
|
||||||
|
const editorPackage = JSON.parse(readFileSync(editorPackageFile, "utf8"));
|
||||||
@@ -102,11 +102,11 @@
|
|||||||
},
|
},
|
||||||
"packages/ai": {
|
"packages/ai": {
|
||||||
"name": "@wrnexus/ai",
|
"name": "@wrnexus/ai",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
},
|
},
|
||||||
"packages/auth": {
|
"packages/auth": {
|
||||||
"name": "@wrnexus/auth",
|
"name": "@wrnexus/auth",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wrnexus/authz": "workspace:*",
|
"@wrnexus/authz": "workspace:*",
|
||||||
"@wrnexus/captcha": "workspace:*",
|
"@wrnexus/captcha": "workspace:*",
|
||||||
@@ -128,22 +128,22 @@
|
|||||||
},
|
},
|
||||||
"packages/authz": {
|
"packages/authz": {
|
||||||
"name": "@wrnexus/authz",
|
"name": "@wrnexus/authz",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
},
|
},
|
||||||
"packages/benchmark": {
|
"packages/benchmark": {
|
||||||
"name": "@wrnexus/benchmark",
|
"name": "@wrnexus/benchmark",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
},
|
},
|
||||||
"packages/cache": {
|
"packages/cache": {
|
||||||
"name": "@wrnexus/cache",
|
"name": "@wrnexus/cache",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wrnexus/core": "workspace:*",
|
"@wrnexus/core": "workspace:*",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"packages/captcha": {
|
"packages/captcha": {
|
||||||
"name": "@wrnexus/captcha",
|
"name": "@wrnexus/captcha",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wrnexus/core": "workspace:*",
|
"@wrnexus/core": "workspace:*",
|
||||||
"@wrnexus/plugin": "workspace:*",
|
"@wrnexus/plugin": "workspace:*",
|
||||||
@@ -158,7 +158,7 @@
|
|||||||
},
|
},
|
||||||
"packages/cli": {
|
"packages/cli": {
|
||||||
"name": "@wrnexus/cli",
|
"name": "@wrnexus/cli",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"bin": {
|
"bin": {
|
||||||
"wrnexus": "src/index.ts",
|
"wrnexus": "src/index.ts",
|
||||||
},
|
},
|
||||||
@@ -184,7 +184,7 @@
|
|||||||
},
|
},
|
||||||
"packages/compiler": {
|
"packages/compiler": {
|
||||||
"name": "@wrnexus/compiler",
|
"name": "@wrnexus/compiler",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wrnexus/csr": "workspace:*",
|
"@wrnexus/csr": "workspace:*",
|
||||||
"@wrnexus/store": "workspace:*",
|
"@wrnexus/store": "workspace:*",
|
||||||
@@ -194,7 +194,7 @@
|
|||||||
},
|
},
|
||||||
"packages/content": {
|
"packages/content": {
|
||||||
"name": "@wrnexus/content",
|
"name": "@wrnexus/content",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/bun": "^1.3.14",
|
"@types/bun": "^1.3.14",
|
||||||
"typescript": "^5.9.2",
|
"typescript": "^5.9.2",
|
||||||
@@ -202,18 +202,18 @@
|
|||||||
},
|
},
|
||||||
"packages/core": {
|
"packages/core": {
|
||||||
"name": "@wrnexus/core",
|
"name": "@wrnexus/core",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
},
|
},
|
||||||
"packages/csr": {
|
"packages/csr": {
|
||||||
"name": "@wrnexus/csr",
|
"name": "@wrnexus/csr",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wrnexus/core": "workspace:*",
|
"@wrnexus/core": "workspace:*",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"packages/db": {
|
"packages/db": {
|
||||||
"name": "@wrnexus/db",
|
"name": "@wrnexus/db",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/bun": "^1.3.14",
|
"@types/bun": "^1.3.14",
|
||||||
"typescript": "^5.9.2",
|
"typescript": "^5.9.2",
|
||||||
@@ -221,7 +221,7 @@
|
|||||||
},
|
},
|
||||||
"packages/dev-server": {
|
"packages/dev-server": {
|
||||||
"name": "@wrnexus/dev-server",
|
"name": "@wrnexus/dev-server",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wrnexus/cache": "workspace:*",
|
"@wrnexus/cache": "workspace:*",
|
||||||
"@wrnexus/compiler": "workspace:*",
|
"@wrnexus/compiler": "workspace:*",
|
||||||
@@ -246,7 +246,7 @@
|
|||||||
},
|
},
|
||||||
"packages/dev-toolbar": {
|
"packages/dev-toolbar": {
|
||||||
"name": "@wrnexus/dev-toolbar",
|
"name": "@wrnexus/dev-toolbar",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/bun": "^1.3.14",
|
"@types/bun": "^1.3.14",
|
||||||
"typescript": "^5.9.2",
|
"typescript": "^5.9.2",
|
||||||
@@ -254,7 +254,7 @@
|
|||||||
},
|
},
|
||||||
"packages/encryption": {
|
"packages/encryption": {
|
||||||
"name": "@wrnexus/encryption",
|
"name": "@wrnexus/encryption",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wrnexus/core": "workspace:*",
|
"@wrnexus/core": "workspace:*",
|
||||||
},
|
},
|
||||||
@@ -265,21 +265,21 @@
|
|||||||
},
|
},
|
||||||
"packages/graphql": {
|
"packages/graphql": {
|
||||||
"name": "@wrnexus/graphql",
|
"name": "@wrnexus/graphql",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wrnexus/plugin": "workspace:*",
|
"@wrnexus/plugin": "workspace:*",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"packages/helpers": {
|
"packages/helpers": {
|
||||||
"name": "@wrnexus/helpers",
|
"name": "@wrnexus/helpers",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wrnexus/core": "workspace:*",
|
"@wrnexus/core": "workspace:*",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"packages/i18n": {
|
"packages/i18n": {
|
||||||
"name": "@wrnexus/i18n",
|
"name": "@wrnexus/i18n",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wrnexus/core": "workspace:*",
|
"@wrnexus/core": "workspace:*",
|
||||||
"@wrnexus/plugin": "workspace:*",
|
"@wrnexus/plugin": "workspace:*",
|
||||||
@@ -293,7 +293,7 @@
|
|||||||
},
|
},
|
||||||
"packages/identity": {
|
"packages/identity": {
|
||||||
"name": "@wrnexus/identity",
|
"name": "@wrnexus/identity",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wrnexus/auth": "workspace:*",
|
"@wrnexus/auth": "workspace:*",
|
||||||
"@wrnexus/authz": "workspace:*",
|
"@wrnexus/authz": "workspace:*",
|
||||||
@@ -302,7 +302,7 @@
|
|||||||
},
|
},
|
||||||
"packages/image": {
|
"packages/image": {
|
||||||
"name": "@wrnexus/image",
|
"name": "@wrnexus/image",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wrnexus/plugin": "workspace:*",
|
"@wrnexus/plugin": "workspace:*",
|
||||||
"@wrnexus/security": "workspace:*",
|
"@wrnexus/security": "workspace:*",
|
||||||
@@ -322,7 +322,7 @@
|
|||||||
},
|
},
|
||||||
"packages/jwt": {
|
"packages/jwt": {
|
||||||
"name": "@wrnexus/jwt",
|
"name": "@wrnexus/jwt",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wrnexus/core": "workspace:*",
|
"@wrnexus/core": "workspace:*",
|
||||||
},
|
},
|
||||||
@@ -333,7 +333,7 @@
|
|||||||
},
|
},
|
||||||
"packages/language-server": {
|
"packages/language-server": {
|
||||||
"name": "@wrnexus/language-server",
|
"name": "@wrnexus/language-server",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"bin": {
|
"bin": {
|
||||||
"wrnexus-language-server": "src/server.ts",
|
"wrnexus-language-server": "src/server.ts",
|
||||||
},
|
},
|
||||||
@@ -344,39 +344,39 @@
|
|||||||
},
|
},
|
||||||
"packages/mcp": {
|
"packages/mcp": {
|
||||||
"name": "@wrnexus/mcp",
|
"name": "@wrnexus/mcp",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"bin": {
|
"bin": {
|
||||||
"wrnexus-mcp": "src/stdio.ts",
|
"wrnexus-mcp": "src/stdio.ts",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"packages/mobile": {
|
"packages/mobile": {
|
||||||
"name": "@wrnexus/mobile",
|
"name": "@wrnexus/mobile",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wrnexus/native": "workspace:*",
|
"@wrnexus/native": "workspace:*",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"packages/native": {
|
"packages/native": {
|
||||||
"name": "@wrnexus/native",
|
"name": "@wrnexus/native",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
},
|
},
|
||||||
"packages/oauth": {
|
"packages/oauth": {
|
||||||
"name": "@wrnexus/oauth",
|
"name": "@wrnexus/oauth",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wrnexus/jwt": "workspace:*",
|
"@wrnexus/jwt": "workspace:*",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"packages/observability": {
|
"packages/observability": {
|
||||||
"name": "@wrnexus/observability",
|
"name": "@wrnexus/observability",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wrnexus/core": "workspace:*",
|
"@wrnexus/core": "workspace:*",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"packages/playground": {
|
"packages/playground": {
|
||||||
"name": "@wrnexus/playground",
|
"name": "@wrnexus/playground",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wrnexus/compiler": "workspace:*",
|
"@wrnexus/compiler": "workspace:*",
|
||||||
"@wrnexus/syntax": "workspace:*",
|
"@wrnexus/syntax": "workspace:*",
|
||||||
@@ -384,18 +384,18 @@
|
|||||||
},
|
},
|
||||||
"packages/plugin": {
|
"packages/plugin": {
|
||||||
"name": "@wrnexus/plugin",
|
"name": "@wrnexus/plugin",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wrnexus/syntax": "workspace:*",
|
"@wrnexus/syntax": "workspace:*",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"packages/pubsub": {
|
"packages/pubsub": {
|
||||||
"name": "@wrnexus/pubsub",
|
"name": "@wrnexus/pubsub",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
},
|
},
|
||||||
"packages/pwa": {
|
"packages/pwa": {
|
||||||
"name": "@wrnexus/pwa",
|
"name": "@wrnexus/pwa",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/bun": "^1.3.14",
|
"@types/bun": "^1.3.14",
|
||||||
"typescript": "^5.9.2",
|
"typescript": "^5.9.2",
|
||||||
@@ -403,18 +403,18 @@
|
|||||||
},
|
},
|
||||||
"packages/queue": {
|
"packages/queue": {
|
||||||
"name": "@wrnexus/queue",
|
"name": "@wrnexus/queue",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wrnexus/core": "workspace:*",
|
"@wrnexus/core": "workspace:*",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"packages/reactive": {
|
"packages/reactive": {
|
||||||
"name": "@wrnexus/reactive",
|
"name": "@wrnexus/reactive",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
},
|
},
|
||||||
"packages/realtime": {
|
"packages/realtime": {
|
||||||
"name": "@wrnexus/realtime",
|
"name": "@wrnexus/realtime",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wrnexus/core": "workspace:*",
|
"@wrnexus/core": "workspace:*",
|
||||||
"@wrnexus/plugin": "workspace:*",
|
"@wrnexus/plugin": "workspace:*",
|
||||||
@@ -428,7 +428,7 @@
|
|||||||
},
|
},
|
||||||
"packages/router": {
|
"packages/router": {
|
||||||
"name": "@wrnexus/router",
|
"name": "@wrnexus/router",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wrnexus/compiler": "workspace:*",
|
"@wrnexus/compiler": "workspace:*",
|
||||||
"@wrnexus/core": "workspace:*",
|
"@wrnexus/core": "workspace:*",
|
||||||
@@ -436,14 +436,14 @@
|
|||||||
},
|
},
|
||||||
"packages/security": {
|
"packages/security": {
|
||||||
"name": "@wrnexus/security",
|
"name": "@wrnexus/security",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wrnexus/core": "workspace:*",
|
"@wrnexus/core": "workspace:*",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"packages/ssr": {
|
"packages/ssr": {
|
||||||
"name": "@wrnexus/ssr",
|
"name": "@wrnexus/ssr",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wrnexus/core": "workspace:*",
|
"@wrnexus/core": "workspace:*",
|
||||||
"@wrnexus/security": "workspace:*",
|
"@wrnexus/security": "workspace:*",
|
||||||
@@ -452,11 +452,11 @@
|
|||||||
},
|
},
|
||||||
"packages/store": {
|
"packages/store": {
|
||||||
"name": "@wrnexus/store",
|
"name": "@wrnexus/store",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
},
|
},
|
||||||
"packages/styles": {
|
"packages/styles": {
|
||||||
"name": "@wrnexus/styles",
|
"name": "@wrnexus/styles",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wrnexus/core": "workspace:*",
|
"@wrnexus/core": "workspace:*",
|
||||||
"@wrnexus/plugin": "workspace:*",
|
"@wrnexus/plugin": "workspace:*",
|
||||||
@@ -465,19 +465,19 @@
|
|||||||
},
|
},
|
||||||
"packages/syntax": {
|
"packages/syntax": {
|
||||||
"name": "@wrnexus/syntax",
|
"name": "@wrnexus/syntax",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
},
|
},
|
||||||
"packages/test": {
|
"packages/test": {
|
||||||
"name": "@wrnexus/test",
|
"name": "@wrnexus/test",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
},
|
},
|
||||||
"packages/tracking": {
|
"packages/tracking": {
|
||||||
"name": "@wrnexus/tracking",
|
"name": "@wrnexus/tracking",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
},
|
},
|
||||||
"packages/typecheck": {
|
"packages/typecheck": {
|
||||||
"name": "@wrnexus/typecheck",
|
"name": "@wrnexus/typecheck",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wrnexus/syntax": "workspace:*",
|
"@wrnexus/syntax": "workspace:*",
|
||||||
"typescript": "^5.5.0",
|
"typescript": "^5.5.0",
|
||||||
@@ -485,14 +485,14 @@
|
|||||||
},
|
},
|
||||||
"packages/ui": {
|
"packages/ui": {
|
||||||
"name": "@wrnexus/ui",
|
"name": "@wrnexus/ui",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wrnexus/core": "workspace:*",
|
"@wrnexus/core": "workspace:*",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"packages/uploader": {
|
"packages/uploader": {
|
||||||
"name": "@wrnexus/uploader",
|
"name": "@wrnexus/uploader",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wrnexus/core": "workspace:*",
|
"@wrnexus/core": "workspace:*",
|
||||||
"@wrnexus/plugin": "workspace:*",
|
"@wrnexus/plugin": "workspace:*",
|
||||||
@@ -506,7 +506,7 @@
|
|||||||
},
|
},
|
||||||
"packages/validation": {
|
"packages/validation": {
|
||||||
"name": "@wrnexus/validation",
|
"name": "@wrnexus/validation",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wrnexus/core": "workspace:*",
|
"@wrnexus/core": "workspace:*",
|
||||||
"@wrnexus/plugin": "workspace:*",
|
"@wrnexus/plugin": "workspace:*",
|
||||||
|
|||||||
@@ -56,6 +56,61 @@ in the preparation environment and stops only when dependencies excluded from th
|
|||||||
are required. The complete TypeScript and Bun test gates still need to be rerun on the release
|
are required. The complete TypeScript and Bun test gates still need to be rerun on the release
|
||||||
machine after `bun install --frozen-lockfile`.
|
machine after `bun install --frozen-lockfile`.
|
||||||
|
|
||||||
|
## Editor and language-server correction validation
|
||||||
|
|
||||||
|
The editor correction candidate includes framework-level fixes for the issues reproduced in the
|
||||||
|
Framework Showcase and official UI components:
|
||||||
|
|
||||||
|
- The packaged language server starts and completes initialize/shutdown over stdio under Node.
|
||||||
|
- Diagnostic requests are isolated so one malformed or unsupported document cannot terminate the
|
||||||
|
language-server process.
|
||||||
|
- HTML tag validation runs only against `view` markup and ignores TypeScript generics, typed output
|
||||||
|
payloads, WRN expressions, HTML comments, and JavaScript-looking text in `<pre><code>` blocks.
|
||||||
|
- `AuthForm.wrn` and `FeatureGrid.wrn` publish zero editor diagnostics in the packaged language-server
|
||||||
|
smoke test.
|
||||||
|
- Dynamic boolean and literal-option component expressions are accepted while invalid static literals
|
||||||
|
remain errors or warnings.
|
||||||
|
- WRN boolean string attributes such as `required="true"` are accepted by component contract checks.
|
||||||
|
- Reserved prop names such as `class` are not emitted as invalid standalone TypeScript bindings.
|
||||||
|
- Missing TypeScript standard libraries from a packaged bundle no longer create a flood of unrelated
|
||||||
|
global-type errors; runtime, component, and syntax diagnostics remain available.
|
||||||
|
- Formatter tests cover raw code samples, compact adjacent/nested markup, long bare elements,
|
||||||
|
multiline component attributes, inline control blocks, and repeated-pass idempotence.
|
||||||
|
- The embedded compiler, language server, and extension bundles carry normalized source/generator
|
||||||
|
fingerprints, and the language-server bundle is smoke-tested as a Node entrypoint.
|
||||||
|
|
||||||
|
A Windows editor test run exposed four remaining semantic diagnostics in `AuthForm.wrn`: an untyped
|
||||||
|
dynamic output name was emitted as `unknown`, its payload was narrowed to `{}`, and indexed output
|
||||||
|
dispatch was rejected by the synthetic output contract. The virtual TypeScript generator now follows
|
||||||
|
JavaScript semantics for omitted parameter types, keeps explicitly typed parameters strict, and gives
|
||||||
|
the synthetic output object a callable string index used only for dynamic dispatch. Direct unknown
|
||||||
|
`output.name()` calls remain protected by WRN syntax diagnostics.
|
||||||
|
|
||||||
|
The language-server typecheck host now resolves TypeScript standard libraries from the active
|
||||||
|
workspace when the bundled TypeScript compiler cannot find libraries beside `language-server.cjs`.
|
||||||
|
A dedicated test intentionally introduces a typed payload error and requires `WRN-TYPE-2322`, so the
|
||||||
|
editor suite can no longer pass merely because semantic checking was silently skipped.
|
||||||
|
|
||||||
|
The Node editor test suite passes 41 tests, including zero diagnostics for `AuthForm.wrn` and
|
||||||
|
`FeatureGrid.wrn` with workspace TypeScript libraries enabled. Extension manifest/grammar validation
|
||||||
|
passes, all 21 Framework Showcase WRN files compile, and all 146 official WRN component files pass
|
||||||
|
parser/codegen and explicit-import validation in the preparation environment.
|
||||||
|
|
||||||
|
A subsequent Windows production-gate run exposed two final ESLint failures and one reviewed visual
|
||||||
|
contract delta. The unused legacy `stripComments` helper was removed from the VS Code diagnostics
|
||||||
|
source and generated extension bundle. `scripts/build-editor-language-server.mjs` now imports
|
||||||
|
`Buffer` from `node:buffer`, so the release scripts remain compatible with the repository's
|
||||||
|
`no-undef` policy. The language-server generator fingerprint was refreshed accordingly.
|
||||||
|
|
||||||
|
The UI visual gate differed only for `packages/ui/components/AuthForm.wrn`. Comparison with the
|
||||||
|
previous accepted baseline confirmed that the source change consisted solely of corrected indentation
|
||||||
|
for the `size` prop and nested `Checkbox`; no classes, attributes, conditions, or rendered content
|
||||||
|
changed. The 0.8 visual baseline was regenerated after that review.
|
||||||
|
|
||||||
|
The editor suite passes 41/41 tests when workspace TypeScript libraries are present. UI visual
|
||||||
|
contract validation passes for 109 artifacts, explicit component imports pass for 146 WRN files, the
|
||||||
|
public API baseline matches all 46 packages, and extension manifest/grammar validation passes.
|
||||||
|
|
||||||
## Required final release verification
|
## Required final release verification
|
||||||
|
|
||||||
The preparation environment did not include the Bun executable. Therefore the complete Bun test,
|
The preparation environment did not include the Bun executable. Therefore the complete Bun test,
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
"packages/ui/components/AdvancedRangeSlider.wrn": "e78b5cf825503d0cd306c6e490d71317ee3db1844882d13b500df1f7f1c408e1",
|
"packages/ui/components/AdvancedRangeSlider.wrn": "e78b5cf825503d0cd306c6e490d71317ee3db1844882d13b500df1f7f1c408e1",
|
||||||
"packages/ui/components/AdvancedSelect.wrn": "29d43f8019a3e0e98af7cf12440f41523665535a39a973441b4faea78e67114a",
|
"packages/ui/components/AdvancedSelect.wrn": "29d43f8019a3e0e98af7cf12440f41523665535a39a973441b4faea78e67114a",
|
||||||
"packages/ui/components/AnnouncementBar.wrn": "5946ef5eb646f7414056f59d9cfe14680aa3c935acc9a1ca6528dc53b23caa48",
|
"packages/ui/components/AnnouncementBar.wrn": "5946ef5eb646f7414056f59d9cfe14680aa3c935acc9a1ca6528dc53b23caa48",
|
||||||
"packages/ui/components/AuthForm.wrn": "e6ab0cc4a557b3fd41528368c5aa7aaf39e9adcd49d9907df8d26d77bc515870",
|
"packages/ui/components/AuthForm.wrn": "496d240090269e69679e01cda3b071c012ec4e925a7c01ae883d25e359e381e1",
|
||||||
"packages/ui/components/AuthSplitLayout.wrn": "72b82495dd14d40a2d594ccdcf3adc925d1567c155750fb1072347b8845aab7b",
|
"packages/ui/components/AuthSplitLayout.wrn": "72b82495dd14d40a2d594ccdcf3adc925d1567c155750fb1072347b8845aab7b",
|
||||||
"packages/ui/components/AvatarGroup.wrn": "9c583f567213f8e1ad4506a7d2abc3fb462d11679fd2196d3828030f28a28964",
|
"packages/ui/components/AvatarGroup.wrn": "9c583f567213f8e1ad4506a7d2abc3fb462d11679fd2196d3828030f28a28964",
|
||||||
"packages/ui/components/BackToTop.wrn": "b12c56ec8b7aabad68cbdc47f4b3237e2c21a61398f0aa51f9add6229b7357cd",
|
"packages/ui/components/BackToTop.wrn": "b12c56ec8b7aabad68cbdc47f4b3237e2c21a61398f0aa51f9add6229b7357cd",
|
||||||
|
|||||||
@@ -5,6 +5,21 @@
|
|||||||
- Rebuilt the embedded WRN compiler with the 0.8.3 SSR, computed-value, Async-scope, and typed loop-prop fixes.
|
- Rebuilt the embedded WRN compiler with the 0.8.3 SSR, computed-value, Async-scope, and typed loop-prop fixes.
|
||||||
- Added compiler awareness for pruned and bundled browser hydration imports.
|
- Added compiler awareness for pruned and bundled browser hydration imports.
|
||||||
- Aligned the extension version with the WRNexusJS 0.8.3 framework release.
|
- Aligned the extension version with the WRNexusJS 0.8.3 framework release.
|
||||||
|
- Fixed repeated language-server crashes by producing a Node-executable bundle and containing
|
||||||
|
diagnostics failures without disposing the language-client connection.
|
||||||
|
- Fixed false missing/mismatched HTML tags from TypeScript generics, object payload types, comments,
|
||||||
|
and code samples outside or inside WRN `view` blocks.
|
||||||
|
- Fixed formatting of `<pre><code>` examples, compact sibling markup, long text elements, and
|
||||||
|
multiline tags so repeated formatting is idempotent.
|
||||||
|
- Fixed false boolean/string and literal-option diagnostics for dynamic component props such as
|
||||||
|
`external='{item.external || false}'`.
|
||||||
|
- Kept component prop/event intelligence active while the shared language server is enabled.
|
||||||
|
- Suppressed unavailable TypeScript standard-library and unmapped virtual-document implementation
|
||||||
|
diagnostics in packaged extension environments.
|
||||||
|
- Fixed false `unknown`/index-access diagnostics in valid dynamic event forwarding handlers such as
|
||||||
|
`output[type](payload)` by preserving JavaScript semantics for omitted parameter types.
|
||||||
|
- Resolved TypeScript standard libraries from the active workspace so semantic diagnostics run
|
||||||
|
consistently in the repository and extension development environment.
|
||||||
|
|
||||||
## 0.8.0
|
## 0.8.0
|
||||||
|
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "wrnexus",
|
"name": "wrnexus",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "wrnexus",
|
"name": "wrnexus",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"license": "SEE LICENSE IN LICENSE",
|
"license": "SEE LICENSE IN LICENSE",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"vscode-languageclient": "^10.1.0"
|
"vscode-languageclient": "^10.1.0"
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"name": "wrnexus",
|
"name": "wrnexus",
|
||||||
"displayName": "WRNexus Language Support",
|
"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.",
|
"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.3",
|
"version": "0.8.4",
|
||||||
"publisher": "wrnexus",
|
"publisher": "wrnexus",
|
||||||
"private": true,
|
"private": true,
|
||||||
"license": "SEE LICENSE IN LICENSE",
|
"license": "SEE LICENSE IN LICENSE",
|
||||||
@@ -126,6 +126,14 @@
|
|||||||
"default": true,
|
"default": true,
|
||||||
"scope": "resource",
|
"scope": "resource",
|
||||||
"description": "Place opening-tag attributes on separate lines, with the closing delimiter on its own line."
|
"description": "Place opening-tag attributes on separate lines, with the closing delimiter on its own line."
|
||||||
|
},
|
||||||
|
"wrnexus.formatting.printWidth": {
|
||||||
|
"type": "number",
|
||||||
|
"default": 100,
|
||||||
|
"minimum": 60,
|
||||||
|
"maximum": 240,
|
||||||
|
"scope": "resource",
|
||||||
|
"description": "Preferred WRNexus formatter line width before long tags are expanded."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
// Generated by scripts/build-editor-compiler.mjs. Do not edit directly.
|
// Generated by scripts/build-editor-compiler.mjs. Do not edit directly.
|
||||||
// WRN editor compiler source hash: b9e9a9ed0492c387198e368436696480ef2eca5a850ed94852c78c73c70d2959
|
// WRN editor compiler source hash: 1d2e0d1e1c38a4513b0fce3631bae003f7c28069920c5386ecc68cbe728bf57b
|
||||||
// WRN editor compiler generator hash: c71e7fe4258c97b73b384ff14b321f0cf0b30cc2ed0322f5f84b04e757159b18
|
// WRN editor compiler generator hash: c71e7fe4258c97b73b384ff14b321f0cf0b30cc2ed0322f5f84b04e757159b18
|
||||||
// Generated with TypeScript: 5.9.3
|
// Generated with TypeScript: 5.9.3
|
||||||
const __nodeRequire = require;
|
const __nodeRequire = require;
|
||||||
@@ -4845,6 +4845,18 @@ function formatOpeningTag(value, unit, depth, printWidth = 100, multilineAttribu
|
|||||||
opensElement,
|
opensElement,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
if (parsed.attributes.length === 0 && !parsed.selfClosing) {
|
||||||
|
const lines = [`${baseIndent}<${parsed.tagName}>`];
|
||||||
|
if (parsed.trailingClosing) {
|
||||||
|
if (parsed.inlineContent)
|
||||||
|
lines.push(`${childIndent}${parsed.inlineContent}`);
|
||||||
|
lines.push(`${baseIndent}</${parsed.tagName}>`);
|
||||||
|
}
|
||||||
|
else if (parsed.remainder) {
|
||||||
|
lines.push(`${childIndent}${parsed.remainder}`);
|
||||||
|
}
|
||||||
|
return { lines, opensElement };
|
||||||
|
}
|
||||||
const lines = [
|
const lines = [
|
||||||
`${baseIndent}<${parsed.tagName}`,
|
`${baseIndent}<${parsed.tagName}`,
|
||||||
...parsed.attributes.flatMap((attribute) => formatAttribute(attribute, childIndent, unit)),
|
...parsed.attributes.flatMap((attribute) => formatAttribute(attribute, childIndent, unit)),
|
||||||
@@ -4886,6 +4898,73 @@ function isMultilineOpeningTagStart(value) {
|
|||||||
function isClosingTag(value) {
|
function isClosingTag(value) {
|
||||||
return /^<\/[A-Za-z][\w$:.-]*\s*>/.test(value);
|
return /^<\/[A-Za-z][\w$:.-]*\s*>/.test(value);
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Preserve compact, already-balanced HTML fragments as one line.
|
||||||
|
*
|
||||||
|
* A line such as `<span>Page</span><b>→</b><span>API</span>` is valid and
|
||||||
|
* intentionally compact. Expanding only its first opening tag makes later
|
||||||
|
* formatter passes treat the remaining siblings as children, causing runaway
|
||||||
|
* indentation and false closing-tag diagnostics. Balanced fragments are kept
|
||||||
|
* intact while normal multiline opening tags continue through the formatter.
|
||||||
|
*/
|
||||||
|
function isBalancedInlineHtmlFragment(value) {
|
||||||
|
if (!value.startsWith("<") || value.startsWith("</") || value.startsWith("<!--")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const stack = [];
|
||||||
|
let tagCount = 0;
|
||||||
|
let rootCount = 0;
|
||||||
|
let hasNestedElement = false;
|
||||||
|
let hasOutsideText = false;
|
||||||
|
let index = 0;
|
||||||
|
while (index < value.length) {
|
||||||
|
const tagStart = value.indexOf("<", index);
|
||||||
|
if (tagStart === -1) {
|
||||||
|
if (stack.length === 0 && value.slice(index).trim())
|
||||||
|
hasOutsideText = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (stack.length === 0 && value.slice(index, tagStart).trim())
|
||||||
|
hasOutsideText = true;
|
||||||
|
if (value.startsWith("<!--", tagStart)) {
|
||||||
|
const commentEnd = value.indexOf("-->", 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("</");
|
||||||
|
const selfClosing = /\/\s*>$/.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) {
|
function isControlBlockOpen(value) {
|
||||||
return /^\{#(?:if|each)\b[\s\S]*\}$/.test(value);
|
return /^\{#(?:if|each)\b[\s\S]*\}$/.test(value);
|
||||||
}
|
}
|
||||||
@@ -4991,6 +5070,41 @@ function collectOpeningTag(inputLines, startIndex) {
|
|||||||
endIndex: index,
|
endIndex: index,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
function isPreservedRawBlockStart(value) {
|
||||||
|
return (/<pre(?:\s|>)/i.test(value) &&
|
||||||
|
!/<\/pre\s*>/i.test(value.slice(0, value.search(/<pre(?:\s|>)/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 };
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Put WRN template control markers on their own lines before indentation.
|
* Put WRN template control markers on their own lines before indentation.
|
||||||
*
|
*
|
||||||
@@ -5000,7 +5114,7 @@ function collectOpeningTag(inputLines, startIndex) {
|
|||||||
*/
|
*/
|
||||||
function expandInlineControlBlocks(lines) {
|
function expandInlineControlBlocks(lines) {
|
||||||
const marker = /(\{#(?:if|each)\b[^}]*\}|\{:(?:else(?:\s+if\b[^}]*)?|empty)\}|\{\/(?:if|each)\})/g;
|
const marker = /(\{#(?:if|each)\b[^}]*\}|\{:(?:else(?:\s+if\b[^}]*)?|empty)\}|\{\/(?:if|each)\})/g;
|
||||||
return lines.flatMap((line) => {
|
return transformOutsidePreservedRawBlocks(lines, (line) => {
|
||||||
if (!marker.test(line))
|
if (!marker.test(line))
|
||||||
return [line];
|
return [line];
|
||||||
marker.lastIndex = 0;
|
marker.lastIndex = 0;
|
||||||
@@ -5013,7 +5127,7 @@ function expandInlineControlBlocks(lines) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
function expandStructuredStateDeclarations(lines, unit) {
|
function expandStructuredStateDeclarations(lines, unit) {
|
||||||
return lines.flatMap((line) => {
|
return transformOutsidePreservedRawBlocks(lines, (line) => {
|
||||||
const match = /^(\s*state\s+[A-Za-z_$][\w$]*\s*=\s*)([\\[{][\s\S]*)$/.exec(line);
|
const match = /^(\s*state\s+[A-Za-z_$][\w$]*\s*=\s*)([\\[{][\s\S]*)$/.exec(line);
|
||||||
if (!match)
|
if (!match)
|
||||||
return [line];
|
return [line];
|
||||||
@@ -5068,6 +5182,14 @@ function formatWrnPass(source, options = {}) {
|
|||||||
index += 1;
|
index += 1;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (isPreservedRawBlockStart(value)) {
|
||||||
|
const collected = collectPreservedRawBlock(inputLines, index);
|
||||||
|
const depth = codeDepth + htmlDepth + controlDepth;
|
||||||
|
output.push(`${unit.repeat(depth)}${collected.lines[0].trimStart()}`);
|
||||||
|
output.push(...collected.lines.slice(1));
|
||||||
|
index = collected.endIndex + 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (isMultilineOpeningTagStart(value)) {
|
if (isMultilineOpeningTagStart(value)) {
|
||||||
const collected = collectOpeningTag(inputLines, index);
|
const collected = collectOpeningTag(inputLines, index);
|
||||||
value = collected.value;
|
value = collected.value;
|
||||||
@@ -5090,7 +5212,11 @@ function formatWrnPass(source, options = {}) {
|
|||||||
lineHtmlDepth = Math.max(0, htmlDepth - 1);
|
lineHtmlDepth = Math.max(0, htmlDepth - 1);
|
||||||
}
|
}
|
||||||
const depth = lineCodeDepth + lineHtmlDepth + lineControlDepth;
|
const depth = lineCodeDepth + lineHtmlDepth + lineControlDepth;
|
||||||
if (value.startsWith("<") &&
|
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("</") &&
|
!value.startsWith("</") &&
|
||||||
!value.startsWith("<!--") &&
|
!value.startsWith("<!--") &&
|
||||||
!value.startsWith("<!") &&
|
!value.startsWith("<!") &&
|
||||||
@@ -6639,11 +6765,17 @@ function runtimeTypeOf(annotation) {
|
|||||||
if (!annotation)
|
if (!annotation)
|
||||||
return "unknown";
|
return "unknown";
|
||||||
const type = annotation.trim().replace(/^readonly\s+/, "");
|
const type = annotation.trim().replace(/^readonly\s+/, "");
|
||||||
if (/^(?:string|String)(?:\s*\|\s*(?:null|undefined))*$/.test(type))
|
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";
|
return "string";
|
||||||
if (/^(?:number|Number)(?:\s*\|\s*(?:null|undefined))*$/.test(type))
|
if (/^(?:number|Number)(?:\s*\|\s*(?:null|undefined))*$/.test(type) ||
|
||||||
|
(concreteParts.length > 0 &&
|
||||||
|
concreteParts.every((part) => /^-?(?:\d+\.?\d*|\.\d+)$/.test(part))))
|
||||||
return "number";
|
return "number";
|
||||||
if (/^(?:boolean|Boolean)(?:\s*\|\s*(?:null|undefined))*$/.test(type))
|
if (/^(?:boolean|Boolean)(?:\s*\|\s*(?:null|undefined))*$/.test(type) ||
|
||||||
|
(concreteParts.length > 0 && concreteParts.every((part) => /^(?:true|false)$/.test(part))))
|
||||||
return "boolean";
|
return "boolean";
|
||||||
if (/^bigint(?:\s*\|\s*(?:null|undefined))*$/.test(type))
|
if (/^bigint(?:\s*\|\s*(?:null|undefined))*$/.test(type))
|
||||||
return "bigint";
|
return "bigint";
|
||||||
|
|||||||
@@ -39,9 +39,24 @@ function runtimeType(type) {
|
|||||||
const value = String(type || "")
|
const value = String(type || "")
|
||||||
.trim()
|
.trim()
|
||||||
.replace(/^readonly\s+/, "");
|
.replace(/^readonly\s+/, "");
|
||||||
if (/^(?:string|String)(?:\s*\|\s*(?:null|undefined))*$/.test(value)) return "string";
|
const unionParts = value.split("|").map((part) => part.trim());
|
||||||
if (/^(?:number|Number)(?:\s*\|\s*(?:null|undefined))*$/.test(value)) return "number";
|
const concreteParts = unionParts.filter((part) => !/^(?:null|undefined)$/.test(part));
|
||||||
if (/^(?:boolean|Boolean)(?:\s*\|\s*(?:null|undefined))*$/.test(value)) return "boolean";
|
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 (/^bigint(?:\s*\|\s*(?:null|undefined))*$/.test(value)) return "bigint";
|
||||||
if (/^(?:Array\s*<|ReadonlyArray\s*<|.+\[\])/.test(value) || /^\[/.test(value)) return "array";
|
if (/^(?:Array\s*<|ReadonlyArray\s*<|.+\[\])/.test(value) || /^\[/.test(value)) return "array";
|
||||||
if (/^(?:Record\s*<|object\b|\{)/.test(value)) return "object";
|
if (/^(?:Record\s*<|object\b|\{)/.test(value)) return "object";
|
||||||
@@ -138,15 +153,47 @@ function unwrapAttributeValue(value) {
|
|||||||
return trimmed;
|
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()) {
|
function attributeValueType(value, symbols = new Map()) {
|
||||||
|
const expression = isWrappedExpression(value);
|
||||||
const unwrapped = unwrapAttributeValue(value);
|
const unwrapped = unwrapAttributeValue(value);
|
||||||
if (/^[A-Za-z_$][\w$]*$/.test(unwrapped) && symbols.has(unwrapped)) {
|
if (/^[A-Za-z_$][\w$]*$/.test(unwrapped) && symbols.has(unwrapped)) {
|
||||||
return symbols.get(unwrapped);
|
return symbols.get(unwrapped);
|
||||||
}
|
}
|
||||||
if (/^(?:true|false)$/.test(unwrapped)) return "boolean";
|
|
||||||
if (/^-?(?:\d+\.?\d*|\.\d+)$/.test(unwrapped)) return "number";
|
const literal = expressionLiteralType(unwrapped);
|
||||||
if (unwrapped.startsWith("[")) return "array";
|
if (literal) return literal;
|
||||||
if (unwrapped.startsWith("{")) return "object";
|
|
||||||
|
if (expression) {
|
||||||
|
if (
|
||||||
|
/^!\s*/.test(unwrapped) ||
|
||||||
|
/(?:===|!==|==|!=|<=|>=|<|>|\bin\b|\binstanceof\b)/.test(unwrapped)
|
||||||
|
) {
|
||||||
|
return "boolean";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dynamic member access, calls, logical expressions and ternaries cannot be
|
||||||
|
// proven from component metadata alone. Treat them as unknown instead of as
|
||||||
|
// quoted strings so valid expressions such as `{item.external || false}` do
|
||||||
|
// not produce false boolean/string diagnostics.
|
||||||
|
return "unknown";
|
||||||
|
}
|
||||||
|
|
||||||
return "string";
|
return "string";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,6 +201,7 @@ function isTypeCompatible(prop, value, symbols = new Map()) {
|
|||||||
const expected = runtimeType(prop.type);
|
const expected = runtimeType(prop.type);
|
||||||
if (expected === "unknown" || expected === "null") return true;
|
if (expected === "unknown" || expected === "null") return true;
|
||||||
const actual = runtimeType(attributeValueType(value, symbols));
|
const actual = runtimeType(attributeValueType(value, symbols));
|
||||||
|
if (actual === "unknown") return true;
|
||||||
if (expected === actual) return true;
|
if (expected === actual) return true;
|
||||||
if (expected === "number" && actual === "string") return Number.isFinite(Number(value));
|
if (expected === "number" && actual === "string") return Number.isFinite(Number(value));
|
||||||
if (expected === "boolean" && actual === "string")
|
if (expected === "boolean" && actual === "string")
|
||||||
@@ -255,8 +303,10 @@ function validateComponentTags(source, components) {
|
|||||||
end: attribute.nameEnd,
|
end: attribute.nameEnd,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const literal = stringLiteral(attribute.value) ?? attribute.value;
|
const optionValue = isWrappedExpression(attribute.value)
|
||||||
if (prop.options.length > 0 && !prop.options.includes(literal)) {
|
? stringLiteral(unwrapAttributeValue(attribute.value))
|
||||||
|
: (stringLiteral(attribute.value) ?? attribute.value);
|
||||||
|
if (optionValue !== null && prop.options.length > 0 && !prop.options.includes(optionValue)) {
|
||||||
diagnostics.push({
|
diagnostics.push({
|
||||||
severity: "warning",
|
severity: "warning",
|
||||||
code: "wrn-component-prop-option",
|
code: "wrn-component-prop-option",
|
||||||
|
|||||||
@@ -94,65 +94,6 @@ function lineDiagnostic(
|
|||||||
return diagnostic;
|
return diagnostic;
|
||||||
}
|
}
|
||||||
|
|
||||||
function stripComments(source) {
|
|
||||||
const masked = [...source];
|
|
||||||
let quote = null;
|
|
||||||
let escaped = false;
|
|
||||||
|
|
||||||
const maskRange = (start, end) => {
|
|
||||||
for (let index = start; index < end; index += 1) {
|
|
||||||
if (source[index] !== "\n" && source[index] !== "\r") {
|
|
||||||
masked[index] = " ";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
for (let index = 0; index < source.length; index += 1) {
|
|
||||||
const character = source[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 (source.startsWith("//", index)) {
|
|
||||||
const lineEnd = source.indexOf("\n", index + 2);
|
|
||||||
const end = lineEnd === -1 ? source.length : lineEnd;
|
|
||||||
maskRange(index, end);
|
|
||||||
index = end - 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (source.startsWith("/*", index)) {
|
|
||||||
const commentEnd = source.indexOf("*/", index + 2);
|
|
||||||
const end = commentEnd === -1 ? source.length : commentEnd + 2;
|
|
||||||
maskRange(index, end);
|
|
||||||
index = end - 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (source.startsWith("<!--", index)) {
|
|
||||||
const commentEnd = source.indexOf("-->", index + 4);
|
|
||||||
const end = commentEnd === -1 ? source.length : commentEnd + 3;
|
|
||||||
maskRange(index, end);
|
|
||||||
index = end - 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return masked.join("");
|
|
||||||
}
|
|
||||||
|
|
||||||
function maskLeadingTrivia(source) {
|
function maskLeadingTrivia(source) {
|
||||||
const masked = [...source];
|
const masked = [...source];
|
||||||
let offset = 0;
|
let offset = 0;
|
||||||
@@ -350,10 +291,96 @@ function validateBalancedCharacters(document, source) {
|
|||||||
return diagnostics;
|
return diagnostics;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function maskHtmlComments(source) {
|
||||||
|
const masked = [...source];
|
||||||
|
let index = 0;
|
||||||
|
|
||||||
|
while (index < source.length) {
|
||||||
|
if (!source.startsWith("<!--", index)) {
|
||||||
|
index += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const commentEnd = source.indexOf("-->", index + 4);
|
||||||
|
const end = commentEnd === -1 ? source.length : commentEnd + 3;
|
||||||
|
for (let cursor = index; cursor < end; cursor += 1) {
|
||||||
|
if (source[cursor] !== "\n" && 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] !== "\n" && 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) {
|
function validateHtmlTags(document, source) {
|
||||||
const diagnostics = [];
|
const diagnostics = [];
|
||||||
const stack = [];
|
|
||||||
|
|
||||||
const voidElements = new Set([
|
const voidElements = new Set([
|
||||||
"area",
|
"area",
|
||||||
"base",
|
"base",
|
||||||
@@ -371,58 +398,63 @@ function validateHtmlTags(document, source) {
|
|||||||
"wbr",
|
"wbr",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const cleaned = stripComments(source);
|
for (const range of findViewRanges(source)) {
|
||||||
|
const fragment = source.slice(range.start, range.end);
|
||||||
|
// JavaScript-style `//` is valid rendered text inside <pre>/<code>. Only
|
||||||
|
// HTML comments and WRN expressions are masked before tag validation.
|
||||||
|
const cleaned = maskTemplateExpressions(maskHtmlComments(fragment));
|
||||||
|
const stack = [];
|
||||||
const tagPattern = /<\/?([A-Za-z][A-Za-z0-9_$:.-]*)(?:\s[\s\S]*?)?\/?>/g;
|
const tagPattern = /<\/?([A-Za-z][A-Za-z0-9_$:.-]*)(?:\s[\s\S]*?)?\/?>/g;
|
||||||
|
|
||||||
let match;
|
let match;
|
||||||
|
|
||||||
while ((match = tagPattern.exec(cleaned)) !== null) {
|
while ((match = tagPattern.exec(cleaned)) !== null) {
|
||||||
const completeTag = match[0];
|
const completeTag = match[0];
|
||||||
const tagName = match[1];
|
const tagName = match[1];
|
||||||
|
const normalizedName = /^[a-z]/.test(tagName) ? tagName.toLowerCase() : tagName;
|
||||||
const lowerTag = tagName.toLowerCase();
|
const lowerTag = tagName.toLowerCase();
|
||||||
|
const absoluteStart = range.start + match.index;
|
||||||
const isClosing = completeTag.startsWith("</");
|
const isClosing = completeTag.startsWith("</");
|
||||||
const isSelfClosing = completeTag.endsWith("/>");
|
const isSelfClosing = /\/\s*>$/.test(completeTag);
|
||||||
const isVoid = voidElements.has(lowerTag);
|
const isVoid = voidElements.has(lowerTag);
|
||||||
|
|
||||||
if (isClosing) {
|
if (isClosing) {
|
||||||
const last = stack.pop();
|
const matchingIndex = stack.findLastIndex((item) => item.normalizedName === normalizedName);
|
||||||
|
if (matchingIndex === -1) {
|
||||||
if (!last) {
|
|
||||||
diagnostics.push(
|
diagnostics.push(
|
||||||
createDiagnostic(
|
createDiagnostic(
|
||||||
document,
|
document,
|
||||||
match.index,
|
absoluteStart,
|
||||||
match.index + completeTag.length,
|
absoluteStart + completeTag.length,
|
||||||
`Unexpected closing tag </${tagName}>.`,
|
`Unexpected closing tag </${tagName}>.`,
|
||||||
vscode.DiagnosticSeverity.Error,
|
vscode.DiagnosticSeverity.Error,
|
||||||
"wrn-unexpected-html-close",
|
"wrn-unexpected-html-close",
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (last.tagName !== tagName) {
|
const last = stack.at(-1);
|
||||||
|
if (last.normalizedName !== normalizedName) {
|
||||||
diagnostics.push(
|
diagnostics.push(
|
||||||
createDiagnostic(
|
createDiagnostic(
|
||||||
document,
|
document,
|
||||||
match.index,
|
absoluteStart,
|
||||||
match.index + completeTag.length,
|
absoluteStart + completeTag.length,
|
||||||
`Mismatched closing tag </${tagName}>. Expected </${last.tagName}>.`,
|
`Mismatched closing tag </${tagName}>. Expected </${last.tagName}>.`,
|
||||||
vscode.DiagnosticSeverity.Error,
|
vscode.DiagnosticSeverity.Error,
|
||||||
"wrn-mismatched-html-tag",
|
"wrn-mismatched-html-tag",
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
stack.splice(matchingIndex);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isSelfClosing && !isVoid) {
|
if (!isSelfClosing && !isVoid) {
|
||||||
stack.push({
|
stack.push({
|
||||||
tagName,
|
tagName,
|
||||||
offset: match.index,
|
normalizedName,
|
||||||
|
offset: absoluteStart,
|
||||||
length: completeTag.length,
|
length: completeTag.length,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -440,6 +472,7 @@ function validateHtmlTags(document, source) {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return diagnostics;
|
return diagnostics;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
// WRN editor extension source hash: 825c406dea4b196976b1b6e53b3c71c3263bd63f403acdaf2fe7e5ad174275da
|
||||||
|
// WRN editor extension generator hash: 456d1d614e44e5fb1f19b784176c09cf2ade9b64ef73a17934c2698150b62728
|
||||||
"use strict";
|
"use strict";
|
||||||
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
||||||
|
|
||||||
@@ -27187,6 +27189,17 @@ ${serverFunctions}
|
|||||||
opensElement
|
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}</${parsed.tagName}>`);
|
||||||
|
} else if (parsed.remainder) {
|
||||||
|
lines2.push(`${childIndent}${parsed.remainder}`);
|
||||||
|
}
|
||||||
|
return { lines: lines2, opensElement };
|
||||||
|
}
|
||||||
const lines = [
|
const lines = [
|
||||||
`${baseIndent}<${parsed.tagName}`,
|
`${baseIndent}<${parsed.tagName}`,
|
||||||
...parsed.attributes.flatMap((attribute) => formatAttribute(attribute, childIndent, unit))
|
...parsed.attributes.flatMap((attribute) => formatAttribute(attribute, childIndent, unit))
|
||||||
@@ -27224,6 +27237,63 @@ ${serverFunctions}
|
|||||||
function isClosingTag(value) {
|
function isClosingTag(value) {
|
||||||
return /^<\/[A-Za-z][\w$:.-]*\s*>/.test(value);
|
return /^<\/[A-Za-z][\w$:.-]*\s*>/.test(value);
|
||||||
}
|
}
|
||||||
|
function isBalancedInlineHtmlFragment(value) {
|
||||||
|
if (!value.startsWith("<") || value.startsWith("</") || value.startsWith("<!--")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const stack = [];
|
||||||
|
let tagCount = 0;
|
||||||
|
let rootCount = 0;
|
||||||
|
let hasNestedElement = false;
|
||||||
|
let hasOutsideText = false;
|
||||||
|
let index = 0;
|
||||||
|
while (index < value.length) {
|
||||||
|
const tagStart = value.indexOf("<", index);
|
||||||
|
if (tagStart === -1) {
|
||||||
|
if (stack.length === 0 && value.slice(index).trim())
|
||||||
|
hasOutsideText = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (stack.length === 0 && value.slice(index, tagStart).trim())
|
||||||
|
hasOutsideText = true;
|
||||||
|
if (value.startsWith("<!--", tagStart)) {
|
||||||
|
const commentEnd = value.indexOf("-->", 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("</");
|
||||||
|
const selfClosing = /\/\s*>$/.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) {
|
function isControlBlockOpen(value) {
|
||||||
return /^\{#(?:if|each)\b[\s\S]*\}$/.test(value);
|
return /^\{#(?:if|each)\b[\s\S]*\}$/.test(value);
|
||||||
}
|
}
|
||||||
@@ -27319,9 +27389,43 @@ ${serverFunctions}
|
|||||||
endIndex: index
|
endIndex: index
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
function isPreservedRawBlockStart(value) {
|
||||||
|
return /<pre(?:\s|>)/i.test(value) && !/<\/pre\s*>/i.test(value.slice(0, value.search(/<pre(?:\s|>)/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) {
|
function expandInlineControlBlocks(lines) {
|
||||||
const marker = /(\{#(?:if|each)\b[^}]*\}|\{:(?:else(?:\s+if\b[^}]*)?|empty)\}|\{\/(?:if|each)\})/g;
|
const marker = /(\{#(?:if|each)\b[^}]*\}|\{:(?:else(?:\s+if\b[^}]*)?|empty)\}|\{\/(?:if|each)\})/g;
|
||||||
return lines.flatMap((line) => {
|
return transformOutsidePreservedRawBlocks(lines, (line) => {
|
||||||
if (!marker.test(line))
|
if (!marker.test(line))
|
||||||
return [line];
|
return [line];
|
||||||
marker.lastIndex = 0;
|
marker.lastIndex = 0;
|
||||||
@@ -27331,7 +27435,7 @@ ${serverFunctions}
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
function expandStructuredStateDeclarations(lines, unit) {
|
function expandStructuredStateDeclarations(lines, unit) {
|
||||||
return lines.flatMap((line) => {
|
return transformOutsidePreservedRawBlocks(lines, (line) => {
|
||||||
const match = /^(\s*state\s+[A-Za-z_$][\w$]*\s*=\s*)([\\[{][\s\S]*)$/.exec(line);
|
const match = /^(\s*state\s+[A-Za-z_$][\w$]*\s*=\s*)([\\[{][\s\S]*)$/.exec(line);
|
||||||
if (!match)
|
if (!match)
|
||||||
return [line];
|
return [line];
|
||||||
@@ -27387,6 +27491,14 @@ ${serverFunctions}
|
|||||||
index += 1;
|
index += 1;
|
||||||
continue;
|
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)) {
|
if (isMultilineOpeningTagStart(value)) {
|
||||||
const collected = collectOpeningTag(inputLines, index);
|
const collected = collectOpeningTag(inputLines, index);
|
||||||
value = collected.value;
|
value = collected.value;
|
||||||
@@ -27409,7 +27521,10 @@ ${serverFunctions}
|
|||||||
lineHtmlDepth = Math.max(0, htmlDepth - 1);
|
lineHtmlDepth = Math.max(0, htmlDepth - 1);
|
||||||
}
|
}
|
||||||
const depth = lineCodeDepth + lineHtmlDepth + lineControlDepth;
|
const depth = lineCodeDepth + lineHtmlDepth + lineControlDepth;
|
||||||
if (value.startsWith("<") && !value.startsWith("</") && !value.startsWith("<!--") && !value.startsWith("<!") && !value.startsWith("<?")) {
|
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("</") && !value.startsWith("<!--") && !value.startsWith("<!") && !value.startsWith("<?")) {
|
||||||
const formattedTag = formatOpeningTag(value, unit, depth, printWidth, multilineAttributes);
|
const formattedTag = formatOpeningTag(value, unit, depth, printWidth, multilineAttributes);
|
||||||
output.push(...formattedTag.lines);
|
output.push(...formattedTag.lines);
|
||||||
if (formattedTag.opensElement) {
|
if (formattedTag.opensElement) {
|
||||||
@@ -28895,11 +29010,13 @@ ${serverFunctions}
|
|||||||
if (!annotation)
|
if (!annotation)
|
||||||
return "unknown";
|
return "unknown";
|
||||||
const type = annotation.trim().replace(/^readonly\s+/, "");
|
const type = annotation.trim().replace(/^readonly\s+/, "");
|
||||||
if (/^(?:string|String)(?:\s*\|\s*(?:null|undefined))*$/.test(type))
|
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";
|
return "string";
|
||||||
if (/^(?:number|Number)(?:\s*\|\s*(?:null|undefined))*$/.test(type))
|
if (/^(?:number|Number)(?:\s*\|\s*(?:null|undefined))*$/.test(type) || concreteParts.length > 0 && concreteParts.every((part) => /^-?(?:\d+\.?\d*|\.\d+)$/.test(part)))
|
||||||
return "number";
|
return "number";
|
||||||
if (/^(?:boolean|Boolean)(?:\s*\|\s*(?:null|undefined))*$/.test(type))
|
if (/^(?:boolean|Boolean)(?:\s*\|\s*(?:null|undefined))*$/.test(type) || concreteParts.length > 0 && concreteParts.every((part) => /^(?:true|false)$/.test(part)))
|
||||||
return "boolean";
|
return "boolean";
|
||||||
if (/^bigint(?:\s*\|\s*(?:null|undefined))*$/.test(type))
|
if (/^bigint(?:\s*\|\s*(?:null|undefined))*$/.test(type))
|
||||||
return "bigint";
|
return "bigint";
|
||||||
@@ -30196,11 +30313,13 @@ var require_component_metadata = __commonJS((exports2, module2) => {
|
|||||||
}
|
}
|
||||||
function runtimeType(type) {
|
function runtimeType(type) {
|
||||||
const value = String(type || "").trim().replace(/^readonly\s+/, "");
|
const value = String(type || "").trim().replace(/^readonly\s+/, "");
|
||||||
if (/^(?:string|String)(?:\s*\|\s*(?:null|undefined))*$/.test(value))
|
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";
|
return "string";
|
||||||
if (/^(?:number|Number)(?:\s*\|\s*(?:null|undefined))*$/.test(value))
|
if (/^(?:number|Number)(?:\s*\|\s*(?:null|undefined))*$/.test(value) || concreteParts.length > 0 && concreteParts.every((part) => /^-?(?:\d+\.?\d*|\.\d+)$/.test(part)))
|
||||||
return "number";
|
return "number";
|
||||||
if (/^(?:boolean|Boolean)(?:\s*\|\s*(?:null|undefined))*$/.test(value))
|
if (/^(?:boolean|Boolean)(?:\s*\|\s*(?:null|undefined))*$/.test(value) || concreteParts.length > 0 && concreteParts.every((part) => /^(?:true|false)$/.test(part)))
|
||||||
return "boolean";
|
return "boolean";
|
||||||
if (/^bigint(?:\s*\|\s*(?:null|undefined))*$/.test(value))
|
if (/^bigint(?:\s*\|\s*(?:null|undefined))*$/.test(value))
|
||||||
return "bigint";
|
return "bigint";
|
||||||
@@ -30289,19 +30408,41 @@ var require_component_metadata = __commonJS((exports2, module2) => {
|
|||||||
return trimmed.slice(1, -1).trim();
|
return trimmed.slice(1, -1).trim();
|
||||||
return trimmed;
|
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) {
|
function attributeValueType(value, symbols = new Map) {
|
||||||
|
const expression = isWrappedExpression(value);
|
||||||
const unwrapped = unwrapAttributeValue(value);
|
const unwrapped = unwrapAttributeValue(value);
|
||||||
if (/^[A-Za-z_$][\w$]*$/.test(unwrapped) && symbols.has(unwrapped)) {
|
if (/^[A-Za-z_$][\w$]*$/.test(unwrapped) && symbols.has(unwrapped)) {
|
||||||
return symbols.get(unwrapped);
|
return symbols.get(unwrapped);
|
||||||
}
|
}
|
||||||
if (/^(?:true|false)$/.test(unwrapped))
|
const literal = expressionLiteralType(unwrapped);
|
||||||
|
if (literal)
|
||||||
|
return literal;
|
||||||
|
if (expression) {
|
||||||
|
if (/^!\s*/.test(unwrapped) || /(?:===|!==|==|!=|<=|>=|<|>|\bin\b|\binstanceof\b)/.test(unwrapped)) {
|
||||||
return "boolean";
|
return "boolean";
|
||||||
if (/^-?(?:\d+\.?\d*|\.\d+)$/.test(unwrapped))
|
}
|
||||||
return "number";
|
return "unknown";
|
||||||
if (unwrapped.startsWith("["))
|
}
|
||||||
return "array";
|
|
||||||
if (unwrapped.startsWith("{"))
|
|
||||||
return "object";
|
|
||||||
return "string";
|
return "string";
|
||||||
}
|
}
|
||||||
function isTypeCompatible(prop, value, symbols = new Map) {
|
function isTypeCompatible(prop, value, symbols = new Map) {
|
||||||
@@ -30309,6 +30450,8 @@ var require_component_metadata = __commonJS((exports2, module2) => {
|
|||||||
if (expected === "unknown" || expected === "null")
|
if (expected === "unknown" || expected === "null")
|
||||||
return true;
|
return true;
|
||||||
const actual = runtimeType(attributeValueType(value, symbols));
|
const actual = runtimeType(attributeValueType(value, symbols));
|
||||||
|
if (actual === "unknown")
|
||||||
|
return true;
|
||||||
if (expected === actual)
|
if (expected === actual)
|
||||||
return true;
|
return true;
|
||||||
if (expected === "number" && actual === "string")
|
if (expected === "number" && actual === "string")
|
||||||
@@ -30409,8 +30552,8 @@ var require_component_metadata = __commonJS((exports2, module2) => {
|
|||||||
end: attribute.nameEnd
|
end: attribute.nameEnd
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const literal = stringLiteral(attribute.value) ?? attribute.value;
|
const optionValue = isWrappedExpression(attribute.value) ? stringLiteral(unwrapAttributeValue(attribute.value)) : stringLiteral(attribute.value) ?? attribute.value;
|
||||||
if (prop.options.length > 0 && !prop.options.includes(literal)) {
|
if (optionValue !== null && prop.options.length > 0 && !prop.options.includes(optionValue)) {
|
||||||
diagnostics.push({
|
diagnostics.push({
|
||||||
severity: "warning",
|
severity: "warning",
|
||||||
code: "wrn-component-prop-option",
|
code: "wrn-component-prop-option",
|
||||||
@@ -30727,58 +30870,6 @@ var require_diagnostics = __commonJS((exports2, module2) => {
|
|||||||
}
|
}
|
||||||
return diagnostic;
|
return diagnostic;
|
||||||
}
|
}
|
||||||
function stripComments(source) {
|
|
||||||
const masked = [...source];
|
|
||||||
let quote = null;
|
|
||||||
let escaped = false;
|
|
||||||
const maskRange = (start, end) => {
|
|
||||||
for (let index = start;index < end; index += 1) {
|
|
||||||
if (source[index] !== `
|
|
||||||
` && source[index] !== "\r") {
|
|
||||||
masked[index] = " ";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
for (let index = 0;index < source.length; index += 1) {
|
|
||||||
const character = source[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 (source.startsWith("//", index)) {
|
|
||||||
const lineEnd = source.indexOf(`
|
|
||||||
`, index + 2);
|
|
||||||
const end = lineEnd === -1 ? source.length : lineEnd;
|
|
||||||
maskRange(index, end);
|
|
||||||
index = end - 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (source.startsWith("/*", index)) {
|
|
||||||
const commentEnd = source.indexOf("*/", index + 2);
|
|
||||||
const end = commentEnd === -1 ? source.length : commentEnd + 2;
|
|
||||||
maskRange(index, end);
|
|
||||||
index = end - 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (source.startsWith("<!--", index)) {
|
|
||||||
const commentEnd = source.indexOf("-->", index + 4);
|
|
||||||
const end = commentEnd === -1 ? source.length : commentEnd + 3;
|
|
||||||
maskRange(index, end);
|
|
||||||
index = end - 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return masked.join("");
|
|
||||||
}
|
|
||||||
function maskLeadingTrivia(source) {
|
function maskLeadingTrivia(source) {
|
||||||
const masked = [...source];
|
const masked = [...source];
|
||||||
let offset = 0;
|
let offset = 0;
|
||||||
@@ -30897,9 +30988,89 @@ var require_diagnostics = __commonJS((exports2, module2) => {
|
|||||||
}
|
}
|
||||||
return diagnostics;
|
return diagnostics;
|
||||||
}
|
}
|
||||||
|
function maskHtmlComments(source) {
|
||||||
|
const masked = [...source];
|
||||||
|
let index = 0;
|
||||||
|
while (index < source.length) {
|
||||||
|
if (!source.startsWith("<!--", index)) {
|
||||||
|
index += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const commentEnd = source.indexOf("-->", 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) {
|
function validateHtmlTags(document, source) {
|
||||||
const diagnostics = [];
|
const diagnostics = [];
|
||||||
const stack = [];
|
|
||||||
const voidElements = new Set([
|
const voidElements = new Set([
|
||||||
"area",
|
"area",
|
||||||
"base",
|
"base",
|
||||||
@@ -30916,31 +31087,39 @@ var require_diagnostics = __commonJS((exports2, module2) => {
|
|||||||
"track",
|
"track",
|
||||||
"wbr"
|
"wbr"
|
||||||
]);
|
]);
|
||||||
const cleaned = stripComments(source);
|
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;
|
const tagPattern = /<\/?([A-Za-z][A-Za-z0-9_$:.-]*)(?:\s[\s\S]*?)?\/?>/g;
|
||||||
let match;
|
let match;
|
||||||
while ((match = tagPattern.exec(cleaned)) !== null) {
|
while ((match = tagPattern.exec(cleaned)) !== null) {
|
||||||
const completeTag = match[0];
|
const completeTag = match[0];
|
||||||
const tagName = match[1];
|
const tagName = match[1];
|
||||||
|
const normalizedName = /^[a-z]/.test(tagName) ? tagName.toLowerCase() : tagName;
|
||||||
const lowerTag = tagName.toLowerCase();
|
const lowerTag = tagName.toLowerCase();
|
||||||
|
const absoluteStart = range.start + match.index;
|
||||||
const isClosing = completeTag.startsWith("</");
|
const isClosing = completeTag.startsWith("</");
|
||||||
const isSelfClosing = completeTag.endsWith("/>");
|
const isSelfClosing = /\/\s*>$/.test(completeTag);
|
||||||
const isVoid = voidElements.has(lowerTag);
|
const isVoid = voidElements.has(lowerTag);
|
||||||
if (isClosing) {
|
if (isClosing) {
|
||||||
const last = stack.pop();
|
const matchingIndex = stack.findLastIndex((item) => item.normalizedName === normalizedName);
|
||||||
if (!last) {
|
if (matchingIndex === -1) {
|
||||||
diagnostics.push(createDiagnostic(document, match.index, match.index + completeTag.length, `Unexpected closing tag </${tagName}>.`, vscode.DiagnosticSeverity.Error, "wrn-unexpected-html-close"));
|
diagnostics.push(createDiagnostic(document, absoluteStart, absoluteStart + completeTag.length, `Unexpected closing tag </${tagName}>.`, vscode.DiagnosticSeverity.Error, "wrn-unexpected-html-close"));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (last.tagName !== tagName) {
|
const last = stack.at(-1);
|
||||||
diagnostics.push(createDiagnostic(document, match.index, match.index + completeTag.length, `Mismatched closing tag </${tagName}>. Expected </${last.tagName}>.`, vscode.DiagnosticSeverity.Error, "wrn-mismatched-html-tag"));
|
if (last.normalizedName !== normalizedName) {
|
||||||
|
diagnostics.push(createDiagnostic(document, absoluteStart, absoluteStart + completeTag.length, `Mismatched closing tag </${tagName}>. Expected </${last.tagName}>.`, vscode.DiagnosticSeverity.Error, "wrn-mismatched-html-tag"));
|
||||||
}
|
}
|
||||||
|
stack.splice(matchingIndex);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!isSelfClosing && !isVoid) {
|
if (!isSelfClosing && !isVoid) {
|
||||||
stack.push({
|
stack.push({
|
||||||
tagName,
|
tagName,
|
||||||
offset: match.index,
|
normalizedName,
|
||||||
|
offset: absoluteStart,
|
||||||
length: completeTag.length
|
length: completeTag.length
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -30948,6 +31127,7 @@ var require_diagnostics = __commonJS((exports2, module2) => {
|
|||||||
for (const tag of stack) {
|
for (const tag of stack) {
|
||||||
diagnostics.push(createDiagnostic(document, tag.offset, tag.offset + tag.length, `Missing closing tag </${tag.tagName}>.`, vscode.DiagnosticSeverity.Error, "wrn-missing-html-close"));
|
diagnostics.push(createDiagnostic(document, tag.offset, tag.offset + tag.length, `Missing closing tag </${tag.tagName}>.`, vscode.DiagnosticSeverity.Error, "wrn-missing-html-close"));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return diagnostics;
|
return diagnostics;
|
||||||
}
|
}
|
||||||
function getRootBodyRange(source, rootMatch) {
|
function getRootBodyRange(source, rootMatch) {
|
||||||
@@ -31923,9 +32103,9 @@ async function activate(context) {
|
|||||||
registerCompilerDiagnostics(context);
|
registerCompilerDiagnostics(context);
|
||||||
registerCompletionProvider(context);
|
registerCompletionProvider(context);
|
||||||
registerDefinitionProvider(context);
|
registerDefinitionProvider(context);
|
||||||
registerComponentIntelligence(context);
|
|
||||||
registerFormatter(context);
|
registerFormatter(context);
|
||||||
}
|
}
|
||||||
|
registerComponentIntelligence(context);
|
||||||
registerSemanticTokens(context);
|
registerSemanticTokens(context);
|
||||||
registerV060LanguageFeatures(context);
|
registerV060LanguageFeatures(context);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -376,9 +376,12 @@ async function activate(context) {
|
|||||||
registerCompilerDiagnostics(context);
|
registerCompilerDiagnostics(context);
|
||||||
registerCompletionProvider(context);
|
registerCompletionProvider(context);
|
||||||
registerDefinitionProvider(context);
|
registerDefinitionProvider(context);
|
||||||
registerComponentIntelligence(context);
|
|
||||||
registerFormatter(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);
|
registerSemanticTokens(context);
|
||||||
registerV060LanguageFeatures(context);
|
registerV060LanguageFeatures(context);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
#!/usr/bin/env bun
|
#!/usr/bin/env node
|
||||||
|
// WRN editor language server source hash: 5397c7912894dd5ab330f6fafc0f4093adbb94d95c23366ba7883605a724a7ae
|
||||||
|
// WRN editor language server generator hash: f593a44aaf05495b789ce7a3086bee1eebb951b884d41c0e017bbcfe5f547e72
|
||||||
// @bun @bun-cjs
|
// @bun @bun-cjs
|
||||||
(function(exports, require, module, __filename, __dirname) {var __create = Object.create;
|
(function(exports, require, module, __filename, __dirname) {var __create = Object.create;
|
||||||
var __getProtoOf = Object.getPrototypeOf;
|
var __getProtoOf = Object.getPrototypeOf;
|
||||||
@@ -169438,6 +169440,17 @@ function formatOpeningTag(value, unit, depth, printWidth = 100, multilineAttribu
|
|||||||
opensElement
|
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}</${parsed.tagName}>`);
|
||||||
|
} else if (parsed.remainder) {
|
||||||
|
lines2.push(`${childIndent}${parsed.remainder}`);
|
||||||
|
}
|
||||||
|
return { lines: lines2, opensElement };
|
||||||
|
}
|
||||||
const lines = [
|
const lines = [
|
||||||
`${baseIndent}<${parsed.tagName}`,
|
`${baseIndent}<${parsed.tagName}`,
|
||||||
...parsed.attributes.flatMap((attribute) => formatAttribute(attribute, childIndent, unit))
|
...parsed.attributes.flatMap((attribute) => formatAttribute(attribute, childIndent, unit))
|
||||||
@@ -169475,6 +169488,63 @@ function isMultilineOpeningTagStart(value) {
|
|||||||
function isClosingTag(value) {
|
function isClosingTag(value) {
|
||||||
return /^<\/[A-Za-z][\w$:.-]*\s*>/.test(value);
|
return /^<\/[A-Za-z][\w$:.-]*\s*>/.test(value);
|
||||||
}
|
}
|
||||||
|
function isBalancedInlineHtmlFragment(value) {
|
||||||
|
if (!value.startsWith("<") || value.startsWith("</") || value.startsWith("<!--")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const stack = [];
|
||||||
|
let tagCount = 0;
|
||||||
|
let rootCount = 0;
|
||||||
|
let hasNestedElement = false;
|
||||||
|
let hasOutsideText = false;
|
||||||
|
let index = 0;
|
||||||
|
while (index < value.length) {
|
||||||
|
const tagStart = value.indexOf("<", index);
|
||||||
|
if (tagStart === -1) {
|
||||||
|
if (stack.length === 0 && value.slice(index).trim())
|
||||||
|
hasOutsideText = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (stack.length === 0 && value.slice(index, tagStart).trim())
|
||||||
|
hasOutsideText = true;
|
||||||
|
if (value.startsWith("<!--", tagStart)) {
|
||||||
|
const commentEnd = value.indexOf("-->", 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("</");
|
||||||
|
const selfClosing = /\/\s*>$/.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) {
|
function isControlBlockOpen(value) {
|
||||||
return /^\{#(?:if|each)\b[\s\S]*\}$/.test(value);
|
return /^\{#(?:if|each)\b[\s\S]*\}$/.test(value);
|
||||||
}
|
}
|
||||||
@@ -169570,9 +169640,43 @@ function collectOpeningTag(inputLines, startIndex) {
|
|||||||
endIndex: index
|
endIndex: index
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
function isPreservedRawBlockStart(value) {
|
||||||
|
return /<pre(?:\s|>)/i.test(value) && !/<\/pre\s*>/i.test(value.slice(0, value.search(/<pre(?:\s|>)/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) {
|
function expandInlineControlBlocks(lines) {
|
||||||
const marker = /(\{#(?:if|each)\b[^}]*\}|\{:(?:else(?:\s+if\b[^}]*)?|empty)\}|\{\/(?:if|each)\})/g;
|
const marker = /(\{#(?:if|each)\b[^}]*\}|\{:(?:else(?:\s+if\b[^}]*)?|empty)\}|\{\/(?:if|each)\})/g;
|
||||||
return lines.flatMap((line) => {
|
return transformOutsidePreservedRawBlocks(lines, (line) => {
|
||||||
if (!marker.test(line))
|
if (!marker.test(line))
|
||||||
return [line];
|
return [line];
|
||||||
marker.lastIndex = 0;
|
marker.lastIndex = 0;
|
||||||
@@ -169582,7 +169686,7 @@ function expandInlineControlBlocks(lines) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
function expandStructuredStateDeclarations(lines, unit) {
|
function expandStructuredStateDeclarations(lines, unit) {
|
||||||
return lines.flatMap((line) => {
|
return transformOutsidePreservedRawBlocks(lines, (line) => {
|
||||||
const match = /^(\s*state\s+[A-Za-z_$][\w$]*\s*=\s*)([\\[{][\s\S]*)$/.exec(line);
|
const match = /^(\s*state\s+[A-Za-z_$][\w$]*\s*=\s*)([\\[{][\s\S]*)$/.exec(line);
|
||||||
if (!match)
|
if (!match)
|
||||||
return [line];
|
return [line];
|
||||||
@@ -169638,6 +169742,14 @@ function formatWrnPass(source, options = {}) {
|
|||||||
index += 1;
|
index += 1;
|
||||||
continue;
|
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)) {
|
if (isMultilineOpeningTagStart(value)) {
|
||||||
const collected = collectOpeningTag(inputLines, index);
|
const collected = collectOpeningTag(inputLines, index);
|
||||||
value = collected.value;
|
value = collected.value;
|
||||||
@@ -169660,7 +169772,10 @@ function formatWrnPass(source, options = {}) {
|
|||||||
lineHtmlDepth = Math.max(0, htmlDepth - 1);
|
lineHtmlDepth = Math.max(0, htmlDepth - 1);
|
||||||
}
|
}
|
||||||
const depth = lineCodeDepth + lineHtmlDepth + lineControlDepth;
|
const depth = lineCodeDepth + lineHtmlDepth + lineControlDepth;
|
||||||
if (value.startsWith("<") && !value.startsWith("</") && !value.startsWith("<!--") && !value.startsWith("<!") && !value.startsWith("<?")) {
|
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("</") && !value.startsWith("<!--") && !value.startsWith("<!") && !value.startsWith("<?")) {
|
||||||
const formattedTag = formatOpeningTag(value, unit, depth, printWidth, multilineAttributes);
|
const formattedTag = formatOpeningTag(value, unit, depth, printWidth, multilineAttributes);
|
||||||
output.push(...formattedTag.lines);
|
output.push(...formattedTag.lines);
|
||||||
if (formattedTag.opensElement) {
|
if (formattedTag.opensElement) {
|
||||||
@@ -169745,11 +169860,13 @@ function runtimeTypeOf(annotation) {
|
|||||||
if (!annotation)
|
if (!annotation)
|
||||||
return "unknown";
|
return "unknown";
|
||||||
const type = annotation.trim().replace(/^readonly\s+/, "");
|
const type = annotation.trim().replace(/^readonly\s+/, "");
|
||||||
if (/^(?:string|String)(?:\s*\|\s*(?:null|undefined))*$/.test(type))
|
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";
|
return "string";
|
||||||
if (/^(?:number|Number)(?:\s*\|\s*(?:null|undefined))*$/.test(type))
|
if (/^(?:number|Number)(?:\s*\|\s*(?:null|undefined))*$/.test(type) || concreteParts.length > 0 && concreteParts.every((part) => /^-?(?:\d+\.?\d*|\.\d+)$/.test(part)))
|
||||||
return "number";
|
return "number";
|
||||||
if (/^(?:boolean|Boolean)(?:\s*\|\s*(?:null|undefined))*$/.test(type))
|
if (/^(?:boolean|Boolean)(?:\s*\|\s*(?:null|undefined))*$/.test(type) || concreteParts.length > 0 && concreteParts.every((part) => /^(?:true|false)$/.test(part)))
|
||||||
return "boolean";
|
return "boolean";
|
||||||
if (/^bigint(?:\s*\|\s*(?:null|undefined))*$/.test(type))
|
if (/^bigint(?:\s*\|\s*(?:null|undefined))*$/.test(type))
|
||||||
return "bigint";
|
return "bigint";
|
||||||
@@ -171689,7 +171806,7 @@ function runtimeNamespace(runtime) {
|
|||||||
return `__wrn_${runtime}`;
|
return `__wrn_${runtime}`;
|
||||||
}
|
}
|
||||||
function functionDeclaration(fn) {
|
function functionDeclaration(fn) {
|
||||||
const params = fn.parameters.map((param) => `${param.name}${param.optional ? "?" : ""}${param.valueType ? `: ${param.valueType}` : param.name === "event" ? ": CustomEvent<any> & { target: HTMLElement }" : ": unknown"}${param.default ? ` = ${param.default}` : ""}`).join(", ");
|
const params = fn.parameters.map((param) => `${param.name}${param.optional ? "?" : ""}${param.valueType ? `: ${param.valueType}` : param.name === "event" ? ": CustomEvent<any> & { target: HTMLElement }" : ": any"}${param.default ? ` = ${param.default}` : ""}`).join(", ");
|
||||||
return `export ${fn.async ? "async " : ""}function ${fn.name}(${params})${fn.returnType ? `: ${fn.returnType}` : ""} {${fn.body}}`;
|
return `export ${fn.async ? "async " : ""}function ${fn.name}(${params})${fn.returnType ? `: ${fn.returnType}` : ""} {${fn.body}}`;
|
||||||
}
|
}
|
||||||
function retainedImports(ast) {
|
function retainedImports(ast) {
|
||||||
@@ -171702,6 +171819,51 @@ function retainedImports(ast) {
|
|||||||
}).map((entry) => entry.raw).join(`
|
}).map((entry) => entry.raw).join(`
|
||||||
`);
|
`);
|
||||||
}
|
}
|
||||||
|
var RESERVED_BINDING_NAMES = new Set([
|
||||||
|
"await",
|
||||||
|
"break",
|
||||||
|
"case",
|
||||||
|
"catch",
|
||||||
|
"class",
|
||||||
|
"const",
|
||||||
|
"continue",
|
||||||
|
"debugger",
|
||||||
|
"default",
|
||||||
|
"delete",
|
||||||
|
"do",
|
||||||
|
"else",
|
||||||
|
"enum",
|
||||||
|
"export",
|
||||||
|
"extends",
|
||||||
|
"false",
|
||||||
|
"finally",
|
||||||
|
"for",
|
||||||
|
"function",
|
||||||
|
"if",
|
||||||
|
"import",
|
||||||
|
"in",
|
||||||
|
"instanceof",
|
||||||
|
"let",
|
||||||
|
"new",
|
||||||
|
"null",
|
||||||
|
"return",
|
||||||
|
"static",
|
||||||
|
"super",
|
||||||
|
"switch",
|
||||||
|
"this",
|
||||||
|
"throw",
|
||||||
|
"true",
|
||||||
|
"try",
|
||||||
|
"typeof",
|
||||||
|
"var",
|
||||||
|
"void",
|
||||||
|
"while",
|
||||||
|
"with",
|
||||||
|
"yield"
|
||||||
|
]);
|
||||||
|
function safeBindingName(name) {
|
||||||
|
return /^[A-Za-z_$][\w$]*$/.test(name) && !RESERVED_BINDING_NAMES.has(name);
|
||||||
|
}
|
||||||
function virtualTypeScriptModule(source, filePath = "component.wrn", appRoot = findAppRoot(filePath)) {
|
function virtualTypeScriptModule(source, filePath = "component.wrn", appRoot = findAppRoot(filePath)) {
|
||||||
const ast = parse(source);
|
const ast = parse(source);
|
||||||
const chunks = [];
|
const chunks = [];
|
||||||
@@ -171728,6 +171890,8 @@ function virtualTypeScriptModule(source, filePath = "component.wrn", appRoot = f
|
|||||||
|
|
||||||
`), ast.types[0]?.trim());
|
`), ast.types[0]?.trim());
|
||||||
for (const prop of ast.props) {
|
for (const prop of ast.props) {
|
||||||
|
if (!safeBindingName(prop.name))
|
||||||
|
continue;
|
||||||
append(`declare const ${prop.name}: Readonly<${prop.valueType ?? "unknown"}>;`, prop.name);
|
append(`declare const ${prop.name}: Readonly<${prop.valueType ?? "unknown"}>;`, prop.name);
|
||||||
}
|
}
|
||||||
for (const state of ast.states) {
|
for (const state of ast.states) {
|
||||||
@@ -171739,7 +171903,7 @@ function virtualTypeScriptModule(source, filePath = "component.wrn", appRoot = f
|
|||||||
const outputType = ast.outputs.map((output) => `${output.name}: (${output.payload ? `${output.payload.name}${output.payload.optional ? "?" : ""}: ${output.payload.valueType}` : ""}) => void`).join("; ");
|
const outputType = ast.outputs.map((output) => `${output.name}: (${output.payload ? `${output.payload.name}${output.payload.optional ? "?" : ""}: ${output.payload.valueType}` : ""}) => void`).join("; ");
|
||||||
const serverFunctions = ast.runtimeFunctions.filter((fn) => fn.runtime === "server");
|
const serverFunctions = ast.runtimeFunctions.filter((fn) => fn.runtime === "server");
|
||||||
const serverType = serverFunctions.map((fn) => `${fn.name}: (${fn.parameters.map((param) => `${param.name}${param.optional ? "?" : ""}: ${param.valueType ?? "unknown"}`).join(", ")}) => ${fn.returnType ?? (fn.async ? "Promise<unknown>" : "unknown")}`).join("; ");
|
const serverType = serverFunctions.map((fn) => `${fn.name}: (${fn.parameters.map((param) => `${param.name}${param.optional ? "?" : ""}: ${param.valueType ?? "unknown"}`).join(", ")}) => ${fn.returnType ?? (fn.async ? "Promise<unknown>" : "unknown")}`).join("; ");
|
||||||
append(`declare const output: { ${outputType} };`);
|
append(`declare const output: { [name: string]: (...args: any[]) => void; ${outputType} };`);
|
||||||
for (const output of ast.outputs)
|
for (const output of ast.outputs)
|
||||||
append(`declare const ${output.name}: (${output.payload ? `${output.payload.name}${output.payload.optional ? "?" : ""}: ${output.payload.valueType}` : ""}) => void;`);
|
append(`declare const ${output.name}: (${output.payload ? `${output.payload.name}${output.payload.optional ? "?" : ""}: ${output.payload.valueType}` : ""}) => void;`);
|
||||||
for (const match of source.matchAll(/@event\s+([A-Za-z_$][\w$]*)\s*=\s*function/g))
|
for (const match of source.matchAll(/@event\s+([A-Za-z_$][\w$]*)\s*=\s*function/g))
|
||||||
@@ -171923,7 +172087,8 @@ function componentUsageDiagnostics(source, ast, filePath, appRoot) {
|
|||||||
continue;
|
continue;
|
||||||
const received = attr.boolean ? "boolean" : inferredRuntimeType(JSON.stringify(attr.value));
|
const received = attr.boolean ? "boolean" : inferredRuntimeType(JSON.stringify(attr.value));
|
||||||
const expected = runtimeTypeOf(prop.type);
|
const expected = runtimeTypeOf(prop.type);
|
||||||
if (expected !== "unknown" && received !== "unknown" && expected !== received) {
|
const compatibleLiteral = expected === "boolean" && /^(?:true|false|1|0|yes|no|on|off)?$/i.test(attr.value) || expected === "number" && attr.value.trim() !== "" && Number.isFinite(Number(attr.value));
|
||||||
|
if (expected !== "unknown" && received !== "unknown" && expected !== received && !compatibleLiteral) {
|
||||||
diagnostics.push({
|
diagnostics.push({
|
||||||
code: "WRN-COMPONENT-PROP-TYPE",
|
code: "WRN-COMPONENT-PROP-TYPE",
|
||||||
category: "error",
|
category: "error",
|
||||||
@@ -171956,8 +172121,12 @@ function componentUsageDiagnostics(source, ast, filePath, appRoot) {
|
|||||||
function category(value) {
|
function category(value) {
|
||||||
return value === import_typescript.default.DiagnosticCategory.Error ? "error" : value === import_typescript.default.DiagnosticCategory.Warning ? "warning" : "info";
|
return value === import_typescript.default.DiagnosticCategory.Error ? "error" : value === import_typescript.default.DiagnosticCategory.Warning ? "warning" : "info";
|
||||||
}
|
}
|
||||||
function hostWithVirtualFiles(files, options) {
|
function hostWithVirtualFiles(files, options, standardLibraryDirectory) {
|
||||||
const host = import_typescript.default.createCompilerHost(options, true);
|
const host = import_typescript.default.createCompilerHost(options, true);
|
||||||
|
if (standardLibraryDirectory) {
|
||||||
|
host.getDefaultLibFileName = (compilerOptions) => import_node_path2.join(standardLibraryDirectory, import_typescript.default.getDefaultLibFileName(compilerOptions));
|
||||||
|
host.getDefaultLibLocation = () => standardLibraryDirectory;
|
||||||
|
}
|
||||||
const originalGet = host.getSourceFile.bind(host);
|
const originalGet = host.getSourceFile.bind(host);
|
||||||
host.fileExists = (fileName) => files.has(import_node_path2.normalize(fileName)) || import_typescript.default.sys.fileExists(fileName);
|
host.fileExists = (fileName) => files.has(import_node_path2.normalize(fileName)) || import_typescript.default.sys.fileExists(fileName);
|
||||||
host.readFile = (fileName) => files.get(import_node_path2.normalize(fileName)) ?? import_typescript.default.sys.readFile(fileName);
|
host.readFile = (fileName) => files.get(import_node_path2.normalize(fileName)) ?? import_typescript.default.sys.readFile(fileName);
|
||||||
@@ -171967,10 +172136,23 @@ function hostWithVirtualFiles(files, options) {
|
|||||||
};
|
};
|
||||||
return host;
|
return host;
|
||||||
}
|
}
|
||||||
|
function resolveStandardLibraryDirectory(appRoot, options) {
|
||||||
|
const candidates = [
|
||||||
|
import_node_path2.dirname(import_typescript.default.getDefaultLibFilePath(options)),
|
||||||
|
import_node_path2.join(appRoot, "node_modules", "typescript", "lib"),
|
||||||
|
import_node_path2.join(process.cwd(), "node_modules", "typescript", "lib")
|
||||||
|
];
|
||||||
|
for (const candidate of candidates) {
|
||||||
|
if ((options.lib ?? []).every((name) => import_typescript.default.sys.fileExists(import_node_path2.join(candidate, name)))) {
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
function mappedPosition(virtual, line, column) {
|
function mappedPosition(virtual, line, column) {
|
||||||
const mapping = virtual.mappings.find((entry) => line >= entry.virtualStartLine && line <= entry.virtualEndLine);
|
const mapping = virtual.mappings.find((entry) => line >= entry.virtualStartLine && line <= entry.virtualEndLine);
|
||||||
if (!mapping)
|
if (!mapping)
|
||||||
return { line: 1, column: 1 };
|
return null;
|
||||||
const offset = line - mapping.virtualStartLine;
|
const offset = line - mapping.virtualStartLine;
|
||||||
return {
|
return {
|
||||||
line: mapping.sourceStartLine + offset,
|
line: mapping.sourceStartLine + offset,
|
||||||
@@ -172099,25 +172281,37 @@ function checkWrnSource(source, options = {}) {
|
|||||||
lib: ["lib.esnext.d.ts", "lib.dom.d.ts", "lib.dom.iterable.d.ts"]
|
lib: ["lib.esnext.d.ts", "lib.dom.d.ts", "lib.dom.iterable.d.ts"]
|
||||||
};
|
};
|
||||||
const rootNames = [virtual.fileName, ...appTypes.files.keys()];
|
const rootNames = [virtual.fileName, ...appTypes.files.keys()];
|
||||||
const program = import_typescript.default.createProgram(rootNames, compilerOptions, hostWithVirtualFiles(files, compilerOptions));
|
const standardLibraryDirectory = resolveStandardLibraryDirectory(appRoot, compilerOptions);
|
||||||
const tsDiagnostics = import_typescript.default.getPreEmitDiagnostics(program).map((diagnostic) => {
|
const tsDiagnostics = [];
|
||||||
|
if (standardLibraryDirectory) {
|
||||||
|
const program = import_typescript.default.createProgram(rootNames, compilerOptions, hostWithVirtualFiles(files, compilerOptions, standardLibraryDirectory));
|
||||||
|
for (const diagnostic of import_typescript.default.getPreEmitDiagnostics(program)) {
|
||||||
const file = diagnostic.file;
|
const file = diagnostic.file;
|
||||||
|
if (!file)
|
||||||
|
continue;
|
||||||
|
const normalizedFile = import_node_path2.normalize(file.fileName);
|
||||||
|
const isVirtual = normalizedFile === import_node_path2.normalize(virtual.fileName);
|
||||||
|
const isApplicationType = files.has(normalizedFile);
|
||||||
|
if (!isVirtual && !isApplicationType)
|
||||||
|
continue;
|
||||||
const start = diagnostic.start ?? 0;
|
const start = diagnostic.start ?? 0;
|
||||||
const virtualPosition = file?.getLineAndCharacterOfPosition(start) ?? { line: 0, character: 0 };
|
const virtualPosition = file.getLineAndCharacterOfPosition(start);
|
||||||
const isVirtual = import_node_path2.normalize(file?.fileName ?? "") === import_node_path2.normalize(virtual.fileName);
|
|
||||||
const sourcePosition = isVirtual ? mappedPosition(virtual, virtualPosition.line + 1, virtualPosition.character + 1) : { line: virtualPosition.line + 1, column: virtualPosition.character + 1 };
|
const sourcePosition = isVirtual ? mappedPosition(virtual, virtualPosition.line + 1, virtualPosition.character + 1) : { line: virtualPosition.line + 1, column: virtualPosition.character + 1 };
|
||||||
return {
|
if (!sourcePosition)
|
||||||
|
continue;
|
||||||
|
tsDiagnostics.push({
|
||||||
code: `WRN-TYPE-${diagnostic.code}`,
|
code: `WRN-TYPE-${diagnostic.code}`,
|
||||||
category: category(diagnostic.category),
|
category: category(diagnostic.category),
|
||||||
message: import_typescript.default.flattenDiagnosticMessageText(diagnostic.messageText, `
|
message: import_typescript.default.flattenDiagnosticMessageText(diagnostic.messageText, `
|
||||||
`),
|
`),
|
||||||
file: isVirtual ? filePath : file?.fileName ?? filePath,
|
file: isVirtual ? filePath : file.fileName,
|
||||||
line: sourcePosition.line,
|
line: sourcePosition.line,
|
||||||
column: sourcePosition.column,
|
column: sourcePosition.column,
|
||||||
length: diagnostic.length ?? 1,
|
length: diagnostic.length ?? 1,
|
||||||
hint: "Fix the TypeScript contract or expression in the related .wrn declaration."
|
hint: "Fix the TypeScript contract or expression in the related .wrn declaration."
|
||||||
};
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
return [
|
return [
|
||||||
...options.checkRuntimeBoundaries === false ? [] : runtimeDiagnostics(source, virtual.ast, filePath),
|
...options.checkRuntimeBoundaries === false ? [] : runtimeDiagnostics(source, virtual.ast, filePath),
|
||||||
...componentUsageDiagnostics(source, virtual.ast, filePath, appRoot),
|
...componentUsageDiagnostics(source, virtual.ast, filePath, appRoot),
|
||||||
@@ -172527,11 +172721,30 @@ function send(value) {
|
|||||||
function result(id, value) {
|
function result(id, value) {
|
||||||
send({ jsonrpc: "2.0", id, result: value });
|
send({ jsonrpc: "2.0", id, result: value });
|
||||||
}
|
}
|
||||||
|
function internalDiagnostic(document, error) {
|
||||||
|
const message = error instanceof Error ? error.message : String(error);
|
||||||
|
return {
|
||||||
|
range: { start: { line: 0, character: 0 }, end: { line: 0, character: 1 } },
|
||||||
|
severity: 1,
|
||||||
|
code: "WRN-LSP-INTERNAL",
|
||||||
|
source: "wrnexus",
|
||||||
|
message: `WRNexus language analysis failed safely: ${message}`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function safeDocumentDiagnostics(document) {
|
||||||
|
try {
|
||||||
|
return documentDiagnostics(document);
|
||||||
|
} 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) {
|
||||||
send({
|
send({
|
||||||
jsonrpc: "2.0",
|
jsonrpc: "2.0",
|
||||||
method: "textDocument/publishDiagnostics",
|
method: "textDocument/publishDiagnostics",
|
||||||
params: { uri: document.uri, diagnostics: documentDiagnostics(document) }
|
params: { uri: document.uri, diagnostics: safeDocumentDiagnostics(document) }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
function clearDiagnosticTimer(uri) {
|
function clearDiagnosticTimer(uri) {
|
||||||
@@ -172567,7 +172780,7 @@ async function handle(message) {
|
|||||||
case "initialize":
|
case "initialize":
|
||||||
workspaceRoot = params.rootPath ?? rootFromUri(params.rootUri);
|
workspaceRoot = params.rootPath ?? rootFromUri(params.rootUri);
|
||||||
result(message.id, {
|
result(message.id, {
|
||||||
serverInfo: { name: "WRNexus Language Server", version: "0.8.0" },
|
serverInfo: { name: "WRNexus Language Server", version: "0.8.3" },
|
||||||
capabilities: {
|
capabilities: {
|
||||||
textDocumentSync: 1,
|
textDocumentSync: 1,
|
||||||
documentFormattingProvider: true,
|
documentFormattingProvider: true,
|
||||||
@@ -172676,7 +172889,7 @@ async function handle(message) {
|
|||||||
result(message.id, []);
|
result(message.id, []);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
const actions = documentDiagnostics(document).filter((item) => item.code === "WRNA11Y001").map((item) => ({
|
const actions = safeDocumentDiagnostics(document).filter((item) => item.code === "WRNA11Y001").map((item) => ({
|
||||||
title: "Add empty alt attribute",
|
title: "Add empty alt attribute",
|
||||||
kind: "quickfix",
|
kind: "quickfix",
|
||||||
diagnostics: [item],
|
diagnostics: [item],
|
||||||
@@ -172740,12 +172953,36 @@ function consume() {
|
|||||||
return;
|
return;
|
||||||
const body = buffer.subarray(bodyStart, bodyStart + length).toString();
|
const body = buffer.subarray(bodyStart, bodyStart + length).toString();
|
||||||
buffer = buffer.subarray(bodyStart + length);
|
buffer = buffer.subarray(bodyStart + length);
|
||||||
handle(JSON.parse(body));
|
let message;
|
||||||
|
try {
|
||||||
|
message = JSON.parse(body);
|
||||||
|
} catch (error) {
|
||||||
|
process.stderr.write(`[wrnexus-lsp] invalid JSON-RPC payload: ${error instanceof Error ? error.message : String(error)}
|
||||||
|
`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
handle(message).catch((error) => {
|
||||||
|
const detail = error instanceof Error ? error.stack ?? error.message : String(error);
|
||||||
|
process.stderr.write(`[wrnexus-lsp] request failed: ${detail}
|
||||||
|
`);
|
||||||
|
if (message.id !== undefined) {
|
||||||
|
send({
|
||||||
|
jsonrpc: "2.0",
|
||||||
|
id: message.id,
|
||||||
|
error: { code: -32603, message: "WRNexus language server request failed", data: detail }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
process.stdin.on("data", (chunk) => {
|
process.stdin.on("data", (chunk) => {
|
||||||
|
try {
|
||||||
buffer = Buffer.concat([buffer, Buffer.from(chunk)]);
|
buffer = Buffer.concat([buffer, Buffer.from(chunk)]);
|
||||||
consume();
|
consume();
|
||||||
|
} catch (error) {
|
||||||
|
process.stderr.write(`[wrnexus-lsp] input processing failed: ${error instanceof Error ? error.stack ?? error.message : String(error)}
|
||||||
|
`);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
process.stdin.resume();
|
process.stdin.resume();
|
||||||
})
|
})(exports, require, module, __filename, __dirname);
|
||||||
|
|||||||
@@ -195,8 +195,12 @@
|
|||||||
"patterns": [
|
"patterns": [
|
||||||
{
|
{
|
||||||
"include": "source.ts"
|
"include": "source.ts"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"include": "#ts-braces"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"applyEndPatternLast": true
|
||||||
},
|
},
|
||||||
"props-block": {
|
"props-block": {
|
||||||
"begin": "\\b(props)\\b\\s*(\\{)",
|
"begin": "\\b(props)\\b\\s*(\\{)",
|
||||||
@@ -254,8 +258,15 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"include": "#strings"
|
"include": "#strings"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"include": "#ts-braces"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"include": "source.ts"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"applyEndPatternLast": true
|
||||||
},
|
},
|
||||||
"seo-block": {
|
"seo-block": {
|
||||||
"begin": "\\b(seo)\\b\\s*(\\{)",
|
"begin": "\\b(seo)\\b\\s*(\\{)",
|
||||||
@@ -651,7 +662,8 @@
|
|||||||
{
|
{
|
||||||
"include": "text.html.basic"
|
"include": "text.html.basic"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"applyEndPatternLast": true
|
||||||
},
|
},
|
||||||
"wire-event": {
|
"wire-event": {
|
||||||
"match": "(@)(?:(window|document)(:))?([A-Za-z][A-Za-z0-9_-]*)(\\s*=)",
|
"match": "(@)(?:(window|document)(:))?([A-Za-z][A-Za-z0-9_-]*)(\\s*=)",
|
||||||
@@ -1067,8 +1079,15 @@
|
|||||||
"name": "punctuation.definition.parameters.end.wrn"
|
"name": "punctuation.definition.parameters.end.wrn"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"include": "#ts-braces"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"include": "source.ts"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"applyEndPatternLast": true
|
||||||
},
|
},
|
||||||
"grouped-state-block": {
|
"grouped-state-block": {
|
||||||
"begin": "\\b(?:(client|server|shared)\\s+)?(state)\\b\\s*(\\{)",
|
"begin": "\\b(?:(client|server|shared)\\s+)?(state)\\b\\s*(\\{)",
|
||||||
|
|||||||
@@ -149,3 +149,63 @@ test("extracts v0.6 optional props, declared union options, and outputs", () =>
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("accepts unresolved dynamic expressions for typed component props", () => {
|
||||||
|
const featureCard = parseComponentMetadata(`component FeatureCard {
|
||||||
|
props {
|
||||||
|
external: boolean = false
|
||||||
|
disabled: boolean = false
|
||||||
|
showArrow: boolean = true
|
||||||
|
}
|
||||||
|
view { <article></article> }
|
||||||
|
}`);
|
||||||
|
const source = `component FeatureGrid {
|
||||||
|
props { items: unknown[] = [] }
|
||||||
|
view {
|
||||||
|
{#each items as item}
|
||||||
|
<FeatureCard
|
||||||
|
external='{item.external || false}'
|
||||||
|
disabled='{item.disabled || false}'
|
||||||
|
showArrow='{item.showArrow !== false}'
|
||||||
|
/>
|
||||||
|
{/each}
|
||||||
|
}
|
||||||
|
}`;
|
||||||
|
|
||||||
|
assert.equal(attributeValueType("{item.external || false}"), "unknown");
|
||||||
|
assert.equal(attributeValueType("{item.showArrow !== false}"), "boolean");
|
||||||
|
assert.deepEqual(validateComponentTags(source, new Map([["FeatureCard", featureCard]])), []);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("continues to reject invalid literal boolean props", () => {
|
||||||
|
const target = parseComponentMetadata(`component Toggle {
|
||||||
|
props { enabled: boolean = false }
|
||||||
|
view { <button></button> }
|
||||||
|
}`);
|
||||||
|
const diagnostics = validateComponentTags(
|
||||||
|
`<Toggle enabled="definitely" />`,
|
||||||
|
new Map([["Toggle", target]]),
|
||||||
|
);
|
||||||
|
assert.deepEqual(
|
||||||
|
diagnostics.map(({ code }) => code),
|
||||||
|
["wrn-component-prop-type"],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("accepts unresolved dynamic expressions for declared string options", () => {
|
||||||
|
const target = parseComponentMetadata(`component FeatureCard {
|
||||||
|
props { target: "_blank" | "_self" = "_self" }
|
||||||
|
view { <article></article> }
|
||||||
|
}`);
|
||||||
|
const dynamic = `<FeatureCard target='{item.target || ""}' />`;
|
||||||
|
assert.deepEqual(validateComponentTags(dynamic, new Map([["FeatureCard", target]])), []);
|
||||||
|
|
||||||
|
const invalidLiteral = validateComponentTags(
|
||||||
|
`<FeatureCard target="popup" />`,
|
||||||
|
new Map([["FeatureCard", target]]),
|
||||||
|
);
|
||||||
|
assert.deepEqual(
|
||||||
|
invalidLiteral.map(({ code }) => code),
|
||||||
|
["wrn-component-prop-option"],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|||||||
@@ -201,3 +201,36 @@ page Test {
|
|||||||
|
|
||||||
assert.deepEqual(validateHtmlTags(mockDocument(source), source), []);
|
assert.deepEqual(validateHtmlTags(mockDocument(source), source), []);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("does not interpret TypeScript generic types in outputs as HTML tags", () => {
|
||||||
|
const source = `component AuthForm {
|
||||||
|
outputs {
|
||||||
|
change(payload: { values?: Array<string | number | boolean | null | object>; sourceEvent?: Event })
|
||||||
|
}
|
||||||
|
view { <form><strong>Sign in</strong></form> }
|
||||||
|
}`;
|
||||||
|
|
||||||
|
assert.deepEqual(validateHtmlTags(mockDocument(source), source), []);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("allows JavaScript-looking documentation text inside pre and code", () => {
|
||||||
|
const source = `page Docs {
|
||||||
|
view {
|
||||||
|
<pre><code><span class="code-muted">// One project, one language</span>
|
||||||
|
<span class="code-key">page</span> Dashboard {
|
||||||
|
<button>Ship</button>
|
||||||
|
}</code></pre>
|
||||||
|
}
|
||||||
|
}`;
|
||||||
|
|
||||||
|
assert.deepEqual(validateHtmlTags(mockDocument(source), source), []);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("still reports genuinely mismatched view tags", () => {
|
||||||
|
const source = `page Broken {
|
||||||
|
view { <main><strong>Broken</main> }
|
||||||
|
}`;
|
||||||
|
const diagnostics = validateHtmlTags(mockDocument(source), source);
|
||||||
|
assert.equal(diagnostics.length, 1);
|
||||||
|
assert.equal(diagnostics[0].code, "wrn-mismatched-html-tag");
|
||||||
|
});
|
||||||
|
|||||||
@@ -255,3 +255,48 @@ test("formats v0.6 grouped state and outputs blocks", () => {
|
|||||||
/\n {2}outputs \{\n {4}confirm\(payload: ConfirmPayload\)\n {4}cancel\(\)\n {2}\}/,
|
/\n {2}outputs \{\n {4}confirm\(payload: ConfirmPayload\)\n {4}cancel\(\)\n {2}\}/,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("preserves preformatted WRN code examples", () => {
|
||||||
|
const source = `page Docs {
|
||||||
|
view {
|
||||||
|
<pre><code><span class="code-muted">// One project, one language</span>
|
||||||
|
<span class="code-key">page</span> ProductDashboard {
|
||||||
|
<span class="code-key">state</span> count: number = 0
|
||||||
|
<button @click='count++'>Ship {count}</button>
|
||||||
|
}</code></pre>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
const options = { insertSpaces: true, tabSize: 2, printWidth: 100 };
|
||||||
|
const formatted = formatWrn(source, options);
|
||||||
|
assert.match(
|
||||||
|
formatted,
|
||||||
|
/<pre><code><span class="code-muted">\/\/ One project, one language<\/span>\n<span class="code-key">page<\/span>/,
|
||||||
|
);
|
||||||
|
assert.equal(formatWrn(formatted, options), formatted);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("keeps attribute-free opening tags intact when content is long", () => {
|
||||||
|
const source = `page Docs {
|
||||||
|
view {
|
||||||
|
<p>This documentation sentence is deliberately long enough to exceed the formatter print width without splitting the p opening delimiter.</p>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
const options = { insertSpaces: true, tabSize: 2, printWidth: 70 };
|
||||||
|
const formatted = formatWrn(source, options);
|
||||||
|
assert.match(formatted, /^ {4}<p>$/m);
|
||||||
|
assert.doesNotMatch(formatted, /^ {4}<p\n {4}>$/m);
|
||||||
|
assert.equal(formatWrn(formatted, options), formatted);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("preserves balanced compact sibling markup without indentation drift", () => {
|
||||||
|
const source = `page Compact {
|
||||||
|
view {
|
||||||
|
<div class="strip"><span>Page</span><b>→</b><span>API</span></div>
|
||||||
|
}
|
||||||
|
}\n`;
|
||||||
|
const formatted = formatWrn(source, { tabSize: 2, printWidth: 80 });
|
||||||
|
assert.match(formatted, /<div class="strip"><span>Page<\/span><b>→<\/b><span>API<\/span><\/div>/);
|
||||||
|
assert.equal(formatWrn(formatted, { tabSize: 2, printWidth: 80 }), formatted);
|
||||||
|
});
|
||||||
|
|||||||
@@ -0,0 +1,141 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
const assert = require("node:assert");
|
||||||
|
const { spawnSync } = require("node:child_process");
|
||||||
|
const fs = require("node:fs");
|
||||||
|
const path = require("node:path");
|
||||||
|
const { test } = require("node:test");
|
||||||
|
|
||||||
|
const root = path.resolve(__dirname, "../../..");
|
||||||
|
const server = path.join(root, "editors", "vscode", "src", "language-server.cjs");
|
||||||
|
|
||||||
|
function rpc(value) {
|
||||||
|
const body = Buffer.from(JSON.stringify(value));
|
||||||
|
return Buffer.concat([Buffer.from(`Content-Length: ${body.length}\r\n\r\n`), body]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseResponses(output) {
|
||||||
|
const responses = [];
|
||||||
|
let position = 0;
|
||||||
|
while (position < output.length) {
|
||||||
|
const headerEnd = output.indexOf(Buffer.from("\r\n\r\n"), position);
|
||||||
|
if (headerEnd < 0) break;
|
||||||
|
const header = output.subarray(position, headerEnd).toString();
|
||||||
|
const length = Number(/Content-Length:\s*(\d+)/i.exec(header)?.[1] || 0);
|
||||||
|
const start = headerEnd + 4;
|
||||||
|
const end = start + length;
|
||||||
|
if (!length || end > output.length) break;
|
||||||
|
responses.push(JSON.parse(output.subarray(start, end).toString()));
|
||||||
|
position = end;
|
||||||
|
}
|
||||||
|
return responses;
|
||||||
|
}
|
||||||
|
|
||||||
|
function run(messages) {
|
||||||
|
const result = spawnSync(process.execPath, [server], {
|
||||||
|
cwd: root,
|
||||||
|
input: Buffer.concat(messages.map(rpc)),
|
||||||
|
timeout: 10_000,
|
||||||
|
});
|
||||||
|
assert.equal(result.status, 0, (result.stderr || Buffer.alloc(0)).toString());
|
||||||
|
return parseResponses(result.stdout || Buffer.alloc(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
test("language server starts under Node and keeps official typed components clean", () => {
|
||||||
|
const files = [
|
||||||
|
path.join(root, "packages", "ui", "components", "AuthForm.wrn"),
|
||||||
|
path.join(root, "packages", "ui", "components", "FeatureGrid.wrn"),
|
||||||
|
];
|
||||||
|
const messages = [
|
||||||
|
{ jsonrpc: "2.0", id: 1, method: "initialize", params: { rootPath: root } },
|
||||||
|
{ jsonrpc: "2.0", method: "initialized", params: {} },
|
||||||
|
];
|
||||||
|
files.forEach((file, index) => {
|
||||||
|
messages.push({
|
||||||
|
jsonrpc: "2.0",
|
||||||
|
method: "textDocument/didOpen",
|
||||||
|
params: {
|
||||||
|
textDocument: {
|
||||||
|
uri: `file://${file}`,
|
||||||
|
languageId: "wrn",
|
||||||
|
version: index + 1,
|
||||||
|
text: fs.readFileSync(file, "utf8"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
messages.push({ jsonrpc: "2.0", id: 2, method: "shutdown", params: {} });
|
||||||
|
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 diagnostics = responses.filter((item) => item.method === "textDocument/publishDiagnostics");
|
||||||
|
assert.equal(diagnostics.length, 2);
|
||||||
|
assert.deepEqual(
|
||||||
|
diagnostics.map((item) => item.params.diagnostics),
|
||||||
|
[[], []],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("language server formatter preserves code samples and compact sibling markup", () => {
|
||||||
|
const source = `page Docs {
|
||||||
|
view {
|
||||||
|
<pre><code><span class="muted">// One project, one language</span></code></pre>
|
||||||
|
<div class="strip"><span>Page</span><b>→</b><span>API</span></div>
|
||||||
|
}
|
||||||
|
}\n`;
|
||||||
|
const uri = "file:///tmp/wrnexus-docs.wrn";
|
||||||
|
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: source } },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
jsonrpc: "2.0",
|
||||||
|
id: 2,
|
||||||
|
method: "textDocument/formatting",
|
||||||
|
params: { textDocument: { uri }, options: { tabSize: 2, insertSpaces: true } },
|
||||||
|
},
|
||||||
|
{ jsonrpc: "2.0", id: 3, method: "shutdown", params: {} },
|
||||||
|
{ jsonrpc: "2.0", method: "exit", params: {} },
|
||||||
|
]);
|
||||||
|
const formatted = responses.find((item) => item.id === 2)?.result?.[0]?.newText;
|
||||||
|
assert.ok(formatted);
|
||||||
|
assert.match(formatted, /\/\/ One project, one language/);
|
||||||
|
assert.match(formatted, /<span>Page<\/span><b>→<\/b><span>API<\/span>/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("language server runs TypeScript diagnostics with workspace standard libraries", () => {
|
||||||
|
const uri = "file:///tmp/wrnexus-invalid-state.wrn";
|
||||||
|
const source = `component InvalidState {
|
||||||
|
outputs { save(payload: { id: string }) }
|
||||||
|
functions {
|
||||||
|
client function submit(value: string): void {
|
||||||
|
output.save({ id: 123 })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
view { <button @click="submit('demo')"></button> }
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
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: source } },
|
||||||
|
},
|
||||||
|
{ jsonrpc: "2.0", id: 2, method: "shutdown", params: {} },
|
||||||
|
{ jsonrpc: "2.0", method: "exit", params: {} },
|
||||||
|
]);
|
||||||
|
const diagnostics = responses.find((item) => item.method === "textDocument/publishDiagnostics")
|
||||||
|
?.params?.diagnostics;
|
||||||
|
assert.ok(Array.isArray(diagnostics));
|
||||||
|
assert.ok(
|
||||||
|
diagnostics.some((diagnostic) => diagnostic.code === "WRN-TYPE-2322"),
|
||||||
|
JSON.stringify(diagnostics, null, 2),
|
||||||
|
);
|
||||||
|
});
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "wrnexus",
|
"name": "wrnexus",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "An SSR-first full-stack web framework with server-rendered reactive components. Bun-first, Node-friendly.",
|
"description": "An SSR-first full-stack web framework with server-rendered reactive components. Bun-first, Node-friendly.",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/ai",
|
"name": "@wrnexus/ai",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Zero-dependency Claude (Anthropic) client for WrNexus apps.",
|
"description": "Zero-dependency Claude (Anthropic) client for WrNexus apps.",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/auth",
|
"name": "@wrnexus/auth",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"description": "Complete authentication, account security, MFA, passkeys, recovery, devices, risk, and audit system for WRNexusJS.",
|
"description": "Complete authentication, account security, MFA, passkeys, recovery, devices, risk, and audit system for WRNexusJS.",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/authz",
|
"name": "@wrnexus/authz",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/benchmark",
|
"name": "@wrnexus/benchmark",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Deterministic benchmark runner and performance regression budgets for WRNexusJS.",
|
"description": "Deterministic benchmark runner and performance regression budgets for WRNexusJS.",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
|
|||||||
Vendored
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/cache",
|
"name": "@wrnexus/cache",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Tag-aware memory and response caching with stale-while-revalidate for WRNexusJS.",
|
"description": "Tag-aware memory and response caching with stale-while-revalidate for WRNexusJS.",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/captcha",
|
"name": "@wrnexus/captcha",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"description": "First-class CAPTCHA challenges, providers, verification guards, page gates, and WRNexusJS UI.",
|
"description": "First-class CAPTCHA challenges, providers, verification guards, page gates, and WRNexusJS UI.",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/cli",
|
"name": "@wrnexus/cli",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
@@ -1999,6 +1999,20 @@ const MIGRATIONS: Migration[] = [
|
|||||||
// The compile-cache generation changes automatically invalidate old artifacts.
|
// The compile-cache generation changes automatically invalidate old artifacts.
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
version: "0.8.4",
|
||||||
|
id: "0.8.4-editor-language-support-stability",
|
||||||
|
description:
|
||||||
|
"Fixes WRNexus Language Server crashes, WRN formatting stability, false closing-tag diagnostics, typed event inference, dynamic component prop validation, and VS Code extension reliability.",
|
||||||
|
apply() {
|
||||||
|
// Compiler, diagnostics, formatter, language-server, and VS Code
|
||||||
|
// extension fixes require no application source migration.
|
||||||
|
//
|
||||||
|
// Existing projects receive the framework package changes through the
|
||||||
|
// dependency update. Developers must separately update/reinstall the
|
||||||
|
// WRNexus Language Support VS Code extension.
|
||||||
|
},
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
/** Release tooling uses this to require an explicit migration entry per version. */
|
/** Release tooling uses this to require an explicit migration entry per version. */
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/compiler",
|
"name": "@wrnexus/compiler",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/content",
|
"name": "@wrnexus/content",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Typed Markdown content collections, loaders, indexes, feeds, and preview workflows for WRNexusJS.",
|
"description": "Typed Markdown content collections, loaders, indexes, feeds, and preview workflows for WRNexusJS.",
|
||||||
"main": "./src/index.ts",
|
"main": "./src/index.ts",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/core",
|
"name": "@wrnexus/core",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/csr",
|
"name": "@wrnexus/csr",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/db",
|
"name": "@wrnexus/db",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./src/index.ts",
|
"main": "./src/index.ts",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/dev-server",
|
"name": "@wrnexus/dev-server",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/dev-toolbar",
|
"name": "@wrnexus/dev-toolbar",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/encryption",
|
"name": "@wrnexus/encryption",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./src/index.ts",
|
"main": "./src/index.ts",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/graphql",
|
"name": "@wrnexus/graphql",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./src/index.ts",
|
"main": "./src/index.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/helpers",
|
"name": "@wrnexus/helpers",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Safe convenience helpers for WrNexus request contexts and common application flows.",
|
"description": "Safe convenience helpers for WrNexus request contexts and common application flows.",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/i18n",
|
"name": "@wrnexus/i18n",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./src/index.ts",
|
"main": "./src/index.ts",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/identity",
|
"name": "@wrnexus/identity",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Enterprise federation, provisioning, machine identity, and privacy governance for WRNexusJS.",
|
"description": "Enterprise federation, provisioning, machine identity, and privacy governance for WRNexusJS.",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/image",
|
"name": "@wrnexus/image",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Responsive image planning, secure remote image policies, and performance auditing for WRNexusJS.",
|
"description": "Responsive image planning, secure remote image policies, and performance auditing for WRNexusJS.",
|
||||||
"main": "./src/index.ts",
|
"main": "./src/index.ts",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/jwt",
|
"name": "@wrnexus/jwt",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./src/index.ts",
|
"main": "./src/index.ts",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/language-server",
|
"name": "@wrnexus/language-server",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Editor-neutral Language Server Protocol implementation for WRNexus .wrn files.",
|
"description": "Editor-neutral Language Server Protocol implementation for WRNexus .wrn files.",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
|
|||||||
@@ -36,11 +36,33 @@ function send(value: unknown): void {
|
|||||||
function result(id: JsonRpc["id"], value: unknown): void {
|
function result(id: JsonRpc["id"], value: unknown): void {
|
||||||
send({ jsonrpc: "2.0", id, result: value });
|
send({ jsonrpc: "2.0", id, result: value });
|
||||||
}
|
}
|
||||||
|
function internalDiagnostic(document: TextDocument, error: unknown) {
|
||||||
|
const message = error instanceof Error ? error.message : String(error);
|
||||||
|
return {
|
||||||
|
range: { start: { line: 0, character: 0 }, end: { line: 0, character: 1 } },
|
||||||
|
severity: 1,
|
||||||
|
code: "WRN-LSP-INTERNAL",
|
||||||
|
source: "wrnexus",
|
||||||
|
message: `WRNexus language analysis failed safely: ${message}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function safeDocumentDiagnostics(document: TextDocument) {
|
||||||
|
try {
|
||||||
|
return documentDiagnostics(document);
|
||||||
|
} catch (error) {
|
||||||
|
process.stderr.write(
|
||||||
|
`[wrnexus-lsp] diagnostics failed for ${document.uri}: ${error instanceof Error ? (error.stack ?? error.message) : String(error)}\n`,
|
||||||
|
);
|
||||||
|
return [internalDiagnostic(document, error)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function publish(document: TextDocument): void {
|
function publish(document: TextDocument): void {
|
||||||
send({
|
send({
|
||||||
jsonrpc: "2.0",
|
jsonrpc: "2.0",
|
||||||
method: "textDocument/publishDiagnostics",
|
method: "textDocument/publishDiagnostics",
|
||||||
params: { uri: document.uri, diagnostics: documentDiagnostics(document) },
|
params: { uri: document.uri, diagnostics: safeDocumentDiagnostics(document) },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,7 +102,7 @@ async function handle(message: JsonRpc): Promise<void> {
|
|||||||
case "initialize":
|
case "initialize":
|
||||||
workspaceRoot = params.rootPath ?? rootFromUri(params.rootUri);
|
workspaceRoot = params.rootPath ?? rootFromUri(params.rootUri);
|
||||||
result(message.id, {
|
result(message.id, {
|
||||||
serverInfo: { name: "WRNexus Language Server", version: "0.8.0" },
|
serverInfo: { name: "WRNexus Language Server", version: "0.8.3" },
|
||||||
capabilities: {
|
capabilities: {
|
||||||
textDocumentSync: 1,
|
textDocumentSync: 1,
|
||||||
documentFormattingProvider: true,
|
documentFormattingProvider: true,
|
||||||
@@ -195,7 +217,7 @@ async function handle(message: JsonRpc): Promise<void> {
|
|||||||
result(message.id, []);
|
result(message.id, []);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
const actions: any[] = documentDiagnostics(document)
|
const actions: any[] = safeDocumentDiagnostics(document)
|
||||||
.filter((item) => item.code === "WRNA11Y001")
|
.filter((item) => item.code === "WRNA11Y001")
|
||||||
.map((item) => ({
|
.map((item) => ({
|
||||||
title: "Add empty alt attribute",
|
title: "Add empty alt attribute",
|
||||||
@@ -268,11 +290,36 @@ function consume(): void {
|
|||||||
if (buffer.length < bodyStart + length) return;
|
if (buffer.length < bodyStart + length) return;
|
||||||
const body = buffer.subarray(bodyStart, bodyStart + length).toString();
|
const body = buffer.subarray(bodyStart, bodyStart + length).toString();
|
||||||
buffer = buffer.subarray(bodyStart + length);
|
buffer = buffer.subarray(bodyStart + length);
|
||||||
void handle(JSON.parse(body));
|
let message: JsonRpc;
|
||||||
|
try {
|
||||||
|
message = JSON.parse(body) as JsonRpc;
|
||||||
|
} catch (error) {
|
||||||
|
process.stderr.write(
|
||||||
|
`[wrnexus-lsp] invalid JSON-RPC payload: ${error instanceof Error ? error.message : String(error)}\n`,
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
void handle(message).catch((error) => {
|
||||||
|
const detail = error instanceof Error ? (error.stack ?? error.message) : String(error);
|
||||||
|
process.stderr.write(`[wrnexus-lsp] request failed: ${detail}\n`);
|
||||||
|
if (message.id !== undefined) {
|
||||||
|
send({
|
||||||
|
jsonrpc: "2.0",
|
||||||
|
id: message.id,
|
||||||
|
error: { code: -32603, message: "WRNexus language server request failed", data: detail },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
process.stdin.on("data", (chunk) => {
|
process.stdin.on("data", (chunk) => {
|
||||||
|
try {
|
||||||
buffer = Buffer.concat([buffer, Buffer.from(chunk)]);
|
buffer = Buffer.concat([buffer, Buffer.from(chunk)]);
|
||||||
consume();
|
consume();
|
||||||
|
} catch (error) {
|
||||||
|
process.stderr.write(
|
||||||
|
`[wrnexus-lsp] input processing failed: ${error instanceof Error ? (error.stack ?? error.message) : String(error)}\n`,
|
||||||
|
);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
process.stdin.resume();
|
process.stdin.resume();
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/mcp",
|
"name": "@wrnexus/mcp",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Model Context Protocol server exposing WRNexus application and framework context.",
|
"description": "Model Context Protocol server exposing WRNexus application and framework context.",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/mobile",
|
"name": "@wrnexus/mobile",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/native",
|
"name": "@wrnexus/native",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/oauth",
|
"name": "@wrnexus/oauth",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/observability",
|
"name": "@wrnexus/observability",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Metrics, Web Vitals collection, request instrumentation, and exporter adapters for WRNexusJS.",
|
"description": "Metrics, Web Vitals collection, request instrumentation, and exporter adapters for WRNexusJS.",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/playground",
|
"name": "@wrnexus/playground",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Secure, shareable WRNexus compiler and UI playground.",
|
"description": "Secure, shareable WRNexus compiler and UI playground.",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/plugin",
|
"name": "@wrnexus/plugin",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/pubsub",
|
"name": "@wrnexus/pubsub",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/pwa",
|
"name": "@wrnexus/pwa",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Service workers, manifests, offline queues, background sync, push, and conflict resolution for WRNexusJS.",
|
"description": "Service workers, manifests, offline queues, background sync, push, and conflict resolution for WRNexusJS.",
|
||||||
"main": "./src/index.ts",
|
"main": "./src/index.ts",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/queue",
|
"name": "@wrnexus/queue",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/reactive",
|
"name": "@wrnexus/reactive",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/realtime",
|
"name": "@wrnexus/realtime",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"description": "Typed realtime rooms, message helpers, presence utilities, package UI blocks, and WRNexusJS integration.",
|
"description": "Typed realtime rooms, message helpers, presence utilities, package UI blocks, and WRNexusJS integration.",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/router",
|
"name": "@wrnexus/router",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/security",
|
"name": "@wrnexus/security",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Security policies, safe serialization, SSRF protection, request limits, and secure cookie helpers for WRNexusJS.",
|
"description": "Security policies, safe serialization, SSRF protection, request limits, and secure cookie helpers for WRNexusJS.",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/ssr",
|
"name": "@wrnexus/ssr",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/store",
|
"name": "@wrnexus/store",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/styles",
|
"name": "@wrnexus/styles",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/syntax",
|
"name": "@wrnexus/syntax",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
@@ -416,6 +416,17 @@ function formatOpeningTag(value, unit, depth, printWidth = 100, multilineAttribu
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (parsed.attributes.length === 0 && !parsed.selfClosing) {
|
||||||
|
const lines = [`${baseIndent}<${parsed.tagName}>`];
|
||||||
|
if (parsed.trailingClosing) {
|
||||||
|
if (parsed.inlineContent) lines.push(`${childIndent}${parsed.inlineContent}`);
|
||||||
|
lines.push(`${baseIndent}</${parsed.tagName}>`);
|
||||||
|
} else if (parsed.remainder) {
|
||||||
|
lines.push(`${childIndent}${parsed.remainder}`);
|
||||||
|
}
|
||||||
|
return { lines, opensElement };
|
||||||
|
}
|
||||||
|
|
||||||
const lines = [
|
const lines = [
|
||||||
`${baseIndent}<${parsed.tagName}`,
|
`${baseIndent}<${parsed.tagName}`,
|
||||||
...parsed.attributes.flatMap((attribute) => formatAttribute(attribute, childIndent, unit)),
|
...parsed.attributes.flatMap((attribute) => formatAttribute(attribute, childIndent, unit)),
|
||||||
@@ -468,6 +479,75 @@ function isClosingTag(value) {
|
|||||||
return /^<\/[A-Za-z][\w$:.-]*\s*>/.test(value);
|
return /^<\/[A-Za-z][\w$:.-]*\s*>/.test(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Preserve compact, already-balanced HTML fragments as one line.
|
||||||
|
*
|
||||||
|
* A line such as `<span>Page</span><b>→</b><span>API</span>` is valid and
|
||||||
|
* intentionally compact. Expanding only its first opening tag makes later
|
||||||
|
* formatter passes treat the remaining siblings as children, causing runaway
|
||||||
|
* indentation and false closing-tag diagnostics. Balanced fragments are kept
|
||||||
|
* intact while normal multiline opening tags continue through the formatter.
|
||||||
|
*/
|
||||||
|
function isBalancedInlineHtmlFragment(value) {
|
||||||
|
if (!value.startsWith("<") || value.startsWith("</") || value.startsWith("<!--")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const stack = [];
|
||||||
|
let tagCount = 0;
|
||||||
|
let rootCount = 0;
|
||||||
|
let hasNestedElement = false;
|
||||||
|
let hasOutsideText = false;
|
||||||
|
let index = 0;
|
||||||
|
|
||||||
|
while (index < value.length) {
|
||||||
|
const tagStart = value.indexOf("<", index);
|
||||||
|
if (tagStart === -1) {
|
||||||
|
if (stack.length === 0 && value.slice(index).trim()) hasOutsideText = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (stack.length === 0 && value.slice(index, tagStart).trim()) hasOutsideText = true;
|
||||||
|
|
||||||
|
if (value.startsWith("<!--", tagStart)) {
|
||||||
|
const commentEnd = value.indexOf("-->", 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("</");
|
||||||
|
const selfClosing = /\/\s*>$/.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) {
|
function isControlBlockOpen(value) {
|
||||||
return /^\{#(?:if|each)\b[\s\S]*\}$/.test(value);
|
return /^\{#(?:if|each)\b[\s\S]*\}$/.test(value);
|
||||||
}
|
}
|
||||||
@@ -597,6 +677,50 @@ function collectOpeningTag(inputLines, startIndex) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isPreservedRawBlockStart(value) {
|
||||||
|
return (
|
||||||
|
/<pre(?:\s|>)/i.test(value) &&
|
||||||
|
!/<\/pre\s*>/i.test(value.slice(0, value.search(/<pre(?:\s|>)/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 };
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Put WRN template control markers on their own lines before indentation.
|
* Put WRN template control markers on their own lines before indentation.
|
||||||
*
|
*
|
||||||
@@ -608,7 +732,7 @@ function expandInlineControlBlocks(lines) {
|
|||||||
const marker =
|
const marker =
|
||||||
/(\{#(?:if|each)\b[^}]*\}|\{:(?:else(?:\s+if\b[^}]*)?|empty)\}|\{\/(?:if|each)\})/g;
|
/(\{#(?:if|each)\b[^}]*\}|\{:(?:else(?:\s+if\b[^}]*)?|empty)\}|\{\/(?:if|each)\})/g;
|
||||||
|
|
||||||
return lines.flatMap((line) => {
|
return transformOutsidePreservedRawBlocks(lines, (line) => {
|
||||||
if (!marker.test(line)) return [line];
|
if (!marker.test(line)) return [line];
|
||||||
marker.lastIndex = 0;
|
marker.lastIndex = 0;
|
||||||
|
|
||||||
@@ -623,7 +747,7 @@ function expandInlineControlBlocks(lines) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function expandStructuredStateDeclarations(lines, unit) {
|
function expandStructuredStateDeclarations(lines, unit) {
|
||||||
return lines.flatMap((line) => {
|
return transformOutsidePreservedRawBlocks(lines, (line) => {
|
||||||
const match = /^(\s*state\s+[A-Za-z_$][\w$]*\s*=\s*)([\\[{][\s\S]*)$/.exec(line);
|
const match = /^(\s*state\s+[A-Za-z_$][\w$]*\s*=\s*)([\\[{][\s\S]*)$/.exec(line);
|
||||||
if (!match) return [line];
|
if (!match) return [line];
|
||||||
|
|
||||||
@@ -695,6 +819,15 @@ function formatWrnPass(source: string, options: FormatWrnOptions = {}): string {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isPreservedRawBlockStart(value)) {
|
||||||
|
const collected = collectPreservedRawBlock(inputLines, index);
|
||||||
|
const depth = codeDepth + htmlDepth + controlDepth;
|
||||||
|
output.push(`${unit.repeat(depth)}${collected.lines[0].trimStart()}`);
|
||||||
|
output.push(...collected.lines.slice(1));
|
||||||
|
index = collected.endIndex + 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (isMultilineOpeningTagStart(value)) {
|
if (isMultilineOpeningTagStart(value)) {
|
||||||
const collected = collectOpeningTag(inputLines, index);
|
const collected = collectOpeningTag(inputLines, index);
|
||||||
|
|
||||||
@@ -730,7 +863,11 @@ function formatWrnPass(source: string, options: FormatWrnOptions = {}): string {
|
|||||||
|
|
||||||
const depth = lineCodeDepth + lineHtmlDepth + lineControlDepth;
|
const depth = lineCodeDepth + lineHtmlDepth + lineControlDepth;
|
||||||
|
|
||||||
if (
|
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("<") &&
|
||||||
!value.startsWith("</") &&
|
!value.startsWith("</") &&
|
||||||
!value.startsWith("<!--") &&
|
!value.startsWith("<!--") &&
|
||||||
|
|||||||
@@ -6,9 +6,24 @@ export type RuntimeType =
|
|||||||
export function runtimeTypeOf(annotation: string | undefined): RuntimeType {
|
export function runtimeTypeOf(annotation: string | undefined): RuntimeType {
|
||||||
if (!annotation) return "unknown";
|
if (!annotation) return "unknown";
|
||||||
const type = annotation.trim().replace(/^readonly\s+/, "");
|
const type = annotation.trim().replace(/^readonly\s+/, "");
|
||||||
if (/^(?:string|String)(?:\s*\|\s*(?:null|undefined))*$/.test(type)) return "string";
|
const unionParts = type.split("|").map((part) => part.trim());
|
||||||
if (/^(?:number|Number)(?:\s*\|\s*(?:null|undefined))*$/.test(type)) return "number";
|
const concreteParts = unionParts.filter((part) => !/^(?:null|undefined)$/.test(part));
|
||||||
if (/^(?:boolean|Boolean)(?:\s*\|\s*(?:null|undefined))*$/.test(type)) return "boolean";
|
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 (/^bigint(?:\s*\|\s*(?:null|undefined))*$/.test(type)) return "bigint";
|
||||||
if (/^(?:Array\s*<|ReadonlyArray\s*<|.+\[\])/.test(type) || /^\[/.test(type)) return "array";
|
if (/^(?:Array\s*<|ReadonlyArray\s*<|.+\[\])/.test(type) || /^\[/.test(type)) return "array";
|
||||||
if (/^(?:Record\s*<|object\b|\{)/.test(type)) return "object";
|
if (/^(?:Record\s*<|object\b|\{)/.test(type)) return "object";
|
||||||
|
|||||||
@@ -37,3 +37,49 @@ view {
|
|||||||
expect(formatted).not.toContain("\r");
|
expect(formatted).not.toContain("\r");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("preserves preformatted code examples while formatting surrounding markup", () => {
|
||||||
|
const source = `page Docs {
|
||||||
|
view {
|
||||||
|
<section class="docs">
|
||||||
|
<pre><code><span class="code-muted">// One project, one language</span>
|
||||||
|
<span class="code-key">page</span> ProductDashboard {
|
||||||
|
<span class="code-key">state</span> count: number = 0
|
||||||
|
<button @click='count++'>Ship {count}</button>
|
||||||
|
}</code></pre>
|
||||||
|
</section>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
const formatted = formatWrn(source, options);
|
||||||
|
expect(formatted)
|
||||||
|
.toContain(`<pre><code><span class="code-muted">// One project, one language</span>
|
||||||
|
<span class="code-key">page</span> ProductDashboard {
|
||||||
|
<span class="code-key">state</span> count: number = 0`);
|
||||||
|
expect(formatWrn(formatted, options)).toBe(formatted);
|
||||||
|
expect(() => parse(formatted)).not.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("does not split an attribute-free opening tag delimiter", () => {
|
||||||
|
const source = `page Docs {
|
||||||
|
view {
|
||||||
|
<p>This is intentionally long documentation copy that should wrap as content without formatting the opening p tag as separate less-than and greater-than lines.</p>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
const formatted = formatWrn(source, options);
|
||||||
|
expect(formatted).toContain(" <p>\n");
|
||||||
|
expect(formatted).not.toContain(" <p\n >");
|
||||||
|
expect(formatWrn(formatted, options)).toBe(formatted);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("preserves balanced compact sibling markup without indentation drift", () => {
|
||||||
|
const source = `page Compact {
|
||||||
|
view {
|
||||||
|
<div class="strip"><span>Page</span><b>→</b><span>API</span></div>
|
||||||
|
}
|
||||||
|
}\n`;
|
||||||
|
const formatted = formatWrn(source, { tabSize: 2, printWidth: 80 });
|
||||||
|
expect(formatted).toContain('<div class="strip"><span>Page</span><b>→</b><span>API</span></div>');
|
||||||
|
expect(formatWrn(formatted, { tabSize: 2, printWidth: 80 })).toBe(formatted);
|
||||||
|
});
|
||||||
|
|||||||
@@ -200,3 +200,10 @@ test("parses edge and worker execution targets", () => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("maps literal unions to their runtime primitive types", async () => {
|
||||||
|
const { runtimeTypeOf } = await import("../src/types.ts");
|
||||||
|
expect(runtimeTypeOf('"_blank" | "_self"')).toBe("string");
|
||||||
|
expect(runtimeTypeOf("1 | 2 | 3")).toBe("number");
|
||||||
|
expect(runtimeTypeOf("true | false")).toBe("boolean");
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/test",
|
"name": "@wrnexus/test",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/tracking",
|
"name": "@wrnexus/tracking",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/typecheck",
|
"name": "@wrnexus/typecheck",
|
||||||
"version": "0.8.3",
|
"version": "0.8.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
+112
-12
@@ -139,9 +139,13 @@ function functionDeclaration(fn: RuntimeFunctionDecl): string {
|
|||||||
const params = fn.parameters
|
const params = fn.parameters
|
||||||
.map(
|
.map(
|
||||||
(param) =>
|
(param) =>
|
||||||
`${param.name}${param.optional ? "?" : ""}${param.valueType ? `: ${param.valueType}` : param.name === "event" ? ": CustomEvent<any> & { target: HTMLElement }" : ": unknown"}${param.default ? ` = ${param.default}` : ""}`,
|
`${param.name}${param.optional ? "?" : ""}${param.valueType ? `: ${param.valueType}` : param.name === "event" ? ": CustomEvent<any> & { target: HTMLElement }" : ": any"}${param.default ? ` = ${param.default}` : ""}`,
|
||||||
)
|
)
|
||||||
.join(", ");
|
.join(", ");
|
||||||
|
// An omitted WRN parameter type follows JavaScript semantics. Emitting it as
|
||||||
|
// explicit `any` prevents the virtual TypeScript document from inventing
|
||||||
|
// false errors for valid dynamic handlers such as output[type](payload).
|
||||||
|
// Developers can opt into strict checking by declaring the parameter type.
|
||||||
return `export ${fn.async ? "async " : ""}function ${fn.name}(${params})${fn.returnType ? `: ${fn.returnType}` : ""} {${fn.body}}`;
|
return `export ${fn.async ? "async " : ""}function ${fn.name}(${params})${fn.returnType ? `: ${fn.returnType}` : ""} {${fn.body}}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,6 +164,53 @@ function retainedImports(ast: PageAst): string {
|
|||||||
.join("\n");
|
.join("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const RESERVED_BINDING_NAMES = new Set([
|
||||||
|
"await",
|
||||||
|
"break",
|
||||||
|
"case",
|
||||||
|
"catch",
|
||||||
|
"class",
|
||||||
|
"const",
|
||||||
|
"continue",
|
||||||
|
"debugger",
|
||||||
|
"default",
|
||||||
|
"delete",
|
||||||
|
"do",
|
||||||
|
"else",
|
||||||
|
"enum",
|
||||||
|
"export",
|
||||||
|
"extends",
|
||||||
|
"false",
|
||||||
|
"finally",
|
||||||
|
"for",
|
||||||
|
"function",
|
||||||
|
"if",
|
||||||
|
"import",
|
||||||
|
"in",
|
||||||
|
"instanceof",
|
||||||
|
"let",
|
||||||
|
"new",
|
||||||
|
"null",
|
||||||
|
"return",
|
||||||
|
"static",
|
||||||
|
"super",
|
||||||
|
"switch",
|
||||||
|
"this",
|
||||||
|
"throw",
|
||||||
|
"true",
|
||||||
|
"try",
|
||||||
|
"typeof",
|
||||||
|
"var",
|
||||||
|
"void",
|
||||||
|
"while",
|
||||||
|
"with",
|
||||||
|
"yield",
|
||||||
|
]);
|
||||||
|
|
||||||
|
function safeBindingName(name: string): boolean {
|
||||||
|
return /^[A-Za-z_$][\w$]*$/.test(name) && !RESERVED_BINDING_NAMES.has(name);
|
||||||
|
}
|
||||||
|
|
||||||
export function virtualTypeScriptModule(
|
export function virtualTypeScriptModule(
|
||||||
source: string,
|
source: string,
|
||||||
filePath = "component.wrn",
|
filePath = "component.wrn",
|
||||||
@@ -190,6 +241,10 @@ export function virtualTypeScriptModule(
|
|||||||
append(ast.types.join("\n\n"), ast.types[0]?.trim());
|
append(ast.types.join("\n\n"), ast.types[0]?.trim());
|
||||||
|
|
||||||
for (const prop of ast.props) {
|
for (const prop of ast.props) {
|
||||||
|
// JavaScript keywords such as the conventional `class` component prop are
|
||||||
|
// valid WRN prop names but cannot be emitted as standalone TS bindings.
|
||||||
|
// They remain available through the typed `props` object.
|
||||||
|
if (!safeBindingName(prop.name)) continue;
|
||||||
append(`declare const ${prop.name}: Readonly<${prop.valueType ?? "unknown"}>;`, prop.name);
|
append(`declare const ${prop.name}: Readonly<${prop.valueType ?? "unknown"}>;`, prop.name);
|
||||||
}
|
}
|
||||||
for (const state of ast.states) {
|
for (const state of ast.states) {
|
||||||
@@ -218,7 +273,7 @@ export function virtualTypeScriptModule(
|
|||||||
`${fn.name}: (${fn.parameters.map((param) => `${param.name}${param.optional ? "?" : ""}: ${param.valueType ?? "unknown"}`).join(", ")}) => ${fn.returnType ?? (fn.async ? "Promise<unknown>" : "unknown")}`,
|
`${fn.name}: (${fn.parameters.map((param) => `${param.name}${param.optional ? "?" : ""}: ${param.valueType ?? "unknown"}`).join(", ")}) => ${fn.returnType ?? (fn.async ? "Promise<unknown>" : "unknown")}`,
|
||||||
)
|
)
|
||||||
.join("; ");
|
.join("; ");
|
||||||
append(`declare const output: { ${outputType} };`);
|
append(`declare const output: { [name: string]: (...args: any[]) => void; ${outputType} };`);
|
||||||
for (const output of ast.outputs)
|
for (const output of ast.outputs)
|
||||||
append(
|
append(
|
||||||
`declare const ${output.name}: (${output.payload ? `${output.payload.name}${output.payload.optional ? "?" : ""}: ${output.payload.valueType}` : ""}) => void;`,
|
`declare const ${output.name}: (${output.payload ? `${output.payload.name}${output.payload.optional ? "?" : ""}: ${output.payload.valueType}` : ""}) => void;`,
|
||||||
@@ -439,7 +494,15 @@ function componentUsageDiagnostics(
|
|||||||
if (attr.value.startsWith("{") && attr.value.endsWith("}")) continue;
|
if (attr.value.startsWith("{") && attr.value.endsWith("}")) continue;
|
||||||
const received = attr.boolean ? "boolean" : inferredRuntimeType(JSON.stringify(attr.value));
|
const received = attr.boolean ? "boolean" : inferredRuntimeType(JSON.stringify(attr.value));
|
||||||
const expected = runtimeTypeOf(prop.type);
|
const expected = runtimeTypeOf(prop.type);
|
||||||
if (expected !== "unknown" && received !== "unknown" && expected !== received) {
|
const compatibleLiteral =
|
||||||
|
(expected === "boolean" && /^(?:true|false|1|0|yes|no|on|off)?$/i.test(attr.value)) ||
|
||||||
|
(expected === "number" && attr.value.trim() !== "" && Number.isFinite(Number(attr.value)));
|
||||||
|
if (
|
||||||
|
expected !== "unknown" &&
|
||||||
|
received !== "unknown" &&
|
||||||
|
expected !== received &&
|
||||||
|
!compatibleLiteral
|
||||||
|
) {
|
||||||
diagnostics.push({
|
diagnostics.push({
|
||||||
code: "WRN-COMPONENT-PROP-TYPE",
|
code: "WRN-COMPONENT-PROP-TYPE",
|
||||||
category: "error",
|
category: "error",
|
||||||
@@ -481,8 +544,15 @@ function category(value: ts.DiagnosticCategory): WrnTypeDiagnostic["category"] {
|
|||||||
function hostWithVirtualFiles(
|
function hostWithVirtualFiles(
|
||||||
files: Map<string, string>,
|
files: Map<string, string>,
|
||||||
options: ts.CompilerOptions,
|
options: ts.CompilerOptions,
|
||||||
|
standardLibraryDirectory?: string,
|
||||||
): ts.CompilerHost {
|
): ts.CompilerHost {
|
||||||
const host = ts.createCompilerHost(options, true);
|
const host = ts.createCompilerHost(options, true);
|
||||||
|
if (standardLibraryDirectory) {
|
||||||
|
host.getDefaultLibFileName = (compilerOptions) =>
|
||||||
|
join(standardLibraryDirectory, ts.getDefaultLibFileName(compilerOptions));
|
||||||
|
(host as ts.CompilerHost & { getDefaultLibLocation?: () => string }).getDefaultLibLocation =
|
||||||
|
() => standardLibraryDirectory;
|
||||||
|
}
|
||||||
const originalGet = host.getSourceFile.bind(host);
|
const originalGet = host.getSourceFile.bind(host);
|
||||||
host.fileExists = (fileName) => files.has(normalize(fileName)) || ts.sys.fileExists(fileName);
|
host.fileExists = (fileName) => files.has(normalize(fileName)) || ts.sys.fileExists(fileName);
|
||||||
host.readFile = (fileName) => files.get(normalize(fileName)) ?? ts.sys.readFile(fileName);
|
host.readFile = (fileName) => files.get(normalize(fileName)) ?? ts.sys.readFile(fileName);
|
||||||
@@ -495,15 +565,32 @@ function hostWithVirtualFiles(
|
|||||||
return host;
|
return host;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function resolveStandardLibraryDirectory(
|
||||||
|
appRoot: string,
|
||||||
|
options: ts.CompilerOptions,
|
||||||
|
): string | null {
|
||||||
|
const candidates = [
|
||||||
|
dirname(ts.getDefaultLibFilePath(options)),
|
||||||
|
join(appRoot, "node_modules", "typescript", "lib"),
|
||||||
|
join(process.cwd(), "node_modules", "typescript", "lib"),
|
||||||
|
];
|
||||||
|
for (const candidate of candidates) {
|
||||||
|
if ((options.lib ?? []).every((name) => ts.sys.fileExists(join(candidate, name)))) {
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
function mappedPosition(
|
function mappedPosition(
|
||||||
virtual: VirtualTypeScriptModule,
|
virtual: VirtualTypeScriptModule,
|
||||||
line: number,
|
line: number,
|
||||||
column: number,
|
column: number,
|
||||||
): { line: number; column: number } {
|
): { line: number; column: number } | null {
|
||||||
const mapping = virtual.mappings.find(
|
const mapping = virtual.mappings.find(
|
||||||
(entry) => line >= entry.virtualStartLine && line <= entry.virtualEndLine,
|
(entry) => line >= entry.virtualStartLine && line <= entry.virtualEndLine,
|
||||||
);
|
);
|
||||||
if (!mapping) return { line: 1, column: 1 };
|
if (!mapping) return null;
|
||||||
const offset = line - mapping.virtualStartLine;
|
const offset = line - mapping.virtualStartLine;
|
||||||
return {
|
return {
|
||||||
line: mapping.sourceStartLine + offset,
|
line: mapping.sourceStartLine + offset,
|
||||||
@@ -644,30 +731,43 @@ export function checkWrnSource(
|
|||||||
lib: ["lib.esnext.d.ts", "lib.dom.d.ts", "lib.dom.iterable.d.ts"],
|
lib: ["lib.esnext.d.ts", "lib.dom.d.ts", "lib.dom.iterable.d.ts"],
|
||||||
};
|
};
|
||||||
const rootNames = [virtual.fileName, ...appTypes.files.keys()];
|
const rootNames = [virtual.fileName, ...appTypes.files.keys()];
|
||||||
|
const standardLibraryDirectory = resolveStandardLibraryDirectory(appRoot, compilerOptions);
|
||||||
|
|
||||||
|
const tsDiagnostics: WrnTypeDiagnostic[] = [];
|
||||||
|
if (standardLibraryDirectory) {
|
||||||
const program = ts.createProgram(
|
const program = ts.createProgram(
|
||||||
rootNames,
|
rootNames,
|
||||||
compilerOptions,
|
compilerOptions,
|
||||||
hostWithVirtualFiles(files, compilerOptions),
|
hostWithVirtualFiles(files, compilerOptions, standardLibraryDirectory),
|
||||||
);
|
);
|
||||||
const tsDiagnostics = ts.getPreEmitDiagnostics(program).map((diagnostic): WrnTypeDiagnostic => {
|
for (const diagnostic of ts.getPreEmitDiagnostics(program)) {
|
||||||
const file = diagnostic.file;
|
const file = diagnostic.file;
|
||||||
|
if (!file) continue;
|
||||||
|
const normalizedFile = normalize(file.fileName);
|
||||||
|
const isVirtual = normalizedFile === normalize(virtual.fileName);
|
||||||
|
const isApplicationType = files.has(normalizedFile);
|
||||||
|
if (!isVirtual && !isApplicationType) continue;
|
||||||
|
|
||||||
const start = diagnostic.start ?? 0;
|
const start = diagnostic.start ?? 0;
|
||||||
const virtualPosition = file?.getLineAndCharacterOfPosition(start) ?? { line: 0, character: 0 };
|
const virtualPosition = file.getLineAndCharacterOfPosition(start);
|
||||||
const isVirtual = normalize(file?.fileName ?? "") === normalize(virtual.fileName);
|
|
||||||
const sourcePosition = isVirtual
|
const sourcePosition = isVirtual
|
||||||
? mappedPosition(virtual, virtualPosition.line + 1, virtualPosition.character + 1)
|
? mappedPosition(virtual, virtualPosition.line + 1, virtualPosition.character + 1)
|
||||||
: { line: virtualPosition.line + 1, column: virtualPosition.character + 1 };
|
: { line: virtualPosition.line + 1, column: virtualPosition.character + 1 };
|
||||||
return {
|
// Synthetic declarations without a source mapping are implementation
|
||||||
|
// details of the virtual document and must not appear as editor errors.
|
||||||
|
if (!sourcePosition) continue;
|
||||||
|
tsDiagnostics.push({
|
||||||
code: `WRN-TYPE-${diagnostic.code}`,
|
code: `WRN-TYPE-${diagnostic.code}`,
|
||||||
category: category(diagnostic.category),
|
category: category(diagnostic.category),
|
||||||
message: ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n"),
|
message: ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n"),
|
||||||
file: isVirtual ? filePath : (file?.fileName ?? filePath),
|
file: isVirtual ? filePath : file.fileName,
|
||||||
line: sourcePosition.line,
|
line: sourcePosition.line,
|
||||||
column: sourcePosition.column,
|
column: sourcePosition.column,
|
||||||
length: diagnostic.length ?? 1,
|
length: diagnostic.length ?? 1,
|
||||||
hint: "Fix the TypeScript contract or expression in the related .wrn declaration.",
|
hint: "Fix the TypeScript contract or expression in the related .wrn declaration.",
|
||||||
};
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
return [
|
return [
|
||||||
...(options.checkRuntimeBoundaries === false
|
...(options.checkRuntimeBoundaries === false
|
||||||
? []
|
? []
|
||||||
|
|||||||
@@ -60,3 +60,26 @@ page Home {
|
|||||||
);
|
);
|
||||||
expect(diagnostics.some((item) => item.code === "WRN-TYPE-2345")).toBe(true);
|
expect(diagnostics.some((item) => item.code === "WRN-TYPE-2345")).toBe(true);
|
||||||
}, 15_000);
|
}, 15_000);
|
||||||
|
|
||||||
|
test("accepts WRN boolean string literals and reserved class props", () => {
|
||||||
|
const root = fixture();
|
||||||
|
const componentPath = join(root, "app", "components", "Toggle.wrn");
|
||||||
|
writeFileSync(
|
||||||
|
componentPath,
|
||||||
|
`component Toggle {
|
||||||
|
props { enabled: boolean = false class: string = "" }
|
||||||
|
view { <button class='{class}'></button> }
|
||||||
|
}`,
|
||||||
|
);
|
||||||
|
const filePath = join(root, "app", "pages", "home.wrn");
|
||||||
|
const diagnostics = checkWrnSource(
|
||||||
|
`import Toggle from "@/components/Toggle.wrn"
|
||||||
|
page Home {
|
||||||
|
view { <Toggle enabled="true" class="demo" /> }
|
||||||
|
}`,
|
||||||
|
{ appRoot: root, filePath },
|
||||||
|
);
|
||||||
|
expect(diagnostics.some((item) => item.code === "WRN-COMPONENT-PROP-TYPE")).toBe(false);
|
||||||
|
expect(diagnostics.some((item) => item.code === "WRN-TYPE-1389")).toBe(false);
|
||||||
|
expect(diagnostics.some((item) => item.code === "WRN-TYPE-1005")).toBe(false);
|
||||||
|
}, 15_000);
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user