Repository: FHIR/sushi (ts)

Issue:
Extra Extensions on Instances
See https://chat.fhir.org/#narrow/stream/215610-shorthand/topic/extensions.20on.20repeating.20elements for more details. To recreate this bug, you can use the following full example:
```
Instance: PatInstance
InstanceOf: MyPatient
* generalPractitioner[0] = Reference(drwelby)       //the GP
* generalPractitioner[0].display = "Dr Marcus Welby"

* generalPractitioner[1] = Reference(gp-org1)       //the GP Practice
* generalPractitioner[1].display = "Good Health Medcial Centre"
* generalPractitioner[1].extension[edi-address].valueCode = #F
* generalPractitioner[1].extension[gp-practice-enrollment-date].valueBoolean = true

Profile: MyPatient
Parent: Patient
* generalPractitioner only Reference(Practitioner | Organization)
* generalPractitioner.extension contains
    http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex named edi-address 0..1 and
    http://hl7.org/fhir/us/core/StructureDefinition/us-core-direct named gp-practice-enrollment-date 0..1
```

You should see that there are extraneous extension elements added to `generlPractitioner[0]`. This looks like it is happening in the `setImpliedPropertiesOnInstance` function, so that may be a good place to start investigating this.

Pull request:
Implied path search maintains ancestor indices
Fixes #502 and completes task [CIMPL-429](https://standardhealthrecord.atlassian.net/browse/CIMPL-429).

When finding implied paths, the numeric index on the last part of the path should be removed. But, numeric indices on path parts earlier in the path should be maintained. This will prevent implied paths from including siblings of ancestor elements that have numeric indices and minimum cardinality of 0.

Patch:
diff --git a/src/fhirtypes/common.ts b/src/fhirtypes/common.ts
index 3851d11..2089863 100644
--- a/src/fhirtypes/common.ts
+++ b/src/fhirtypes/common.ts
@@ -104,7 +104,7 @@ export function setImpliedPropertiesOnInstance(
           const impliedPaths = getAllImpliedPaths(
             associatedEl,
             // Implied paths are found via the index free path
-            finalPath.replace(/\[[-+]?\d+\]/g, '')
+            finalPath.replace(/\[[-+]?\d+\]$/g, '')
           );
           [finalPath, ...impliedPaths].forEach(ip => sdRuleMap.set(ip, foundFixedValue));
         }
@@ -459,15 +459,17 @@ export function getAllImpliedPaths(element: ElementDefinition, path: string): st
   const parentPaths = [];
   const parent = element.parent();
   if (parent) {
+    const nextPath = splitOnPathPeriods(path)
+      .slice(0, -1)
+      .join('.')
+      .replace(/\[[-+]?\d+\]$/g, '');
     if (parent.min === 0) {
       // If the parent has min = 0, then the path above this point has no additional implied paths
       // so add the path to this point to the parentPaths
-      parentPaths.push(splitOnPathPeriods(path).slice(0, -1).join('.'));
+      parentPaths.push(nextPath);
     } else {
       // If min >= 1, the parent or its parents my have implied paths, recursively find those
-      parentPaths.push(
-        ...getAllImpliedPaths(parent, splitOnPathPeriods(path).slice(0, -1).join('.'))
-      );
+      parentPaths.push(...getAllImpliedPaths(parent, nextPath));
     }
   } else {
     parentPaths.push('');


Test patch:
diff --git a/test/export/InstanceExporter.test.ts b/test/export/InstanceExporter.test.ts
index 590e1c3..4d0d148 100644
--- a/test/export/InstanceExporter.test.ts
+++ b/test/export/InstanceExporter.test.ts
@@ -1740,6 +1740,62 @@ describe('InstanceExporter', () => {
       expect(exportedBundledPatient).toHaveLength(1);
     });
 
+    it('should only add optional children of list elements and the implied elements of those children to entries in the list that fix values on those children', () => {
+      // * generalPractitioner only Reference(Practitioner | Organization)
+      const onlyRule = new OnlyRule('generalPractitioner');
+      onlyRule.types.push(
+        {
+          type: 'Practitioner',
+          isReference: true
+        },
+        {
+          type: 'Organization',
+          isReference: true
+        }
+      );
+      // * generalPractitioner.extension contains
+      //   http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName named mothers-maiden-name 0..1
+      const containsRule = new ContainsRule('generalPractitioner.extension');
+      containsRule.items.push({
+        name: 'mothers-maiden-name',
+        type: 'http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName'
+      });
+      const extensionCard = new CardRule('generalPractitioner.extension[mothers-maiden-name]');
+      extensionCard.min = 0;
+      extensionCard.max = '1';
+      patient.rules.push(containsRule, extensionCard);
+
+      // * generalPractitioner[0] = Reference(my-doctor)
+      const gp = new FixedValueRule('generalPractitioner[0]');
+      gp.fixedValue = new FshReference('my-doctor');
+      // * generalPractitioner[1] = Reference(gp-org1)
+      const gpOrg = new FixedValueRule('generalPractitioner[1]');
+      gpOrg.fixedValue = new FshReference('gp-org1');
+      // * generalPractitioner[1].extension[mothers-maiden-name].valueString = "Belnades"
+      const directValue = new FixedValueRule(
+        'generalPractitioner[1].extension[mothers-maiden-name].valueString'
+      );
+      directValue.fixedValue = 'Belnades';
+      patientInstance.rules.push(gp, gpOrg, directValue);
+
+      sdExporter.export();
+      const result = exportInstance(patientInstance);
+      expect(result.generalPractitioner.length).toBe(2);
+      expect(result.generalPractitioner[0]).toEqual({
+        reference: 'my-doctor'
+      });
+      expect(result.generalPractitioner[0].extension).toBeUndefined();
+      expect(result.generalPractitioner[1]).toEqual({
+        reference: 'gp-org1',
+        extension: [
+          {
+            url: 'http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName',
+            valueString: 'Belnades'
+          }
+        ]
+      });
+    });
+
     describe('#Inline Instances', () => {
       beforeEach(() => {
         const inlineInstance = new Instance('MyInlinePatient');