Skip to content

fix: Wrap nested readonly members #1320

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/curly-mice-call.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"openapi-typescript": patch
---

Wrap nested readonly types in parentheses, allowing for nested immutable arrays
6 changes: 3 additions & 3 deletions packages/openapi-typescript/examples/digital-ocean-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5522,10 +5522,10 @@ export interface external {
* "doadmin"
* ]
*/
db_names?: readonly string[] | null;
db_names?: (readonly string[]) | null;
connection?: external["resources/databases/models/database_connection.yml"];
private_connection?: external["resources/databases/models/database_connection.yml"];
users?: readonly external["resources/databases/models/database_user.yml"][] | null;
users?: (readonly external["resources/databases/models/database_user.yml"][]) | null;
maintenance_window?: external["resources/databases/models/database_maintenance_window.yml"];
/**
* Format: uuid
Expand Down Expand Up @@ -15392,7 +15392,7 @@ export interface external {
* @description An array containing the IDs of the Droplets the volume is attached to. Note that at this time, a volume can only be attached to a single Droplet.
* @example []
*/
droplet_ids?: readonly number[] | null;
droplet_ids?: (readonly number[]) | null;
/**
* @description A human-readable name for the block storage volume. Must be lowercase and be composed only of numbers, letters and "-", up to a limit of 64 characters. The name must begin with a letter.
* @example example
Expand Down
14 changes: 7 additions & 7 deletions packages/openapi-typescript/examples/github-api-next.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10744,7 +10744,7 @@ export interface components {
/** @description The package version that resolve the vulnerability. */
first_patched_version: string | null;
/** @description The functions in the package that are affected by the vulnerability. */
vulnerable_functions: readonly string[] | null;
vulnerable_functions: (readonly string[]) | null;
})[]) | null;
cvss: ({
/** @description The CVSS vector. */
Expand All @@ -10758,10 +10758,10 @@ export interface components {
/** @description The name of the CWE. */
name: string;
}[] | null;
credits: readonly {
credits: (readonly {
user: components["schemas"]["simple-user"];
type: components["schemas"]["security-advisory-credit-types"];
}[] | null;
}[]) | null;
};
/**
* Basic Error
Expand Down Expand Up @@ -14434,20 +14434,20 @@ export interface components {
/** @description The CVSS score. */
score: number | null;
}) | null;
cwes: readonly {
cwes: (readonly {
/** @description The Common Weakness Enumeration (CWE) identifier. */
cwe_id: string;
/** @description The name of the CWE. */
name: string;
}[] | null;
}[]) | null;
/** @description A list of only the CWE IDs. */
cwe_ids: string[] | null;
credits: {
/** @description The username of the user credited. */
login?: string;
type?: components["schemas"]["security-advisory-credit-types"];
}[] | null;
credits_detailed: readonly components["schemas"]["repository-advisory-credit"][] | null;
credits_detailed: (readonly components["schemas"]["repository-advisory-credit"][]) | null;
/** @description A list of users that collaborate on the advisory. */
collaborating_users: components["schemas"]["simple-user"][] | null;
/** @description A list of teams that collaborate on the advisory. */
Expand Down Expand Up @@ -16518,7 +16518,7 @@ export interface components {
*/
analyses_url?: string | null;
/** @description Any errors that ocurred during processing of the delivery. */
errors?: readonly string[] | null;
errors?: (readonly string[]) | null;
};
/**
* CODEOWNERS errors
Expand Down
14 changes: 7 additions & 7 deletions packages/openapi-typescript/examples/github-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8251,7 +8251,7 @@ export interface components {
/** @description The package version that resolve the vulnerability. */
first_patched_version: string | null;
/** @description The functions in the package that are affected by the vulnerability. */
vulnerable_functions: readonly string[] | null;
vulnerable_functions: (readonly string[]) | null;
})[]) | null;
cvss: ({
/** @description The CVSS vector. */
Expand All @@ -8265,10 +8265,10 @@ export interface components {
/** @description The name of the CWE. */
name: string;
}[] | null;
credits: readonly {
credits: (readonly {
user: components["schemas"]["simple-user"];
type: components["schemas"]["security-advisory-credit-types"];
}[] | null;
}[]) | null;
};
/**
* Basic Error
Expand Down Expand Up @@ -13572,20 +13572,20 @@ export interface components {
/** @description The CVSS score. */
score: number | null;
}) | null;
cwes: readonly {
cwes: (readonly {
/** @description The Common Weakness Enumeration (CWE) identifier. */
cwe_id: string;
/** @description The name of the CWE. */
name: string;
}[] | null;
}[]) | null;
/** @description A list of only the CWE IDs. */
cwe_ids: string[] | null;
credits: {
/** @description The username of the user credited. */
login?: string;
type?: components["schemas"]["security-advisory-credit-types"];
}[] | null;
credits_detailed: readonly components["schemas"]["repository-advisory-credit"][] | null;
credits_detailed: (readonly components["schemas"]["repository-advisory-credit"][]) | null;
/** @description A list of users that collaborate on the advisory. */
collaborating_users: components["schemas"]["simple-user"][] | null;
/** @description A list of teams that collaborate on the advisory. */
Expand Down Expand Up @@ -17232,7 +17232,7 @@ export interface components {
*/
analyses_url?: string | null;
/** @description Any errors that ocurred during processing of the delivery. */
errors?: readonly string[] | null;
errors?: (readonly string[]) | null;
};
/**
* CODEOWNERS errors
Expand Down
5 changes: 3 additions & 2 deletions packages/openapi-typescript/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const TILDE_RE = /~/g;
const FS_RE = /\//g;
export const TS_INDEX_RE = /\[("(\\"|[^"])+"|'(\\'|[^'])+')]/g; // splits apart TS indexes (and allows for escaped quotes)
const TS_UNION_INTERSECTION_RE = /[&|]/;
const TS_READONLY_RE = /^readonly\s+/;
const JS_OBJ_KEY = /^(\d+|[A-Za-z_$][A-Za-z0-9_$]*)$/;

/** Walk through any JSON-serializable object */
Expand Down Expand Up @@ -168,9 +169,9 @@ export function encodeRef(ref: string): string {
return ref.replace(TILDE_RE, "~0").replace(FS_RE, "~1");
}

/** if the type has & or | we should parenthesise it for safety */
/** add parenthesis around union, intersection (| and &) and readonly types */
function parenthesise(type: string) {
return TS_UNION_INTERSECTION_RE.test(type) ? `(${type})` : type;
return TS_UNION_INTERSECTION_RE.test(type) || TS_READONLY_RE.test(type) ? `(${type})` : type;
}

/** T[] */
Expand Down
22 changes: 20 additions & 2 deletions packages/openapi-typescript/test/schema-object.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -790,11 +790,29 @@ describe("Schema Object", () => {
ctx: { ...options.ctx, immutableTypes: true },
});
expect(generated).toBe(`{
readonly array?: readonly {
readonly array?: (readonly {
[key: string]: unknown;
}[] | null;
}[]) | null;
}`);
});

test("readonly arrays", () => {
const schema: SchemaObject = {
type: "array",
items: {
type: "array",
items: {
type: "string",
},
},
};

const generated = transformSchemaObject(schema, {
...options,
ctx: { ...options.ctx, immutableTypes: true },
});
expect(generated).toBe(`readonly (readonly string[])[]`);
});
});
});

Expand Down