Skip to content

Commit eee01b9

Browse files
committed
refactor(types): consolidate canonical-URL version stripping
205 -> 189 under noUncheckedIndexedAccess. stripVersionFromCanonicalUrl already existed in core/utils, but 18 sites across the parsers and all three emitters hand-rolled it as `url.split('|')[0]`, which the flag reads as string | undefined. They now call the helper. Same shape as the stripSliceSuffix pass: the flag was pointing at copy-paste, so the fix is deletion rather than a guard per site. Verified against a develop baseline: us-core and sdoh generate byte-identical output apart from generation-timing.json. 769 tests green.
1 parent ee56ba2 commit eee01b9

13 files changed

Lines changed: 28 additions & 20 deletions

src/generator/core/constants.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
*/
88

99
import { createRequire } from 'module';
10+
import { stripVersionFromCanonicalUrl } from './utils.js';
1011
import { fileURLToPath } from 'url';
1112
import path from 'path';
1213

@@ -292,7 +293,7 @@ export const VALUESET_DEFAULTS: Record<string, ValueSetDefault> = {
292293
*/
293294
export function resolveValueSetDefault(bindingUri: string): ValueSetDefault | undefined {
294295
// Strip version suffix: "http://...ValueSet/foo|4.0.1" → "http://...ValueSet/foo"
295-
const bare = bindingUri.split('|')[0];
296+
const bare = stripVersionFromCanonicalUrl(bindingUri);
296297
return VALUESET_DEFAULTS[bare];
297298
}
298299

src/generator/emitters/class/classGenerator.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { ctx } from '../../fhir/versionContext.js';
2+
import { stripVersionFromCanonicalUrl } from '../../core/utils.js';
23
import {
34
TS,
45
} from '../../core/constants.js';
@@ -392,7 +393,7 @@ export function generateClass(className: string, interfaceName: string, baseReso
392393
// code from a different context and should be deferred to runtime resolution.
393394
const sliceHasOwnBinding = !!(slice.binding?.uri && slice.binding.uri.length > 0);
394395
const sliceBindingMatchesParent = !rf.bindingUri ||
395-
(sliceHasOwnBinding && slice.binding!.uri.split('|')[0] === rf.bindingUri.split('|')[0]);
396+
(sliceHasOwnBinding && stripVersionFromCanonicalUrl(slice.binding!.uri) === stripVersionFromCanonicalUrl(rf.bindingUri));
396397
if (sliceBindingMatchesParent && slice.binding?.sampleCode) {
397398
if (slice.binding.sampleCode.system) {
398399
system = slice.binding.sampleCode.system;

src/generator/emitters/interface/processFlatField.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -471,7 +471,7 @@ export function handleFlatField(args: FieldHandlerArgs): boolean {
471471

472472
// Apply ValueSet binding constraints for primitive code fields (generates union literal type)
473473
if (isPrimitiveCodeField && hasNarrowingBinding && field.binding?.codes) {
474-
const vsUri = field.binding.uri?.split('|')[0];
474+
const vsUri = field.binding.uri ? stripVersionFromCanonicalUrl(field.binding.uri) : undefined;
475475
const vs = vsUri && valueSets ? (valueSets.get(vsUri) || valueSets.get(field.binding.uri!)) : undefined;
476476
if (vs && vs.isSmall && vs.concepts.length > 0) {
477477
// Import the ValueSet Code type from the generated valuesets directory

src/generator/emitters/validator/validatorBindingBuilder.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { Field } from '../../core/sdTypes.js';
22
import type { ValueSetBindingInfo } from './validatorTypes.js';
3+
import { stripVersionFromCanonicalUrl } from '../../core/utils.js';
34
import {
45
buildNestedLoopCode,
56
emitSystemPresenceLeaf,
@@ -139,7 +140,7 @@ function emitSystemPresenceFallback(
139140
): void {
140141
const type = field.type || field.baseTypeCode;
141142
if (type !== 'CodeableConcept' && type !== 'Coding' && type !== 'Quantity') return;
142-
const vsUri = (field.binding!.uri || '').split('|')[0];
143+
const vsUri = stripVersionFromCanonicalUrl(field.binding!.uri || '');
143144
const vsDisplayName = vsUri.split('/').pop() || vsUri;
144145

145146
if (isNested) {
@@ -203,7 +204,7 @@ function emitSystemOnlyBinding(
203204
const type = field.type || field.baseTypeCode;
204205
if (type !== 'CodeableConcept' && type !== 'Coding' && type !== 'Quantity') return;
205206

206-
const sysVsUri = (field.binding!.uri || '').split('|')[0];
207+
const sysVsUri = stripVersionFromCanonicalUrl(field.binding!.uri || '');
207208
const systemsLiteral = JSON.stringify(vsInfo.systems);
208209
const vsDisplayName = vsInfo.displayName;
209210

@@ -286,7 +287,7 @@ function emitResolvedBinding(
286287
const type = field.type || field.baseTypeCode;
287288
const vsName = vsInfo.sanitizedName;
288289
const vsDisplayName = vsInfo.displayName;
289-
const vsUri = (field.binding!.uri || '').split('|')[0];
290+
const vsUri = stripVersionFromCanonicalUrl(field.binding!.uri || '');
290291

291292
if (isNested) {
292293
const code = buildNestedLoopCode(

src/generator/emitters/validator/validatorConstraintBuilders.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { Field } from '../../core/sdTypes.js';
2-
import { stripSliceSuffix } from '../../core/utils.js';
2+
import { stripSliceSuffix, stripVersionFromCanonicalUrl } from '../../core/utils.js';
33
import type { ConstraintWithContext } from './validatorTypes.js';
44

55
/**

src/generator/emitters/validator/validatorGenerator.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { Field } from "../../core/sdTypes.js";
2-
import { stripSliceSuffix } from '../../core/utils.js';
2+
import { stripSliceSuffix, stripVersionFromCanonicalUrl } from '../../core/utils.js';
33
import { logger } from '../../../logger.js';
44
import { sanitizeValueSetName } from '../valueset/valueSetGenerator.js';
55
import type { ParsedValueSet } from '../../parser/vsParser.js';
@@ -206,7 +206,7 @@ export function generateValidateProfileFunction(
206206
let vs = valueSets.get(bindingUri);
207207
let resolvedUri = bindingUri;
208208
if (!vs && bindingUri.includes('|')) {
209-
resolvedUri = bindingUri.split('|')[0];
209+
resolvedUri = stripVersionFromCanonicalUrl(bindingUri);
210210
vs = valueSets.get(resolvedUri);
211211
}
212212
if (!vs) return undefined;
@@ -268,7 +268,7 @@ export function generateValidateProfileFunction(
268268
: fieldPath;
269269
if (!fieldPath) return;
270270
fieldPath = sliceScopedBindingPath(fieldPath, f, fields);
271-
const vsUri = (f.binding!.uri || '').split('|')[0];
271+
const vsUri = stripVersionFromCanonicalUrl(f.binding!.uri || '');
272272
const vsDisplayName = vsUri.split('/').pop() || vsUri;
273273
constraintsWithContext.push({
274274
constraint: {

src/generator/emitters/validator/validatorTemplates.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { Field } from '../../core/sdTypes.js';
2+
import { stripVersionFromCanonicalUrl } from '../../core/utils.js';
23

34
/**
45
* Emit the reference target-type check for one profile.
@@ -18,7 +19,7 @@ export function buildReferenceTargetTypeValidation(
1819
profileUrlToType?: Map<string, string>,
1920
): string {
2021
const resolveTargetType = (url: string): string | undefined => {
21-
const bare = url.split('|')[0];
22+
const bare = stripVersionFromCanonicalUrl(url);
2223
const lastSegment = bare.split('/').pop() || '';
2324
if (/^[A-Z][A-Za-z]+$/.test(lastSegment)) return lastSegment;
2425
return profileUrlToType?.get(bare) ?? profileUrlToType?.get(url);

src/generator/emitters/zod/zodRefinementBuilder.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import type { Field, Constraint } from '../../core/sdTypes.js';
77
import type { ParsedValueSet } from '../../parser/vsParser.js';
88
import { sanitizeValueSetName } from '../valueset/valueSetGenerator.js';
99
import { scopeConstraintExpression } from '../validator/validatorExpressions.js';
10-
import { stripSliceSuffix } from '../../core/utils.js';
10+
import { stripSliceSuffix, stripVersionFromCanonicalUrl } from '../../core/utils.js';
1111

1212
/** Metadata for a FHIRPath constraint tracked by the builder */
1313
export interface ConstraintMeta {
@@ -305,7 +305,7 @@ export function buildRefinements(
305305
codes = field.binding.codes.map(c => c.code);
306306
vsDisplayName = field.binding.uri?.split('/').pop() || 'ValueSet';
307307
} else if (field.binding.uri && valueSets) {
308-
const vsUri = field.binding.uri.split('|')[0];
308+
const vsUri = stripVersionFromCanonicalUrl(field.binding.uri);
309309
const vs = valueSets.get(vsUri) || valueSets.get(field.binding.uri);
310310
if (vs && vs.concepts.length > 0 && vs.concepts.length <= 200) {
311311
codes = vs.concepts.map(c => c.code);

src/generator/parser/sdFetcher.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
*/
66

77
import type { FhirStructureDefinition as StructureDefinition } from '../core/fhirTypes.js';
8+
import { stripVersionFromCanonicalUrl } from '../core/utils.js';
89
import path from "path";
910
import fs from "fs";
1011
import https from "https";
@@ -402,7 +403,7 @@ export function collectValueSetBindingUrls(structureDefinitions: StructureDefini
402403
for (const element of sd.differential.element) {
403404
if (element.binding?.valueSet) {
404405
// Strip version fragment if present (e.g., "...ValueSet|4.0.1")
405-
const url = element.binding.valueSet.split('|')[0];
406+
const url = stripVersionFromCanonicalUrl(element.binding.valueSet);
406407
bindingUrls.add(url);
407408
}
408409
}
@@ -412,7 +413,7 @@ export function collectValueSetBindingUrls(structureDefinitions: StructureDefini
412413
if (sd.snapshot?.element) {
413414
for (const element of sd.snapshot.element) {
414415
if (element.binding?.valueSet) {
415-
const url = element.binding.valueSet.split('|')[0];
416+
const url = stripVersionFromCanonicalUrl(element.binding.valueSet);
416417
bindingUrls.add(url);
417418
}
418419
}

src/generator/parser/sdParser.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -448,7 +448,7 @@ export async function parseStructureDefinition(
448448

449449
if (bindingUri) {
450450
// Strip version suffix for map lookup (e.g. "…/ValueSet/foo|1.0.0" → "…/ValueSet/foo")
451-
const lookupUri = bindingUri.split('|')[0];
451+
const lookupUri = stripVersionFromCanonicalUrl(bindingUri);
452452
const codes = valueSetCodesMap?.[lookupUri] ?? valueSetCodesMap?.[bindingUri];
453453
if (codes && codes.length) {
454454
// Deterministic pick based on simple hash of binding URI

0 commit comments

Comments
 (0)