Repository: FHIR/sushi (ts)

Issue:
SUSHI incorrectly handles indented rules below a dot path
```
Extension: TestExtension
* ^context.type = #element
* ^context.expression = "Patient"
* . 0..1
  * ^short = "A test extension."
  * ^definition = "Yep.  It is a test extension."
* value[x] only boolean
```

In SUSHI 2.0.0 beta.1, this works correctly.  In SUSHI 2.0.0 beta.2, this results in the following errors:
```
error Rule cannot be indented below rule which has no path. The rule will be processed as if it is not indented.
  File: /Users/cmoesel/dev/fsh/TestBadThings/input/fsh/patient.fsh
  Line: 5
error Rule cannot be indented below rule which has no path. The rule will be processed as if it is not indented.
  File: /Users/cmoesel/dev/fsh/TestBadThings/input/fsh/patient.fsh
  Line: 6
```
and then later on in the log:
```
error The element or path you referenced does not exist: short
  File: /Users/cmoesel/dev/fsh/TestBadThings/input/fsh/patient.fsh
  Line: 5
error The element or path you referenced does not exist: definition
  File: /Users/cmoesel/dev/fsh/TestBadThings/input/fsh/patient.fsh
  Line: 6
```

Pull request:
Set . path as context
Fixes #887.

When a rule's path is ".", the rule applies to the root element. Allow these rules to set a context so that they may be followed by an indented caret rule with no path.

Basically, the check for "did we indent a rule with a . path" was happening a bit too early. Now, when we have a . path on a rule with no indent, we'll first check that we're not indented, and therefore everything is fine.

Patch:
diff --git a/src/import/FSHImporter.ts b/src/import/FSHImporter.ts
index a4265da..079071a 100644
--- a/src/import/FSHImporter.ts
+++ b/src/import/FSHImporter.ts
@@ -1130,10 +1130,8 @@ export class FSHImporter extends FSHVisitor {
   }
 
   getPathWithContext(path: string, parentCtx: ParserRuleContext): string {
-    return this.getArrayPathWithContext(
-      splitOnPathPeriods(path).filter(p => p),
-      parentCtx
-    ).join('.');
+    const splitPath = path === '.' ? [path] : splitOnPathPeriods(path).filter(p => p);
+    return this.getArrayPathWithContext(splitPath, parentCtx).join('.');
   }
 
   getArrayPathWithContext(pathArray: string[], parentCtx: ParserRuleContext): string[] {
@@ -2051,17 +2049,6 @@ export class FSHImporter extends FSHVisitor {
     const currentIndent = location.startColumn - this.baseIndent;
     const contextIndex = currentIndent / INDENT_WIDTH;
 
-    if (this.pathContext.length && path.length === 1 && path[0] === '.') {
-      logger.error(
-        "The special '.' path is only allowed in top-level rules. The rule will be processed as if it is not indented.",
-        {
-          location,
-          file: this.currentFile
-        }
-      );
-      return path;
-    }
-
     if (!this.isValidContext(location, currentIndent, this.pathContext)) {
       return path;
     }
@@ -2077,6 +2064,17 @@ export class FSHImporter extends FSHVisitor {
       return path;
     }
 
+    if (path.length === 1 && path[0] === '.') {
+      logger.error(
+        "The special '.' path is only allowed in top-level rules. The rule will be processed as if it is not indented.",
+        {
+          location,
+          file: this.currentFile
+        }
+      );
+      return path;
+    }
+
     // Otherwise, get the context based on the indent level.
     const currentContext = this.pathContext[contextIndex - 1];
     if (currentContext.length === 0) {


Test patch:
diff --git a/test/import/FSHImporter.context.test.ts b/test/import/FSHImporter.context.test.ts
index 7e93db6..90fdabd 100644
--- a/test/import/FSHImporter.context.test.ts
+++ b/test/import/FSHImporter.context.test.ts
@@ -9,7 +9,8 @@ import {
   assertObeysRule,
   assertValueSetConceptComponent,
   assertValueSetFilterComponent,
-  assertAddElementRule
+  assertAddElementRule,
+  assertOnlyRule
 } from '../testhelpers/asserts';
 import { FshCode } from '../../src/fshtypes';
 
@@ -340,6 +341,35 @@ describe('FSHImporter', () => {
       });
     });
 
+    it('should set the context correctly when a rule with . path is followed by a caret rule with no path', () => {
+      const input = `
+      Extension: TestExtension
+      * value[x] only boolean
+      * . 0..1
+        * ^short = "A test extension."
+        * ^definition = "Yep.  It is a test extension."
+      * ^context.expression = "Patient"
+      `;
+      const result = importSingleText(input, 'Context.fsh');
+      expect(loggerSpy.getAllMessages('error')).toHaveLength(0);
+      expect(loggerSpy.getAllMessages('warn')).toHaveLength(0);
+      expect(result.extensions.size).toBe(1);
+      const extension = result.extensions.get('TestExtension');
+      expect(extension.rules).toHaveLength(5);
+      assertOnlyRule(extension.rules[0], 'value[x]', { type: 'boolean' });
+      assertCardRule(extension.rules[1], '.', 0, '1');
+      assertCaretValueRule(extension.rules[2], '.', 'short', 'A test extension.', false, ['.']);
+      assertCaretValueRule(
+        extension.rules[3],
+        '.',
+        'definition',
+        'Yep.  It is a test extension.',
+        false,
+        ['.']
+      );
+      assertCaretValueRule(extension.rules[4], '', 'context.expression', 'Patient', false, []);
+    });
+
     it('should log an error when a . rule is indented', () => {
       const input = `
       Profile: Foo