Repository: FHIR/sushi (ts)

Issue:
Trying to create association Cannot set property 'fixedUri' of undefined  
## Input
```
Profile:        AnvilResearchStudy
Parent:         ResearchStudy
Id:             anvil-research-study
Title:          "AnVIL ResearchStudy"
Description:    "An AnVIL ResearchStudy."
* extension contains 
    AnvilDocumentReference named documents 0..*

Extension:      AnvilDocumentReference
Parent:         DocumentReference
Id:             anvil-document-reference
Title:          "AnVIL document reference"
Description:    "An association to a document."


```

## Output
```
error Cannot set property 'fixedUri' of undefined
  File: /src/AnVIL.fsh
  Line: 9 - 13
```
SUSHI should log an error if an Extension declares a non-Extension as its parent
Title says it all but also see #387 for context.

Pull request description:
Enforce that Extension Parent is Extension
Fixes #387 and fixes #388.

We used to allow Extensions to set non-Extensions as their Parent. This PR enforces that the Parent of an Extension is an Extension.

Patch:
diff --git a/src/errors/InvalidExtensionParentError.ts b/src/errors/InvalidExtensionParentError.ts
new file mode 100644
index 0000000..bb51bc4
--- /dev/null
+++ b/src/errors/InvalidExtensionParentError.ts
@@ -0,0 +1,10 @@
+import { WithSource } from './WithSource';
+import { SourceInfo } from '../fshtypes';
+
+export class InvalidExtensionParentError extends Error implements WithSource {
+  constructor(public childName: string, public parentName: string, public sourceInfo: SourceInfo) {
+    super(
+      `Parent ${parentName} is not of type Extension, so it is an invalid Parent for Extension ${childName}.`
+    );
+  }
+}
diff --git a/src/errors/index.ts b/src/errors/index.ts
index b428c98..3bddbe0 100644
--- a/src/errors/index.ts
+++ b/src/errors/index.ts
@@ -40,3 +40,4 @@ export * from './InvalidFHIRIdError';
 export * from './ParentDeclaredAsProfileNameError';
 export * from './InvalidResourceTypeError';
 export * from './FixingNonResourceError';
+export * from './InvalidExtensionParentError';
diff --git a/src/export/StructureDefinitionExporter.ts b/src/export/StructureDefinitionExporter.ts
index 03e1e68..2255bfc 100644
--- a/src/export/StructureDefinitionExporter.ts
+++ b/src/export/StructureDefinitionExporter.ts
@@ -10,7 +10,8 @@ import { FSHTank } from '../import';
 import {
   ParentNotDefinedError,
   ParentDeclaredAsProfileNameError,
-  InvalidFHIRIdError
+  InvalidFHIRIdError,
+  InvalidExtensionParentError
 } from '../errors';
 import {
   CardRule,
@@ -430,6 +431,12 @@ export class StructureDefinitionExporter implements Fishable {
     // If we still don't have a resolution, then it's not defined
     if (!json) {
       throw new ParentNotDefinedError(fshDefinition.name, parentName, fshDefinition.sourceInfo);
+    } else if (fshDefinition instanceof Extension && json.type !== 'Extension') {
+      throw new InvalidExtensionParentError(
+        fshDefinition.name,
+        parentName,
+        fshDefinition.sourceInfo
+      );
     }
 
     const structDef = StructureDefinition.fromJSON(json);


Test patch:
diff --git a/test/export/StructureDefinition.ExtensionExporter.test.ts b/test/export/StructureDefinition.ExtensionExporter.test.ts
index b69ea8e..6140635 100644
--- a/test/export/StructureDefinition.ExtensionExporter.test.ts
+++ b/test/export/StructureDefinition.ExtensionExporter.test.ts
@@ -72,6 +72,16 @@ describe('ExtensionExporter', () => {
     expect(loggerSpy.getLastMessage('error')).toMatch(/File: Wrong\.fsh.*Line: 14 - 24\D*/s);
   });
 
+  it('should log a message with source information when the parent is not an extension', () => {
+    const extension = new Extension('Wrong').withFile('Wrong.fsh').withLocation([14, 8, 24, 17]);
+    extension.parent = 'Patient';
+    doc.extensions.set(extension.name, extension);
+    exporter.export();
+    expect(loggerSpy.getLastMessage('error')).toMatch(
+      /Parent Patient is not of type Extension, so it is an invalid Parent for Extension Wrong.*File: Wrong\.fsh.*Line: 14 - 24\D*/s
+    );
+  });
+
   it('should export extensions with FSHy parents', () => {
     const extensionFoo = new Extension('Foo');
     const extensionBar = new Extension('Bar');