Repository: rokucommunity/brighterscript
Pull request #1192 (2024-05-16 13:39:59)

Fixes some issues with comments and transpilation
Fixes:

- leading comments before `alias`, `typecast`, and `import` statements

- comments on same line as previous statement before `namespace`


markwpearce: Fixes #1190 

Patch:
diff --git a/src/files/BrsFile.spec.ts b/src/files/BrsFile.spec.ts
index b2bfa7c0..9fdfc704 100644
--- a/src/files/BrsFile.spec.ts
+++ b/src/files/BrsFile.spec.ts
@@ -3157,6 +3157,89 @@ describe('BrsFile', () => {
             });
 
         });
+
+        it('allows comments after alias/typecast/import statements', async () => {
+            program.setFile('source/types.bs', `
+                class Person
+                end class
+
+                interface MyComponent
+                    name as string
+                end interface
+            `);
+            await testTranspile(`
+                typecast m as MyComponent ' typecast comment
+                import "types.bs" ' import comment
+                alias Person2 = Person ' alias comment
+
+                sub test()
+                    dude = new Person2()
+                    print m.name
+                end sub
+            `, `
+                'typecast m as dynamic ' typecast comment
+                'import "types.bs" ' import comment
+                'alias Person2 = Person ' alias comment
+
+                sub test()
+                    dude = Person()
+                    print m.name
+                end sub
+            `);
+        });
+
+        it('allows comments on same line as alias before namespace', async () => {
+            program.setFile('source/types.bs', `
+                class Person
+                end class
+            `);
+            await testTranspile(`
+                import "types.bs" ' import comment
+                alias Person2 = Person ' alias comment
+
+                namespace alpha
+                    sub foo()
+                    end sub
+                end namespace
+            `, `
+                'import "types.bs" ' import comment
+                'alias Person2 = Person ' alias comment
+
+                sub alpha_foo()
+                end sub
+            `);
+        });
+
+        it('adds comments before eventually empty nodes', async () => {
+            await testTranspile(`
+                ' comment before enum
+                enum Direction
+                    north
+                    south
+                end enum
+
+                ' comment before const
+                const myconst = 1234
+
+                ' comment before interface
+                interface ABC
+                    id as integer
+                    function func() as string
+                end interface
+
+                ' comment before empty namespace
+                namespace alpha
+                end namespace
+            `, `
+                ' comment before enum
+
+                ' comment before const
+
+                ' comment before interface
+
+                ' comment before empty namespace
+            `);
+        });
     });
 
     describe('callfunc operator', () => {
diff --git a/src/parser/Statement.ts b/src/parser/Statement.ts
index 452dae91..20ab7c83 100644
--- a/src/parser/Statement.ts
+++ b/src/parser/Statement.ts
@@ -1524,7 +1524,11 @@ export class NamespaceStatement extends Statement implements TypedefProvider {
 
     transpile(state: BrsTranspileState) {
         //namespaces don't actually have any real content, so just transpile their bodies
-        return this.body.transpile(state);
+        return [
+            state.transpileLeadingComments(this.tokens.namespace),
+            this.body.transpile(state),
+            state.transpileLeadingComments(this.tokens.endNamespace)
+        ];
     }
 
     getTypedef(state: BrsTranspileState) {
@@ -1615,8 +1619,7 @@ export class ImportStatement extends Statement implements TypedefProvider {
         //The xml files are responsible for adding the additional script imports, but
         //add the import statement as a comment just for debugging purposes
         return [
-            `'`,
-            state.transpileToken(this.tokens.import, 'import'),
+            state.transpileToken(this.tokens.import, 'import', true),
             ' ',
             state.transpileToken(this.tokens.path)
         ];
@@ -1749,7 +1752,9 @@ export class InterfaceStatement extends Statement implements TypedefProvider {
 
     public transpile(state: BrsTranspileState): TranspileResult {
         //interfaces should completely disappear at runtime
-        return [];
+        return [
+            state.transpileLeadingComments(this.tokens.interface)
+        ];
     }
 
     getTypedef(state: BrsTranspileState) {
@@ -3172,7 +3177,9 @@ export class EnumStatement extends Statement implements TypedefProvider {
 
     transpile(state: BrsTranspileState) {
         //enum declarations do not exist at runtime, so don't transpile anything...
-        return [];
+        return [
+            state.transpileLeadingComments(this.tokens.enum)
+        ];
     }
 
     getTypedef(state: BrsTranspileState) {
@@ -3375,7 +3382,7 @@ export class ConstStatement extends Statement implements TypedefProvider {
 
     public transpile(state: BrsTranspileState): TranspileResult {
         //const declarations don't exist at runtime, so just transpile empty
-        return [];
+        return [state.transpileLeadingComments(this.tokens.const)];
     }
 
     getTypedef(state: BrsTranspileState): TranspileResult {
@@ -3483,8 +3490,7 @@ export class TypecastStatement extends Statement {
     transpile(state: BrsTranspileState) {
         //the typecast statement is a comment just for debugging purposes
         return [
-            `'`,
-            state.transpileToken(this.tokens.typecast, 'typecast'),
+            state.transpileToken(this.tokens.typecast, 'typecast', true),
             ' ',
             this.typecastExpression.obj.transpile(state),
             ' ',
@@ -3588,8 +3594,7 @@ export class AliasStatement extends Statement {
     transpile(state: BrsTranspileState) {
         //the alias statement is a comment just for debugging purposes
         return [
-            `'`,
-            state.transpileToken(this.tokens.alias, 'alias'),
+            state.transpileToken(this.tokens.alias, 'alias', true),
             ' ',
             state.transpileToken(this.tokens.name),
             ' ',
diff --git a/src/parser/TranspileState.ts b/src/parser/TranspileState.ts
index 60b86562..624db002 100644
--- a/src/parser/TranspileState.ts
+++ b/src/parser/TranspileState.ts
@@ -132,25 +132,29 @@ export class TranspileState {
 
     /**
      * Create a SourceNode from a token, accounting for missing range and multi-line text
+     * Adds all leading trivia for the token
      */
-    public transpileToken(token: TranspileToken, defaultValue?: string): TranspileResult {
+    public transpileToken(token: TranspileToken, defaultValue?: string, commentOut = false): TranspileResult {
+        const leadingCommentsSourceNodes = this.transpileLeadingComments(token);
+        const commentIfCommentedOut = commentOut ? `'` : '';
+
         if (!token?.text && defaultValue !== undefined) {
-            return [new SourceNode(null, null, null, defaultValue)];
+            return [new SourceNode(null, null, null, [...leadingCommentsSourceNodes, commentIfCommentedOut, defaultValue])];
         }
-        const leadingCommentsSourceNodes = this.transpileLeadingComments(token);
 
         if (!token.range) {
-            return [new SourceNode(null, null, null, [...leadingCommentsSourceNodes, token.text])];
+            return [new SourceNode(null, null, null, [...leadingCommentsSourceNodes, commentIfCommentedOut, token.text])];
         }
         //split multi-line text
         if (token.range?.end.line > token.range?.start.line) {
             const lines = token.text.split(/\r?\n/g);
             const code = [
-                this.sourceNode(token, [...leadingCommentsSourceNodes, lines[0]])
+                this.sourceNode(token, [...leadingCommentsSourceNodes, commentIfCommentedOut, lines[0]])
             ] as Array<string | SourceNode>;
             for (let i = 1; i < lines.length; i++) {
                 code.push(
                     this.newline,
+                    commentIfCommentedOut,
                     new SourceNode(
                         //convert 0-based range line to 1-based SourceNode line
                         token.range.start.line + i + 1,
@@ -163,7 +167,7 @@ export class TranspileState {
             }
             return [new SourceNode(null, null, null, code)];
         } else {
-            return [...leadingCommentsSourceNodes, this.tokenToSourceNode(token)];
+            return [...leadingCommentsSourceNodes, commentIfCommentedOut, this.tokenToSourceNode(token)];
         }
     }
 


Test patch:
diff --git a/src/parser/tests/statement/InterfaceStatement.spec.ts b/src/parser/tests/statement/InterfaceStatement.spec.ts
index 5ce3453e..d50ea62c 100644
--- a/src/parser/tests/statement/InterfaceStatement.spec.ts
+++ b/src/parser/tests/statement/InterfaceStatement.spec.ts
@@ -84,6 +84,7 @@ describe('InterfaceStatement', () => {
                 prop as dynamic
             end interface
         `, `
+            'this comment was throwing exception during transpile
         `);
     });