From f68f79786f97beb1c85f9fa73bdfa5f72f964be1 Mon Sep 17 00:00:00 2001 From: Ajay Ghanwat Date: Wed, 12 Aug 2026 19:36:18 +0530 Subject: [PATCH] fix: compile typed catches and reject event loop syntax --- bun.lock | 8 ++++---- packages/cli/package.json | 2 +- packages/compiler/package.json | 2 +- packages/dev-server/package.json | 2 +- packages/syntax/package.json | 2 +- packages/syntax/src/diagnostics.ts | 12 ++++++++++++ packages/syntax/src/types.ts | 26 ++++++++++++++------------ packages/syntax/test/v060.test.ts | 19 ++++++++++++++++++- 8 files changed, 52 insertions(+), 21 deletions(-) diff --git a/bun.lock b/bun.lock index 63be2038..3bb664a4 100644 --- a/bun.lock +++ b/bun.lock @@ -268,7 +268,7 @@ }, "packages/cli": { "name": "@wrnexus/cli", - "version": "0.8.22", + "version": "0.8.23", "bin": { "wrnexus": "src/index.ts", }, @@ -295,7 +295,7 @@ }, "packages/compiler": { "name": "@wrnexus/compiler", - "version": "0.8.9", + "version": "0.8.10", "dependencies": { "@wrnexus/csr": "workspace:*", "@wrnexus/store": "workspace:*", @@ -332,7 +332,7 @@ }, "packages/dev-server": { "name": "@wrnexus/dev-server", - "version": "0.8.21", + "version": "0.8.22", "dependencies": { "@wrnexus/authz": "workspace:*", "@wrnexus/cache": "workspace:*", @@ -597,7 +597,7 @@ }, "packages/syntax": { "name": "@wrnexus/syntax", - "version": "0.8.8", + "version": "0.8.9", }, "packages/test": { "name": "@wrnexus/test", diff --git a/packages/cli/package.json b/packages/cli/package.json index 29bede88..8a349cd5 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/cli", - "version": "0.8.22", + "version": "0.8.23", "type": "module", "main": "src/index.ts", "exports": { diff --git a/packages/compiler/package.json b/packages/compiler/package.json index f5139a51..3939f202 100644 --- a/packages/compiler/package.json +++ b/packages/compiler/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/compiler", - "version": "0.8.9", + "version": "0.8.10", "type": "module", "main": "src/index.ts", "exports": { diff --git a/packages/dev-server/package.json b/packages/dev-server/package.json index 8607da09..91254cf2 100644 --- a/packages/dev-server/package.json +++ b/packages/dev-server/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/dev-server", - "version": "0.8.21", + "version": "0.8.22", "type": "module", "main": "src/index.ts", "exports": { diff --git a/packages/syntax/package.json b/packages/syntax/package.json index b76cbf13..0486b1a1 100644 --- a/packages/syntax/package.json +++ b/packages/syntax/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/syntax", - "version": "0.8.8", + "version": "0.8.9", "type": "module", "main": "src/index.ts", "exports": { diff --git a/packages/syntax/src/diagnostics.ts b/packages/syntax/src/diagnostics.ts index aebd85eb..61b00de2 100644 --- a/packages/syntax/src/diagnostics.ts +++ b/packages/syntax/src/diagnostics.ts @@ -246,6 +246,18 @@ function astDiagnostics(ast: PageAst, options: DiagnoseOptions): WrnDiagnostic[] const tag = node.tag.toLowerCase(); for (const attribute of node.attrs) { + if (attribute.event && (attribute.name === "for" || attribute.name === "key")) { + diagnostics.push({ + code: "WRN-TEMPLATE-LOOP-DIRECTIVE", + severity: "error", + message: `@${attribute.name} is an event binding, not a loop directive.`, + hint: + attribute.name === "for" + ? 'Use data-for="item in items".' + : 'Use data-key="item.id" alongside data-for.', + file: options.file, + }); + } if (attribute.event || attribute.boolean || !urlAttributes.has(attribute.name.toLowerCase())) continue; if (attribute.value.includes("{")) continue; diff --git a/packages/syntax/src/types.ts b/packages/syntax/src/types.ts index a068acd9..de291a51 100644 --- a/packages/syntax/src/types.ts +++ b/packages/syntax/src/types.ts @@ -63,16 +63,18 @@ export function validateTypedInitializer( * Server output retains the original typed source. */ export function eraseFunctionTypes(source: string): string { - return source.replace( - /(\b(?:async\s+)?function\s+[A-Za-z_$][\w$]*\s*\()([^)]*)(\)\s*)(?::\s*([^{}=>]+)\s*)?(\{)/g, - (_whole, open: string, params: string, close: string, _returnType: string, brace: string) => { - const plainParams = params - .split(",") - .map((param) => - param.replace(/([A-Za-z_$][\w$]*)(\?)?\s*:\s*([^=]+?)(?=\s*=|$)/, "$1").trim(), - ) - .join(", "); - return `${open}${plainParams}${close}${brace}`; - }, - ); + return source + .replace(/\bcatch\s*\(\s*([A-Za-z_$][\w$]*)\s*:\s*(?:any|unknown)\s*\)/g, "catch ($1)") + .replace( + /(\b(?:async\s+)?function\s+[A-Za-z_$][\w$]*\s*\()([^)]*)(\)\s*)(?::\s*([^{}=>]+)\s*)?(\{)/g, + (_whole, open: string, params: string, close: string, _returnType: string, brace: string) => { + const plainParams = params + .split(",") + .map((param) => + param.replace(/([A-Za-z_$][\w$]*)(\?)?\s*:\s*([^=]+?)(?=\s*=|$)/, "$1").trim(), + ) + .join(", "); + return `${open}${plainParams}${close}${brace}`; + }, + ); } diff --git a/packages/syntax/test/v060.test.ts b/packages/syntax/test/v060.test.ts index 48ec46ae..21eeb091 100644 --- a/packages/syntax/test/v060.test.ts +++ b/packages/syntax/test/v060.test.ts @@ -1,5 +1,5 @@ import { expect, test } from "bun:test"; -import { diagnose, parse } from "../src/index.ts"; +import { diagnose, eraseFunctionTypes, parse } from "../src/index.ts"; const source = `import type { PublicUser } from "@/types/user.ts" import PublicLayout from "@/layouts/PublicLayout.wrn" @@ -118,3 +118,20 @@ test("readonly prop diagnostics still reject direct prop assignments", () => { }`); expect(diagnostics.some((diagnostic) => diagnostic.code === "WRN-PROP-READONLY")).toBe(true); }); + +test("browser type erasure removes typed catch bindings", () => { + const source = `async function load(): Promise { + try { await fetch("/api") } catch (error: any) { console.error(error) } + }`; + expect(eraseFunctionTypes(source)).toContain("catch (error)"); + expect(eraseFunctionTypes(source)).not.toContain(": any"); +}); + +test("diagnoses event syntax mistakenly used for loop directives", () => { + const diagnostics = diagnose(`component Rows { + view { } + }`); + expect(diagnostics.filter((entry) => entry.code === "WRN-TEMPLATE-LOOP-DIRECTIVE")).toHaveLength( + 2, + ); +});