Repository: lvcabral/brs-engine
Pull request #377 (2024-05-18 21:17:32)

Added support for `formatJson()` undocumented flags 256 and 512
Closes issue #376 

sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=lvcabral_brs-emu&pullRequest=377) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=lvcabral_brs-emu&pullRequest=377&resolved=false&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=lvcabral_brs-emu&pullRequest=377&resolutions=WONTFIX)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=lvcabral_brs-emu&pullRequest=377&resolved=false&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/no-data-16px.png '') No data about Coverage  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=lvcabral_brs-emu&pullRequest=377&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=lvcabral_brs-emu&pullRequest=377)



Patch:
diff --git a/package.json b/package.json
index 0fd77ed5..35518781 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
 {
   "name": "brs-engine",
   "version": "1.6.1",
-  "description": "BrightScript Simulation Engine",
+  "description": "BrightScript Simulation Engine - Run Roku apps on Browsers and Node.js",
   "author": "Marcelo Lv Cabral <marcelo@lvcabral.com> (https://lvcabral.com/)",
   "license": "MIT",
   "copyright": "© 2019-2024, Marcelo Lv Cabral",
diff --git a/src/worker/interpreter/index.ts b/src/worker/interpreter/index.ts
index ed71fae1..13e1081c 100644
--- a/src/worker/interpreter/index.ts
+++ b/src/worker/interpreter/index.ts
@@ -1834,7 +1834,7 @@ export class Interpreter implements Expr.Visitor<BrsType>, Stmt.Visitor<BrsType>
     }
 
     evaluate(this: Interpreter, expression: Expr.Expression): BrsType {
-        this.location = expression.location;
+        if (expression.location.start.line !== -1) this.location = expression.location;
         return expression.accept<BrsType>(this);
     }
 
diff --git a/src/worker/stdlib/Json.ts b/src/worker/stdlib/Json.ts
index 1770bfea..5595dc98 100644
--- a/src/worker/stdlib/Json.ts
+++ b/src/worker/stdlib/Json.ts
@@ -4,6 +4,7 @@ import { Interpreter } from "../interpreter";
 
 import {
     BrsBoolean,
+    BrsComponent,
     BrsInvalid,
     BrsString,
     BrsType,
@@ -20,7 +21,7 @@ import {
  * Converts a value to its representation as a BrsType. If no such
  * representation is possible, throws an Error.
  * @param {any} x Some value.
- * @return {BrsType} The BrsType representaion of `x`.
+ * @return {BrsType} The BrsType representation of `x`.
  * @throws {Error} If `x` cannot be represented as a BrsType.
  */
 function brsValueOf(x: any): BrsType {
@@ -76,7 +77,9 @@ function visit(x: BrsAggregate, visited: Set<BrsAggregate>): void {
 function jsonOf(
     interpreter: Interpreter,
     x: BrsType,
-    visited: Set<BrsAggregate> = new Set()
+    flags: number = 0,
+    visited: Set<BrsAggregate> = new Set(),
+    key: string = ""
 ): string {
     switch (x.kind) {
         case ValueKind.Invalid:
@@ -95,7 +98,8 @@ function jsonOf(
                 return `{${x
                     .getElements()
                     .map((k: BrsString) => {
-                        return `"${k.toString()}":${jsonOf(interpreter, x.get(k), visited)}`;
+                        key = k.toString();
+                        return `"${key}":${jsonOf(interpreter, x.get(k), flags, visited, key)}`;
                     })
                     .join(",")}}`;
             }
@@ -104,12 +108,12 @@ function jsonOf(
                 return `[${x
                     .getElements()
                     .map((el: BrsType) => {
-                        return jsonOf(interpreter, el, visited);
+                        return jsonOf(interpreter, el, flags, visited, key);
                     })
                     .join(",")}]`;
             }
             if (isUnboxable(x)) {
-                return jsonOf(interpreter, x.unbox(), visited);
+                return jsonOf(interpreter, x.unbox(), flags, visited, key);
             }
             break;
         case ValueKind.Callable:
@@ -122,7 +126,19 @@ function jsonOf(
             const _: never = x;
             break;
     }
-    throw new Error(`jsonOf not implemented for: ${x}`);
+    let xType = x instanceof BrsComponent ? x.getComponentName() : x;
+    if (flags & 256) {
+        // UnsupportedIgnore
+        return "null";
+    } else if (flags & 512) {
+        // UnsupportedAnnotate
+        return `"<${xType}>"`;
+    }
+    let errMessage = `Value type not supported: ${xType}`;
+    if (key !== "") {
+        errMessage = `${key}: ${errMessage}`;
+    }
+    throw new Error(errMessage);
 }
 
 function logBrsErr(interpreter: Interpreter, functionName: string, err: Error): void {
@@ -141,12 +157,10 @@ export const FormatJson = new Callable("FormatJson", {
             new StdlibArgument("flags", ValueKind.Int32, new Int32(0)),
         ],
     },
-    impl: (interpreter: Interpreter, x: BrsType, _flags: Int32) => {
+    impl: (interpreter: Interpreter, x: BrsType, flags: Int32) => {
         try {
-            return new BrsString(jsonOf(interpreter, x));
+            return new BrsString(jsonOf(interpreter, x, flags.getValue()));
         } catch (err: any) {
-            // example RBI error:
-            // "BRIGHTSCRIPT: ERROR: FormatJSON: Value type not supported: roFunction: pkg:/source/main.brs(14)"
             logBrsErr(interpreter, "FormatJSON", err);
             return new BrsString("");
         }


Test patch:
diff --git a/test/cli/cli.test.js b/test/cli/cli.test.js
index 3f779d2b..8f4fed94 100644
--- a/test/cli/cli.test.js
+++ b/test/cli/cli.test.js
@@ -38,7 +38,7 @@ describe("cli", () => {
             let errors = err.stderr.match(new RegExp(filename, "g"));
             expect(errors.length).toEqual(2);
         }
-    });
+    }, 10000);
 
     it("prints eval errors once", async () => {
         let folder = "errors";
@@ -57,6 +57,6 @@ describe("cli", () => {
             let errors = err.stderr.match(new RegExp(filename, "g"));
             expect(errors.length).toEqual(2);
         }
-    });
+    }, 10000);
     it.todo("add tests for the remaining CLI options");
 });
diff --git a/test/e2e/StdLib.test.js b/test/e2e/StdLib.test.js
index f928f122..700c4811 100644
--- a/test/e2e/StdLib.test.js
+++ b/test/e2e/StdLib.test.js
@@ -128,6 +128,9 @@ describe("end to end standard libary", () => {
                 '    string: "ok"',
                 "}",
             ].join("\n"),
+            "",
+            `{"ar":[1,2,3],"di":null,"nx":123,"sa":"abc"}`,
+            `{"ar":[1,2,3],"di":"<roDeviceInfo>","nx":123,"sa":"abc"}`,
         ]);
     });
 
diff --git a/test/e2e/resources/stdlib/json.brs b/test/e2e/resources/stdlib/json.brs
index 09e0b243..7701ca83 100644
--- a/test/e2e/resources/stdlib/json.brs
+++ b/test/e2e/resources/stdlib/json.brs
@@ -1,18 +1,29 @@
-aa = {
-    foo: "bar",
-    lorem: 1.234
-}
-aa.self = aa
-print formatJson(aa)
+sub main()
+    aa = {
+        foo: "bar",
+        lorem: 1.234
+    }
+    aa.self = aa
+    print formatJson(aa)
 
-print formatJson(parseJson("{""boolean"":false,""float"":3.14,""integer"":2147483647,""longinteger"":2147483650,""null"":null,""string"":""ok""}"))
-pi = createObject("roFloat")
-pi.setFloat(3.14)
-print parseJson(formatJson({
-    string: "ok",
-    null: invalid,
-    longinteger: 2147483650,
-    integer: 2147483647,
-    float: pi,
-    boolean: false
-}))
+    print formatJson(parseJson("{""boolean"":false,""float"":3.14,""integer"":2147483647,""longinteger"":2147483650,""null"":null,""string"":""ok""}"))
+    pi = createObject("roFloat")
+    pi.setFloat(3.14)
+    print parseJson(formatJson({
+        string: "ok",
+        null: invalid,
+        longinteger: 2147483650,
+        integer: 2147483647,
+        float: pi,
+        boolean: false
+    }))
+    a = {
+        nx: 123
+        sa: "abc"
+        di: CreateObject("roDeviceInfo")
+        ar: [1, 2, 3]
+    }
+    print formatJson(a)
+    print formatJson(a, 257)
+    print formatJson(a, 513)
+end sub
\ No newline at end of file
diff --git a/test/stdlib/Json.test.js b/test/stdlib/Json.test.js
index ceb11087..3f8610c3 100644
--- a/test/stdlib/Json.test.js
+++ b/test/stdlib/Json.test.js
@@ -4,13 +4,13 @@ const { FormatJson, ParseJson } = brs.stdlib;
 const {
     RoArray,
     RoAssociativeArray,
+    RoDateTime,
     BrsBoolean,
     BrsInvalid,
     BrsString,
     Float,
     Int32,
     Int64,
-    Uninitialized,
     RoInt,
 } = brs.types;
 
@@ -42,12 +42,22 @@ describe("global JSON functions", () => {
     describe("FormatJson", () => {
         it("rejects non-convertible types", () => {
             expectConsoleError(/BRIGHTSCRIPT: ERROR: FormatJSON: /, () => {
-                expect(FormatJson.call(interpreter, Uninitialized.Instance)).toEqual(
-                    new BrsString("")
-                );
+                expect(FormatJson.call(interpreter, new RoDateTime())).toEqual(new BrsString(""));
             });
         });
 
+        it("returns `null` for non-convertible types (flag 256)", () => {
+            expect(FormatJson.call(interpreter, new RoDateTime(), new Int32(256))).toEqual(
+                new BrsString("null")
+            );
+        });
+
+        it("returns the type annotation for non-convertible types (flag 512)", () => {
+            expect(FormatJson.call(interpreter, new RoDateTime(), new Int32(512))).toEqual(
+                new BrsString(`"<roDateTime>"`)
+            );
+        });
+
         it("rejects nested associative array references", () => {
             let aa = new RoAssociativeArray([
                 { name: new BrsString("foo"), value: new BrsString("bar") },