Skip to content

Commit 5b549fd

Browse files
committed
refactor(types): clear interfaceGenerator and classGenerator
334 -> 307, continuing the noUncheckedIndexedAccess adoption on develop now that 1.6.0 is published. interfaceGenerator: the base-field index skips a field whose name has no last segment; the slice-binding lookup reads the first slice once and keys off it rather than testing .length and then indexing; the Omit<> post-pass guards the interface entry, the captured base type and the property-name capture. classGenerator: the binding-code skeleton returns when bindingCodes yields no primary code, since there is no value to emit without one; the coding-profile branch keys off profileUrls?.[0]; the pattern-CodeableConcept branch takes the first coding through optional indexing; the complex-skeleton lookup holds the mapped expression instead of indexing the record twice. Both shape emitted classes and interfaces rather than validators, so output was diffed directly against the previous commit for us-core@9.0.0. The only difference across the package is the valueDateTime timestamp inside a random() literal, which differs between any two runs. No `!` anywhere. Typecheck, lint and 748 tests green.
1 parent 707c164 commit 5b549fd

2 files changed

Lines changed: 22 additions & 15 deletions

File tree

src/generator/emitters/class/classGenerator.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,10 @@ export function generateClass(className: string, interfaceName: string, baseReso
199199
.filter((s): s is string => typeof s === 'string' && s.length > 0);
200200
const uniqueSystems = Array.from(new Set(systems));
201201
const singleSystem = uniqueSystems.length === 1 ? JSON.stringify(uniqueSystems[0]) : undefined;
202+
// bindingCodes is non-empty per the guard above, but indexing does not say
203+
// so; without a code there is no skeleton value to emit.
202204
const primary = rf.bindingCodes[0];
205+
if (!primary) continue;
203206
if (rf.type === 'CodeableConcept') {
204207
const expr = singleSystem
205208
? `skeletonCodeableConcept(${singleSystem}, ${codesArray})`
@@ -364,8 +367,8 @@ export function generateClass(className: string, interfaceName: string, baseReso
364367
}
365368

366369
// Check profile URLs for known coding profiles
367-
if (slice.profileUrls && slice.profileUrls.length > 0) {
368-
const profileUrl = slice.profileUrls[0];
370+
const profileUrl = slice.profileUrls?.[0];
371+
if (profileUrl) {
369372
const profileUrlLower = profileUrl.toLowerCase();
370373
// ISiK SNOMED-CT coding profile
371374
if (profileUrlLower.includes('snomedctcoding') || profileUrlLower.includes('snomed')) {
@@ -396,7 +399,7 @@ export function generateClass(className: string, interfaceName: string, baseReso
396399
}
397400
code = JSON.stringify(slice.binding.sampleCode.code);
398401
codeResolved = true;
399-
} else if (sliceBindingMatchesParent && slice.binding?.codes && slice.binding.codes.length > 0) {
402+
} else if (sliceBindingMatchesParent && slice.binding?.codes?.[0]) {
400403
// Fallback to first code from the binding's codes array
401404
const firstCode = slice.binding.codes[0];
402405
if (firstCode.system) {
@@ -467,8 +470,8 @@ export function generateClass(className: string, interfaceName: string, baseReso
467470
// Handle CodeableConcept with pattern constraint - use the pattern instead of skeleton
468471
if (rf.type === 'CodeableConcept' && rf.patternConstraint && typeof rf.patternConstraint === 'object') {
469472
const pc = rf.patternConstraint as { coding?: Array<{ system?: string; code?: string; display?: string }> };
470-
if (pc.coding && Array.isArray(pc.coding) && pc.coding.length > 0) {
471-
const firstCoding = pc.coding[0];
473+
const firstCoding = Array.isArray(pc.coding) ? pc.coding[0] : undefined;
474+
if (firstCoding) {
472475
const systemStr = JSON.stringify(firstCoding.system || TS.NULL_FLAVOR);
473476
const codeStr = JSON.stringify(firstCoding.code || 'UNK');
474477
const displayStr = firstCoding.display ? `, ${JSON.stringify(firstCoding.display)}` : '';
@@ -533,8 +536,9 @@ export function generateClass(className: string, interfaceName: string, baseReso
533536
}
534537
// Complex type skeletons (also check baseType for profiled types like CodeableConcept-uv-ips)
535538
const skeletonType = COMPLEX_SKELETON_MAP[rf.type] ? rf.type : (rf.baseType && COMPLEX_SKELETON_MAP[rf.baseType] ? rf.baseType : undefined);
536-
if (skeletonType) {
537-
const expr = COMPLEX_SKELETON_MAP[skeletonType];
539+
const skeletonExpr = skeletonType ? COMPLEX_SKELETON_MAP[skeletonType] : undefined;
540+
if (skeletonType && skeletonExpr) {
541+
const expr = skeletonExpr;
538542
const initExpr = buildValueExpression(rf, expr);
539543
requiredInits.push(`${propKey(rf.name)}: ${initExpr}`);
540544
continue;

src/generator/emitters/interface/interfaceGenerator.ts

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,8 @@ export function generateInterfaces(
8686
const baseFieldAnyArray = new Map<string, boolean>();
8787
for (const bf of baseFields) {
8888
const parts = bf.name.split('.');
89-
const seg = parts[parts.length - 1];
89+
const seg = parts.at(-1);
90+
if (!seg) continue;
9091
if (!baseFieldByLastSegment.has(seg)) baseFieldByLastSegment.set(seg, bf);
9192
// Track the widest array flag across direct children only (not nested duplicates)
9293
if (bf.isArray && parts.length === 2) baseFieldAnyArray.set(seg, true);
@@ -112,10 +113,11 @@ export function generateInterfaces(
112113
);
113114

114115
// Use the first slice binding if available (slices typically have more specific constraints)
115-
if (slicesForThisField.length > 0 && slicesForThisField[0].binding) {
116-
binding = slicesForThisField[0].binding;
116+
const firstSlice = slicesForThisField[0];
117+
if (firstSlice?.binding) {
118+
binding = firstSlice.binding;
117119
isSlicedField = true;
118-
debug(`Using binding from slice ${slicesForThisField[0].sliceName} for ${field.name}`);
120+
debug(`Using binding from slice ${firstSlice.sliceName} for ${field.name}`);
119121
}
120122
}
121123

@@ -238,20 +240,21 @@ export function generateInterfaces(
238240
// This must run after all interfaces + extension unions are assembled.
239241
for (let i = 0; i < interfaces.length; i++) {
240242
const iface = interfaces[i];
243+
if (!iface) continue;
241244
// Match "export interface Foo extends Bar {" or "export interface Foo extends Omit<Bar, ...> {"
242245
const extendsMatch = iface.match(/^export interface (\S+) extends (Omit<)?(\S+?)([,>])? \{/);
243246
if (!extendsMatch) continue;
244247
const alreadyOmit = !!extendsMatch[2];
245248
const baseType = extendsMatch[3];
246249
// Only wrap FHIR base types (not local profile interfaces)
247-
if (!isFhirType(baseType)) continue;
250+
if (!baseType || !isFhirType(baseType)) continue;
248251
// Extract all declared property names from the interface body
249252
const bodyStart = iface.indexOf('{');
250253
const body = iface.slice(bodyStart + 1);
251254
const propNames: string[] = [];
252255
for (const line of body.split('\n')) {
253-
const m = line.match(/^\s+(\w+)[?:]/);
254-
if (m && m[1] !== 'resourceType') propNames.push(m[1]);
256+
const propName = line.match(/^\s+(\w+)[?:]/)?.[1];
257+
if (propName && propName !== 'resourceType') propNames.push(propName);
255258
}
256259
if (propNames.length === 0) continue;
257260
const omitKeys = propNames.map(p => `'${p}'`).join(' | ');
@@ -306,7 +309,7 @@ export function generateInterfaces(
306309
logger.debug('[DEBUG FINAL INTERFACES]', {
307310
interfaceName,
308311
interfaceCount: interfaces.length,
309-
interfaceNames: interfaces.map(i => i.split('\n')[0]).filter(n => n.includes('ConditionDeBasis02'))
312+
interfaceNames: interfaces.map(i => i.split('\n')[0] ?? '').filter(n => n.includes('ConditionDeBasis02'))
310313
});
311314
}
312315

0 commit comments

Comments
 (0)