diff --git a/packages/compiler/src/codegen.ts b/packages/compiler/src/codegen.ts index ba7657f4..e6d8e28f 100644 --- a/packages/compiler/src/codegen.ts +++ b/packages/compiler/src/codegen.ts @@ -2414,7 +2414,7 @@ function generateComponent(ast: PageAst): string { ); } decls.push( - ` const ${nameRefs.get(prop.name)}: ${prop.valueType ?? "any"} = __coerce(__p[${JSON.stringify(prop.name)}], (${resolveExpr(prop.default)}), ${JSON.stringify(runtimeTypeOf(prop.valueType))});`, + ` const ${nameRefs.get(prop.name)}: ${prop.valueType ?? "any"} = __coerce(__p[${JSON.stringify(prop.name)}], (${resolveExpr(prop.default)}), ${JSON.stringify(runtimeTypeOf(prop.valueType))}, ${JSON.stringify(prop.name)});`, ); } if (!effectiveProps.some((prop) => prop.name === "attrs")) { @@ -2504,7 +2504,7 @@ function generateComponent(ast: PageAst): string { ); } - out.push(`function __coerce(v: any, def: any, declared: string = "unknown"): any { + out.push(`function __coerce(v: any, def: any, declared: string = "unknown", propName: string = "prop"): any { if (v === undefined || v === null) { return def; } @@ -2529,14 +2529,14 @@ function generateComponent(ast: PageAst): string { if (typeof v === "string") { try { const parsed = JSON.parse(v); - return Array.isArray(parsed) ? parsed : def; + if (!Array.isArray(parsed)) throw new TypeError("Expected an array prop '" + propName + "'"); + return parsed; } catch { - if (declared === "array") throw new TypeError("Expected an array prop"); - return def; + throw new TypeError("Expected an array prop '" + propName + "'"); } } - return def; + throw new TypeError("Expected an array prop '" + propName + "'"); } if (declared === "object" || (def !== null && typeof def === "object")) { @@ -2552,20 +2552,16 @@ function generateComponent(ast: PageAst): string { try { const parsed = JSON.parse(v); - return ( - parsed !== null && - typeof parsed === "object" && - !Array.isArray(parsed) - ) - ? parsed - : def; + if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) { + throw new TypeError("Expected an object prop '" + propName + "'"); + } + return parsed; } catch { - if (declared === "object") throw new TypeError("Expected an object prop"); - return def; + throw new TypeError("Expected an object prop '" + propName + "'"); } } - return def; + throw new TypeError("Expected an object prop '" + propName + "'"); } if (declared === "bigint") return BigInt(v); diff --git a/packages/compiler/test/__snapshots__/resilience.test.ts.snap b/packages/compiler/test/__snapshots__/resilience.test.ts.snap index ee5e1b9b..1920b3c7 100644 --- a/packages/compiler/test/__snapshots__/resilience.test.ts.snap +++ b/packages/compiler/test/__snapshots__/resilience.test.ts.snap @@ -45,7 +45,7 @@ export interface CounterOutputs { "change"(value: number): void; } -function __coerce(v: any, def: any, declared: string = "unknown"): any { +function __coerce(v: any, def: any, declared: string = "unknown", propName: string = "prop"): any { if (v === undefined || v === null) { return def; } @@ -70,14 +70,14 @@ function __coerce(v: any, def: any, declared: string = "unknown"): any { if (typeof v === "string") { try { const parsed = JSON.parse(v); - return Array.isArray(parsed) ? parsed : def; + if (!Array.isArray(parsed)) throw new TypeError("Expected an array prop '" + propName + "'"); + return parsed; } catch { - if (declared === "array") throw new TypeError("Expected an array prop"); - return def; + throw new TypeError("Expected an array prop '" + propName + "'"); } } - return def; + throw new TypeError("Expected an array prop '" + propName + "'"); } if (declared === "object" || (def !== null && typeof def === "object")) { @@ -93,20 +93,16 @@ function __coerce(v: any, def: any, declared: string = "unknown"): any { try { const parsed = JSON.parse(v); - return ( - parsed !== null && - typeof parsed === "object" && - !Array.isArray(parsed) - ) - ? parsed - : def; + if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) { + throw new TypeError("Expected an object prop '" + propName + "'"); + } + return parsed; } catch { - if (declared === "object") throw new TypeError("Expected an object prop"); - return def; + throw new TypeError("Expected an object prop '" + propName + "'"); } } - return def; + throw new TypeError("Expected an object prop '" + propName + "'"); } if (declared === "bigint") return BigInt(v); @@ -264,7 +260,7 @@ function __wrnexusSerializeScopeValue(value: any): string { export function render(props: CounterProps = {} as CounterProps): string { const __p = props || {}; - const label: string = __coerce(__p["label"], ("Count"), "string"); + const label: string = __coerce(__p["label"], ("Count"), "string", "label"); const __attrs = __restProps(__p, new Set(["label"])); let count = (0); const __scopeState = { "label": label, "count": count }; diff --git a/packages/compiler/test/compiler.test.ts b/packages/compiler/test/compiler.test.ts index 3ef0b8e4..06e6acb0 100644 --- a/packages/compiler/test/compiler.test.ts +++ b/packages/compiler/test/compiler.test.ts @@ -526,6 +526,17 @@ component NativeProps { ); }); +test("malformed structured prop strings fail with the prop name", async () => { + const mod = await compileAndImport(`component StructuredProps { + props { items = [] options = {} } + view {
} + }`); + const render = mod.render as (props?: Record