Skip to content

Commit 8be45c5

Browse files
committed
ref: inline generic-info computation in TS writer
Drop the standalone computeGenericInfo helper and the GenericInfo type in favor of inlining the (small) logic directly into generateType. The helper duplicated the existing typeFamilyFields detection that already worked; the genuinely new bit is just the nested-type pass-through. generateType now returns the parent's GenericParam[] (only paramList is needed by callers — fieldMap and nestedArgsByField are local concerns), and generateNestedTypes threads a Record<string, GenericParam[]> back to the parent. Net change vs main: +61/-32 (was +81/-36). No behavior change.
1 parent a9cee6f commit 8be45c5

1 file changed

Lines changed: 62 additions & 78 deletions

File tree

src/api/writer-generator/typescript/writer.ts

Lines changed: 62 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -42,64 +42,7 @@ export const resolveTsAssets = (fn: string) => {
4242
return Path.resolve(__dirname, "../../../..", "assets", "api", "writer-generator", "typescript", fn);
4343
};
4444

45-
export type GenericInfo = {
46-
paramList: { name: string; constraint: string }[];
47-
/** fieldName (ts-name) -> generic param name, used by resolveFieldTsType to substitute the field's type */
48-
fieldMap: Record<string, string>;
49-
/** fieldName (ts-name) -> generic args suffix to append (e.g. "<T>") when the field references a generic nested type */
50-
nestedArgsByField: Record<string, string>;
51-
};
52-
53-
const computeGenericInfo = (
54-
tsIndex: TypeSchemaIndex,
55-
schema: SpecializationTypeSchema | NestedTypeSchema,
56-
nestedGenericInfos: Record<string, GenericInfo>,
57-
): GenericInfo => {
58-
const typeFamilyFields: { fieldName: string; familyTypeName: string }[] = [];
59-
const nestedFields: { fieldName: string; nestedName: string; info: GenericInfo }[] = [];
60-
for (const [fieldName, field] of Object.entries(schema.fields ?? {})) {
61-
if (isChoiceDeclarationField(field) || !field.type) continue;
62-
const tsName = tsFieldName(fieldName);
63-
if (isNestedIdentifier(field.type)) {
64-
const nestedInfo = nestedGenericInfos[field.type.name];
65-
if (nestedInfo && nestedInfo.paramList.length > 0) {
66-
nestedFields.push({ fieldName: tsName, nestedName: field.type.name, info: nestedInfo });
67-
}
68-
continue;
69-
}
70-
const fieldTypeSchema = tsIndex.resolveType(field.type);
71-
if (isSpecializationTypeSchema(fieldTypeSchema) && (fieldTypeSchema.typeFamily?.resources?.length ?? 0) > 0) {
72-
typeFamilyFields.push({ fieldName: tsName, familyTypeName: field.type.name });
73-
}
74-
}
75-
76-
const fieldMap: Record<string, string> = {};
77-
const nestedArgsByField: Record<string, string> = {};
78-
const paramList: { name: string; constraint: string }[] = [];
79-
const [first] = typeFamilyFields;
80-
if (typeFamilyFields.length === 1 && nestedFields.length === 0 && first) {
81-
fieldMap[first.fieldName] = "T";
82-
paramList.push({ name: "T", constraint: first.familyTypeName });
83-
} else {
84-
for (const tf of typeFamilyFields) {
85-
const paramName = `T${uppercaseFirstLetter(tf.fieldName)}`;
86-
fieldMap[tf.fieldName] = paramName;
87-
paramList.push({ name: paramName, constraint: tf.familyTypeName });
88-
}
89-
}
90-
// Inherit nested type's generic params by reusing the same parameter names.
91-
// Only propagate when the parent doesn't already define a param with the same name/constraint.
92-
for (const nf of nestedFields) {
93-
const passThrough: string[] = [];
94-
for (const np of nf.info.paramList) {
95-
const existing = paramList.find((p) => p.name === np.name);
96-
if (!existing) paramList.push({ name: np.name, constraint: np.constraint });
97-
passThrough.push(np.name);
98-
}
99-
nestedArgsByField[nf.fieldName] = `<${passThrough.join(", ")}>`;
100-
}
101-
return { paramList, fieldMap, nestedArgsByField };
102-
};
45+
type GenericParam = { name: string; constraint: string };
10346

10447
export type TypeScriptOptions = {
10548
lineWidth?: number;
@@ -264,23 +207,64 @@ export class TypeScript extends Writer<TypeScriptOptions> {
264207
tsIndex: TypeSchemaIndex,
265208
schema: SpecializationTypeSchema | NestedTypeSchema,
266209
isFamilyType?: (ref: TypeIdentifier) => boolean,
267-
nestedGenericInfos: Record<string, GenericInfo> = {},
268-
): GenericInfo {
210+
nestedGenericParams: Record<string, GenericParam[]> = {},
211+
): GenericParam[] {
269212
let name: string;
270213
// Generic types: Reference, Coding, CodeableConcept
271214
const genericTypes = ["Reference", "Coding", "CodeableConcept"];
272-
if (genericTypes.includes(schema.identifier.name)) {
215+
const isHardcodedGeneric = genericTypes.includes(schema.identifier.name);
216+
if (isHardcodedGeneric) {
273217
name = `${schema.identifier.name}<T extends string = string>`;
274218
} else {
275219
name = tsResourceName(schema.identifier);
276220
}
277221

278-
const isHardcodedGeneric = genericTypes.includes(schema.identifier.name);
279-
const genericInfo: GenericInfo = isHardcodedGeneric
280-
? { paramList: [], fieldMap: {}, nestedArgsByField: {} }
281-
: computeGenericInfo(tsIndex, schema, nestedGenericInfos);
282-
if (!isHardcodedGeneric && genericInfo.paramList.length > 0) {
283-
const declParams = genericInfo.paramList.map((p) => `${p.name} extends ${p.constraint} = ${p.constraint}`);
222+
// Collect type-family fields (e.g. `resource: Resource` -> needs T extends Resource)
223+
// and references to generic nested types (e.g. `entry: BundleEntry` when BundleEntry is generic).
224+
const typeFamilyFields: { fieldName: string; familyTypeName: string }[] = [];
225+
const nestedFields: { fieldName: string; params: GenericParam[] }[] = [];
226+
if (!isHardcodedGeneric) {
227+
for (const [fieldName, field] of Object.entries(schema.fields ?? {})) {
228+
if (isChoiceDeclarationField(field) || !field.type) continue;
229+
const tsName = tsFieldName(fieldName);
230+
if (isNestedIdentifier(field.type)) {
231+
const params = nestedGenericParams[field.type.name];
232+
if (params?.length) nestedFields.push({ fieldName: tsName, params });
233+
continue;
234+
}
235+
const fieldTypeSchema = tsIndex.resolveType(field.type);
236+
if (
237+
isSpecializationTypeSchema(fieldTypeSchema) &&
238+
(fieldTypeSchema.typeFamily?.resources?.length ?? 0) > 0
239+
) {
240+
typeFamilyFields.push({ fieldName: tsName, familyTypeName: field.type.name });
241+
}
242+
}
243+
}
244+
245+
// Build generic params from type-family fields, then pass-through nested-type params.
246+
const fieldMap: Record<string, string> = {};
247+
const paramList: GenericParam[] = [];
248+
const [first] = typeFamilyFields;
249+
if (typeFamilyFields.length === 1 && nestedFields.length === 0 && first) {
250+
fieldMap[first.fieldName] = "T";
251+
paramList.push({ name: "T", constraint: first.familyTypeName });
252+
} else {
253+
for (const tf of typeFamilyFields) {
254+
const paramName = `T${uppercaseFirstLetter(tf.fieldName)}`;
255+
fieldMap[tf.fieldName] = paramName;
256+
paramList.push({ name: paramName, constraint: tf.familyTypeName });
257+
}
258+
}
259+
const nestedArgsByField: Record<string, string> = {};
260+
for (const nf of nestedFields) {
261+
for (const np of nf.params) {
262+
if (!paramList.find((p) => p.name === np.name)) paramList.push(np);
263+
}
264+
nestedArgsByField[nf.fieldName] = `<${nf.params.map((p) => p.name).join(", ")}>`;
265+
}
266+
if (!isHardcodedGeneric && paramList.length > 0) {
267+
const declParams = paramList.map((p) => `${p.name} extends ${p.constraint} = ${p.constraint}`);
284268
name += `<${declParams.join(", ")}>`;
285269
}
286270

@@ -290,7 +274,7 @@ export class TypeScript extends Writer<TypeScriptOptions> {
290274
this.debugComment(schema.identifier);
291275
if (!schema.fields && !extendsClause && !isResourceTypeSchema(schema)) {
292276
this.lineSM(`export type ${name} = object`);
293-
return genericInfo;
277+
return paramList;
294278
}
295279
this.curlyBlock(["export", "interface", name, extendsClause], () => {
296280
if (isResourceTypeSchema(schema)) {
@@ -322,12 +306,12 @@ export class TypeScript extends Writer<TypeScriptOptions> {
322306
tsName,
323307
field,
324308
undefined,
325-
genericInfo.fieldMap,
309+
fieldMap,
326310
isFamilyType,
327311
);
328312
const optionalSymbol = field.required ? "" : "?";
329313
const arraySymbol = field.array ? "[]" : "";
330-
const nestedArgs = genericInfo.nestedArgsByField[tsName] ?? "";
314+
const nestedArgs = nestedArgsByField[tsName] ?? "";
331315
this.lineSM(`${tsName}${optionalSymbol}: ${tsType}${nestedArgs}${arraySymbol}`);
332316

333317
if (this.withPrimitiveTypeExtension(schema)) {
@@ -337,7 +321,7 @@ export class TypeScript extends Writer<TypeScriptOptions> {
337321
}
338322
}
339323
});
340-
return genericInfo;
324+
return paramList;
341325
}
342326

343327
withPrimitiveTypeExtension(schema: TypeSchema | NestedTypeSchema): boolean {
@@ -364,16 +348,16 @@ export class TypeScript extends Writer<TypeScriptOptions> {
364348
tsIndex: TypeSchemaIndex,
365349
schema: SpecializationTypeSchema,
366350
isFamilyType?: (ref: TypeIdentifier) => boolean,
367-
): Record<string, GenericInfo> {
368-
const nestedGenericInfos: Record<string, GenericInfo> = {};
351+
): Record<string, GenericParam[]> {
352+
const nestedGenericParams: Record<string, GenericParam[]> = {};
369353
if (schema.nested) {
370354
for (const subtype of schema.nested) {
371-
const info = this.generateType(tsIndex, subtype, isFamilyType, nestedGenericInfos);
372-
nestedGenericInfos[subtype.identifier.name] = info;
355+
const params = this.generateType(tsIndex, subtype, isFamilyType, nestedGenericParams);
356+
nestedGenericParams[subtype.identifier.name] = params;
373357
this.line();
374358
}
375359
}
376-
return nestedGenericInfos;
360+
return nestedGenericParams;
377361
}
378362

379363
generateResourceModule(tsIndex: TypeSchemaIndex, schema: TypeSchema) {
@@ -392,13 +376,13 @@ export class TypeScript extends Writer<TypeScriptOptions> {
392376
this.generateDisclaimer();
393377
this.generateDependenciesImports(tsIndex, schema);
394378
this.generateComplexTypeReexports(schema);
395-
const nestedGenericInfos = this.generateNestedTypes(tsIndex, schema, isFamilyType);
379+
const nestedGenericParams = this.generateNestedTypes(tsIndex, schema, isFamilyType);
396380
this.comment(
397381
"CanonicalURL:",
398382
schema.identifier.url,
399383
`(pkg: ${packageMetaToFhir(packageMeta(schema))})`,
400384
);
401-
this.generateType(tsIndex, schema, isFamilyType, nestedGenericInfos);
385+
this.generateType(tsIndex, schema, isFamilyType, nestedGenericParams);
402386
this.generateResourceTypePredicate(schema);
403387
});
404388
} else {

0 commit comments

Comments
 (0)