Repository: FHIR/sushi
Pull request #792 (2021-04-01 19:29:42)

Soft Indexing sequences can now being at non-zero indices
This PR addresses another issue found in [PR #106](https://github.com/FHIR/GoFSH/pull/106) on GoFSH. When unwinding soft indexing, Sushi currently makes the assumption that the first element being assigned by an array utilizing soft indexing will always be at index 0. In the event that sushi first encounters an array as it references a non-zero element, this could result in something like the following... 
```
* effective[x] ^constraint[1].key = "us-core-1"
* effective[x] ^constraint[=].severity = #error
* effective[x] ^constraint[=].human = "Datetime must be at least to day."
```
resolving to this:
```
* effective[x] ^constraint[1].key = "us-core-1"
* effective[x] ^constraint[0].severity = #error
* effective[x] ^constraint[0].human = "Datetime must be at least to day."
```
This PR fixes this bug, taking numeric indices into account at all times. The issue can be illustrated by running the unit test included in commit `6035ded`, which should fail in that commit but pass in commit `037cd63` which adds the fix.

cmoesel: This seems reasonable to me, but I'm going to run a full regression to ensure it doesn't affect any existing IGs (or if it does, that it affects them in a good way).
cmoesel: This did well in regression.  In fact, it fixed a (previously undetected) bug in the mCODE IG generation because mCODE uses a numeric index followed by a `[=]` here: https://github.com/HL7/fhir-mCODE-ig/blob/master/input/fsh/EX_Bundle.fsh#L21-L22

So... way to go!

That said, @ngfreiter has asked that we not merge this until he has a chance to review and test it in combination w/ the GoFSH PR that raised this issue.

Patch:
diff --git a/src/utils/PathUtils.ts b/src/utils/PathUtils.ts
index 8979789..ff787cc 100644
--- a/src/utils/PathUtils.ts
+++ b/src/utils/PathUtils.ts
@@ -74,15 +74,20 @@ function convertSoftIndexes(element: PathPart, pathMap: Map<string, number>) {
   const mapName = `${element.prefix ?? ''}.${element.base}|${(element.slices ?? []).join('|')}`;
   const indexRegex = /^[0-9]+$/;
   if (!pathMap.has(mapName)) {
-    pathMap.set(mapName, 0);
-    if (element.brackets?.includes('+')) {
-      element.brackets[element.brackets.indexOf('+')] = '0';
-    } else if (element.brackets?.includes('=')) {
-      // If a sequence begins with a '=', we log an error but assume a value of 0
-      element.brackets[element.brackets.indexOf('=')] = '0';
-      throw new Error(
-        'The first index in a Soft Indexing sequence must be "+", an actual index of "0" has been assumed'
-      );
+    const existingNumericBracket = element.brackets?.find(bracket => indexRegex.test(bracket));
+    if (existingNumericBracket) {
+      pathMap.set(mapName, parseInt(existingNumericBracket));
+    } else {
+      pathMap.set(mapName, 0);
+      if (element.brackets?.includes('+')) {
+        element.brackets[element.brackets.indexOf('+')] = '0';
+      } else if (element.brackets?.includes('=')) {
+        // If a sequence begins with a '=', we log an error but assume a value of 0
+        element.brackets[element.brackets.indexOf('=')] = '0';
+        throw new Error(
+          'The first index in a Soft Indexing sequence must be "+", an actual index of "0" has been assumed'
+        );
+      }
     }
   } else {
     element.brackets?.forEach((bracket: string, index: number) => {


Test patch:
diff --git a/test/utils/PathUtils.test.ts b/test/utils/PathUtils.test.ts
index 2dcf82b..2834f2f 100644
--- a/test/utils/PathUtils.test.ts
+++ b/test/utils/PathUtils.test.ts
@@ -147,6 +147,18 @@ describe('PathUtils', () => {
         'name[1]'
       ]);
     });
+
+    it('should resolve soft indexing beginning with a non-zero index', () => {
+      const rules = ['name[2]', 'name[=]', 'name[=]', 'name[+]', 'name[=]'].map(r => new Rule(r));
+      resolveSoftIndexing(rules);
+      expect(rules.map(r => r.path)).toEqual([
+        'name[2]',
+        'name[2]',
+        'name[2]',
+        'name[3]',
+        'name[3]'
+      ]);
+    });
   });
 
   describe('#parseFSHPath', () => {