Repository: rokucommunity/brighterscript
Pull request #638 (2022-07-13 19:30:21)

No top level statements
Add diagnostics for unsupported statements that are declared at the root of the file or within a namespace body. Like this:

![image](https://user-images.githubusercontent.com/2544493/178816031-1053d04a-c0a9-4b7e-97a5-6a603cf40b6c.png)



Patch:
diff --git a/src/DiagnosticMessages.ts b/src/DiagnosticMessages.ts
index b9d87b22..ecd65306 100644
--- a/src/DiagnosticMessages.ts
+++ b/src/DiagnosticMessages.ts
@@ -683,6 +683,11 @@ export let DiagnosticMessages = {
         message: `Circular reference detected between ${Array.isArray(items) ? items.join(' -> ') : ''} in scope '${scopeName}'`,
         code: 1132,
         severity: DiagnosticSeverity.Error
+    }),
+    unexpectedStatementOutsideFunction: () => ({
+        message: `Unexpected statement found outside of function body`,
+        code: 1133,
+        severity: DiagnosticSeverity.Error
     })
 };
 
diff --git a/src/Program.spec.ts b/src/Program.spec.ts
index 682a91ed..b1b1407a 100644
--- a/src/Program.spec.ts
+++ b/src/Program.spec.ts
@@ -75,6 +75,22 @@ describe('Program', () => {
             //if the program didn't get stuck in an infinite loop, this test passes
         });
 
+        it('flags unsupported statements at root of file', () => {
+            program.setFile('source/main.brs', `
+                result = true
+                print true
+                createObject("roSGNode", "Rectangle")
+            `);
+            program.validate();
+            expectDiagnostics(program, [{
+                ...DiagnosticMessages.unexpectedStatementOutsideFunction()
+            }, {
+                ...DiagnosticMessages.unexpectedStatementOutsideFunction()
+            }, {
+                ...DiagnosticMessages.unexpectedStatementOutsideFunction()
+            }]);
+        });
+
         it('only parses xml files as components when file is found within the "components" folder', () => {
             expect(Object.keys(program.files).length).to.equal(0);
 
diff --git a/src/bscPlugin/validation/BrsFileValidator.ts b/src/bscPlugin/validation/BrsFileValidator.ts
index 6f4f9d09..d83a9b27 100644
--- a/src/bscPlugin/validation/BrsFileValidator.ts
+++ b/src/bscPlugin/validation/BrsFileValidator.ts
@@ -1,4 +1,4 @@
-import { isLiteralExpression } from '../..';
+import { isClassStatement, isCommentStatement, isEnumStatement, isFunctionStatement, isImportStatement, isInterfaceStatement, isLibraryStatement, isLiteralExpression, isNamespaceStatement } from '../../astUtils/reflection';
 import { DiagnosticMessages } from '../../DiagnosticMessages';
 import type { BrsFile } from '../../files/BrsFile';
 import type { BsDiagnostic, OnFileValidateEvent } from '../../interfaces';
@@ -14,9 +14,10 @@ export class BrsFileValidator {
 
     public process() {
         this.validateEnumDeclarations();
+        this.flagTopLevelStatements();
     }
 
-    public validateEnumDeclarations() {
+    private validateEnumDeclarations() {
         const diagnostics = [] as BsDiagnostic[];
         for (const stmt of this.event.file.parser.references.enumStatements) {
             const members = stmt.getMembers();
@@ -93,4 +94,33 @@ export class BrsFileValidator {
             }
         }
     }
+
+    /**
+     * Find statements defined at the top level (or inside a namespace body) that are not allowed to be there
+     */
+    private flagTopLevelStatements() {
+        const statements = [...this.event.file.ast.statements];
+        while (statements.length > 0) {
+            const statement = statements.pop();
+            if (isNamespaceStatement(statement)) {
+                statements.push(...statement.body.statements);
+            } else {
+                //only allow these statement types
+                if (
+                    !isFunctionStatement(statement) &&
+                    !isClassStatement(statement) &&
+                    !isEnumStatement(statement) &&
+                    !isInterfaceStatement(statement) &&
+                    !isCommentStatement(statement) &&
+                    !isLibraryStatement(statement) &&
+                    !isImportStatement(statement)
+                ) {
+                    this.event.file.addDiagnostic({
+                        ...DiagnosticMessages.unexpectedStatementOutsideFunction(),
+                        range: statement.range
+                    });
+                }
+            }
+        }
+    }
 }
diff --git a/src/files/BrsFile.spec.ts b/src/files/BrsFile.spec.ts
index 637a7299..720ae9d0 100644
--- a/src/files/BrsFile.spec.ts
+++ b/src/files/BrsFile.spec.ts
@@ -2019,15 +2019,17 @@ describe('BrsFile', () => {
 
         it('transpiles if statement keywords as provided', () => {
             const code = `
-                If True Then
-                    Print True
-                Else If True Then
-                    print True
-                Else If False Then
-                    Print False
-                Else
-                    Print False
-                End If
+                sub main()
+                    If True Then
+                        Print True
+                    Else If True Then
+                        print True
+                    Else If False Then
+                        Print False
+                    Else
+                        Print False
+                    End If
+                end sub
             `;
             testTranspile(code);
             testTranspile(code.toLowerCase());
@@ -2035,36 +2037,41 @@ describe('BrsFile', () => {
         });
 
         it('does not transpile `then` tokens', () => {
-            const code = `
-                if true
-                    print true
-                else if true
-                    print false
-                end if
-            `;
-            testTranspile(code);
+            testTranspile(`
+                sub main()
+                    if true
+                        print true
+                    else if true
+                        print false
+                    end if
+                end sub
+            `);
         });
 
         it('honors spacing between multi-word tokens', () => {
             testTranspile(`
-                if true
-                    print true
-                elseif true
-                    print false
-                endif
+                sub main()
+                    if true
+                        print true
+                    elseif true
+                        print false
+                    endif
+                end sub
             `);
         });
 
         it('handles when only some of the statements have `then`', () => {
             testTranspile(`
-                if true
-                else if true then
-                else if true
-                else if true then
-                    if true then
-                        return true
+                sub main()
+                    if true
+                    else if true then
+                    else if true
+                    else if true then
+                        if true then
+                            return true
+                        end if
                     end if
-                end if
+                end sub
             `);
         });
 
@@ -2467,51 +2474,57 @@ describe('BrsFile', () => {
 
         it('handles empty if block', () => {
             testTranspile(`
-                if true then
-                end if
-                if true then
-                else
-                    print "else"
-                end if
-                if true then
-                else if true then
-                    print "else"
-                end if
-                if true then
-                else if true then
-                    print "elseif"
-                else
-                    print "else"
-                end if
+                sub main()
+                    if true then
+                    end if
+                    if true then
+                    else
+                        print "else"
+                    end if
+                    if true then
+                    else if true then
+                        print "else"
+                    end if
+                    if true then
+                    else if true then
+                        print "elseif"
+                    else
+                        print "else"
+                    end if
+                end sub
             `);
         });
 
         it('handles empty elseif block', () => {
             testTranspile(`
-                if true then
-                    print "if"
-                else if true then
-                end if
-                if true then
-                    print "if"
-                else if true then
-                else if true then
-                end if
+                sub main()
+                    if true then
+                        print "if"
+                    else if true then
+                    end if
+                    if true then
+                        print "if"
+                    else if true then
+                    else if true then
+                    end if
+                end sub
             `);
         });
 
         it('handles empty else block', () => {
             testTranspile(`
-                if true then
-                    print "if"
-                else
-                end if
-                if true then
-                    print "if"
-                else if true then
-                    print "elseif"
-                else
-                end if
+                sub main()
+                    if true then
+                        print "if"
+                    else
+                    end if
+                    if true then
+                        print "if"
+                    else if true then
+                        print "elseif"
+                    else
+                    end if
+                end sub
             `);
         });
 
diff --git a/src/files/BrsFile.ts b/src/files/BrsFile.ts
index d8142b4e..48a6263d 100644
--- a/src/files/BrsFile.ts
+++ b/src/files/BrsFile.ts
@@ -1,13 +1,13 @@
 import type { CodeWithSourceMap } from 'source-map';
 import { SourceNode } from 'source-map';
-import type { CompletionItem, Hover, Position, Location } from 'vscode-languageserver';
+import type { CompletionItem, Hover, Position, Location, Diagnostic } from 'vscode-languageserver';
 import { CompletionItemKind, SymbolKind, SignatureInformation, ParameterInformation, DocumentSymbol, SymbolInformation, TextEdit } from 'vscode-languageserver';
 import chalk from 'chalk';
 import * as path from 'path';
 import type { Scope } from '../Scope';
 import { DiagnosticCodeMap, diagnosticCodes, DiagnosticMessages } from '../DiagnosticMessages';
 import { FunctionScope } from '../FunctionScope';
-import type { Callable, CallableArg, CallableParam, CommentFlag, FunctionCall, BsDiagnostic, FileReference, FileLink } from '../interfaces';
+import type { Callable, CallableArg, CallableParam, CommentFlag, FunctionCall, BsDiagnostic, FileReference, FileLink, BscFile } from '../interfaces';
 import type { Token } from '../lexer/Token';
 import { Lexer } from '../lexer/Lexer';
 import { TokenKind, AllowedLocalIdentifiers, Keywords } from '../lexer/TokenKind';
@@ -101,6 +101,13 @@ export class BrsFile {
         return [...this.diagnostics];
     }
 
+    public addDiagnostic(diagnostic: Diagnostic & { file?: BscFile }) {
+        if (!diagnostic.file) {
+            diagnostic.file = this;
+        }
+        this.diagnostics.push(diagnostic as any);
+    }
+
     public addDiagnostics(diagnostics: BsDiagnostic[]) {
         this.diagnostics.push(...diagnostics);
     }


Test patch:
diff --git a/src/files/tests/optionalChaning.spec.ts b/src/files/tests/optionalChaning.spec.ts
index eb66538c..b788d847 100644
--- a/src/files/tests/optionalChaning.spec.ts
+++ b/src/files/tests/optionalChaning.spec.ts
@@ -73,24 +73,26 @@ describe('optional chaining', () => {
 
     it('transpiles various use cases', () => {
         testTranspile(`
-            obj = {}
-            arr = []
-            print arr?.["0"]
-            print arr?.value
-            print obj?.[0]
-            print obj?.getName()?.first?.second
-            print createObject("roByteArray")?.value
-            print createObject("roByteArray")?["0"]
-            print createObject("roList")?.value
-            print createObject("roList")?["0"]
-            print createObject("roXmlList")?["0"]
-            print createObject("roDateTime")?.value
-            print createObject("roDateTime")?.GetTimeZoneOffset
-            print createObject("roSGNode", "Node")?[0]
-            print obj?.first?.second
-            print obj?.first?.second
-            print obj.b.xmlThing?@someAttr
-            print obj.b.localFunc?()
+            sub main()
+                obj = {}
+                arr = []
+                print arr?.["0"]
+                print arr?.value
+                print obj?.[0]
+                print obj?.getName()?.first?.second
+                print createObject("roByteArray")?.value
+                print createObject("roByteArray")?["0"]
+                print createObject("roList")?.value
+                print createObject("roList")?["0"]
+                print createObject("roXmlList")?["0"]
+                print createObject("roDateTime")?.value
+                print createObject("roDateTime")?.GetTimeZoneOffset
+                print createObject("roSGNode", "Node")?[0]
+                print obj?.first?.second
+                print obj?.first?.second
+                print obj.b.xmlThing?@someAttr
+                print obj.b.localFunc?()
+            end sub
         `);
     });
 });
diff --git a/src/parser/tests/expression/NullCoalescenceExpression.spec.ts b/src/parser/tests/expression/NullCoalescenceExpression.spec.ts
index 94872ca2..fafc0668 100644
--- a/src/parser/tests/expression/NullCoalescenceExpression.spec.ts
+++ b/src/parser/tests/expression/NullCoalescenceExpression.spec.ts
@@ -191,14 +191,29 @@ describe('NullCoalescingExpression', () => {
 
         it('uses the proper prefix when aliased package is installed', () => {
             program.setFile('source/roku_modules/rokucommunity_bslib/bslib.brs', '');
-            testTranspile(
-                'a = user ?? false',
-                `a = rokucommunity_bslib_coalesce(user, false)`
-            );
+            testTranspile(`
+                sub main()
+                    a = user ?? false
+                end sub
+            `, `
+                sub main()
+                    a = rokucommunity_bslib_coalesce(user, false)
+                end sub
+            `);
         });
 
         it('properly transpiles null coalesence assignments - simple', () => {
-            testTranspile(`a = user ?? {"id": "default"}`, 'a = bslib_coalesce(user, {\n    "id": "default"\n})', 'none');
+            testTranspile(`
+                sub main()
+                    a = user ?? {"id": "default"}
+                end sub
+            `, `
+                sub main()
+                    a = bslib_coalesce(user, {
+                        "id": "default"
+                    })
+                end sub
+            `);
         });
 
         it('properly transpiles null coalesence assignments - complex consequent', () => {
@@ -225,15 +240,21 @@ describe('NullCoalescingExpression', () => {
         });
 
         it('transpiles null coalesence assignment for variable alternate- complex consequent', () => {
-            testTranspile(`a = obj.link ?? false`, `
-                a = (function(obj)
-                        __bsConsequent = obj.link
-                        if __bsConsequent <> invalid then
-                            return __bsConsequent
-                        else
-                            return false
-                        end if
-                    end function)(obj)
+            testTranspile(`
+                sub main()
+                    a = obj.link ?? false
+                end sub
+            `, `
+                sub main()
+                    a = (function(obj)
+                            __bsConsequent = obj.link
+                            if __bsConsequent <> invalid then
+                                return __bsConsequent
+                            else
+                                return false
+                            end if
+                        end function)(obj)
+                end sub
             `);
         });
 
diff --git a/src/parser/tests/expression/TemplateStringExpression.spec.ts b/src/parser/tests/expression/TemplateStringExpression.spec.ts
index 3760777b..35461a24 100644
--- a/src/parser/tests/expression/TemplateStringExpression.spec.ts
+++ b/src/parser/tests/expression/TemplateStringExpression.spec.ts
@@ -76,133 +76,201 @@ describe('TemplateStringExpression', () => {
 
         it('uses the proper prefix when aliased package is installed', () => {
             program.setFile('source/roku_modules/rokucommunity_bslib/bslib.brs', '');
-            testTranspile(
-                'a = `${LINE_NUM},${LINE_NUM}`',
-                `a = rokucommunity_bslib_toString(LINE_NUM) + "," + rokucommunity_bslib_toString(LINE_NUM)`
-            );
+            testTranspile(`
+                sub main()
+                    a = \`\${LINE_NUM},\${LINE_NUM}\`
+                end sub
+            `, `
+                sub main()
+                    a = rokucommunity_bslib_toString(LINE_NUM) + "," + rokucommunity_bslib_toString(LINE_NUM)
+                end sub
+            `);
         });
 
         it('properly transpiles simple template string with no leading text', () => {
-            testTranspile(
-                'a = `${LINE_NUM},${LINE_NUM}`',
-                `a = bslib_toString(LINE_NUM) + "," + bslib_toString(LINE_NUM)`
+            testTranspile(`
+                    sub main()
+                        a = \`\${LINE_NUM},\${LINE_NUM}\`
+                    end sub
+                `, `
+                    sub main()
+                        a = bslib_toString(LINE_NUM) + "," + bslib_toString(LINE_NUM)
+                    end sub
+                `
             );
         });
 
         it('properly transpiles simple template string', () => {
-            testTranspile(
-                'a = `hello world`',
-                'a = "hello world"'
-            );
+            testTranspile(`
+                sub main()
+                    a = \`hello world\`
+                end sub
+            `, `
+                sub main()
+                    a = "hello world"
+                end sub
+            `);
         });
 
         it('properly transpiles one line template string with expressions', () => {
-            testTranspile(
-                'a = `hello ${LINE_NUM.text} world ${"template" + "".getChars()} test`',
-                `a = "hello " + bslib_toString(LINE_NUM.text) + " world " + bslib_toString("template" + "".getChars()) + " test"`
-            );
+            testTranspile(`
+                sub main()
+                    a = \`hello \${LINE_NUM.text} world \${"template" + "".getChars()} test\`
+                end sub
+            `, `
+                sub main()
+                    a = "hello " + bslib_toString(LINE_NUM.text) + " world " + bslib_toString("template" + "".getChars()) + " test"
+                end sub
+            `);
         });
 
         it('handles escaped characters', () => {
-            testTranspile(
-                'a = `\\r\\n\\`\\$`',
-                `a = chr(13) + chr(10) + chr(96) + chr(36)`
-            );
+            testTranspile(`
+                sub main()
+                    a = \`\\r\\n\\\`\\$\`
+                end sub
+            `, `
+                sub main()
+                    a = chr(13) + chr(10) + chr(96) + chr(36)
+                end sub
+            `);
         });
 
         it('handles escaped unicode char codes', () => {
-            testTranspile(
-                'a = `\\c2\\c987`',
-                `a = chr(2) + chr(987)`
-            );
+            testTranspile(`
+                sub main()
+                    a = \`\\c2\\c987\`
+                end sub
+            `, `
+                sub main()
+                    a = chr(2) + chr(987)
+                end sub
+            `);
         });
 
         it('properly transpiles simple multiline template string', () => {
-            testTranspile(
-                'a = `hello world\nI am multiline`',
-                'a = "hello world" + chr(10) + "I am multiline"'
-            );
+            testTranspile(`
+                sub main()
+                    a = \`hello world\nI am multiline\`
+                end sub
+            `, `
+                sub main()
+                    a = "hello world" + chr(10) + "I am multiline"
+                end sub
+            `);
         });
 
         it('properly handles newlines', () => {
-            testTranspile(
-                'a = `\n`',
-                'a = chr(10)'
-            );
+            testTranspile(`
+                sub main()
+                    a = \`\n\`
+                end sub
+            `, `
+                sub main()
+                    a = chr(10)
+                end sub
+            `);
         });
 
         it('properly handles clrf', () => {
-            testTranspile(
-                'a = `\r\n`',
-                'a = chr(13) + chr(10)'
-            );
+            testTranspile(`
+                sub main()
+                    a = \`\r\n\`
+                end sub
+            `, `
+                sub main()
+                    a = chr(13) + chr(10)
+                end sub
+            `);
         });
 
         it('properly transpiles more complex multiline template string', () => {
-            testTranspile(
-                'a = `I am multiline\n${a.isRunning()}\nmore`',
-                'a = "I am multiline" + chr(10) + bslib_toString(a.isRunning()) + chr(10) + "more"'
-            );
+            testTranspile(`
+                sub main()
+                    a = \`I am multiline\n\${a.isRunning()}\nmore\`
+                end sub
+            `, `
+                sub main()
+                    a = "I am multiline" + chr(10) + bslib_toString(a.isRunning()) + chr(10) + "more"
+                end sub
+            `);
         });
 
         it('properly transpiles complex multiline template string in array def', () => {
-            testTranspile(
-                `a = [
-                    "one",
-                    "two",
-                    \`I am a complex example\${a.isRunning(["a", "b", "c"])}\`
-                ]
+            testTranspile(`
+                sub main()
+                    a = [
+                        "one",
+                        "two",
+                        \`I am a complex example\${a.isRunning(["a", "b", "c"])}\`
+                    ]
+                end sub
             `, `
-                a = [
-                    "one"
-                    "two"
-                    "I am a complex example" + bslib_toString(a.isRunning([
-                        "a"
-                        "b"
-                        "c"
-                    ]))
-                ]
+                sub main()
+                    a = [
+                        "one"
+                        "two"
+                        "I am a complex example" + bslib_toString(a.isRunning([
+                            "a"
+                            "b"
+                            "c"
+                        ]))
+                    ]
+                end sub
             `);
         });
 
         it('properly transpiles complex multiline template string in array def, with nested template', () => {
             testTranspile(`
-                a = [
-                    "one",
-                    "two",
-                    \`I am a complex example \${a.isRunning([
-                        "a",
-                        "b",
-                        "c",
-                        \`d_open \${"inside" + m.items[1]} d_close\`
-                    ])}\`
-                ]
+                sub main()
+                    a = [
+                        "one",
+                        "two",
+                        \`I am a complex example \${a.isRunning([
+                            "a",
+                            "b",
+                            "c",
+                            \`d_open \${"inside" + m.items[1]} d_close\`
+                        ])}\`
+                    ]
+                end sub
             `, `
-                a = [
-                    "one"
-                    "two"
-                    "I am a complex example " + bslib_toString(a.isRunning([
-                        "a"
-                        "b"
-                        "c"
-                        "d_open " + bslib_toString("inside" + m.items[1]) + " d_close"
-                    ]))
-                ]
+                sub main()
+                    a = [
+                        "one"
+                        "two"
+                        "I am a complex example " + bslib_toString(a.isRunning([
+                            "a"
+                            "b"
+                            "c"
+                            "d_open " + bslib_toString("inside" + m.items[1]) + " d_close"
+                        ]))
+                    ]
+                end sub
             `);
         });
 
         it('properly transpiles two template strings side-by-side', () => {
-            testTranspile(
-                'a = `${"hello"}${"world"}`',
-                'a = "hello" + "world"'
-            );
+            testTranspile(`
+                sub main()
+                    a = \`\${"hello"}\${"world"}\`
+                end sub
+            `, `
+                sub main()
+                    a = "hello" + "world"
+                end sub
+            `);
         });
 
         it('skips calling toString on strings', () => {
             testTranspile(`
-                text = \`Hello \${"world"}\`
+                sub main()
+                    text = \`Hello \${"world"}\`
+                end sub
             `, `
-                text = "Hello " + "world"
+                sub main()
+                    text = "Hello " + "world"
+                end sub
             `);
         });
 
diff --git a/src/parser/tests/expression/TernaryExpression.spec.ts b/src/parser/tests/expression/TernaryExpression.spec.ts
index 32d7d7c4..8ef777bd 100644
--- a/src/parser/tests/expression/TernaryExpression.spec.ts
+++ b/src/parser/tests/expression/TernaryExpression.spec.ts
@@ -405,10 +405,15 @@ describe('ternary expressions', () => {
         });
 
         it('complex conditions do not cause scope capture', () => {
-            testTranspile(
-                `a = str("true") = "true" ? true : false `,
-                `a = bslib_ternary(str("true") = "true", true, false)`
-            );
+            testTranspile(`
+                sub main()
+                    a = str("true") = "true" ? true : false
+                end sub
+            `, `
+                sub main()
+                    a = bslib_ternary(str("true") = "true", true, false)
+                end sub
+            `);
 
             testTranspile(`
                 sub main()
@@ -525,18 +530,22 @@ describe('ternary expressions', () => {
         it('uses scope capture for property access', () => {
             testTranspile(
                 `
-                    person = {}
-                    name = person <> invalid ? person.name : "John Doe"
-                `,
+                    sub main()
+                        person = {}
+                        name = person <> invalid ? person.name : "John Doe"
+                    end sub
+                    `,
                 `
-                    person = {}
-                    name = (function(__bsCondition, person)
-                            if __bsCondition then
-                                return person.name
-                            else
-                                return "John Doe"
-                            end if
-                        end function)(person <> invalid, person)
+                    sub main()
+                        person = {}
+                        name = (function(__bsCondition, person)
+                                if __bsCondition then
+                                    return person.name
+                                else
+                                    return "John Doe"
+                                end if
+                            end function)(person <> invalid, person)
+                    end sub
                 `
             );
         });
diff --git a/src/parser/tests/statement/PrintStatement.spec.ts b/src/parser/tests/statement/PrintStatement.spec.ts
index 76256879..54b90b6b 100644
--- a/src/parser/tests/statement/PrintStatement.spec.ts
+++ b/src/parser/tests/statement/PrintStatement.spec.ts
@@ -108,15 +108,19 @@ describe('parser print statements', () => {
     describe('transpile', () => {
         it('retains comma separators', () => {
             testTranspile(`
-                a$ = "string"
-                print a$, a$, a$
+                sub main()
+                    a$ = "string"
+                    print a$, a$, a$
+                end sub
             `);
         });
 
         it('retains semicolon separators', () => {
             testTranspile(`
-                a$ = "string"
-                print a$; a$; a$
+                sub main()
+                    a$ = "string"
+                    print a$; a$; a$
+                end sub
             `);
         });
 
@@ -125,71 +129,82 @@ describe('parser print statements', () => {
                 function getText()
                     return "text"
                 end function
-                print getText() getText() getText()
+
+                function main()
+                    print getText() getText() getText()
+                end function
             `);
         });
 
         it('supports print in loop', () => {
             testTranspile(`
-                paramArr = ["This", "is", true, "and", "this", "is", 1]
-                print "This is one line of stuff:";
-                for each item in paramArr
-                    print item; " ";
-                end for
-                print ""
+                sub main()
+                    paramArr = ["This", "is", true, "and", "this", "is", 1]
+                    print "This is one line of stuff:";
+                    for each item in paramArr
+                        print item; " ";
+                    end for
+                    print ""
+                end sub
             `, `
-                paramArr = [
-                    "This"
-                    "is"
-                    true
-                    "and"
-                    "this"
-                    "is"
-                    1
-                ]
-                print "This is one line of stuff:";
-                for each item in paramArr
-                    print item; " ";
-                end for
-                print ""
+                sub main()
+                    paramArr = [
+                        "This"
+                        "is"
+                        true
+                        "and"
+                        "this"
+                        "is"
+                        1
+                    ]
+                    print "This is one line of stuff:";
+                    for each item in paramArr
+                        print item; " ";
+                    end for
+                    print ""
+                end sub
             `);
         });
 
         it('handles roku documentation examples', () => {
             testTranspile(`
-                x=5:print 25; " is equal to"; x^2
-                a$="string":print a$;a$,a$;" ";a$
-                print "zone 1","zone 2","zone 3","zone 4"
-                print "print statement #1 ":print "print statement #2"
-                print "this is a five " 5 "!!"
-                print {}
-                print {a:1}
-                print []
-                print [5]
-                print tab(5)"tabbed 5";tab(25)"tabbed 25"
-                print tab(40) pos(0) 'prints 40 at position 40
-                print "these" tab(pos(0)+5)"words" tab(pos(0)+5)"are":print tab(pos(0)+5)"evenly" tab(pos(0)+5)"spaced"
+                sub main()
+                    x=5:print 25; " is equal to"; x^2
+                    a$="string":print a$;a$,a$;" ";a$
+                    print "zone 1","zone 2","zone 3","zone 4"
+                    print "print statement #1 ":print "print statement #2"
+                    print "this is a five " 5 "!!"
+                    print {}
+                    print {a:1}
+                    print []
+                    print [5]
+                    print tab(5)"tabbed 5";tab(25)"tabbed 25"
+                    print tab(40) pos(0) 'prints 40 at position 40
+                    print "these" tab(pos(0)+5)"words" tab(pos(0)+5)"are":print tab(pos(0)+5)"evenly" tab(pos(0)+5)"spaced"
+                end sub
             `, `
-                x = 5
-                print 25; " is equal to"; x ^ 2
-                a$ = "string"
-                print a$; a$, a$; " "; a$
-                print "zone 1", "zone 2", "zone 3", "zone 4"
-                print "print statement #1 "
-                print "print statement #2"
-                print "this is a five " 5 "!!"
-                print {}
-                print {
-                    a: 1
-                }
-                print []
-                print [
-                    5
-                ]
-                print tab(5) "tabbed 5"; tab(25) "tabbed 25"
-                print tab(40) pos(0) 'prints 40 at position 40
-                print "these" tab(pos(0) + 5) "words" tab(pos(0) + 5) "are"
-                print tab(pos(0) + 5) "evenly" tab(pos(0) + 5) "spaced"
+                sub main()
+                    x = 5
+                    print 25; " is equal to"; x ^ 2
+                    a$ = "string"
+                    print a$; a$, a$; " "; a$
+                    print "zone 1", "zone 2", "zone 3", "zone 4"
+                    print "print statement #1 "
+                    print "print statement #2"
+                    print "this is a five " 5 "!!"
+                    print {}
+                    print {
+                        a: 1
+                    }
+                    print []
+                    print [
+                        5
+                    ]
+                    print tab(5) "tabbed 5"; tab(25) "tabbed 25"
+                    print tab(40) pos(0) 'prints 40 at position 40
+                    print "these" tab(pos(0) + 5) "words" tab(pos(0) + 5) "are"
+                    print tab(pos(0) + 5) "evenly" tab(pos(0) + 5) "spaced"
+                end sub
             `);
         });
     });