Repository: FHIR/sushi (ts)

Issue:
SUSHI drops version in versioned "only" rules
Given this FSH:
```
Profile: MyPatient
Parent: Patient
* ^version = "1.0"

Profile: MyComposition
Parent: Composition
* section.entry only Reference(MyPatient|1.0)
```

SUSHI drops the version when it constrains `section.entry`:
```json
      {
        "id": "Composition.section.entry",
        "path": "Composition.section.entry",
        "type": [
          {
            "code": "Reference",
            "targetProfile": [
              "http://example.org/StructureDefinition/MyPatient"
            ]
          }
        ]
      }
```

Since `targetProfile` is a `canonical`, it should retain the `|1.0`.

See: https://chat.fhir.org/#narrow/stream/215610-shorthand/topic/Adding.20version.20to.20Reference

Pull request:
Include version in Only Rules
This PR ensures that if a version is specified in a `Reference` or `CodeableReference`, the version is included in the targetProfile information as well. Previous updates already ensure that the version is used when fishing and the fallback if the version is not found works as intended. This just updates the url to include the version so it is not lost when building the targetProfile list. This was already in place for `Canonical`s specifically, so I included both `Reference` and `CodeableReference` in the same place.

Fixes #1270.

Patch:
diff --git a/src/fhirtypes/ElementDefinition.ts b/src/fhirtypes/ElementDefinition.ts
index e76c9ba..4365fb5 100644
--- a/src/fhirtypes/ElementDefinition.ts
+++ b/src/fhirtypes/ElementDefinition.ts
@@ -1019,7 +1019,10 @@ export class ElementDefinition {
     for (const type of types) {
       const typeMatch = this.findTypeMatch(type, targetTypes, fisher);
       // if the type is Canonical, it may have a version. preserve it in the match's metadata.
-      if (type.isCanonical && type.type.indexOf('|') > -1) {
+      if (
+        (type.isCanonical || type.isReference || type.isCodeableReference) &&
+        type.type.indexOf('|') > -1
+      ) {
         typeMatch.metadata.url = `${typeMatch.metadata.url}|${type.type.split('|', 2)[1]}`;
       }
       typeMatches.get(typeMatch.code).push(typeMatch);


Test patch:
diff --git a/test/fhirtypes/ElementDefinition.constrainType.test.ts b/test/fhirtypes/ElementDefinition.constrainType.test.ts
index 8aa33a4..894c438 100644
--- a/test/fhirtypes/ElementDefinition.constrainType.test.ts
+++ b/test/fhirtypes/ElementDefinition.constrainType.test.ts
@@ -622,6 +622,27 @@ describe('ElementDefinition', () => {
       expect(hasMember.type[0]).toEqual(expectedType);
     });
 
+    it('should allow a resource type in a reference to multiple types to be constrained to with a versioned reference', () => {
+      const subject = observation.elements.find(e => e.id === 'Observation.subject');
+      const subjectConstraint = new OnlyRule('subject');
+      subjectConstraint.types = [
+        { type: 'http://hl7.org/fhir/StructureDefinition/actualgroup|4.0.1', isReference: true },
+        { type: 'Patient|4.0.1', isReference: true },
+        { type: 'Device', isReference: true }
+      ];
+      subject.constrainType(subjectConstraint, fisher);
+      expect(subject.type).toHaveLength(1);
+      expect(subject.type[0]).toEqual(
+        new ElementDefinitionType('Reference').withTargetProfiles(
+          'http://hl7.org/fhir/StructureDefinition/actualgroup|4.0.1',
+          'http://hl7.org/fhir/StructureDefinition/Patient|4.0.1',
+          'http://hl7.org/fhir/StructureDefinition/Device'
+        )
+      );
+      expect(loggerSpy.getAllMessages('error')).toHaveLength(0);
+      expect(loggerSpy.getAllMessages('warn')).toHaveLength(0);
+    });
+
     it('should allow us to constrain a reference to a profile whose parent is specified using a versioned canonical URL', () => {
       const hasMember = observation.elements.find(e => e.id === 'Observation.hasMember');
       const hasMemberConstraint = new OnlyRule('hasMember');
@@ -1357,7 +1378,7 @@ describe('ElementDefinition R5', () => {
     });
 
     describe('CodeableReference() keyword', () => {
-      it('should allow a CodeableReference to multiple resource types to be constrained to a reference to a subset using CodeableReference keyword', () => {
+      it('should allow a CodeableReference to multiple resource types to be constrained to a reference to a subset', () => {
         const performedActivity = r5CarePlan.elements.find(
           e => e.id === 'CarePlan.activity.performedActivity'
         );
@@ -1378,7 +1399,7 @@ describe('ElementDefinition R5', () => {
         expect(loggerSpy.getAllMessages('warn')).toHaveLength(0);
       });
 
-      it('should allow a CodeableReference to multiple resource types to be constrained to a reference to a single type using CodeableReference keyword', () => {
+      it('should allow a CodeableReference to multiple resource types to be constrained to a reference to a single type', () => {
         const performedActivity = r5CarePlan.elements.find(
           e => e.id === 'CarePlan.activity.performedActivity'
         );
@@ -1395,7 +1416,7 @@ describe('ElementDefinition R5', () => {
         expect(loggerSpy.getAllMessages('warn')).toHaveLength(0);
       });
 
-      it('should allow a CodeableReference to multiple resource types to be constrained to a reference to a single profile  using CodeableReference keyword', () => {
+      it('should allow a CodeableReference to multiple resource types to be constrained to a reference to a single profile', () => {
         const performedActivity = r5CarePlan.elements.find(
           e => e.id === 'CarePlan.activity.performedActivity'
         );
@@ -1414,7 +1435,7 @@ describe('ElementDefinition R5', () => {
         expect(loggerSpy.getAllMessages('warn')).toHaveLength(0);
       });
 
-      it('should allow a CodeableReference to multiple resource types to be constrained to a reference to multiple profiles using CodeableReference keyword', () => {
+      it('should allow a CodeableReference to multiple resource types to be constrained to a reference to multiple profiles', () => {
         const performedActivity = r5CarePlan.elements.find(
           e => e.id === 'CarePlan.activity.performedActivity'
         );
@@ -1481,6 +1502,32 @@ describe('ElementDefinition R5', () => {
         expect(loggerSpy.getAllMessages('warn')).toHaveLength(0);
       });
 
+      it('should allow a CodeableReference to multiple resource types to be constrained to a versioned reference', () => {
+        const performedActivity = r5CarePlan.elements.find(
+          e => e.id === 'CarePlan.activity.performedActivity'
+        );
+        const onlyRule = new OnlyRule('activity.performedActivity');
+        onlyRule.types = [
+          { type: 'Practitioner|5.0.0', isCodeableReference: true },
+          {
+            type: 'http://hl7.org/fhir/StructureDefinition/Group|5.0.0',
+            isCodeableReference: true
+          },
+          { type: 'Organization', isCodeableReference: true }
+        ];
+        performedActivity.constrainType(onlyRule, fisher);
+        expect(performedActivity.type).toHaveLength(1);
+        expect(performedActivity.type[0]).toEqual(
+          new ElementDefinitionType('CodeableReference').withTargetProfiles(
+            'http://hl7.org/fhir/StructureDefinition/Practitioner|5.0.0',
+            'http://hl7.org/fhir/StructureDefinition/Group|5.0.0',
+            'http://hl7.org/fhir/StructureDefinition/Organization'
+          )
+        );
+        expect(loggerSpy.getAllMessages('error')).toHaveLength(0);
+        expect(loggerSpy.getAllMessages('warn')).toHaveLength(0);
+      });
+
       it('should throw InvalidTypeError when a passed in reference to a type that cannot constrain any existing references to types on a CodeableReference using CodeableReference keyword', () => {
         const addresses = r5CarePlan.elements.find(e => e.id === 'CarePlan.addresses');
         const onlyRule = new OnlyRule('addresses');