Repository: FHIR/sushi
Pull request #896 (2021-07-30 16:40:37)

FshCode code and display are fshified for string output
Completes task [CIMPL-771](https://standardhealthrecord.atlassian.net/browse/CIMPL-771).

A `FshCode`'s code can contain basically any non-whitespace characters, as long as it doesn't start or end with whitespace characters. If it contains any spaces or any characters that need to be escaped in FSH, fshify and quote the code. A `FshCode`'s display is basically free text, so it should also be fshified for output. The same applies to a `FshQuantity`'s display property. Fshifying strings in these locations also makes `FshRatio` and `ValueSetConceptComponentRule` produce correct output, since those classes make use of the `toString` methods provided by `FshCode` and `FshQuantity`.


Patch:
diff --git a/src/fshtypes/FshCode.ts b/src/fshtypes/FshCode.ts
index 8750192..574f57b 100644
--- a/src/fshtypes/FshCode.ts
+++ b/src/fshtypes/FshCode.ts
@@ -1,5 +1,6 @@
 import { FshEntity } from './FshEntity';
 import { Coding, CodeableConcept, Quantity } from '../fhirtypes';
+import { fshifyString } from './common';
 
 export class FshCode extends FshEntity {
   constructor(public code: string, public system?: string, public display?: string) {
@@ -7,8 +8,12 @@ export class FshCode extends FshEntity {
   }
 
   toString(): string {
-    const str = `${this.system ?? ''}#${/\s/.test(this.code) ? `"${this.code}"` : this.code}`;
-    return this.display ? `${str} "${this.display}"` : str;
+    let codeToShow = this.code;
+    if (/\s/.test(this.code)) {
+      codeToShow = `"${fshifyString(this.code)}"`;
+    }
+    const str = `${this.system ?? ''}#${codeToShow}`;
+    return this.display ? `${str} "${fshifyString(this.display)}"` : str;
   }
 
   toFHIRCoding(): Coding {
diff --git a/src/fshtypes/FshQuantity.ts b/src/fshtypes/FshQuantity.ts
index 2d2e70a..2202e37 100644
--- a/src/fshtypes/FshQuantity.ts
+++ b/src/fshtypes/FshQuantity.ts
@@ -1,6 +1,7 @@
 import { FshCode } from './FshCode';
 import { FshEntity } from './FshEntity';
 import { Quantity } from '../fhirtypes';
+import { fshifyString } from './common';
 
 export class FshQuantity extends FshEntity {
   constructor(public value: number, public unit?: FshCode) {
@@ -12,8 +13,12 @@ export class FshQuantity extends FshEntity {
     if (this.unit?.code != null) {
       if (this.unit?.system == 'http://unitsofmeasure.org') {
         str += ` '${this.unit.code}'`;
-        if (this.unit.display) str += ` "${this.unit.display}"`;
-      } else str += ` ${this.unit.toString()}`;
+        if (this.unit.display) {
+          str += ` "${fshifyString(this.unit.display)}"`;
+        }
+      } else {
+        str += ` ${this.unit.toString()}`;
+      }
     }
     return str;
   }


Test patch:
diff --git a/test/fshtypes/FshCode.test.ts b/test/fshtypes/FshCode.test.ts
index 8edac3c..f5dbb66 100644
--- a/test/fshtypes/FshCode.test.ts
+++ b/test/fshtypes/FshCode.test.ts
@@ -11,40 +11,52 @@ describe('FshCode', () => {
   });
 
   describe('#toString', () => {
-    it('should return string for basic code', () => {
+    it('should return a string for a basic code', () => {
       const code = new FshCode('my-code');
       const result = code.toString();
       expect(result).toEqual('#my-code');
     });
 
-    it('should return string for code with system', () => {
+    it('should return a string for a code with system', () => {
       const code = new FshCode('my-code', 'http://foo.com');
       const result = code.toString();
       expect(result).toEqual('http://foo.com#my-code');
     });
 
-    it('should return string for code with display', () => {
+    it('should return a string for a code with display', () => {
       const code = new FshCode('my-code', null, 'Display');
       const result = code.toString();
       expect(result).toEqual('#my-code "Display"');
     });
 
-    it('should return string for code with system and display', () => {
+    it('should return a string for a code with system and display', () => {
       const code = new FshCode('my-code', 'http://foo.com', 'Display');
       const result = code.toString();
       expect(result).toEqual('http://foo.com#my-code "Display"');
     });
 
-    it('should return string for code with spaces', () => {
+    it('should return a string for a code with spaces', () => {
       const code = new FshCode('my spacey code');
       const result = code.toString();
       expect(result).toEqual('#"my spacey code"');
     });
 
-    it('should return string for code with tabs', () => {
+    it('should return a string for a code with tabs', () => {
       const code = new FshCode('my\ttabby\tcode');
       const result = code.toString();
-      expect(result).toEqual('#"my\ttabby\tcode"');
+      expect(result).toEqual('#"my\\ttabby\\tcode"');
+    });
+
+    it('should return a string for a code where the code has non-whitespace characters that must be escaped', () => {
+      const code = new FshCode('strange\\ "code"');
+      const result = code.toString();
+      expect(result).toEqual('#"strange\\\\ \\"code\\""');
+    });
+
+    it('should return a string for a code with a display where the display has non-whitespace characters that must be escaped', () => {
+      const code = new FshCode('strange-display', null, 'very "strange"\\display');
+      const result = code.toString();
+      expect(result).toEqual('#strange-display "very \\"strange\\"\\\\display"');
     });
   });
 });
diff --git a/test/fshtypes/FshQuantity.test.ts b/test/fshtypes/FshQuantity.test.ts
index 41a74b5..138a906 100644
--- a/test/fshtypes/FshQuantity.test.ts
+++ b/test/fshtypes/FshQuantity.test.ts
@@ -69,5 +69,12 @@ describe('FshQuantity', () => {
       const result = quantity.toString();
       expect(result).toEqual('100 #"milli meters"');
     });
+
+    it('should return a string for a unit code with UCUM system and a display containing characters that must be escaped', () => {
+      const code = new FshCode('mm', 'http://unitsofmeasure.org', 'strange" display\\');
+      const quantity = new FshQuantity(100, code);
+      const result = quantity.toString();
+      expect(result).toEqual('100 \'mm\' "strange\\" display\\\\"');
+    });
   });
 });
diff --git a/test/fshtypes/rules/AssignmentRule.test.ts b/test/fshtypes/rules/AssignmentRule.test.ts
index c4077d0..add1022 100644
--- a/test/fshtypes/rules/AssignmentRule.test.ts
+++ b/test/fshtypes/rules/AssignmentRule.test.ts
@@ -52,7 +52,7 @@ describe('AssignmentRule', () => {
       const rule = new AssignmentRule('code');
       rule.value = new FshCode('573', 'http://example.com/codes', 'speed setting \\"high\\"');
       expect(rule.toFSH()).toEqual(
-        '* code = http://example.com/codes#573 "speed setting \\"high\\""'
+        '* code = http://example.com/codes#573 "speed setting \\\\\\"high\\\\\\""'
       );
     });