Repository: FHIR/sushi (ts)
Issue: Multiline strings losing letters
I've noticed that it seems like the leading letters in multiline strings can still get cut off in an unexpected way. I thought we had addressed this before, but maybe I am remembering incorrectly. For example, with the following definition:
```
Profile: CancerRelatedMedicationRequest
Parent: USCoreMedicationRequestProfile
Id: mcode-cancer-related-medication-request
Title: "Cancer-Related Medication Request"
Description: """A record of a medication prescription, administration, or consumption used to treat cancer. The medication may reported by the prescriber, prescribing organization, or patient. It does not have to be directly observed.

Conformance statement:

 MedicationRequest resources associated with an mCODE patient and associated with cancer treatment MUST conform to this profile. Beyond this requirement, a producer of resources SHOULD ensure that any resource instance associated with an mCODE patient that would reasonably be expected to conform to this profile (i.e., a prescription, administration, or medication exposure) SHOULD be published in this form."""
```
The resulting string is:
```
" record of a medication prescription, administration, or consumption used to treat cancer. The medication may reported by the prescriber, prescribing organization, or patient. It does not have to be directly observed.\n\nonformance statement:\n\nMedicationRequest resources associated with an mCODE patient and associated with cancer treatment MUST conform to this profile. Beyond this requirement, a producer of resources SHOULD ensure that any resource instance associated with an mCODE patient that would reasonably be expected to conform to this profile (i.e., a prescription, administration, or medication exposure) SHOULD be published in this form."
```
Notice that the "A" to start the description and the "C" in "Conformance are both cut off. I know we do some processing to remove leading spaces, and the "MedicationRequest resources..." line is unnecessarily indented one space, so I'm guessing that is what causes this, but still even if that space is likely a typo this behavior seems unexpected.
Pull request description: Fix multiline string minimum indent detection
Fixes #708 and completes task [CIMPL-626](https://standardhealthrecord.atlassian.net/browse/CIMPL-626).

When the last line of a multiline string was the only one with leading spaces, it was detected as having the minimum indentation, causing loss of data on previous lines. Use a min function instead of comparing with zero in order to determine the minimum indentation.

Patch:
diff --git a/src/import/FSHImporter.ts b/src/import/FSHImporter.ts
index 80cf63b..d150f17 100644
--- a/src/import/FSHImporter.ts
+++ b/src/import/FSHImporter.ts
@@ -59,6 +59,7 @@ import {
 import isEqual from 'lodash/isEqual';
 import sortBy from 'lodash/sortBy';
 import upperFirst from 'lodash/upperFirst';
+import _min from 'lodash/min';
 import { parseCodeLexeme } from './parseCodeLexeme';
 import { EOL } from 'os';
 
@@ -1912,14 +1913,17 @@ export class FSHImporter extends FSHVisitor {
     lines = lines.map(l => (/^\s*$/.test(l) ? '' : l));
 
     // find the minimum number of spaces before the first char (ignore zero-length lines)
-    let minSpaces = 0;
-    lines.forEach(line => {
-      const firstNonSpace = line.search(/\S|$/);
-      const lineIsEmpty = /^$/.test(line);
-      if (!lineIsEmpty && firstNonSpace >= 0 && (minSpaces === 0 || firstNonSpace < minSpaces)) {
-        minSpaces = firstNonSpace;
-      }
-    });
+    const minSpaces = _min(
+      lines.map(line => {
+        const firstNonSpace = line.search(/\S|$/);
+        const lineIsEmpty = /^$/.test(line);
+        if (!lineIsEmpty) {
+          return firstNonSpace;
+        } else {
+          return null;
+        }
+      })
+    );
 
     // consistently remove the common leading spaces and join the lines back together
     return lines.map(l => (l.length >= minSpaces ? l.slice(minSpaces) : l)).join('\n');


Test patch:
diff --git a/test/import/FSHImporter.test.ts b/test/import/FSHImporter.test.ts
index d936039..8361c1b 100644
--- a/test/import/FSHImporter.test.ts
+++ b/test/import/FSHImporter.test.ts
@@ -324,6 +324,28 @@ describe('FSHImporter', () => {
     expect(profile.description).toBe(expectedDescription);
   });
 
+  it('should not change indentation of multi-line strings when there is at least one unindented non-blank line', () => {
+    const input = `
+    Profile: ObservationProfile
+    Parent: Observation
+    Description: """A long statement follows this.
+
+Long statement:
+
+ This statement is much longer than the previous statement."""`;
+    const expectedDescription = [
+      'A long statement follows this.',
+      '',
+      'Long statement:',
+      '',
+      ' This statement is much longer than the previous statement.'
+    ].join('\n');
+
+    const result = importSingleText(input);
+    const profile = result.profiles.get('ObservationProfile');
+    expect(profile.description).toBe(expectedDescription);
+  });
+
   it('should truncate whitespace lines in multi-line strings', () => {
     const input = `
     Profile: StaircaseObservation
diff --git a/test/import/ensureConfigurationFile.test.ts b/test/import/ensureConfigurationFile.test.ts
index 9a504c4..1079fc1 100644
--- a/test/import/ensureConfigurationFile.test.ts
+++ b/test/import/ensureConfigurationFile.test.ts
@@ -157,7 +157,7 @@ describe('ensureConfigurationFile', () => {
       fhirVersion: '4.0.1',
       dependencies: { 'hl7.fhir.us.core': '3.1.0', 'hl7.fhir.uv.vhdir': 'current' },
       parameters: { 'show-inherited-invariants': false },
-      copyrightYear: '2020+',
+      copyrightYear: '2021+',
       releaseLabel: 'CI Build',
       template: 'fhir.base.template#current',
       menu: {
@@ -233,7 +233,7 @@ describe('ensureConfigurationFile', () => {
       // No longer any publisher, description, license, dependencies
       fhirVersion: '4.0.1',
       parameters: { 'show-inherited-invariants': false },
-      copyrightYear: '2020+',
+      copyrightYear: '2021+',
       releaseLabel: 'CI Build',
       template: 'fhir.base.template#current',
       menu: {
@@ -332,7 +332,7 @@ describe('ensureConfigurationFile', () => {
       fhirVersion: '4.0.1',
       dependencies: { 'hl7.fhir.us.core': '3.1.0', 'hl7.fhir.uv.vhdir': 'current' },
       parameters: { 'show-inherited-invariants': false },
-      copyrightYear: '2020+',
+      copyrightYear: '2021+',
       releaseLabel: 'CI Build',
       template: 'fhir.base.template#current',
       menu: {
@@ -491,7 +491,7 @@ describe('ensureConfigurationFile', () => {
       fhirVersion: '4.0.1',
       dependencies: { 'hl7.fhir.us.core': '3.1.0', 'hl7.fhir.uv.vhdir': 'current' },
       parameters: { 'show-inherited-invariants': false }, // no more extra parameter
-      copyrightYear: '2020+', // back to default copyrightYear
+      copyrightYear: '2021+', // back to default copyrightYear
       releaseLabel: 'CI Build', // backto default CI Build
       menu: {
         'IG Home': 'index.html',
@@ -548,7 +548,7 @@ describe('ensureConfigurationFile', () => {
       fhirVersion: '4.0.1',
       dependencies: { 'hl7.fhir.us.core': '3.1.0', 'hl7.fhir.uv.vhdir': 'current' },
       parameters: { 'show-inherited-invariants': false },
-      copyrightYear: '2020+',
+      copyrightYear: '2021+',
       releaseLabel: 'CI Build',
       template: 'fhir.base.template#current',
       menu: {
@@ -650,7 +650,7 @@ describe('ensureConfigurationFile', () => {
       fhirVersion: '4.0.1',
       dependencies: { 'hl7.fhir.us.core': '3.1.0', 'hl7.fhir.uv.vhdir': 'current' },
       parameters: { 'show-inherited-invariants': false },
-      copyrightYear: '2020+',
+      copyrightYear: '2021+',
       releaseLabel: 'CI Build',
       template: 'fhir.base.template#current',
       menu: {
@@ -725,7 +725,7 @@ describe('ensureConfigurationFile', () => {
       fhirVersion: '4.0.1',
       dependencies: { 'hl7.fhir.us.core': '3.1.0', 'hl7.fhir.uv.vhdir': 'current' },
       parameters: { 'show-inherited-invariants': false },
-      copyrightYear: '2020+',
+      copyrightYear: '2021+',
       releaseLabel: 'CI Build',
       template: 'fhir.base.template#current',
       // menu should no longer exist since the provided menu.xml is used
@@ -813,7 +813,7 @@ describe('ensureConfigurationFile', () => {
       fhirVersion: '4.0.1',
       dependencies: { 'hl7.fhir.us.core': '3.1.0', 'hl7.fhir.uv.vhdir': 'current' },
       parameters: { 'show-inherited-invariants': false },
-      copyrightYear: '2020+',
+      copyrightYear: '2021+',
       releaseLabel: 'CI Build',
       template: 'fhir.base.template#current',
       menu: {
diff --git a/test/utils/fixtures/init-config/default-config.yaml b/test/utils/fixtures/init-config/default-config.yaml
index 3e4164c..baad553 100644
--- a/test/utils/fixtures/init-config/default-config.yaml
+++ b/test/utils/fixtures/init-config/default-config.yaml
@@ -8,7 +8,7 @@ name: ExampleIG
 status: draft
 version: 0.1.0
 fhirVersion: 4.0.1
-copyrightYear: 2020+
+copyrightYear: 2021+
 releaseLabel: ci-build
 
 # ╭────────────────────────────────────────────menu.xml────────────────────────────────────────────╮
@@ -16,4 +16,4 @@ releaseLabel: ci-build
 # ╰────────────────────────────────────────────────────────────────────────────────────────────────╯
 menu:
   Home: index.html
-  Artifacts: artifacts.html
\ No newline at end of file
+  Artifacts: artifacts.html
diff --git a/test/utils/fixtures/init-config/user-input-config.yaml b/test/utils/fixtures/init-config/user-input-config.yaml
index fb9a60b..c53489c 100644
--- a/test/utils/fixtures/init-config/user-input-config.yaml
+++ b/test/utils/fixtures/init-config/user-input-config.yaml
@@ -8,10 +8,9 @@ name: MyNonDefaultName
 status: active
 version: 2.0.0
 fhirVersion: 4.0.1
-copyrightYear: 2020+
+copyrightYear: 2021+
 releaseLabel: ci-build
 
-
 # ╭────────────────────────────────────────────menu.xml────────────────────────────────────────────╮
 # │  To use a provided ig-data/input/includes/menu.xml file, delete the "menu" property below.     │
 # ╰────────────────────────────────────────────────────────────────────────────────────────────────╯