Repository: FHIR/sushi (ts)

Issue:
Parameterized RuleSets not working with hierarchical code system
If you use RuleSets to add codes in a code system, SUSHI complains if you try to add nested codes.  E.g.:
```
RuleSet: AddCode(code, display, definition)
* {code} {display} {definition}

CodeSystem: MyCodeSystemWithRuleSets
* insert AddCode(#MyCode, "MyCode", "This is my code")
* insert AddCode(#MyCode #SubCode, "SubCode", "This is my sub-code")
```

The above should work, but ends up with an error like this:
> error CodeSystem MyCodeSystemWithRuleSets does not have the specified hierarchy to add code SubCode

Similarly, `#MyCode insert AddCode(#SubCode, "SubCode", "This is my sub-code")` also fails.

FSH School example for the above (and other broken variations): https://fshschool.org/FSHOnline/#/share/3htxOHF

More advanced example that led to discovering this issue: https://fshschool.org/FSHOnline/#/share/3VVkR8o

Pull request description:
Use inserted rules when checking new concepts
Fixes #1190 and completes task [CIMPL-1049](https://standardhealthrecord.atlassian.net/browse/CIMPL-1049).

When applying insert rules on a CodeSystem, provide the expanded rules when checking new concepts. This way, concepts added by RuleSets can provide a hierarchy. Additionally, this allows for duplicate concepts to be detected when they are added by RuleSets.

In addition to the new tests, you can try running the FSH samples from the issue description. The two example CodeSystems that use insert rules should work correctly with this update.

Patch:
diff --git a/src/errors/CodeSystemDuplicateCodeError.ts b/src/errors/CodeSystemDuplicateCodeError.ts
deleted file mode 100644
index bff4f2b..0000000
--- a/src/errors/CodeSystemDuplicateCodeError.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import { Annotated } from './Annotated';
-
-export class CodeSystemDuplicateCodeError extends Error implements Annotated {
-  specReferences = ['http://hl7.org/fhir/codesystem.html#invs'];
-  constructor(system: string, code: string) {
-    super(`CodeSystem ${system} already contains code ${code}.`);
-  }
-}
diff --git a/src/errors/CodeSystemIncorrectHierarchyError.ts b/src/errors/CodeSystemIncorrectHierarchyError.ts
deleted file mode 100644
index 6e4cb93..0000000
--- a/src/errors/CodeSystemIncorrectHierarchyError.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-export class CodeSystemIncorrectHierarchyError extends Error {
-  constructor(public system: string, public code: string) {
-    super(`CodeSystem ${system} does not have the specified hierarchy to add code ${code}`);
-  }
-}
diff --git a/src/errors/index.ts b/src/errors/index.ts
index 6baa70f..66d8401 100644
--- a/src/errors/index.ts
+++ b/src/errors/index.ts
@@ -6,8 +6,6 @@ export * from './NoSingleTypeError';
 export * from './MismatchedTypeError';
 export * from './CodeAndSystemMismatchError';
 export * from './CodedTypeNotFoundError';
-export * from './CodeSystemDuplicateCodeError';
-export * from './CodeSystemIncorrectHierarchyError';
 export * from './InvalidCanonicalUrlError';
 export * from './InvalidCardinalityError';
 export * from './InvalidDateTimeError';
diff --git a/src/export/CodeSystemExporter.ts b/src/export/CodeSystemExporter.ts
index 13f24f2..d2f725f 100644
--- a/src/export/CodeSystemExporter.ts
+++ b/src/export/CodeSystemExporter.ts
@@ -1,5 +1,5 @@
 import { FSHTank } from '../import/FSHTank';
-import { CodeSystem, CodeSystemConcept, StructureDefinition } from '../fhirtypes';
+import { CodeSystem, CodeSystemConcept } from '../fhirtypes';
 import {
   setPropertyOnDefinitionInstance,
   applyInsertRules,
@@ -8,9 +8,10 @@ import {
 import { FshCodeSystem } from '../fshtypes';
 import { CaretValueRule, ConceptRule } from '../fshtypes/rules';
 import { logger } from '../utils/FSHLogger';
-import { MasterFisher, Type, resolveSoftIndexing } from '../utils';
+import { MasterFisher, resolveSoftIndexing } from '../utils';
 import { Package } from '.';
 import { CannotResolvePathError } from '../errors';
+import { isEqual } from 'lodash';
 
 export class CodeSystemExporter {
   constructor(
@@ -40,42 +41,60 @@ export class CodeSystemExporter {
   private setConcepts(codeSystem: CodeSystem, concepts: ConceptRule[]): void {
     if (concepts.length > 0) {
       codeSystem.concept = [];
+      const existingConcepts = new Map<string, ConceptRule>();
       concepts.forEach(concept => {
-        let conceptContainer = codeSystem.concept;
-        const newConcept: CodeSystemConcept = { code: concept.code };
-        if (concept.display) {
-          newConcept.display = concept.display;
-        }
-        if (concept.definition) {
-          newConcept.definition = concept.definition;
-        }
-        for (const ancestorCode of concept.hierarchy) {
-          const ancestorConcept = conceptContainer.find(
-            ancestorConcept => ancestorConcept.code === ancestorCode
-          );
-          if (ancestorConcept) {
-            if (!ancestorConcept.concept) {
-              ancestorConcept.concept = [];
-            }
-            conceptContainer = ancestorConcept.concept;
-          } else {
+        const existingConcept = existingConcepts.get(concept.code);
+        if (existingConcept) {
+          // if this concept has only a code (and optionally, a hierarchy),
+          // and the existing concept has the same code and hierarchy,
+          // this concept may just be used to establish path context, which is fine.
+          if (
+            !(
+              concept.display == null &&
+              concept.definition == null &&
+              isEqual(concept.hierarchy, existingConcept.hierarchy)
+            )
+          ) {
+            // duplicates are prohibited: http://hl7.org/fhir/codesystem.html#invs
             logger.error(
-              `Could not find ${ancestorCode} in concept hierarchy to use as ancestor of ${concept.code}.`,
+              `CodeSystem ${codeSystem.id} already contains code ${concept.code}.`,
               concept.sourceInfo
             );
-            return;
           }
+        } else {
+          let conceptContainer = codeSystem.concept;
+          const newConcept: CodeSystemConcept = { code: concept.code };
+          if (concept.display) {
+            newConcept.display = concept.display;
+          }
+          if (concept.definition) {
+            newConcept.definition = concept.definition;
+          }
+          for (const ancestorCode of concept.hierarchy) {
+            const ancestorConcept = conceptContainer.find(
+              ancestorConcept => ancestorConcept.code === ancestorCode
+            );
+            if (ancestorConcept) {
+              if (!ancestorConcept.concept) {
+                ancestorConcept.concept = [];
+              }
+              conceptContainer = ancestorConcept.concept;
+            } else {
+              logger.error(
+                `Could not find ${ancestorCode} in concept hierarchy to use as ancestor of ${concept.code}.`,
+                concept.sourceInfo
+              );
+              return;
+            }
+          }
+          conceptContainer.push(newConcept);
+          existingConcepts.set(concept.code, concept);
         }
-        conceptContainer.push(newConcept);
       });
     }
   }
 
-  private setCaretPathRules(
-    codeSystem: CodeSystem,
-    csStructureDefinition: StructureDefinition,
-    rules: CaretValueRule[]
-  ) {
+  private setCaretPathRules(codeSystem: CodeSystem, rules: CaretValueRule[]) {
     // soft index resolution relies on the rule's path attribute.
     // a CaretValueRule is created with an empty path, so first
     // transform its arrayPath into a path.
@@ -171,16 +190,12 @@ export class CodeSystemExporter {
     }
     const codeSystem = new CodeSystem();
     this.setMetadata(codeSystem, fshDefinition);
-    const csStructureDefinition = StructureDefinition.fromJSON(
-      this.fisher.fishForFHIR('CodeSystem', Type.Resource)
-    );
     this.setConcepts(
       codeSystem,
       fshDefinition.rules.filter(rule => rule instanceof ConceptRule) as ConceptRule[]
     );
     this.setCaretPathRules(
       codeSystem,
-      csStructureDefinition,
       fshDefinition.rules.filter(rule => rule instanceof CaretValueRule) as CaretValueRule[]
     );
 
diff --git a/src/fhirtypes/common.ts b/src/fhirtypes/common.ts
index 93ce445..55d0ce7 100644
--- a/src/fhirtypes/common.ts
+++ b/src/fhirtypes/common.ts
@@ -707,25 +707,19 @@ export function applyInsertRules(
               ruleSetRuleClone.pathArray.unshift(...rule.pathArray);
             }
           }
-          if (ruleSetRuleClone instanceof ConceptRule && fshDefinition instanceof FshCodeSystem) {
+          if (
+            ruleSetRuleClone instanceof ConceptRule &&
+            fshDefinition instanceof FshCodeSystem &&
+            context
+          ) {
             // ConceptRules should not have a path context, so if one exists, show an error.
             // The concept is still added to the CodeSystem.
-            if (context) {
-              logger.error(
-                'Do not insert a RuleSet at a path when the RuleSet adds a concept.',
-                ruleSetRuleClone.sourceInfo
-              );
-            }
-            try {
-              if (fshDefinition.checkConcept(ruleSetRuleClone)) {
-                expandedRules.push(ruleSetRuleClone);
-              }
-            } catch (e) {
-              logger.error(e.message, ruleSetRuleClone.sourceInfo);
-            }
-          } else {
-            expandedRules.push(ruleSetRuleClone);
+            logger.error(
+              'Do not insert a RuleSet at a path when the RuleSet adds a concept.',
+              ruleSetRuleClone.sourceInfo
+            );
           }
+          expandedRules.push(ruleSetRuleClone);
           if (firstRule) {
             // Once one rule has been applied, all future rules should inherit the index used on that rule
             // rather than continuing to increment the index with the [+] operator
diff --git a/src/fshtypes/FshCodeSystem.ts b/src/fshtypes/FshCodeSystem.ts
index b19dcf2..4d48149 100644
--- a/src/fshtypes/FshCodeSystem.ts
+++ b/src/fshtypes/FshCodeSystem.ts
@@ -1,10 +1,7 @@
 import { FshEntity } from './FshEntity';
-import { CodeSystemDuplicateCodeError } from '../errors/CodeSystemDuplicateCodeError';
-import { CodeSystemIncorrectHierarchyError } from '../errors/CodeSystemIncorrectHierarchyError';
 import { CaretValueRule, InsertRule, ConceptRule } from './rules';
 import { EOL } from 'os';
 import { fshifyString, findIdCaretRule } from './common';
-import isEqual from 'lodash/isEqual';
 
 /**
  * For more information about a CodeSystem in FHIR,
@@ -38,51 +35,6 @@ export class FshCodeSystem extends FshEntity {
     this._id = id;
   }
 
-  addConcept(newConcept: ConceptRule) {
-    if (this.checkConcept(newConcept)) {
-      this.rules.push(newConcept);
-    }
-  }
-
-  checkConcept(newConcept: ConceptRule): boolean {
-    const existingConcept = this.rules.find(
-      rule => rule instanceof ConceptRule && rule.code == newConcept.code
-    ) as ConceptRule;
-    if (existingConcept) {
-      // if the new ConceptRule has only a code and a hierarchy,
-      // and the existing ConceptRule with that code has a matching hierarchy,
-      // the user may simply be referencing the existing concept to establish context.
-      if (
-        newConcept.display == null &&
-        newConcept.definition == null &&
-        isEqual(existingConcept.hierarchy, newConcept.hierarchy)
-      ) {
-        return false;
-      } else {
-        throw new CodeSystemDuplicateCodeError(this.id, newConcept.code);
-      }
-    }
-    // check the hierarchy, if applicable
-    // for each predecessor element in the new concept's hierarchy, we should be able to find a rule that
-    // 1. defines a concept
-    // 2. with the predecessor's code
-    // 3. and a hierarchy equal to everything before it in the new concept's hierarchy
-    newConcept.hierarchy.forEach((predecessor, i) => {
-      if (
-        !this.rules.some(rule => {
-          return (
-            rule instanceof ConceptRule &&
-            rule.code === predecessor &&
-            isEqual(rule.hierarchy, newConcept.hierarchy.slice(0, i))
-          );
-        })
-      ) {
-        throw new CodeSystemIncorrectHierarchyError(this.id, newConcept.code);
-      }
-    });
-    return true;
-  }
-
   metadataToFSH(): string {
     const resultLines: string[] = [];
     resultLines.push(`CodeSystem: ${this.name}`);
diff --git a/src/import/FSHImporter.ts b/src/import/FSHImporter.ts
index 84a4b51..2bb11cb 100644
--- a/src/import/FSHImporter.ts
+++ b/src/import/FSHImporter.ts
@@ -581,15 +581,7 @@ export class FSHImporter extends FSHVisitor {
       });
     csRuleCtx.forEach(ruleCtx => {
       const rule = this.visitCsRule(ruleCtx);
-      if (rule instanceof ConceptRule) {
-        try {
-          codeSystem.addConcept(rule);
-        } catch (e) {
-          logger.error(e.message, rule.sourceInfo);
-        }
-      } else if (rule) {
-        codeSystem.rules.push(rule);
-      }
+      codeSystem.rules.push(rule);
     });
   }
 


Test patch:
diff --git a/test/export/CodeSystemExporter.test.ts b/test/export/CodeSystemExporter.test.ts
index 01387fe..f307028 100644
--- a/test/export/CodeSystemExporter.test.ts
+++ b/test/export/CodeSystemExporter.test.ts
@@ -214,6 +214,62 @@ describe('CodeSystemExporter', () => {
     });
   });
 
+  it('should log an error when encountering a duplicate code', () => {
+    // CodeSystem: Zoo
+    // * #goat "A goat"
+    // * #goat "Another goat?"
+    const codeSystem = new FshCodeSystem('Zoo');
+    const goatCode = new ConceptRule('goat', 'A goat');
+    const anotherGoat = new ConceptRule('goat', 'Another goat?')
+      .withFile('Zoo.fsh')
+      .withLocation([3, 0, 3, 23]);
+    codeSystem.rules.push(goatCode, anotherGoat);
+
+    const exported = exporter.exportCodeSystem(codeSystem);
+    expect(exported.concept[0].code).toBe('goat');
+    expect(exported.concept[0].display).toBe('A goat');
+    expect(loggerSpy.getLastMessage('error')).toMatch(
+      /CodeSystem Zoo already contains code goat.*File: Zoo\.fsh.*Line: 3\D*/s
+    );
+  });
+
+  it('should not log an error when encountering a duplicate code if the new code has no display or definition', () => {
+    // CodeSystem: Zoo
+    // * #goat "A goat"
+    // * #goat
+    const codeSystem = new FshCodeSystem('Zoo');
+    const goatCode = new ConceptRule('goat', 'A goat');
+    const anotherGoat = new ConceptRule('goat');
+    codeSystem.rules.push(goatCode, anotherGoat);
+
+    const exported = exporter.exportCodeSystem(codeSystem);
+    expect(exported.concept).toHaveLength(1);
+    expect(exported.concept[0].code).toBe('goat');
+    expect(exported.concept[0].display).toBe('A goat');
+    expect(loggerSpy.getAllMessages('error')).toHaveLength(0);
+  });
+
+  it('should log an error when encountering a code with an incorrectly defined hierarchy', () => {
+    // CodeSystem: Zoo
+    // * #bear "Bear" "A member of family Ursidae."
+    // * #bear #sunbear #ursula "Ursula the sun bear"
+    const codeSystem = new FshCodeSystem('Zoo');
+    const bearCode = new ConceptRule('bear', 'Bear', 'A member of family Ursidae.');
+    const ursulaCode = new ConceptRule('ursula', 'Ursula the sun bear')
+      .withFile('Zoo.fsh')
+      .withLocation([3, 0, 3, 46]);
+    ursulaCode.hierarchy = ['bear', 'sunbear'];
+    codeSystem.rules.push(bearCode, ursulaCode);
+
+    const exported = exporter.exportCodeSystem(codeSystem);
+    expect(exported.concept[0].code).toBe('bear');
+    expect(exported.concept[0].display).toBe('Bear');
+    expect(exported.concept[0].concept).toBeNull();
+    expect(loggerSpy.getLastMessage('error')).toMatch(
+      /Could not find sunbear in concept hierarchy to use as ancestor of ursula.*File: Zoo\.fsh.*Line: 3\D*/s
+    );
+  });
+
   it('should warn when title and/or description is an empty string', () => {
     const codeSystem = new FshCodeSystem('MyCodeSystem');
     codeSystem.title = '';
@@ -973,6 +1029,61 @@ describe('CodeSystemExporter', () => {
       expect(loggerSpy.getAllMessages('error')).toHaveLength(0);
     });
 
+    it('should add nested concepts from an insert rule', () => {
+      // RuleSet: Bar
+      // * #main "MainCode"
+      //
+      // RuleSet: AnotherBar
+      // * #main #sub "SubCode"
+      //
+      // CodeSystem: Foo
+      // * insert Bar
+      // * insert AnotherBar
+      const mainCode = new ConceptRule('main', 'MainCode');
+      ruleSet.rules.push(mainCode);
+
+      const anotherRuleSet = new RuleSet('AnotherBar');
+      doc.ruleSets.set(anotherRuleSet.name, anotherRuleSet);
+      const subCode = new ConceptRule('sub', 'SubCode');
+      subCode.hierarchy = ['main'];
+      anotherRuleSet.rules.push(subCode);
+
+      const insertBar = new InsertRule('');
+      insertBar.ruleSet = 'Bar';
+      const insertAnother = new InsertRule('');
+      insertAnother.ruleSet = 'AnotherBar';
+      cs.rules.push(insertBar, insertAnother);
+
+      exporter.applyInsertRules();
+      const exported = exporter.exportCodeSystem(cs);
+      expect(exported.concept[0].code).toBe('main');
+      expect(exported.concept[0].concept[0].code).toBe('sub');
+      expect(loggerSpy.getAllMessages('error')).toHaveLength(0);
+    });
+
+    it('should add nested concepts whose hierarchy is created by an insert rule', () => {
+      // RuleSet: Bar
+      // * #MyCode "MyCode" "This is my code"
+      //
+      // CodeSystem: Foo
+      // * insert Bar
+      // * #MyCode #SubCode "SubCode" "This is my sub-code"
+      const myCode = new ConceptRule('MyCode', 'MyCode', 'This is my code');
+      ruleSet.rules.push(myCode);
+
+      const insertBar = new InsertRule('');
+      insertBar.ruleSet = 'Bar';
+      const subCode = new ConceptRule('SubCode', 'SubCode', 'This is my sub-code');
+      subCode.hierarchy = ['MyCode'];
+      cs.rules.push(insertBar, subCode);
+
+      exporter.applyInsertRules();
+      const exported = exporter.exportCodeSystem(cs);
+      expect(exported.concept[0].code).toBe('MyCode');
+      expect(exported.concept[0].concept[0].code).toBe('SubCode');
+      expect(loggerSpy.getAllMessages('error')).toHaveLength(0);
+    });
+
     it('should not add concepts from an insert rule that are duplicates of existing concepts', () => {
       // RuleSet: Bar
       // * #lion "Lion"
@@ -1005,5 +1116,43 @@ describe('CodeSystemExporter', () => {
         /CodeSystem Foo already contains code bear.*File: RuleSet\.fsh.*Line: 3\D.*Applied in File: CodeSystem\.fsh.*Applied on Line: 4\D*/s
       );
     });
+
+    it('should not add concepts from an insert rule that are duplicates of concepts added by a previous insert rule', () => {
+      // RuleSet: Bar
+      // * #bear "Bear"
+      //
+      // RuleSet: AnotherBar
+      // * #bear "Another Bear"
+      //
+      // CodeSystem: Foo
+      // * insert Bar
+      // * insert AnotherBar
+      const bearRule = new ConceptRule('bear', 'Bear');
+      ruleSet.rules.push(bearRule);
+
+      const anotherRuleSet = new RuleSet('AnotherBar');
+      doc.ruleSets.set(anotherRuleSet.name, anotherRuleSet);
+      const anotherBearRule = new ConceptRule('bear', 'Another Bear')
+        .withFile('RuleSet.fsh')
+        .withLocation([5, 0, 5, 22]);
+      anotherRuleSet.rules.push(anotherBearRule);
+
+      const insertBarRule = new InsertRule('');
+      insertBarRule.ruleSet = 'Bar';
+      const insertAnotherRule = new InsertRule('')
+        .withFile('CodeSystem.fsh')
+        .withLocation([9, 0, 9, 19]);
+      insertAnotherRule.ruleSet = 'AnotherBar';
+      cs.rules.push(insertBarRule, insertAnotherRule);
+
+      exporter.applyInsertRules();
+      const exported = exporter.exportCodeSystem(cs);
+      expect(exported.concept[0].code).toBe('bear');
+      expect(exported.concept[0].display).toBe('Bear');
+      expect(exported.count).toBe(1);
+      expect(loggerSpy.getLastMessage('error')).toMatch(
+        /CodeSystem Foo already contains code bear.*File: RuleSet\.fsh.*Line: 5\D.*Applied in File: CodeSystem\.fsh.*Applied on Line: 9\D*/s
+      );
+    });
   });
 });
diff --git a/test/export/StructureDefinitionExporter.test.ts b/test/export/StructureDefinitionExporter.test.ts
index 6d6e012..54214ac 100644
--- a/test/export/StructureDefinitionExporter.test.ts
+++ b/test/export/StructureDefinitionExporter.test.ts
@@ -4017,7 +4017,7 @@ describe('StructureDefinitionExporter R4', () => {
       profile.rules.push(rule);
 
       const visibleSystem = new FshCodeSystem('Visible');
-      visibleSystem.addConcept(new ConceptRule('bright'));
+      visibleSystem.rules.push(new ConceptRule('bright'));
       doc.codeSystems.set(visibleSystem.name, visibleSystem);
 
       exporter.exportStructDef(profile);
@@ -4040,7 +4040,7 @@ describe('StructureDefinitionExporter R4', () => {
       profile.rules.push(rule);
 
       const visibleSystem = new FshCodeSystem('Visible');
-      visibleSystem.addConcept(new ConceptRule('disco'));
+      visibleSystem.rules.push(new ConceptRule('disco'));
       const contentRule = new CaretValueRule('');
       contentRule.caretPath = 'content';
       contentRule.value = new FshCode('fragment');
@@ -4133,7 +4133,7 @@ describe('StructureDefinitionExporter R4', () => {
       doc.ruleSets.set(ruleSet.name, ruleSet);
 
       const visibleSystem = new FshCodeSystem('Visible');
-      visibleSystem.addConcept(new ConceptRule('dim'));
+      visibleSystem.rules.push(new ConceptRule('dim'));
       const insertRule = new InsertRule('');
       insertRule.ruleSet = 'ExtraLightRules';
       visibleSystem.rules.push(insertRule);
@@ -4200,7 +4200,7 @@ describe('StructureDefinitionExporter R4', () => {
       profile.rules.push(rule);
 
       const visibleSystem = new FshCodeSystem('Visible');
-      visibleSystem.addConcept(new ConceptRule('bright'));
+      visibleSystem.rules.push(new ConceptRule('bright'));
       doc.codeSystems.set(visibleSystem.name, visibleSystem);
 
       exporter.exportStructDef(profile);
@@ -4264,7 +4264,7 @@ describe('StructureDefinitionExporter R4', () => {
       profile.rules.push(rule);
 
       const visibleSystem = new FshCodeSystem('Visible');
-      visibleSystem.addConcept(new ConceptRule('bright'));
+      visibleSystem.rules.push(new ConceptRule('bright'));
       doc.codeSystems.set(visibleSystem.name, visibleSystem);
 
       exporter.exportStructDef(profile);
diff --git a/test/export/ValueSetExporter.test.ts b/test/export/ValueSetExporter.test.ts
index aca36b6..906cb63 100644
--- a/test/export/ValueSetExporter.test.ts
+++ b/test/export/ValueSetExporter.test.ts
@@ -481,8 +481,8 @@ describe('ValueSetExporter', () => {
     doc.valueSets.set(valueSet.name, valueSet);
     const foodCS = new FshCodeSystem('FoodCS');
     foodCS.id = 'food';
-    foodCS.addConcept(new ConceptRule('Pizza', 'Delicious pizza to share.'));
-    foodCS.addConcept(new ConceptRule('Salad', 'Plenty of fresh vegetables.'));
+    foodCS.rules.push(new ConceptRule('Pizza', 'Delicious pizza to share.'));
+    foodCS.rules.push(new ConceptRule('Salad', 'Plenty of fresh vegetables.'));
     doc.codeSystems.set(foodCS.name, foodCS);
     const exported = exporter.export().valueSets;
     expect(exported.length).toBe(1);
@@ -571,8 +571,8 @@ describe('ValueSetExporter', () => {
 
     const foodCS = new FshCodeSystem('FoodCS');
     foodCS.id = 'food';
-    foodCS.addConcept(new ConceptRule('Pizza', 'Delicious pizza to share.'));
-    foodCS.addConcept(new ConceptRule('Fruit', 'Get that good fruit.'));
+    foodCS.rules.push(new ConceptRule('Pizza', 'Delicious pizza to share.'));
+    foodCS.rules.push(new ConceptRule('Fruit', 'Get that good fruit.'));
     const insertRule = new InsertRule('');
     insertRule.ruleSet = 'ExtraFoodRules';
     foodCS.rules.push(insertRule);
@@ -713,7 +713,7 @@ describe('ValueSetExporter', () => {
     doc.valueSets.set(valueSet.name, valueSet);
     const foodCS = new FshCodeSystem('FoodCS');
     foodCS.id = 'food';
-    foodCS.addConcept(new ConceptRule('Pizza', 'Delicious pizza to share.'));
+    foodCS.rules.push(new ConceptRule('Pizza', 'Delicious pizza to share.'));
     const contentRule = new CaretValueRule('');
     contentRule.caretPath = 'content';
     contentRule.value = new FshCode('fragment');
@@ -799,7 +799,7 @@ describe('ValueSetExporter', () => {
     doc.valueSets.set(valueSet.name, valueSet);
     const foodCS = new FshCodeSystem('FoodCS');
     foodCS.id = 'food';
-    foodCS.addConcept(new ConceptRule('Pizza', 'Delicious pizza to share.'));
+    foodCS.rules.push(new ConceptRule('Pizza', 'Delicious pizza to share.'));
     doc.codeSystems.set(foodCS.name, foodCS);
     const exported = exporter.export().valueSets;
     expect(exported.length).toBe(1);
@@ -902,7 +902,7 @@ describe('ValueSetExporter', () => {
     systemUrl.caretPath = 'url';
     systemUrl.value = 'http://food.org/food';
     foodCS.rules.push(systemUrl);
-    foodCS.addConcept(new ConceptRule('Pizza', 'Delicious pizza to share.'));
+    foodCS.rules.push(new ConceptRule('Pizza', 'Delicious pizza to share.'));
     doc.codeSystems.set(foodCS.name, foodCS);
     const exported = exporter.export().valueSets;
     expect(exported.length).toBe(1);
@@ -1057,7 +1057,7 @@ describe('ValueSetExporter', () => {
 
     const foodCS = new FshCodeSystem('FoodCS');
     foodCS.id = 'food';
-    foodCS.addConcept(new ConceptRule('Pizza', 'Delicious pizza to share.'));
+    foodCS.rules.push(new ConceptRule('Pizza', 'Delicious pizza to share.'));
     doc.codeSystems.set(foodCS.name, foodCS);
 
     const exported = exporter.export().valueSets;
@@ -1101,7 +1101,7 @@ describe('ValueSetExporter', () => {
 
     const foodCS = new FshCodeSystem('FoodCS');
     foodCS.id = 'food';
-    foodCS.addConcept(new ConceptRule('Pizza', 'Delicious pizza to share.'));
+    foodCS.rules.push(new ConceptRule('Pizza', 'Delicious pizza to share.'));
     const contentRule = new CaretValueRule('');
     contentRule.caretPath = 'content';
     contentRule.value = new FshCode('fragment');
@@ -1257,7 +1257,7 @@ describe('ValueSetExporter', () => {
 
     const foodCS = new FshCodeSystem('FoodCS');
     foodCS.id = 'food';
-    foodCS.addConcept(new ConceptRule('Pizza', 'Delicious pizza to share.'));
+    foodCS.rules.push(new ConceptRule('Pizza', 'Delicious pizza to share.'));
     doc.codeSystems.set(foodCS.name, foodCS);
 
     const exported = exporter.export().valueSets;
diff --git a/test/import/FSHImporter.CodeSystem.test.ts b/test/import/FSHImporter.CodeSystem.test.ts
index b95bf70..0ab31f5 100644
--- a/test/import/FSHImporter.CodeSystem.test.ts
+++ b/test/import/FSHImporter.CodeSystem.test.ts
@@ -362,48 +362,6 @@ describe('FSHImporter', () => {
         expect(loggerSpy.getAllMessages('error')).toHaveLength(0);
       });
 
-      it('should log an error when encountering a duplicate code', () => {
-        const input = leftAlign(`
-        CodeSystem: ZOO
-        * #goat "A goat"
-        * #goat "Another goat?"
-        `);
-        const result = importSingleText(input, 'Zoo.fsh');
-        expect(result.codeSystems.size).toBe(1);
-        const codeSystem = result.codeSystems.get('ZOO');
-        expect(codeSystem.name).toBe('ZOO');
-        expect(codeSystem.rules.length).toBe(1);
-        expect(loggerSpy.getLastMessage('error')).toMatch(/File: Zoo\.fsh.*Line: 4\D*/s);
-      });
-
-      it('should not log an error when encountering a duplicate code if the new code has no display or definition', () => {
-        const input = leftAlign(`
-        CodeSystem: ZOO
-        * #goat "A goat"
-        * #goat
-        `);
-        const result = importSingleText(input, 'Zoo.fsh');
-        expect(result.codeSystems.size).toBe(1);
-        const codeSystem = result.codeSystems.get('ZOO');
-        expect(codeSystem.name).toBe('ZOO');
-        expect(codeSystem.rules.length).toBe(1);
-        expect(loggerSpy.getAllMessages('error')).toHaveLength(0);
-      });
-
-      it('should log an error when encountering a code with an incorrectly defined hierarchy', () => {
-        const input = leftAlign(`
-        CodeSystem: ZOO
-        * #bear "Bear" "A member of family Ursidae."
-        * #bear #sunbear #ursula "Ursula the sun bear"
-        `);
-        const result = importSingleText(input, 'Zoo.fsh');
-        expect(result.codeSystems.size).toBe(1);
-        const codeSystem = result.codeSystems.get('ZOO');
-        expect(codeSystem.name).toBe('ZOO');
-        expect(codeSystem.rules.length).toBe(1);
-        expect(loggerSpy.getLastMessage('error')).toMatch(/File: Zoo\.fsh.*Line: 4\D*/s);
-      });
-
       it('should log an error when a concept includes a system declaration', () => {
         const input = leftAlign(`
         CodeSystem: ZOO
diff --git a/test/import/FSHImporter.context.test.ts b/test/import/FSHImporter.context.test.ts
index 20cf02e..478fd4c 100644
--- a/test/import/FSHImporter.context.test.ts
+++ b/test/import/FSHImporter.context.test.ts
@@ -759,7 +759,7 @@ describe('FSHImporter', () => {
       `);
       const result = importSingleText(input, 'Zoo.fsh');
       const codeSystem = result.codeSystems.get('ZOO');
-      expect(codeSystem.rules).toHaveLength(4);
+      expect(codeSystem.rules).toHaveLength(5);
       assertConceptRule(codeSystem.rules[0], 'anteater', 'Anteater', undefined, []);
       expect(codeSystem.rules[0].sourceInfo.file).toBe('Zoo.fsh');
       assertConceptRule(codeSystem.rules[1], 'northern', 'Northern tamandua', undefined, [
@@ -775,15 +775,17 @@ describe('FSHImporter', () => {
         ['anteater', 'northern']
       );
       expect(codeSystem.rules[2].sourceInfo.file).toBe('Zoo.fsh');
+      assertConceptRule(codeSystem.rules[3], 'anteater', undefined, undefined, []);
+      expect(codeSystem.rules[3].sourceInfo.file).toBe('Zoo.fsh');
       assertCaretValueRule(
-        codeSystem.rules[3],
+        codeSystem.rules[4],
         '',
         'property[0].valueString',
         'Their threat pose is really cute.',
         false,
         ['anteater']
       );
-      expect(codeSystem.rules[3].sourceInfo.file).toBe('Zoo.fsh');
+      expect(codeSystem.rules[4].sourceInfo.file).toBe('Zoo.fsh');
       expect(loggerSpy.getAllMessages('error')).toHaveLength(0);
     });
 
@@ -798,26 +800,28 @@ describe('FSHImporter', () => {
       `);
       const result = importSingleText(input, 'Zoo.fsh');
       const codeSystem = result.codeSystems.get('ZOO');
-      expect(codeSystem.rules).toHaveLength(4);
+      expect(codeSystem.rules).toHaveLength(5);
       assertConceptRule(codeSystem.rules[0], 'anteater', 'Anteater', undefined, []);
       expect(codeSystem.rules[0].sourceInfo.file).toBe('Zoo.fsh');
       assertConceptRule(codeSystem.rules[1], 'northern', 'Northern tamandua', undefined, [
         'anteater'
       ]);
-      expect(codeSystem.rules[2].sourceInfo.file).toBe('Zoo.fsh');
+      expect(codeSystem.rules[1].sourceInfo.file).toBe('Zoo.fsh');
       assertConceptRule(codeSystem.rules[2], 'southern', 'Southern tamandua', undefined, [
         'anteater'
       ]);
       expect(codeSystem.rules[2].sourceInfo.file).toBe('Zoo.fsh');
+      assertConceptRule(codeSystem.rules[3], 'northern', undefined, undefined, ['anteater']);
+      expect(codeSystem.rules[3].sourceInfo.file).toBe('Zoo.fsh');
       assertCaretValueRule(
-        codeSystem.rules[3],
+        codeSystem.rules[4],
         '',
         'property[0].valueString',
         'They are strong climbers.',
         false,
         ['anteater', 'northern']
       );
-      expect(codeSystem.rules[3].sourceInfo.file).toBe('Zoo.fsh');
+      expect(codeSystem.rules[4].sourceInfo.file).toBe('Zoo.fsh');
       expect(loggerSpy.getAllMessages('error')).toHaveLength(0);
     });