From 0e07019c9c4bf1f44f2cc121784cea3d564d29db Mon Sep 17 00:00:00 2001
From: Christopher Dwyer-Perkins <chris@inverted-solutions.com>
Date: Thu, 11 Jan 2024 15:06:59 -0400
Subject: [PATCH 01/26] Added function to bast test suite to store global
 mocked functions

---
 framework/src/source/BaseTestSuite.bs | 30 +++++++++++++++++++++++++++
 1 file changed, 30 insertions(+)

diff --git a/framework/src/source/BaseTestSuite.bs b/framework/src/source/BaseTestSuite.bs
index eeb2fef4..ae63c1ef 100644
--- a/framework/src/source/BaseTestSuite.bs
+++ b/framework/src/source/BaseTestSuite.bs
@@ -2260,6 +2260,36 @@ namespace rooibos
       end for
       m.mocks = invalid
       rooibos.resetMocksByFunctionName()
+
+      ' Clean up the global functions mocks as well
+      globalAa = getGlobalAa()
+      globalAa._globalMocks = invalid
+    end function
+
+    ' /**
+    '  * @memberof module:BaseTestSuite
+    '  * @name mockGlobalFunction
+    '  * @function
+    '  * @instance
+    '  * @description Adds a function to the global mock for the current components scope
+    '  * @param {String|Function} functionOrFunctionName - a function by reference or the string name of a function to mock
+    '  * @param {Function} mockFunction - the function to use as a mock
+    '  */
+    function mockGlobalFunction(functionOrFunctionName as dynamic, mockFunction as function) as void
+      if type(functionOrFunctionName) = "roFunction" or type(functionOrFunctionName) = "Function"
+        functionName = functionOrFunctionName.toStr().tokenize(" ").peek()
+      else if type(functionOrFunctionName) = "String" or type(functionOrFunctionName) = "roString"
+        functionName = functionOrFunctionName
+      else
+        throw "Did not provide a function or function name to mock"
+      end if
+
+      globalAa = getGlobalAa()
+      if type(globalAa?._globalMocks) <> "roAssociativeArray"
+        globalAa._globalMocks = {}
+      end if
+
+      globalAa._globalMocks[functionName] = mockFunction
     end function
 
     ' /**

From 77d06a4937c04c9f8c135551a7eae16aa7f59be1 Mon Sep 17 00:00:00 2001
From: Christopher Dwyer-Perkins <chris@inverted-solutions.com>
Date: Thu, 11 Jan 2024 15:07:44 -0400
Subject: [PATCH 02/26] Moved some logic to the utils

---
 bsc-plugin/src/lib/rooibos/MockUtil.ts | 4 ++--
 bsc-plugin/src/lib/rooibos/Utils.ts    | 8 +++++++-
 2 files changed, 9 insertions(+), 3 deletions(-)

diff --git a/bsc-plugin/src/lib/rooibos/MockUtil.ts b/bsc-plugin/src/lib/rooibos/MockUtil.ts
index 0af49936..c3d14725 100644
--- a/bsc-plugin/src/lib/rooibos/MockUtil.ts
+++ b/bsc-plugin/src/lib/rooibos/MockUtil.ts
@@ -12,7 +12,7 @@ import type { RooibosSession } from './RooibosSession';
 import { diagnosticErrorProcessingFile } from '../utils/Diagnostics';
 import type { TestCase } from './TestCase';
 import type { TestSuite } from './TestSuite';
-import { getAllDottedGetParts } from './Utils';
+import { functionRequiresReturnValue, getAllDottedGetParts } from './Utils';
 
 export class MockUtil {
 
@@ -87,7 +87,7 @@ export class MockUtil {
         }
         const paramNames = functionStatement.func.parameters.map((param) => param.name.text).join(',');
 
-        const returnStatement = ((functionStatement.func.functionType?.kind === brighterscript.TokenKind.Sub && (functionStatement.func.returnTypeToken === undefined || functionStatement.func.returnTypeToken?.kind === brighterscript.TokenKind.Void)) || functionStatement.func.returnTypeToken?.kind === brighterscript.TokenKind.Void) ? 'return' : 'return result';
+        const returnStatement = functionRequiresReturnValue(functionStatement) ? 'return' : 'return result';
         this.astEditor.addToArray(functionStatement.func.body.statements, 0, new RawCodeStatement(undent`
             if RBS_SM_${this.fileId}_getMocksByFunctionName()["${methodName}"] <> invalid
                 result = RBS_SM_${this.fileId}_getMocksByFunctionName()["${methodName}"].callback(${paramNames})
diff --git a/bsc-plugin/src/lib/rooibos/Utils.ts b/bsc-plugin/src/lib/rooibos/Utils.ts
index 7299297b..b507b7e7 100644
--- a/bsc-plugin/src/lib/rooibos/Utils.ts
+++ b/bsc-plugin/src/lib/rooibos/Utils.ts
@@ -1,4 +1,4 @@
-import type { BrsFile, ClassStatement, Expression, FunctionStatement, AnnotationExpression, AstEditor } from 'brighterscript';
+import { type BrsFile, type ClassStatement, type Expression, type FunctionStatement, type AnnotationExpression, type AstEditor, TokenKind } from 'brighterscript';
 import * as brighterscript from 'brighterscript';
 import { diagnosticCorruptTestProduced } from '../utils/Diagnostics';
 
@@ -36,6 +36,12 @@ export function sanitizeBsJsonString(text: string) {
     return `"${text ? text.replace(/"/g, '\'') : ''}"`;
 }
 
+export function functionRequiresReturnValue(statement: FunctionStatement) {
+    const returnTypeToken = statement.func.returnTypeToken;
+    const functionType = statement.func.functionType;
+    return !((functionType?.kind === TokenKind.Sub && (returnTypeToken === undefined || returnTypeToken?.kind === TokenKind.Void)) || returnTypeToken?.kind === TokenKind.Void);
+}
+
 export function getAllDottedGetParts(dg: brighterscript.DottedGetExpression) {
     let parts = [dg?.name?.text];
     let nextPart = dg.obj;

From 9f759249da51313ca61f58eff77737be0fc088ea Mon Sep 17 00:00:00 2001
From: Christopher Dwyer-Perkins <chris@inverted-solutions.com>
Date: Thu, 11 Jan 2024 15:19:24 -0400
Subject: [PATCH 03/26] Added logic to inject code that adds global mock checks
 at the start of function bodys

---
 bsc-plugin/src/lib/rooibos/MockUtil.ts | 41 ++++++++++++++++++++++++--
 bsc-plugin/src/plugin.ts               |  1 +
 2 files changed, 40 insertions(+), 2 deletions(-)

diff --git a/bsc-plugin/src/lib/rooibos/MockUtil.ts b/bsc-plugin/src/lib/rooibos/MockUtil.ts
index c3d14725..d4529f95 100644
--- a/bsc-plugin/src/lib/rooibos/MockUtil.ts
+++ b/bsc-plugin/src/lib/rooibos/MockUtil.ts
@@ -1,7 +1,7 @@
 /* eslint-disable @typescript-eslint/no-var-requires */
 /* eslint-disable @typescript-eslint/no-unsafe-assignment */
-import type { BrsFile, Editor, ProgramBuilder } from 'brighterscript';
-import { Position, isClassStatement } from 'brighterscript';
+import type { BrsFile, Editor, NamespaceStatement, ProgramBuilder } from 'brighterscript';
+import { ParseMode, Parser, Position, TokenKind, WalkMode, createVisitor, isClassStatement, isNamespaceStatement } from 'brighterscript';
 import * as brighterscript from 'brighterscript';
 import type { RooibosConfig } from './RooibosConfig';
 import { RawCodeStatement } from './RawCodeStatement';
@@ -186,4 +186,41 @@ export class MockUtil {
         }
     }
 
+    public addRuntimeGlobalFunctionMocks(file: BrsFile, astEditor: Editor) {
+        file.ast.walk(createVisitor({
+            FunctionStatement: (statement, parent, owner, key) => {
+                if (!file.pkgPath.includes('rooibos/') && !file.pkgPath.endsWith('spec.brs')) {
+                    let isDisabledFoMocking = statement.annotations?.find(x => x.name.toLowerCase() === 'disablemocking');
+                    let parentNamespace = statement.findAncestor<NamespaceStatement>(isNamespaceStatement);
+                    while (parentNamespace && !isDisabledFoMocking) {
+                        if (parentNamespace) {
+                            isDisabledFoMocking = parentNamespace.annotations?.find(x => x.name.toLowerCase() === 'disablemocking');
+                            parentNamespace = parentNamespace.findAncestor<NamespaceStatement>(isNamespaceStatement);
+                        }
+                    }
+
+                    if (!isDisabledFoMocking) {
+                        const funcName = statement.getName(ParseMode.BrightScript);
+                        const returnResult = functionRequiresReturnValue(statement);
+                        const globalAaName = '__mocks_globalAa';
+                        const storageName = '_globalMocks';
+                        const template = undent`
+                            ${globalAaName} = getGlobalAa()
+                            if type(${globalAaName}?.${storageName}?.${funcName}) = "roFunction" or type(${globalAaName}?.${storageName}?.${funcName}) = "Function" then
+                                __mockedFunction = ${globalAaName}.${storageName}.${funcName}
+                                __mockResult = __mockedFunction(${statement.func.parameters.map(x => x.name.text).join(', ')})
+                                return ${returnResult ? '__mockResult' : ''}
+                            end if
+                        `;
+                        const mockStatements = Parser.parse(template).ast.statements;
+                        astEditor.arrayUnshift(statement.func.body.statements, ...mockStatements);
+                        file.needsTranspiled = true;
+                    }
+                }
+            }
+        }), {
+            walkMode: WalkMode.visitAllRecursive
+        });
+    }
+
 }
diff --git a/bsc-plugin/src/plugin.ts b/bsc-plugin/src/plugin.ts
index d89721df..f9d35f18 100644
--- a/bsc-plugin/src/plugin.ts
+++ b/bsc-plugin/src/plugin.ts
@@ -165,6 +165,7 @@ export class RooibosPlugin implements CompilerPlugin {
         }
 
         if (isBrsFile(event.file)) {
+            this.mockUtil.addRuntimeGlobalFunctionMocks(event.file, event.editor);
             if (this.shouldAddCodeCoverageToFile(event.file)) {
                 this.codeCoverageProcessor.addCodeCoverage(event.file, event.editor);
             }

From 90edc04ac960df31dc967e87bf6af6f18faf910e Mon Sep 17 00:00:00 2001
From: Christopher Dwyer-Perkins <chris@inverted-solutions.com>
Date: Mon, 15 Jan 2024 10:59:27 -0400
Subject: [PATCH 04/26] Fixed a reversed check

---
 bsc-plugin/src/lib/rooibos/MockUtil.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/bsc-plugin/src/lib/rooibos/MockUtil.ts b/bsc-plugin/src/lib/rooibos/MockUtil.ts
index d4529f95..b8b770fa 100644
--- a/bsc-plugin/src/lib/rooibos/MockUtil.ts
+++ b/bsc-plugin/src/lib/rooibos/MockUtil.ts
@@ -87,7 +87,7 @@ export class MockUtil {
         }
         const paramNames = functionStatement.func.parameters.map((param) => param.name.text).join(',');
 
-        const returnStatement = functionRequiresReturnValue(functionStatement) ? 'return' : 'return result';
+        const returnStatement = functionRequiresReturnValue(functionStatement) ? 'return result' : 'return';
         this.astEditor.addToArray(functionStatement.func.body.statements, 0, new RawCodeStatement(undent`
             if RBS_SM_${this.fileId}_getMocksByFunctionName()["${methodName}"] <> invalid
                 result = RBS_SM_${this.fileId}_getMocksByFunctionName()["${methodName}"].callback(${paramNames})

From 2c4feef04deecf5d052c4c48a753a3ab7b7be1da Mon Sep 17 00:00:00 2001
From: Christopher Dwyer-Perkins <chris@inverted-solutions.com>
Date: Mon, 15 Jan 2024 11:04:32 -0400
Subject: [PATCH 05/26] Removed new api in favor of expanding stubcall and
 updated injection code

---
 bsc-plugin/src/lib/rooibos/MockUtil.ts | 25 ++++++++++----
 bsc-plugin/src/plugin.ts               |  1 -
 framework/src/source/BaseTestSuite.bs  | 46 +++++++++++---------------
 3 files changed, 38 insertions(+), 34 deletions(-)

diff --git a/bsc-plugin/src/lib/rooibos/MockUtil.ts b/bsc-plugin/src/lib/rooibos/MockUtil.ts
index b8b770fa..cbecab22 100644
--- a/bsc-plugin/src/lib/rooibos/MockUtil.ts
+++ b/bsc-plugin/src/lib/rooibos/MockUtil.ts
@@ -85,15 +85,28 @@ export class MockUtil {
         for (let param of functionStatement.func.parameters) {
             param.asToken = null;
         }
-        const paramNames = functionStatement.func.parameters.map((param) => param.name.text).join(',');
 
-        const returnStatement = functionRequiresReturnValue(functionStatement) ? 'return result' : 'return';
-        this.astEditor.addToArray(functionStatement.func.body.statements, 0, new RawCodeStatement(undent`
+        const funcName = functionStatement.getName(ParseMode.BrightScript);
+        const paramNames = functionStatement.func.parameters.map((param) => param.name.text).join(',');
+        const requiresReturnValue = functionRequiresReturnValue(functionStatement);
+        const globalAaName = '__stubs_globalAa';
+        const resultName = '__stubOrMockResult';
+        const storageName = '__globalStubs';
+
+        console.log(funcName);
+        const template = undent`
+            ${globalAaName} = getGlobalAa()
             if RBS_SM_${this.fileId}_getMocksByFunctionName()["${methodName}"] <> invalid
-                result = RBS_SM_${this.fileId}_getMocksByFunctionName()["${methodName}"].callback(${paramNames})
-                ${returnStatement}
+                ${resultName} = RBS_SM_${this.fileId}_getMocksByFunctionName()["${methodName}"].callback(${paramNames})
+                return${requiresReturnValue ? ` ${resultName}` : '' }
+            else if type(${globalAaName}?.${storageName}?.${funcName}).endsWith("Function")
+                __stubFunction = ${globalAaName}.${storageName}.${funcName}
+                ${resultName} = __stubFunction(${paramNames})
+                return${requiresReturnValue ? ` ${resultName}` : ''}
             end if
-            `));
+        `;
+        const astCodeToInject = Parser.parse(template).ast.statements;
+        this.astEditor.arrayUnshift(functionStatement.func.body.statements, ...astCodeToInject);
 
         this.processedStatements.add(functionStatement);
     }
diff --git a/bsc-plugin/src/plugin.ts b/bsc-plugin/src/plugin.ts
index f9d35f18..d89721df 100644
--- a/bsc-plugin/src/plugin.ts
+++ b/bsc-plugin/src/plugin.ts
@@ -165,7 +165,6 @@ export class RooibosPlugin implements CompilerPlugin {
         }
 
         if (isBrsFile(event.file)) {
-            this.mockUtil.addRuntimeGlobalFunctionMocks(event.file, event.editor);
             if (this.shouldAddCodeCoverageToFile(event.file)) {
                 this.codeCoverageProcessor.addCodeCoverage(event.file, event.editor);
             }
diff --git a/framework/src/source/BaseTestSuite.bs b/framework/src/source/BaseTestSuite.bs
index ae63c1ef..5424e86d 100644
--- a/framework/src/source/BaseTestSuite.bs
+++ b/framework/src/source/BaseTestSuite.bs
@@ -1802,8 +1802,25 @@ namespace rooibos
       return invalid
     end function
 
-    function stubCall(invocation as dynamic, returnValue = invalid as dynamic) as object
-      'mock function body - the plugin replaces this
+    function stubCall(invocation as dynamic, stubOrReturnValue = invalid as dynamic, functionName = "" as string) as object
+      ' When stubbing global functions this will be called. Other wise the test code will be updated to call m._stubCall()
+      if type(invocation).endsWith("Function") and functionName = ""
+        functionName = invocation.toStr().tokenize(" ").peek()
+      else
+        throw "Did not provide a function to be stubbed"
+      end if
+
+      if not type(stubOrReturnValue).endsWith("Function")
+        throw "Did not provide a stub function"
+      end if
+
+      ' Store the stub on the component scope
+      globalAa = getGlobalAa()
+      if type(globalAa?.__globalStubs) <> "roAssociativeArray"
+        globalAa.__globalStubs = {}
+      end if
+
+      globalAa.__globalStubs[functionName] = stubOrReturnValue
       return invalid
     end function
 
@@ -2266,31 +2283,6 @@ namespace rooibos
       globalAa._globalMocks = invalid
     end function
 
-    ' /**
-    '  * @memberof module:BaseTestSuite
-    '  * @name mockGlobalFunction
-    '  * @function
-    '  * @instance
-    '  * @description Adds a function to the global mock for the current components scope
-    '  * @param {String|Function} functionOrFunctionName - a function by reference or the string name of a function to mock
-    '  * @param {Function} mockFunction - the function to use as a mock
-    '  */
-    function mockGlobalFunction(functionOrFunctionName as dynamic, mockFunction as function) as void
-      if type(functionOrFunctionName) = "roFunction" or type(functionOrFunctionName) = "Function"
-        functionName = functionOrFunctionName.toStr().tokenize(" ").peek()
-      else if type(functionOrFunctionName) = "String" or type(functionOrFunctionName) = "roString"
-        functionName = functionOrFunctionName
-      else
-        throw "Did not provide a function or function name to mock"
-      end if
-
-      globalAa = getGlobalAa()
-      if type(globalAa?._globalMocks) <> "roAssociativeArray"
-        globalAa._globalMocks = {}
-      end if
-
-      globalAa._globalMocks[functionName] = mockFunction
-    end function
 
     ' /**
     '  * @memberof module:BaseTestSuite

From 9f6111ea2cc7f385caeda53e845e963f396308d7 Mon Sep 17 00:00:00 2001
From: Christopher Dwyer-Perkins <chris@inverted-solutions.com>
Date: Mon, 15 Jan 2024 11:09:36 -0400
Subject: [PATCH 06/26] Updated some of the global function detection logic

---
 bsc-plugin/src/lib/rooibos/MockUtil.ts  | 30 +++++++++++++++++++++++++
 bsc-plugin/src/lib/rooibos/TestGroup.ts | 14 +++++++++---
 2 files changed, 41 insertions(+), 3 deletions(-)

diff --git a/bsc-plugin/src/lib/rooibos/MockUtil.ts b/bsc-plugin/src/lib/rooibos/MockUtil.ts
index cbecab22..b62933b9 100644
--- a/bsc-plugin/src/lib/rooibos/MockUtil.ts
+++ b/bsc-plugin/src/lib/rooibos/MockUtil.ts
@@ -171,6 +171,36 @@ export class MockUtil {
         }
         //modify args
         let arg0 = callExpression.args[0];
+        let arg1 = callExpression.args[1];
+
+        if (isStubCall) {
+            if (brighterscript.isFunctionExpression(arg1)) {
+                if (brighterscript.isDottedGetExpression(arg0)) {
+                    let nameParts = getAllDottedGetParts(arg0);
+                    let name = nameParts.pop();
+                    console.log(nameParts[0], namespaceLookup.has(nameParts[0].toLowerCase()));
+
+                    if (name) {
+                        //is a namespace?
+                        if (nameParts[0] && namespaceLookup.has(nameParts[0].toLowerCase())) {
+                            //then this must be a namespace method
+                            let fullPathName = nameParts.join('.').toLowerCase();
+                            let ns = namespaceLookup.get(fullPathName);
+                            if (!ns) {
+                                //TODO this is an error condition!
+                            }
+                            nameParts.push(name);
+                            let functionName = nameParts.join('_').toLowerCase();
+                            this.session.globalStubbedMethods.add(functionName);
+                        }
+                    }
+                } else if (brighterscript.isVariableExpression(arg0)) {
+                    const functionName = arg0.getName(ParseMode.BrightScript).toLowerCase();
+                    this.session.globalStubbedMethods.add(functionName);
+                }
+            }
+        }
+
         if (brighterscript.isCallExpression(arg0) && brighterscript.isDottedGetExpression(arg0.callee)) {
 
             //is it a namespace?
diff --git a/bsc-plugin/src/lib/rooibos/TestGroup.ts b/bsc-plugin/src/lib/rooibos/TestGroup.ts
index b09da4c2..64d6189f 100644
--- a/bsc-plugin/src/lib/rooibos/TestGroup.ts
+++ b/bsc-plugin/src/lib/rooibos/TestGroup.ts
@@ -98,14 +98,22 @@ export class TestGroup extends TestBlock {
     private modifyModernRooibosExpectCallExpression(callExpression: CallExpression, editor: AstEditor, namespaceLookup: Map<string, NamespaceContainer>) {
         let isNotCalled = false;
         let isStubCall = false;
+
+        //modify args
+        let arg0 = callExpression.args[0];
+        let arg1 = callExpression.args[1];
         if (isDottedGetExpression(callExpression.callee)) {
             const nameText = callExpression.callee.name.text;
-            editor.setProperty(callExpression.callee.name, 'text', `_${nameText}`);
             isNotCalled = nameText === 'expectNotCalled';
             isStubCall = nameText === 'stubCall';
+
+            if (isStubCall && (brighterscript.isDottedGetExpression(arg0) || brighterscript.isVariableExpression(arg0)) && brighterscript.isFunctionExpression(arg1)) {
+                console.log('modifyModernRooibosExpectCallExpression', callExpression.callee.name);
+                return;
+            }
+            editor.setProperty(callExpression.callee.name, 'text', `_${nameText}`);
         }
-        //modify args
-        let arg0 = callExpression.args[0];
+
         if (brighterscript.isCallExpression(arg0) && isDottedGetExpression(arg0.callee)) {
 
             //is it a namespace?

From fadbf2c450e71d03d21fa8c83f39b9184904d0cb Mon Sep 17 00:00:00 2001
From: Christopher Dwyer-Perkins <chris@inverted-solutions.com>
Date: Mon, 15 Jan 2024 12:00:49 -0400
Subject: [PATCH 07/26] Unit tests, fixes, sample test project update

---
 bsc-plugin/src/lib/rooibos/MockUtil.spec.ts | 133 +++++++++++++-------
 bsc-plugin/src/lib/rooibos/MockUtil.ts      |   6 +-
 bsc-plugin/src/plugin.spec.ts               |  91 ++++++++------
 framework/src/source/BaseTestSuite.bs       |   4 +-
 tests/src/source/NewExpectSyntax.spec.bs    |  61 +++++++++
 5 files changed, 205 insertions(+), 90 deletions(-)

diff --git a/bsc-plugin/src/lib/rooibos/MockUtil.spec.ts b/bsc-plugin/src/lib/rooibos/MockUtil.spec.ts
index 0183f3ec..b14865c2 100644
--- a/bsc-plugin/src/lib/rooibos/MockUtil.spec.ts
+++ b/bsc-plugin/src/lib/rooibos/MockUtil.spec.ts
@@ -78,9 +78,14 @@ describe('MockUtil', () => {
                 await builder.transpile();
                 let a = getContents('source/code.brs');
                 let b = trimLeading(`function sayHello(a1, a2)
+                __stubs_globalAa = getGlobalAa()
                 if RBS_SM_1_getMocksByFunctionName()["sayhello"] <> invalid
-                result = RBS_SM_1_getMocksByFunctionName()["sayhello"].callback(a1,a2)
-                return result
+                __stubOrMockResult = RBS_SM_1_getMocksByFunctionName()["sayhello"].callback(a1, a2)
+                return __stubOrMockResult
+                else if type(__stubs_globalAa?.__globalStubs?.sayhello).endsWith("Function")
+                __stubFunction = __stubs_globalAa.__globalStubs.sayhello
+                __stubOrMockResult = __stubFunction(a1, a2)
+                return __stubOrMockResult
                 end if
                 print "hello"
                 end function
@@ -109,9 +114,14 @@ describe('MockUtil', () => {
                 await builder.transpile();
                 let a = getContents('source/code.brs');
                 let b = trimLeading(`function sayHello(a1, a2)
+                __stubs_globalAa = getGlobalAa()
                 if RBS_SM_1_getMocksByFunctionName()["sayhello"] <> invalid
-                result = RBS_SM_1_getMocksByFunctionName()["sayhello"].callback(a1,a2)
-                return result
+                __stubOrMockResult = RBS_SM_1_getMocksByFunctionName()["sayhello"].callback(a1, a2)
+                return __stubOrMockResult
+                else if type(__stubs_globalAa?.__globalStubs?.sayhello).endsWith("Function")
+                __stubFunction = __stubs_globalAa.__globalStubs.sayhello
+                __stubOrMockResult = __stubFunction(a1, a2)
+                return __stubOrMockResult
                 end if
                 print "hello"
                 end function
@@ -143,8 +153,13 @@ describe('MockUtil', () => {
                 await builder.transpile();
                 let a = getContents('source/code.brs');
                 let b = trimLeading(`Sub RedLines_SetRulerLines(rulerLines)
+                __stubs_globalAa = getGlobalAa()
                 if RBS_SM_1_getMocksByFunctionName()["redlines_setrulerlines"] <> invalid
-                result = RBS_SM_1_getMocksByFunctionName()["redlines_setrulerlines"].callback(rulerLines)
+                __stubOrMockResult = RBS_SM_1_getMocksByFunctionName()["redlines_setrulerlines"].callback(rulerLines)
+                return
+                else if type(__stubs_globalAa?.__globalStubs?.redlines_setrulerlines).endsWith("Function")
+                __stubFunction = __stubs_globalAa.__globalStubs.redlines_setrulerlines
+                __stubOrMockResult = __stubFunction(rulerLines)
                 return
                 end if
                 For Each line In rulerLines.Items()
@@ -153,9 +168,14 @@ describe('MockUtil', () => {
                 end Sub
 
                 Sub RedLines_AddLine(id, position, coords, node, childMap) as Object
+                __stubs_globalAa = getGlobalAa()
                 if RBS_SM_1_getMocksByFunctionName()["redlines_addline"] <> invalid
-                result = RBS_SM_1_getMocksByFunctionName()["redlines_addline"].callback(id,position,coords,node,childMap)
-                return result
+                __stubOrMockResult = RBS_SM_1_getMocksByFunctionName()["redlines_addline"].callback(id, position, coords, node, childMap)
+                return __stubOrMockResult
+                else if type(__stubs_globalAa?.__globalStubs?.redlines_addline).endsWith("Function")
+                __stubFunction = __stubs_globalAa.__globalStubs.redlines_addline
+                __stubOrMockResult = __stubFunction(id, position, coords, node, childMap)
+                return __stubOrMockResult
                 end if
                 line = CreateObject("roSGNode", "Rectangle")
                 line.setField("id", id)
@@ -183,8 +203,13 @@ describe('MockUtil', () => {
                 await builder.transpile();
                 let a = getContents('source/code.brs');
                 let b = trimLeading(`sub sayHello(a1, a2)
+                __stubs_globalAa = getGlobalAa()
                 if RBS_SM_1_getMocksByFunctionName()["sayhello"] <> invalid
-                result = RBS_SM_1_getMocksByFunctionName()["sayhello"].callback(a1,a2)
+                __stubOrMockResult = RBS_SM_1_getMocksByFunctionName()["sayhello"].callback(a1, a2)
+                return
+                else if type(__stubs_globalAa?.__globalStubs?.sayhello).endsWith("Function")
+                __stubFunction = __stubs_globalAa.__globalStubs.sayhello
+                __stubOrMockResult = __stubFunction(a1, a2)
                 return
                 end if
                 print "hello"
@@ -214,9 +239,14 @@ describe('MockUtil', () => {
                 await builder.transpile();
                 let a = getContents('source/code.brs');
                 let b = trimLeading(`function person_utils_sayHello(a1, a2)
+                __stubs_globalAa = getGlobalAa()
                 if RBS_SM_1_getMocksByFunctionName()["person_utils_sayhello"] <> invalid
-                result = RBS_SM_1_getMocksByFunctionName()["person_utils_sayhello"].callback(a1,a2)
-                return result
+                __stubOrMockResult = RBS_SM_1_getMocksByFunctionName()["person_utils_sayhello"].callback(a1, a2)
+                return __stubOrMockResult
+                else if type(__stubs_globalAa?.__globalStubs?.person_utils_sayhello).endsWith("Function")
+                __stubFunction = __stubs_globalAa.__globalStubs.person_utils_sayhello
+                __stubOrMockResult = __stubFunction(a1, a2)
+                return __stubOrMockResult
                 end if
                 print "hello"
                 end function
@@ -245,8 +275,13 @@ describe('MockUtil', () => {
                 await builder.transpile();
                 let a = getContents('source/code.brs');
                 let b = trimLeading(`sub person_utils_sayHello(a1, a2)
+                __stubs_globalAa = getGlobalAa()
                 if RBS_SM_1_getMocksByFunctionName()["person_utils_sayhello"] <> invalid
-                result = RBS_SM_1_getMocksByFunctionName()["person_utils_sayhello"].callback(a1,a2)
+                __stubOrMockResult = RBS_SM_1_getMocksByFunctionName()["person_utils_sayhello"].callback(a1, a2)
+                return
+                else if type(__stubs_globalAa?.__globalStubs?.person_utils_sayhello).endsWith("Function")
+                __stubFunction = __stubs_globalAa.__globalStubs.person_utils_sayhello
+                __stubOrMockResult = __stubFunction(a1, a2)
                 return
                 end if
                 print "hello"
@@ -313,42 +348,52 @@ describe('MockUtil', () => {
                 await builder.transpile();
                 let a = getContents('source/code.brs');
                 let b = trimLeading(`function __beings_Person_builder()
-                instance = {}
-                instance.new = sub()
-                end sub
-                instance.sayHello = sub(a1, a2)
-                print "hello"
-                end sub
-                return instance
-                end function
-                function beings_Person()
-                instance = __beings_Person_builder()
-                instance.new()
-                return instance
-                end function
+                    instance = {}
+                    instance.new = sub()
+                    end sub
+                    instance.sayHello = sub(a1, a2)
+                    print "hello"
+                    end sub
+                    return instance
+                    end function
+                    function beings_Person()
+                    instance = __beings_Person_builder()
+                    instance.new()
+                    return instance
+                    end function
 
-                function beings_sayHello()
-                if RBS_SM_1_getMocksByFunctionName()["beings_sayhello"] <> invalid
-                result = RBS_SM_1_getMocksByFunctionName()["beings_sayhello"].callback()
-                return result
-                end if
-                print "hello2"
-                end function
+                    function beings_sayHello()
+                    __stubs_globalAa = getGlobalAa()
+                    if RBS_SM_1_getMocksByFunctionName()["beings_sayhello"] <> invalid
+                    __stubOrMockResult = RBS_SM_1_getMocksByFunctionName()["beings_sayhello"].callback()
+                    return __stubOrMockResult
+                    else if type(__stubs_globalAa?.__globalStubs?.beings_sayhello).endsWith("Function")
+                    __stubFunction = __stubs_globalAa.__globalStubs.beings_sayhello
+                    __stubOrMockResult = __stubFunction()
+                    return __stubOrMockResult
+                    end if
+                    print "hello2"
+                    end function
 
-                function sayHello()
-                if RBS_SM_1_getMocksByFunctionName()["sayhello"] <> invalid
-                result = RBS_SM_1_getMocksByFunctionName()["sayhello"].callback()
-                return result
-                end if
-                print "hello3"
-                end function
+                    function sayHello()
+                    __stubs_globalAa = getGlobalAa()
+                    if RBS_SM_1_getMocksByFunctionName()["sayhello"] <> invalid
+                    __stubOrMockResult = RBS_SM_1_getMocksByFunctionName()["sayhello"].callback()
+                    return __stubOrMockResult
+                    else if type(__stubs_globalAa?.__globalStubs?.sayhello).endsWith("Function")
+                    __stubFunction = __stubs_globalAa.__globalStubs.sayhello
+                    __stubOrMockResult = __stubFunction()
+                    return __stubOrMockResult
+                    end if
+                    print "hello3"
+                    end function
 
-                function RBS_SM_1_getMocksByFunctionName()
-                if m._rMocksByFunctionName = invalid
-                m._rMocksByFunctionName = {}
-                end if
-                return m._rMocksByFunctionName
-                end function
+                    function RBS_SM_1_getMocksByFunctionName()
+                    if m._rMocksByFunctionName = invalid
+                    m._rMocksByFunctionName = {}
+                    end if
+                    return m._rMocksByFunctionName
+                    end function
 `);
                 expect(a).to.equal(b);
 
diff --git a/bsc-plugin/src/lib/rooibos/MockUtil.ts b/bsc-plugin/src/lib/rooibos/MockUtil.ts
index b62933b9..28f808ff 100644
--- a/bsc-plugin/src/lib/rooibos/MockUtil.ts
+++ b/bsc-plugin/src/lib/rooibos/MockUtil.ts
@@ -86,21 +86,19 @@ export class MockUtil {
             param.asToken = null;
         }
 
-        const funcName = functionStatement.getName(ParseMode.BrightScript);
         const paramNames = functionStatement.func.parameters.map((param) => param.name.text).join(',');
         const requiresReturnValue = functionRequiresReturnValue(functionStatement);
         const globalAaName = '__stubs_globalAa';
         const resultName = '__stubOrMockResult';
         const storageName = '__globalStubs';
 
-        console.log(funcName);
         const template = undent`
             ${globalAaName} = getGlobalAa()
             if RBS_SM_${this.fileId}_getMocksByFunctionName()["${methodName}"] <> invalid
                 ${resultName} = RBS_SM_${this.fileId}_getMocksByFunctionName()["${methodName}"].callback(${paramNames})
                 return${requiresReturnValue ? ` ${resultName}` : '' }
-            else if type(${globalAaName}?.${storageName}?.${funcName}).endsWith("Function")
-                __stubFunction = ${globalAaName}.${storageName}.${funcName}
+            else if type(${globalAaName}?.${storageName}?.${methodName}).endsWith("Function")
+                __stubFunction = ${globalAaName}.${storageName}.${methodName}
                 ${resultName} = __stubFunction(${paramNames})
                 return${requiresReturnValue ? ` ${resultName}` : ''}
             end if
diff --git a/bsc-plugin/src/plugin.spec.ts b/bsc-plugin/src/plugin.spec.ts
index 45dc4d9e..ca66f89e 100644
--- a/bsc-plugin/src/plugin.spec.ts
+++ b/bsc-plugin/src/plugin.spec.ts
@@ -1021,19 +1021,24 @@ describe('RooibosPlugin', () => {
                 let codeText = getContents('code.brs');
                 expect(codeText).to.equal(undent`
                 function sayHello(firstName = "", lastName = "")
+                    __stubs_globalAa = getGlobalAa()
                     if RBS_SM_1_getMocksByFunctionName()["sayhello"] <> invalid
-                        result = RBS_SM_1_getMocksByFunctionName()["sayhello"].callback(firstName,lastName)
-                        return result
+                        __stubOrMockResult = RBS_SM_1_getMocksByFunctionName()["sayhello"].callback(firstName,lastName)
+                        return __stubOrMockResult
+                    else if type(__stubs_globalAa?.__globalStubs?.sayhello).endsWith("Function")
+                        __stubFunction = __stubs_globalAa.__globalStubs.sayhello
+                        __stubOrMockResult = __stubFunction()
+                        return __stubOrMockResult
                     end if
                     print firstName + " " + lastName
                 end function
 
-                    function RBS_SM_1_getMocksByFunctionName()
-                        if m._rMocksByFunctionName = invalid
-                        m._rMocksByFunctionName = {}
-                        end if
-                        return m._rMocksByFunctionName
-                    end function`);
+                function RBS_SM_1_getMocksByFunctionName()
+                    if m._rMocksByFunctionName = invalid
+                    m._rMocksByFunctionName = {}
+                    end if
+                    return m._rMocksByFunctionName
+                end function`);
             });
             it('correctly transpiles namespaced function calls', async () => {
                 plugin.config.isGlobalMethodMockingEnabled = true;
@@ -1066,50 +1071,56 @@ describe('RooibosPlugin', () => {
                 expect(
                     testText
                 ).to.eql(undent`
-                item = {
-                id: "item"
-                }
+                    item = {
+                        id: "item"
+                    }
 
-                m.currentAssertLineNumber = 7
-                m._expectCalled(utils_sayhello, "utils_sayhello", invalid, invalid, [
-                "arg1"
-                "arg2"
-                ], "return")
-                if m.currentResult?.isFail = true then m.done() : return invalid
+                    m.currentAssertLineNumber = 7
+                    m._expectCalled(utils_sayhello, "utils_sayhello", invalid, invalid, [
+                        "arg1"
+                        "arg2"
+                    ], "return")
+                    if m.currentResult?.isFail = true then m.done() : return invalid
 
 
-                m.currentAssertLineNumber = 8
-                m._expectCalled(utils_sayhello, "utils_sayhello", invalid, invalid, [])
-                if m.currentResult?.isFail = true then m.done() : return invalid
+                    m.currentAssertLineNumber = 8
+                    m._expectCalled(utils_sayhello, "utils_sayhello", invalid, invalid, [])
+                    if m.currentResult?.isFail = true then m.done() : return invalid
 
 
-                m.currentAssertLineNumber = 9
-                m._expectCalled(utils_sayhello, "utils_sayhello", invalid, invalid, [], "return")
-                if m.currentResult?.isFail = true then m.done() : return invalid
+                    m.currentAssertLineNumber = 9
+                    m._expectCalled(utils_sayhello, "utils_sayhello", invalid, invalid, [], "return")
+                    if m.currentResult?.isFail = true then m.done() : return invalid
 
 
-                m.currentAssertLineNumber = 10
-                m._expectCalled(utils_sayhello, "utils_sayhello", invalid, invalid, [
-                "arg1"
-                "arg2"
-                ])
-                if m.currentResult?.isFail = true then m.done() : return invalid
-`);
+                    m.currentAssertLineNumber = 10
+                    m._expectCalled(utils_sayhello, "utils_sayhello", invalid, invalid, [
+                        "arg1"
+                        "arg2"
+                    ])
+                    if m.currentResult?.isFail = true then m.done() : return invalid
+                `);
 
                 let codeText = trimLeading(getContents('code.brs'));
-                expect(codeText).to.equal(trimLeading(`function utils_sayHello(firstName = "", lastName = "")
-                if RBS_SM_1_getMocksByFunctionName()["utils_sayhello"] <> invalid
-                result = RBS_SM_1_getMocksByFunctionName()["utils_sayhello"].callback(firstName,lastName)
-                return result
-                end if
-                print firstName + " " + lastName
+                expect(codeText).to.equal(trimLeading(`
+                function utils_sayHello(firstName = "", lastName = "")
+                    __stubs_globalAa = getGlobalAa()
+                    if RBS_SM_1_getMocksByFunctionName()["utils_sayhello"] <> invalid
+                        __stubOrMockResult = RBS_SM_1_getMocksByFunctionName()["utils_sayhello"].callback(firstName,lastName)
+                        return __stubOrMockResult
+                    else if type(__stubs_globalAa?.__globalStubs?.beings_sayHello).endsWith("Function")
+                        __stubFunction = __stubs_globalAa.__globalStubs.beings_sayHello
+                        __stubOrMockResult = __stubFunction()
+                        return __stubOrMockResult
+                    end if
+                    print firstName + " " + lastName
                 end function
 
                 function RBS_SM_1_getMocksByFunctionName()
-                if m._rMocksByFunctionName = invalid
-                m._rMocksByFunctionName = {}
-                end if
-                return m._rMocksByFunctionName
+                    if m._rMocksByFunctionName = invalid
+                    m._rMocksByFunctionName = {}
+                    end if
+                    return m._rMocksByFunctionName
                 end function`));
             });
         });
diff --git a/framework/src/source/BaseTestSuite.bs b/framework/src/source/BaseTestSuite.bs
index 5424e86d..3f42a567 100644
--- a/framework/src/source/BaseTestSuite.bs
+++ b/framework/src/source/BaseTestSuite.bs
@@ -1820,7 +1820,7 @@ namespace rooibos
         globalAa.__globalStubs = {}
       end if
 
-      globalAa.__globalStubs[functionName] = stubOrReturnValue
+      globalAa.__globalStubs[lCase(functionName)] = stubOrReturnValue
       return invalid
     end function
 
@@ -2280,7 +2280,7 @@ namespace rooibos
 
       ' Clean up the global functions mocks as well
       globalAa = getGlobalAa()
-      globalAa._globalMocks = invalid
+      globalAa.__globalStubs = invalid
     end function
 
 
diff --git a/tests/src/source/NewExpectSyntax.spec.bs b/tests/src/source/NewExpectSyntax.spec.bs
index 6b79ba06..7cf0e06a 100644
--- a/tests/src/source/NewExpectSyntax.spec.bs
+++ b/tests/src/source/NewExpectSyntax.spec.bs
@@ -164,6 +164,47 @@ namespace tests
       m.assertRunningTestIsPassed()
     end function
 
+    '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+    @describe("stubCall local and namespace functions with new runtime functions")
+    '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+    @it("supports function stubbing with a new function at runtime")
+    function _()
+      m.wasCalled = false
+      m.stubCall(locationFunctionWithReturn, function()
+        m.wasCalled = true
+        return true
+      end function)
+      m.assertTrue(locationFunctionWithReturn())
+      m.assertTrue(m.wasCalled)
+      m.wasCalled = false
+
+      m.stubCall(locationFunctionWithoutReturn, sub()
+        m.wasCalled = true
+      end sub)
+      m.assertInvalid(locationFunctionWithoutReturn())
+      m.assertTrue(m.wasCalled)
+      m.wasCalled = false
+
+      m.wasCalled = false
+      m.stubCall(textNamespace.functionWithReturn, function()
+        m.wasCalled = true
+        return true
+      end function)
+      m.assertTrue(textNamespace.functionWithReturn())
+      m.assertTrue(m.wasCalled)
+      m.wasCalled = false
+
+      m.stubCall(textNamespace.functionWithoutReturn, sub()
+        m.wasCalled = true
+      end sub)
+      m.assertInvalid(textNamespace.functionWithoutReturn())
+      m.assertTrue(m.wasCalled)
+      m.wasCalled = false
+
+      m.assertRunningTestIsPassed()
+    end function
+
     '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
     @describe("stubCall callFunc functions")
     '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
@@ -391,4 +432,24 @@ namespace tests
 
   end class
 
+end namespace
+
+function locationFunctionWithReturn() as dynamic
+  m.wasCalled = false
+  return false
+end function
+
+sub locationFunctionWithoutReturn()
+  m.wasCalled = false
+end sub
+
+namespace textNamespace
+  function functionWithReturn() as dynamic
+    m.wasCalled = false
+    return false
+  end function
+
+  sub FunctionWithoutReturn()
+    m.wasCalled = false
+  end sub
 end namespace
\ No newline at end of file

From fc6ce1d708ccb96409fadf34b5accbb27b7cec00 Mon Sep 17 00:00:00 2001
From: Christopher Dwyer-Perkins <chris@inverted-solutions.com>
Date: Mon, 15 Jan 2024 12:02:33 -0400
Subject: [PATCH 08/26] removed console logs

---
 bsc-plugin/src/lib/rooibos/MockUtil.ts  | 1 -
 bsc-plugin/src/lib/rooibos/TestGroup.ts | 1 -
 2 files changed, 2 deletions(-)

diff --git a/bsc-plugin/src/lib/rooibos/MockUtil.ts b/bsc-plugin/src/lib/rooibos/MockUtil.ts
index 28f808ff..ee79d41c 100644
--- a/bsc-plugin/src/lib/rooibos/MockUtil.ts
+++ b/bsc-plugin/src/lib/rooibos/MockUtil.ts
@@ -176,7 +176,6 @@ export class MockUtil {
                 if (brighterscript.isDottedGetExpression(arg0)) {
                     let nameParts = getAllDottedGetParts(arg0);
                     let name = nameParts.pop();
-                    console.log(nameParts[0], namespaceLookup.has(nameParts[0].toLowerCase()));
 
                     if (name) {
                         //is a namespace?
diff --git a/bsc-plugin/src/lib/rooibos/TestGroup.ts b/bsc-plugin/src/lib/rooibos/TestGroup.ts
index 64d6189f..50ae80d4 100644
--- a/bsc-plugin/src/lib/rooibos/TestGroup.ts
+++ b/bsc-plugin/src/lib/rooibos/TestGroup.ts
@@ -108,7 +108,6 @@ export class TestGroup extends TestBlock {
             isStubCall = nameText === 'stubCall';
 
             if (isStubCall && (brighterscript.isDottedGetExpression(arg0) || brighterscript.isVariableExpression(arg0)) && brighterscript.isFunctionExpression(arg1)) {
-                console.log('modifyModernRooibosExpectCallExpression', callExpression.callee.name);
                 return;
             }
             editor.setProperty(callExpression.callee.name, 'text', `_${nameText}`);

From 8b3fee512c6ebdf2ebd9c0b939cc0a47ed404027 Mon Sep 17 00:00:00 2001
From: Christopher Dwyer-Perkins <chris@inverted-solutions.com>
Date: Mon, 15 Jan 2024 13:39:51 -0400
Subject: [PATCH 09/26] Transpile the file if we had to touch modify it

---
 bsc-plugin/src/lib/rooibos/MockUtil.ts | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/bsc-plugin/src/lib/rooibos/MockUtil.ts b/bsc-plugin/src/lib/rooibos/MockUtil.ts
index ee79d41c..129be378 100644
--- a/bsc-plugin/src/lib/rooibos/MockUtil.ts
+++ b/bsc-plugin/src/lib/rooibos/MockUtil.ts
@@ -52,8 +52,8 @@ export class MockUtil {
         this.processedStatements = new Set<brighterscript.FunctionStatement>();
         this.astEditor = astEditor;
         // console.log('processing global methods on ', file.pkgPath);
-        for (let fs of file.parser.references.functionStatements) {
-            this.enableMockOnFunction(fs);
+        for (let functionStatement of file.parser.references.functionStatements) {
+            this.enableMockOnFunction(file, functionStatement);
         }
 
         this.filePathMap[this.fileId] = file.pkgPath;
@@ -62,7 +62,7 @@ export class MockUtil {
         }
     }
 
-    private enableMockOnFunction(functionStatement: brighterscript.FunctionStatement) {
+    private enableMockOnFunction(file: BrsFile, functionStatement: brighterscript.FunctionStatement) {
         if (isClassStatement(functionStatement.parent?.parent)) {
             // console.log('skipping class', functionStatement.parent?.parent?.name?.text);
             return;
@@ -107,6 +107,7 @@ export class MockUtil {
         this.astEditor.arrayUnshift(functionStatement.func.body.statements, ...astCodeToInject);
 
         this.processedStatements.add(functionStatement);
+        file.needsTranspiled = true;
     }
 
     addBrsAPIText(file: BrsFile) {

From 33079553dc415abb5cb119a7df358bb3d95acaea Mon Sep 17 00:00:00 2001
From: Christopher Dwyer-Perkins <chris@inverted-solutions.com>
Date: Mon, 15 Jan 2024 14:21:22 -0400
Subject: [PATCH 10/26] Fixed global mocks and stubs not clearing

---
 framework/src/source/BaseTestSuite.bs | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/framework/src/source/BaseTestSuite.bs b/framework/src/source/BaseTestSuite.bs
index 3f42a567..170a5712 100644
--- a/framework/src/source/BaseTestSuite.bs
+++ b/framework/src/source/BaseTestSuite.bs
@@ -2268,6 +2268,10 @@ namespace rooibos
     '  * @description Cleans up all tracking data associated with mocks
     '  */
     function cleanMocks() as void
+      ' Clean up the global functions mocks as well
+      globalAa = getGlobalAa()
+      globalAa.__globalStubs = invalid
+
       if m.mocks = invalid
         return
       end if
@@ -2277,10 +2281,6 @@ namespace rooibos
       end for
       m.mocks = invalid
       rooibos.resetMocksByFunctionName()
-
-      ' Clean up the global functions mocks as well
-      globalAa = getGlobalAa()
-      globalAa.__globalStubs = invalid
     end function
 
 

From a155a290be96f7d9111cbaca00fbe92b77bf72c6 Mon Sep 17 00:00:00 2001
From: Christopher Dwyer-Perkins <chris@inverted-solutions.com>
Date: Tue, 16 Jan 2024 09:46:34 -0400
Subject: [PATCH 11/26] updated some of the checks around transforming stubcall
 functions

---
 bsc-plugin/src/lib/rooibos/MockUtil.ts  | 2 +-
 bsc-plugin/src/lib/rooibos/TestGroup.ts | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/bsc-plugin/src/lib/rooibos/MockUtil.ts b/bsc-plugin/src/lib/rooibos/MockUtil.ts
index 129be378..61e6d09b 100644
--- a/bsc-plugin/src/lib/rooibos/MockUtil.ts
+++ b/bsc-plugin/src/lib/rooibos/MockUtil.ts
@@ -173,7 +173,7 @@ export class MockUtil {
         let arg1 = callExpression.args[1];
 
         if (isStubCall) {
-            if (brighterscript.isFunctionExpression(arg1)) {
+            if (!brighterscript.isCallExpression(arg0)) {
                 if (brighterscript.isDottedGetExpression(arg0)) {
                     let nameParts = getAllDottedGetParts(arg0);
                     let name = nameParts.pop();
diff --git a/bsc-plugin/src/lib/rooibos/TestGroup.ts b/bsc-plugin/src/lib/rooibos/TestGroup.ts
index 50ae80d4..ad38bbe3 100644
--- a/bsc-plugin/src/lib/rooibos/TestGroup.ts
+++ b/bsc-plugin/src/lib/rooibos/TestGroup.ts
@@ -107,7 +107,7 @@ export class TestGroup extends TestBlock {
             isNotCalled = nameText === 'expectNotCalled';
             isStubCall = nameText === 'stubCall';
 
-            if (isStubCall && (brighterscript.isDottedGetExpression(arg0) || brighterscript.isVariableExpression(arg0)) && brighterscript.isFunctionExpression(arg1)) {
+            if (isStubCall && !brighterscript.isCallExpression(arg0)) {
                 return;
             }
             editor.setProperty(callExpression.callee.name, 'text', `_${nameText}`);

From a342165056d754ccd151267e0ab57d4842184cf2 Mon Sep 17 00:00:00 2001
From: Christopher Dwyer-Perkins <chris@inverted-solutions.com>
Date: Tue, 16 Jan 2024 09:47:28 -0400
Subject: [PATCH 12/26] Made some code reusable and fixed some imports

---
 bsc-plugin/src/lib/rooibos/MockUtil.ts       | 65 +++++++++-----------
 bsc-plugin/src/lib/rooibos/RooibosSession.ts | 14 +----
 bsc-plugin/src/lib/rooibos/TestGroup.ts      |  3 +-
 bsc-plugin/src/lib/rooibos/Utils.ts          |  4 +-
 4 files changed, 33 insertions(+), 53 deletions(-)

diff --git a/bsc-plugin/src/lib/rooibos/MockUtil.ts b/bsc-plugin/src/lib/rooibos/MockUtil.ts
index 61e6d09b..6da276a1 100644
--- a/bsc-plugin/src/lib/rooibos/MockUtil.ts
+++ b/bsc-plugin/src/lib/rooibos/MockUtil.ts
@@ -1,7 +1,7 @@
 /* eslint-disable @typescript-eslint/no-var-requires */
 /* eslint-disable @typescript-eslint/no-unsafe-assignment */
-import type { BrsFile, Editor, NamespaceStatement, ProgramBuilder } from 'brighterscript';
-import { ParseMode, Parser, Position, TokenKind, WalkMode, createVisitor, isClassStatement, isNamespaceStatement } from 'brighterscript';
+import type { BrsFile, Editor, NamespaceContainer, NamespaceStatement, ProgramBuilder } from 'brighterscript';
+import { ParseMode, Parser, Position, WalkMode, createVisitor, isClassStatement, isNamespaceStatement } from 'brighterscript';
 import * as brighterscript from 'brighterscript';
 import type { RooibosConfig } from './RooibosConfig';
 import { RawCodeStatement } from './RawCodeStatement';
@@ -175,22 +175,10 @@ export class MockUtil {
         if (isStubCall) {
             if (!brighterscript.isCallExpression(arg0)) {
                 if (brighterscript.isDottedGetExpression(arg0)) {
-                    let nameParts = getAllDottedGetParts(arg0);
-                    let name = nameParts.pop();
+                    const functionName = this.getFinalNamespaceFunctionNameFromDottedGet(arg0, namespaceLookup);
 
-                    if (name) {
-                        //is a namespace?
-                        if (nameParts[0] && namespaceLookup.has(nameParts[0].toLowerCase())) {
-                            //then this must be a namespace method
-                            let fullPathName = nameParts.join('.').toLowerCase();
-                            let ns = namespaceLookup.get(fullPathName);
-                            if (!ns) {
-                                //TODO this is an error condition!
-                            }
-                            nameParts.push(name);
-                            let functionName = nameParts.join('_').toLowerCase();
-                            this.session.globalStubbedMethods.add(functionName);
-                        }
+                    if (functionName) {
+                        this.session.globalStubbedMethods.add(functionName);
                     }
                 } else if (brighterscript.isVariableExpression(arg0)) {
                     const functionName = arg0.getName(ParseMode.BrightScript).toLowerCase();
@@ -200,26 +188,10 @@ export class MockUtil {
         }
 
         if (brighterscript.isCallExpression(arg0) && brighterscript.isDottedGetExpression(arg0.callee)) {
+            const functionName = this.getFinalNamespaceFunctionNameFromDottedGet(arg0.callee, namespaceLookup);
 
-            //is it a namespace?
-            let dg = arg0.callee;
-            let nameParts = getAllDottedGetParts(dg);
-            let name = nameParts.pop();
-
-            // console.log('found expect with name', name);
-            if (name) {
-                //is a namespace?
-                if (nameParts[0] && namespaceLookup.has(nameParts[0].toLowerCase())) {
-                    //then this must be a namespace method
-                    let fullPathName = nameParts.join('.').toLowerCase();
-                    let ns = namespaceLookup.get(fullPathName);
-                    if (!ns) {
-                        //TODO this is an error condition!
-                    }
-                    nameParts.push(name);
-                    let functionName = nameParts.join('_').toLowerCase();
-                    this.session.globalStubbedMethods.add(functionName);
-                }
+            if (functionName) {
+                this.session.globalStubbedMethods.add(functionName);
             }
         } else if (brighterscript.isCallExpression(arg0) && brighterscript.isVariableExpression(arg0.callee)) {
             let functionName = arg0.callee.getName(brighterscript.ParseMode.BrightScript).toLowerCase();
@@ -227,6 +199,27 @@ export class MockUtil {
         }
     }
 
+
+    private getFinalNamespaceFunctionNameFromDottedGet(dg: brighterscript.DottedGetExpression, namespaceLookup: Map<string, NamespaceContainer>) {
+        //is it a namespace?
+        let nameParts = getAllDottedGetParts(dg);
+        let name = nameParts.pop();
+
+        if (name) {
+            //is a namespace?
+            if (nameParts[0] && namespaceLookup.has(nameParts[0].toLowerCase())) {
+                //then this must be a namespace method
+                let fullPathName = nameParts.join('.').toLowerCase();
+                let ns = namespaceLookup.get(fullPathName);
+                if (!ns) {
+                    //TODO this is an error condition!
+                }
+                nameParts.push(name);
+                return nameParts.join('_').toLowerCase();
+            }
+        }
+    }
+
     public addRuntimeGlobalFunctionMocks(file: BrsFile, astEditor: Editor) {
         file.ast.walk(createVisitor({
             FunctionStatement: (statement, parent, owner, key) => {
diff --git a/bsc-plugin/src/lib/rooibos/RooibosSession.ts b/bsc-plugin/src/lib/rooibos/RooibosSession.ts
index 53e7fa88..70c9f73e 100644
--- a/bsc-plugin/src/lib/rooibos/RooibosSession.ts
+++ b/bsc-plugin/src/lib/rooibos/RooibosSession.ts
@@ -1,5 +1,5 @@
 import * as path from 'path';
-import type { BrsFile, BscFile, ClassStatement, FunctionStatement, NamespaceStatement, Program, ProgramBuilder, Scope, Statement } from 'brighterscript';
+import type { BrsFile, ClassStatement, FunctionStatement, NamespaceContainer, NamespaceStatement, Program, ProgramBuilder, Scope } from 'brighterscript';
 import { isBrsFile, isCallExpression, isVariableExpression, ParseMode, WalkMode } from 'brighterscript';
 import type { AstEditor } from 'brighterscript/dist/astUtils/AstEditor';
 import type { RooibosConfig } from './RooibosConfig';
@@ -16,18 +16,6 @@ import type { MockUtil } from './MockUtil';
 // eslint-disable-next-line
 const pkg = require('../../../package.json');
 
-
-export interface NamespaceContainer {
-    file: BscFile;
-    fullName: string;
-    nameRange: Range;
-    lastPartName: string;
-    statements: Statement[];
-    classStatements: Record<string, ClassStatement>;
-    functionStatements: Record<string, FunctionStatement>;
-    namespaces: Record<string, NamespaceContainer>;
-}
-
 export class RooibosSession {
 
     constructor(builder: ProgramBuilder, fileFactory: FileFactory) {
diff --git a/bsc-plugin/src/lib/rooibos/TestGroup.ts b/bsc-plugin/src/lib/rooibos/TestGroup.ts
index ad38bbe3..7e912c08 100644
--- a/bsc-plugin/src/lib/rooibos/TestGroup.ts
+++ b/bsc-plugin/src/lib/rooibos/TestGroup.ts
@@ -1,4 +1,4 @@
-import type { AstEditor, CallExpression, DottedGetExpression } from 'brighterscript';
+import type { AstEditor, CallExpression, DottedGetExpression, NamespaceContainer } from 'brighterscript';
 import { ArrayLiteralExpression, createInvalidLiteral, createStringLiteral, createToken, isDottedGetExpression, TokenKind, isFunctionExpression, Parser } from 'brighterscript';
 import * as brighterscript from 'brighterscript';
 import { BrsTranspileState } from 'brighterscript/dist/parser/BrsTranspileState';
@@ -8,7 +8,6 @@ import type { TestCase } from './TestCase';
 import type { TestSuite } from './TestSuite';
 import { TestBlock } from './TestSuite';
 import { getAllDottedGetParts, getRootObjectFromDottedGet, getStringPathFromDottedGet, sanitizeBsJsonString } from './Utils';
-import type { NamespaceContainer } from './RooibosSession';
 
 export class TestGroup extends TestBlock {
 
diff --git a/bsc-plugin/src/lib/rooibos/Utils.ts b/bsc-plugin/src/lib/rooibos/Utils.ts
index b507b7e7..df13beae 100644
--- a/bsc-plugin/src/lib/rooibos/Utils.ts
+++ b/bsc-plugin/src/lib/rooibos/Utils.ts
@@ -1,4 +1,5 @@
-import { type BrsFile, type ClassStatement, type Expression, type FunctionStatement, type AnnotationExpression, type AstEditor, TokenKind } from 'brighterscript';
+import type { AnnotationExpression, AstEditor, BrsFile, ClassStatement, Expression, FunctionStatement } from 'brighterscript';
+import { TokenKind } from 'brighterscript';
 import * as brighterscript from 'brighterscript';
 import { diagnosticCorruptTestProduced } from '../utils/Diagnostics';
 
@@ -52,7 +53,6 @@ export function getAllDottedGetParts(dg: brighterscript.DottedGetExpression) {
     return parts.reverse();
 }
 
-
 export function getRootObjectFromDottedGet(value: brighterscript.DottedGetExpression) {
     let root;
     if (brighterscript.isDottedGetExpression(value) || brighterscript.isIndexedGetExpression(value)) {

From 02c6f4e8d32a4eaba5f3cc233b0ce89805743a30 Mon Sep 17 00:00:00 2001
From: Christopher Dwyer-Perkins <chris@inverted-solutions.com>
Date: Tue, 16 Jan 2024 10:51:48 -0400
Subject: [PATCH 13/26] Added back the disablemocking logic and removed unused
 code

---
 bsc-plugin/src/lib/rooibos/MockUtil.spec.ts | 37 +++++++++++++++
 bsc-plugin/src/lib/rooibos/MockUtil.ts      | 52 ++++++---------------
 2 files changed, 50 insertions(+), 39 deletions(-)

diff --git a/bsc-plugin/src/lib/rooibos/MockUtil.spec.ts b/bsc-plugin/src/lib/rooibos/MockUtil.spec.ts
index b14865c2..f0c44f26 100644
--- a/bsc-plugin/src/lib/rooibos/MockUtil.spec.ts
+++ b/bsc-plugin/src/lib/rooibos/MockUtil.spec.ts
@@ -399,6 +399,43 @@ describe('MockUtil', () => {
 
             });
 
+            it('will skip functions with the disableMocking annotation', async () => {
+                program.setFile('source/code.bs', `
+                @disableMocking
+                namespace beings
+                function sayHello()
+                    print "hello2"
+                end function
+                end namespace
+                namespace aliens
+                @disableMocking
+                function sayHello()
+                    print "hello3"
+                end function
+                end namespace
+                @disableMocking
+                function sayHello()
+                    print "hello4"
+                end function
+            `);
+                program.validate();
+                expect(program.getDiagnostics()).to.be.empty;
+                await builder.transpile();
+                let a = getContents('source/code.brs');
+                let b = trimLeading(`function beings_sayHello()
+                    print "hello2"
+                    end function
+                    function aliens_sayHello()
+                    print "hello3"
+                    end function
+
+                    function sayHello()
+                    print "hello4"
+                    end function`);
+                expect(a).to.equal(b);
+
+            });
+
         });
 
         it('excludes files from coverage', async () => {
diff --git a/bsc-plugin/src/lib/rooibos/MockUtil.ts b/bsc-plugin/src/lib/rooibos/MockUtil.ts
index 6da276a1..805c485f 100644
--- a/bsc-plugin/src/lib/rooibos/MockUtil.ts
+++ b/bsc-plugin/src/lib/rooibos/MockUtil.ts
@@ -79,6 +79,19 @@ export class MockUtil {
             return;
         }
 
+        let isDisabledFoMocking = functionStatement.annotations?.find(x => x.name.toLowerCase() === 'disablemocking');
+        let parentNamespace = functionStatement.findAncestor<NamespaceStatement>(isNamespaceStatement);
+        while (parentNamespace && !isDisabledFoMocking) {
+            if (parentNamespace) {
+                isDisabledFoMocking = parentNamespace.annotations?.find(x => x.name.toLowerCase() === 'disablemocking');
+                parentNamespace = parentNamespace.findAncestor<NamespaceStatement>(isNamespaceStatement);
+            }
+        }
+        if (isDisabledFoMocking) {
+            // The developer has stated that this function is not safe to be mocked
+            return;
+        }
+
         // console.log('processing  stubbed method', methodName);
         // TODO check if the user has actually mocked or stubbed this function, otherwise leave it alone!
 
@@ -171,7 +184,6 @@ export class MockUtil {
         //modify args
         let arg0 = callExpression.args[0];
         let arg1 = callExpression.args[1];
-
         if (isStubCall) {
             if (!brighterscript.isCallExpression(arg0)) {
                 if (brighterscript.isDottedGetExpression(arg0)) {
@@ -219,42 +231,4 @@ export class MockUtil {
             }
         }
     }
-
-    public addRuntimeGlobalFunctionMocks(file: BrsFile, astEditor: Editor) {
-        file.ast.walk(createVisitor({
-            FunctionStatement: (statement, parent, owner, key) => {
-                if (!file.pkgPath.includes('rooibos/') && !file.pkgPath.endsWith('spec.brs')) {
-                    let isDisabledFoMocking = statement.annotations?.find(x => x.name.toLowerCase() === 'disablemocking');
-                    let parentNamespace = statement.findAncestor<NamespaceStatement>(isNamespaceStatement);
-                    while (parentNamespace && !isDisabledFoMocking) {
-                        if (parentNamespace) {
-                            isDisabledFoMocking = parentNamespace.annotations?.find(x => x.name.toLowerCase() === 'disablemocking');
-                            parentNamespace = parentNamespace.findAncestor<NamespaceStatement>(isNamespaceStatement);
-                        }
-                    }
-
-                    if (!isDisabledFoMocking) {
-                        const funcName = statement.getName(ParseMode.BrightScript);
-                        const returnResult = functionRequiresReturnValue(statement);
-                        const globalAaName = '__mocks_globalAa';
-                        const storageName = '_globalMocks';
-                        const template = undent`
-                            ${globalAaName} = getGlobalAa()
-                            if type(${globalAaName}?.${storageName}?.${funcName}) = "roFunction" or type(${globalAaName}?.${storageName}?.${funcName}) = "Function" then
-                                __mockedFunction = ${globalAaName}.${storageName}.${funcName}
-                                __mockResult = __mockedFunction(${statement.func.parameters.map(x => x.name.text).join(', ')})
-                                return ${returnResult ? '__mockResult' : ''}
-                            end if
-                        `;
-                        const mockStatements = Parser.parse(template).ast.statements;
-                        astEditor.arrayUnshift(statement.func.body.statements, ...mockStatements);
-                        file.needsTranspiled = true;
-                    }
-                }
-            }
-        }), {
-            walkMode: WalkMode.visitAllRecursive
-        });
-    }
-
 }

From 02c64df7dcc7bbe715bd7f9cbdfc4b95b5ef0d07 Mon Sep 17 00:00:00 2001
From: Christopher Dwyer-Perkins <chris@inverted-solutions.com>
Date: Tue, 16 Jan 2024 13:13:23 -0400
Subject: [PATCH 14/26] Fixed bad ast related to noEarlyExit

---
 bsc-plugin/src/lib/rooibos/TestGroup.ts | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/bsc-plugin/src/lib/rooibos/TestGroup.ts b/bsc-plugin/src/lib/rooibos/TestGroup.ts
index 7e912c08..bff566f9 100644
--- a/bsc-plugin/src/lib/rooibos/TestGroup.ts
+++ b/bsc-plugin/src/lib/rooibos/TestGroup.ts
@@ -74,10 +74,11 @@ export class TestGroup extends TestBlock {
                                 if (dge.name.text === 'expectCalled' || dge.name.text === 'expectNotCalled') {
                                     this.modifyModernRooibosExpectCallExpression(callExpression, editor, namespaceLookup);
                                 }
-                                const trailingLine = Parser.parse(`${noEarlyExit ? '' : `if m.currentResult?.isFail = true then m.done() : return ${isSub ? '' : 'invalid'}`}`).ast.statements[0];
-
-                                editor.arraySplice(owner, key + 1, 0, trailingLine);
 
+                                if (!noEarlyExit) {
+                                    const trailingLine = Parser.parse(`if m.currentResult?.isFail = true then m.done() : return ${isSub ? '' : 'invalid'}`).ast.statements[0];
+                                    editor.arraySplice(owner, key + 1, 0, trailingLine);
+                                }
                                 const leadingLine = Parser.parse(`m.currentAssertLineNumber = ${callExpression.range.start.line}`).ast.statements[0];
                                 editor.arraySplice(owner, key, 0, leadingLine);
                             }

From 86656dd1258e111891905674c60eabcec933ce1e Mon Sep 17 00:00:00 2001
From: Christopher Dwyer-Perkins <chris@inverted-solutions.com>
Date: Tue, 16 Jan 2024 13:13:47 -0400
Subject: [PATCH 15/26] Updated bsc in tests app

---
 tests/package-lock.json | 754 +++++++++++++++++++++++++++++-----------
 tests/package.json      |   4 +-
 2 files changed, 546 insertions(+), 212 deletions(-)

diff --git a/tests/package-lock.json b/tests/package-lock.json
index 6a67ac3c..6ec48dfe 100644
--- a/tests/package-lock.json
+++ b/tests/package-lock.json
@@ -1,15 +1,15 @@
 {
   "name": "rooibos-tests",
-  "version": "4.1.0",
+  "version": "4.1.1",
   "lockfileVersion": 2,
   "requires": true,
   "packages": {
     "": {
       "name": "rooibos-tests",
-      "version": "4.1.0",
+      "version": "4.1.1",
       "license": "MIT",
       "devDependencies": {
-        "brighterscript": "^0.64.0",
+        "brighterscript": "^0.65.15",
         "ts-node": "^10.7.0",
         "typescript": "^4.6.4"
       }
@@ -86,6 +86,56 @@
         "node": ">= 8"
       }
     },
+    "node_modules/@postman/form-data": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/@postman/form-data/-/form-data-3.1.1.tgz",
+      "integrity": "sha512-vjh8Q2a8S6UCm/KKs31XFJqEEgmbjBmpPNVV2eVav6905wyFAwaUOBGA1NPBI4ERH9MMZc6w0umFgM6WbEPMdg==",
+      "dev": true,
+      "dependencies": {
+        "asynckit": "^0.4.0",
+        "combined-stream": "^1.0.8",
+        "mime-types": "^2.1.12"
+      },
+      "engines": {
+        "node": ">= 6"
+      }
+    },
+    "node_modules/@postman/tough-cookie": {
+      "version": "4.1.3-postman.1",
+      "resolved": "https://registry.npmjs.org/@postman/tough-cookie/-/tough-cookie-4.1.3-postman.1.tgz",
+      "integrity": "sha512-txpgUqZOnWYnUHZpHjkfb0IwVH4qJmyq77pPnJLlfhMtdCLMFTEeQHlzQiK906aaNCe4NEB5fGJHo9uzGbFMeA==",
+      "dev": true,
+      "dependencies": {
+        "psl": "^1.1.33",
+        "punycode": "^2.1.1",
+        "universalify": "^0.2.0",
+        "url-parse": "^1.5.3"
+      },
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/@postman/tough-cookie/node_modules/universalify": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
+      "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==",
+      "dev": true,
+      "engines": {
+        "node": ">= 4.0.0"
+      }
+    },
+    "node_modules/@postman/tunnel-agent": {
+      "version": "0.6.3",
+      "resolved": "https://registry.npmjs.org/@postman/tunnel-agent/-/tunnel-agent-0.6.3.tgz",
+      "integrity": "sha512-k57fzmAZ2PJGxfOA4SGR05ejorHbVAa/84Hxh/2nAztjNXc4ZjOm9NUIk6/Z6LCrBvJZqjRZbN8e/nROVUPVdg==",
+      "dev": true,
+      "dependencies": {
+        "safe-buffer": "^5.0.1"
+      },
+      "engines": {
+        "node": "*"
+      }
+    },
     "node_modules/@rokucommunity/bslib": {
       "version": "0.1.1",
       "resolved": "https://registry.npmjs.org/@rokucommunity/bslib/-/bslib-0.1.1.tgz",
@@ -211,6 +261,12 @@
       "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
       "dev": true
     },
+    "node_modules/argparse": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+      "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+      "dev": true
+    },
     "node_modules/array-flat-polyfill": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/array-flat-polyfill/-/array-flat-polyfill-1.0.1.tgz",
@@ -265,6 +321,26 @@
       "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
       "dev": true
     },
+    "node_modules/base64-js": {
+      "version": "1.5.1",
+      "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
+      "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/feross"
+        },
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/feross"
+        },
+        {
+          "type": "consulting",
+          "url": "https://feross.org/support"
+        }
+      ]
+    },
     "node_modules/bcrypt-pbkdf": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
@@ -283,6 +359,12 @@
         "node": ">=8"
       }
     },
+    "node_modules/bluebird": {
+      "version": "2.11.0",
+      "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.11.0.tgz",
+      "integrity": "sha512-UfFSr22dmHPQqPP9XWHRhq+gWnHCYguQGkXQlbyPtW5qTnhFWA8/iXg765tH0cAjy7l/zPJ1aBTO0g5XgA7kvQ==",
+      "dev": true
+    },
     "node_modules/brace-expansion": {
       "version": "1.1.11",
       "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@@ -306,10 +388,11 @@
       }
     },
     "node_modules/brighterscript": {
-      "version": "0.64.3",
-      "resolved": "https://registry.npmjs.org/brighterscript/-/brighterscript-0.64.3.tgz",
-      "integrity": "sha512-W0k0ID3yCbFGba80d8RlggldvRs6u5Y9F6F+AQ4fWDdJcWeHJ3vy5/pabzDbYD9sen3trVxiZ2yXdA7XK35V5g==",
+      "version": "0.65.16",
+      "resolved": "https://registry.npmjs.org/brighterscript/-/brighterscript-0.65.16.tgz",
+      "integrity": "sha512-2RJMF8itkrPXtZ92JkJf3emvzcCl5ETXGVyPsdO/hMKbUGinEGMRFG07FR0bzjRotbALzsozGE/Hjzw412Sjww==",
       "dev": true,
+      "license": "MIT",
       "dependencies": {
         "@rokucommunity/bslib": "^0.1.1",
         "@xml-tools/parser": "^1.0.7",
@@ -318,6 +401,7 @@
         "chevrotain": "^7.0.1",
         "chokidar": "^3.5.1",
         "clear": "^0.1.0",
+        "coveralls-next": "^4.2.0",
         "cross-platform-clear-console": "^2.3.0",
         "debounce-promise": "^3.1.0",
         "eventemitter3": "^4.0.0",
@@ -333,7 +417,7 @@
         "parse-ms": "^2.1.0",
         "readline": "^1.3.0",
         "require-relative": "^0.8.7",
-        "roku-deploy": "^3.10.1",
+        "roku-deploy": "^3.11.2",
         "serialize-error": "^7.0.1",
         "source-map": "^0.7.4",
         "vscode-languageserver": "7.0.0",
@@ -347,6 +431,15 @@
         "bsc": "dist/cli.js"
       }
     },
+    "node_modules/brotli": {
+      "version": "1.3.3",
+      "resolved": "https://registry.npmjs.org/brotli/-/brotli-1.3.3.tgz",
+      "integrity": "sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==",
+      "dev": true,
+      "dependencies": {
+        "base64-js": "^1.1.2"
+      }
+    },
     "node_modules/caseless": {
       "version": "0.12.0",
       "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
@@ -462,6 +555,25 @@
       "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
       "dev": true
     },
+    "node_modules/coveralls-next": {
+      "version": "4.2.0",
+      "resolved": "https://registry.npmjs.org/coveralls-next/-/coveralls-next-4.2.0.tgz",
+      "integrity": "sha512-zg41a/4QDSASPtlV6gp+6owoU43U5CguxuPZR3nPZ26M5ZYdEK3MdUe7HwE+AnCZPkucudfhqqJZehCNkz2rYg==",
+      "dev": true,
+      "dependencies": {
+        "form-data": "4.0.0",
+        "js-yaml": "4.1.0",
+        "lcov-parse": "1.0.0",
+        "log-driver": "1.2.7",
+        "minimist": "1.2.7"
+      },
+      "bin": {
+        "coveralls": "bin/coveralls.js"
+      },
+      "engines": {
+        "node": ">=14"
+      }
+    },
     "node_modules/create-require": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
@@ -496,9 +608,9 @@
       }
     },
     "node_modules/dayjs": {
-      "version": "1.11.7",
-      "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.7.tgz",
-      "integrity": "sha512-+Yw9U6YO5TQohxLcIkrXBeY73WP3ejHWVvx8XCk3gxvQDCTEmS48ZrSZCKciI7Bhl/uCMyxYtE9UqRILmFphkQ==",
+      "version": "1.11.10",
+      "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz",
+      "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==",
       "dev": true
     },
     "node_modules/debounce-promise": {
@@ -587,9 +699,9 @@
       "dev": true
     },
     "node_modules/fast-glob": {
-      "version": "3.2.12",
-      "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz",
-      "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==",
+      "version": "3.3.2",
+      "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
+      "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==",
       "dev": true,
       "dependencies": {
         "@nodelib/fs.stat": "^2.0.2",
@@ -609,9 +721,9 @@
       "dev": true
     },
     "node_modules/fastq": {
-      "version": "1.15.0",
-      "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz",
-      "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==",
+      "version": "1.16.0",
+      "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.16.0.tgz",
+      "integrity": "sha512-ifCoaXsDrsdkWTtiNJX5uzHDsrck5TzfKKDcuFFTIrrc/BS076qgEIfoIy1VeZqViznfKiysPYTh/QeHtnIsYA==",
       "dev": true,
       "dependencies": {
         "reusify": "^1.0.4"
@@ -648,17 +760,17 @@
       }
     },
     "node_modules/form-data": {
-      "version": "2.3.3",
-      "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
-      "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
+      "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
       "dev": true,
       "dependencies": {
         "asynckit": "^0.4.0",
-        "combined-stream": "^1.0.6",
+        "combined-stream": "^1.0.8",
         "mime-types": "^2.1.12"
       },
       "engines": {
-        "node": ">= 0.12"
+        "node": ">= 6"
       }
     },
     "node_modules/fs-extra": {
@@ -758,18 +870,17 @@
       }
     },
     "node_modules/http-signature": {
-      "version": "1.2.0",
-      "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
-      "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==",
+      "version": "1.3.6",
+      "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.3.6.tgz",
+      "integrity": "sha512-3adrsD6zqo4GsTqtO7FyrejHNv+NgiIfAfv68+jVlFmSr9OGy7zrxONceFRLKvnnZA5jbxQBX1u9PpB6Wi32Gw==",
       "dev": true,
       "dependencies": {
         "assert-plus": "^1.0.0",
-        "jsprim": "^1.2.2",
-        "sshpk": "^1.7.0"
+        "jsprim": "^2.0.2",
+        "sshpk": "^1.14.1"
       },
       "engines": {
-        "node": ">=0.8",
-        "npm": ">=1.3.7"
+        "node": ">=0.10"
       }
     },
     "node_modules/immediate": {
@@ -853,6 +964,18 @@
       "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==",
       "dev": true
     },
+    "node_modules/js-yaml": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+      "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+      "dev": true,
+      "dependencies": {
+        "argparse": "^2.0.1"
+      },
+      "bin": {
+        "js-yaml": "bin/js-yaml.js"
+      }
+    },
     "node_modules/jsbn": {
       "version": "0.1.1",
       "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
@@ -893,18 +1016,18 @@
       }
     },
     "node_modules/jsprim": {
-      "version": "1.4.2",
-      "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz",
-      "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==",
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-2.0.2.tgz",
+      "integrity": "sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ==",
       "dev": true,
+      "engines": [
+        "node >=0.6.0"
+      ],
       "dependencies": {
         "assert-plus": "1.0.0",
         "extsprintf": "1.3.0",
         "json-schema": "0.4.0",
         "verror": "1.10.0"
-      },
-      "engines": {
-        "node": ">=0.6.0"
       }
     },
     "node_modules/jszip": {
@@ -919,6 +1042,15 @@
         "setimmediate": "^1.0.5"
       }
     },
+    "node_modules/lcov-parse": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/lcov-parse/-/lcov-parse-1.0.0.tgz",
+      "integrity": "sha512-aprLII/vPzuQvYZnDRU78Fns9I2Ag3gi4Ipga/hxnVMCZC8DnR2nI7XBqrPoywGfxqIx/DgarGvDJZAD3YBTgQ==",
+      "dev": true,
+      "bin": {
+        "lcov-parse": "bin/cli.js"
+      }
+    },
     "node_modules/lie": {
       "version": "3.3.0",
       "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
@@ -928,6 +1060,21 @@
         "immediate": "~3.0.5"
       }
     },
+    "node_modules/lodash": {
+      "version": "4.17.21",
+      "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+      "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
+      "dev": true
+    },
+    "node_modules/log-driver": {
+      "version": "1.2.7",
+      "resolved": "https://registry.npmjs.org/log-driver/-/log-driver-1.2.7.tgz",
+      "integrity": "sha512-U7KCmLdqsGHBLeWqYlFA0V0Sl6P08EE1ZrmA9cxjUE0WVqT9qnyVDPz1kzpFEP0jdJuFnasWIfSd7fsaNXkpbg==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.8.6"
+      }
+    },
     "node_modules/long": {
       "version": "3.2.0",
       "resolved": "https://registry.npmjs.org/long/-/long-3.2.0.tgz",
@@ -1007,10 +1154,19 @@
         "node": "*"
       }
     },
+    "node_modules/minimist": {
+      "version": "1.2.7",
+      "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz",
+      "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==",
+      "dev": true,
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
     "node_modules/moment": {
-      "version": "2.29.4",
-      "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
-      "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==",
+      "version": "2.30.1",
+      "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz",
+      "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==",
       "dev": true,
       "engines": {
         "node": "*"
@@ -1110,6 +1266,39 @@
         "url": "https://github.com/sponsors/jonschlinkert"
       }
     },
+    "node_modules/postman-request": {
+      "version": "2.88.1-postman.33",
+      "resolved": "https://registry.npmjs.org/postman-request/-/postman-request-2.88.1-postman.33.tgz",
+      "integrity": "sha512-uL9sCML4gPH6Z4hreDWbeinKU0p0Ke261nU7OvII95NU22HN6Dk7T/SaVPaj6T4TsQqGKIFw6/woLZnH7ugFNA==",
+      "dev": true,
+      "dependencies": {
+        "@postman/form-data": "~3.1.1",
+        "@postman/tough-cookie": "~4.1.3-postman.1",
+        "@postman/tunnel-agent": "^0.6.3",
+        "aws-sign2": "~0.7.0",
+        "aws4": "^1.12.0",
+        "brotli": "^1.3.3",
+        "caseless": "~0.12.0",
+        "combined-stream": "~1.0.6",
+        "extend": "~3.0.2",
+        "forever-agent": "~0.6.1",
+        "har-validator": "~5.1.3",
+        "http-signature": "~1.3.1",
+        "is-typedarray": "~1.0.0",
+        "isstream": "~0.1.2",
+        "json-stringify-safe": "~5.0.1",
+        "mime-types": "^2.1.35",
+        "oauth-sign": "~0.9.0",
+        "performance-now": "^2.1.0",
+        "qs": "~6.5.3",
+        "safe-buffer": "^5.1.2",
+        "stream-length": "^1.0.2",
+        "uuid": "^8.3.2"
+      },
+      "engines": {
+        "node": ">= 6"
+      }
+    },
     "node_modules/process-nextick-args": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
@@ -1123,9 +1312,9 @@
       "dev": true
     },
     "node_modules/punycode": {
-      "version": "2.3.0",
-      "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz",
-      "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==",
+      "version": "2.3.1",
+      "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+      "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
       "dev": true,
       "engines": {
         "node": ">=6"
@@ -1140,6 +1329,12 @@
         "node": ">=0.6"
       }
     },
+    "node_modules/querystringify": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
+      "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==",
+      "dev": true
+    },
     "node_modules/queue-microtask": {
       "version": "1.2.3",
       "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
@@ -1175,6 +1370,12 @@
         "util-deprecate": "~1.0.1"
       }
     },
+    "node_modules/readable-stream/node_modules/safe-buffer": {
+      "version": "5.1.2",
+      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+      "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+      "dev": true
+    },
     "node_modules/readdirp": {
       "version": "3.6.0",
       "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
@@ -1199,38 +1400,6 @@
       "integrity": "sha512-tlbJqcMHnPKI9zSrystikWKwHkBqu2a/Sgw01h3zFjvYrMxEDYHzzoMZnUrbIfpTFEsoRnnviOXNCzFiSc54Qw==",
       "dev": true
     },
-    "node_modules/request": {
-      "version": "2.88.2",
-      "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz",
-      "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==",
-      "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142",
-      "dev": true,
-      "dependencies": {
-        "aws-sign2": "~0.7.0",
-        "aws4": "^1.8.0",
-        "caseless": "~0.12.0",
-        "combined-stream": "~1.0.6",
-        "extend": "~3.0.2",
-        "forever-agent": "~0.6.1",
-        "form-data": "~2.3.2",
-        "har-validator": "~5.1.3",
-        "http-signature": "~1.2.0",
-        "is-typedarray": "~1.0.0",
-        "isstream": "~0.1.2",
-        "json-stringify-safe": "~5.0.1",
-        "mime-types": "~2.1.19",
-        "oauth-sign": "~0.9.0",
-        "performance-now": "^2.1.0",
-        "qs": "~6.5.2",
-        "safe-buffer": "^5.1.2",
-        "tough-cookie": "~2.5.0",
-        "tunnel-agent": "^0.6.0",
-        "uuid": "^3.3.2"
-      },
-      "engines": {
-        "node": ">= 6"
-      }
-    },
     "node_modules/require-directory": {
       "version": "2.1.1",
       "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
@@ -1246,6 +1415,12 @@
       "integrity": "sha512-AKGr4qvHiryxRb19m3PsLRGuKVAbJLUD7E6eOaHkfKhwc+vSgVOCY5xNvm9EkolBKTOf0GrQAZKLimOCz81Khg==",
       "dev": true
     },
+    "node_modules/requires-port": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
+      "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
+      "dev": true
+    },
     "node_modules/reusify": {
       "version": "1.0.4",
       "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
@@ -1257,9 +1432,9 @@
       }
     },
     "node_modules/roku-deploy": {
-      "version": "3.10.1",
-      "resolved": "https://registry.npmjs.org/roku-deploy/-/roku-deploy-3.10.1.tgz",
-      "integrity": "sha512-utPNny0a2m/N0AQT6zyVLXtrr81KR5QeJqPUbc59VBcqGM+WIi7rQ9hBLmp5dFqIo/7JVKQYFs+nFGebOA6F7w==",
+      "version": "3.11.2",
+      "resolved": "https://registry.npmjs.org/roku-deploy/-/roku-deploy-3.11.2.tgz",
+      "integrity": "sha512-3JDlnbTxv6Xk5GVolQoA3+d34MLZXXwZWMySprHwazZoWLP3LvulYHP92YvFOJAo/aI4IZp/TFA8kR82IrmHKA==",
       "dev": true,
       "dependencies": {
         "chalk": "^2.4.2",
@@ -1270,10 +1445,11 @@
         "is-glob": "^4.0.3",
         "jsonc-parser": "^2.3.0",
         "jszip": "^3.6.0",
+        "lodash": "^4.17.21",
         "micromatch": "^4.0.4",
         "moment": "^2.29.1",
         "parse-ms": "^2.1.0",
-        "request": "^2.88.0",
+        "postman-request": "^2.88.1-postman.32",
         "temp-dir": "^2.0.0",
         "xml2js": "^0.5.0"
       },
@@ -1319,10 +1495,24 @@
       }
     },
     "node_modules/safe-buffer": {
-      "version": "5.1.2",
-      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
-      "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
-      "dev": true
+      "version": "5.2.1",
+      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+      "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/feross"
+        },
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/feross"
+        },
+        {
+          "type": "consulting",
+          "url": "https://feross.org/support"
+        }
+      ]
     },
     "node_modules/safer-buffer": {
       "version": "2.1.2",
@@ -1331,9 +1521,9 @@
       "dev": true
     },
     "node_modules/sax": {
-      "version": "1.2.4",
-      "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
-      "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==",
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/sax/-/sax-1.3.0.tgz",
+      "integrity": "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==",
       "dev": true
     },
     "node_modules/serialize-error": {
@@ -1367,9 +1557,9 @@
       }
     },
     "node_modules/sshpk": {
-      "version": "1.17.0",
-      "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz",
-      "integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==",
+      "version": "1.18.0",
+      "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz",
+      "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==",
       "dev": true,
       "dependencies": {
         "asn1": "~0.2.3",
@@ -1391,6 +1581,15 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/stream-length": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/stream-length/-/stream-length-1.0.2.tgz",
+      "integrity": "sha512-aI+qKFiwoDV4rsXiS7WRoCt+v2RX1nUj17+KJC5r2gfh5xoSJIfP6Y3Do/HtvesFcTSWthIuJ3l1cvKQY/+nZg==",
+      "dev": true,
+      "dependencies": {
+        "bluebird": "^2.6.2"
+      }
+    },
     "node_modules/string_decoder": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
@@ -1400,6 +1599,12 @@
         "safe-buffer": "~5.1.0"
       }
     },
+    "node_modules/string_decoder/node_modules/safe-buffer": {
+      "version": "5.1.2",
+      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+      "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+      "dev": true
+    },
     "node_modules/string-width": {
       "version": "4.2.3",
       "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
@@ -1459,19 +1664,6 @@
         "node": ">=8.0"
       }
     },
-    "node_modules/tough-cookie": {
-      "version": "2.5.0",
-      "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz",
-      "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==",
-      "dev": true,
-      "dependencies": {
-        "psl": "^1.1.28",
-        "punycode": "^2.1.1"
-      },
-      "engines": {
-        "node": ">=0.8"
-      }
-    },
     "node_modules/ts-node": {
       "version": "10.9.1",
       "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz",
@@ -1515,18 +1707,6 @@
         }
       }
     },
-    "node_modules/tunnel-agent": {
-      "version": "0.6.0",
-      "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
-      "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==",
-      "dev": true,
-      "dependencies": {
-        "safe-buffer": "^5.0.1"
-      },
-      "engines": {
-        "node": "*"
-      }
-    },
     "node_modules/tweetnacl": {
       "version": "0.14.5",
       "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
@@ -1576,6 +1756,16 @@
         "punycode": "^2.1.0"
       }
     },
+    "node_modules/url-parse": {
+      "version": "1.5.10",
+      "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
+      "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
+      "dev": true,
+      "dependencies": {
+        "querystringify": "^2.1.1",
+        "requires-port": "^1.0.0"
+      }
+    },
     "node_modules/util-deprecate": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
@@ -1583,13 +1773,12 @@
       "dev": true
     },
     "node_modules/uuid": {
-      "version": "3.4.0",
-      "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
-      "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==",
-      "deprecated": "Please upgrade  to version 7 or higher.  Older versions may use Math.random() in certain circumstances, which is known to be problematic.  See https://v8.dev/blog/math-random for details.",
+      "version": "8.3.2",
+      "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
+      "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
       "dev": true,
       "bin": {
-        "uuid": "bin/uuid"
+        "uuid": "dist/bin/uuid"
       }
     },
     "node_modules/v8-compile-cache-lib": {
@@ -1843,6 +2032,46 @@
         "fastq": "^1.6.0"
       }
     },
+    "@postman/form-data": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/@postman/form-data/-/form-data-3.1.1.tgz",
+      "integrity": "sha512-vjh8Q2a8S6UCm/KKs31XFJqEEgmbjBmpPNVV2eVav6905wyFAwaUOBGA1NPBI4ERH9MMZc6w0umFgM6WbEPMdg==",
+      "dev": true,
+      "requires": {
+        "asynckit": "^0.4.0",
+        "combined-stream": "^1.0.8",
+        "mime-types": "^2.1.12"
+      }
+    },
+    "@postman/tough-cookie": {
+      "version": "4.1.3-postman.1",
+      "resolved": "https://registry.npmjs.org/@postman/tough-cookie/-/tough-cookie-4.1.3-postman.1.tgz",
+      "integrity": "sha512-txpgUqZOnWYnUHZpHjkfb0IwVH4qJmyq77pPnJLlfhMtdCLMFTEeQHlzQiK906aaNCe4NEB5fGJHo9uzGbFMeA==",
+      "dev": true,
+      "requires": {
+        "psl": "^1.1.33",
+        "punycode": "^2.1.1",
+        "universalify": "^0.2.0",
+        "url-parse": "^1.5.3"
+      },
+      "dependencies": {
+        "universalify": {
+          "version": "0.2.0",
+          "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
+          "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==",
+          "dev": true
+        }
+      }
+    },
+    "@postman/tunnel-agent": {
+      "version": "0.6.3",
+      "resolved": "https://registry.npmjs.org/@postman/tunnel-agent/-/tunnel-agent-0.6.3.tgz",
+      "integrity": "sha512-k57fzmAZ2PJGxfOA4SGR05ejorHbVAa/84Hxh/2nAztjNXc4ZjOm9NUIk6/Z6LCrBvJZqjRZbN8e/nROVUPVdg==",
+      "dev": true,
+      "requires": {
+        "safe-buffer": "^5.0.1"
+      }
+    },
     "@rokucommunity/bslib": {
       "version": "0.1.1",
       "resolved": "https://registry.npmjs.org/@rokucommunity/bslib/-/bslib-0.1.1.tgz",
@@ -1948,6 +2177,12 @@
       "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
       "dev": true
     },
+    "argparse": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+      "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+      "dev": true
+    },
     "array-flat-polyfill": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/array-flat-polyfill/-/array-flat-polyfill-1.0.1.tgz",
@@ -1993,6 +2228,12 @@
       "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
       "dev": true
     },
+    "base64-js": {
+      "version": "1.5.1",
+      "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
+      "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
+      "dev": true
+    },
     "bcrypt-pbkdf": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
@@ -2008,6 +2249,12 @@
       "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
       "dev": true
     },
+    "bluebird": {
+      "version": "2.11.0",
+      "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.11.0.tgz",
+      "integrity": "sha512-UfFSr22dmHPQqPP9XWHRhq+gWnHCYguQGkXQlbyPtW5qTnhFWA8/iXg765tH0cAjy7l/zPJ1aBTO0g5XgA7kvQ==",
+      "dev": true
+    },
     "brace-expansion": {
       "version": "1.1.11",
       "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@@ -2028,9 +2275,9 @@
       }
     },
     "brighterscript": {
-      "version": "0.64.3",
-      "resolved": "https://registry.npmjs.org/brighterscript/-/brighterscript-0.64.3.tgz",
-      "integrity": "sha512-W0k0ID3yCbFGba80d8RlggldvRs6u5Y9F6F+AQ4fWDdJcWeHJ3vy5/pabzDbYD9sen3trVxiZ2yXdA7XK35V5g==",
+      "version": "0.65.16",
+      "resolved": "https://registry.npmjs.org/brighterscript/-/brighterscript-0.65.16.tgz",
+      "integrity": "sha512-2RJMF8itkrPXtZ92JkJf3emvzcCl5ETXGVyPsdO/hMKbUGinEGMRFG07FR0bzjRotbALzsozGE/Hjzw412Sjww==",
       "dev": true,
       "requires": {
         "@rokucommunity/bslib": "^0.1.1",
@@ -2040,6 +2287,7 @@
         "chevrotain": "^7.0.1",
         "chokidar": "^3.5.1",
         "clear": "^0.1.0",
+        "coveralls-next": "^4.2.0",
         "cross-platform-clear-console": "^2.3.0",
         "debounce-promise": "^3.1.0",
         "eventemitter3": "^4.0.0",
@@ -2055,7 +2303,7 @@
         "parse-ms": "^2.1.0",
         "readline": "^1.3.0",
         "require-relative": "^0.8.7",
-        "roku-deploy": "^3.10.1",
+        "roku-deploy": "^3.11.2",
         "serialize-error": "^7.0.1",
         "source-map": "^0.7.4",
         "vscode-languageserver": "7.0.0",
@@ -2066,6 +2314,15 @@
         "yargs": "^16.2.0"
       }
     },
+    "brotli": {
+      "version": "1.3.3",
+      "resolved": "https://registry.npmjs.org/brotli/-/brotli-1.3.3.tgz",
+      "integrity": "sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==",
+      "dev": true,
+      "requires": {
+        "base64-js": "^1.1.2"
+      }
+    },
     "caseless": {
       "version": "0.12.0",
       "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
@@ -2161,6 +2418,19 @@
       "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
       "dev": true
     },
+    "coveralls-next": {
+      "version": "4.2.0",
+      "resolved": "https://registry.npmjs.org/coveralls-next/-/coveralls-next-4.2.0.tgz",
+      "integrity": "sha512-zg41a/4QDSASPtlV6gp+6owoU43U5CguxuPZR3nPZ26M5ZYdEK3MdUe7HwE+AnCZPkucudfhqqJZehCNkz2rYg==",
+      "dev": true,
+      "requires": {
+        "form-data": "4.0.0",
+        "js-yaml": "4.1.0",
+        "lcov-parse": "1.0.0",
+        "log-driver": "1.2.7",
+        "minimist": "1.2.7"
+      }
+    },
     "create-require": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
@@ -2189,9 +2459,9 @@
       "dev": true
     },
     "dayjs": {
-      "version": "1.11.7",
-      "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.7.tgz",
-      "integrity": "sha512-+Yw9U6YO5TQohxLcIkrXBeY73WP3ejHWVvx8XCk3gxvQDCTEmS48ZrSZCKciI7Bhl/uCMyxYtE9UqRILmFphkQ==",
+      "version": "1.11.10",
+      "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz",
+      "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==",
       "dev": true
     },
     "debounce-promise": {
@@ -2265,9 +2535,9 @@
       "dev": true
     },
     "fast-glob": {
-      "version": "3.2.12",
-      "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz",
-      "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==",
+      "version": "3.3.2",
+      "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
+      "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==",
       "dev": true,
       "requires": {
         "@nodelib/fs.stat": "^2.0.2",
@@ -2284,9 +2554,9 @@
       "dev": true
     },
     "fastq": {
-      "version": "1.15.0",
-      "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz",
-      "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==",
+      "version": "1.16.0",
+      "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.16.0.tgz",
+      "integrity": "sha512-ifCoaXsDrsdkWTtiNJX5uzHDsrck5TzfKKDcuFFTIrrc/BS076qgEIfoIy1VeZqViznfKiysPYTh/QeHtnIsYA==",
       "dev": true,
       "requires": {
         "reusify": "^1.0.4"
@@ -2314,13 +2584,13 @@
       "dev": true
     },
     "form-data": {
-      "version": "2.3.3",
-      "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
-      "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
+      "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
       "dev": true,
       "requires": {
         "asynckit": "^0.4.0",
-        "combined-stream": "^1.0.6",
+        "combined-stream": "^1.0.8",
         "mime-types": "^2.1.12"
       }
     },
@@ -2395,14 +2665,14 @@
       "dev": true
     },
     "http-signature": {
-      "version": "1.2.0",
-      "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
-      "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==",
+      "version": "1.3.6",
+      "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.3.6.tgz",
+      "integrity": "sha512-3adrsD6zqo4GsTqtO7FyrejHNv+NgiIfAfv68+jVlFmSr9OGy7zrxONceFRLKvnnZA5jbxQBX1u9PpB6Wi32Gw==",
       "dev": true,
       "requires": {
         "assert-plus": "^1.0.0",
-        "jsprim": "^1.2.2",
-        "sshpk": "^1.7.0"
+        "jsprim": "^2.0.2",
+        "sshpk": "^1.14.1"
       }
     },
     "immediate": {
@@ -2471,6 +2741,15 @@
       "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==",
       "dev": true
     },
+    "js-yaml": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+      "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+      "dev": true,
+      "requires": {
+        "argparse": "^2.0.1"
+      }
+    },
     "jsbn": {
       "version": "0.1.1",
       "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
@@ -2511,9 +2790,9 @@
       }
     },
     "jsprim": {
-      "version": "1.4.2",
-      "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz",
-      "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==",
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-2.0.2.tgz",
+      "integrity": "sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ==",
       "dev": true,
       "requires": {
         "assert-plus": "1.0.0",
@@ -2534,6 +2813,12 @@
         "setimmediate": "^1.0.5"
       }
     },
+    "lcov-parse": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/lcov-parse/-/lcov-parse-1.0.0.tgz",
+      "integrity": "sha512-aprLII/vPzuQvYZnDRU78Fns9I2Ag3gi4Ipga/hxnVMCZC8DnR2nI7XBqrPoywGfxqIx/DgarGvDJZAD3YBTgQ==",
+      "dev": true
+    },
     "lie": {
       "version": "3.3.0",
       "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
@@ -2543,6 +2828,18 @@
         "immediate": "~3.0.5"
       }
     },
+    "lodash": {
+      "version": "4.17.21",
+      "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+      "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
+      "dev": true
+    },
+    "log-driver": {
+      "version": "1.2.7",
+      "resolved": "https://registry.npmjs.org/log-driver/-/log-driver-1.2.7.tgz",
+      "integrity": "sha512-U7KCmLdqsGHBLeWqYlFA0V0Sl6P08EE1ZrmA9cxjUE0WVqT9qnyVDPz1kzpFEP0jdJuFnasWIfSd7fsaNXkpbg==",
+      "dev": true
+    },
     "long": {
       "version": "3.2.0",
       "resolved": "https://registry.npmjs.org/long/-/long-3.2.0.tgz",
@@ -2601,10 +2898,16 @@
         "brace-expansion": "^1.1.7"
       }
     },
+    "minimist": {
+      "version": "1.2.7",
+      "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz",
+      "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==",
+      "dev": true
+    },
     "moment": {
-      "version": "2.29.4",
-      "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
-      "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==",
+      "version": "2.30.1",
+      "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz",
+      "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==",
       "dev": true
     },
     "normalize-path": {
@@ -2674,6 +2977,36 @@
       "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
       "dev": true
     },
+    "postman-request": {
+      "version": "2.88.1-postman.33",
+      "resolved": "https://registry.npmjs.org/postman-request/-/postman-request-2.88.1-postman.33.tgz",
+      "integrity": "sha512-uL9sCML4gPH6Z4hreDWbeinKU0p0Ke261nU7OvII95NU22HN6Dk7T/SaVPaj6T4TsQqGKIFw6/woLZnH7ugFNA==",
+      "dev": true,
+      "requires": {
+        "@postman/form-data": "~3.1.1",
+        "@postman/tough-cookie": "~4.1.3-postman.1",
+        "@postman/tunnel-agent": "^0.6.3",
+        "aws-sign2": "~0.7.0",
+        "aws4": "^1.12.0",
+        "brotli": "^1.3.3",
+        "caseless": "~0.12.0",
+        "combined-stream": "~1.0.6",
+        "extend": "~3.0.2",
+        "forever-agent": "~0.6.1",
+        "har-validator": "~5.1.3",
+        "http-signature": "~1.3.1",
+        "is-typedarray": "~1.0.0",
+        "isstream": "~0.1.2",
+        "json-stringify-safe": "~5.0.1",
+        "mime-types": "^2.1.35",
+        "oauth-sign": "~0.9.0",
+        "performance-now": "^2.1.0",
+        "qs": "~6.5.3",
+        "safe-buffer": "^5.1.2",
+        "stream-length": "^1.0.2",
+        "uuid": "^8.3.2"
+      }
+    },
     "process-nextick-args": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
@@ -2687,9 +3020,9 @@
       "dev": true
     },
     "punycode": {
-      "version": "2.3.0",
-      "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz",
-      "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==",
+      "version": "2.3.1",
+      "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+      "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
       "dev": true
     },
     "qs": {
@@ -2698,6 +3031,12 @@
       "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==",
       "dev": true
     },
+    "querystringify": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
+      "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==",
+      "dev": true
+    },
     "queue-microtask": {
       "version": "1.2.3",
       "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
@@ -2717,6 +3056,14 @@
         "safe-buffer": "~5.1.1",
         "string_decoder": "~1.1.1",
         "util-deprecate": "~1.0.1"
+      },
+      "dependencies": {
+        "safe-buffer": {
+          "version": "5.1.2",
+          "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+          "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+          "dev": true
+        }
       }
     },
     "readdirp": {
@@ -2740,34 +3087,6 @@
       "integrity": "sha512-tlbJqcMHnPKI9zSrystikWKwHkBqu2a/Sgw01h3zFjvYrMxEDYHzzoMZnUrbIfpTFEsoRnnviOXNCzFiSc54Qw==",
       "dev": true
     },
-    "request": {
-      "version": "2.88.2",
-      "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz",
-      "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==",
-      "dev": true,
-      "requires": {
-        "aws-sign2": "~0.7.0",
-        "aws4": "^1.8.0",
-        "caseless": "~0.12.0",
-        "combined-stream": "~1.0.6",
-        "extend": "~3.0.2",
-        "forever-agent": "~0.6.1",
-        "form-data": "~2.3.2",
-        "har-validator": "~5.1.3",
-        "http-signature": "~1.2.0",
-        "is-typedarray": "~1.0.0",
-        "isstream": "~0.1.2",
-        "json-stringify-safe": "~5.0.1",
-        "mime-types": "~2.1.19",
-        "oauth-sign": "~0.9.0",
-        "performance-now": "^2.1.0",
-        "qs": "~6.5.2",
-        "safe-buffer": "^5.1.2",
-        "tough-cookie": "~2.5.0",
-        "tunnel-agent": "^0.6.0",
-        "uuid": "^3.3.2"
-      }
-    },
     "require-directory": {
       "version": "2.1.1",
       "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
@@ -2780,6 +3099,12 @@
       "integrity": "sha512-AKGr4qvHiryxRb19m3PsLRGuKVAbJLUD7E6eOaHkfKhwc+vSgVOCY5xNvm9EkolBKTOf0GrQAZKLimOCz81Khg==",
       "dev": true
     },
+    "requires-port": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
+      "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
+      "dev": true
+    },
     "reusify": {
       "version": "1.0.4",
       "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
@@ -2787,9 +3112,9 @@
       "dev": true
     },
     "roku-deploy": {
-      "version": "3.10.1",
-      "resolved": "https://registry.npmjs.org/roku-deploy/-/roku-deploy-3.10.1.tgz",
-      "integrity": "sha512-utPNny0a2m/N0AQT6zyVLXtrr81KR5QeJqPUbc59VBcqGM+WIi7rQ9hBLmp5dFqIo/7JVKQYFs+nFGebOA6F7w==",
+      "version": "3.11.2",
+      "resolved": "https://registry.npmjs.org/roku-deploy/-/roku-deploy-3.11.2.tgz",
+      "integrity": "sha512-3JDlnbTxv6Xk5GVolQoA3+d34MLZXXwZWMySprHwazZoWLP3LvulYHP92YvFOJAo/aI4IZp/TFA8kR82IrmHKA==",
       "dev": true,
       "requires": {
         "chalk": "^2.4.2",
@@ -2800,10 +3125,11 @@
         "is-glob": "^4.0.3",
         "jsonc-parser": "^2.3.0",
         "jszip": "^3.6.0",
+        "lodash": "^4.17.21",
         "micromatch": "^4.0.4",
         "moment": "^2.29.1",
         "parse-ms": "^2.1.0",
-        "request": "^2.88.0",
+        "postman-request": "^2.88.1-postman.32",
         "temp-dir": "^2.0.0",
         "xml2js": "^0.5.0"
       },
@@ -2831,9 +3157,9 @@
       }
     },
     "safe-buffer": {
-      "version": "5.1.2",
-      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
-      "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+      "version": "5.2.1",
+      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+      "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
       "dev": true
     },
     "safer-buffer": {
@@ -2843,9 +3169,9 @@
       "dev": true
     },
     "sax": {
-      "version": "1.2.4",
-      "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
-      "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==",
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/sax/-/sax-1.3.0.tgz",
+      "integrity": "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==",
       "dev": true
     },
     "serialize-error": {
@@ -2870,9 +3196,9 @@
       "dev": true
     },
     "sshpk": {
-      "version": "1.17.0",
-      "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz",
-      "integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==",
+      "version": "1.18.0",
+      "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz",
+      "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==",
       "dev": true,
       "requires": {
         "asn1": "~0.2.3",
@@ -2886,6 +3212,15 @@
         "tweetnacl": "~0.14.0"
       }
     },
+    "stream-length": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/stream-length/-/stream-length-1.0.2.tgz",
+      "integrity": "sha512-aI+qKFiwoDV4rsXiS7WRoCt+v2RX1nUj17+KJC5r2gfh5xoSJIfP6Y3Do/HtvesFcTSWthIuJ3l1cvKQY/+nZg==",
+      "dev": true,
+      "requires": {
+        "bluebird": "^2.6.2"
+      }
+    },
     "string_decoder": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
@@ -2893,6 +3228,14 @@
       "dev": true,
       "requires": {
         "safe-buffer": "~5.1.0"
+      },
+      "dependencies": {
+        "safe-buffer": {
+          "version": "5.1.2",
+          "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+          "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+          "dev": true
+        }
       }
     },
     "string-width": {
@@ -2939,16 +3282,6 @@
         "is-number": "^7.0.0"
       }
     },
-    "tough-cookie": {
-      "version": "2.5.0",
-      "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz",
-      "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==",
-      "dev": true,
-      "requires": {
-        "psl": "^1.1.28",
-        "punycode": "^2.1.1"
-      }
-    },
     "ts-node": {
       "version": "10.9.1",
       "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz",
@@ -2970,15 +3303,6 @@
         "yn": "3.1.1"
       }
     },
-    "tunnel-agent": {
-      "version": "0.6.0",
-      "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
-      "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==",
-      "dev": true,
-      "requires": {
-        "safe-buffer": "^5.0.1"
-      }
-    },
     "tweetnacl": {
       "version": "0.14.5",
       "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
@@ -3012,6 +3336,16 @@
         "punycode": "^2.1.0"
       }
     },
+    "url-parse": {
+      "version": "1.5.10",
+      "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
+      "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
+      "dev": true,
+      "requires": {
+        "querystringify": "^2.1.1",
+        "requires-port": "^1.0.0"
+      }
+    },
     "util-deprecate": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
@@ -3019,9 +3353,9 @@
       "dev": true
     },
     "uuid": {
-      "version": "3.4.0",
-      "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
-      "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==",
+      "version": "8.3.2",
+      "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
+      "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
       "dev": true
     },
     "v8-compile-cache-lib": {
diff --git a/tests/package.json b/tests/package.json
index fff341f3..2c9b4550 100644
--- a/tests/package.json
+++ b/tests/package.json
@@ -7,7 +7,7 @@
     "watch": "npx bsc --project bsconfig.json --watch"
   },
   "devDependencies": {
-    "brighterscript": "^0.64.0",
+    "brighterscript": "^0.65.15",
     "ts-node": "^10.7.0",
     "typescript": "^4.6.4"
   },
@@ -27,4 +27,4 @@
     "url": "https://github.com/georgejecook/rooibos/issues"
   },
   "homepage": "https://github.com/georgejecook/rooibos#readme"
-}
\ No newline at end of file
+}

From db7104879190447a7af9e91edd77dcd2d983b03f Mon Sep 17 00:00:00 2001
From: Christopher Dwyer-Perkins <chris@inverted-solutions.com>
Date: Tue, 16 Jan 2024 13:14:40 -0400
Subject: [PATCH 16/26] added tests for global stubcall on device

---
 tests/src/source/NewExpectSyntax.spec.bs | 195 +++++++++++++++++++++--
 1 file changed, 181 insertions(+), 14 deletions(-)

diff --git a/tests/src/source/NewExpectSyntax.spec.bs b/tests/src/source/NewExpectSyntax.spec.bs
index 7cf0e06a..a565db99 100644
--- a/tests/src/source/NewExpectSyntax.spec.bs
+++ b/tests/src/source/NewExpectSyntax.spec.bs
@@ -165,42 +165,209 @@ namespace tests
     end function
 
     '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-    @describe("stubCall local and namespace functions with new runtime functions")
+    @describe("stubCall global functions with new runtime functions")
     '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 
-    @it("supports function stubbing with a new function at runtime")
+    @it("stubs global with inline anon with return value")
     function _()
       m.wasCalled = false
-      m.stubCall(locationFunctionWithReturn, function()
+      m.stubCall(globalFunctionWithReturn, function()
         m.wasCalled = true
         return true
       end function)
-      m.assertTrue(locationFunctionWithReturn())
+
+      m.assertTrue(globalFunctionWithReturn())
       m.assertTrue(m.wasCalled)
+      m.assertRunningTestIsPassed()
+    end function
+
+    @it("stubs global with anon from variable with return value")
+    function _()
       m.wasCalled = false
+      stub = function()
+        m.wasCalled = true
+        return true
+      end function
+      m.stubCall(globalFunctionWithReturn, stub)
 
-      m.stubCall(locationFunctionWithoutReturn, sub()
+      m.assertTrue(globalFunctionWithReturn())
+      m.assertTrue(m.wasCalled)
+      m.assertRunningTestIsPassed()
+    end function
+
+    @it("stubs global with inline anon without return value")
+    function _()
+      m.wasCalled = false
+      m.stubCall(globalFunctionWithoutReturn, sub()
         m.wasCalled = true
       end sub)
-      m.assertInvalid(locationFunctionWithoutReturn())
+
+      m.assertInvalid(globalFunctionWithoutReturn())
       m.assertTrue(m.wasCalled)
+      m.assertRunningTestIsPassed()
+    end function
+
+    @it("stubs global with anon from variable without return value")
+    function _()
       m.wasCalled = false
+      m.stubCall(globalFunctionWithoutReturn, sub()
+        m.wasCalled = true
+      end sub)
+
+      m.assertInvalid(globalFunctionWithoutReturn())
+      m.assertTrue(m.wasCalled)
+      m.assertRunningTestIsPassed()
+    end function
 
+    '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+    @describe("stubCall namespace functions with new runtime functions")
+    '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+    @it("stubs namespace with inline anon with return value")
+    function _()
       m.wasCalled = false
-      m.stubCall(textNamespace.functionWithReturn, function()
+      m.stubCall(testNamespace.functionWithReturn, function()
         m.wasCalled = true
         return true
       end function)
-      m.assertTrue(textNamespace.functionWithReturn())
+      m.assertTrue(testNamespace.functionWithReturn())
+      m.assertTrue(m.wasCalled)
+      m.assertRunningTestIsPassed()
+    end function
+
+    @it("stubs namespace with anon from variable with return value")
+    function _()
+      m.wasCalled = false
+      stub = function()
+        m.wasCalled = true
+        return true
+      end function
+      m.stubCall(testNamespace.functionWithReturn, stub)
+
+      m.assertTrue(testNamespace.functionWithReturn())
+      m.assertTrue(m.wasCalled)
+      m.assertRunningTestIsPassed()
+    end function
+
+    @it("stubs namespace with inline anon without return value")
+    function _()
+      m.wasCalled = false
+      m.stubCall(testNamespace.functionWithoutReturn, sub()
+        m.wasCalled = true
+      end sub)
+      m.assertInvalid(testNamespace.functionWithoutReturn())
+      m.assertTrue(m.wasCalled)
+      m.assertRunningTestIsPassed()
+    end function
+
+    @it("stubs namespace with anon from variable without return value")
+    function _()
+      m.wasCalled = false
+      m.stubCall(testNamespace.functionWithoutReturn, sub()
+        m.wasCalled = true
+      end sub)
+      m.assertInvalid(testNamespace.functionWithoutReturn())
+      m.assertTrue(m.wasCalled)
+      m.assertRunningTestIsPassed()
+    end function
+
+    '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+    @describe("stubCall global or namespace functions multiple times in a test")
+    '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+    @it("stubs namespace multiple times in one test")
+    function _()
+      m.wasCalled = false
+      m.stubCall(testNamespace.functionWithoutReturn, sub()
+        m.wasCalled = true
+      end sub)
+
+      m.assertInvalid(testNamespace.functionWithoutReturn())
       m.assertTrue(m.wasCalled)
+
+      m.stubCall(testNamespace.functionWithoutReturn, sub()
+        m.wasCalled = 1
+      end sub)
+      m.assertInvalid(testNamespace.functionWithoutReturn())
+      m.assertEqual(m.wasCalled, 1)
+
+      m.assertRunningTestIsPassed()
+    end function
+
+    @it("stubs global multiple times in one test")
+    function _()
       m.wasCalled = false
+      m.stubCall(testNamespace.functionWithoutReturn, sub()
+        m.wasCalled = true
+      end sub)
+
+      m.assertInvalid(testNamespace.functionWithoutReturn())
+      m.assertTrue(m.wasCalled)
+
+      m.stubCall(testNamespace.functionWithoutReturn, sub()
+        m.wasCalled = 1
+      end sub)
+      m.assertInvalid(testNamespace.functionWithoutReturn())
+      m.assertEqual(m.wasCalled, 1)
+
+      m.assertRunningTestIsPassed()
+    end function
 
-      m.stubCall(textNamespace.functionWithoutReturn, sub()
+    '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+    @describe("stubCall global can be cleaned")
+    '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+    @it("stubs namespace and then cleans it")
+    function _()
+      m.wasCalled = false
+      m.stubCall(testNamespace.functionWithoutReturn, sub()
         m.wasCalled = true
       end sub)
-      m.assertInvalid(textNamespace.functionWithoutReturn())
+
+      m.assertInvalid(testNamespace.functionWithoutReturn())
+      m.assertTrue(m.wasCalled)
+
+      m.cleanMocks()
+
+      m.assertInvalid(testNamespace.functionWithoutReturn())
+      m.assertFalse(m.wasCalled)
+
+      m.assertRunningTestIsPassed()
+    end function
+
+    @it("stubs global and then cleans it")
+    function _()
+      m.wasCalled = false
+      m.stubCall(globalFunctionWithoutReturn, sub()
+        m.wasCalled = true
+      end sub)
+
+      m.assertInvalid(globalFunctionWithoutReturn())
       m.assertTrue(m.wasCalled)
+
+      m.cleanMocks()
+
+      m.assertInvalid(globalFunctionWithoutReturn())
+      m.assertFalse(m.wasCalled)
+
+      m.assertRunningTestIsPassed()
+    end function
+
+    @it("stubs global multiple times in one test")
+    function _()
       m.wasCalled = false
+      m.stubCall(globalFunctionWithoutReturn, sub()
+        m.wasCalled = true
+      end sub)
+
+      m.assertInvalid(testNamespace.functionWithoutReturn())
+      m.assertTrue(m.wasCalled)
+
+      m.stubCall(testNamespace.functionWithoutReturn, sub()
+        m.wasCalled = 1
+      end sub)
+      m.assertInvalid(testNamespace.functionWithoutReturn())
+      m.assertEqual(m.wasCalled, 1)
 
       m.assertRunningTestIsPassed()
     end function
@@ -434,22 +601,22 @@ namespace tests
 
 end namespace
 
-function locationFunctionWithReturn() as dynamic
+function globalFunctionWithReturn() as dynamic
   m.wasCalled = false
   return false
 end function
 
-sub locationFunctionWithoutReturn()
+sub globalFunctionWithoutReturn()
   m.wasCalled = false
 end sub
 
-namespace textNamespace
+namespace testNamespace
   function functionWithReturn() as dynamic
     m.wasCalled = false
     return false
   end function
 
-  sub FunctionWithoutReturn()
+  sub functionWithoutReturn()
     m.wasCalled = false
   end sub
 end namespace
\ No newline at end of file

From 4c3d753ac31aec95b46aa19c937f30d426b9ba51 Mon Sep 17 00:00:00 2001
From: Christopher Dwyer-Perkins <chris@inverted-solutions.com>
Date: Tue, 16 Jan 2024 13:27:22 -0400
Subject: [PATCH 17/26] Fixed some device tests

---
 tests/src/source/Main.bs                 | 22 +++++-
 tests/src/source/NewExpectSyntax.spec.bs | 95 +++++++-----------------
 2 files changed, 49 insertions(+), 68 deletions(-)

diff --git a/tests/src/source/Main.bs b/tests/src/source/Main.bs
index f669bacc..627ac475 100644
--- a/tests/src/source/Main.bs
+++ b/tests/src/source/Main.bs
@@ -3,4 +3,24 @@ function Main(args)
 
   ? "here is my code"
   ? "hello"
-end function
\ No newline at end of file
+end function
+
+function globalFunctionWithReturn() as dynamic
+  m.wasCalled = false
+  return false
+end function
+
+sub globalFunctionWithoutReturn()
+  m.wasCalled = false
+end sub
+
+namespace testNamespace
+  function functionWithReturn() as dynamic
+    m.wasCalled = false
+    return false
+  end function
+
+  sub functionWithoutReturn()
+    m.wasCalled = false
+  end sub
+end namespace
\ No newline at end of file
diff --git a/tests/src/source/NewExpectSyntax.spec.bs b/tests/src/source/NewExpectSyntax.spec.bs
index a565db99..1dc3aca4 100644
--- a/tests/src/source/NewExpectSyntax.spec.bs
+++ b/tests/src/source/NewExpectSyntax.spec.bs
@@ -170,20 +170,20 @@ namespace tests
 
     @it("stubs global with inline anon with return value")
     function _()
-      m.wasCalled = false
+      getGlobalAA().wasCalled = false
       m.stubCall(globalFunctionWithReturn, function()
         m.wasCalled = true
         return true
       end function)
 
       m.assertTrue(globalFunctionWithReturn())
-      m.assertTrue(m.wasCalled)
+      m.assertTrue(getGlobalAA().wasCalled)
       m.assertRunningTestIsPassed()
     end function
 
     @it("stubs global with anon from variable with return value")
     function _()
-      m.wasCalled = false
+      getGlobalAA().wasCalled = false
       stub = function()
         m.wasCalled = true
         return true
@@ -191,31 +191,31 @@ namespace tests
       m.stubCall(globalFunctionWithReturn, stub)
 
       m.assertTrue(globalFunctionWithReturn())
-      m.assertTrue(m.wasCalled)
+      m.assertTrue(getGlobalAA().wasCalled)
       m.assertRunningTestIsPassed()
     end function
 
     @it("stubs global with inline anon without return value")
     function _()
-      m.wasCalled = false
+      getGlobalAA().wasCalled = false
       m.stubCall(globalFunctionWithoutReturn, sub()
         m.wasCalled = true
       end sub)
 
       m.assertInvalid(globalFunctionWithoutReturn())
-      m.assertTrue(m.wasCalled)
+      m.assertTrue(getGlobalAA().wasCalled)
       m.assertRunningTestIsPassed()
     end function
 
     @it("stubs global with anon from variable without return value")
     function _()
-      m.wasCalled = false
+      getGlobalAA().wasCalled = false
       m.stubCall(globalFunctionWithoutReturn, sub()
         m.wasCalled = true
       end sub)
 
       m.assertInvalid(globalFunctionWithoutReturn())
-      m.assertTrue(m.wasCalled)
+      m.assertTrue(getGlobalAA().wasCalled)
       m.assertRunningTestIsPassed()
     end function
 
@@ -225,19 +225,19 @@ namespace tests
 
     @it("stubs namespace with inline anon with return value")
     function _()
-      m.wasCalled = false
+      getGlobalAA().wasCalled = false
       m.stubCall(testNamespace.functionWithReturn, function()
         m.wasCalled = true
         return true
       end function)
       m.assertTrue(testNamespace.functionWithReturn())
-      m.assertTrue(m.wasCalled)
+      m.assertTrue(getGlobalAA().wasCalled)
       m.assertRunningTestIsPassed()
     end function
 
     @it("stubs namespace with anon from variable with return value")
     function _()
-      m.wasCalled = false
+      getGlobalAA().wasCalled = false
       stub = function()
         m.wasCalled = true
         return true
@@ -245,29 +245,29 @@ namespace tests
       m.stubCall(testNamespace.functionWithReturn, stub)
 
       m.assertTrue(testNamespace.functionWithReturn())
-      m.assertTrue(m.wasCalled)
+      m.assertTrue(getGlobalAA().wasCalled)
       m.assertRunningTestIsPassed()
     end function
 
     @it("stubs namespace with inline anon without return value")
     function _()
-      m.wasCalled = false
+      getGlobalAA().wasCalled = false
       m.stubCall(testNamespace.functionWithoutReturn, sub()
         m.wasCalled = true
       end sub)
       m.assertInvalid(testNamespace.functionWithoutReturn())
-      m.assertTrue(m.wasCalled)
+      m.assertTrue(getGlobalAA().wasCalled)
       m.assertRunningTestIsPassed()
     end function
 
     @it("stubs namespace with anon from variable without return value")
     function _()
-      m.wasCalled = false
+      getGlobalAA().wasCalled = false
       m.stubCall(testNamespace.functionWithoutReturn, sub()
         m.wasCalled = true
       end sub)
       m.assertInvalid(testNamespace.functionWithoutReturn())
-      m.assertTrue(m.wasCalled)
+      m.assertTrue(getGlobalAA().wasCalled)
       m.assertRunningTestIsPassed()
     end function
 
@@ -277,38 +277,38 @@ namespace tests
 
     @it("stubs namespace multiple times in one test")
     function _()
-      m.wasCalled = false
+      getGlobalAA().wasCalled = false
       m.stubCall(testNamespace.functionWithoutReturn, sub()
         m.wasCalled = true
       end sub)
 
       m.assertInvalid(testNamespace.functionWithoutReturn())
-      m.assertTrue(m.wasCalled)
+      m.assertTrue(getGlobalAA().wasCalled)
 
       m.stubCall(testNamespace.functionWithoutReturn, sub()
         m.wasCalled = 1
       end sub)
       m.assertInvalid(testNamespace.functionWithoutReturn())
-      m.assertEqual(m.wasCalled, 1)
+      m.assertEqual(getGlobalAA().wasCalled, 1)
 
       m.assertRunningTestIsPassed()
     end function
 
     @it("stubs global multiple times in one test")
     function _()
-      m.wasCalled = false
+      getGlobalAA().wasCalled = false
       m.stubCall(testNamespace.functionWithoutReturn, sub()
         m.wasCalled = true
       end sub)
 
       m.assertInvalid(testNamespace.functionWithoutReturn())
-      m.assertTrue(m.wasCalled)
+      m.assertTrue(getGlobalAA().wasCalled)
 
       m.stubCall(testNamespace.functionWithoutReturn, sub()
         m.wasCalled = 1
       end sub)
       m.assertInvalid(testNamespace.functionWithoutReturn())
-      m.assertEqual(m.wasCalled, 1)
+      m.assertEqual(getGlobalAA().wasCalled, 1)
 
       m.assertRunningTestIsPassed()
     end function
@@ -319,55 +319,36 @@ namespace tests
 
     @it("stubs namespace and then cleans it")
     function _()
-      m.wasCalled = false
+      getGlobalAA().wasCalled = false
       m.stubCall(testNamespace.functionWithoutReturn, sub()
         m.wasCalled = true
       end sub)
 
       m.assertInvalid(testNamespace.functionWithoutReturn())
-      m.assertTrue(m.wasCalled)
+      m.assertTrue(getGlobalAA().wasCalled)
 
       m.cleanMocks()
 
       m.assertInvalid(testNamespace.functionWithoutReturn())
-      m.assertFalse(m.wasCalled)
+      m.assertFalse(getGlobalAA().wasCalled)
 
       m.assertRunningTestIsPassed()
     end function
 
     @it("stubs global and then cleans it")
     function _()
-      m.wasCalled = false
+      getGlobalAA().wasCalled = false
       m.stubCall(globalFunctionWithoutReturn, sub()
         m.wasCalled = true
       end sub)
 
       m.assertInvalid(globalFunctionWithoutReturn())
-      m.assertTrue(m.wasCalled)
+      m.assertTrue(getGlobalAA().wasCalled)
 
       m.cleanMocks()
 
       m.assertInvalid(globalFunctionWithoutReturn())
-      m.assertFalse(m.wasCalled)
-
-      m.assertRunningTestIsPassed()
-    end function
-
-    @it("stubs global multiple times in one test")
-    function _()
-      m.wasCalled = false
-      m.stubCall(globalFunctionWithoutReturn, sub()
-        m.wasCalled = true
-      end sub)
-
-      m.assertInvalid(testNamespace.functionWithoutReturn())
-      m.assertTrue(m.wasCalled)
-
-      m.stubCall(testNamespace.functionWithoutReturn, sub()
-        m.wasCalled = 1
-      end sub)
-      m.assertInvalid(testNamespace.functionWithoutReturn())
-      m.assertEqual(m.wasCalled, 1)
+      m.assertFalse(getGlobalAA().wasCalled)
 
       m.assertRunningTestIsPassed()
     end function
@@ -600,23 +581,3 @@ namespace tests
   end class
 
 end namespace
-
-function globalFunctionWithReturn() as dynamic
-  m.wasCalled = false
-  return false
-end function
-
-sub globalFunctionWithoutReturn()
-  m.wasCalled = false
-end sub
-
-namespace testNamespace
-  function functionWithReturn() as dynamic
-    m.wasCalled = false
-    return false
-  end function
-
-  sub functionWithoutReturn()
-    m.wasCalled = false
-  end sub
-end namespace
\ No newline at end of file

From b4024ee003626a9e38c2b3571db90e4720227437 Mon Sep 17 00:00:00 2001
From: Christopher Dwyer-Perkins <chris@inverted-solutions.com>
Date: Tue, 16 Jan 2024 15:40:25 -0400
Subject: [PATCH 18/26] Fixed more on device tests

---
 bsc-plugin/src/lib/rooibos/TestGroup.ts | 48 ++++++++++++++++++++-----
 bsc-plugin/src/plugin.spec.ts           |  6 ++--
 2 files changed, 43 insertions(+), 11 deletions(-)

diff --git a/bsc-plugin/src/lib/rooibos/TestGroup.ts b/bsc-plugin/src/lib/rooibos/TestGroup.ts
index bff566f9..b9f081c1 100644
--- a/bsc-plugin/src/lib/rooibos/TestGroup.ts
+++ b/bsc-plugin/src/lib/rooibos/TestGroup.ts
@@ -1,5 +1,5 @@
-import type { AstEditor, CallExpression, DottedGetExpression, NamespaceContainer } from 'brighterscript';
-import { ArrayLiteralExpression, createInvalidLiteral, createStringLiteral, createToken, isDottedGetExpression, TokenKind, isFunctionExpression, Parser } from 'brighterscript';
+import type { AstEditor, CallExpression, DottedGetExpression, Expression, NamespaceContainer, Scope } from 'brighterscript';
+import { ArrayLiteralExpression, createInvalidLiteral, createStringLiteral, createToken, isDottedGetExpression, TokenKind, isFunctionExpression, Parser, ParseMode } from 'brighterscript';
 import * as brighterscript from 'brighterscript';
 import { BrsTranspileState } from 'brighterscript/dist/parser/BrsTranspileState';
 import { diagnosticErrorProcessingFile } from '../utils/Diagnostics';
@@ -39,7 +39,6 @@ export class TestGroup extends TestBlock {
         } else {
             this.hasAsyncTests = testCase.isAsync;
         }
-
     }
 
     public getTestCases(): TestCase[] {
@@ -51,6 +50,7 @@ export class TestGroup extends TestBlock {
         //if assertion
         //wrap with if is not fail
         //add line number as last param
+        let scope = this.file.program.getFirstScopeForFile(this.file);
         const transpileState = new BrsTranspileState(this.file);
         try {
             let func = this.testSuite.classStatement.methods.find((m) => m.name.text.toLowerCase() === testCase.funcName.toLowerCase());
@@ -63,16 +63,16 @@ export class TestGroup extends TestBlock {
                         let assertRegex = /(?:fail|assert(?:[a-z0-9]*)|expect(?:[a-z0-9]*)|stubCall)/i;
                         if (dge && assertRegex.test(dge.name.text)) {
                             if (dge.name.text === 'stubCall') {
-                                this.modifyModernRooibosExpectCallExpression(callExpression, editor, namespaceLookup);
+                                this.modifyModernRooibosExpectCallExpression(callExpression, editor, namespaceLookup, scope);
                                 return expressionStatement;
 
                             } else {
 
                                 if (dge.name.text === 'expectCalled' || dge.name.text === 'expectNotCalled') {
-                                    this.modifyModernRooibosExpectCallExpression(callExpression, editor, namespaceLookup);
+                                    this.modifyModernRooibosExpectCallExpression(callExpression, editor, namespaceLookup, scope);
                                 }
                                 if (dge.name.text === 'expectCalled' || dge.name.text === 'expectNotCalled') {
-                                    this.modifyModernRooibosExpectCallExpression(callExpression, editor, namespaceLookup);
+                                    this.modifyModernRooibosExpectCallExpression(callExpression, editor, namespaceLookup, scope);
                                 }
 
                                 if (!noEarlyExit) {
@@ -95,7 +95,7 @@ export class TestGroup extends TestBlock {
         }
     }
 
-    private modifyModernRooibosExpectCallExpression(callExpression: CallExpression, editor: AstEditor, namespaceLookup: Map<string, NamespaceContainer>) {
+    private modifyModernRooibosExpectCallExpression(callExpression: CallExpression, editor: AstEditor, namespaceLookup: Map<string, NamespaceContainer>, scope: Scope) {
         let isNotCalled = false;
         let isStubCall = false;
 
@@ -107,7 +107,7 @@ export class TestGroup extends TestBlock {
             isNotCalled = nameText === 'expectNotCalled';
             isStubCall = nameText === 'stubCall';
 
-            if (isStubCall && !brighterscript.isCallExpression(arg0)) {
+            if (isStubCall && this.shouldNotModifyStubCall(arg0, namespaceLookup, scope)) {
                 return;
             }
             editor.setProperty(callExpression.callee.name, 'text', `_${nameText}`);
@@ -198,6 +198,38 @@ export class TestGroup extends TestBlock {
         }
     }
 
+    private shouldNotModifyStubCall(arg0: Expression, namespaceLookup: Map<string, NamespaceContainer>, scope: Scope) {
+        if (brighterscript.isDottedGetExpression(arg0)) {
+            let nameParts = getAllDottedGetParts(arg0);
+            let name = nameParts.pop();
+            let functionName: string;
+
+            if (name) {
+                //is a namespace?
+                if (nameParts[0] && namespaceLookup.has(nameParts[0].toLowerCase())) {
+                    //then this must be a namespace method
+                    let fullPathName = nameParts.join('.').toLowerCase();
+                    let ns = namespaceLookup.get(fullPathName);
+                    if (!ns) {
+                        //TODO this is an error condition!
+                    }
+                    nameParts.push(name);
+                    functionName = nameParts.join('.').toLowerCase();
+                }
+            }
+
+            if (functionName && scope.getCallableByName(functionName)) {
+                return true;
+            }
+        } else if (brighterscript.isVariableExpression(arg0)) {
+            const functionName = arg0.getName(ParseMode.BrightScript).toLowerCase();
+            if (scope.getCallableByName(functionName)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     public asText(): string {
         let testCaseText = [...this.testCases.values()].filter((tc) => tc.isIncluded).map((tc) => tc.asText());
 
diff --git a/bsc-plugin/src/plugin.spec.ts b/bsc-plugin/src/plugin.spec.ts
index ca66f89e..f2d9a761 100644
--- a/bsc-plugin/src/plugin.spec.ts
+++ b/bsc-plugin/src/plugin.spec.ts
@@ -638,7 +638,7 @@ describe('RooibosPlugin', () => {
             expect(statements[0]).to.be.instanceof(PrintStatement);
         });
 
-        describe.skip('expectCalled transpilation', () => {
+        describe('expectCalled transpilation', () => {
             it('correctly transpiles call funcs', async () => {
                 program.setFile('source/test.spec.bs', `
                     @suite
@@ -1125,7 +1125,7 @@ describe('RooibosPlugin', () => {
             });
         });
 
-        describe.skip('stubCall transpilation', () => {
+        describe('stubCall transpilation', () => {
             it('correctly transpiles call funcs', async () => {
                 program.setFile('source/test.spec.bs', `
                     @suite
@@ -1267,7 +1267,7 @@ describe('RooibosPlugin', () => {
             });
         });
 
-        describe.skip('expectNotCalled transpilation', () => {
+        describe('expectNotCalled transpilation', () => {
             it('correctly transpiles call funcs', async () => {
                 program.setFile('source/test.spec.bs', `
                     @suite

From 67ec05ae087373616189f8c57088e83f1d18c183 Mon Sep 17 00:00:00 2001
From: Christopher Dwyer-Perkins <chris@inverted-solutions.com>
Date: Tue, 16 Jan 2024 16:24:14 -0400
Subject: [PATCH 19/26] More test fixes as the result of moving to ast editor

---
 bsc-plugin/src/lib/rooibos/MockUtil.spec.ts | 430 ++++++++++----------
 bsc-plugin/src/lib/rooibos/MockUtil.ts      |   5 +-
 bsc-plugin/src/plugin.spec.ts               | 396 ++++++++++--------
 3 files changed, 454 insertions(+), 377 deletions(-)

diff --git a/bsc-plugin/src/lib/rooibos/MockUtil.spec.ts b/bsc-plugin/src/lib/rooibos/MockUtil.spec.ts
index f0c44f26..8bb250b4 100644
--- a/bsc-plugin/src/lib/rooibos/MockUtil.spec.ts
+++ b/bsc-plugin/src/lib/rooibos/MockUtil.spec.ts
@@ -4,6 +4,7 @@ import { expect } from 'chai';
 import PluginInterface from 'brighterscript/dist/PluginInterface';
 import * as fsExtra from 'fs-extra';
 import { RooibosPlugin } from '../../plugin';
+import undent from 'undent';
 
 let tmpPath = s`${process.cwd()}/tmp`;
 let _rootDir = s`${tmpPath}/rootDir`;
@@ -19,9 +20,13 @@ describe('MockUtil', () => {
     let plugin: RooibosPlugin;
     let options;
 
-    function getContents(filename: string) {
+    function getContents(filename: string, trim = true) {
         // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
-        return trimLeading(fsExtra.readFileSync(s`${_stagingFolderPath}/${filename}`).toString());
+        let contents = fsExtra.readFileSync(s`${_stagingFolderPath}/${filename}`).toString();
+        if (trim) {
+            return trimLeading(contents);
+        }
+        return contents;
     }
 
     describe('MockUtil', () => {
@@ -69,34 +74,35 @@ describe('MockUtil', () => {
             // in `beforeEach`. This is because the compiler normally skips processing .brs files and copies them as-is.
             it('adds util code to a brs file', async () => {
                 program.setFile('source/code.brs', `
-                function sayHello(a1, a2)
-                    print "hello"
-                end function
-            `);
+                    function sayHello(a1, a2)
+                        print "hello"
+                    end function
+                `);
                 program.validate();
                 expect(program.getDiagnostics()).to.be.empty;
                 await builder.transpile();
-                let a = getContents('source/code.brs');
-                let b = trimLeading(`function sayHello(a1, a2)
-                __stubs_globalAa = getGlobalAa()
-                if RBS_SM_1_getMocksByFunctionName()["sayhello"] <> invalid
-                __stubOrMockResult = RBS_SM_1_getMocksByFunctionName()["sayhello"].callback(a1, a2)
-                return __stubOrMockResult
-                else if type(__stubs_globalAa?.__globalStubs?.sayhello).endsWith("Function")
-                __stubFunction = __stubs_globalAa.__globalStubs.sayhello
-                __stubOrMockResult = __stubFunction(a1, a2)
-                return __stubOrMockResult
-                end if
-                print "hello"
-                end function
+                let a = getContents('source/code.brs', false);
+                let b = undent(`
+                    function sayHello(a1, a2)
+                        __stubs_globalAa = getGlobalAa()
+                        if RBS_SM_1_getMocksByFunctionName()["sayhello"] <> invalid
+                            __stubOrMockResult = RBS_SM_1_getMocksByFunctionName()["sayhello"].callback(a1, a2)
+                            return __stubOrMockResult
+                        else if type(__stubs_globalAa?.__globalStubs?.sayhello).endsWith("Function")
+                            __stubFunction = __stubs_globalAa.__globalStubs.sayhello
+                            __stubOrMockResult = __stubFunction(a1, a2)
+                            return __stubOrMockResult
+                        end if
+                        print "hello"
+                    end function
 
-                function RBS_SM_1_getMocksByFunctionName()
-                if m._rMocksByFunctionName = invalid
-                m._rMocksByFunctionName = {}
-                end if
-                return m._rMocksByFunctionName
-                end function
-`);
+                    function RBS_SM_1_getMocksByFunctionName()
+                        if m._rMocksByFunctionName = invalid
+                            m._rMocksByFunctionName = {}
+                        end if
+                        return m._rMocksByFunctionName
+                    end function
+                `);
                 expect(a).to.equal(b);
 
             });
@@ -105,195 +111,200 @@ describe('MockUtil', () => {
 
             it('enables mocking on global functions', async () => {
                 program.setFile('source/code.bs', `
-                function sayHello(a1, a2)
-                    print "hello"
-                end function
-            `);
+                    function sayHello(a1, a2)
+                        print "hello"
+                    end function
+                `);
                 program.validate();
                 expect(program.getDiagnostics()).to.be.empty;
                 await builder.transpile();
-                let a = getContents('source/code.brs');
-                let b = trimLeading(`function sayHello(a1, a2)
-                __stubs_globalAa = getGlobalAa()
-                if RBS_SM_1_getMocksByFunctionName()["sayhello"] <> invalid
-                __stubOrMockResult = RBS_SM_1_getMocksByFunctionName()["sayhello"].callback(a1, a2)
-                return __stubOrMockResult
-                else if type(__stubs_globalAa?.__globalStubs?.sayhello).endsWith("Function")
-                __stubFunction = __stubs_globalAa.__globalStubs.sayhello
-                __stubOrMockResult = __stubFunction(a1, a2)
-                return __stubOrMockResult
-                end if
-                print "hello"
-                end function
+                let a = getContents('source/code.brs', false);
+                let b = undent(`
+                    function sayHello(a1, a2)
+                        __stubs_globalAa = getGlobalAa()
+                        if RBS_SM_1_getMocksByFunctionName()["sayhello"] <> invalid
+                            __stubOrMockResult = RBS_SM_1_getMocksByFunctionName()["sayhello"].callback(a1, a2)
+                            return __stubOrMockResult
+                        else if type(__stubs_globalAa?.__globalStubs?.sayhello).endsWith("Function")
+                            __stubFunction = __stubs_globalAa.__globalStubs.sayhello
+                            __stubOrMockResult = __stubFunction(a1, a2)
+                            return __stubOrMockResult
+                        end if
+                        print "hello"
+                    end function
 
-                function RBS_SM_1_getMocksByFunctionName()
-                if m._rMocksByFunctionName = invalid
-                m._rMocksByFunctionName = {}
-                end if
-                return m._rMocksByFunctionName
-                end function
-`);
+                    function RBS_SM_1_getMocksByFunctionName()
+                        if m._rMocksByFunctionName = invalid
+                            m._rMocksByFunctionName = {}
+                        end if
+                        return m._rMocksByFunctionName
+                    end function
+                `);
                 expect(a).to.equal(b);
 
             });
             it('weird raletracker task issue I saw', async () => {
                 program.setFile('source/code.bs', `
-                Sub RedLines_SetRulerLines(rulerLines)
-                    For Each line In rulerLines.Items()
-                        RedLines_AddLine(line.key, line.value.position, line.value.coords, m.node, m.childMap)
-                    End For
-                end Sub
-                Sub RedLines_AddLine(id, position, coords, node, childMap) as Object
-                    line = CreateObject("roSGNode", "Rectangle")
-                    line.setField("id", id)
-                end sub
-            `);
+                    Sub RedLines_SetRulerLines(rulerLines)
+                        For Each line In rulerLines.Items()
+                            RedLines_AddLine(line.key, line.value.position, line.value.coords, m.node, m.childMap)
+                        End For
+                    end Sub
+                    Sub RedLines_AddLine(id, position, coords, node, childMap) as Object
+                        line = CreateObject("roSGNode", "Rectangle")
+                        line.setField("id", id)
+                    end sub
+                `);
                 program.validate();
                 expect(program.getDiagnostics()).to.be.empty;
                 await builder.transpile();
-                let a = getContents('source/code.brs');
-                let b = trimLeading(`Sub RedLines_SetRulerLines(rulerLines)
-                __stubs_globalAa = getGlobalAa()
-                if RBS_SM_1_getMocksByFunctionName()["redlines_setrulerlines"] <> invalid
-                __stubOrMockResult = RBS_SM_1_getMocksByFunctionName()["redlines_setrulerlines"].callback(rulerLines)
-                return
-                else if type(__stubs_globalAa?.__globalStubs?.redlines_setrulerlines).endsWith("Function")
-                __stubFunction = __stubs_globalAa.__globalStubs.redlines_setrulerlines
-                __stubOrMockResult = __stubFunction(rulerLines)
-                return
-                end if
-                For Each line In rulerLines.Items()
-                RedLines_AddLine(line.key, line.value.position, line.value.coords, m.node, m.childMap)
-                End For
-                end Sub
-
-                Sub RedLines_AddLine(id, position, coords, node, childMap) as Object
-                __stubs_globalAa = getGlobalAa()
-                if RBS_SM_1_getMocksByFunctionName()["redlines_addline"] <> invalid
-                __stubOrMockResult = RBS_SM_1_getMocksByFunctionName()["redlines_addline"].callback(id, position, coords, node, childMap)
-                return __stubOrMockResult
-                else if type(__stubs_globalAa?.__globalStubs?.redlines_addline).endsWith("Function")
-                __stubFunction = __stubs_globalAa.__globalStubs.redlines_addline
-                __stubOrMockResult = __stubFunction(id, position, coords, node, childMap)
-                return __stubOrMockResult
-                end if
-                line = CreateObject("roSGNode", "Rectangle")
-                line.setField("id", id)
-                end sub
+                let a = getContents('source/code.brs', false);
+                let b = undent(`
+                    Sub RedLines_SetRulerLines(rulerLines)
+                        __stubs_globalAa = getGlobalAa()
+                        if RBS_SM_1_getMocksByFunctionName()["redlines_setrulerlines"] <> invalid
+                            __stubOrMockResult = RBS_SM_1_getMocksByFunctionName()["redlines_setrulerlines"].callback(rulerLines)
+                            return
+                        else if type(__stubs_globalAa?.__globalStubs?.redlines_setrulerlines).endsWith("Function")
+                            __stubFunction = __stubs_globalAa.__globalStubs.redlines_setrulerlines
+                            __stubOrMockResult = __stubFunction(rulerLines)
+                            return
+                        end if
+                        For Each line In rulerLines.Items()
+                            RedLines_AddLine(line.key, line.value.position, line.value.coords, m.node, m.childMap)
+                        End For
+                    end Sub
+
+                    Sub RedLines_AddLine(id, position, coords, node, childMap) as Object
+                        __stubs_globalAa = getGlobalAa()
+                        if RBS_SM_1_getMocksByFunctionName()["redlines_addline"] <> invalid
+                            __stubOrMockResult = RBS_SM_1_getMocksByFunctionName()["redlines_addline"].callback(id, position, coords, node, childMap)
+                            return __stubOrMockResult
+                        else if type(__stubs_globalAa?.__globalStubs?.redlines_addline).endsWith("Function")
+                            __stubFunction = __stubs_globalAa.__globalStubs.redlines_addline
+                            __stubOrMockResult = __stubFunction(id, position, coords, node, childMap)
+                            return __stubOrMockResult
+                        end if
+                        line = CreateObject("roSGNode", "Rectangle")
+                        line.setField("id", id)
+                    end sub
 
-                function RBS_SM_1_getMocksByFunctionName()
-                if m._rMocksByFunctionName = invalid
-                m._rMocksByFunctionName = {}
-                end if
-                return m._rMocksByFunctionName
-                end function
-`);
+                    function RBS_SM_1_getMocksByFunctionName()
+                        if m._rMocksByFunctionName = invalid
+                            m._rMocksByFunctionName = {}
+                        end if
+                        return m._rMocksByFunctionName
+                    end function
+                `);
                 expect(a).to.equal(b);
 
             });
 
             it('enables mocking on global sub', async () => {
                 program.setFile('source/code.bs', `
-                sub sayHello(a1, a2)
-                    print "hello"
-                end sub
-            `);
+                    sub sayHello(a1, a2)
+                        print "hello"
+                    end sub
+                `);
                 program.validate();
                 expect(program.getDiagnostics()).to.be.empty;
                 await builder.transpile();
-                let a = getContents('source/code.brs');
-                let b = trimLeading(`sub sayHello(a1, a2)
-                __stubs_globalAa = getGlobalAa()
-                if RBS_SM_1_getMocksByFunctionName()["sayhello"] <> invalid
-                __stubOrMockResult = RBS_SM_1_getMocksByFunctionName()["sayhello"].callback(a1, a2)
-                return
-                else if type(__stubs_globalAa?.__globalStubs?.sayhello).endsWith("Function")
-                __stubFunction = __stubs_globalAa.__globalStubs.sayhello
-                __stubOrMockResult = __stubFunction(a1, a2)
-                return
-                end if
-                print "hello"
-                end sub
+                let a = getContents('source/code.brs', false);
+                let b = undent(`
+                    sub sayHello(a1, a2)
+                        __stubs_globalAa = getGlobalAa()
+                        if RBS_SM_1_getMocksByFunctionName()["sayhello"] <> invalid
+                            __stubOrMockResult = RBS_SM_1_getMocksByFunctionName()["sayhello"].callback(a1, a2)
+                            return
+                        else if type(__stubs_globalAa?.__globalStubs?.sayhello).endsWith("Function")
+                            __stubFunction = __stubs_globalAa.__globalStubs.sayhello
+                            __stubOrMockResult = __stubFunction(a1, a2)
+                            return
+                        end if
+                        print "hello"
+                    end sub
 
-                function RBS_SM_1_getMocksByFunctionName()
-                if m._rMocksByFunctionName = invalid
-                m._rMocksByFunctionName = {}
-                end if
-                return m._rMocksByFunctionName
-                end function
-`);
+                    function RBS_SM_1_getMocksByFunctionName()
+                        if m._rMocksByFunctionName = invalid
+                            m._rMocksByFunctionName = {}
+                        end if
+                        return m._rMocksByFunctionName
+                    end function
+                `);
                 expect(a).to.equal(b);
 
             });
 
             it('enables mocking on namespaced function', async () => {
                 program.setFile('source/code.bs', `
-                namespace person.utils
-                    function sayHello(a1, a2)
-                        print "hello"
-                    end function
-                end namespace
-            `);
+                    namespace person.utils
+                        function sayHello(a1, a2)
+                            print "hello"
+                        end function
+                    end namespace
+                `);
                 program.validate();
                 expect(program.getDiagnostics()).to.be.empty;
                 await builder.transpile();
-                let a = getContents('source/code.brs');
-                let b = trimLeading(`function person_utils_sayHello(a1, a2)
-                __stubs_globalAa = getGlobalAa()
-                if RBS_SM_1_getMocksByFunctionName()["person_utils_sayhello"] <> invalid
-                __stubOrMockResult = RBS_SM_1_getMocksByFunctionName()["person_utils_sayhello"].callback(a1, a2)
-                return __stubOrMockResult
-                else if type(__stubs_globalAa?.__globalStubs?.person_utils_sayhello).endsWith("Function")
-                __stubFunction = __stubs_globalAa.__globalStubs.person_utils_sayhello
-                __stubOrMockResult = __stubFunction(a1, a2)
-                return __stubOrMockResult
-                end if
-                print "hello"
-                end function
+                let a = getContents('source/code.brs', false);
+                let b = undent(`
+                    function person_utils_sayHello(a1, a2)
+                        __stubs_globalAa = getGlobalAa()
+                        if RBS_SM_1_getMocksByFunctionName()["person_utils_sayhello"] <> invalid
+                            __stubOrMockResult = RBS_SM_1_getMocksByFunctionName()["person_utils_sayhello"].callback(a1, a2)
+                            return __stubOrMockResult
+                        else if type(__stubs_globalAa?.__globalStubs?.person_utils_sayhello).endsWith("Function")
+                            __stubFunction = __stubs_globalAa.__globalStubs.person_utils_sayhello
+                            __stubOrMockResult = __stubFunction(a1, a2)
+                            return __stubOrMockResult
+                        end if
+                        print "hello"
+                    end function
 
-                function RBS_SM_1_getMocksByFunctionName()
-                if m._rMocksByFunctionName = invalid
-                m._rMocksByFunctionName = {}
-                end if
-                return m._rMocksByFunctionName
-                end function
-`);
+                    function RBS_SM_1_getMocksByFunctionName()
+                        if m._rMocksByFunctionName = invalid
+                            m._rMocksByFunctionName = {}
+                        end if
+                        return m._rMocksByFunctionName
+                    end function
+                `);
                 expect(a).to.equal(b);
 
             });
 
             it('enables mocking on namespaced sub', async () => {
                 program.setFile('source/code.bs', `
-                namespace person.utils
-                    sub sayHello(a1, a2)
-                        print "hello"
-                    end sub
-                end namespace
-            `);
+                    namespace person.utils
+                        sub sayHello(a1, a2)
+                            print "hello"
+                        end sub
+                    end namespace
+                `);
                 program.validate();
                 expect(program.getDiagnostics()).to.be.empty;
                 await builder.transpile();
-                let a = getContents('source/code.brs');
-                let b = trimLeading(`sub person_utils_sayHello(a1, a2)
-                __stubs_globalAa = getGlobalAa()
-                if RBS_SM_1_getMocksByFunctionName()["person_utils_sayhello"] <> invalid
-                __stubOrMockResult = RBS_SM_1_getMocksByFunctionName()["person_utils_sayhello"].callback(a1, a2)
-                return
-                else if type(__stubs_globalAa?.__globalStubs?.person_utils_sayhello).endsWith("Function")
-                __stubFunction = __stubs_globalAa.__globalStubs.person_utils_sayhello
-                __stubOrMockResult = __stubFunction(a1, a2)
-                return
-                end if
-                print "hello"
-                end sub
+                let a = getContents('source/code.brs', false);
+                let b = undent(`
+                    sub person_utils_sayHello(a1, a2)
+                        __stubs_globalAa = getGlobalAa()
+                        if RBS_SM_1_getMocksByFunctionName()["person_utils_sayhello"] <> invalid
+                            __stubOrMockResult = RBS_SM_1_getMocksByFunctionName()["person_utils_sayhello"].callback(a1, a2)
+                            return
+                        else if type(__stubs_globalAa?.__globalStubs?.person_utils_sayhello).endsWith("Function")
+                            __stubFunction = __stubs_globalAa.__globalStubs.person_utils_sayhello
+                            __stubOrMockResult = __stubFunction(a1, a2)
+                            return
+                        end if
+                        print "hello"
+                    end sub
 
-                function RBS_SM_1_getMocksByFunctionName()
-                if m._rMocksByFunctionName = invalid
-                m._rMocksByFunctionName = {}
-                end if
-                return m._rMocksByFunctionName
-                end function
-`);
+                    function RBS_SM_1_getMocksByFunctionName()
+                        if m._rMocksByFunctionName = invalid
+                            m._rMocksByFunctionName = {}
+                        end if
+                        return m._rMocksByFunctionName
+                    end function
+                `);
                 expect(a).to.equal(b);
 
             });
@@ -346,55 +357,56 @@ describe('MockUtil', () => {
                 program.validate();
                 expect(program.getDiagnostics()).to.be.empty;
                 await builder.transpile();
-                let a = getContents('source/code.brs');
-                let b = trimLeading(`function __beings_Person_builder()
-                    instance = {}
-                    instance.new = sub()
-                    end sub
-                    instance.sayHello = sub(a1, a2)
-                    print "hello"
-                    end sub
-                    return instance
+                let a = getContents('source/code.brs', false);
+                let b = undent(`
+                    function __beings_Person_builder()
+                        instance = {}
+                        instance.new = sub()
+                        end sub
+                        instance.sayHello = sub(a1, a2)
+                            print "hello"
+                        end sub
+                        return instance
                     end function
                     function beings_Person()
-                    instance = __beings_Person_builder()
-                    instance.new()
-                    return instance
+                        instance = __beings_Person_builder()
+                        instance.new()
+                        return instance
                     end function
 
                     function beings_sayHello()
-                    __stubs_globalAa = getGlobalAa()
-                    if RBS_SM_1_getMocksByFunctionName()["beings_sayhello"] <> invalid
-                    __stubOrMockResult = RBS_SM_1_getMocksByFunctionName()["beings_sayhello"].callback()
-                    return __stubOrMockResult
-                    else if type(__stubs_globalAa?.__globalStubs?.beings_sayhello).endsWith("Function")
-                    __stubFunction = __stubs_globalAa.__globalStubs.beings_sayhello
-                    __stubOrMockResult = __stubFunction()
-                    return __stubOrMockResult
-                    end if
-                    print "hello2"
+                        __stubs_globalAa = getGlobalAa()
+                        if RBS_SM_1_getMocksByFunctionName()["beings_sayhello"] <> invalid
+                            __stubOrMockResult = RBS_SM_1_getMocksByFunctionName()["beings_sayhello"].callback()
+                            return __stubOrMockResult
+                        else if type(__stubs_globalAa?.__globalStubs?.beings_sayhello).endsWith("Function")
+                            __stubFunction = __stubs_globalAa.__globalStubs.beings_sayhello
+                            __stubOrMockResult = __stubFunction()
+                            return __stubOrMockResult
+                        end if
+                        print "hello2"
                     end function
 
                     function sayHello()
-                    __stubs_globalAa = getGlobalAa()
-                    if RBS_SM_1_getMocksByFunctionName()["sayhello"] <> invalid
-                    __stubOrMockResult = RBS_SM_1_getMocksByFunctionName()["sayhello"].callback()
-                    return __stubOrMockResult
-                    else if type(__stubs_globalAa?.__globalStubs?.sayhello).endsWith("Function")
-                    __stubFunction = __stubs_globalAa.__globalStubs.sayhello
-                    __stubOrMockResult = __stubFunction()
-                    return __stubOrMockResult
-                    end if
-                    print "hello3"
+                        __stubs_globalAa = getGlobalAa()
+                        if RBS_SM_1_getMocksByFunctionName()["sayhello"] <> invalid
+                            __stubOrMockResult = RBS_SM_1_getMocksByFunctionName()["sayhello"].callback()
+                            return __stubOrMockResult
+                        else if type(__stubs_globalAa?.__globalStubs?.sayhello).endsWith("Function")
+                            __stubFunction = __stubs_globalAa.__globalStubs.sayhello
+                            __stubOrMockResult = __stubFunction()
+                            return __stubOrMockResult
+                        end if
+                        print "hello3"
                     end function
 
                     function RBS_SM_1_getMocksByFunctionName()
-                    if m._rMocksByFunctionName = invalid
-                    m._rMocksByFunctionName = {}
-                    end if
-                    return m._rMocksByFunctionName
+                        if m._rMocksByFunctionName = invalid
+                            m._rMocksByFunctionName = {}
+                        end if
+                        return m._rMocksByFunctionName
                     end function
-`);
+                `);
                 expect(a).to.equal(b);
 
             });
diff --git a/bsc-plugin/src/lib/rooibos/MockUtil.ts b/bsc-plugin/src/lib/rooibos/MockUtil.ts
index 805c485f..3bd264e7 100644
--- a/bsc-plugin/src/lib/rooibos/MockUtil.ts
+++ b/bsc-plugin/src/lib/rooibos/MockUtil.ts
@@ -124,9 +124,8 @@ export class MockUtil {
     }
 
     addBrsAPIText(file: BrsFile) {
-        //TODO should use ast editor!
-        const func = new RawCodeStatement(this.brsFileAdditions.replace(/\#ID\#/g, this.fileId.toString().trim()), file, Range.create(Position.create(1, 1), Position.create(1, 1)));
-        file.ast.statements.push(func);
+        const func = Parser.parse(this.brsFileAdditions.replace(/\#ID\#/g, this.fileId.toString().trim())).ast.statements;
+        this.astEditor.arrayPush(file.ast.statements, ...func);
     }
 
 
diff --git a/bsc-plugin/src/plugin.spec.ts b/bsc-plugin/src/plugin.spec.ts
index f2d9a761..a248e164 100644
--- a/bsc-plugin/src/plugin.spec.ts
+++ b/bsc-plugin/src/plugin.spec.ts
@@ -657,40 +657,46 @@ describe('RooibosPlugin', () => {
                 expect(program.getDiagnostics()).to.be.empty;
                 expect(plugin.session.sessionInfo.testSuitesToRun).to.not.be.empty;
                 await builder.transpile();
-                const testContents = getTestFunctionContents(true);
+                const testContents = getTestFunctionContents();
                 expect(
                     testContents
                 ).to.eql(undent`
                     m.currentAssertLineNumber = 6
                     m._expectCalled(m.thing, "callFunc", m, "m.thing", [
-                    "getFunction"
+                        "getFunction"
                     ])
-                    if m.currentResult?.isFail = true then m.done() : return invalid
-
-
+                    if m.currentResult?.isFail = true then
+                        m.done()
+                        return invalid
+                    end if
                     m.currentAssertLineNumber = 7
                     m._expectCalled(m.thing, "callFunc", m, "m.thing", [
-                    "getFunction"
+                        "getFunction"
                     ], "return")
-                    if m.currentResult?.isFail = true then m.done() : return invalid
-
-
+                    if m.currentResult?.isFail = true then
+                        m.done()
+                        return invalid
+                    end if
                     m.currentAssertLineNumber = 8
                     m._expectCalled(m.thing, "callFunc", m, "m.thing", [
-                    "getFunction"
-                    "a"
-                    "b"
+                        "getFunction"
+                        "a"
+                        "b"
                     ])
-                    if m.currentResult?.isFail = true then m.done() : return invalid
-
-
+                    if m.currentResult?.isFail = true then
+                        m.done()
+                        return invalid
+                    end if
                     m.currentAssertLineNumber = 9
                     m._expectCalled(m.thing, "callFunc", m, "m.thing", [
-                    "getFunction"
-                    "a"
-                    "b"
+                        "getFunction"
+                        "a"
+                        "b"
                     ], "return")
-                    if m.currentResult?.isFail = true then m.done() : return invalid
+                    if m.currentResult?.isFail = true then
+                        m.done()
+                        return invalid
+                    end if
                 `);
             });
 
@@ -715,12 +721,16 @@ describe('RooibosPlugin', () => {
                 ).to.eql(undent`
                     m.currentAssertLineNumber = 6
                     m._expectCalled(m.thing, "getFunctionField", m, "m.thing", invalid)
-                    if m.currentResult?.isFail = true then m.done() : return invalid
-
-
+                    if m.currentResult?.isFail = true then
+                        m.done()
+                        return invalid
+                    end if
                     m.currentAssertLineNumber = 7
                     m._expectCalled(m.thing, "getFunctionField", m, "m.thing", invalid, "return")
-                    if m.currentResult?.isFail = true then m.done() : return invalid
+                    if m.currentResult?.isFail = true then
+                        m.done()
+                        return invalid
+                    end if
                 `);
             });
 
@@ -742,34 +752,40 @@ describe('RooibosPlugin', () => {
                 await builder.transpile();
                 expect(program.getDiagnostics().filter((d) => d.code !== 'RBS2213')).to.be.empty;
                 expect(plugin.session.sessionInfo.testSuitesToRun).to.not.be.empty;
-                const testContents = getTestFunctionContents(true);
+                const testContents = getTestFunctionContents();
                 expect(
                     testContents
                 ).to.eql(undent`
                     m.currentAssertLineNumber = 6
                     m._expectCalled(m.thing, "getFunction", m, "m.thing", [])
-                    if m.currentResult?.isFail = true then m.done() : return invalid
-
-
+                    if m.currentResult?.isFail = true then
+                        m.done()
+                        return invalid
+                    end if
                     m.currentAssertLineNumber = 7
                     m._expectCalled(m.thing, "getFunction", m, "m.thing", [], "return")
-                    if m.currentResult?.isFail = true then m.done() : return invalid
-
-
+                    if m.currentResult?.isFail = true then
+                        m.done()
+                        return invalid
+                    end if
                     m.currentAssertLineNumber = 8
                     m._expectCalled(m.thing, "getFunction", m, "m.thing", [
-                    "arg1"
-                    "arg2"
+                        "arg1"
+                        "arg2"
                     ])
-                    if m.currentResult?.isFail = true then m.done() : return invalid
-
-
+                    if m.currentResult?.isFail = true then
+                        m.done()
+                        return invalid
+                    end if
                     m.currentAssertLineNumber = 9
                     m._expectCalled(m.thing, "getFunction", m, "m.thing", [
-                    "arg1"
-                    "arg2"
+                        "arg1"
+                        "arg2"
                     ], "return")
-                    if m.currentResult?.isFail = true then m.done() : return invalid
+                    if m.currentResult?.isFail = true then
+                        m.done()
+                        return invalid
+                    end if
                 `);
             });
             it('does not produce crashing code for subs', async () => {
@@ -790,34 +806,39 @@ describe('RooibosPlugin', () => {
                 await builder.transpile();
                 expect(program.getDiagnostics().filter((d) => d.code !== 'RBS2213')).to.be.empty;
                 expect(plugin.session.sessionInfo.testSuitesToRun).to.not.be.empty;
-                let a = getTestSubContents(true);
                 expect(
-                    getTestSubContents(true)
+                    getTestSubContents()
                 ).to.eql(undent`
                     m.currentAssertLineNumber = 6
                     m._expectCalled(m.thing, "getFunction", m, "m.thing", [])
-                    if m.currentResult?.isFail = true then m.done() : return
-
-
+                    if m.currentResult?.isFail = true then
+                        m.done()
+                        return
+                    end if
                     m.currentAssertLineNumber = 7
                     m._expectCalled(m.thing, "getFunction", m, "m.thing", [], "return")
-                    if m.currentResult?.isFail = true then m.done() : return
-
-
+                    if m.currentResult?.isFail = true then
+                        m.done()
+                        return
+                    end if
                     m.currentAssertLineNumber = 8
                     m._expectCalled(m.thing, "getFunction", m, "m.thing", [
-                    "arg1"
-                    "arg2"
+                        "arg1"
+                        "arg2"
                     ])
-                    if m.currentResult?.isFail = true then m.done() : return
-
-
+                    if m.currentResult?.isFail = true then
+                        m.done()
+                        return
+                    end if
                     m.currentAssertLineNumber = 9
                     m._expectCalled(m.thing, "getFunction", m, "m.thing", [
-                    "arg1"
-                    "arg2"
+                        "arg1"
+                        "arg2"
                     ], "return")
-                    if m.currentResult?.isFail = true then m.done() : return
+                    if m.currentResult?.isFail = true then
+                        m.done()
+                        return
+                    end if
                 `);
             });
             it('does not break when validating again after a transpile', async () => {
@@ -839,34 +860,40 @@ describe('RooibosPlugin', () => {
                 program.validate();
                 expect(program.getDiagnostics().filter((d) => d.code !== 'RBS2213')).to.be.empty;
                 expect(plugin.session.sessionInfo.testSuitesToRun).to.not.be.empty;
-                const testContents = getTestFunctionContents(true);
+                const testContents = getTestFunctionContents();
                 expect(
                     testContents
                 ).to.eql(undent`
                     m.currentAssertLineNumber = 6
                     m._expectCalled(m.thing, "getFunction", m, "m.thing", [])
-                    if m.currentResult?.isFail = true then m.done() : return invalid
-
-
+                    if m.currentResult?.isFail = true then
+                        m.done()
+                        return invalid
+                    end if
                     m.currentAssertLineNumber = 7
                     m._expectCalled(m.thing, "getFunction", m, "m.thing", [], "return")
-                    if m.currentResult?.isFail = true then m.done() : return invalid
-
-
+                    if m.currentResult?.isFail = true then
+                        m.done()
+                        return invalid
+                    end if
                     m.currentAssertLineNumber = 8
                     m._expectCalled(m.thing, "getFunction", m, "m.thing", [
-                    "arg1"
-                    "arg2"
+                        "arg1"
+                        "arg2"
                     ])
-                    if m.currentResult?.isFail = true then m.done() : return invalid
-
-
+                    if m.currentResult?.isFail = true then
+                        m.done()
+                        return invalid
+                    end if
                     m.currentAssertLineNumber = 9
                     m._expectCalled(m.thing, "getFunction", m, "m.thing", [
-                    "arg1"
-                    "arg2"
+                        "arg1"
+                        "arg2"
                     ], "return")
-                    if m.currentResult?.isFail = true then m.done() : return invalid
+                    if m.currentResult?.isFail = true then
+                        m.done()
+                        return invalid
+                    end if
                 `);
             });
 
@@ -891,19 +918,22 @@ describe('RooibosPlugin', () => {
                 expect(program.getDiagnostics()).to.be.empty;
                 // expect(plugin.session.sessionInfo.testSuitesToRun).to.not.be.empty;
                 await builder.transpile();
-                const testContents = getTestFunctionContents(true);
+                const testContents = getTestFunctionContents();
                 expect(
                     testContents
                 ).to.eql(undent`
                     b = {
-                    someValue: "value"
+                        someValue: "value"
                     }
-
                     m.currentAssertLineNumber = 12
                     m.assertEqual(b, {
-                    someValue: "value"
+                        someValue: "value"
                     })
-                    if m.currentResult?.isFail = true then m.done() : return invalid`);
+                    if m.currentResult?.isFail = true then
+                        m.done()
+                        return invalid
+                    end if
+                `);
             });
 
             it('correctly transpiles function invocations - simple object', async () => {
@@ -925,38 +955,43 @@ describe('RooibosPlugin', () => {
                 expect(program.getDiagnostics()).to.be.empty;
                 expect(plugin.session.sessionInfo.testSuitesToRun).to.not.be.empty;
                 await builder.transpile();
-                const testContents = getTestFunctionContents(true);
+                const testContents = getTestFunctionContents();
                 expect(
                     testContents
                 ).to.eql(undent`
                     item = {
-                    id: "item"
+                        id: "item"
                     }
-
                     m.currentAssertLineNumber = 7
                     m._expectCalled(item, "getFunction", item, "item", [])
-                    if m.currentResult?.isFail = true then m.done() : return invalid
-
-
+                    if m.currentResult?.isFail = true then
+                        m.done()
+                        return invalid
+                    end if
                     m.currentAssertLineNumber = 8
                     m._expectCalled(item, "getFunction", item, "item", [], "return")
-                    if m.currentResult?.isFail = true then m.done() : return invalid
-
-
+                    if m.currentResult?.isFail = true then
+                        m.done()
+                        return invalid
+                    end if
                     m.currentAssertLineNumber = 9
                     m._expectCalled(item, "getFunction", item, "item", [
-                    "arg1"
-                    "arg2"
+                        "arg1"
+                        "arg2"
                     ])
-                    if m.currentResult?.isFail = true then m.done() : return invalid
-
-
+                    if m.currentResult?.isFail = true then
+                        m.done()
+                        return invalid
+                    end if
                     m.currentAssertLineNumber = 10
                     m._expectCalled(item, "getFunction", item, "item", [
-                    "arg1"
-                    "arg2"
+                        "arg1"
+                        "arg2"
                     ], "return")
-                    if m.currentResult?.isFail = true then m.done() : return invalid
+                    if m.currentResult?.isFail = true then
+                        m.done()
+                        return invalid
+                    end if
                 `);
             });
             it('correctly transpiles global function calls', async () => {
@@ -984,38 +1019,43 @@ describe('RooibosPlugin', () => {
                 expect(program.getDiagnostics()).to.be.empty;
                 expect(plugin.session.sessionInfo.testSuitesToRun).to.not.be.empty;
                 await builder.transpile();
-                const testText = getTestFunctionContents(true);
+                const testText = getTestFunctionContents();
                 expect(
                     testText
                 ).to.eql(undent`
                 item = {
-                id: "item"
+                    id: "item"
                 }
-
                 m.currentAssertLineNumber = 7
                 m._expectCalled(sayHello, "sayHello", invalid, invalid, [
-                "arg1"
-                "arg2"
+                    "arg1"
+                    "arg2"
                 ], "return")
-                if m.currentResult?.isFail = true then m.done() : return invalid
-
-
+                if m.currentResult?.isFail = true then
+                    m.done()
+                    return invalid
+                end if
                 m.currentAssertLineNumber = 8
                 m._expectCalled(sayHello, "sayHello", invalid, invalid, [])
-                if m.currentResult?.isFail = true then m.done() : return invalid
-
-
+                if m.currentResult?.isFail = true then
+                    m.done()
+                    return invalid
+                end if
                 m.currentAssertLineNumber = 9
                 m._expectCalled(sayHello, "sayHello", invalid, invalid, [], "return")
-                if m.currentResult?.isFail = true then m.done() : return invalid
-
-
+                if m.currentResult?.isFail = true then
+                    m.done()
+                    return invalid
+                end if
                 m.currentAssertLineNumber = 10
                 m._expectCalled(sayHello, "sayHello", invalid, invalid, [
-                "arg1"
-                "arg2"
+                    "arg1"
+                    "arg2"
                 ])
-                if m.currentResult?.isFail = true then m.done() : return invalid
+                if m.currentResult?.isFail = true then
+                    m.done()
+                    return invalid
+                end if
 `);
 
                 let codeText = getContents('code.brs');
@@ -1023,11 +1063,11 @@ describe('RooibosPlugin', () => {
                 function sayHello(firstName = "", lastName = "")
                     __stubs_globalAa = getGlobalAa()
                     if RBS_SM_1_getMocksByFunctionName()["sayhello"] <> invalid
-                        __stubOrMockResult = RBS_SM_1_getMocksByFunctionName()["sayhello"].callback(firstName,lastName)
+                        __stubOrMockResult = RBS_SM_1_getMocksByFunctionName()["sayhello"].callback(firstName, lastName)
                         return __stubOrMockResult
                     else if type(__stubs_globalAa?.__globalStubs?.sayhello).endsWith("Function")
                         __stubFunction = __stubs_globalAa.__globalStubs.sayhello
-                        __stubOrMockResult = __stubFunction()
+                        __stubOrMockResult = __stubFunction(firstName, lastName)
                         return __stubOrMockResult
                     end if
                     print firstName + " " + lastName
@@ -1035,7 +1075,7 @@ describe('RooibosPlugin', () => {
 
                 function RBS_SM_1_getMocksByFunctionName()
                     if m._rMocksByFunctionName = invalid
-                    m._rMocksByFunctionName = {}
+                        m._rMocksByFunctionName = {}
                     end if
                     return m._rMocksByFunctionName
                 end function`);
@@ -1067,50 +1107,55 @@ describe('RooibosPlugin', () => {
                 expect(program.getDiagnostics()).to.be.empty;
                 expect(plugin.session.sessionInfo.testSuitesToRun).to.not.be.empty;
                 await builder.transpile();
-                const testText = getTestFunctionContents(true);
+                const testText = getTestFunctionContents();
                 expect(
                     testText
                 ).to.eql(undent`
                     item = {
                         id: "item"
                     }
-
                     m.currentAssertLineNumber = 7
                     m._expectCalled(utils_sayhello, "utils_sayhello", invalid, invalid, [
                         "arg1"
                         "arg2"
                     ], "return")
-                    if m.currentResult?.isFail = true then m.done() : return invalid
-
-
+                    if m.currentResult?.isFail = true then
+                        m.done()
+                        return invalid
+                    end if
                     m.currentAssertLineNumber = 8
                     m._expectCalled(utils_sayhello, "utils_sayhello", invalid, invalid, [])
-                    if m.currentResult?.isFail = true then m.done() : return invalid
-
-
+                    if m.currentResult?.isFail = true then
+                        m.done()
+                        return invalid
+                    end if
                     m.currentAssertLineNumber = 9
                     m._expectCalled(utils_sayhello, "utils_sayhello", invalid, invalid, [], "return")
-                    if m.currentResult?.isFail = true then m.done() : return invalid
-
-
+                    if m.currentResult?.isFail = true then
+                        m.done()
+                        return invalid
+                    end if
                     m.currentAssertLineNumber = 10
                     m._expectCalled(utils_sayhello, "utils_sayhello", invalid, invalid, [
                         "arg1"
                         "arg2"
                     ])
-                    if m.currentResult?.isFail = true then m.done() : return invalid
+                    if m.currentResult?.isFail = true then
+                        m.done()
+                        return invalid
+                    end if
                 `);
 
-                let codeText = trimLeading(getContents('code.brs'));
-                expect(codeText).to.equal(trimLeading(`
+                let codeText = getContents('code.brs');
+                expect(codeText).to.equal(undent(`
                 function utils_sayHello(firstName = "", lastName = "")
                     __stubs_globalAa = getGlobalAa()
                     if RBS_SM_1_getMocksByFunctionName()["utils_sayhello"] <> invalid
-                        __stubOrMockResult = RBS_SM_1_getMocksByFunctionName()["utils_sayhello"].callback(firstName,lastName)
+                        __stubOrMockResult = RBS_SM_1_getMocksByFunctionName()["utils_sayhello"].callback(firstName, lastName)
                         return __stubOrMockResult
-                    else if type(__stubs_globalAa?.__globalStubs?.beings_sayHello).endsWith("Function")
-                        __stubFunction = __stubs_globalAa.__globalStubs.beings_sayHello
-                        __stubOrMockResult = __stubFunction()
+                    else if type(__stubs_globalAa?.__globalStubs?.utils_sayhello).endsWith("Function")
+                        __stubFunction = __stubs_globalAa.__globalStubs.utils_sayhello
+                        __stubOrMockResult = __stubFunction(firstName, lastName)
                         return __stubOrMockResult
                     end if
                     print firstName + " " + lastName
@@ -1118,10 +1163,11 @@ describe('RooibosPlugin', () => {
 
                 function RBS_SM_1_getMocksByFunctionName()
                     if m._rMocksByFunctionName = invalid
-                    m._rMocksByFunctionName = {}
+                        m._rMocksByFunctionName = {}
                     end if
                     return m._rMocksByFunctionName
-                end function`));
+                end function
+            `));
             });
         });
 
@@ -1291,22 +1337,28 @@ describe('RooibosPlugin', () => {
                 ).to.eql(undent`
                     m.currentAssertLineNumber = 6
                     m._expectNotCalled(m.thing, "callFunc", m, "m.thing")
-                    if m.currentResult?.isFail = true then m.done() : return invalid
-
-
+                    if m.currentResult?.isFail = true then
+                        m.done()
+                        return invalid
+                    end if
                     m.currentAssertLineNumber = 7
                     m._expectNotCalled(m.thing, "callFunc", m, "m.thing", "return")
-                    if m.currentResult?.isFail = true then m.done() : return invalid
-
-
+                    if m.currentResult?.isFail = true then
+                        m.done()
+                        return invalid
+                    end if
                     m.currentAssertLineNumber = 8
                     m._expectNotCalled(m.thing, "callFunc", m, "m.thing")
-                    if m.currentResult?.isFail = true then m.done() : return invalid
-
-
+                    if m.currentResult?.isFail = true then
+                        m.done()
+                        return invalid
+                    end if
                     m.currentAssertLineNumber = 9
                     m._expectNotCalled(m.thing, "callFunc", m, "m.thing", "return")
-                    if m.currentResult?.isFail = true then m.done() : return invalid
+                    if m.currentResult?.isFail = true then
+                        m.done()
+                        return invalid
+                    end if
                 `);
             });
 
@@ -1331,15 +1383,18 @@ describe('RooibosPlugin', () => {
                     getTestFunctionContents()
                 ).to.eql(undent`
                     thing = {}
-
                     m.currentAssertLineNumber = 7
                     m._expectNotCalled(thing, "callFunc", thing, "thing")
-                    if m.currentResult?.isFail = true then m.done() : return invalid
-
-
+                    if m.currentResult?.isFail = true then
+                        m.done()
+                        return invalid
+                    end if
                     m.currentAssertLineNumber = 8
                     m._expectNotCalled(thing, "callFunc", thing, "thing")
-                    if m.currentResult?.isFail = true then m.done() : return invalid
+                    if m.currentResult?.isFail = true then
+                        m.done()
+                        return invalid
+                    end if
                 `);
                 //verify original code does not remain modified after the transpile cycle
                 const testMethod = ((file.ast.statements[0] as ClassStatement).memberMap['_'] as ClassMethodStatement);
@@ -1376,7 +1431,10 @@ describe('RooibosPlugin', () => {
                 ).to.eql(undent`
                     m.currentAssertLineNumber = 6
                     m._expectNotCalled(m.thing, "getFunctionField", m, "m.thing")
-                    if m.currentResult?.isFail = true then m.done() : return invalid
+                    if m.currentResult?.isFail = true then
+                        m.done()
+                        return invalid
+                    end if
                 `);
                 //verify original code does not remain modified after the transpile cycle
                 const testMethod = ((file.ast.statements[0] as ClassStatement).memberMap['_'] as ClassMethodStatement);
@@ -1410,23 +1468,28 @@ describe('RooibosPlugin', () => {
                 ).to.eql(undent`
                     m.currentAssertLineNumber = 6
                     m._expectNotCalled(m.thing, "getFunction", m, "m.thing")
-                    if m.currentResult?.isFail = true then m.done() : return invalid
-
-
+                    if m.currentResult?.isFail = true then
+                        m.done()
+                        return invalid
+                    end if
                     m.currentAssertLineNumber = 7
                     m._expectNotCalled(m.thing, "getFunction", m, "m.thing", "return")
-                    if m.currentResult?.isFail = true then m.done() : return invalid
-
-
+                    if m.currentResult?.isFail = true then
+                        m.done()
+                        return invalid
+                    end if
                     m.currentAssertLineNumber = 8
                     m._expectNotCalled(m.thing, "getFunction", m, "m.thing")
-                    if m.currentResult?.isFail = true then m.done() : return invalid
-
-
+                    if m.currentResult?.isFail = true then
+                        m.done()
+                        return invalid
+                    end if
                     m.currentAssertLineNumber = 9
                     m._expectNotCalled(m.thing, "getFunction", m, "m.thing", "return")
-                    if m.currentResult?.isFail = true then m.done() : return invalid
-                `);
+                    if m.currentResult?.isFail = true then
+                        m.done()
+                        return invalid
+                    end if                `);
             });
 
             it('correctly transpiles function invocations - simple object', async () => {
@@ -1446,22 +1509,25 @@ describe('RooibosPlugin', () => {
                 expect(program.getDiagnostics()).to.be.empty;
                 expect(plugin.session.sessionInfo.testSuitesToRun).to.not.be.empty;
                 await builder.transpile();
-                const testContents = getTestFunctionContents(true);
+                const testContents = getTestFunctionContents();
                 expect(
                     testContents
                 ).to.eql(undent`
                     item = {
-                    id: "item"
+                        id: "item"
                     }
-
                     m.currentAssertLineNumber = 7
                     m._expectNotCalled(item, "getFunction", item, "item")
-                    if m.currentResult?.isFail = true then m.done() : return invalid
-
-
+                    if m.currentResult?.isFail = true then
+                        m.done()
+                        return invalid
+                    end if
                     m.currentAssertLineNumber = 8
                     m._expectNotCalled(item, "getFunction", item, "item")
-                    if m.currentResult?.isFail = true then m.done() : return invalid
+                    if m.currentResult?.isFail = true then
+                        m.done()
+                        return invalid
+                    end if
                 `);
             });
         });

From bcfd24d91b57bba513bca471ebbd8a7d088001e1 Mon Sep 17 00:00:00 2001
From: Christopher Dwyer-Perkins <chris@inverted-solutions.com>
Date: Tue, 16 Jan 2024 16:28:31 -0400
Subject: [PATCH 20/26] Fixed some indenting

---
 bsc-plugin/src/lib/rooibos/MockUtil.spec.ts | 48 +++++++++++----------
 1 file changed, 25 insertions(+), 23 deletions(-)

diff --git a/bsc-plugin/src/lib/rooibos/MockUtil.spec.ts b/bsc-plugin/src/lib/rooibos/MockUtil.spec.ts
index 8bb250b4..9d2f9de1 100644
--- a/bsc-plugin/src/lib/rooibos/MockUtil.spec.ts
+++ b/bsc-plugin/src/lib/rooibos/MockUtil.spec.ts
@@ -413,37 +413,39 @@ describe('MockUtil', () => {
 
             it('will skip functions with the disableMocking annotation', async () => {
                 program.setFile('source/code.bs', `
-                @disableMocking
-                namespace beings
-                function sayHello()
-                    print "hello2"
-                end function
-                end namespace
-                namespace aliens
-                @disableMocking
-                function sayHello()
-                    print "hello3"
-                end function
-                end namespace
-                @disableMocking
-                function sayHello()
-                    print "hello4"
-                end function
-            `);
+                    @disableMocking
+                    namespace beings
+                    function sayHello()
+                        print "hello2"
+                    end function
+                    end namespace
+                    namespace aliens
+                    @disableMocking
+                    function sayHello()
+                        print "hello3"
+                    end function
+                    end namespace
+                    @disableMocking
+                    function sayHello()
+                        print "hello4"
+                    end function
+                `);
                 program.validate();
                 expect(program.getDiagnostics()).to.be.empty;
                 await builder.transpile();
-                let a = getContents('source/code.brs');
-                let b = trimLeading(`function beings_sayHello()
-                    print "hello2"
+                let a = getContents('source/code.brs', false);
+                let b = undent(`
+                    function beings_sayHello()
+                        print "hello2"
                     end function
                     function aliens_sayHello()
-                    print "hello3"
+                        print "hello3"
                     end function
 
                     function sayHello()
-                    print "hello4"
-                    end function`);
+                        print "hello4"
+                    end function
+                `);
                 expect(a).to.equal(b);
 
             });

From 9f4eabd9a5c36f641afff9b4f503f32ebe5eb975 Mon Sep 17 00:00:00 2001
From: Christopher Dwyer-Perkins <chris@inverted-solutions.com>
Date: Wed, 17 Jan 2024 08:55:15 -0400
Subject: [PATCH 21/26] Fixed node test xml files being added after modifying
 assertions leading to crashes

---
 bsc-plugin/src/plugin.ts | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/bsc-plugin/src/plugin.ts b/bsc-plugin/src/plugin.ts
index d89721df..0f6f2c14 100644
--- a/bsc-plugin/src/plugin.ts
+++ b/bsc-plugin/src/plugin.ts
@@ -154,14 +154,14 @@ export class RooibosPlugin implements CompilerPlugin {
             }
 
             testSuite.addDataFunctions(event.editor as any);
+            if (testSuite.isNodeTest) {
+                this.session.createNodeFile(event.program, testSuite);
+            }
             for (let group of [...testSuite.testGroups.values()].filter((tg) => tg.isIncluded)) {
                 for (let testCase of [...group.testCases.values()].filter((tc) => tc.isIncluded)) {
                     group.modifyAssertions(testCase, noEarlyExit, event.editor as any, this.session.namespaceLookup);
                 }
             }
-            if (testSuite.isNodeTest) {
-                this.session.createNodeFile(event.program, testSuite);
-            }
         }
 
         if (isBrsFile(event.file)) {

From 3acbf16d6041871dfa54e42738293ee56389a114 Mon Sep 17 00:00:00 2001
From: Christopher Dwyer-Perkins <chris@inverted-solutions.com>
Date: Wed, 17 Jan 2024 10:00:31 -0400
Subject: [PATCH 22/26] Updated the modify stub detection logic for globals

---
 bsc-plugin/src/lib/rooibos/TestGroup.ts | 32 ++++++-------------------
 1 file changed, 7 insertions(+), 25 deletions(-)

diff --git a/bsc-plugin/src/lib/rooibos/TestGroup.ts b/bsc-plugin/src/lib/rooibos/TestGroup.ts
index b9f081c1..946212d0 100644
--- a/bsc-plugin/src/lib/rooibos/TestGroup.ts
+++ b/bsc-plugin/src/lib/rooibos/TestGroup.ts
@@ -89,7 +89,7 @@ export class TestGroup extends TestBlock {
                 walkMode: brighterscript.WalkMode.visitStatementsRecursive
             });
         } catch (e) {
-            // console.log(e);
+            console.error(e);
             // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
             diagnosticErrorProcessingFile(this.testSuite.file, e.message);
         }
@@ -201,31 +201,13 @@ export class TestGroup extends TestBlock {
     private shouldNotModifyStubCall(arg0: Expression, namespaceLookup: Map<string, NamespaceContainer>, scope: Scope) {
         if (brighterscript.isDottedGetExpression(arg0)) {
             let nameParts = getAllDottedGetParts(arg0);
-            let name = nameParts.pop();
-            let functionName: string;
-
-            if (name) {
-                //is a namespace?
-                if (nameParts[0] && namespaceLookup.has(nameParts[0].toLowerCase())) {
-                    //then this must be a namespace method
-                    let fullPathName = nameParts.join('.').toLowerCase();
-                    let ns = namespaceLookup.get(fullPathName);
-                    if (!ns) {
-                        //TODO this is an error condition!
-                    }
-                    nameParts.push(name);
-                    functionName = nameParts.join('.').toLowerCase();
-                }
-            }
-
-            if (functionName && scope.getCallableByName(functionName)) {
-                return true;
-            }
+            let functionName = nameParts.join('.');
+            return scope.getCallableByName(functionName);
         } else if (brighterscript.isVariableExpression(arg0)) {
-            const functionName = arg0.getName(ParseMode.BrightScript).toLowerCase();
-            if (scope.getCallableByName(functionName)) {
-                return true;
-            }
+            return (
+                arg0.getSymbolTable().hasSymbol(arg0.getName(ParseMode.BrightScript)) ||
+                scope.getCallableByName(arg0.getName(ParseMode.BrighterScript))
+            );
         }
         return false;
     }

From 1be78f611bfa0fc6b247185dc78d2bc876b1772f Mon Sep 17 00:00:00 2001
From: Christopher Dwyer-Perkins <chris@inverted-solutions.com>
Date: Wed, 17 Jan 2024 10:01:04 -0400
Subject: [PATCH 23/26] more tests for global stub call modifications

---
 bsc-plugin/src/plugin.spec.ts | 311 +++++++++++++++++++++++++++++++++-
 1 file changed, 306 insertions(+), 5 deletions(-)

diff --git a/bsc-plugin/src/plugin.spec.ts b/bsc-plugin/src/plugin.spec.ts
index a248e164..2038e661 100644
--- a/bsc-plugin/src/plugin.spec.ts
+++ b/bsc-plugin/src/plugin.spec.ts
@@ -1311,6 +1311,308 @@ describe('RooibosPlugin', () => {
                     m._stubCall(item, "getFunction", item, "item", "return")
                 `);
             });
+
+            it('correctly transpiles global function and inline anon function param', async () => {
+                program.setFile('source/test.spec.bs', `
+                    @suite
+                    class ATest
+                        @describe("groupA")
+                        @it("stubs global with inline anon with return value")
+                        function _()
+                          getGlobalAA().wasCalled = false
+                          m.stubCall(globalFunctionWithReturn, function()
+                            m.wasCalled = true
+                            return true
+                          end function)
+
+                          m.assertTrue(globalFunctionWithReturn())
+                          m.assertTrue(getGlobalAA().wasCalled)
+                          m.assertRunningTestIsPassed()
+                        end function
+                    end class
+
+                    function globalFunctionWithReturn() as dynamic
+                        m.wasCalled = false
+                        return false
+                    end function
+                `);
+                program.validate();
+                expect(program.getDiagnostics()).to.be.empty;
+                expect(plugin.session.sessionInfo.testSuitesToRun).to.not.be.empty;
+                await builder.transpile();
+                const result = getTestFunctionContents();
+                expect(
+                    result
+                ).to.eql(undent`
+                    getGlobalAA().wasCalled = false
+                    m.stubCall(globalFunctionWithReturn, function()
+                        m.wasCalled = true
+                        return true
+                    end function)
+                    m.currentAssertLineNumber = 12
+                    m.assertTrue(globalFunctionWithReturn())
+                    if m.currentResult?.isFail = true then
+                        m.done()
+                        return invalid
+                    end if
+                    m.currentAssertLineNumber = 13
+                    m.assertTrue(getGlobalAA().wasCalled)
+                    if m.currentResult?.isFail = true then
+                        m.done()
+                        return invalid
+                    end if
+                    m.currentAssertLineNumber = 14
+                    m.assertRunningTestIsPassed()
+                    if m.currentResult?.isFail = true then
+                        m.done()
+                        return invalid
+                    end if
+                `);
+            });
+
+            it('correctly transpiles namespace function and inline anon function param', async () => {
+                program.setFile('source/test.spec.bs', `
+                    @suite
+                    class ATest
+                        @describe("groupA")
+                        @it("stubs global with inline anon with return value")
+                        function _()
+                          getGlobalAA().wasCalled = false
+                          m.stubCall(testNamespace.functionWithReturn, function()
+                            m.wasCalled = true
+                            return true
+                          end function)
+
+                          m.assertTrue(testNamespace.functionWithReturn())
+                          m.assertTrue(getGlobalAA().wasCalled)
+                          m.assertRunningTestIsPassed()
+                        end function
+                    end class
+
+                    namespace testNamespace
+                        function functionWithReturn() as dynamic
+                            m.wasCalled = false
+                            return false
+                        end function
+                    end namespace
+                `);
+                program.validate();
+                expect(program.getDiagnostics()).to.be.empty;
+                expect(plugin.session.sessionInfo.testSuitesToRun).to.not.be.empty;
+                await builder.transpile();
+                const result = getTestFunctionContents();
+                expect(
+                    result
+                ).to.eql(undent`
+                    getGlobalAA().wasCalled = false
+                    m.stubCall(testNamespace_functionWithReturn, function()
+                        m.wasCalled = true
+                        return true
+                    end function)
+                    m.currentAssertLineNumber = 12
+                    m.assertTrue(testNamespace_functionWithReturn())
+                    if m.currentResult?.isFail = true then
+                        m.done()
+                        return invalid
+                    end if
+                    m.currentAssertLineNumber = 13
+                    m.assertTrue(getGlobalAA().wasCalled)
+                    if m.currentResult?.isFail = true then
+                        m.done()
+                        return invalid
+                    end if
+                    m.currentAssertLineNumber = 14
+                    m.assertRunningTestIsPassed()
+                    if m.currentResult?.isFail = true then
+                        m.done()
+                        return invalid
+                    end if
+                `);
+            });
+
+            it('correctly transpiles namespace function and variable anon function param', async () => {
+                program.setFile('source/test.spec.bs', `
+                    @suite
+                    class ATest
+                        @describe("groupA")
+                        @it("stubs global with anon from variable with return value")
+                        function _()
+                          getGlobalAA().wasCalled = false
+                          stub = function()
+                            m.wasCalled = true
+                            return true
+                          end function
+                          m.stubCall(testNamespace.functionWithReturn, stub)
+
+                          m.assertTrue(testNamespace.functionWithReturn())
+                          m.assertTrue(getGlobalAA().wasCalled)
+                          m.assertRunningTestIsPassed()
+                        end function
+                    end class
+
+                    namespace testNamespace
+                        function functionWithReturn() as dynamic
+                            m.wasCalled = false
+                            return false
+                        end function
+                    end namespace
+                `);
+                program.validate();
+                expect(program.getDiagnostics()).to.be.empty;
+                expect(plugin.session.sessionInfo.testSuitesToRun).to.not.be.empty;
+                await builder.transpile();
+                const result = getTestFunctionContents();
+                expect(
+                    result
+                ).to.eql(undent`
+                    getGlobalAA().wasCalled = false
+                    stub = function()
+                        m.wasCalled = true
+                        return true
+                    end function
+                    m.stubCall(testNamespace_functionWithReturn, stub)
+                    m.currentAssertLineNumber = 13
+                    m.assertTrue(testNamespace_functionWithReturn())
+                    if m.currentResult?.isFail = true then
+                        m.done()
+                        return invalid
+                    end if
+                    m.currentAssertLineNumber = 14
+                    m.assertTrue(getGlobalAA().wasCalled)
+                    if m.currentResult?.isFail = true then
+                        m.done()
+                        return invalid
+                    end if
+                    m.currentAssertLineNumber = 15
+                    m.assertRunningTestIsPassed()
+                    if m.currentResult?.isFail = true then
+                        m.done()
+                        return invalid
+                    end if
+                `);
+            });
+
+            it('correctly transpiles namespace function by brightscript name and inline anon function param', async () => {
+                program.setFile('source/test.spec.bs', `
+                    @suite
+                    class ATest
+                        @describe("groupA")
+                        @it("stubs global with inline anon with return value")
+                        function _()
+                          getGlobalAA().wasCalled = false
+                          m.stubCall(testNamespace_functionWithReturn, function()
+                            m.wasCalled = true
+                            return true
+                          end function)
+
+                          m.assertTrue(testNamespace_functionWithReturn())
+                          m.assertTrue(getGlobalAA().wasCalled)
+                          m.assertRunningTestIsPassed()
+                        end function
+                    end class
+
+                    namespace testNamespace
+                        function functionWithReturn() as dynamic
+                            m.wasCalled = false
+                            return false
+                        end function
+                    end namespace
+                `);
+                program.validate();
+                expect(program.getDiagnostics()).to.be.empty;
+                expect(plugin.session.sessionInfo.testSuitesToRun).to.not.be.empty;
+                await builder.transpile();
+                const result = getTestFunctionContents();
+                expect(
+                    result
+                ).to.eql(undent`
+                    getGlobalAA().wasCalled = false
+                    m.stubCall(testNamespace_functionWithReturn, function()
+                        m.wasCalled = true
+                        return true
+                    end function)
+                    m.currentAssertLineNumber = 12
+                    m.assertTrue(testNamespace_functionWithReturn())
+                    if m.currentResult?.isFail = true then
+                        m.done()
+                        return invalid
+                    end if
+                    m.currentAssertLineNumber = 13
+                    m.assertTrue(getGlobalAA().wasCalled)
+                    if m.currentResult?.isFail = true then
+                        m.done()
+                        return invalid
+                    end if
+                    m.currentAssertLineNumber = 14
+                    m.assertRunningTestIsPassed()
+                    if m.currentResult?.isFail = true then
+                        m.done()
+                        return invalid
+                    end if
+                `);
+            });
+
+            it('correctly transpiles namespace function by brightscript name and variable anon function param', async () => {
+                program.setFile('source/test.spec.bs', `
+                    @suite
+                    class ATest
+                        @describe("groupA")
+                        @it("stubs global with anon from variable with return value")
+                        function _()
+                          getGlobalAA().wasCalled = false
+                          stub = function()
+                            m.wasCalled = true
+                            return true
+                          end function
+                          m.stubCall(testNamespace_functionWithReturn, stub)
+
+                          m.assertTrue(testNamespace_functionWithReturn())
+                          m.assertTrue(getGlobalAA().wasCalled)
+                          m.assertRunningTestIsPassed()
+                        end function
+                    end class
+
+                    namespace testNamespace
+                        function functionWithReturn() as dynamic
+                            m.wasCalled = false
+                            return false
+                        end function
+                    end namespace
+                `);
+                program.validate();
+                expect(program.getDiagnostics()).to.be.empty;
+                expect(plugin.session.sessionInfo.testSuitesToRun).to.not.be.empty;
+                await builder.transpile();
+                const result = getTestFunctionContents();
+                expect(
+                    result
+                ).to.eql(undent`
+                    getGlobalAA().wasCalled = false
+                    stub = function()
+                        m.wasCalled = true
+                        return true
+                    end function
+                    m.stubCall(testNamespace_functionWithReturn, stub)
+                    m.currentAssertLineNumber = 13
+                    m.assertTrue(testNamespace_functionWithReturn())
+                    if m.currentResult?.isFail = true then
+                        m.done()
+                        return invalid
+                    end if
+                    m.currentAssertLineNumber = 14
+                    m.assertTrue(getGlobalAA().wasCalled)
+                    if m.currentResult?.isFail = true then
+                        m.done()
+                        return invalid
+                    end if
+                    m.currentAssertLineNumber = 15
+                    m.assertRunningTestIsPassed()
+                    if m.currentResult?.isFail = true then
+                        m.done()
+                        return invalid
+                    end if
+                `);
+            });
         });
 
         describe('expectNotCalled transpilation', () => {
@@ -1909,14 +2211,13 @@ function getContents(filename: string) {
 
 function getTestFunctionContents(trimEveryLine = false) {
     const contents = getContents('test.spec.brs');
-    const [, body] = /\= function\(\)([\S\s]*|.*)(?=end function)/gim.exec(contents);
-    let result = undent(
-        body.split('end function')[0]
-    );
+
+    let [, result] = /instance.[\w_]+\s?\= function\(\)\s?([\S\s]*|.*)(?=^\s*end function\s+instance\.)/img.exec(contents);
+
     if (trimEveryLine) {
         result = trim(result);
     }
-    return result;
+    return undent(result);
 }
 
 function getTestSubContents(trimEveryLine = false) {

From 3164492c7d86860890242053ad47e23f418093b0 Mon Sep 17 00:00:00 2001
From: Christopher Dwyer-Perkins <chris@inverted-solutions.com>
Date: Wed, 17 Jan 2024 12:09:15 -0400
Subject: [PATCH 24/26] Fixed some race conditons and more global function
 detection refinments

---
 bsc-plugin/src/lib/rooibos/MockUtil.ts       | 87 +++++++++-----------
 bsc-plugin/src/lib/rooibos/RooibosSession.ts |  3 +
 bsc-plugin/src/lib/rooibos/TestGroup.ts      |  2 +-
 bsc-plugin/src/plugin.ts                     |  3 -
 4 files changed, 44 insertions(+), 51 deletions(-)

diff --git a/bsc-plugin/src/lib/rooibos/MockUtil.ts b/bsc-plugin/src/lib/rooibos/MockUtil.ts
index 3bd264e7..ac8b9d0b 100644
--- a/bsc-plugin/src/lib/rooibos/MockUtil.ts
+++ b/bsc-plugin/src/lib/rooibos/MockUtil.ts
@@ -1,11 +1,9 @@
 /* eslint-disable @typescript-eslint/no-var-requires */
 /* eslint-disable @typescript-eslint/no-unsafe-assignment */
-import type { BrsFile, Editor, NamespaceContainer, NamespaceStatement, ProgramBuilder } from 'brighterscript';
-import { ParseMode, Parser, Position, WalkMode, createVisitor, isClassStatement, isNamespaceStatement } from 'brighterscript';
+import type { BrsFile, Editor, NamespaceStatement, ProgramBuilder, Scope } from 'brighterscript';
+import { ParseMode, Parser, isClassStatement, isNamespaceStatement } from 'brighterscript';
 import * as brighterscript from 'brighterscript';
 import type { RooibosConfig } from './RooibosConfig';
-import { RawCodeStatement } from './RawCodeStatement';
-import { Range } from 'vscode-languageserver-types';
 import type { FileFactory } from './FileFactory';
 import undent from 'undent';
 import type { RooibosSession } from './RooibosSession';
@@ -26,13 +24,13 @@ export class MockUtil {
     session: RooibosSession;
 
     private brsFileAdditions = `
-    function RBS_SM_#ID#_getMocksByFunctionName()
-        if m._rMocksByFunctionName = invalid
-        m._rMocksByFunctionName = {}
-        end if
-        return m._rMocksByFunctionName
-    end function
-`;
+        function RBS_SM_#ID#_getMocksByFunctionName()
+            if m._rMocksByFunctionName = invalid
+            m._rMocksByFunctionName = {}
+            end if
+            return m._rMocksByFunctionName
+        end function
+    `;
 
     private config: RooibosConfig;
     private fileId: number;
@@ -149,13 +147,13 @@ export class MockUtil {
                         let assertRegex = /(?:fail|assert(?:[a-z0-9]*)|expect(?:[a-z0-9]*)|stubCall)/i;
                         if (dge && assertRegex.test(dge.name.text)) {
                             if (dge.name.text === 'stubCall') {
-                                this.processGlobalStubbedMethod(callExpression);
+                                this.processGlobalStubbedMethod(callExpression, testSuite);
                                 return expressionStatement;
 
                             } else {
 
                                 if (dge.name.text === 'expectCalled' || dge.name.text === 'expectNotCalled') {
-                                    this.processGlobalStubbedMethod(callExpression);
+                                    this.processGlobalStubbedMethod(callExpression, testSuite);
                                 }
                             }
                         }
@@ -171,9 +169,10 @@ export class MockUtil {
         }
     }
 
-    private processGlobalStubbedMethod(callExpression: brighterscript.CallExpression) {
+    private processGlobalStubbedMethod(callExpression: brighterscript.CallExpression, testSuite: TestSuite) {
         let isNotCalled = false;
         let isStubCall = false;
+        const scope = testSuite.file.program.getFirstScopeForFile(testSuite.file);
         const namespaceLookup = this.session.namespaceLookup;
         if (brighterscript.isDottedGetExpression(callExpression.callee)) {
             const nameText = callExpression.callee.name.text;
@@ -183,51 +182,45 @@ export class MockUtil {
         //modify args
         let arg0 = callExpression.args[0];
         let arg1 = callExpression.args[1];
-        if (isStubCall) {
-            if (!brighterscript.isCallExpression(arg0)) {
-                if (brighterscript.isDottedGetExpression(arg0)) {
-                    const functionName = this.getFinalNamespaceFunctionNameFromDottedGet(arg0, namespaceLookup);
 
-                    if (functionName) {
-                        this.session.globalStubbedMethods.add(functionName);
-                    }
-                } else if (brighterscript.isVariableExpression(arg0)) {
-                    const functionName = arg0.getName(ParseMode.BrightScript).toLowerCase();
-                    this.session.globalStubbedMethods.add(functionName);
-                }
+        if (isStubCall) {
+            let functionName = this.getGlobalFunctionName(arg0, scope);
+            if (functionName) {
+                this.session.globalStubbedMethods.add(functionName.toLowerCase());
+                return;
             }
         }
 
-        if (brighterscript.isCallExpression(arg0) && brighterscript.isDottedGetExpression(arg0.callee)) {
-            const functionName = this.getFinalNamespaceFunctionNameFromDottedGet(arg0.callee, namespaceLookup);
-
+        if (brighterscript.isCallExpression(arg0)) {
+            let functionName = this.getGlobalFunctionName(arg0.callee, scope);
             if (functionName) {
-                this.session.globalStubbedMethods.add(functionName);
+                this.session.globalStubbedMethods.add(functionName.toLowerCase());
             }
-        } else if (brighterscript.isCallExpression(arg0) && brighterscript.isVariableExpression(arg0.callee)) {
-            let functionName = arg0.callee.getName(brighterscript.ParseMode.BrightScript).toLowerCase();
-            this.session.globalStubbedMethods.add(functionName);
         }
     }
 
 
-    private getFinalNamespaceFunctionNameFromDottedGet(dg: brighterscript.DottedGetExpression, namespaceLookup: Map<string, NamespaceContainer>) {
-        //is it a namespace?
-        let nameParts = getAllDottedGetParts(dg);
-        let name = nameParts.pop();
+    private getGlobalFunctionName(expression: brighterscript.Expression, scope: Scope) {
+        let result: string;
+        if (brighterscript.isDottedGetExpression(expression)) {
+            let nameParts = getAllDottedGetParts(expression);
+            let functionName = nameParts.join('.');
+            let callable = scope.getCallableByName(functionName);
+            if (callable) {
+                result = callable.getName(ParseMode.BrightScript);
+            }
+        } else if (brighterscript.isVariableExpression(expression)) {
+            let functionName = expression.getName(ParseMode.BrightScript);
+            if (scope.symbolTable.hasSymbol(functionName)) {
+                result = functionName;
+            }
 
-        if (name) {
-            //is a namespace?
-            if (nameParts[0] && namespaceLookup.has(nameParts[0].toLowerCase())) {
-                //then this must be a namespace method
-                let fullPathName = nameParts.join('.').toLowerCase();
-                let ns = namespaceLookup.get(fullPathName);
-                if (!ns) {
-                    //TODO this is an error condition!
-                }
-                nameParts.push(name);
-                return nameParts.join('_').toLowerCase();
+            functionName = expression.getName(ParseMode.BrighterScript);
+            if (scope.getCallableByName(functionName)) {
+                result = functionName;
             }
         }
+
+        return result;
     }
 }
diff --git a/bsc-plugin/src/lib/rooibos/RooibosSession.ts b/bsc-plugin/src/lib/rooibos/RooibosSession.ts
index 70c9f73e..4410d7f5 100644
--- a/bsc-plugin/src/lib/rooibos/RooibosSession.ts
+++ b/bsc-plugin/src/lib/rooibos/RooibosSession.ts
@@ -46,6 +46,9 @@ export class RooibosSession {
             console.log('Efficient global stubbing is enabled');
             this.namespaceLookup = this.getNamespaces(program);
             for (let testSuite of this.sessionInfo.testSuitesToRun) {
+                if (testSuite.isNodeTest) {
+                    this.createNodeFile(program, testSuite);
+                }
                 mockUtil.gatherGlobalMethodMocks(testSuite);
             }
 
diff --git a/bsc-plugin/src/lib/rooibos/TestGroup.ts b/bsc-plugin/src/lib/rooibos/TestGroup.ts
index 946212d0..cf83f282 100644
--- a/bsc-plugin/src/lib/rooibos/TestGroup.ts
+++ b/bsc-plugin/src/lib/rooibos/TestGroup.ts
@@ -205,7 +205,7 @@ export class TestGroup extends TestBlock {
             return scope.getCallableByName(functionName);
         } else if (brighterscript.isVariableExpression(arg0)) {
             return (
-                arg0.getSymbolTable().hasSymbol(arg0.getName(ParseMode.BrightScript)) ||
+                scope.symbolTable.hasSymbol(arg0.getName(ParseMode.BrightScript)) ||
                 scope.getCallableByName(arg0.getName(ParseMode.BrighterScript))
             );
         }
diff --git a/bsc-plugin/src/plugin.ts b/bsc-plugin/src/plugin.ts
index 0f6f2c14..fc529240 100644
--- a/bsc-plugin/src/plugin.ts
+++ b/bsc-plugin/src/plugin.ts
@@ -154,9 +154,6 @@ export class RooibosPlugin implements CompilerPlugin {
             }
 
             testSuite.addDataFunctions(event.editor as any);
-            if (testSuite.isNodeTest) {
-                this.session.createNodeFile(event.program, testSuite);
-            }
             for (let group of [...testSuite.testGroups.values()].filter((tg) => tg.isIncluded)) {
                 for (let testCase of [...group.testCases.values()].filter((tc) => tc.isIncluded)) {
                     group.modifyAssertions(testCase, noEarlyExit, event.editor as any, this.session.namespaceLookup);

From 4132da2e40c5fbcbddcb3ad5eca66cb216c7a72a Mon Sep 17 00:00:00 2001
From: Christopher Dwyer-Perkins <chris@inverted-solutions.com>
Date: Fri, 19 Jan 2024 16:49:51 -0400
Subject: [PATCH 25/26] Fixed some issues picking the wrong scope and make sure
 stubcall worked with async tests

---
 bsc-plugin/src/lib/rooibos/MockUtil.ts  |  4 +-
 bsc-plugin/src/lib/rooibos/TestGroup.ts |  3 +-
 bsc-plugin/src/lib/rooibos/Utils.ts     | 14 +++++-
 bsc-plugin/src/plugin.ts                |  4 +-
 framework/src/source/BaseTestSuite.bs   |  4 +-
 tests/src/components/NodeExample.bs     | 31 +++++++++++--
 tests/src/source/Async.spec.bs          | 58 +++++++++++++++++++++++++
 7 files changed, 107 insertions(+), 11 deletions(-)
 create mode 100644 tests/src/source/Async.spec.bs

diff --git a/bsc-plugin/src/lib/rooibos/MockUtil.ts b/bsc-plugin/src/lib/rooibos/MockUtil.ts
index ac8b9d0b..fa66034a 100644
--- a/bsc-plugin/src/lib/rooibos/MockUtil.ts
+++ b/bsc-plugin/src/lib/rooibos/MockUtil.ts
@@ -10,7 +10,7 @@ import type { RooibosSession } from './RooibosSession';
 import { diagnosticErrorProcessingFile } from '../utils/Diagnostics';
 import type { TestCase } from './TestCase';
 import type { TestSuite } from './TestSuite';
-import { functionRequiresReturnValue, getAllDottedGetParts } from './Utils';
+import { functionRequiresReturnValue, getAllDottedGetParts, getScopeForSuite } from './Utils';
 
 export class MockUtil {
 
@@ -172,7 +172,7 @@ export class MockUtil {
     private processGlobalStubbedMethod(callExpression: brighterscript.CallExpression, testSuite: TestSuite) {
         let isNotCalled = false;
         let isStubCall = false;
-        const scope = testSuite.file.program.getFirstScopeForFile(testSuite.file);
+        const scope = getScopeForSuite(testSuite);
         const namespaceLookup = this.session.namespaceLookup;
         if (brighterscript.isDottedGetExpression(callExpression.callee)) {
             const nameText = callExpression.callee.name.text;
diff --git a/bsc-plugin/src/lib/rooibos/TestGroup.ts b/bsc-plugin/src/lib/rooibos/TestGroup.ts
index cf83f282..71df26f5 100644
--- a/bsc-plugin/src/lib/rooibos/TestGroup.ts
+++ b/bsc-plugin/src/lib/rooibos/TestGroup.ts
@@ -45,12 +45,11 @@ export class TestGroup extends TestBlock {
         return [...this.testCases.values()];
     }
 
-    public modifyAssertions(testCase: TestCase, noEarlyExit: boolean, editor: AstEditor, namespaceLookup: Map<string, NamespaceContainer>) {
+    public modifyAssertions(testCase: TestCase, noEarlyExit: boolean, editor: AstEditor, namespaceLookup: Map<string, NamespaceContainer>, scope: Scope) {
         //for each method
         //if assertion
         //wrap with if is not fail
         //add line number as last param
-        let scope = this.file.program.getFirstScopeForFile(this.file);
         const transpileState = new BrsTranspileState(this.file);
         try {
             let func = this.testSuite.classStatement.methods.find((m) => m.name.text.toLowerCase() === testCase.funcName.toLowerCase());
diff --git a/bsc-plugin/src/lib/rooibos/Utils.ts b/bsc-plugin/src/lib/rooibos/Utils.ts
index df13beae..39bd00a8 100644
--- a/bsc-plugin/src/lib/rooibos/Utils.ts
+++ b/bsc-plugin/src/lib/rooibos/Utils.ts
@@ -1,7 +1,8 @@
 import type { AnnotationExpression, AstEditor, BrsFile, ClassStatement, Expression, FunctionStatement } from 'brighterscript';
-import { TokenKind } from 'brighterscript';
+import { TokenKind, isXmlScope } from 'brighterscript';
 import * as brighterscript from 'brighterscript';
 import { diagnosticCorruptTestProduced } from '../utils/Diagnostics';
+import type { TestSuite } from './TestSuite';
 
 export function addOverriddenMethod(file: BrsFile, annotation: AnnotationExpression, target: ClassStatement, name: string, source: string, editor: AstEditor): boolean {
     let functionSource = `
@@ -103,3 +104,14 @@ export function getPathValuePartAsString(expr: Expression) {
         }
     }
 }
+
+export function getScopeForSuite(testSuite: TestSuite) {
+    if (testSuite.isNodeTest) {
+        return testSuite.file.program.getScopesForFile(testSuite.file).find((scope)=> {
+            return isXmlScope(scope) && scope.xmlFile.componentName.text === testSuite.generatedNodeName;
+        });
+
+    } else {
+        return testSuite.file.program.getFirstScopeForFile(testSuite.file);
+    }
+}
diff --git a/bsc-plugin/src/plugin.ts b/bsc-plugin/src/plugin.ts
index fc529240..74dfbc77 100644
--- a/bsc-plugin/src/plugin.ts
+++ b/bsc-plugin/src/plugin.ts
@@ -22,6 +22,7 @@ import { FileFactory } from './lib/rooibos/FileFactory';
 import type { RooibosConfig } from './lib/rooibos/RooibosConfig';
 import * as minimatch from 'minimatch';
 import { MockUtil } from './lib/rooibos/MockUtil';
+import { getScopeForSuite } from './lib/rooibos/Utils';
 
 export class RooibosPlugin implements CompilerPlugin {
 
@@ -148,6 +149,7 @@ export class RooibosPlugin implements CompilerPlugin {
     beforeFileTranspile(event: BeforeFileTranspileEvent) {
         let testSuite = this.session.sessionInfo.testSuitesToRun.find((ts) => ts.file.pkgPath === event.file.pkgPath);
         if (testSuite) {
+            const scope = getScopeForSuite(testSuite);
             let noEarlyExit = testSuite.annotation.noEarlyExit;
             if (noEarlyExit) {
                 console.warn(`WARNING: testSuite "${testSuite.name}" is marked as noEarlyExit`);
@@ -156,7 +158,7 @@ export class RooibosPlugin implements CompilerPlugin {
             testSuite.addDataFunctions(event.editor as any);
             for (let group of [...testSuite.testGroups.values()].filter((tg) => tg.isIncluded)) {
                 for (let testCase of [...group.testCases.values()].filter((tc) => tc.isIncluded)) {
-                    group.modifyAssertions(testCase, noEarlyExit, event.editor as any, this.session.namespaceLookup);
+                    group.modifyAssertions(testCase, noEarlyExit, event.editor as any, this.session.namespaceLookup, scope);
                 }
             }
         }
diff --git a/framework/src/source/BaseTestSuite.bs b/framework/src/source/BaseTestSuite.bs
index 170a5712..678e792c 100644
--- a/framework/src/source/BaseTestSuite.bs
+++ b/framework/src/source/BaseTestSuite.bs
@@ -267,7 +267,7 @@ namespace rooibos
       if m.catchCrashes and not test.noCatch and not m.noCatch
         try
           test.run()
-          if m.isAutoAssertingMocks = true
+          if m.isAutoAssertingMocks = true and not test.isAsync
             m.AssertMocks()
             m.CleanMocks()
             m.CleanStubs()
@@ -278,7 +278,7 @@ namespace rooibos
         end try
       else
         test.run()
-        if m.isAutoAssertingMocks = true
+        if m.isAutoAssertingMocks = true and not test.isAsync
           m.AssertMocks()
           m.CleanMocks()
           m.CleanStubs()
diff --git a/tests/src/components/NodeExample.bs b/tests/src/components/NodeExample.bs
index 7be0042b..2ab37287 100644
--- a/tests/src/components/NodeExample.bs
+++ b/tests/src/components/NodeExample.bs
@@ -1,3 +1,5 @@
+import "pkg:/source/Main.bs"
+
 function Init() as void
     m.nameText = m.top.findNode("nameText")
 end function
@@ -7,10 +9,10 @@ function HelloFromNode(name, age) as string
 end function
 
 function UpdateState(newState) as void
-    m.top.state = newState 
+    m.top.state = newState
 end function
 
-function SetLabelText(newText) as void
+function SetLabelText(newText = "") as void
     m.nameText.text = newText
 end function
 
@@ -28,10 +30,33 @@ function r_real_HelloFromNode(name, age) as string
 end function
 
 function r_real_UpdateState(newState) as void
-  m.top.state = newState 
+  m.top.state = newState
 end function
 
 function r_real_SetLabelText(newText) as void
   m.nameText.text = newText
 end function
 
+function delayCall(delay, callback) as void
+    timer = createObject("roSgNode", "Timer")
+    timer.update({
+        duration: delay
+        repeat: false
+        id: createObject("roDeviceInfo").getRandomUUID()
+    })
+
+    m[timer.id] = {
+        timer: timer
+        callback: callback
+    }
+
+    onDelayedFired = sub(event)
+        id = event.getNode()
+        callback  = m[id].callback
+        callback()
+        m.delete(id)
+    end sub
+
+    timer.observeFieldScoped("fire", onDelayedFired.toStr().tokenize(" ").peek())
+    timer.control = "start"
+end function
diff --git a/tests/src/source/Async.spec.bs b/tests/src/source/Async.spec.bs
new file mode 100644
index 00000000..89972b78
--- /dev/null
+++ b/tests/src/source/Async.spec.bs
@@ -0,0 +1,58 @@
+namespace tests
+  @async(1000)
+  @SGNode("NodeExample")
+  @only
+  @suite
+  class AsyncNodeTests extends rooibos.BaseTestSuite
+
+    '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+    @describe("NodeExample tests")
+    '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+    @beforeEach
+    function beforeEachGroup()
+      m.cleanMocks()
+    end function
+
+    @async
+    @it("normal async test")
+    function _()
+      callbackFunction = sub()
+        m.wasCalled = true
+        m.testSuite.assertTrue(true)
+        m.testSuite.done()
+        ? "done one"
+      end sub
+
+      delayCall(0.02, callbackFunction)
+    end function
+
+    @async
+    @it("async with stub call for source function")
+    function _()
+      getGlobalAA().wasCalled = false
+      m.stubCall(globalFunctionWithoutReturn, function()
+        m.wasCalled = true
+        m.testSuite.assertTrue(true)
+        m.testSuite.done()
+        ? "done two"
+      end function)
+
+      delayCall(0.02, globalFunctionWithoutReturn)
+    end function
+
+    @async
+    @it("async with stub call for node function")
+    function _()
+      m.stubCall(SetLabelText, function(text)
+        m.wasCalled = true
+        m.testSuite.assertEqual(text, "")
+        m.testSuite.assertTrue(true)
+        m.testSuite.done()
+        ? "done three"
+      end function)
+
+      delayCall(0.02, SetLabelText)
+    end function
+  end class
+end namespace

From f435bba028a548f887c6e7627559317760b0c031 Mon Sep 17 00:00:00 2001
From: Christopher Dwyer-Perkins <chris@inverted-solutions.com>
Date: Sat, 20 Jan 2024 13:19:25 -0400
Subject: [PATCH 26/26] Moved global stub clearing to clearStubs()

---
 framework/src/source/BaseTestSuite.bs    | 8 ++++----
 tests/src/source/Async.spec.bs           | 3 +--
 tests/src/source/NewExpectSyntax.spec.bs | 4 ++--
 3 files changed, 7 insertions(+), 8 deletions(-)

diff --git a/framework/src/source/BaseTestSuite.bs b/framework/src/source/BaseTestSuite.bs
index 678e792c..e85771cc 100644
--- a/framework/src/source/BaseTestSuite.bs
+++ b/framework/src/source/BaseTestSuite.bs
@@ -2268,10 +2268,6 @@ namespace rooibos
     '  * @description Cleans up all tracking data associated with mocks
     '  */
     function cleanMocks() as void
-      ' Clean up the global functions mocks as well
-      globalAa = getGlobalAa()
-      globalAa.__globalStubs = invalid
-
       if m.mocks = invalid
         return
       end if
@@ -2292,6 +2288,10 @@ namespace rooibos
     '  * @description Cleans up all tracking data associated with stubs
     '  */
     function cleanStubs() as void
+      ' Clean up the global functions mocks as well
+      globalAa = getGlobalAa()
+      globalAa.__globalStubs = invalid
+
       if m.stubs = invalid
         return
       end if
diff --git a/tests/src/source/Async.spec.bs b/tests/src/source/Async.spec.bs
index 89972b78..49e78337 100644
--- a/tests/src/source/Async.spec.bs
+++ b/tests/src/source/Async.spec.bs
@@ -1,7 +1,6 @@
 namespace tests
   @async(1000)
   @SGNode("NodeExample")
-  @only
   @suite
   class AsyncNodeTests extends rooibos.BaseTestSuite
 
@@ -11,7 +10,7 @@ namespace tests
 
     @beforeEach
     function beforeEachGroup()
-      m.cleanMocks()
+      m.cleanStubs()
     end function
 
     @async
diff --git a/tests/src/source/NewExpectSyntax.spec.bs b/tests/src/source/NewExpectSyntax.spec.bs
index 1dc3aca4..efd6956b 100644
--- a/tests/src/source/NewExpectSyntax.spec.bs
+++ b/tests/src/source/NewExpectSyntax.spec.bs
@@ -327,7 +327,7 @@ namespace tests
       m.assertInvalid(testNamespace.functionWithoutReturn())
       m.assertTrue(getGlobalAA().wasCalled)
 
-      m.cleanMocks()
+      m.cleanStubs()
 
       m.assertInvalid(testNamespace.functionWithoutReturn())
       m.assertFalse(getGlobalAA().wasCalled)
@@ -345,7 +345,7 @@ namespace tests
       m.assertInvalid(globalFunctionWithoutReturn())
       m.assertTrue(getGlobalAA().wasCalled)
 
-      m.cleanMocks()
+      m.cleanStubs()
 
       m.assertInvalid(globalFunctionWithoutReturn())
       m.assertFalse(getGlobalAA().wasCalled)