ajsc 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/JSONSchemaConverter.js +370 -0
- package/dist/JSONSchemaConverter.js.map +1 -0
- package/dist/JSONSchemaConverter.test.js +302 -0
- package/dist/JSONSchemaConverter.test.js.map +1 -0
- package/dist/TypescriptBaseConverter.js +131 -0
- package/dist/TypescriptBaseConverter.js.map +1 -0
- package/dist/TypescriptConverter.js +107 -0
- package/dist/TypescriptConverter.js.map +1 -0
- package/dist/TypescriptConverter.test.js +199 -0
- package/dist/TypescriptConverter.test.js.map +1 -0
- package/dist/TypescriptProcedureConverter.js +118 -0
- package/dist/TypescriptProcedureConverter.js.map +1 -0
- package/dist/TypescriptProceduresConverter.test.js +948 -0
- package/dist/TypescriptProceduresConverter.test.js.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/path-utils.js +78 -0
- package/dist/utils/path-utils.js.map +1 -0
- package/dist/utils/path-utils.test.js +92 -0
- package/dist/utils/path-utils.test.js.map +1 -0
- package/dist/utils/to-pascal-case.js +11 -0
- package/dist/utils/to-pascal-case.js.map +1 -0
- package/package.json +56 -0
- package/src/JSONSchemaConverter.test.ts +342 -0
- package/src/JSONSchemaConverter.ts +459 -0
- package/src/TypescriptBaseConverter.ts +161 -0
- package/src/TypescriptConverter.test.ts +264 -0
- package/src/TypescriptConverter.ts +161 -0
- package/src/TypescriptProcedureConverter.ts +160 -0
- package/src/TypescriptProceduresConverter.test.ts +952 -0
- package/src/types.ts +101 -0
- package/src/utils/path-utils.test.ts +102 -0
- package/src/utils/path-utils.ts +89 -0
- package/src/utils/to-pascal-case.ts +10 -0
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { TypescriptConverter } from "./TypescriptConverter.js";
|
|
3
|
+
|
|
4
|
+
describe("TypeScriptLanguagePlugin", () => {
|
|
5
|
+
it("should convert strings", () => {
|
|
6
|
+
expect(new TypescriptConverter({ type: "string" }).code).toMatch("string");
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it("should convert numbers", () => {
|
|
10
|
+
expect(new TypescriptConverter({ type: "number" }).code).toMatch("number");
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("should convert booleans", () => {
|
|
14
|
+
expect(new TypescriptConverter({ type: "boolean" }).code).toMatch(
|
|
15
|
+
"boolean",
|
|
16
|
+
);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("should convert null", () => {
|
|
20
|
+
expect(new TypescriptConverter({ type: "null" }).code).toMatch("null");
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("should convert literals", () => {
|
|
24
|
+
expect(new TypescriptConverter({ const: "fixedValue" }).code).toMatch(
|
|
25
|
+
"fixedValue",
|
|
26
|
+
);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("should convert enums", () => {
|
|
30
|
+
expect(
|
|
31
|
+
new TypescriptConverter({
|
|
32
|
+
type: "string",
|
|
33
|
+
enum: ["value1", "value2", "value3"],
|
|
34
|
+
}).code,
|
|
35
|
+
).toMatch(`"value1" | "value2" | "value3"`);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("should convert unions", () => {
|
|
39
|
+
expect(
|
|
40
|
+
new TypescriptConverter({ type: ["string", "number"] }).code,
|
|
41
|
+
).toMatch(`string | number`);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("should convert intersections", () => {
|
|
45
|
+
expect(
|
|
46
|
+
new TypescriptConverter({
|
|
47
|
+
allOf: [{ type: "string" }, { type: "number" }],
|
|
48
|
+
}).code,
|
|
49
|
+
).toMatch(`string & number`);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("should convert arrays", () => {
|
|
53
|
+
expect(
|
|
54
|
+
new TypescriptConverter({
|
|
55
|
+
type: "array",
|
|
56
|
+
items: { type: "string" },
|
|
57
|
+
}).code,
|
|
58
|
+
).toMatch(`Array<string>`);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("should convert objects", () => {
|
|
62
|
+
expect(
|
|
63
|
+
new TypescriptConverter({
|
|
64
|
+
type: "object",
|
|
65
|
+
properties: {
|
|
66
|
+
title: { type: "string" },
|
|
67
|
+
year: { type: "number" },
|
|
68
|
+
},
|
|
69
|
+
required: ["title"],
|
|
70
|
+
}).code,
|
|
71
|
+
).toMatch(`{ title: string; year?: number; }`);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("should convert an array of objects", () => {
|
|
75
|
+
expect(
|
|
76
|
+
new TypescriptConverter(
|
|
77
|
+
{
|
|
78
|
+
type: "array",
|
|
79
|
+
items: {
|
|
80
|
+
anyOf: [
|
|
81
|
+
{
|
|
82
|
+
type: "object",
|
|
83
|
+
properties: {
|
|
84
|
+
year: { type: "number" },
|
|
85
|
+
},
|
|
86
|
+
required: ["year"],
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
type: "object",
|
|
90
|
+
properties: {
|
|
91
|
+
title: { type: "string" },
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
],
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
inlineTypes: true,
|
|
99
|
+
},
|
|
100
|
+
).code,
|
|
101
|
+
).toMatch(`Array<{ year: number; } | { title?: string; }>`);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("should convert a simple JSON schema to Typescript", () => {
|
|
105
|
+
expect(
|
|
106
|
+
new TypescriptConverter(
|
|
107
|
+
{
|
|
108
|
+
type: "object",
|
|
109
|
+
properties: {
|
|
110
|
+
name: { type: "string" },
|
|
111
|
+
age: { type: "number" },
|
|
112
|
+
contacts: {
|
|
113
|
+
type: "array",
|
|
114
|
+
items: {
|
|
115
|
+
type: "object",
|
|
116
|
+
properties: {
|
|
117
|
+
email: { type: "string" },
|
|
118
|
+
},
|
|
119
|
+
required: ["email"],
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
profile: {
|
|
123
|
+
type: "object",
|
|
124
|
+
properties: {
|
|
125
|
+
email: { type: "string" },
|
|
126
|
+
},
|
|
127
|
+
required: ["email"],
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
required: ["name", "age"],
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
inlineTypes: true,
|
|
134
|
+
},
|
|
135
|
+
).code.replace(/\s/g, ""),
|
|
136
|
+
).toMatch(
|
|
137
|
+
`{
|
|
138
|
+
name: string;
|
|
139
|
+
age: number;
|
|
140
|
+
contacts?: Array<{ email: string; }>;
|
|
141
|
+
profile?: { email: string; };
|
|
142
|
+
}`.replace(/\s/g, ""),
|
|
143
|
+
);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it("should convert a named JSON schema top-level object to Typescript", () => {
|
|
147
|
+
expect(
|
|
148
|
+
new TypescriptConverter(
|
|
149
|
+
{
|
|
150
|
+
$schema: "http://json-schema.org/draft-07/schema#",
|
|
151
|
+
title: "Person",
|
|
152
|
+
type: "object",
|
|
153
|
+
properties: {
|
|
154
|
+
name: { type: "string" },
|
|
155
|
+
age: { type: "number" },
|
|
156
|
+
contacts: {
|
|
157
|
+
type: "array",
|
|
158
|
+
items: {
|
|
159
|
+
type: "object",
|
|
160
|
+
properties: {
|
|
161
|
+
email: { type: "string" },
|
|
162
|
+
},
|
|
163
|
+
required: ["email"],
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
profile: {
|
|
167
|
+
type: "object",
|
|
168
|
+
properties: {
|
|
169
|
+
email: { type: "string" },
|
|
170
|
+
},
|
|
171
|
+
required: ["email"],
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
required: ["name", "age"],
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
inlineTypes: true,
|
|
178
|
+
},
|
|
179
|
+
).code.replace(/\s/g, ""),
|
|
180
|
+
).toEqual(
|
|
181
|
+
`{ name: string; age: number; contacts?: Array<{ email: string; }>; profile?: { email: string; }; }`.replace(
|
|
182
|
+
/\s/g,
|
|
183
|
+
"",
|
|
184
|
+
),
|
|
185
|
+
);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it("should convert a named JSON schema top-level array to Typescript", () => {
|
|
189
|
+
expect(
|
|
190
|
+
new TypescriptConverter(
|
|
191
|
+
{
|
|
192
|
+
$schema: "http://json-schema.org/draft-07/schema#",
|
|
193
|
+
title: "People",
|
|
194
|
+
type: "array",
|
|
195
|
+
items: {
|
|
196
|
+
type: "object",
|
|
197
|
+
properties: {
|
|
198
|
+
name: { type: "string" },
|
|
199
|
+
age: { type: "number" },
|
|
200
|
+
},
|
|
201
|
+
required: ["name", "age"],
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
inlineTypes: true,
|
|
206
|
+
},
|
|
207
|
+
).code.replace(/\s/g, ""),
|
|
208
|
+
).toEqual(`Array<{ name: string; age: number; }>`.replace(/\s/g, ""));
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it("should convert JSON schema with top-level re-used objects in Typescript", async () => {
|
|
212
|
+
expect(
|
|
213
|
+
new TypescriptConverter(
|
|
214
|
+
{
|
|
215
|
+
$schema: "http://json-schema.org/draft-07/schema#",
|
|
216
|
+
title: "Person",
|
|
217
|
+
type: "object",
|
|
218
|
+
properties: {
|
|
219
|
+
contacts: {
|
|
220
|
+
type: "array",
|
|
221
|
+
items: {
|
|
222
|
+
type: "object",
|
|
223
|
+
properties: {
|
|
224
|
+
email: { type: "string" },
|
|
225
|
+
},
|
|
226
|
+
required: ["email"],
|
|
227
|
+
},
|
|
228
|
+
},
|
|
229
|
+
profile: {
|
|
230
|
+
type: "object",
|
|
231
|
+
properties: {
|
|
232
|
+
email: { type: "string" },
|
|
233
|
+
},
|
|
234
|
+
required: ["email"],
|
|
235
|
+
},
|
|
236
|
+
contact: {
|
|
237
|
+
type: "object",
|
|
238
|
+
properties: {
|
|
239
|
+
email: { type: "string" },
|
|
240
|
+
},
|
|
241
|
+
required: ["email"],
|
|
242
|
+
},
|
|
243
|
+
email: {
|
|
244
|
+
type: "object",
|
|
245
|
+
properties: {
|
|
246
|
+
email: { type: "string" },
|
|
247
|
+
},
|
|
248
|
+
required: ["email"],
|
|
249
|
+
},
|
|
250
|
+
},
|
|
251
|
+
required: ["name", "age"],
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
inlineTypes: true,
|
|
255
|
+
},
|
|
256
|
+
).code.replace(/\s/g, ""),
|
|
257
|
+
).toEqual(
|
|
258
|
+
`{contacts?:Array<{email:string;}>;profile?:{email:string;};contact?:{email:string;};email?:{email:string;};}`.replace(
|
|
259
|
+
/\s/g,
|
|
260
|
+
"",
|
|
261
|
+
),
|
|
262
|
+
);
|
|
263
|
+
});
|
|
264
|
+
});
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { IRNode, LanguagePlugin } from "./types.js";
|
|
2
|
+
import {
|
|
3
|
+
RefTypeName,
|
|
4
|
+
RefTypes,
|
|
5
|
+
TypescriptBaseConverter,
|
|
6
|
+
} from "./TypescriptBaseConverter.js";
|
|
7
|
+
import { JSONSchema7Definition } from "json-schema";
|
|
8
|
+
import { JSONSchemaConverter } from "./JSONSchemaConverter.js";
|
|
9
|
+
import { PathUtils } from "./utils/path-utils.js";
|
|
10
|
+
import { toPascalCase } from "./utils/to-pascal-case.js";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A TypeScript language converter plugin.
|
|
14
|
+
*/
|
|
15
|
+
export class TypescriptConverter
|
|
16
|
+
extends TypescriptBaseConverter
|
|
17
|
+
implements LanguagePlugin
|
|
18
|
+
{
|
|
19
|
+
public readonly language = "typescript";
|
|
20
|
+
|
|
21
|
+
private ir: JSONSchemaConverter;
|
|
22
|
+
private refTypes: RefTypes = [];
|
|
23
|
+
|
|
24
|
+
readonly code: string;
|
|
25
|
+
|
|
26
|
+
constructor(
|
|
27
|
+
schema: JSONSchema7Definition,
|
|
28
|
+
opts?: {
|
|
29
|
+
/**
|
|
30
|
+
* If true, referenced types will not be created for objects and instead
|
|
31
|
+
* the object type will be inlined.
|
|
32
|
+
*/
|
|
33
|
+
inlineTypes?: boolean;
|
|
34
|
+
},
|
|
35
|
+
) {
|
|
36
|
+
super();
|
|
37
|
+
|
|
38
|
+
this.ir = new JSONSchemaConverter(schema);
|
|
39
|
+
|
|
40
|
+
const code = this.generateType(this.ir.irNode, {
|
|
41
|
+
getReferencedType: opts?.inlineTypes
|
|
42
|
+
? () => undefined
|
|
43
|
+
: this.getReferencedType.bind(this),
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const rootName = this.ir.irNode.name ? this.ir.irNode.name : "Root";
|
|
47
|
+
|
|
48
|
+
this.code = `${this.refTypes
|
|
49
|
+
.map(([sig, name, { code }]) => `export type ${name} = ${code}`)
|
|
50
|
+
.join("\n")}
|
|
51
|
+
|
|
52
|
+
${code}`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
private getUniqueRefTypeName(
|
|
56
|
+
signature: string,
|
|
57
|
+
nodePath: string,
|
|
58
|
+
): RefTypeName {
|
|
59
|
+
const path = PathUtils.parsePath(nodePath);
|
|
60
|
+
|
|
61
|
+
// if the path ends in "0" it's an array item
|
|
62
|
+
const isArrayItem = nodePath.split(".").slice(-1)[0] === "0";
|
|
63
|
+
|
|
64
|
+
const postFixes = [
|
|
65
|
+
"Type",
|
|
66
|
+
"Element",
|
|
67
|
+
"Schema",
|
|
68
|
+
"Object",
|
|
69
|
+
"Shape",
|
|
70
|
+
"1",
|
|
71
|
+
"2",
|
|
72
|
+
"3",
|
|
73
|
+
"4",
|
|
74
|
+
"5",
|
|
75
|
+
"6",
|
|
76
|
+
];
|
|
77
|
+
|
|
78
|
+
if (isArrayItem) {
|
|
79
|
+
postFixes.unshift("Item");
|
|
80
|
+
} else {
|
|
81
|
+
postFixes.unshift("");
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
let pathsSegmentsToInclude = 1;
|
|
85
|
+
let name = "";
|
|
86
|
+
let postFixIndexToTry = 0;
|
|
87
|
+
|
|
88
|
+
while (!name) {
|
|
89
|
+
const proposedName =
|
|
90
|
+
path.slice(-pathsSegmentsToInclude).map(toPascalCase).join("") +
|
|
91
|
+
postFixes[postFixIndexToTry];
|
|
92
|
+
|
|
93
|
+
const foundSignatureMatch = this.refTypes.find(([sig, name]) => {
|
|
94
|
+
return sig === signature && name === proposedName;
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
if (foundSignatureMatch) {
|
|
98
|
+
return foundSignatureMatch[1];
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const nameAlreadyUsed = this.refTypes.find(([_, name]) => {
|
|
102
|
+
return name === proposedName;
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
if (nameAlreadyUsed) {
|
|
106
|
+
pathsSegmentsToInclude++;
|
|
107
|
+
|
|
108
|
+
if (pathsSegmentsToInclude >= path.length) {
|
|
109
|
+
// we're out of unique paths, increment a postfix and start the loop again
|
|
110
|
+
postFixIndexToTry++;
|
|
111
|
+
pathsSegmentsToInclude = 1;
|
|
112
|
+
|
|
113
|
+
// absolute fallback that should never/very-rarely happen
|
|
114
|
+
if (postFixIndexToTry === postFixes.length) {
|
|
115
|
+
name = proposedName + Math.floor(Math.random() * 1000);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
} else {
|
|
119
|
+
name = proposedName;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return name;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
protected getReferencedType(ir: IRNode): string | undefined {
|
|
127
|
+
const signature = ir.signature;
|
|
128
|
+
|
|
129
|
+
if (!signature) {
|
|
130
|
+
return undefined;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const name = this.getUniqueRefTypeName(signature, ir.path);
|
|
134
|
+
|
|
135
|
+
// account for recursion, the ref type could have already been created
|
|
136
|
+
if (
|
|
137
|
+
this.refTypes.find(([sig, _name]) => sig === signature && _name === name)
|
|
138
|
+
) {
|
|
139
|
+
return name;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// push the sig/name pair to the refTypes array for recursion to avoid duplicates
|
|
143
|
+
this.refTypes.push([
|
|
144
|
+
signature,
|
|
145
|
+
name,
|
|
146
|
+
{
|
|
147
|
+
code: "",
|
|
148
|
+
},
|
|
149
|
+
]);
|
|
150
|
+
|
|
151
|
+
const code = this.generateObjectType(ir, {
|
|
152
|
+
getReferencedType: this.getReferencedType.bind(this),
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
this.refTypes.find(
|
|
156
|
+
([sig, _name]) => sig === signature && _name === name,
|
|
157
|
+
)![2].code = code;
|
|
158
|
+
|
|
159
|
+
return name;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { IRNode, SignatureOccurrenceValue } from "./types.js";
|
|
2
|
+
import { JSONSchema7Definition } from "json-schema";
|
|
3
|
+
import { JSONSchemaConverter } from "./JSONSchemaConverter.js";
|
|
4
|
+
import {RefTypeName, RefTypes, TypescriptBaseConverter} from "./TypescriptBaseConverter.js";
|
|
5
|
+
import { PathUtils } from "./utils/path-utils.js";
|
|
6
|
+
import { toPascalCase } from "./utils/to-pascal-case.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* A TypeScript language converter plugin that implements namespace scoping.
|
|
10
|
+
*
|
|
11
|
+
* Referenced/Shared typings are moved to the top level of the namespace scope
|
|
12
|
+
* that is created for each IR node.
|
|
13
|
+
*/
|
|
14
|
+
export class TypescriptProcedureConverter extends TypescriptBaseConverter {
|
|
15
|
+
private ir: { args: JSONSchemaConverter; data: JSONSchemaConverter };
|
|
16
|
+
private refTypes: RefTypes = [];
|
|
17
|
+
|
|
18
|
+
readonly code: string;
|
|
19
|
+
|
|
20
|
+
constructor(
|
|
21
|
+
scopeName: string,
|
|
22
|
+
rpcJsonSchemas: {
|
|
23
|
+
args: JSONSchema7Definition;
|
|
24
|
+
data: JSONSchema7Definition;
|
|
25
|
+
},
|
|
26
|
+
) {
|
|
27
|
+
super();
|
|
28
|
+
|
|
29
|
+
this.ir = {
|
|
30
|
+
args: new JSONSchemaConverter(rpcJsonSchemas.args),
|
|
31
|
+
data: new JSONSchemaConverter(rpcJsonSchemas.data),
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const argsCode = this.generateObjectType(this.ir.args.irNode, {
|
|
35
|
+
getReferencedType: this.getReferencedType.bind(this),
|
|
36
|
+
});
|
|
37
|
+
const dataCode = this.generateObjectType(this.ir.data.irNode, {
|
|
38
|
+
getReferencedType: this.getReferencedType.bind(this),
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
this.code = `export namespace ${scopeName} {
|
|
42
|
+
export interface Args ${argsCode}
|
|
43
|
+
|
|
44
|
+
export interface Data ${dataCode}
|
|
45
|
+
|
|
46
|
+
${this.refTypes
|
|
47
|
+
.map(([sig, name, { code }]) => `export type ${name} = ${code}`)
|
|
48
|
+
.join("\n")}
|
|
49
|
+
}`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
private getUniqueRefTypeName(
|
|
53
|
+
signature: string,
|
|
54
|
+
nodePath: string,
|
|
55
|
+
): RefTypeName {
|
|
56
|
+
const path = PathUtils.parsePath(nodePath);
|
|
57
|
+
|
|
58
|
+
// if the path ends in "0" it's an array item
|
|
59
|
+
const isArrayItem = nodePath.split(".").slice(-1)[0] === "0";
|
|
60
|
+
|
|
61
|
+
const postFixes = [
|
|
62
|
+
"Type",
|
|
63
|
+
"Element",
|
|
64
|
+
"Schema",
|
|
65
|
+
"Object",
|
|
66
|
+
"Shape",
|
|
67
|
+
"1",
|
|
68
|
+
"2",
|
|
69
|
+
"3",
|
|
70
|
+
"4",
|
|
71
|
+
"5",
|
|
72
|
+
"6",
|
|
73
|
+
];
|
|
74
|
+
|
|
75
|
+
if (isArrayItem) {
|
|
76
|
+
postFixes.unshift("Item");
|
|
77
|
+
// remove pluralization from the last segment
|
|
78
|
+
path[path.length - 1] = path[path.length - 1].replace(/s$/, "");
|
|
79
|
+
} else {
|
|
80
|
+
postFixes.unshift("");
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
let pathsSegmentsToInclude = 1;
|
|
84
|
+
let name = "";
|
|
85
|
+
let postFixIndexToTry = 0;
|
|
86
|
+
|
|
87
|
+
while (!name) {
|
|
88
|
+
const proposedName =
|
|
89
|
+
path.slice(-pathsSegmentsToInclude).map(toPascalCase).join("") +
|
|
90
|
+
postFixes[postFixIndexToTry];
|
|
91
|
+
|
|
92
|
+
const foundSignatureMatch = this.refTypes.find(([sig, name]) => {
|
|
93
|
+
return sig === signature && name === proposedName;
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
if (foundSignatureMatch) {
|
|
97
|
+
return foundSignatureMatch[1];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const nameAlreadyUsed = this.refTypes.find(([_, name]) => {
|
|
101
|
+
return name === proposedName;
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
if (nameAlreadyUsed) {
|
|
105
|
+
pathsSegmentsToInclude++;
|
|
106
|
+
|
|
107
|
+
if (pathsSegmentsToInclude >= path.length) {
|
|
108
|
+
// we're out of unique paths, increment a postfix and start the loop again
|
|
109
|
+
postFixIndexToTry++;
|
|
110
|
+
pathsSegmentsToInclude = 1;
|
|
111
|
+
|
|
112
|
+
// absolute fallback that should never/very-rarely happen
|
|
113
|
+
if (postFixIndexToTry === postFixes.length) {
|
|
114
|
+
name = proposedName + Math.floor(Math.random() * 1000);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
} else {
|
|
118
|
+
name = proposedName;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return name;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
protected getReferencedType(ir: IRNode): string | undefined {
|
|
126
|
+
const signature = ir.signature;
|
|
127
|
+
|
|
128
|
+
if (!signature) {
|
|
129
|
+
return undefined;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const name = this.getUniqueRefTypeName(signature, ir.path);
|
|
133
|
+
|
|
134
|
+
// account for recursion, the ref type could have already been created
|
|
135
|
+
if (
|
|
136
|
+
this.refTypes.find(([sig, _name]) => sig === signature && _name === name)
|
|
137
|
+
) {
|
|
138
|
+
return name;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// push the sig/name pair to the refTypes array for recursion to avoid duplicates
|
|
142
|
+
this.refTypes.push([
|
|
143
|
+
signature,
|
|
144
|
+
name,
|
|
145
|
+
{
|
|
146
|
+
code: "",
|
|
147
|
+
},
|
|
148
|
+
]);
|
|
149
|
+
|
|
150
|
+
const code = this.generateObjectType(ir, {
|
|
151
|
+
getReferencedType: this.getReferencedType.bind(this),
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
this.refTypes.find(
|
|
155
|
+
([sig, _name]) => sig === signature && _name === name,
|
|
156
|
+
)![2].code = code;
|
|
157
|
+
|
|
158
|
+
return name;
|
|
159
|
+
}
|
|
160
|
+
}
|