diff --git a/packages/quicktype-core/src/language/CJSON/CJSONRenderer.ts b/packages/quicktype-core/src/language/CJSON/CJSONRenderer.ts index ccca25aa50..a69d1f7106 100644 --- a/packages/quicktype-core/src/language/CJSON/CJSONRenderer.ts +++ b/packages/quicktype-core/src/language/CJSON/CJSONRenderer.ts @@ -536,7 +536,7 @@ export class CJSONRenderer extends ConvenienceRenderer { ], () => { this.emitLine("enum ", enumName, " x = 0;"); - this.emitBlock(["if (NULL != j)"], () => { + this.emitBlock(["if (cJSON_IsString(j))"], () => { let onFirst = true; const combinedName = allUpperWordStyle( this.sourcelikeToString(enumName), @@ -2502,7 +2502,7 @@ export class CJSONRenderer extends ConvenienceRenderer { ); if (pattern !== undefined) { this.emitLine( - `regex_t regex; regcomp(®ex, "${stringEscape(pattern)}", REG_EXTENDED); if (regexec(®ex, ${value}->valuestring, 0, NULL, 0)) { regfree(®ex); cJSON_Delete${this.sourcelikeToString(className)}(x); return NULL; } regfree(®ex);`, + `regex_t regex; if (0 == regcomp(®ex, "${stringEscape(pattern)}", REG_EXTENDED)) { if (regexec(®ex, ${value}->valuestring, 0, NULL, 0)) { regfree(®ex); cJSON_Delete${this.sourcelikeToString(className)}(x); return NULL; } regfree(®ex); }`, ); } if (minLength !== undefined) @@ -3004,21 +3004,14 @@ if (NULL == validated) { x${level > 0 ? level.toString() : ""}->${this.sourcelikeToString(name)} = x${child_level}; cJSON_Delete${this.sourcelikeToString(className)}(x); return NULL; -} -${this.sourcelikeToString(cJSON.items?.deleteType ?? "")}(validated);`, +}`, ); this.emitLine( "hashtable_add(x", child_level.toString(), ", e", child_level.toString(), - "->string, ", - cJSON - .items - ?.getValue, - "(e", - child_level.toString(), - "), sizeof(", + "->string, validated, sizeof(", cJSON .items ?.cType, @@ -4985,13 +4978,31 @@ ${this.sourcelikeToString(cJSON.items?.deleteType ?? "")}(validated);`, type, false, ); + const container = + cJSON.cjsonType === "cJSON_Array" + ? { + create: ["list_create(false, NULL)"], + prefix: "list_add_tail(x->value, ", + } + : cJSON.cjsonType === "cJSON_Map" + ? { + create: [ + "hashtable_create(", + this.hashtableSize, + ", false)", + ], + prefix: "hashtable_add(x->value, e->string, ", + } + : undefined; + const items = cJSON.items; if ( - cJSON.cjsonType === "cJSON_Array" && - cJSON.items !== undefined + container !== undefined && + items !== undefined ) { - const items = cJSON.items; this.emitLine( - "x->value = list_create(false, NULL);", + "x->value = ", + container.create, + ";", ); this.emitBlock( ["if (NULL != x->value)"], @@ -5000,83 +5011,19 @@ ${this.sourcelikeToString(cJSON.items?.deleteType ?? "")}(validated);`, this.emitBlock( ["cJSON_ArrayForEach(e, j)"], () => { - const add = ( - cJSON: TypeCJSON, - ) => { - if ( - cJSON.items - ?.cjsonType === - "cJSON_Array" - ) { - /* Not supported */ - } else if ( - cJSON.items - ?.cjsonType === - "cJSON_Map" - ) { - /* Not supported */ - } else if ( - cJSON.items - ?.cjsonType === - "cJSON_Invalid" || - cJSON.items - ?.cjsonType === - "cJSON_NULL" - ) { - this.emitLine( - "list_add_tail(x->value, (", - cJSON.items?.cType, - " *)0xDEADBEAF, sizeof(", - cJSON.items?.cType, - " *));", - ); - } else if ( - cJSON.items - ?.cjsonType === - "cJSON_String" - ) { - this.emitLine( - "list_add_tail(x->value, strdup(", - cJSON.items - ?.getValue, - "(e)), sizeof(", - cJSON.items?.cType, - " *));", - ); - } else if ( - cJSON.items - ?.deleteType === - "cJSON_free" - ) { - this.emitLine( - cJSON.items.cType, - " * item = cJSON_malloc(sizeof(*item));", - ); - this.emitLine( - "if (NULL != item) { *item = ", - cJSON.items - .getValue, - "(e); list_add_tail(x->value, item, sizeof(*item)); }", - ); - } else { - this.emitLine( - "list_add_tail(x->value, ", - // @ts-expect-error awaiting refactor - cJSON.items - ?.getValue, - "(e), sizeof(", - cJSON.items?.cType, - " *));", - ); - } - }; - + /* A map's elements are checked too, nullable or not */ if ( - !items.isNullable && - items.isType !== "" + items.isType !== "" && + (cJSON.cjsonType === + "cJSON_Map" || + !items.isNullable) ) { this.emitLine( - "if (!", + "if (", + items.isNullable + ? "!cJSON_IsNull(e) && " + : "", + "!", items.isType, "(e)) { cJSON_Delete", className, @@ -5090,115 +5037,28 @@ ${this.sourcelikeToString(cJSON.items?.deleteType ?? "")}(validated);`, "if (!cJSON_IsNull(e))", ], () => { - add(cJSON); - }, - ); - this.emitBlock( - ["else"], - () => { - this.emitLine( - "list_add_tail(x->value, (void *)0xDEADBEEF, sizeof(void *));", + this.emitTopLevelAdd( + container.prefix, + items, + className, ); }, ); - } else { - add(cJSON); - } - }, - ); - }, - ); - } else if ( - cJSON.cjsonType === "cJSON_Map" && - cJSON.items !== undefined - ) { - this.emitLine( - "x->value = hashtable_create(", - this.hashtableSize, - ", false);", - ); - this.emitBlock( - ["if (NULL != x->value)"], - () => { - this.emitLine("cJSON * e = NULL;"); - this.emitBlock( - ["cJSON_ArrayForEach(e, j)"], - () => { - const add = ( - cJSON: TypeCJSON, - ) => { - if ( - cJSON.items - ?.cjsonType === - "cJSON_Array" - ) { - /* Not supported */ - } else if ( - cJSON.items - ?.cjsonType === - "cJSON_Map" - ) { - /* Not supported */ - } else if ( - cJSON.items - ?.cjsonType === - "cJSON_Invalid" || - cJSON.items - ?.cjsonType === - "cJSON_NULL" - ) { - this.emitLine( - "hashtable_add(x->value, e->string, (", - cJSON.items?.cType, - " *)0xDEADBEEF, sizeof(", - cJSON.items?.cType, - " *));", - ); - } else if ( - cJSON.items - ?.cjsonType === - "cJSON_String" - ) { - this.emitLine( - "hashtable_add(x->value, e->string, strdup(", - cJSON.items - ?.getValue, - "(e)), sizeof(", - cJSON.items?.cType, - " *));", - ); - } else { - this.emitLine( - "hashtable_add(x->value, e->string, ", - // @ts-expect-error awaiting refactor - cJSON.items - ?.getValue, - "(e), sizeof(", - cJSON.items?.cType, - " *));", - ); - } - }; - - if (cJSON.items?.isNullable) { - this.emitBlock( - [ - "if (!cJSON_IsNull(e))", - ], - () => { - add(cJSON); - }, - ); this.emitBlock( ["else"], () => { this.emitLine( - "hashtable_add(x->value, e->string, (void *)0xDEADBEEF, sizeof(void *));", + container.prefix, + "(void *)0xDEADBEEF, sizeof(void *));", ); }, ); } else { - add(cJSON); + this.emitTopLevelAdd( + container.prefix, + items, + className, + ); } }, ); @@ -5483,32 +5343,14 @@ ${this.sourcelikeToString(cJSON.items?.deleteType ?? "")}(validated);`, cJSON.cjsonType === "cJSON_Array" && cJSON.items !== undefined ) { + const items = cJSON.items; this.emitBlock(["if (NULL != x->value)"], () => { this.emitLine( - // @ts-expect-error awaiting refactor - cJSON.items?.cType, + items.cType, " * x1 = list_get_head(x->value);", ); this.emitBlock(["while (NULL != x1)"], () => { - if (cJSON.items?.isNullable) { - this.emitBlock( - ["if ((void *)0xDEADBEEF != x1)"], - () => { - this.emitLine( - // @ts-expect-error awaiting refactor - cJSON.items?.deleteType, - "(x1);", - ); - }, - ); - } else { - this.emitLine( - // @ts-expect-error awaiting refactor - cJSON.items?.deleteType, - "(x1);", - ); - } - + this.emitTopLevelDeleteItem(items, "x1"); this.emitLine("x1 = list_get_next(x->value);"); }); this.emitLine(cJSON.deleteType, "(x->value);"); @@ -5517,6 +5359,7 @@ ${this.sourcelikeToString(cJSON.items?.deleteType ?? "")}(validated);`, cJSON.cjsonType === "cJSON_Map" && cJSON.items !== undefined ) { + const items = cJSON.items; this.emitBlock(["if (NULL != x->value)"], () => { this.emitLine("char **keys = NULL;"); this.emitLine( @@ -5529,36 +5372,16 @@ ${this.sourcelikeToString(cJSON.items?.deleteType ?? "")}(validated);`, ], () => { this.emitLine( - // @ts-expect-error awaiting refactor - cJSON.items?.cType, + items.cType, " *x2 = hashtable_lookup(x->value, keys[index]);", ); this.emitBlock( ["if (NULL != x2)"], () => { - if (cJSON.items?.isNullable) { - this.emitBlock( - [ - "if ((", - cJSON.items?.cType, - " *)0xDEADBEEF != x2)", - ], - () => { - this.emitLine( - // @ts-expect-error awaiting refactor - cJSON.items - ?.deleteType, - "(x2);", - ); - }, - ); - } else { - this.emitLine( - // @ts-expect-error awaiting refactor - cJSON.items?.deleteType, - "(x2);", - ); - } + this.emitTopLevelDeleteItem( + items, + "x2", + ); }, ); }, @@ -5589,6 +5412,93 @@ ${this.sourcelikeToString(cJSON.items?.deleteType ?? "")}(validated);`, this.ensureBlankLine(); } + /** + * Adds the element `e` to a top-level list or map + * @param prefix: the call that adds it, up to the element + * @param items: the element's type + * @param className: the top-level's name + */ + private emitTopLevelAdd( + prefix: string, + items: TypeCJSON, + className: Name, + ): void { + if ( + items.cjsonType === "cJSON_Array" || + items.cjsonType === "cJSON_Map" + ) { + /* Not supported */ + } else if ( + items.cjsonType === "cJSON_Invalid" || + items.cjsonType === "cJSON_NULL" + ) { + this.emitLine( + prefix, + "(", + items.cType, + " *)0xDEADBEEF, sizeof(", + items.cType, + " *));", + ); + } else if (items.cjsonType === "cJSON_String") { + this.emitLine( + prefix, + "strdup(", + items.getValue, + "(e)), sizeof(", + items.cType, + " *));", + ); + } else if (items.deleteType === "cJSON_free") { + this.emitLine( + items.cType, + " * item = cJSON_malloc(sizeof(*item));", + ); + this.emitLine( + "if (NULL == item) { cJSON_Delete", + className, + "(x); return NULL; }", + ); + this.emitLine("*item = ", items.getValue, "(e);"); + this.emitLine(prefix, "item, sizeof(*item));"); + } else { + this.emitLine( + prefix, + items.getValue, + "(e), sizeof(", + items.cType, + " *));", + ); + } + } + + /** + * Deletes an element of a top-level list or map, as the delete + * function of a class deletes a list's or map's element: the marker + * 0xDEADBEEF of null is not freed + * @param items: the element's type + * @param variable: the variable that holds the element + */ + private emitTopLevelDeleteItem(items: TypeCJSON, variable: string): void { + if ( + items.cjsonType === "cJSON_Array" || + items.cjsonType === "cJSON_Map" || + items.cjsonType === "cJSON_Invalid" || + items.cjsonType === "cJSON_NULL" + ) { + /* Not supported, or nothing to do */ + } else if (items.isNullable) { + this.emitBlock( + ["if ((void *)0xDEADBEEF != ", variable, ")"], + () => { + this.emitLine(items.deleteType, "(", variable, ");"); + }, + ); + } else { + this.emitLine(items.deleteType, "(", variable, ");"); + } + } + /** * Convert quicktype type to cJSON type * @param t: quicktype type diff --git a/test/inputs/schema/top-level-integer-map.1.json b/test/inputs/schema/top-level-integer-map.1.json new file mode 100644 index 0000000000..c9f63a6c8d --- /dev/null +++ b/test/inputs/schema/top-level-integer-map.1.json @@ -0,0 +1 @@ +{ "a": 1, "b": -2 } diff --git a/test/inputs/schema/top-level-integer-map.schema b/test/inputs/schema/top-level-integer-map.schema new file mode 100644 index 0000000000..b56b87499b --- /dev/null +++ b/test/inputs/schema/top-level-integer-map.schema @@ -0,0 +1,6 @@ +{ + "type": "object", + "additionalProperties": { + "type": "integer" + } +} diff --git a/test/inputs/schema/top-level-null-array.1.json b/test/inputs/schema/top-level-null-array.1.json new file mode 100644 index 0000000000..26d218ea60 --- /dev/null +++ b/test/inputs/schema/top-level-null-array.1.json @@ -0,0 +1 @@ +[null, null] diff --git a/test/inputs/schema/top-level-null-array.schema b/test/inputs/schema/top-level-null-array.schema new file mode 100644 index 0000000000..e964719f46 --- /dev/null +++ b/test/inputs/schema/top-level-null-array.schema @@ -0,0 +1,6 @@ +{ + "type": "array", + "items": { + "type": "null" + } +} diff --git a/test/languages.ts b/test/languages.ts index 8f534c31d2..c1047e8205 100644 --- a/test/languages.ts +++ b/test/languages.ts @@ -952,7 +952,12 @@ export const ObjectiveCLanguage: Language = { "blns-object.json", ], skipMiscJSON: false, - skipSchema: ["optional-property.schema"], + skipSchema: [ + "optional-property.schema", + // A top-level array of null checks its items with `id.class`, which + // does not compile. + "top-level-null-array.schema", + ], rendererOptions: { functions: "true" }, quickTestRendererOptions: [], sourceFiles: ["src/language/Objective-C/index.ts"], @@ -1722,6 +1727,7 @@ export const PHPLanguage: Language = { "recursive-union-flattening.schema", // The driver does not support top-level arrays. "issue2680-top-level-array.schema", + "top-level-null-array.schema", ], rendererOptions: {}, quickTestRendererOptions: [],