Repository: FHIR/sushi
Pull request #949 (2021-11-03 20:38:51)

Find Instances of Profile, Extension, CodeSystem, and ValueSet when fishing
Completes task [CIMPL-837](https://standardhealthrecord.atlassian.net/browse/CIMPL-837).

When fishing in `FSHTank` or `Package`, an `Instance` or `InstanceDefinition` may be returned if a definitional instance of the entity exists.
- Profiles have `InstanceOf` "StructureDefinition" and `derivation` "constraint", and a `type` that is not "Extension".
- Extensions have `InstanceOf` "StructureDefinition", `derivation` "constraint", and `type` "Extension" OR `InstanceOf` "Extension" and `usage` "inline".
- CodeSystems have `InstanceOf` "CodeSystem".
- ValueSets have `InstanceOf` "ValueSet".

The special case of allowing inline instances for Extension is because Extension is an Element type, not a Resource type, so SUSHI gets unhappy when a definitional instance of `Extension` is created. Inline instances are not allowed for the other types.

Adding these new possible return types didn't change too much logic, but did require a few updates in `StructureDefinitionExporter`.

mint-thompson: @cmoesel I think there's just one spot where we fish in the tank and want a Profile, Extension, Logical, or Resource, and may get an Instance equivalent: in `StructureDefinitionExporter`, when fishing for FHIR, we check the type to see which exporter to use. In other places, we only check whether the result exists. I'm basing this statement on a search for calls to `Package.fish`. It seems like most other places call `Package.fishForFHIR` instead, where nothing should need to change.
mint-thompson: @cmoesel I'm investigating the scenario you're describing, and I think it already gets sort of handled? There's a small bit in [MasterFisher.fishForMetadata](https://github.com/FHIR/sushi/blob/1d660484c335fccf233f37c3d698357b1daac36c/src/utils/MasterFisher.ts#L77-L81) that tries to figure out the `resourceType` when it fishes up an `Instance` from the tank. Does this provide the behavior that you're asking about?
cmoesel: @mint-thompson A-ha!  Yes!  That should do the trick since the only code that cares about the resourceType would be using the MasterFisher anyway.  Thanks for finding that!  My approval still stands!

Patch:
diff --git a/src/export/Package.ts b/src/export/Package.ts
index f874af6..7a84556 100644
--- a/src/export/Package.ts
+++ b/src/export/Package.ts
@@ -35,21 +35,77 @@ export class Package implements Fishable {
       switch (type) {
         case Type.Profile:
           def = this.profiles.find(p => p.id === item || p.name === item || p.url === item);
+          if (!def) {
+            def = this.instances.find(
+              i =>
+                i._instanceMeta.usage === 'Definition' &&
+                i.resourceType === 'StructureDefinition' &&
+                i.derivation === 'constraint' &&
+                i.type !== 'Extension' &&
+                (i.id === item || i._instanceMeta.name === item || i.url === item)
+            );
+          }
           break;
         case Type.Extension:
           def = this.extensions.find(e => e.id === item || e.name === item || e.url === item);
+          if (!def) {
+            def = this.instances.find(
+              i =>
+                i._instanceMeta.usage === 'Definition' &&
+                i.resourceType === 'StructureDefinition' &&
+                i.derivation === 'constraint' &&
+                i.type === 'Extension' &&
+                (i.id === item || i._instanceMeta.name === item || i.url === item)
+            );
+          }
           break;
         case Type.Logical:
           def = this.logicals.find(e => e.id === item || e.name === item || e.url === item);
+          if (!def) {
+            def = this.instances.find(
+              i =>
+                i._instanceMeta.usage === 'Definition' &&
+                i.resourceType === 'StructureDefinition' &&
+                i.derivation === 'specialization' &&
+                i.kind === 'logical' &&
+                (i.id === item || i._instanceMeta.name === item || i.url === item)
+            );
+          }
           break;
         case Type.Resource:
           def = this.resources.find(e => e.id === item || e.name === item || e.url === item);
+          if (!def) {
+            def = this.instances.find(
+              i =>
+                i._instanceMeta.usage === 'Definition' &&
+                i.resourceType === 'StructureDefinition' &&
+                i.derivation === 'specialization' &&
+                i.kind === 'resource' &&
+                (i.id === item || i._instanceMeta.name === item || i.url === item)
+            );
+          }
           break;
         case Type.ValueSet:
           def = this.valueSets.find(vs => vs.id === item || vs.name === item || vs.url === item);
+          if (!def) {
+            def = this.instances.find(
+              i =>
+                i._instanceMeta.usage === 'Definition' &&
+                i.resourceType === 'ValueSet' &&
+                (i.id === item || i._instanceMeta.name === item || i.url === item)
+            );
+          }
           break;
         case Type.CodeSystem:
           def = this.codeSystems.find(cs => cs.id === item || cs.name === item || cs.url === item);
+          if (!def) {
+            def = this.instances.find(
+              i =>
+                i._instanceMeta.usage === 'Definition' &&
+                i.resourceType === 'CodeSystem' &&
+                (i.id === item || i._instanceMeta.name === item || i.url === item)
+            );
+          }
           break;
         case Type.Instance:
           def = this.instances.find(i => i.id === item || i._instanceMeta.name === item);
diff --git a/src/export/StructureDefinitionExporter.ts b/src/export/StructureDefinitionExporter.ts
index d21481f..5b81daf 100644
--- a/src/export/StructureDefinitionExporter.ts
+++ b/src/export/StructureDefinitionExporter.ts
@@ -7,7 +7,15 @@ import {
   StructureDefinition,
   STRUCTURE_DEFINITION_R4_BASE
 } from '../fhirtypes';
-import { Extension, Invariant, isAllowedRule, Logical, Profile, Resource } from '../fshtypes';
+import {
+  Extension,
+  Invariant,
+  isAllowedRule,
+  Logical,
+  Profile,
+  Resource,
+  Instance
+} from '../fshtypes';
 import { FSHTank } from '../import';
 import { InstanceExporter } from '../export';
 import {
@@ -804,10 +812,19 @@ export class StructureDefinitionExporter implements Fishable {
         Type.Extension,
         Type.Logical,
         Type.Resource
-      ) as Profile | Extension | Logical | Resource;
-      if (fshDefinition) {
+      ) as Profile | Extension | Logical | Resource | Instance;
+      if (
+        fshDefinition instanceof Profile ||
+        fshDefinition instanceof Extension ||
+        fshDefinition instanceof Logical ||
+        fshDefinition instanceof Resource
+      ) {
         this.exportStructDef(fshDefinition);
         result = this.fisher.fishForFHIR(item, ...types);
+      } else if (fshDefinition instanceof Instance) {
+        const instanceExporter = new InstanceExporter(this.tank, this.pkg, this.fisher);
+        instanceExporter.exportInstance(fshDefinition);
+        result = this.fisher.fishForFHIR(item, ...types);
       }
     }
     return result;
diff --git a/src/import/FSHTank.ts b/src/import/FSHTank.ts
index 4a25c72..7aa6b34 100644
--- a/src/import/FSHTank.ts
+++ b/src/import/FSHTank.ts
@@ -10,7 +10,8 @@ import {
   Invariant,
   RuleSet,
   Mapping,
-  Configuration
+  Configuration,
+  FshCode
 } from '../fshtypes';
 import { AssignmentRule } from '../fshtypes/rules';
 import { Type, Metadata, Fishable } from '../utils/Fishable';
@@ -171,6 +172,29 @@ export class FSHTank implements Fishable {
               p.id === item ||
               getUrlFromFshDefinition(p, this.config.canonical) === item
           );
+          if (!result) {
+            result = this.getAllInstances().find(
+              profileInstance =>
+                profileInstance.instanceOf === 'StructureDefinition' &&
+                profileInstance.usage === 'Definition' &&
+                (profileInstance.name === item ||
+                  profileInstance.id === item ||
+                  getUrlFromFshDefinition(profileInstance, this.config.canonical) === item) &&
+                profileInstance.rules.some(
+                  rule =>
+                    rule instanceof AssignmentRule &&
+                    rule.path === 'derivation' &&
+                    rule.value instanceof FshCode &&
+                    rule.value.code === 'constraint'
+                ) &&
+                !profileInstance.rules.some(
+                  rule =>
+                    rule instanceof AssignmentRule &&
+                    rule.path === 'type' &&
+                    rule.value === 'Extension'
+                )
+            );
+          }
           break;
         case Type.Extension:
           result = this.getAllExtensions().find(
@@ -179,6 +203,30 @@ export class FSHTank implements Fishable {
               e.id === item ||
               getUrlFromFshDefinition(e, this.config.canonical) === item
           );
+          if (!result) {
+            // There may be a matching definitional Instance of StructureDefinition with type Extension
+            result = this.getAllInstances().find(
+              extensionInstance =>
+                extensionInstance.instanceOf === 'StructureDefinition' &&
+                extensionInstance.usage === 'Definition' &&
+                (extensionInstance.name === item ||
+                  extensionInstance.id === item ||
+                  getUrlFromFshDefinition(extensionInstance, this.config.canonical) === item) &&
+                extensionInstance.rules.some(
+                  rule =>
+                    rule instanceof AssignmentRule &&
+                    rule.path === 'derivation' &&
+                    rule.value instanceof FshCode &&
+                    rule.value.code === 'constraint'
+                ) &&
+                extensionInstance.rules.some(
+                  rule =>
+                    rule instanceof AssignmentRule &&
+                    rule.path === 'type' &&
+                    rule.value === 'Extension'
+                )
+            );
+          }
           break;
         case Type.Logical:
           result = this.getAllLogicals().find(
@@ -187,6 +235,30 @@ export class FSHTank implements Fishable {
               l.id === item ||
               getUrlFromFshDefinition(l, this.config.canonical) === item
           );
+          if (!result) {
+            result = this.getAllInstances().find(
+              logicalInstance =>
+                logicalInstance.instanceOf === 'StructureDefinition' &&
+                logicalInstance.usage === 'Definition' &&
+                (logicalInstance.name === item ||
+                  logicalInstance.id === item ||
+                  getUrlFromFshDefinition(logicalInstance, this.config.canonical) === item) &&
+                logicalInstance.rules.some(
+                  rule =>
+                    rule instanceof AssignmentRule &&
+                    rule.path === 'derivation' &&
+                    rule.value instanceof FshCode &&
+                    rule.value.code === 'specialization'
+                ) &&
+                logicalInstance.rules.some(
+                  rule =>
+                    rule instanceof AssignmentRule &&
+                    rule.path === 'kind' &&
+                    rule.value instanceof FshCode &&
+                    rule.value.code === 'logical'
+                )
+            );
+          }
           break;
         case Type.Resource:
           result = this.getAllResources().find(
@@ -195,6 +267,30 @@ export class FSHTank implements Fishable {
               r.id === item ||
               getUrlFromFshDefinition(r, this.config.canonical) === item
           );
+          if (!result) {
+            result = this.getAllInstances().find(
+              resourceInstance =>
+                resourceInstance.instanceOf === 'StructureDefinition' &&
+                resourceInstance.usage === 'Definition' &&
+                (resourceInstance.name === item ||
+                  resourceInstance.id === item ||
+                  getUrlFromFshDefinition(resourceInstance, this.config.canonical) === item) &&
+                resourceInstance.rules.some(
+                  rule =>
+                    rule instanceof AssignmentRule &&
+                    rule.path === 'derivation' &&
+                    rule.value instanceof FshCode &&
+                    rule.value.code === 'specialization'
+                ) &&
+                resourceInstance.rules.some(
+                  rule =>
+                    rule instanceof AssignmentRule &&
+                    rule.path === 'kind' &&
+                    rule.value instanceof FshCode &&
+                    rule.value.code === 'resource'
+                )
+            );
+          }
           break;
         case Type.ValueSet:
           result = this.getAllValueSets().find(
@@ -203,6 +299,16 @@ export class FSHTank implements Fishable {
               vs.id === item ||
               getUrlFromFshDefinition(vs, this.config.canonical) === item
           );
+          if (!result) {
+            result = this.getAllInstances().find(
+              vsInstance =>
+                vsInstance?.instanceOf === 'ValueSet' &&
+                vsInstance?.usage === 'Definition' &&
+                (vsInstance?.name === item ||
+                  vsInstance.id === item ||
+                  getUrlFromFshDefinition(vsInstance, this.config.canonical) === item)
+            );
+          }
           break;
         case Type.CodeSystem:
           result = this.getAllCodeSystems().find(


Test patch:
diff --git a/test/export/Package.test.ts b/test/export/Package.test.ts
index f203a9e..537d6ba 100644
--- a/test/export/Package.test.ts
+++ b/test/export/Package.test.ts
@@ -121,6 +121,73 @@ describe('Package', () => {
     instance1.resourceType = 'Practitioner';
     instance1.gender = 'male';
     pkg.instances.push(instance1);
+    // Instance[2]: Thrilling / thrill-ing / Condition
+    const instance2 = new InstanceDefinition();
+    instance2._instanceMeta.name = 'Thrilling';
+    instance2._instanceMeta.usage = 'Definition';
+    instance2.id = 'thrill-ing';
+    instance2.resourceType = 'StructureDefinition';
+    instance2.derivation = 'constraint';
+    instance2.type = 'Condition';
+    instance2.url = 'http://hl7.org/fhir/us/minimal/StructureDefinition/thrill-ing';
+    instance2.baseDefinition = 'http://hl7.org/fhir/StructureDefinition/Condition';
+    instance2.fhirVersion = '4.0.1';
+    pkg.instances.push(instance2);
+    // Instance[3]: TwoWolves / two-wolves / Extension
+    const instance3 = new InstanceDefinition();
+    instance3._instanceMeta.name = 'TwoWolves';
+    instance3._instanceMeta.usage = 'Definition';
+    instance3.id = 'two-wolves';
+    instance3.resourceType = 'StructureDefinition';
+    instance3.derivation = 'constraint';
+    instance3.type = 'Extension';
+    instance3.url = 'http://hl7.org/fhir/us/minimal/StructureDefinition/two-wolves';
+    instance3.baseDefinition = 'http://hl7.org/fhir/StructureDefinition/Extension';
+    instance3.fhirVersion = '4.0.1';
+    pkg.instances.push(instance3);
+    // Instance[4]: Spices / spices / CodeSystem
+    const instance4 = new InstanceDefinition();
+    instance4._instanceMeta.name = 'Spices';
+    instance4._instanceMeta.usage = 'Definition';
+    instance4.id = 'spices';
+    instance4.resourceType = 'CodeSystem';
+    instance4.url = 'http://hl7.org/fhir/us/minimal/CodeSystem/spices';
+    instance4.content = 'complete';
+    instance4.version = '4.0.1';
+    pkg.instances.push(instance4);
+    // Instance[5]: Cookies / cookie-varieties / ValueSet
+    const instance5 = new InstanceDefinition();
+    instance5._instanceMeta.name = 'Cookies';
+    instance5._instanceMeta.usage = 'Definition';
+    instance5.id = 'cookie-varieties';
+    instance5.resourceType = 'ValueSet';
+    instance5.url = 'http://hl7.org/fhir/us/minimal/ValueSet/cookie-varieties';
+    instance5.version = '4.0.1';
+    pkg.instances.push(instance5);
+    // Instance[6]: SpiderHouse / spider-house / Logical
+    const instance6 = new InstanceDefinition();
+    instance6._instanceMeta.name = 'SpiderHouse';
+    instance6._instanceMeta.usage = 'Definition';
+    instance6.id = 'spider-house';
+    instance6.resourceType = 'StructureDefinition';
+    instance6.derivation = 'specialization';
+    instance6.kind = 'logical';
+    instance6.url = 'http://hl7.org/fhir/us/minimal/StructureDefinition/spider-house';
+    instance6.baseDefinition = 'http://hl7.org/fhir/StructureDefinition/Base';
+    instance6.fhirVersion = '4.0.1';
+    pkg.instances.push(instance6);
+    // Instance[7]: Zone / zone / Resource
+    const instance7 = new InstanceDefinition();
+    instance7._instanceMeta.name = 'Zone';
+    instance7._instanceMeta.usage = 'Definition';
+    instance7.id = 'zone';
+    instance7.resourceType = 'StructureDefinition';
+    instance7.derivation = 'specialization';
+    instance7.kind = 'resource';
+    instance7.url = 'http://hl7.org/fhir/us/minimal/StructureDefinition/zone';
+    instance7.baseDefinition = '';
+    instance7.fhirVersion = '4.0.1';
+    pkg.instances.push(instance7);
   });
 
   describe('#fishForFHIR()', () => {
@@ -134,6 +201,21 @@ describe('Package', () => {
       ).toEqual(funnyProfile);
     });
 
+    it('should find instances of profiles', () => {
+      const thrillingProfile = pkg.fishForFHIR('thrill-ing', Type.Profile);
+      expect(thrillingProfile.url).toBe(
+        'http://hl7.org/fhir/us/minimal/StructureDefinition/thrill-ing'
+      );
+      expect(thrillingProfile.fhirVersion).toBe('4.0.1');
+      expect(pkg.fishForFHIR('Thrilling', Type.Profile)).toEqual(thrillingProfile);
+      expect(
+        pkg.fishForFHIR(
+          'http://hl7.org/fhir/us/minimal/StructureDefinition/thrill-ing',
+          Type.Profile
+        )
+      ).toEqual(thrillingProfile);
+    });
+
     it('should find extensions', () => {
       const poorTasteExtensionByID = pkg.fishForFHIR('poor-taste', Type.Extension);
       expect(poorTasteExtensionByID.url).toBe(
@@ -149,6 +231,21 @@ describe('Package', () => {
       ).toEqual(poorTasteExtensionByID);
     });
 
+    it('should find instances of extensions', () => {
+      const twoWolvesExtension = pkg.fishForFHIR('two-wolves', Type.Extension);
+      expect(twoWolvesExtension.url).toBe(
+        'http://hl7.org/fhir/us/minimal/StructureDefinition/two-wolves'
+      );
+      expect(twoWolvesExtension.fhirVersion).toBe('4.0.1');
+      expect(pkg.fishForFHIR('TwoWolves', Type.Extension)).toEqual(twoWolvesExtension);
+      expect(
+        pkg.fishForFHIR(
+          'http://hl7.org/fhir/us/minimal/StructureDefinition/two-wolves',
+          Type.Extension
+        )
+      ).toEqual(twoWolvesExtension);
+    });
+
     it('should find logicals', () => {
       const beerLogical = pkg.fishForFHIR('wheat-beer', Type.Logical);
       expect(beerLogical.url).toBe('http://hl7.org/fhir/us/minimal/StructureDefinition/wheat-beer');
@@ -162,6 +259,21 @@ describe('Package', () => {
       ).toEqual(beerLogical);
     });
 
+    it('should find instances of logicals', () => {
+      const spiderHouse = pkg.fishForFHIR('spider-house', Type.Logical);
+      expect(spiderHouse.url).toBe(
+        'http://hl7.org/fhir/us/minimal/StructureDefinition/spider-house'
+      );
+      expect(spiderHouse.fhirVersion).toBe('4.0.1');
+      expect(pkg.fishForFHIR('SpiderHouse', Type.Logical)).toEqual(spiderHouse);
+      expect(
+        pkg.fishForFHIR(
+          'http://hl7.org/fhir/us/minimal/StructureDefinition/spider-house',
+          Type.Logical
+        )
+      ).toEqual(spiderHouse);
+    });
+
     it('should find resources', () => {
       const destination = pkg.fishForFHIR('Destination', Type.Resource);
       expect(destination.url).toBe(
@@ -177,6 +289,16 @@ describe('Package', () => {
       ).toEqual(destination);
     });
 
+    it('should find instances of resources', () => {
+      const zoneResource = pkg.fishForFHIR('zone', Type.Resource);
+      expect(zoneResource.url).toBe('http://hl7.org/fhir/us/minimal/StructureDefinition/zone');
+      expect(zoneResource.fhirVersion).toBe('4.0.1');
+      expect(pkg.fishForFHIR('Zone', Type.Resource)).toEqual(zoneResource);
+      expect(
+        pkg.fishForFHIR('http://hl7.org/fhir/us/minimal/StructureDefinition/zone', Type.Resource)
+      ).toEqual(zoneResource);
+    });
+
     it('should find value sets', () => {
       const soupsValueSetByID = pkg.fishForFHIR('soup-flavors', Type.ValueSet);
       expect(soupsValueSetByID.url).toBe('http://hl7.org/fhir/us/minimal/ValueSet/soup-flavors');
@@ -189,6 +311,18 @@ describe('Package', () => {
       ).toEqual(soupsValueSetByID);
     });
 
+    it('should find instances of value sets', () => {
+      const cookiesValueSetByID = pkg.fishForFHIR('cookie-varieties', Type.ValueSet);
+      expect(cookiesValueSetByID.url).toBe(
+        'http://hl7.org/fhir/us/minimal/ValueSet/cookie-varieties'
+      );
+      expect(cookiesValueSetByID.version).toBe('4.0.1');
+      expect(pkg.fishForFHIR('Cookies', Type.ValueSet)).toEqual(cookiesValueSetByID);
+      expect(
+        pkg.fishForFHIR('http://hl7.org/fhir/us/minimal/ValueSet/cookie-varieties', Type.ValueSet)
+      ).toEqual(cookiesValueSetByID);
+    });
+
     it('should find code systems', () => {
       const numericsCodeSystemByID = pkg.fishForFHIR('numerics', Type.CodeSystem);
       expect(numericsCodeSystemByID.url).toBe('http://hl7.org/fhir/us/minimal/CodeSystem/numerics');
@@ -201,6 +335,16 @@ describe('Package', () => {
       ).toEqual(numericsCodeSystemByID);
     });
 
+    it('should find instances of code systems', () => {
+      const spicesCodeSystemByID = pkg.fishForFHIR('spices', Type.CodeSystem);
+      expect(spicesCodeSystemByID.url).toBe('http://hl7.org/fhir/us/minimal/CodeSystem/spices');
+      expect(spicesCodeSystemByID.version).toBe('4.0.1');
+      expect(pkg.fishForFHIR('Spices', Type.CodeSystem)).toEqual(spicesCodeSystemByID);
+      expect(
+        pkg.fishForFHIR('http://hl7.org/fhir/us/minimal/CodeSystem/spices', Type.CodeSystem)
+      ).toEqual(spicesCodeSystemByID);
+    });
+
     it('should find instances', () => {
       const drSueInstanceById = pkg.fishForFHIR('dr-sue', Type.Instance);
       expect(drSueInstanceById.id).toBe('dr-sue');
@@ -221,6 +365,17 @@ describe('Package', () => {
       );
       expect(funnyProfileByID).toBeUndefined();
 
+      const thrillingProfileByID = pkg.fishForFHIR(
+        'thrill-ing',
+        Type.Resource,
+        Type.Logical,
+        Type.Type,
+        Type.Extension,
+        Type.ValueSet,
+        Type.CodeSystem
+      );
+      expect(thrillingProfileByID).toBeUndefined();
+
       const poorTasteExtensionByID = pkg.fishForFHIR(
         'poor-taste',
         Type.Resource,
@@ -233,6 +388,17 @@ describe('Package', () => {
       );
       expect(poorTasteExtensionByID).toBeUndefined();
 
+      const twoWolvesExtensionByID = pkg.fishForFHIR(
+        'two-wolves',
+        Type.Resource,
+        Type.Logical,
+        Type.Type,
+        Type.Profile,
+        Type.ValueSet,
+        Type.CodeSystem
+      );
+      expect(twoWolvesExtensionByID).toBeUndefined();
+
       const wheatBeerLogicalByID = pkg.fishForFHIR(
         'wheat-beer',
         Type.Resource,
diff --git a/test/export/StructureDefinition.ExtensionExporter.test.ts b/test/export/StructureDefinition.ExtensionExporter.test.ts
index 32bd12e..e0536df 100644
--- a/test/export/StructureDefinition.ExtensionExporter.test.ts
+++ b/test/export/StructureDefinition.ExtensionExporter.test.ts
@@ -1,12 +1,12 @@
 import { StructureDefinitionExporter, Package } from '../../src/export';
 import { FSHTank, FSHDocument } from '../../src/import';
 import { FHIRDefinitions, loadFromPath } from '../../src/fhirdefs';
-import { Extension } from '../../src/fshtypes';
+import { Extension, Instance, FshCode } from '../../src/fshtypes';
 import { loggerSpy } from '../testhelpers/loggerSpy';
 import { TestFisher } from '../testhelpers';
 import path from 'path';
 import { minimalConfig } from '../utils/minimalConfig';
-import { ContainsRule } from '../../src/fshtypes/rules';
+import { ContainsRule, AssignmentRule } from '../../src/fshtypes/rules';
 
 describe('ExtensionExporter', () => {
   let defs: FHIRDefinitions;
@@ -163,4 +163,57 @@ describe('ExtensionExporter', () => {
 
     expect(loggerSpy.getAllLogs('error')).toHaveLength(0);
   });
+
+  it('should export extensions with extension instance parents', () => {
+    const parentExtensionInstance = new Instance('ParentExtension');
+    parentExtensionInstance.instanceOf = 'StructureDefinition';
+    parentExtensionInstance.usage = 'Definition';
+    const parentName = new AssignmentRule('name');
+    parentName.value = 'ParentExtension';
+    const parentStatus = new AssignmentRule('status');
+    parentStatus.value = new FshCode('active');
+    const parentKind = new AssignmentRule('kind');
+    parentKind.value = new FshCode('resource');
+    const parentAbstract = new AssignmentRule('abstract');
+    parentAbstract.value = false;
+    const parentType = new AssignmentRule('type');
+    parentType.value = 'Extension';
+    const parentDerivation = new AssignmentRule('derivation');
+    parentDerivation.value = new FshCode('constraint');
+    const parentBaseDefinition = new AssignmentRule('baseDefinition');
+    parentBaseDefinition.value = 'http://hl7.org/fhir/StructureDefinition/Extension';
+    const parentElementId = new AssignmentRule('snapshot.element[0].id');
+    parentElementId.value = 'Extension';
+    const parentElementPath = new AssignmentRule('snapshot.element[0].path');
+    parentElementPath.value = 'Extension';
+    const parentUrlId = new AssignmentRule('snapshot.element[1].id');
+    parentUrlId.value = 'Extension.url';
+    const parentUrlPath = new AssignmentRule('snapshot.element[1].path');
+    parentUrlPath.value = 'Extension.url';
+    parentExtensionInstance.rules.push(
+      parentName,
+      parentStatus,
+      parentKind,
+      parentAbstract,
+      parentType,
+      parentDerivation,
+      parentBaseDefinition,
+      parentElementId,
+      parentElementPath,
+      parentUrlId,
+      parentUrlPath
+    );
+    doc.instances.set(parentExtensionInstance.name, parentExtensionInstance);
+
+    const childExtension = new Extension('ChildExtension');
+    childExtension.parent = 'ParentExtension';
+    doc.extensions.set(childExtension.name, childExtension);
+    const exported = exporter.export().extensions;
+    expect(exported.length).toBe(1);
+    expect(exported[0].name).toBe('ChildExtension');
+    expect(exported[0].baseDefinition).toBe(
+      'http://hl7.org/fhir/us/minimal/StructureDefinition/ParentExtension'
+    );
+    expect(loggerSpy.getAllMessages('error').length).toBe(0);
+  });
 });
diff --git a/test/export/StructureDefinition.ProfileExporter.test.ts b/test/export/StructureDefinition.ProfileExporter.test.ts
index 535149f..d3bd3ea 100644
--- a/test/export/StructureDefinition.ProfileExporter.test.ts
+++ b/test/export/StructureDefinition.ProfileExporter.test.ts
@@ -2,11 +2,16 @@ import path from 'path';
 import { StructureDefinitionExporter, Package } from '../../src/export';
 import { FSHTank, FSHDocument } from '../../src/import';
 import { FHIRDefinitions, loadFromPath } from '../../src/fhirdefs';
-import { Profile, Instance } from '../../src/fshtypes';
+import { Profile, Instance, FshCode } from '../../src/fshtypes';
 import { loggerSpy } from '../testhelpers/loggerSpy';
 import { TestFisher } from '../testhelpers';
 import { minimalConfig } from '../utils/minimalConfig';
-import { BindingRule, CaretValueRule, ContainsRule } from '../../src/fshtypes/rules';
+import {
+  BindingRule,
+  CaretValueRule,
+  ContainsRule,
+  AssignmentRule
+} from '../../src/fshtypes/rules';
 
 describe('ProfileExporter', () => {
   let defs: FHIRDefinitions;
@@ -184,6 +189,53 @@ describe('ProfileExporter', () => {
     expect(loggerSpy.getAllMessages('error').length).toBe(0);
   });
 
+  it('should export profiles with profile instance parents', () => {
+    const parentProfileInstance = new Instance('ParentProfile');
+    parentProfileInstance.instanceOf = 'StructureDefinition';
+    parentProfileInstance.usage = 'Definition';
+    const parentName = new AssignmentRule('name');
+    parentName.value = 'ParentProfile';
+    const parentStatus = new AssignmentRule('status');
+    parentStatus.value = new FshCode('active');
+    const parentKind = new AssignmentRule('kind');
+    parentKind.value = new FshCode('resource');
+    const parentAbstract = new AssignmentRule('abstract');
+    parentAbstract.value = false;
+    const parentType = new AssignmentRule('type');
+    parentType.value = 'Observation';
+    const parentDerivation = new AssignmentRule('derivation');
+    parentDerivation.value = new FshCode('constraint');
+    const parentBaseDefinition = new AssignmentRule('baseDefinition');
+    parentBaseDefinition.value = 'http://hl7.org/fhir/StructureDefinition/Observation';
+    const parentElementId = new AssignmentRule('snapshot.element[0].id');
+    parentElementId.value = 'Observation';
+    const parentElementPath = new AssignmentRule('snapshot.element[0].path');
+    parentElementPath.value = 'Observation';
+    parentProfileInstance.rules.push(
+      parentName,
+      parentStatus,
+      parentKind,
+      parentAbstract,
+      parentType,
+      parentDerivation,
+      parentBaseDefinition,
+      parentElementId,
+      parentElementPath
+    );
+    doc.instances.set(parentProfileInstance.name, parentProfileInstance);
+
+    const childProfile = new Profile('ChildProfile');
+    childProfile.parent = 'ParentProfile';
+    doc.profiles.set(childProfile.name, childProfile);
+    const exported = exporter.export().profiles;
+    expect(exported).toHaveLength(1);
+    expect(exported[0].name).toBe('ChildProfile');
+    expect(exported[0].baseDefinition).toBe(
+      'http://hl7.org/fhir/us/minimal/StructureDefinition/ParentProfile'
+    );
+    expect(loggerSpy.getAllMessages('error').length).toBe(0);
+  });
+
   it('should defer adding an instance to a profile as a contained resource', () => {
     const instance = new Instance('myResource');
     instance.instanceOf = 'Observation';
diff --git a/test/import/FSHTank.test.ts b/test/import/FSHTank.test.ts
index ebf5fcf..7a0ea7b 100644
--- a/test/import/FSHTank.test.ts
+++ b/test/import/FSHTank.test.ts
@@ -15,6 +15,7 @@ import {
 } from '../../src/fshtypes';
 import { Metadata, Type } from '../../src/utils/Fishable';
 import { minimalConfig } from '../utils/minimalConfig';
+import { AssignmentRule } from '../../src/fshtypes/rules';
 
 describe('FSHTank', () => {
   let tank: FSHTank;
@@ -27,6 +28,12 @@ describe('FSHTank', () => {
     doc1.profiles.set('Profile2', new Profile('Profile2'));
     doc1.profiles.get('Profile2').id = 'prf2';
     doc1.profiles.get('Profile2').parent = 'Observation';
+    doc1.instances.set('ProfileInstance', new Instance('ProfileInstance'));
+    doc1.instances.get('ProfileInstance').instanceOf = 'StructureDefinition';
+    doc1.instances.get('ProfileInstance').usage = 'Definition';
+    const profileInstanceDerivation = new AssignmentRule('derivation');
+    profileInstanceDerivation.value = new FshCode('constraint');
+    doc1.instances.get('ProfileInstance').rules.push(profileInstanceDerivation);
     doc1.extensions.set('Extension1', new Extension('Extension1'));
     doc1.extensions.get('Extension1').id = 'ext1';
     doc1.extensions.get('Extension1').parent = 'Extension2';
@@ -34,8 +41,21 @@ describe('FSHTank', () => {
     doc2.aliases.set('BAR', 'http://bar.com');
     doc2.extensions.set('Extension2', new Extension('Extension2'));
     doc2.extensions.get('Extension2').id = 'ext2';
+    doc2.instances.set('ExtensionInstance', new Instance('ExtensionInstance'));
+    doc2.instances.get('ExtensionInstance').instanceOf = 'StructureDefinition';
+    doc2.instances.get('ExtensionInstance').usage = 'Definition';
+    const extensionInstanceDerivation = new AssignmentRule('derivation');
+    extensionInstanceDerivation.value = new FshCode('constraint');
+    const extensionInstanceType = new AssignmentRule('type');
+    extensionInstanceType.value = 'Extension';
+    doc2.instances
+      .get('ExtensionInstance')
+      .rules.push(extensionInstanceDerivation, extensionInstanceType);
     doc2.valueSets.set('ValueSet1', new FshValueSet('ValueSet1'));
     doc2.valueSets.get('ValueSet1').id = 'vs1';
+    doc2.instances.set('ValueSetInstance', new Instance('ValueSetInstance'));
+    doc2.instances.get('ValueSetInstance').instanceOf = 'ValueSet';
+    doc2.instances.get('ValueSetInstance').usage = 'Definition';
     doc2.codeSystems.set('CodeSystem1', new FshCodeSystem('CodeSystem1'));
     doc2.codeSystems.get('CodeSystem1').id = 'cs1';
     doc2.instances.set('CodeSystemInstance', new Instance('CodeSystemInstance'));
@@ -44,6 +64,16 @@ describe('FSHTank', () => {
     doc2.logicals.set('Logical1', new Logical('Logical1'));
     doc2.logicals.get('Logical1').id = 'log1';
     doc2.logicals.get('Logical1').parent = 'Element';
+    doc2.instances.set('LogicalInstance', new Instance('LogicalInstance'));
+    doc2.instances.get('LogicalInstance').instanceOf = 'StructureDefinition';
+    doc2.instances.get('LogicalInstance').usage = 'Definition';
+    const logicalInstanceDerivation = new AssignmentRule('derivation');
+    logicalInstanceDerivation.value = new FshCode('specialization');
+    const logicalInstanceKind = new AssignmentRule('kind');
+    logicalInstanceKind.value = new FshCode('logical');
+    doc2.instances
+      .get('LogicalInstance')
+      .rules.push(logicalInstanceDerivation, logicalInstanceKind);
     doc2.resources.set('Resource1', new Resource('Resource1'));
     doc2.resources.get('Resource1').id = 'res1';
     const doc3 = new FSHDocument('doc3.fsh');
@@ -52,6 +82,16 @@ describe('FSHTank', () => {
     doc3.logicals.get('Logical2').parent = 'Logical1';
     doc3.resources.set('Resource2', new Resource('Resource2'));
     doc3.resources.get('Resource2').id = 'res2';
+    doc3.instances.set('ResourceInstance', new Instance('ResourceInstance'));
+    doc3.instances.get('ResourceInstance').instanceOf = 'StructureDefinition';
+    doc3.instances.get('ResourceInstance').usage = 'Definition';
+    const resourceInstanceDerivation = new AssignmentRule('derivation');
+    resourceInstanceDerivation.value = new FshCode('specialization');
+    const resourceInstanceKind = new AssignmentRule('kind');
+    resourceInstanceKind.value = new FshCode('resource');
+    doc3.instances
+      .get('ResourceInstance')
+      .rules.push(resourceInstanceDerivation, resourceInstanceKind);
     doc3.valueSets.set('ValueSet2', new FshValueSet('ValueSet2'));
     doc3.valueSets.get('ValueSet2').id = 'vs2';
     doc3.codeSystems.set('CodeSystem2', new FshCodeSystem('CodeSystem2'));
@@ -135,6 +175,34 @@ describe('FSHTank', () => {
       ).toBeUndefined();
     });
 
+    it('should find profiles defined as instance definitions when profiles are requested', () => {
+      expect(tank.fish('ProfileInstance', Type.Profile).name).toBe('ProfileInstance');
+      expect(
+        tank.fish(
+          'ProfileInstance',
+          Type.Extension,
+          Type.ValueSet,
+          Type.CodeSystem,
+          Type.Invariant,
+          Type.RuleSet,
+          Type.Mapping,
+          Type.Logical,
+          Type.Resource,
+          Type.Type
+        )
+      ).toBeUndefined();
+    });
+
+    it('should not find instances where the derivation is not constraint when profiles are requested', () => {
+      const instance = tank.docs[0].instances.get('ProfileInstance');
+      const profileInstanceDerivation = new AssignmentRule('derivation');
+      profileInstanceDerivation.value = new FshCode('specialization');
+      instance.rules = [profileInstanceDerivation];
+      expect(tank.fish('ProfileInstance', Type.Profile)).toBeUndefined();
+      instance.rules = [];
+      expect(tank.fish('ProfileInstance', Type.Profile)).toBeUndefined();
+    });
+
     it('should only find extensions when extensions are requested', () => {
       expect(tank.fish('ext1', Type.Extension).name).toBe('Extension1');
       expect(
@@ -154,6 +222,48 @@ describe('FSHTank', () => {
       ).toBeUndefined();
     });
 
+    it('should find extensions defined as instance definitions when extensions are requested', () => {
+      expect(tank.fish('ExtensionInstance', Type.Extension).name).toBe('ExtensionInstance');
+      expect(
+        tank.fish(
+          'ExtensionInstance',
+          Type.Profile,
+          Type.ValueSet,
+          Type.CodeSystem,
+          Type.Invariant,
+          Type.RuleSet,
+          Type.Mapping,
+          Type.Logical,
+          Type.Resource,
+          Type.Type
+        )
+      ).toBeUndefined();
+    });
+
+    it('should not find instances where the type is not Extension when extensions are requested', () => {
+      const instance = tank.docs[1].instances.get('ExtensionInstance');
+      const extensionInstanceType = new AssignmentRule('type');
+      extensionInstanceType.value = 'Observation';
+      const extensionInstanceDerivation = new AssignmentRule('derivation');
+      extensionInstanceDerivation.value = new FshCode('specialization');
+      instance.rules = [extensionInstanceType, extensionInstanceDerivation];
+      expect(tank.fish('ExtensionInstance', Type.Extension)).toBeUndefined();
+      instance.rules = [];
+      expect(tank.fish('ExtensionInstance', Type.Extension)).toBeUndefined();
+    });
+
+    it('should not find instances where the derivation is not constraint when extensions are requested', () => {
+      const instance = tank.docs[1].instances.get('ExtensionInstance');
+      const extensionInstanceType = new AssignmentRule('type');
+      extensionInstanceType.value = 'Extension';
+      const extensionInstanceDerivation = new AssignmentRule('derivation');
+      extensionInstanceDerivation.value = new FshCode('specialization');
+      instance.rules = [extensionInstanceType, extensionInstanceDerivation];
+      expect(tank.fish('ExtensionInstance', Type.Extension)).toBeUndefined();
+      instance.rules = [];
+      expect(tank.fish('ExtensionInstance', Type.Extension)).toBeUndefined();
+    });
+
     it('should only find logical models when logical models are requested', () => {
       expect(tank.fish('log1', Type.Logical).name).toBe('Logical1');
       expect(
@@ -173,6 +283,24 @@ describe('FSHTank', () => {
       ).toBeUndefined();
     });
 
+    it('should find logical models defined as instance definitions when logical models are requested', () => {
+      expect(tank.fish('LogicalInstance', Type.Logical).name).toBe('LogicalInstance');
+      expect(
+        tank.fish(
+          'LogicalInstance',
+          Type.Extension,
+          Type.Profile,
+          Type.ValueSet,
+          Type.CodeSystem,
+          Type.Invariant,
+          Type.RuleSet,
+          Type.Mapping,
+          Type.Resource,
+          Type.Type
+        )
+      ).toBeUndefined();
+    });
+
     it('should only find resources when resources are requested', () => {
       expect(tank.fish('res2', Type.Resource).name).toBe('Resource2');
       expect(
@@ -192,6 +320,24 @@ describe('FSHTank', () => {
       ).toBeUndefined();
     });
 
+    it('should find resources defined as instance definitions when resources are requested', () => {
+      expect(tank.fish('ResourceInstance', Type.Resource).name).toBe('ResourceInstance');
+      expect(
+        tank.fish(
+          'ResourceInstanceres2',
+          Type.Extension,
+          Type.Profile,
+          Type.ValueSet,
+          Type.CodeSystem,
+          Type.Invariant,
+          Type.RuleSet,
+          Type.Mapping,
+          Type.Logical,
+          Type.Type
+        )
+      ).toBeUndefined();
+    });
+
     it('should only find valuesets when valuesets are requested', () => {
       expect(tank.fish('vs1', Type.ValueSet).name).toBe('ValueSet1');
       expect(
@@ -211,6 +357,24 @@ describe('FSHTank', () => {
       ).toBeUndefined();
     });
 
+    it('should find valuesets defined as instance definitions when valuesets are requested', () => {
+      expect(tank.fish('ValueSetInstance', Type.ValueSet).name).toBe('ValueSetInstance');
+      expect(
+        tank.fish(
+          'ValueSetInstance',
+          Type.Profile,
+          Type.Extension,
+          Type.CodeSystem,
+          Type.Invariant,
+          Type.RuleSet,
+          Type.Mapping,
+          Type.Logical,
+          Type.Resource,
+          Type.Type
+        )
+      ).toBeUndefined();
+    });
+
     it('should only find codesystems when codesystems are requested', () => {
       expect(tank.fish('cs1', Type.CodeSystem).name).toBe('CodeSystem1');
       expect(