fix: compile typed catches and reject event loop syntax
Quality / quality (ubuntu-latest) (push) Failing after 9m46s
Quality / quality (windows-latest) (push) Canceled after 0s

This commit is contained in:
2026-08-12 19:36:18 +05:30
parent d47c57a953
commit f68f79786f
8 changed files with 52 additions and 21 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@wrnexus/syntax",
"version": "0.8.8",
"version": "0.8.9",
"type": "module",
"main": "src/index.ts",
"exports": {
+12
View File
@@ -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;
+14 -12
View File
@@ -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}`;
},
);
}
+18 -1
View File
@@ -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<void> {
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 { <ul><li @for="row in rows" @key="row.id">{row.name}</li></ul> }
}`);
expect(diagnostics.filter((entry) => entry.code === "WRN-TEMPLATE-LOOP-DIRECTIVE")).toHaveLength(
2,
);
});