Skip to content

Commit 3cf78b9

Browse files
fix: Wrap nested readonly members (#1320)
1 parent f6bfdd7 commit 3cf78b9

File tree

6 files changed

+45
-21
lines changed

6 files changed

+45
-21
lines changed

.changeset/curly-mice-call.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"openapi-typescript": patch
3+
---
4+
5+
Wrap nested readonly types in parentheses, allowing for nested immutable arrays

packages/openapi-typescript/examples/digital-ocean-api.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -5522,10 +5522,10 @@ export interface external {
55225522
* "doadmin"
55235523
* ]
55245524
*/
5525-
db_names?: readonly string[] | null;
5525+
db_names?: (readonly string[]) | null;
55265526
connection?: external["resources/databases/models/database_connection.yml"];
55275527
private_connection?: external["resources/databases/models/database_connection.yml"];
5528-
users?: readonly external["resources/databases/models/database_user.yml"][] | null;
5528+
users?: (readonly external["resources/databases/models/database_user.yml"][]) | null;
55295529
maintenance_window?: external["resources/databases/models/database_maintenance_window.yml"];
55305530
/**
55315531
* Format: uuid
@@ -15392,7 +15392,7 @@ export interface external {
1539215392
* @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.
1539315393
* @example []
1539415394
*/
15395-
droplet_ids?: readonly number[] | null;
15395+
droplet_ids?: (readonly number[]) | null;
1539615396
/**
1539715397
* @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.
1539815398
* @example example

packages/openapi-typescript/examples/github-api-next.ts

+7-7
Original file line numberDiff line numberDiff line change
@@ -10744,7 +10744,7 @@ export interface components {
1074410744
/** @description The package version that resolve the vulnerability. */
1074510745
first_patched_version: string | null;
1074610746
/** @description The functions in the package that are affected by the vulnerability. */
10747-
vulnerable_functions: readonly string[] | null;
10747+
vulnerable_functions: (readonly string[]) | null;
1074810748
})[]) | null;
1074910749
cvss: ({
1075010750
/** @description The CVSS vector. */
@@ -10758,10 +10758,10 @@ export interface components {
1075810758
/** @description The name of the CWE. */
1075910759
name: string;
1076010760
}[] | null;
10761-
credits: readonly {
10761+
credits: (readonly {
1076210762
user: components["schemas"]["simple-user"];
1076310763
type: components["schemas"]["security-advisory-credit-types"];
10764-
}[] | null;
10764+
}[]) | null;
1076510765
};
1076610766
/**
1076710767
* Basic Error
@@ -14434,20 +14434,20 @@ export interface components {
1443414434
/** @description The CVSS score. */
1443514435
score: number | null;
1443614436
}) | null;
14437-
cwes: readonly {
14437+
cwes: (readonly {
1443814438
/** @description The Common Weakness Enumeration (CWE) identifier. */
1443914439
cwe_id: string;
1444014440
/** @description The name of the CWE. */
1444114441
name: string;
14442-
}[] | null;
14442+
}[]) | null;
1444314443
/** @description A list of only the CWE IDs. */
1444414444
cwe_ids: string[] | null;
1444514445
credits: {
1444614446
/** @description The username of the user credited. */
1444714447
login?: string;
1444814448
type?: components["schemas"]["security-advisory-credit-types"];
1444914449
}[] | null;
14450-
credits_detailed: readonly components["schemas"]["repository-advisory-credit"][] | null;
14450+
credits_detailed: (readonly components["schemas"]["repository-advisory-credit"][]) | null;
1445114451
/** @description A list of users that collaborate on the advisory. */
1445214452
collaborating_users: components["schemas"]["simple-user"][] | null;
1445314453
/** @description A list of teams that collaborate on the advisory. */
@@ -16518,7 +16518,7 @@ export interface components {
1651816518
*/
1651916519
analyses_url?: string | null;
1652016520
/** @description Any errors that ocurred during processing of the delivery. */
16521-
errors?: readonly string[] | null;
16521+
errors?: (readonly string[]) | null;
1652216522
};
1652316523
/**
1652416524
* CODEOWNERS errors

packages/openapi-typescript/examples/github-api.ts

+7-7
Original file line numberDiff line numberDiff line change
@@ -8251,7 +8251,7 @@ export interface components {
82518251
/** @description The package version that resolve the vulnerability. */
82528252
first_patched_version: string | null;
82538253
/** @description The functions in the package that are affected by the vulnerability. */
8254-
vulnerable_functions: readonly string[] | null;
8254+
vulnerable_functions: (readonly string[]) | null;
82558255
})[]) | null;
82568256
cvss: ({
82578257
/** @description The CVSS vector. */
@@ -8265,10 +8265,10 @@ export interface components {
82658265
/** @description The name of the CWE. */
82668266
name: string;
82678267
}[] | null;
8268-
credits: readonly {
8268+
credits: (readonly {
82698269
user: components["schemas"]["simple-user"];
82708270
type: components["schemas"]["security-advisory-credit-types"];
8271-
}[] | null;
8271+
}[]) | null;
82728272
};
82738273
/**
82748274
* Basic Error
@@ -13572,20 +13572,20 @@ export interface components {
1357213572
/** @description The CVSS score. */
1357313573
score: number | null;
1357413574
}) | null;
13575-
cwes: readonly {
13575+
cwes: (readonly {
1357613576
/** @description The Common Weakness Enumeration (CWE) identifier. */
1357713577
cwe_id: string;
1357813578
/** @description The name of the CWE. */
1357913579
name: string;
13580-
}[] | null;
13580+
}[]) | null;
1358113581
/** @description A list of only the CWE IDs. */
1358213582
cwe_ids: string[] | null;
1358313583
credits: {
1358413584
/** @description The username of the user credited. */
1358513585
login?: string;
1358613586
type?: components["schemas"]["security-advisory-credit-types"];
1358713587
}[] | null;
13588-
credits_detailed: readonly components["schemas"]["repository-advisory-credit"][] | null;
13588+
credits_detailed: (readonly components["schemas"]["repository-advisory-credit"][]) | null;
1358913589
/** @description A list of users that collaborate on the advisory. */
1359013590
collaborating_users: components["schemas"]["simple-user"][] | null;
1359113591
/** @description A list of teams that collaborate on the advisory. */
@@ -17232,7 +17232,7 @@ export interface components {
1723217232
*/
1723317233
analyses_url?: string | null;
1723417234
/** @description Any errors that ocurred during processing of the delivery. */
17235-
errors?: readonly string[] | null;
17235+
errors?: (readonly string[]) | null;
1723617236
};
1723717237
/**
1723817238
* CODEOWNERS errors

packages/openapi-typescript/src/utils.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ const TILDE_RE = /~/g;
3232
const FS_RE = /\//g;
3333
export const TS_INDEX_RE = /\[("(\\"|[^"])+"|'(\\'|[^'])+')]/g; // splits apart TS indexes (and allows for escaped quotes)
3434
const TS_UNION_INTERSECTION_RE = /[&|]/;
35+
const TS_READONLY_RE = /^readonly\s+/;
3536
const JS_OBJ_KEY = /^(\d+|[A-Za-z_$][A-Za-z0-9_$]*)$/;
3637

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

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

176177
/** T[] */

packages/openapi-typescript/test/schema-object.test.ts

+20-2
Original file line numberDiff line numberDiff line change
@@ -790,11 +790,29 @@ describe("Schema Object", () => {
790790
ctx: { ...options.ctx, immutableTypes: true },
791791
});
792792
expect(generated).toBe(`{
793-
readonly array?: readonly {
793+
readonly array?: (readonly {
794794
[key: string]: unknown;
795-
}[] | null;
795+
}[]) | null;
796796
}`);
797797
});
798+
799+
test("readonly arrays", () => {
800+
const schema: SchemaObject = {
801+
type: "array",
802+
items: {
803+
type: "array",
804+
items: {
805+
type: "string",
806+
},
807+
},
808+
};
809+
810+
const generated = transformSchemaObject(schema, {
811+
...options,
812+
ctx: { ...options.ctx, immutableTypes: true },
813+
});
814+
expect(generated).toBe(`readonly (readonly string[])[]`);
815+
});
798816
});
799817
});
800818

0 commit comments

Comments
 (0)