fix(compiler): reject malformed structured props
This commit is contained in:
@@ -2414,7 +2414,7 @@ function generateComponent(ast: PageAst): string {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
decls.push(
|
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")) {
|
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) {
|
if (v === undefined || v === null) {
|
||||||
return def;
|
return def;
|
||||||
}
|
}
|
||||||
@@ -2529,14 +2529,14 @@ function generateComponent(ast: PageAst): string {
|
|||||||
if (typeof v === "string") {
|
if (typeof v === "string") {
|
||||||
try {
|
try {
|
||||||
const parsed = JSON.parse(v);
|
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 {
|
} catch {
|
||||||
if (declared === "array") throw new TypeError("Expected an array prop");
|
throw new TypeError("Expected an array prop '" + propName + "'");
|
||||||
return def;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return def;
|
throw new TypeError("Expected an array prop '" + propName + "'");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (declared === "object" || (def !== null && typeof def === "object")) {
|
if (declared === "object" || (def !== null && typeof def === "object")) {
|
||||||
@@ -2552,20 +2552,16 @@ function generateComponent(ast: PageAst): string {
|
|||||||
try {
|
try {
|
||||||
const parsed = JSON.parse(v);
|
const parsed = JSON.parse(v);
|
||||||
|
|
||||||
return (
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
||||||
parsed !== null &&
|
throw new TypeError("Expected an object prop '" + propName + "'");
|
||||||
typeof parsed === "object" &&
|
}
|
||||||
!Array.isArray(parsed)
|
return parsed;
|
||||||
)
|
|
||||||
? parsed
|
|
||||||
: def;
|
|
||||||
} catch {
|
} catch {
|
||||||
if (declared === "object") throw new TypeError("Expected an object prop");
|
throw new TypeError("Expected an object prop '" + propName + "'");
|
||||||
return def;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return def;
|
throw new TypeError("Expected an object prop '" + propName + "'");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (declared === "bigint") return BigInt(v);
|
if (declared === "bigint") return BigInt(v);
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ export interface CounterOutputs {
|
|||||||
"change"(value: number): void;
|
"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) {
|
if (v === undefined || v === null) {
|
||||||
return def;
|
return def;
|
||||||
}
|
}
|
||||||
@@ -70,14 +70,14 @@ function __coerce(v: any, def: any, declared: string = "unknown"): any {
|
|||||||
if (typeof v === "string") {
|
if (typeof v === "string") {
|
||||||
try {
|
try {
|
||||||
const parsed = JSON.parse(v);
|
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 {
|
} catch {
|
||||||
if (declared === "array") throw new TypeError("Expected an array prop");
|
throw new TypeError("Expected an array prop '" + propName + "'");
|
||||||
return def;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return def;
|
throw new TypeError("Expected an array prop '" + propName + "'");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (declared === "object" || (def !== null && typeof def === "object")) {
|
if (declared === "object" || (def !== null && typeof def === "object")) {
|
||||||
@@ -93,20 +93,16 @@ function __coerce(v: any, def: any, declared: string = "unknown"): any {
|
|||||||
try {
|
try {
|
||||||
const parsed = JSON.parse(v);
|
const parsed = JSON.parse(v);
|
||||||
|
|
||||||
return (
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
||||||
parsed !== null &&
|
throw new TypeError("Expected an object prop '" + propName + "'");
|
||||||
typeof parsed === "object" &&
|
}
|
||||||
!Array.isArray(parsed)
|
return parsed;
|
||||||
)
|
|
||||||
? parsed
|
|
||||||
: def;
|
|
||||||
} catch {
|
} catch {
|
||||||
if (declared === "object") throw new TypeError("Expected an object prop");
|
throw new TypeError("Expected an object prop '" + propName + "'");
|
||||||
return def;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return def;
|
throw new TypeError("Expected an object prop '" + propName + "'");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (declared === "bigint") return BigInt(v);
|
if (declared === "bigint") return BigInt(v);
|
||||||
@@ -264,7 +260,7 @@ function __wrnexusSerializeScopeValue(value: any): string {
|
|||||||
|
|
||||||
export function render(props: CounterProps = {} as CounterProps): string {
|
export function render(props: CounterProps = {} as CounterProps): string {
|
||||||
const __p = props || {};
|
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"]));
|
const __attrs = __restProps(__p, new Set(["label"]));
|
||||||
let count = (0);
|
let count = (0);
|
||||||
const __scopeState = { "label": label, "count": count };
|
const __scopeState = { "label": label, "count": count };
|
||||||
|
|||||||
@@ -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 { <div></div> }
|
||||||
|
}`);
|
||||||
|
const render = mod.render as (props?: Record<string, unknown>) => string;
|
||||||
|
|
||||||
|
expect(() => render({ items: "not-json" })).toThrow("Expected an array prop 'items'");
|
||||||
|
expect(() => render({ options: "[]" })).toThrow("Expected an object prop 'options'");
|
||||||
|
});
|
||||||
|
|
||||||
test("parses a layout block", () => {
|
test("parses a layout block", () => {
|
||||||
const ast = parse(`
|
const ast = parse(`
|
||||||
layout AppLayout {
|
layout AppLayout {
|
||||||
|
|||||||
Reference in New Issue
Block a user