fix: compile typed catches and reject event loop syntax
This commit is contained in:
@@ -268,7 +268,7 @@
|
|||||||
},
|
},
|
||||||
"packages/cli": {
|
"packages/cli": {
|
||||||
"name": "@wrnexus/cli",
|
"name": "@wrnexus/cli",
|
||||||
"version": "0.8.22",
|
"version": "0.8.23",
|
||||||
"bin": {
|
"bin": {
|
||||||
"wrnexus": "src/index.ts",
|
"wrnexus": "src/index.ts",
|
||||||
},
|
},
|
||||||
@@ -295,7 +295,7 @@
|
|||||||
},
|
},
|
||||||
"packages/compiler": {
|
"packages/compiler": {
|
||||||
"name": "@wrnexus/compiler",
|
"name": "@wrnexus/compiler",
|
||||||
"version": "0.8.9",
|
"version": "0.8.10",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wrnexus/csr": "workspace:*",
|
"@wrnexus/csr": "workspace:*",
|
||||||
"@wrnexus/store": "workspace:*",
|
"@wrnexus/store": "workspace:*",
|
||||||
@@ -332,7 +332,7 @@
|
|||||||
},
|
},
|
||||||
"packages/dev-server": {
|
"packages/dev-server": {
|
||||||
"name": "@wrnexus/dev-server",
|
"name": "@wrnexus/dev-server",
|
||||||
"version": "0.8.21",
|
"version": "0.8.22",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wrnexus/authz": "workspace:*",
|
"@wrnexus/authz": "workspace:*",
|
||||||
"@wrnexus/cache": "workspace:*",
|
"@wrnexus/cache": "workspace:*",
|
||||||
@@ -597,7 +597,7 @@
|
|||||||
},
|
},
|
||||||
"packages/syntax": {
|
"packages/syntax": {
|
||||||
"name": "@wrnexus/syntax",
|
"name": "@wrnexus/syntax",
|
||||||
"version": "0.8.8",
|
"version": "0.8.9",
|
||||||
},
|
},
|
||||||
"packages/test": {
|
"packages/test": {
|
||||||
"name": "@wrnexus/test",
|
"name": "@wrnexus/test",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/cli",
|
"name": "@wrnexus/cli",
|
||||||
"version": "0.8.22",
|
"version": "0.8.23",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/compiler",
|
"name": "@wrnexus/compiler",
|
||||||
"version": "0.8.9",
|
"version": "0.8.10",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/dev-server",
|
"name": "@wrnexus/dev-server",
|
||||||
"version": "0.8.21",
|
"version": "0.8.22",
|
||||||
"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.8",
|
"version": "0.8.9",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
@@ -246,6 +246,18 @@ function astDiagnostics(ast: PageAst, options: DiagnoseOptions): WrnDiagnostic[]
|
|||||||
const tag = node.tag.toLowerCase();
|
const tag = node.tag.toLowerCase();
|
||||||
|
|
||||||
for (const attribute of node.attrs) {
|
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()))
|
if (attribute.event || attribute.boolean || !urlAttributes.has(attribute.name.toLowerCase()))
|
||||||
continue;
|
continue;
|
||||||
if (attribute.value.includes("{")) continue;
|
if (attribute.value.includes("{")) continue;
|
||||||
|
|||||||
@@ -63,16 +63,18 @@ export function validateTypedInitializer(
|
|||||||
* Server output retains the original typed source.
|
* Server output retains the original typed source.
|
||||||
*/
|
*/
|
||||||
export function eraseFunctionTypes(source: string): string {
|
export function eraseFunctionTypes(source: string): string {
|
||||||
return source.replace(
|
return source
|
||||||
/(\b(?:async\s+)?function\s+[A-Za-z_$][\w$]*\s*\()([^)]*)(\)\s*)(?::\s*([^{}=>]+)\s*)?(\{)/g,
|
.replace(/\bcatch\s*\(\s*([A-Za-z_$][\w$]*)\s*:\s*(?:any|unknown)\s*\)/g, "catch ($1)")
|
||||||
(_whole, open: string, params: string, close: string, _returnType: string, brace: string) => {
|
.replace(
|
||||||
const plainParams = params
|
/(\b(?:async\s+)?function\s+[A-Za-z_$][\w$]*\s*\()([^)]*)(\)\s*)(?::\s*([^{}=>]+)\s*)?(\{)/g,
|
||||||
.split(",")
|
(_whole, open: string, params: string, close: string, _returnType: string, brace: string) => {
|
||||||
.map((param) =>
|
const plainParams = params
|
||||||
param.replace(/([A-Za-z_$][\w$]*)(\?)?\s*:\s*([^=]+?)(?=\s*=|$)/, "$1").trim(),
|
.split(",")
|
||||||
)
|
.map((param) =>
|
||||||
.join(", ");
|
param.replace(/([A-Za-z_$][\w$]*)(\?)?\s*:\s*([^=]+?)(?=\s*=|$)/, "$1").trim(),
|
||||||
return `${open}${plainParams}${close}${brace}`;
|
)
|
||||||
},
|
.join(", ");
|
||||||
);
|
return `${open}${plainParams}${close}${brace}`;
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { expect, test } from "bun:test";
|
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"
|
const source = `import type { PublicUser } from "@/types/user.ts"
|
||||||
import PublicLayout from "@/layouts/PublicLayout.wrn"
|
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);
|
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,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user