Repository: rokucommunity/brighterscript
Pull request #1083 (2024-03-03 03:30:46)

Removes `CommentStatement` and all comment tokens from Lexer output
- Comments now reside SOLELY in leading trivia.
- `TranspileState.transpileToken()` will automatically transpile comments (if they exist) ahead of the token
- To still indent comments before a "block closing token" (eg. `}`, `end sub`, etc), use TranspileState.transpileEndBlockToken()`
- Modified getSignatureHelp, getDocumentation, etc. to use leading trivia only


markwpearce: Addresses #1042 
Addresses #1031

Patch:
diff --git a/src/AstValidationSegmenter.ts b/src/AstValidationSegmenter.ts
index 55731ee4..3f0bbbb2 100644
--- a/src/AstValidationSegmenter.ts
+++ b/src/AstValidationSegmenter.ts
@@ -1,6 +1,6 @@
 import type { DottedGetExpression, TypeExpression, VariableExpression } from './parser/Expression';
 import { SymbolTypeFlag } from './SymbolTableFlag';
-import { isBody, isClassStatement, isCommentStatement, isInterfaceStatement, isNamespaceStatement, isVariableExpression } from './astUtils/reflection';
+import { isBody, isClassStatement, isInterfaceStatement, isNamespaceStatement, isVariableExpression } from './astUtils/reflection';
 import { ChildrenSkipper, WalkMode, createVisitor } from './astUtils/visitors';
 import type { GetTypeOptions, TypeChainEntry } from './interfaces';
 import type { AstNode } from './parser/AstNode';
@@ -43,7 +43,7 @@ export class AstValidationSegmenter {
     }
 
     checkExpressionForUnresolved(segment: AstNode, expression: VariableExpression | DottedGetExpression | TypeExpression, assignedSymbols?: Set<string>) {
-        if (!expression || isCommentStatement(expression)) {
+        if (!expression) {
             return false;
         }
         if (isVariableExpression(expression) && expression.tokens.name.text.toLowerCase() === 'm') {
@@ -74,7 +74,7 @@ export class AstValidationSegmenter {
     private currentNamespaceStatement: NamespaceStatement;
 
     checkSegmentWalk(segment: AstNode) {
-        if (isNamespaceStatement(segment) || isBody(segment) || isCommentStatement(segment)) {
+        if (isNamespaceStatement(segment) || isBody(segment)) {
             return;
         }
         if (isClassStatement(segment)) {
diff --git a/src/CommentFlagProcessor.spec.ts b/src/CommentFlagProcessor.spec.ts
index 4ede7d3a..98057d81 100644
--- a/src/CommentFlagProcessor.spec.ts
+++ b/src/CommentFlagProcessor.spec.ts
@@ -89,7 +89,8 @@ describe('CommentFlagProcessor', () => {
         });
 
         it('works for special case', () => {
-            const token = Lexer.scan(`print "hi" 'bs:disable-line: 123456 999999   aaaab`).tokens[2];
+            const tokens = Lexer.scan(`print "hi" 'bs:disable-line: 123456 999999   aaaab`).tokens;
+            const token = tokens[2].leadingTrivia[1];
             expect(
                 processor['tokenize'](token.text, token.range)
             ).to.eql({
@@ -109,7 +110,7 @@ describe('CommentFlagProcessor', () => {
         });
 
         it('tokenizes bs:disable-line comment with codes', () => {
-            const token = Lexer.scan(`'bs:disable-line:1 2 3`).tokens[0];
+            const token = Lexer.scan(`'bs:disable-line:1 2 3`).tokens[0].leadingTrivia[0];
             expect(
                 processor['tokenize'](token.text, token.range)
             ).to.eql({
@@ -129,7 +130,8 @@ describe('CommentFlagProcessor', () => {
         });
 
         it('tokenizes bs:disable-line comment with leading space', () => {
-            const token = Lexer.scan(`' bs:disable-line:1`).tokens[0];
+            const tokens = Lexer.scan(`' bs:disable-line:1`).tokens;
+            const token = tokens[0].leadingTrivia[0];
             expect(
                 processor['tokenize'](token.text, token.range)
             ).to.eql({
@@ -143,7 +145,8 @@ describe('CommentFlagProcessor', () => {
         });
 
         it('tokenizes bs:disable-line comment with leading tab', () => {
-            const token = Lexer.scan(`'\tbs:disable-line:1`).tokens[0];
+            const tokens = Lexer.scan(`'\tbs:disable-line:1`).tokens;
+            const token = tokens[0].leadingTrivia[0];
             expect(
                 processor['tokenize'](token.text, token.range)
             ).to.eql({
diff --git a/src/astUtils/CachedLookups.ts b/src/astUtils/CachedLookups.ts
index 88048ce4..c81ecbb8 100644
--- a/src/astUtils/CachedLookups.ts
+++ b/src/astUtils/CachedLookups.ts
@@ -3,7 +3,7 @@ import type { AssignmentStatement, ClassStatement, ConstStatement, EnumStatement
 import { Cache } from '../Cache';
 import { WalkMode, createVisitor } from './visitors';
 import type { Expression } from '../parser/AstNode';
-import { isAAMemberExpression, isBinaryExpression, isCallExpression, isCommentStatement, isDottedGetExpression, isFunctionExpression, isIndexedGetExpression, isMethodStatement, isNamespaceStatement, isNewExpression, isVariableExpression } from './reflection';
+import { isAAMemberExpression, isBinaryExpression, isCallExpression, isDottedGetExpression, isFunctionExpression, isIndexedGetExpression, isMethodStatement, isNamespaceStatement, isNewExpression, isVariableExpression } from './reflection';
 import type { Parser } from '../parser/Parser';
 import { ParseMode } from '../parser/Parser';
 import type { Token } from '../lexer/Token';
@@ -170,12 +170,11 @@ export class CachedLookups {
                 propertyHints[name.toLowerCase()] = name;
             } else {
                 for (const member of item.elements) {
-                    if (!isCommentStatement(member)) {
-                        const name = member.tokens.key.text;
-                        if (!name.startsWith('"')) {
-                            propertyHints[name.toLowerCase()] = name;
-                        }
+                    const name = member.tokens.key.text;
+                    if (!name.startsWith('"')) {
+                        propertyHints[name.toLowerCase()] = name;
                     }
+
                 }
             }
         };
@@ -294,9 +293,7 @@ export class CachedLookups {
             ArrayLiteralExpression: e => {
                 for (const element of e.elements) {
                     //keep everything except comments
-                    if (!isCommentStatement(element)) {
-                        expressions.add(element);
-                    }
+                    expressions.add(element);
                 }
             },
             DottedGetExpression: e => {
diff --git a/src/astUtils/reflection.spec.ts b/src/astUtils/reflection.spec.ts
index 4639d573..d372507a 100644
--- a/src/astUtils/reflection.spec.ts
+++ b/src/astUtils/reflection.spec.ts
@@ -1,11 +1,11 @@
 /* eslint-disable no-multi-spaces */
 import { expect } from '../chai-config.spec';
-import { PrintStatement, Block, Body, AssignmentStatement, CommentStatement, ExitForStatement, ExitWhileStatement, ExpressionStatement, FunctionStatement, IfStatement, IncrementStatement, GotoStatement, LabelStatement, ReturnStatement, EndStatement, StopStatement, ForStatement, ForEachStatement, WhileStatement, DottedSetStatement, IndexedSetStatement, LibraryStatement, NamespaceStatement, ImportStatement, ClassStatement, EmptyStatement, TryCatchStatement, CatchStatement, ThrowStatement } from '../parser/Statement';
+import { PrintStatement, Block, Body, AssignmentStatement, ExitForStatement, ExitWhileStatement, ExpressionStatement, FunctionStatement, IfStatement, IncrementStatement, GotoStatement, LabelStatement, ReturnStatement, EndStatement, StopStatement, ForStatement, ForEachStatement, WhileStatement, DottedSetStatement, IndexedSetStatement, LibraryStatement, NamespaceStatement, ImportStatement, ClassStatement, EmptyStatement, TryCatchStatement, CatchStatement, ThrowStatement } from '../parser/Statement';
 import { FunctionExpression, BinaryExpression, CallExpression, DottedGetExpression, IndexedGetExpression, GroupingExpression, EscapedCharCodeLiteralExpression, ArrayLiteralExpression, AALiteralExpression, UnaryExpression, VariableExpression, SourceLiteralExpression, NewExpression, CallfuncExpression, TemplateStringQuasiExpression, XmlAttributeGetExpression, TemplateStringExpression, TaggedTemplateStringExpression, AnnotationExpression } from '../parser/Expression';
 import type { Token } from '../lexer/Token';
 import { TokenKind } from '../lexer/TokenKind';
-import { isPrintStatement, isIfStatement, isBody, isAssignmentStatement, isBlock, isExpressionStatement, isCommentStatement, isExitForStatement, isExitWhileStatement, isFunctionStatement, isIncrementStatement, isGotoStatement, isLabelStatement, isReturnStatement, isEndStatement, isStopStatement, isForStatement, isForEachStatement, isWhileStatement, isDottedSetStatement, isIndexedSetStatement, isLibraryStatement, isNamespaceStatement, isImportStatement, isExpression, isBinaryExpression, isCallExpression, isFunctionExpression, isDottedGetExpression, isXmlAttributeGetExpression, isIndexedGetExpression, isGroupingExpression, isLiteralExpression, isEscapedCharCodeLiteralExpression, isArrayLiteralExpression, isAALiteralExpression, isUnaryExpression, isVariableExpression, isSourceLiteralExpression, isNewExpression, isCallfuncExpression, isTemplateStringQuasiExpression, isTemplateStringExpression, isTaggedTemplateStringExpression, isBrsFile, isXmlFile, isClassStatement, isStatement, isAnnotationExpression, isTryCatchStatement, isCatchStatement, isThrowStatement, isLiteralBoolean, isLiteralDouble, isLiteralFloat, isLiteralInteger, isLiteralInvalid, isLiteralLongInteger, isLiteralNumber } from './reflection';
-import { createToken, createStringLiteral, createVariableExpression, createBooleanLiteral, createDoubleLiteral, createFloatLiteral, createIntegerLiteral, createInvalidLiteral, createLongIntegerLiteral } from './creators';
+import { isPrintStatement, isIfStatement, isBody, isAssignmentStatement, isBlock, isExpressionStatement, isExitForStatement, isExitWhileStatement, isFunctionStatement, isIncrementStatement, isGotoStatement, isLabelStatement, isReturnStatement, isEndStatement, isStopStatement, isForStatement, isForEachStatement, isWhileStatement, isDottedSetStatement, isIndexedSetStatement, isLibraryStatement, isNamespaceStatement, isImportStatement, isExpression, isBinaryExpression, isCallExpression, isFunctionExpression, isDottedGetExpression, isXmlAttributeGetExpression, isIndexedGetExpression, isGroupingExpression, isLiteralExpression, isEscapedCharCodeLiteralExpression, isArrayLiteralExpression, isAALiteralExpression, isUnaryExpression, isVariableExpression, isSourceLiteralExpression, isNewExpression, isCallfuncExpression, isTemplateStringQuasiExpression, isTemplateStringExpression, isTaggedTemplateStringExpression, isBrsFile, isXmlFile, isClassStatement, isStatement, isAnnotationExpression, isTryCatchStatement, isCatchStatement, isThrowStatement, isLiteralInvalid, isLiteralBoolean, isLiteralNumber, isLiteralInteger, isLiteralLongInteger, isLiteralFloat, isLiteralDouble } from './reflection';
+import { createToken, createStringLiteral, createInvalidLiteral, createBooleanLiteral, createIntegerLiteral, createVariableExpression, createFloatLiteral, createDoubleLiteral, createLongIntegerLiteral } from './creators';
 import { Program } from '../Program';
 import { BrsFile } from '../files/BrsFile';
 import { XmlFile } from '../files/XmlFile';
@@ -31,7 +31,6 @@ describe('reflection', () => {
         const assignment = new AssignmentStatement({ equals: undefined, name: ident, value: expr });
         const block = new Block({ statements: [] });
         const expression = new ExpressionStatement({ expression: expr });
-        const comment = new CommentStatement({ comments: [token] });
         const exitFor = new ExitForStatement({ exitFor: token });
         const exitWhile = new ExitWhileStatement({ exitWhile: token });
         const funs = new FunctionStatement({
@@ -93,10 +92,6 @@ describe('reflection', () => {
             expect(isExpressionStatement(expression)).to.be.true;
             expect(isExpressionStatement(body)).to.be.false;
         });
-        it('isCommentStatement', () => {
-            expect(isCommentStatement(comment)).to.be.true;
-            expect(isCommentStatement(body)).to.be.false;
-        });
         it('isExitForStatement', () => {
             expect(isExitForStatement(exitFor)).to.be.true;
             expect(isExitForStatement(body)).to.be.false;
diff --git a/src/astUtils/reflection.ts b/src/astUtils/reflection.ts
index b9218da1..6a9b65c9 100644
--- a/src/astUtils/reflection.ts
+++ b/src/astUtils/reflection.ts
@@ -1,4 +1,4 @@
-import type { Body, AssignmentStatement, Block, ExpressionStatement, CommentStatement, ExitForStatement, ExitWhileStatement, FunctionStatement, IfStatement, IncrementStatement, PrintStatement, GotoStatement, LabelStatement, ReturnStatement, EndStatement, StopStatement, ForStatement, ForEachStatement, WhileStatement, DottedSetStatement, IndexedSetStatement, LibraryStatement, NamespaceStatement, ImportStatement, ClassStatement, InterfaceFieldStatement, InterfaceMethodStatement, InterfaceStatement, EnumStatement, EnumMemberStatement, TryCatchStatement, CatchStatement, ThrowStatement, MethodStatement, FieldStatement, ConstStatement, ContinueStatement } from '../parser/Statement';
+import type { Body, AssignmentStatement, Block, ExpressionStatement, ExitForStatement, ExitWhileStatement, FunctionStatement, IfStatement, IncrementStatement, PrintStatement, GotoStatement, LabelStatement, ReturnStatement, EndStatement, StopStatement, ForStatement, ForEachStatement, WhileStatement, DottedSetStatement, IndexedSetStatement, LibraryStatement, NamespaceStatement, ImportStatement, ClassStatement, InterfaceFieldStatement, InterfaceMethodStatement, InterfaceStatement, EnumStatement, EnumMemberStatement, TryCatchStatement, CatchStatement, ThrowStatement, MethodStatement, FieldStatement, ConstStatement, ContinueStatement } from '../parser/Statement';
 import type { LiteralExpression, BinaryExpression, CallExpression, FunctionExpression, DottedGetExpression, XmlAttributeGetExpression, IndexedGetExpression, GroupingExpression, EscapedCharCodeLiteralExpression, ArrayLiteralExpression, AALiteralExpression, UnaryExpression, VariableExpression, SourceLiteralExpression, NewExpression, CallfuncExpression, TemplateStringQuasiExpression, TemplateStringExpression, TaggedTemplateStringExpression, AnnotationExpression, FunctionParameterExpression, AAMemberExpression, TypeCastExpression, TypeExpression, TypedArrayExpression } from '../parser/Expression';
 import type { BrsFile } from '../files/BrsFile';
 import type { XmlFile } from '../files/XmlFile';
@@ -80,9 +80,6 @@ export function isBlock(element: AstNode | undefined): element is Block {
 export function isExpressionStatement(element: AstNode | undefined): element is ExpressionStatement {
     return element?.kind === AstNodeKind.ExpressionStatement;
 }
-export function isCommentStatement(element: AstNode | undefined): element is CommentStatement {
-    return element?.kind === AstNodeKind.CommentStatement;
-}
 export function isExitForStatement(element: AstNode | undefined): element is ExitForStatement {
     return element?.kind === AstNodeKind.ExitForStatement;
 }
diff --git a/src/astUtils/visitors.spec.ts b/src/astUtils/visitors.spec.ts
index 507062e7..ba3e1279 100644
--- a/src/astUtils/visitors.spec.ts
+++ b/src/astUtils/visitors.spec.ts
@@ -306,8 +306,6 @@ describe('astUtils visitors', () => {
 
             program.setFile('source/main.brs', EXPRESSIONS_SRC);
             expect(actual).to.deep.equal([
-                //The comment statement is weird because it can't be both a statement and expression, but is treated that way. Just ignore it for now until we refactor comments.
-                //'CommentStatement:1:CommentStatement',          // '<comment>
                 'PrintStatement:1:LiteralExpression',             // print <"msg">; 3
                 'PrintStatement:1:LiteralExpression',             // print "msg"; <3>
                 'PrintStatement:1:TemplateStringExpression',      // print <`expand ${var}`>
@@ -819,7 +817,6 @@ describe('astUtils visitors', () => {
                 'Block',
                 'AssignmentStatement',
                 'AALiteralExpression',
-                'CommentStatement',
                 'AAMemberExpression',
                 'LiteralExpression'
             ]);
@@ -894,8 +891,7 @@ describe('astUtils visitors', () => {
                 'LiteralExpression',
                 //else
                 'Block',
-                'ReturnStatement',
-                'CommentStatement'
+                'ReturnStatement'
             ]);
         });
 
diff --git a/src/astUtils/visitors.ts b/src/astUtils/visitors.ts
index 32b06d7b..2ce1916a 100644
--- a/src/astUtils/visitors.ts
+++ b/src/astUtils/visitors.ts
@@ -1,6 +1,6 @@
 /* eslint-disable no-bitwise */
 import type { CancellationToken } from 'vscode-languageserver';
-import type { Body, AssignmentStatement, Block, ExpressionStatement, CommentStatement, ExitForStatement, ExitWhileStatement, FunctionStatement, IfStatement, IncrementStatement, PrintStatement, GotoStatement, LabelStatement, ReturnStatement, EndStatement, StopStatement, ForStatement, ForEachStatement, WhileStatement, DottedSetStatement, IndexedSetStatement, LibraryStatement, NamespaceStatement, ImportStatement, ClassStatement, EnumStatement, EnumMemberStatement, DimStatement, TryCatchStatement, CatchStatement, ThrowStatement, InterfaceStatement, InterfaceFieldStatement, InterfaceMethodStatement, FieldStatement, MethodStatement, ConstStatement, ContinueStatement } from '../parser/Statement';
+import type { Body, AssignmentStatement, Block, ExpressionStatement, ExitForStatement, ExitWhileStatement, FunctionStatement, IfStatement, IncrementStatement, PrintStatement, GotoStatement, LabelStatement, ReturnStatement, EndStatement, StopStatement, ForStatement, ForEachStatement, WhileStatement, DottedSetStatement, IndexedSetStatement, LibraryStatement, NamespaceStatement, ImportStatement, ClassStatement, EnumStatement, EnumMemberStatement, DimStatement, TryCatchStatement, CatchStatement, ThrowStatement, InterfaceStatement, InterfaceFieldStatement, InterfaceMethodStatement, FieldStatement, MethodStatement, ConstStatement, ContinueStatement } from '../parser/Statement';
 import type { AALiteralExpression, AAMemberExpression, AnnotationExpression, ArrayLiteralExpression, BinaryExpression, CallExpression, CallfuncExpression, DottedGetExpression, EscapedCharCodeLiteralExpression, FunctionExpression, FunctionParameterExpression, GroupingExpression, IndexedGetExpression, LiteralExpression, NewExpression, NullCoalescingExpression, RegexLiteralExpression, SourceLiteralExpression, TaggedTemplateStringExpression, TemplateStringExpression, TemplateStringQuasiExpression, TernaryExpression, TypeCastExpression, TypeExpression, UnaryExpression, VariableExpression, XmlAttributeGetExpression } from '../parser/Expression';
 import { isExpression, isStatement } from './reflection';
 import type { Editor } from './Editor';
@@ -107,7 +107,6 @@ export function createVisitor(
         AssignmentStatement?: (statement: AssignmentStatement, parent?: Statement, owner?: any, key?: any) => Statement | void;
         Block?: (statement: Block, parent?: Statement, owner?: any, key?: any) => Statement | void;
         ExpressionStatement?: (statement: ExpressionStatement, parent?: Statement, owner?: any, key?: any) => Statement | void;
-        CommentStatement?: (statement: CommentStatement, parent?: Statement, owner?: any, key?: any) => Statement | void;
         ExitForStatement?: (statement: ExitForStatement, parent?: Statement, owner?: any, key?: any) => Statement | void;
         ExitWhileStatement?: (statement: ExitWhileStatement, parent?: Statement, owner?: any, key?: any) => Statement | void;
         FunctionStatement?: (statement: FunctionStatement, parent?: Statement, owner?: any, key?: any) => Statement | void;
diff --git a/src/bscPlugin/SignatureHelpUtil.ts b/src/bscPlugin/SignatureHelpUtil.ts
index 5faed8b5..b2566bb4 100644
--- a/src/bscPlugin/SignatureHelpUtil.ts
+++ b/src/bscPlugin/SignatureHelpUtil.ts
@@ -78,11 +78,10 @@ export class SignatureHelpUtil {
         const funcStartPosition = func.range.start;
 
         // Get function comments in reverse order
-        let currentToken = file.getTokenAt(funcStartPosition);
+        const trivia = func.getLeadingTrivia().reverse();
         let functionComments = [] as string[];
-        while (currentToken) {
-            currentToken = file.getPreviousToken(currentToken);
 
+        for (const currentToken of trivia) {
             if (!currentToken) {
                 break;
             }
@@ -106,6 +105,7 @@ export class SignatureHelpUtil {
                     break;
                 }
                 functionComments.unshift(currentToken.text);
+            } else if (kind === TokenKind.Whitespace) {
             } else {
                 break;
             }
diff --git a/src/bscPlugin/completions/CompletionsProcessor.ts b/src/bscPlugin/completions/CompletionsProcessor.ts
index a8cd067b..cd1903ca 100644
--- a/src/bscPlugin/completions/CompletionsProcessor.ts
+++ b/src/bscPlugin/completions/CompletionsProcessor.ts
@@ -1,7 +1,7 @@
 import { isBlock, isBrsFile, isCallableType, isClassStatement, isClassType, isComponentType, isConstStatement, isEnumMemberType, isEnumType, isFunctionExpression, isInterfaceType, isMethodStatement, isNamespaceStatement, isNamespaceType, isNativeType, isXmlFile, isXmlScope } from '../../astUtils/reflection';
 import type { FileReference, ProvideCompletionsEvent } from '../../interfaces';
 import type { BscFile } from '../../files/BscFile';
-import { DeclarableTypes, Keywords, TokenKind } from '../../lexer/TokenKind';
+import { AllowedTriviaTokens, DeclarableTypes, Keywords, TokenKind } from '../../lexer/TokenKind';
 import type { XmlScope } from '../../XmlScope';
 import { util } from '../../util';
 import type { Scope } from '../../Scope';
@@ -144,14 +144,15 @@ export class CompletionsProcessor {
      */
     public getBrsFileCompletions(position: Position, file: BrsFile): CompletionItem[] {
         let result = [] as CompletionItem[];
-
-        const currentToken = file.getTokenAt(position) ?? file.getTokenAt(file.getClosestExpression(position)?.range.start);
+        const currentTokenByFilePosition = file.getTokenAt(position);
+        const currentToken = currentTokenByFilePosition ?? file.getTokenAt(file.getClosestExpression(position)?.range.start);
         if (!currentToken) {
             return [];
         }
-        //if cursor is within a comment, disable completions
         const tokenKind = currentToken?.kind;
-        if (tokenKind === TokenKind.Comment) {
+
+        //if cursor is after a comment, disable completions
+        if (this.isPostionInComment(file, position)) {
             return [];
         }
 
@@ -570,6 +571,24 @@ export class CompletionsProcessor {
             }
         ];
     }
+
+    private isPostionInComment(file: BrsFile, position: Position) {
+        const currentToken = file.getCurrentOrNextTokenAt(position);
+        const tokenKind = currentToken?.kind;
+        if (!currentToken) {
+            return true;
+        }
+        if (tokenKind === TokenKind.Comment) {
+            return true;
+        }
+
+        const nextNonComment = file.getNextTokenByPredicate(currentToken, (t: Token) => !AllowedTriviaTokens.includes(t.kind), 1);
+        const firstComment = nextNonComment?.leadingTrivia.find(t => t.kind === TokenKind.Comment);
+        if (firstComment && util.comparePosition(position, firstComment?.range.start) >= 0) {
+            return true;
+        }
+        return false;
+    }
 }
 
 /**
diff --git a/src/bscPlugin/validation/BrsFileValidator.ts b/src/bscPlugin/validation/BrsFileValidator.ts
index 7c3743c4..a0b4540a 100644
--- a/src/bscPlugin/validation/BrsFileValidator.ts
+++ b/src/bscPlugin/validation/BrsFileValidator.ts
@@ -1,4 +1,4 @@
-import { isArrayType, isBody, isBrsFile, isClassStatement, isCommentStatement, isConstStatement, isDottedGetExpression, isDottedSetStatement, isEnumStatement, isForEachStatement, isForStatement, isFunctionExpression, isFunctionStatement, isImportStatement, isIndexedGetExpression, isIndexedSetStatement, isInterfaceStatement, isLibraryStatement, isLiteralExpression, isNamespaceStatement, isUnaryExpression, isWhileStatement } from '../../astUtils/reflection';
+import { isArrayType, isBody, isBrsFile, isClassStatement, isConstStatement, isDottedGetExpression, isDottedSetStatement, isEnumStatement, isForEachStatement, isForStatement, isFunctionExpression, isFunctionStatement, isImportStatement, isIndexedGetExpression, isIndexedSetStatement, isInterfaceStatement, isLibraryStatement, isLiteralExpression, isNamespaceStatement, isUnaryExpression, isWhileStatement } from '../../astUtils/reflection';
 import { createVisitor, WalkMode } from '../../astUtils/visitors';
 import { DiagnosticMessages } from '../../DiagnosticMessages';
 import type { BrsFile } from '../../files/BrsFile';
@@ -292,7 +292,6 @@ export class BrsFileValidator {
                     !isClassStatement(statement) &&
                     !isEnumStatement(statement) &&
                     !isInterfaceStatement(statement) &&
-                    !isCommentStatement(statement) &&
                     !isLibraryStatement(statement) &&
                     !isImportStatement(statement) &&
                     !isConstStatement(statement)
@@ -309,10 +308,6 @@ export class BrsFileValidator {
     private validateImportStatements() {
         let topOfFileIncludeStatements = [] as Array<LibraryStatement | ImportStatement>;
         for (let stmt of this.event.file.parser.ast.statements) {
-            //skip comments
-            if (isCommentStatement(stmt)) {
-                continue;
-            }
             //if we found a non-library statement, this statement is not at the top of the file
             if (isLibraryStatement(stmt) || isImportStatement(stmt)) {
                 topOfFileIncludeStatements.push(stmt);
diff --git a/src/files/BrsFile.Class.spec.ts b/src/files/BrsFile.Class.spec.ts
index 10dee0f3..c29abcca 100644
--- a/src/files/BrsFile.Class.spec.ts
+++ b/src/files/BrsFile.Class.spec.ts
@@ -822,9 +822,11 @@ describe('BrsFile BrighterScript classes', () => {
                     smokey = Animal("Smokey")
                     smokey.move(1)
                     '> Bear moved 1 meters
+
                     donald = Duck("Donald")
                     donald.move(2)
                     '> Waddling...\\nDonald moved 2 meters
+
                     dewey = BabyDuck("Dewey")
                     dewey.move(3)
                     '> Waddling...\\nDewey moved 2 meters\\nFell over...I'm new at this
diff --git a/src/files/BrsFile.spec.ts b/src/files/BrsFile.spec.ts
index 2cae97a6..50908c1a 100644
--- a/src/files/BrsFile.spec.ts
+++ b/src/files/BrsFile.spec.ts
@@ -2381,6 +2381,7 @@ describe('BrsFile', () => {
                 function DoSomething()
                     'lots of empty white space
                     'that will be removed during transpile
+                    'since there are newlines below this comment one newline will be preserved
 
 
 
@@ -2389,6 +2390,8 @@ describe('BrsFile', () => {
                 function DoSomething()
                     'lots of empty white space
                     'that will be removed during transpile
+                    'since there are newlines below this comment one newline will be preserved
+
                 end function
             `);
         });
@@ -2613,6 +2616,28 @@ describe('BrsFile', () => {
             `);
         });
 
+        it('keeps spaces in between comments when a statement ends in a comment ', async () => {
+            await testTranspile(`
+                sub foo()
+                end sub 'comment
+
+                'a function that does something
+                sub foo2()
+                end sub
+            `);
+        });
+
+
+        it('keeps comment in correct place in empty function', async () => {
+            await testTranspile(`
+                sub noop1()
+                end sub
+
+                sub noop2() 'comment in empty function
+                end sub
+            `);
+        });
+
         it('works for a complex function with comments all over the place', async () => {
             await testTranspile(`
                 'import some library
diff --git a/src/files/BrsFile.ts b/src/files/BrsFile.ts
index 8a9dcc8d..4a59fe3b 100644
--- a/src/files/BrsFile.ts
+++ b/src/files/BrsFile.ts
@@ -251,6 +251,17 @@ export class BrsFile implements BscFile {
         }
     }
 
+    /**
+     * Get the token at the specified position, or the next token
+     */
+    public getCurrentOrNextTokenAt(position: Position) {
+        for (let token of this.parser.tokens) {
+            if (util.comparePositionToRange(position, token.range) < 0) {
+                return token;
+            }
+        }
+    }
+
     /**
      * Walk the AST and find the expression that this token is most specifically contained within
      */
@@ -471,9 +482,11 @@ export class BrsFile implements BscFile {
         const processor = new CommentFlagProcessor(this, ['rem', `'`], diagnosticCodes, [DiagnosticCodeMap.unknownDiagnosticCode]);
 
         this.commentFlags = [];
-        for (let token of tokens) {
-            if (token.kind === TokenKind.Comment) {
-                processor.tryAdd(token.text, token.range);
+        for (let lexerToken of tokens) {
+            for (let triviaToken of lexerToken.leadingTrivia ?? []) {
+                if (triviaToken.kind === TokenKind.Comment) {
+                    processor.tryAdd(triviaToken.text, triviaToken.range);
+                }
             }
         }
         this.commentFlags.push(...processor.commentFlags);
@@ -737,9 +750,9 @@ export class BrsFile implements BscFile {
         return false;
     }
 
-    public getTokensUntil(currentToken: Token, tokenKind: TokenKind, direction: -1 | 1 = -1) {
+    public getTokensUntil(currentToken: Token, tokenKind: TokenKind, direction: -1 | 1 = 1) {
         let tokens = [];
-        for (let i = this.parser.tokens.indexOf(currentToken); direction === -1 ? i >= 0 : i === this.parser.tokens.length; i += direction) {
+        for (let i = this.parser.tokens.indexOf(currentToken); direction === -1 ? i >= 0 : i < this.parser.tokens.length; i += direction) {
             currentToken = this.parser.tokens[i];
             if (currentToken.kind === TokenKind.Newline || currentToken.kind === tokenKind) {
                 break;
@@ -749,6 +762,16 @@ export class BrsFile implements BscFile {
         return tokens;
     }
 
+    public getNextTokenByPredicate(currentToken: Token, test: (Token) => boolean, direction: -1 | 1 = 1) {
+        for (let i = this.parser.tokens.indexOf(currentToken); direction === -1 ? i >= 0 : i < this.parser.tokens.length; i += direction) {
+            currentToken = this.parser.tokens[i];
+            if (test(currentToken)) {
+                return currentToken;
+            }
+        }
+        return undefined;
+    }
+
     public getPreviousToken(token: Token) {
         const parser = this.parser;
         let idx = parser.tokens.indexOf(token);
@@ -1054,7 +1077,18 @@ export class BrsFile implements BscFile {
         let transpileResult: SourceNode | undefined;
 
         if (this.needsTranspiled) {
-            transpileResult = util.sourceNodeFromTranspileResult(null, null, state.srcPath, this.ast.transpile(state));
+            const astTranspile = this.ast.transpile(state);
+            const trailingComments = [];
+            if (util.hasLeadingComments(this.parser.eofToken)) {
+                if (util.isLeadingCommentOnSameLine(this.ast.statements[this.ast.statements.length - 1], this.parser.eofToken)) {
+                    trailingComments.push(' ');
+                } else {
+                    trailingComments.push('\n');
+                }
+                trailingComments.push(...state.transpileLeadingComments(this.parser.eofToken));
+            }
+
+            transpileResult = util.sourceNodeFromTranspileResult(null, null, state.srcPath, [...astTranspile, ...trailingComments]);
         } else if (this.program.options.sourceMap) {
             //emit code as-is with a simple map to the original file location
             transpileResult = util.simpleMap(state.srcPath, this.fileContents);
diff --git a/src/lexer/Lexer.spec.ts b/src/lexer/Lexer.spec.ts
index d167a19a..9066b30d 100644
--- a/src/lexer/Lexer.spec.ts
+++ b/src/lexer/Lexer.spec.ts
@@ -134,7 +134,6 @@ describe('lexer', () => {
         expect(tokens.map(t => t.kind)).to.deep.equal([
             TokenKind.Newline,
             TokenKind.Newline,
-            TokenKind.Comment,
             TokenKind.Newline,
             TokenKind.Newline,
             TokenKind.Newline,
@@ -235,12 +234,32 @@ describe('lexer', () => {
     });
 
     describe('comments', () => {
-        it('does not include carriage return character', () => {
+        it('are not included in output', () => {
+            let kinds = Lexer.scan(`
+                'comment
+                REM some comment
+                print 1 ' comment after
+            `).tokens
+                .map(x => x.kind);
+
+            expect(kinds.length).to.eq(7);
+            expect(kinds).to.eql([
+                TokenKind.Newline,
+                TokenKind.Newline,
+                TokenKind.Newline,
+                TokenKind.Print,
+                TokenKind.IntegerLiteral,
+                TokenKind.Newline,
+                TokenKind.Eof
+            ]);
+        });
+
+        it('do not remove Newline token from output', () => {
             let tokens = Lexer.scan(`'someComment\r\nprint "hello"`).tokens;
-            expect(tokens[0].text).to.equal(`'someComment`);
+            expect(tokens[0].kind).to.equal(TokenKind.Newline);
         });
 
-        it('includes the comment characters in the text', () => {
+        it('includes no comment characters in the text', () => {
             let text = Lexer.scan(`
                 'comment
                 REM some comment
@@ -248,10 +267,7 @@ describe('lexer', () => {
                 .filter(x => ![TokenKind.Newline, TokenKind.Eof].includes(x.kind))
                 .map(x => x.text);
 
-            expect(text).to.eql([
-                `'comment`,
-                'REM some comment'
-            ]);
+            expect(text).to.eql([]);
         });
 
         it('tracks the correct location', () => {
@@ -273,7 +289,7 @@ describe('lexer', () => {
                 [1, 24, 1, 25, '('],
                 [1, 25, 1, 26, ')'],
                 [1, 26, 1, 27, ' '],
-                [1, 27, 1, 41, `'first comment`],
+                //  skip `'first comment`
                 [1, 41, 1, 42, '\n'],
                 [2, 0, 2, 20, '                    '],
                 [2, 20, 2, 21, 'k'],
@@ -282,10 +298,10 @@ describe('lexer', () => {
                 [2, 23, 2, 24, ' '],
                 [2, 24, 2, 25, '2'],
                 [2, 25, 2, 26, ' '],
-                [2, 26, 2, 42, `' second comment`],
+                //  skip `' second comment`
                 [2, 42, 2, 43, '\n'],
                 [3, 0, 3, 20, '                    '],
-                [3, 20, 3, 37, 'REM third comment'],
+                //  skip `REM third comment`
                 [3, 37, 3, 38, '\n'],
                 [4, 0, 4, 16, '                '],
                 [4, 16, 4, 23, 'end sub'],
@@ -295,21 +311,6 @@ describe('lexer', () => {
             ]);
         });
 
-        it('tracks the correct location for comments', () => {
-            let tokens = Lexer.scan(`
-                'comment
-                REM some comment
-            `).tokens.filter(x => ![TokenKind.Newline, TokenKind.Eof].includes(x.kind));
-
-            expect(tokens[0].range).to.eql(
-                Range.create(1, 16, 1, 24)
-            );
-
-            expect(tokens[1].range).to.eql(
-                Range.create(2, 16, 2, 32)
-            );
-        });
-
         it('finds correct location for newlines', () => {
             let tokens = Lexer.scan('sub\nsub\r\nsub\n\n').tokens
                 //ignore the Eof token
@@ -325,37 +326,20 @@ describe('lexer', () => {
                 Range.create(3, 0, 3, 1) //  /n
             ]);
         });
-        it('finds correct location for comment after if statement', () => {
-            let { tokens } = Lexer.scan(`
-                sub a()
-                    if true then
-                        print false
-                    else if true then
-                        print "true"
-                    else
-                        print "else"
-                    end if 'comment
-                end sub
-            `);
-            let comments = tokens.filter(x => x.kind === TokenKind.Comment);
-            expect(comments).to.be.lengthOf(1);
-            expect(comments[0].range).to.eql(
-                Range.create(8, 27, 8, 35)
-            );
-        });
+
         it('ignores everything after `\'`', () => {
             let { tokens } = Lexer.scan('= \' (');
-            expect(tokens.map(t => t.kind)).to.deep.equal([TokenKind.Equal, TokenKind.Comment, TokenKind.Eof]);
+            expect(tokens.map(t => t.kind)).to.deep.equal([TokenKind.Equal, TokenKind.Eof]);
         });
 
         it('ignores everything after `REM`', () => {
             let { tokens } = Lexer.scan('= REM (');
-            expect(tokens.map(t => t.kind)).to.deep.equal([TokenKind.Equal, TokenKind.Comment, TokenKind.Eof]);
+            expect(tokens.map(t => t.kind)).to.deep.equal([TokenKind.Equal, TokenKind.Eof]);
         });
 
         it('ignores everything after `rem`', () => {
             let { tokens } = Lexer.scan('= rem (');
-            expect(tokens.map(t => t.kind)).to.deep.equal([TokenKind.Equal, TokenKind.Comment, TokenKind.Eof]);
+            expect(tokens.map(t => t.kind)).to.deep.equal([TokenKind.Equal, TokenKind.Eof]);
         });
     }); // comments
 
diff --git a/src/lexer/Lexer.ts b/src/lexer/Lexer.ts
index f1e795b4..505a99b8 100644
--- a/src/lexer/Lexer.ts
+++ b/src/lexer/Lexer.ts
@@ -1071,6 +1071,7 @@ export class Lexer {
             leadingWhitespace: this.leadingWhitespace,
             leadingTrivia: []
         };
+
         if (this.isTrivia(token)) {
             this.pushTrivia(token);
         } else {
@@ -1078,7 +1079,9 @@ export class Lexer {
             this.leadingTrivia = [];
         }
         this.leadingWhitespace = '';
-        this.tokens.push(token);
+        if (kind !== TokenKind.Comment) {
+            this.tokens.push(token);
+        }
         this.sync();
         return token;
     }
diff --git a/src/lexer/Token.ts b/src/lexer/Token.ts
index be5934a1..d0f5c83b 100644
--- a/src/lexer/Token.ts
+++ b/src/lexer/Token.ts
@@ -45,7 +45,7 @@ export interface Identifier extends Token {
  * @returns `true` is `obj` is a `Token`, otherwise `false`
  */
 export function isToken(obj: Record<string, any>): obj is Token {
-    return !!(obj.kind && obj.text);
+    return !!(obj?.kind && (obj.text || obj.kind === TokenKind.Eof));
 }
 
 /**
diff --git a/src/parser/AstNode.ts b/src/parser/AstNode.ts
index 46dfbaeb..6576eab0 100644
--- a/src/parser/AstNode.ts
+++ b/src/parser/AstNode.ts
@@ -20,7 +20,7 @@ export abstract class AstNode {
     /**
      *  The starting and ending location of the node.
      */
-    public abstract range: Range | undefined;
+    public abstract range?: Range | undefined;
 
     public abstract transpile(state: BrsTranspileState): TranspileResult;
 
@@ -224,7 +224,6 @@ export enum AstNodeKind {
     EmptyStatement = 'EmptyStatement',
     AssignmentStatement = 'AssignmentStatement',
     ExpressionStatement = 'ExpressionStatement',
-    CommentStatement = 'CommentStatement',
     ExitForStatement = 'ExitForStatement',
     ExitWhileStatement = 'ExitWhileStatement',
     FunctionStatement = 'FunctionStatement',
diff --git a/src/parser/Expression.ts b/src/parser/Expression.ts
index a5712647..9f9ac755 100644
--- a/src/parser/Expression.ts
+++ b/src/parser/Expression.ts
@@ -1,7 +1,7 @@
 /* eslint-disable no-bitwise */
 import type { Token, Identifier } from '../lexer/Token';
 import { TokenKind } from '../lexer/TokenKind';
-import type { Block, CommentStatement, FunctionStatement, NamespaceStatement } from './Statement';
+import type { Block, FunctionStatement, NamespaceStatement } from './Statement';
 import type { Range } from 'vscode-languageserver';
 import util from '../util';
 import type { BrsTranspileState } from './BrsTranspileState';
@@ -10,7 +10,7 @@ import * as fileUrl from 'file-url';
 import type { WalkOptions, WalkVisitor } from '../astUtils/visitors';
 import { WalkMode } from '../astUtils/visitors';
 import { walk, InternalWalkMode, walkArray } from '../astUtils/visitors';
-import { isAALiteralExpression, isAAMemberExpression, isArrayLiteralExpression, isArrayType, isCallExpression, isCallableType, isCallfuncExpression, isCommentStatement, isComponentType, isDottedGetExpression, isEscapedCharCodeLiteralExpression, isFunctionExpression, isFunctionStatement, isIntegerType, isInterfaceMethodStatement, isLiteralBoolean, isLiteralExpression, isLiteralNumber, isLiteralString, isLongIntegerType, isMethodStatement, isNamespaceStatement, isNewExpression, isReferenceType, isStringType, isTypeCastExpression, isUnaryExpression, isVariableExpression } from '../astUtils/reflection';
+import { isAALiteralExpression, isAAMemberExpression, isArrayLiteralExpression, isArrayType, isCallExpression, isCallableType, isCallfuncExpression, isComponentType, isDottedGetExpression, isEscapedCharCodeLiteralExpression, isFunctionExpression, isFunctionStatement, isIntegerType, isInterfaceMethodStatement, isLiteralBoolean, isLiteralExpression, isLiteralNumber, isLiteralString, isLongIntegerType, isMethodStatement, isNamespaceStatement, isNewExpression, isReferenceType, isStringType, isTypeCastExpression, isUnaryExpression, isVariableExpression } from '../astUtils/reflection';
 import type { GetTypeOptions, TranspileResult, TypedefProvider } from '../interfaces';
 import { TypeChainEntry } from '../interfaces';
 import { VoidType } from '../types/VoidType';
@@ -58,7 +58,7 @@ export class BinaryExpression extends Expression {
 
     public readonly range: Range | undefined;
 
-    transpile(state: BrsTranspileState) {
+    transpile(state: BrsTranspileState): TranspileResult {
         return [
             state.sourceNode(this.left, this.left.transpile(state)),
             ' ',
@@ -94,6 +94,9 @@ export class BinaryExpression extends Expression {
         return DynamicType.instance;
     }
 
+    getLeadingTrivia(): Token[] {
+        return this.left.getLeadingTrivia();
+    }
 }
 
 
@@ -182,6 +185,10 @@ export class CallExpression extends Expression {
         }
         return new TypePropertyReferenceType(calleeType, 'returnType');
     }
+
+    getLeadingTrivia(): Token[] {
+        return this.callee.getLeadingTrivia();
+    }
 }
 
 export class FunctionExpression extends Expression implements TypedefProvider {
@@ -295,17 +302,18 @@ export class FunctionExpression extends Expression implements TypedefProvider {
                 ...this.returnTypeExpression.transpile(state)
             );
         }
+        let hasBody = false;
         if (includeBody) {
             state.lineage.unshift(this);
             let body = this.body.transpile(state);
+            hasBody = body.length > 0;
             state.lineage.shift();
             results.push(...body);
         }
-        results.push('\n');
-        //'end sub'|'end function'
+
+        const lastLocatable = hasBody ? this.body : this.returnTypeExpression ?? this.tokens.leftParen ?? this.tokens.functionType;
         results.push(
-            state.indent(),
-            state.transpileToken(this.tokens.endFunctionType, `end ${this.tokens.functionType ?? 'function'}`)
+            ...state.transpileEndBlockToken(lastLocatable, this.tokens.endFunctionType, `end ${this.tokens.functionType ?? 'function'}`)
         );
         return results;
     }
@@ -437,10 +445,10 @@ export class FunctionParameterExpression extends Expression {
     }
 
     public transpile(state: BrsTranspileState) {
-        let result = [
+        let result: TranspileResult = [
             //name
             state.transpileToken(this.tokens.name)
-        ] as any[];
+        ];
         //default value
         if (this.defaultValue) {
             result.push(' = ');
@@ -486,6 +494,10 @@ export class FunctionParameterExpression extends Expression {
             walk(this, 'typeExpression', visitor, options);
         }
     }
+
+    getLeadingTrivia(): Token[] {
+        return this.tokens.name.leadingTrivia ?? [];
+    }
 }
 
 export class DottedGetExpression extends Expression {
@@ -561,6 +573,10 @@ export class DottedGetExpression extends Expression {
         return util.getAllDottedGetPartsAsString(this, parseMode);
     }
 
+    getLeadingTrivia(): Token[] {
+        return this.obj.getLeadingTrivia();
+    }
+
 }
 
 export class XmlAttributeGetExpression extends Expression {
@@ -602,6 +618,10 @@ export class XmlAttributeGetExpression extends Expression {
             walk(this, 'obj', visitor, options);
         }
     }
+
+    getLeadingTrivia(): Token[] {
+        return this.obj.getLeadingTrivia();
+    }
 }
 
 export class IndexedGetExpression extends Expression {
@@ -680,6 +700,10 @@ export class IndexedGetExpression extends Expression {
         }
         return super.getType(options);
     }
+
+    getLeadingTrivia(): Token[] {
+        return this.obj.getLeadingTrivia();
+    }
 }
 
 export class GroupingExpression extends Expression {
@@ -727,6 +751,10 @@ export class GroupingExpression extends Expression {
     getType(options: GetTypeOptions) {
         return this.expression.getType(options);
     }
+
+    getLeadingTrivia(): Token[] {
+        return this.tokens.leftParen?.leadingTrivia ?? [];
+    }
 }
 
 export class LiteralExpression extends Expression {
@@ -770,13 +798,17 @@ export class LiteralExpression extends Expression {
         }
 
         return [
-            state.sourceNode(this, text)
+            state.transpileToken({ ...this.tokens.value, text: text })
         ];
     }
 
     walk(visitor: WalkVisitor, options: WalkOptions) {
         //nothing to walk
     }
+
+    getLeadingTrivia(): Token[] {
+        return this.tokens.value.leadingTrivia ?? [];
+    }
 }
 
 /**
@@ -813,7 +845,7 @@ export class EscapedCharCodeLiteralExpression extends Expression {
 
 export class ArrayLiteralExpression extends Expression {
     constructor(options: {
-        elements: Array<Expression | CommentStatement>;
+        elements: Array<Expression>;
         open?: Token;
         close?: Token;
     }) {
@@ -826,7 +858,7 @@ export class ArrayLiteralExpression extends Expression {
         this.range = util.createBoundingRange(this.tokens.open, ...this.elements, this.tokens.close);
     }
 
-    public readonly elements: Array<Expression | CommentStatement>;
+    public readonly elements: Array<Expression>;
 
     public readonly tokens: {
         readonly open?: Token;
@@ -838,7 +870,7 @@ export class ArrayLiteralExpression extends Expression {
     public readonly range: Range | undefined;
 
     transpile(state: BrsTranspileState) {
-        let result = [] as TranspileResult;
+        let result: TranspileResult = [];
         result.push(
             state.transpileToken(this.tokens.open, '[')
         );
@@ -849,39 +881,23 @@ export class ArrayLiteralExpression extends Expression {
             let previousElement = this.elements[i - 1];
             let element = this.elements[i];
 
-            if (isCommentStatement(element)) {
-                //if the comment is on the same line as opening square or previous statement, don't add newline
-                if (util.linesTouch(this.tokens.open, element) || util.linesTouch(previousElement, element)) {
-                    result.push(' ');
-                } else {
-                    result.push(
-                        '\n',
-                        state.indent()
-                    );
-                }
-                state.lineage.unshift(this);
-                result.push(element.transpile(state));
-                state.lineage.shift();
+            if (util.isLeadingCommentOnSameLine(previousElement ?? this.tokens.open, element)) {
+                result.push(' ');
             } else {
-                result.push('\n');
-
                 result.push(
-                    state.indent(),
-                    ...element.transpile(state)
+                    '\n',
+                    state.indent()
                 );
             }
-        }
-        state.blockDepth--;
-        //add a newline between open and close if there are elements
-        if (hasChildren) {
-            result.push('\n');
-            result.push(state.indent());
-        }
-        if (this.tokens.close) {
             result.push(
-                state.transpileToken(this.tokens.close)
+                ...element.transpile(state)
             );
         }
+        state.blockDepth--;
+        //add a newline between open and close if there are elements
+        const lastLocatable = this.elements[this.elements.length - 1] ?? this.tokens.open;
+        result.push(...state.transpileEndBlockToken(lastLocatable, this.tokens.close, ']', hasChildren));
+
         return result;
     }
 
@@ -892,9 +908,12 @@ export class ArrayLiteralExpression extends Expression {
     }
 
     getType(options: GetTypeOptions): BscType {
-        const innerTypes = this.elements.filter(x => !isCommentStatement(x)).map(expr => expr.getType(options));
+        const innerTypes = this.elements.map(expr => expr.getType(options));
         return new ArrayType(...innerTypes);
     }
+    getLeadingTrivia(): Token[] {
+        return this.tokens.open.leadingTrivia ?? [];
+    }
 }
 
 export class AAMemberExpression extends Expression {
@@ -941,11 +960,15 @@ export class AAMemberExpression extends Expression {
         return this.value.getType(options);
     }
 
+    getLeadingTrivia(): Token[] {
+        return this.tokens.key.leadingTrivia ?? [];
+    }
+
 }
 
 export class AALiteralExpression extends Expression {
     constructor(options: {
-        elements: Array<AAMemberExpression | CommentStatement>;
+        elements: Array<AAMemberExpression>;
         open?: Token;
         close?: Token;
     }) {
@@ -958,7 +981,7 @@ export class AALiteralExpression extends Expression {
         this.range = util.createBoundingRange(this.tokens.open, ...this.elements, this.tokens.close);
     }
 
-    public readonly elements: Array<AAMemberExpression | CommentStatement>;
+    public readonly elements: Array<AAMemberExpression>;
     public readonly tokens: {
         readonly open?: Token;
         readonly close?: Token;
@@ -969,14 +992,14 @@ export class AALiteralExpression extends Expression {
     public readonly range: Range | undefined;
 
     transpile(state: BrsTranspileState) {
-        let result = [] as TranspileResult;
+        let result: TranspileResult = [];
         //open curly
         result.push(
             state.transpileToken(this.tokens.open, '{')
         );
         let hasChildren = this.elements.length > 0;
         //add newline if the object has children and the first child isn't a comment starting on the same line as opening curly
-        if (hasChildren && (isCommentStatement(this.elements[0]) === false || !util.linesTouch(this.elements[0], this.tokens.open))) {
+        if (hasChildren && !util.isLeadingCommentOnSameLine(this.tokens.open, this.elements[0])) {
             result.push('\n');
         }
         state.blockDepth++;
@@ -986,51 +1009,36 @@ export class AALiteralExpression extends Expression {
             let nextElement = this.elements[i + 1];
 
             //don't indent if comment is same-line
-            if (isCommentStatement(element as any) &&
-                (util.linesTouch(this.tokens.open, element) || util.linesTouch(previousElement, element))
-            ) {
+            if (util.isLeadingCommentOnSameLine(this.tokens.open, element) ||
+                util.isLeadingCommentOnSameLine(previousElement, element)) {
                 result.push(' ');
-
-                //indent line
             } else {
+                //indent line
                 result.push(state.indent());
             }
 
-            //render comments
-            if (isCommentStatement(element)) {
-                result.push(...element.transpile(state));
-            } else {
-                //key
-                result.push(
-                    state.transpileToken(element.tokens.key)
-                );
-                //colon
-                result.push(
-                    state.transpileToken(element.tokens.colon, ':'),
-                    ' '
-                );
-
-                //value
-                result.push(...element.value.transpile(state));
-            }
-
+            //key
+            result.push(
+                state.transpileToken(element.tokens.key)
+            );
+            //colon
+            result.push(
+                state.transpileToken(element.tokens.colon, ':'),
+                ' '
+            );
+            //value
+            result.push(...element.value.transpile(state));
 
             //if next element is a same-line comment, skip the newline
-            if (nextElement && isCommentStatement(nextElement) && nextElement.range?.start.line === element.range?.start.line) {
-
+            if (nextElement && !util.isLeadingCommentOnSameLine(element, nextElement)) {
                 //add a newline between statements
-            } else {
                 result.push('\n');
             }
         }
         state.blockDepth--;
 
-        //only indent the closing curly if we have children
-        if (hasChildren) {
-            result.push(state.indent());
-        }
-        //close curly
-        result.push(state.transpileToken(this.tokens.close, '}'));
+        const lastElement = this.elements[this.elements.length - 1] ?? this.tokens.open;
+        result.push(...state.transpileEndBlockToken(lastElement, this.tokens.close, '}', hasChildren));
 
         return result;
     }
@@ -1050,6 +1058,11 @@ export class AALiteralExpression extends Expression {
         }
         return resultType;
     }
+
+    public getLeadingTrivia(): Token[] {
+        return this.tokens.open.leadingTrivia ?? [];
+    }
+
 }
 
 export class UnaryExpression extends Expression {
@@ -1100,6 +1113,10 @@ export class UnaryExpression extends Expression {
     getType(options: GetTypeOptions): BscType {
         return util.unaryOperatorResultType(this.tokens.operator, this.right.getType(options));
     }
+
+    public getLeadingTrivia(): Token[] {
+        return this.tokens.operator.leadingTrivia ?? [];
+    }
 }
 
 export class VariableExpression extends Expression {
@@ -1126,7 +1143,7 @@ export class VariableExpression extends Expression {
     }
 
     transpile(state: BrsTranspileState) {
-        let result = [] as TranspileResult;
+        let result: TranspileResult = [];
         const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
         //if the callee is the name of a known namespace function
         if (namespace && state.file.calleeIsKnownNamespaceFunction(this, namespace.getName(ParseMode.BrighterScript))) {
@@ -1162,6 +1179,10 @@ export class VariableExpression extends Expression {
         options.typeChain?.push(new TypeChainEntry({ name: nameKey, type: resultType, data: options.data, range: this.range, kind: this.kind }));
         return resultType;
     }
+
+    getLeadingTrivia(): Token[] {
+        return this.tokens.name.leadingTrivia ?? [];
+    }
 }
 
 export class SourceLiteralExpression extends Expression {
@@ -1274,6 +1295,10 @@ export class SourceLiteralExpression extends Expression {
     walk(visitor: WalkVisitor, options: WalkOptions) {
         //nothing to walk
     }
+
+    getLeadingTrivia(): Token[] {
+        return this.tokens.value.leadingTrivia ?? [];
+    }
 }
 
 /**
@@ -1340,6 +1365,10 @@ export class NewExpression extends Expression {
         }
         return result;
     }
+
+    getLeadingTrivia(): Token[] {
+        return this.tokens.new.leadingTrivia ?? [];
+    }
 }
 
 export class CallfuncExpression extends Expression {
@@ -1455,6 +1484,10 @@ export class CallfuncExpression extends Expression {
 
         return result;
     }
+
+    getLeadingTrivia(): Token[] {
+        return this.callee.getLeadingTrivia();
+    }
 }
 
 /**
@@ -1749,7 +1782,7 @@ export class AnnotationExpression extends Expression {
     }
 
     public getLeadingTrivia(): Token[] {
-        return this.tokens.at?.leadingTrivia;
+        return this.tokens.at?.leadingTrivia ?? [];
     }
 
     transpile(state: BrsTranspileState) {
@@ -1872,6 +1905,10 @@ export class TernaryExpression extends Expression {
             walk(this, 'alternate', visitor, options);
         }
     }
+
+    getLeadingTrivia(): Token[] {
+        return this.test.getLeadingTrivia();
+    }
 }
 
 export class NullCoalescingExpression extends Expression {
@@ -1970,6 +2007,10 @@ export class NullCoalescingExpression extends Expression {
             walk(this, 'alternate', visitor, options);
         }
     }
+
+    getLeadingTrivia(): Token[] {
+        return this.consequent.getLeadingTrivia();
+    }
 }
 
 export class RegexLiteralExpression extends Expression {
@@ -2019,6 +2060,10 @@ export class RegexLiteralExpression extends Expression {
     walk(visitor: WalkVisitor, options: WalkOptions) {
         //nothing to walk
     }
+
+    getLeadingTrivia(): Token[] {
+        return this.tokens.regexLiteral?.leadingTrivia ?? [];
+    }
 }
 
 // eslint-disable-next-line @typescript-eslint/consistent-indexed-object-style
@@ -2044,14 +2089,11 @@ function expressionToValue(expr: Expression, strict: boolean): ExpressionValue {
     }
     if (isArrayLiteralExpression(expr)) {
         return expr.elements
-            .filter(e => !isCommentStatement(e))
             .map(e => expressionToValue(e, strict));
     }
     if (isAALiteralExpression(expr)) {
         return expr.elements.reduce((acc, e) => {
-            if (!isCommentStatement(e)) {
-                acc[e.tokens.key.text] = expressionToValue(e.value, strict);
-            }
+            acc[e.tokens.key.text] = expressionToValue(e.value, strict);
             return acc;
         }, {});
     }
diff --git a/src/parser/Parser.spec.ts b/src/parser/Parser.spec.ts
index 07771312..370260b6 100644
--- a/src/parser/Parser.spec.ts
+++ b/src/parser/Parser.spec.ts
@@ -8,7 +8,7 @@ import type { AssignmentStatement, ClassStatement, InterfaceStatement, ReturnSta
 import { PrintStatement, FunctionStatement, NamespaceStatement, ImportStatement } from './Statement';
 import { Range } from 'vscode-languageserver';
 import { DiagnosticMessages } from '../DiagnosticMessages';
-import { isAssignmentStatement, isBinaryExpression, isBlock, isCallExpression, isClassStatement, isCommentStatement, isDottedGetExpression, isExpression, isExpressionStatement, isFunctionStatement, isGroupingExpression, isIfStatement, isIndexedGetExpression, isInterfaceStatement, isLiteralExpression, isNamespaceStatement, isPrintStatement, isTypeCastExpression, isUnaryExpression, isVariableExpression } from '../astUtils/reflection';
+import { isAssignmentStatement, isBinaryExpression, isBlock, isCallExpression, isClassStatement, isDottedGetExpression, isExpression, isExpressionStatement, isFunctionStatement, isGroupingExpression, isIfStatement, isIndexedGetExpression, isInterfaceStatement, isLiteralExpression, isNamespaceStatement, isPrintStatement, isTypeCastExpression, isUnaryExpression, isVariableExpression } from '../astUtils/reflection';
 import { expectDiagnosticsIncludes, expectTypeToBe, expectZeroDiagnostics } from '../testHelpers.spec';
 import { createVisitor, WalkMode } from '../astUtils/visitors';
 import type { Expression, Statement } from './AstNode';
@@ -569,19 +569,19 @@ describe('parser', () => {
         });
 
         describe('comments', () => {
-            it('combines multi-line comments', () => {
+            it('does not include comments', () => {
                 let { tokens } = Lexer.scan(`
                     'line 1
                     'line 2
                     'line 3
                 `);
                 let { diagnostics, statements } = Parser.parse(tokens) as any;
-                expect(diagnostics).to.be.lengthOf(0, 'Error count should be 0');
+                expectZeroDiagnostics(diagnostics);
 
-                expect(statements[0].text).to.equal(`'line 1\n'line 2\n'line 3`);
+                expect(statements.length).to.equal(0);
             });
 
-            it('does not combile comments separated by newlines', () => {
+            it('does matter if comments separated by newlines', () => {
                 let { tokens } = Lexer.scan(`
                     'line 1
 
@@ -590,13 +590,8 @@ describe('parser', () => {
                     'line 3
                 `);
                 let { diagnostics, statements } = Parser.parse(tokens) as any;
-                expect(diagnostics).to.be.lengthOf(0, 'Error count should be 0');
-
-                expect(statements).to.be.lengthOf(3);
-
-                expect(statements[0].text).to.equal(`'line 1`);
-                expect(statements[1].text).to.equal(`'line 2`);
-                expect(statements[2].text).to.equal(`'line 3`);
+                expectZeroDiagnostics(diagnostics);
+                expect(statements).to.be.lengthOf(0);
             });
 
             it('works after print statement', () => {
@@ -606,12 +601,12 @@ describe('parser', () => {
                     end sub
                 `);
                 let { diagnostics, statements } = Parser.parse(tokens);
-                expect(diagnostics).to.be.lengthOf(0, 'Error count should be 0');
+                expectZeroDiagnostics(diagnostics);
 
-                expect((statements as any)[0].func.body.statements[1].text).to.equal(`'comment 1`);
+                expect((statements[0] as FunctionStatement).func.body.statements.length).to.equal(1);
             });
 
-            it('declaration-level', () => {
+            it('declaration-level should be set as leading trivia', () => {
                 let { tokens } = Lexer.scan(`
                     'comment 1
                     function a()
@@ -620,8 +615,7 @@ describe('parser', () => {
                 `);
                 let { diagnostics, statements } = Parser.parse(tokens);
                 expect(diagnostics).to.be.lengthOf(0, 'Error count should be 0');
-                expect((statements as any)[0].text).to.equal(`'comment 1`);
-                expect((statements as any)[2].text).to.equal(`'comment 2`);
+                expect(statements[0].getLeadingTrivia()[2].text).to.equal(`'comment 1`);
             });
 
             it('works in aa literal as its own statement', () => {
@@ -642,10 +636,9 @@ describe('parser', () => {
                         DoSomething(name) 'comment 1
                     end sub
                 `);
-                let { diagnostics, statements } = Parser.parse(tokens) as any;
+                let { diagnostics } = Parser.parse(tokens) as any;
                 expect(diagnostics).to.be.lengthOf(0, 'Should have zero diagnostics');
 
-                expect(statements[0].func.body.statements[2].text).to.equal(`'comment 1`);
             });
 
             it('function', () => {
@@ -659,10 +652,7 @@ describe('parser', () => {
                 let { diagnostics, statements } = Parser.parse(tokens) as any;
                 expect(diagnostics).to.be.lengthOf(0, 'Should have zero diagnostics');
 
-                expect(statements[0].func.body.statements[0].text).to.equal(`'comment 1`);
-                expect(statements[0].func.body.statements[1].text).to.equal(`'comment 2`);
-                expect(statements[0].func.body.statements[3].text).to.equal(`'comment 3`);
-                expect(statements[1].text).to.equal(`'comment 4`);
+                expect(statements[0].func.body.statements[0].getLeadingTrivia().filter(x => x.kind === TokenKind.Comment).map(x => x.text)).members([`'comment 1`, `'comment 2`]);
             });
 
             it('if statement`', () => {
@@ -689,21 +679,15 @@ describe('parser', () => {
                 if (isFunctionStatement(fnSmt)) {
                     let ifStmt = fnSmt.func.body.statements[0];
                     if (isIfStatement(ifStmt)) {
-                        expectCommentWithText(ifStmt.thenBranch.statements[0], `'comment 1`);
-                        expectCommentWithText(ifStmt.thenBranch.statements[1], `'comment 2`);
-                        expectCommentWithText(ifStmt.thenBranch.statements[3], `'comment 3`);
+                        expectCommentWithText(ifStmt.thenBranch.statements[0], `'comment 1\n'comment 2`);
 
                         let elseIfBranch = ifStmt.elseBranch!;
                         if (isIfStatement(elseIfBranch)) {
-                            expectCommentWithText(elseIfBranch.thenBranch.statements[0], `'comment 4`);
-                            expectCommentWithText(elseIfBranch.thenBranch.statements[1], `'comment 5`);
-                            expectCommentWithText(elseIfBranch.thenBranch.statements[3], `'comment 6`);
+                            expectCommentWithText(elseIfBranch.thenBranch.statements[0], `'comment 4\n'comment 5`);
 
                             let elseBranch = elseIfBranch.elseBranch!;
                             if (isBlock(elseBranch)) {
-                                expectCommentWithText(elseBranch.statements[0], `'comment 7`);
-                                expectCommentWithText(elseBranch.statements[1], `'comment 8`);
-                                expectCommentWithText(elseBranch.statements[3], `'comment 9`);
+                                expectCommentWithText(elseBranch.statements[0], `'comment 7\n'comment 8`);
                             } else {
                                 failStatementType(elseBranch, 'Block');
                             }
@@ -711,7 +695,6 @@ describe('parser', () => {
                         } else {
                             failStatementType(elseIfBranch, 'If');
                         }
-                        expectCommentWithText(fnSmt.func.body.statements[1], `'comment 10`);
                     } else {
                         failStatementType(ifStmt, 'If');
                     }
@@ -734,11 +717,7 @@ describe('parser', () => {
                 expect(diagnostics).to.be.lengthOf(0, 'Error count should be zero');
                 let stmt = statements[0].func.body.statements[0];
 
-                expect(stmt.body.statements[0].text).to.equal(`'comment 1`);
-                expect(stmt.body.statements[1].text).to.equal(`'comment 2`);
-                expect(stmt.body.statements[3].text).to.equal(`'comment 3`);
-
-                expect(statements[0].func.body.statements[1].text).to.equal(`'comment 4`);
+                expectCommentWithText(stmt.body.statements[0], `'comment 1\n'comment 2`);
             });
 
             it('for', () => {
@@ -755,11 +734,7 @@ describe('parser', () => {
                 expect(diagnostics).to.be.lengthOf(0, 'Error count should be zero');
                 let stmt = statements[0].func.body.statements[0];
 
-                expect(stmt.body.statements[0].text).to.equal(`'comment 1`);
-                expect(stmt.body.statements[1].text).to.equal(`'comment 2`);
-                expect(stmt.body.statements[3].text).to.equal(`'comment 3`);
-
-                expect(statements[0].func.body.statements[1].text).to.equal(`'comment 4`);
+                expectCommentWithText(stmt.body.statements[0], `'comment 1\n'comment 2`);
             });
 
             it('for each', () => {
@@ -776,11 +751,7 @@ describe('parser', () => {
                 expect(diagnostics).to.be.lengthOf(0, 'Error count should be zero');
                 let stmt = statements[0].func.body.statements[0];
 
-                expect(stmt.body.statements[0].text).to.equal(`'comment 1`);
-                expect(stmt.body.statements[1].text).to.equal(`'comment 2`);
-                expect(stmt.body.statements[3].text).to.equal(`'comment 3`);
-
-                expect(statements[0].func.body.statements[1].text).to.equal(`'comment 4`);
+                expectCommentWithText(stmt.body.statements[0], `'comment 1\n'comment 2`);
             });
 
         });
@@ -1905,8 +1876,9 @@ export function rangeToArray(range: Range) {
 }
 
 function expectCommentWithText(stat: Statement, text: string) {
-    if (isCommentStatement(stat)) {
-        expect(stat.text).to.equal(text);
+    const trivia = stat.getLeadingTrivia();
+    if (trivia) {
+        expect(trivia.filter(tok => tok.kind === TokenKind.Comment).map(t => t.text).join('\n')).to.equal(text);
     } else {
         failStatementType(stat, 'Comment');
     }
diff --git a/src/parser/Parser.ts b/src/parser/Parser.ts
index 8b197dc5..1618c782 100644
--- a/src/parser/Parser.ts
+++ b/src/parser/Parser.ts
@@ -25,7 +25,6 @@ import {
     ContinueStatement,
     ClassStatement,
     ConstStatement,
-    CommentStatement,
     DimStatement,
     DottedSetStatement,
     EndStatement,
@@ -113,6 +112,13 @@ export class Parser {
      */
     public ast = new Body({ statements: [] });
 
+    public get eofToken(): Token {
+        const lastToken = this.tokens?.[this.tokens.length - 1];
+        if (lastToken?.kind === TokenKind.Eof) {
+            return lastToken;
+        }
+    }
+
     public get statements() {
         return this.ast.statements;
     }
@@ -291,10 +297,6 @@ export class Parser {
                 return this.annotationExpression();
             }
 
-            if (this.check(TokenKind.Comment)) {
-                return this.commentStatement();
-            }
-
             //catch certain global terminators to prevent unnecessary lookahead (i.e. like `end namespace`, no need to continue)
             if (this.checkAny(...this.peekGlobalTerminators())) {
                 return;
@@ -492,9 +494,6 @@ export class Parser {
                 } else if (this.checkAny(TokenKind.Function, TokenKind.Sub) && this.checkAnyNext(TokenKind.Identifier, ...AllowedProperties)) {
                     decl = this.interfaceMethodStatement(optionalKeyword);
 
-                    //comments
-                } else if (this.check(TokenKind.Comment)) {
-                    decl = this.commentStatement();
                 }
                 if (decl) {
                     this.consumePendingAnnotations(decl);
@@ -540,11 +539,11 @@ export class Parser {
 
         this.consumeStatementSeparators();
 
-        const body: Array<EnumMemberStatement | CommentStatement> = [];
+        const body: Array<EnumMemberStatement> = [];
         //gather up all members
         while (this.checkAny(TokenKind.Comment, TokenKind.Identifier, TokenKind.At, ...AllowedProperties)) {
             try {
-                let decl: EnumMemberStatement | CommentStatement;
+                let decl: EnumMemberStatement;
 
                 //collect leading annotations
                 if (this.check(TokenKind.At)) {
@@ -554,10 +553,6 @@ export class Parser {
                 //members
                 if (this.checkAny(TokenKind.Identifier, ...AllowedProperties)) {
                     decl = this.enumMemberStatement();
-
-                    //comments
-                } else if (this.check(TokenKind.Comment)) {
-                    decl = this.commentStatement();
                 }
 
                 if (decl) {
@@ -680,9 +675,6 @@ export class Parser {
                         });
                     }
 
-                    //comments
-                } else if (this.check(TokenKind.Comment)) {
-                    decl = this.commentStatement();
                 }
 
                 if (decl) {
@@ -1304,22 +1296,6 @@ export class Parser {
         return new ExitForStatement({ exitFor: keyword });
     }
 
-    private commentStatement() {
-        //if this comment is on the same line as the previous statement,
-        //then this comment should be treated as a single-line comment
-        let prev = this.previous();
-        if (prev?.range?.end.line === this.peek().range?.start.line) {
-            return new CommentStatement({ comments: [this.advance()] });
-        } else {
-            let comments = [this.advance()];
-            while (this.check(TokenKind.Newline) && this.checkNext(TokenKind.Comment)) {
-                this.advance();
-                comments.push(this.advance());
-            }
-            return new CommentStatement({ comments: comments });
-        }
-    }
-
     private namespaceStatement(): NamespaceStatement | undefined {
         this.warnIfNotBrighterScriptMode('namespace');
         let keyword = this.advance();
@@ -2740,9 +2716,6 @@ export class Parser {
             case this.check(TokenKind.RegexLiteral):
                 return this.regexLiteralExpression();
 
-            case this.check(TokenKind.Comment):
-                return new CommentStatement({ comments: [this.advance()] });
-
             default:
                 //if we found an expected terminator, don't throw a diagnostic...just return undefined
                 if (this.checkAny(...this.peekGlobalTerminators())) {
@@ -2760,14 +2733,9 @@ export class Parser {
     }
 
     private arrayLiteral() {
-        let elements: Array<Expression | CommentStatement> = [];
+        let elements: Array<Expression> = [];
         let openingSquare = this.previous();
 
-        //add any comment found right after the opening square
-        if (this.check(TokenKind.Comment)) {
-            elements.push(new CommentStatement({ comments: [this.advance()] }));
-        }
-
         while (this.match(TokenKind.Newline)) {
         }
         let closingSquare: Token;
@@ -2777,10 +2745,7 @@ export class Parser {
                 elements.push(this.expression());
 
                 while (this.matchAny(TokenKind.Comma, TokenKind.Newline, TokenKind.Comment)) {
-                    if (this.checkPrevious(TokenKind.Comment) || this.check(TokenKind.Comment)) {
-                        let comment = this.check(TokenKind.Comment) ? this.advance() : this.previous();
-                        elements.push(new CommentStatement({ comments: [comment] }));
-                    }
+
                     while (this.match(TokenKind.Newline)) {
 
                     }
@@ -2809,7 +2774,7 @@ export class Parser {
 
     private aaLiteral() {
         let openingBrace = this.previous();
-        let members: Array<AAMemberExpression | CommentStatement> = [];
+        let members: Array<AAMemberExpression> = [];
 
         let key = () => {
             let result = {
@@ -2842,19 +2807,14 @@ export class Parser {
         if (!this.match(TokenKind.RightCurlyBrace)) {
             let lastAAMember: AAMemberExpression;
             try {
-                if (this.check(TokenKind.Comment)) {
-                    lastAAMember = null;
-                    members.push(new CommentStatement({ comments: [this.advance()] }));
-                } else {
-                    let k = key();
-                    let expr = this.expression();
-                    lastAAMember = new AAMemberExpression({
-                        key: k.keyToken,
-                        colon: k.colonToken,
-                        value: expr
-                    });
-                    members.push(lastAAMember);
-                }
+                let k = key();
+                let expr = this.expression();
+                lastAAMember = new AAMemberExpression({
+                    key: k.keyToken,
+                    colon: k.colonToken,
+                    value: expr
+                });
+                members.push(lastAAMember);
 
                 while (this.matchAny(TokenKind.Comma, TokenKind.Newline, TokenKind.Colon, TokenKind.Comment)) {
                     // collect comma at end of expression
@@ -2862,33 +2822,20 @@ export class Parser {
                         (lastAAMember as DeepWriteable<AAMemberExpression>).tokens.comma = this.previous();
                     }
 
-                    //check for comment at the end of the current line
-                    if (this.check(TokenKind.Comment) || this.checkPrevious(TokenKind.Comment)) {
-                        let token = this.checkPrevious(TokenKind.Comment) ? this.previous() : this.advance();
-                        members.push(new CommentStatement({ comments: [token] }));
-                    } else {
-                        this.consumeStatementSeparators(true);
-
-                        //check for a comment on its own line
-                        if (this.check(TokenKind.Comment) || this.checkPrevious(TokenKind.Comment)) {
-                            let token = this.checkPrevious(TokenKind.Comment) ? this.previous() : this.advance();
-                            lastAAMember = null;
-                            members.push(new CommentStatement({ comments: [token] }));
-                            continue;
-                        }
+                    this.consumeStatementSeparators(true);
 
-                        if (this.check(TokenKind.RightCurlyBrace)) {
-                            break;
-                        }
-                        let k = key();
-                        let expr = this.expression();
-                        lastAAMember = new AAMemberExpression({
-                            key: k.keyToken,
-                            colon: k.colonToken,
-                            value: expr
-                        });
-                        members.push(lastAAMember);
+                    if (this.check(TokenKind.RightCurlyBrace)) {
+                        break;
                     }
+                    let k = key();
+                    let expr = this.expression();
+                    lastAAMember = new AAMemberExpression({
+                        key: k.keyToken,
+                        colon: k.colonToken,
+                        value: expr
+                    });
+                    members.push(lastAAMember);
+
                 }
             } catch (error: any) {
                 this.rethrowNonDiagnosticError(error);
@@ -3071,7 +3018,8 @@ export class Parser {
     }
 
     private isAtEnd(): boolean {
-        return this.peek().kind === TokenKind.Eof;
+        const peekToken = this.peek();
+        return !peekToken || peekToken.kind === TokenKind.Eof;
     }
 
     private peekNext(): Token {
diff --git a/src/parser/SGTypes.ts b/src/parser/SGTypes.ts
index ac07ef8e..591a5d6d 100644
--- a/src/parser/SGTypes.ts
+++ b/src/parser/SGTypes.ts
@@ -1,7 +1,7 @@
 import { SourceNode } from 'source-map';
 import type { Range } from 'vscode-languageserver';
 import { createSGAttribute, createSGInterface, createSGInterfaceField, createSGInterfaceFunction } from '../astUtils/creators';
-import type { FileReference } from '../interfaces';
+import type { FileReference, TranspileResult } from '../interfaces';
 import util from '../util';
 import type { TranspileState } from './TranspileState';
 
@@ -74,7 +74,7 @@ export class SGAttribute {
     private _range = null as Range;
 
     public transpile(state: TranspileState) {
-        const result = [
+        const result: TranspileResult = [
             state.transpileToken(this.tokens.key)
         ];
         if (this.tokens.value) {
@@ -85,7 +85,7 @@ export class SGAttribute {
                 state.transpileToken(this.tokens.closingQuote, '"')
             );
         }
-        return new SourceNode(null, null, null, result);
+        return util.sourceNodeFromTranspileResult(null, null, null, result);
     }
 
     public clone() {
@@ -290,7 +290,7 @@ export class SGElement {
     }
 
     public transpile(state: TranspileState) {
-        return new SourceNode(null, null, null, [
+        return util.sourceNodeFromTranspileResult(null, null, null, [
             state.transpileToken(this.tokens.startTagOpen, '<'), // <
             state.transpileToken(this.tokens.startTagName),
             this.transpileAttributes(state, this.attributes),
@@ -300,13 +300,13 @@ export class SGElement {
 
     protected transpileBody(state: TranspileState) {
         if (this.isSelfClosing) {
-            return new SourceNode(null, null, null, [
+            return util.sourceNodeFromTranspileResult(null, null, null, [
                 ' ',
                 state.transpileToken(this.tokens.startTagClose, '/>'),
                 state.newline
             ]);
         } else {
-            const chunks = [
+            const chunks: TranspileResult = [
                 state.transpileToken(this.tokens.startTagClose, '>'),
                 state.newline
             ];
@@ -325,7 +325,7 @@ export class SGElement {
                 state.transpileToken(this.tokens.endTagClose, '>'),
                 state.newline
             );
-            return new SourceNode(null, null, null, chunks);
+            return util.sourceNodeFromTranspileResult(null, null, null, chunks);
         }
     }
 
@@ -366,7 +366,7 @@ export class SGScript extends SGElement {
 
     protected transpileBody(state: TranspileState) {
         if (this.cdata) {
-            return new SourceNode(null, null, null, [
+            return util.sourceNodeFromTranspileResult(null, null, null, [
                 '>',
                 state.transpileToken(this.cdata),
                 '</',
diff --git a/src/parser/Statement.spec.ts b/src/parser/Statement.spec.ts
index 199853de..9774fc37 100644
--- a/src/parser/Statement.spec.ts
+++ b/src/parser/Statement.spec.ts
@@ -1,10 +1,9 @@
 import { expect } from '../chai-config.spec';
 import type { NamespaceStatement, ClassStatement } from './Statement';
-import { Body, CommentStatement, EmptyStatement } from './Statement';
+import { Body, EmptyStatement } from './Statement';
 import { ParseMode, Parser } from './Parser';
 import { WalkMode } from '../astUtils/visitors';
 import { isClassStatement, isNamespaceStatement } from '../astUtils/reflection';
-import { CancellationTokenSource } from 'vscode-languageserver';
 import { Program } from '../Program';
 import { trim } from '../testHelpers.spec';
 import type { BrsFile } from '../files/BrsFile';
@@ -67,19 +66,6 @@ describe('Statement', () => {
         });
     });
 
-    describe('CommentStatement', () => {
-        describe('walk', () => {
-            it('skips visitor if canceled', () => {
-                const comment = new CommentStatement({ comments: [] });
-                const cancel = new CancellationTokenSource();
-                cancel.cancel();
-                comment.walk(() => {
-                    throw new Error('Should not have been called');
-                }, { walkMode: WalkMode.visitAllRecursive, cancel: cancel.token });
-            });
-        });
-    });
-
     describe('ClassStatement', () => {
         describe('getName', () => {
             it('handles null namespace name', () => {
diff --git a/src/parser/Statement.ts b/src/parser/Statement.ts
index 91165d63..93f5c7bf 100644
--- a/src/parser/Statement.ts
+++ b/src/parser/Statement.ts
@@ -9,12 +9,11 @@ import type { BrsTranspileState } from './BrsTranspileState';
 import { ParseMode } from './Parser';
 import type { WalkVisitor, WalkOptions } from '../astUtils/visitors';
 import { InternalWalkMode, walk, createVisitor, WalkMode, walkArray } from '../astUtils/visitors';
-import { isCallExpression, isCommentStatement, isEnumMemberStatement, isExpression, isExpressionStatement, isFieldStatement, isFunctionStatement, isIfStatement, isInterfaceFieldStatement, isInterfaceMethodStatement, isInvalidType, isLiteralExpression, isMethodStatement, isNamespaceStatement, isTypedefProvider, isUnaryExpression, isVoidType } from '../astUtils/reflection';
+import { isCallExpression, isEnumMemberStatement, isExpression, isExpressionStatement, isFieldStatement, isFunctionStatement, isIfStatement, isInterfaceFieldStatement, isInterfaceMethodStatement, isInvalidType, isLiteralExpression, isMethodStatement, isNamespaceStatement, isTypedefProvider, isUnaryExpression, isVoidType } from '../astUtils/reflection';
 import { TypeChainEntry, type GetTypeOptions, type TranspileResult, type TypedefProvider } from '../interfaces';
 import { createInvalidLiteral, createMethodStatement, createToken } from '../astUtils/creators';
 import { DynamicType } from '../types/DynamicType';
 import type { BscType } from '../types/BscType';
-import type { TranspileState } from './TranspileState';
 import { SymbolTable } from '../SymbolTable';
 import type { Expression } from './AstNode';
 import { AstNodeKind } from './AstNode';
@@ -37,7 +36,7 @@ export class EmptyStatement extends Statement {
     /**
      * Create a negative range to indicate this is an interpolated location
      */
-    public readonly range: Range;
+    public readonly range?: Range;
 
     public readonly kind = AstNodeKind.EmptyStatement;
 
@@ -83,17 +82,16 @@ export class Body extends Statement implements TypedefProvider {
                 //this is the first statement. do nothing related to spacing and newlines
 
                 //if comment is on same line as prior sibling
-            } else if (isCommentStatement(statement) && previousStatement && statement.range?.start.line === previousStatement.range?.end.line) {
+            } else if (util.hasLeadingComments(statement) && previousStatement && util.getLeadingComments(statement)?.[0]?.range?.start.line === previousStatement.range?.end.line) {
                 result.push(
                     ' '
                 );
-
                 //add double newline if this is a comment, and next is a function
-            } else if (isCommentStatement(statement) && nextStatement && isFunctionStatement(nextStatement)) {
+            } else if (util.hasLeadingComments(statement) && nextStatement && isFunctionStatement(nextStatement)) {
                 result.push(state.newline, state.newline);
 
                 //add double newline if is function not preceeded by a comment
-            } else if (isFunctionStatement(statement) && previousStatement && !(isCommentStatement(previousStatement))) {
+            } else if (isFunctionStatement(statement) && previousStatement && !util.hasLeadingComments(statement)) {
                 result.push(state.newline, state.newline);
             } else {
                 //separate statements by a single newline
@@ -154,7 +152,7 @@ export class AssignmentStatement extends Statement {
 
     public readonly value: Expression;
 
-    public readonly typeExpression: TypeExpression;
+    public readonly typeExpression?: TypeExpression;
 
     public readonly kind = AstNodeKind.AssignmentStatement;
 
@@ -190,6 +188,10 @@ export class AssignmentStatement extends Statement {
         options.typeChain?.push(new TypeChainEntry({ name: this.tokens.name.text, type: variableType, data: options.data, range: this.tokens.name.range, kind: this.kind }));
         return variableType;
     }
+
+    getLeadingTrivia(): Token[] {
+        return this.tokens.name.leadingTrivia ?? [];
+    }
 }
 
 export class Block extends Statement {
@@ -207,7 +209,7 @@ export class Block extends Statement {
     }
 
     public readonly statements: Statement[];
-    public readonly startingRange: Range;
+    public readonly startingRange?: Range;
 
     public readonly kind = AstNodeKind.Block;
 
@@ -219,10 +221,10 @@ export class Block extends Statement {
         for (let i = 0; i < this.statements.length; i++) {
             let previousStatement = this.statements[i - 1];
             let statement = this.statements[i];
-
+            //is not a comment
             //if comment is on same line as parent
-            if (isCommentStatement(statement) &&
-                (util.linesTouch(state.lineage[0], statement) || util.linesTouch(previousStatement, statement))
+            if (util.isLeadingCommentOnSameLine(state.lineage[0], statement) ||
+                util.isLeadingCommentOnSameLine(previousStatement, statement)
             ) {
                 results.push(' ');
 
@@ -276,63 +278,13 @@ export class ExpressionStatement extends Statement {
             walk(this, 'expression', visitor, options);
         }
     }
-}
-
-export class CommentStatement extends Statement implements Expression, TypedefProvider {
-    constructor(options: {
-        comments: Token[];
-    }) {
-        super();
-        this.visitMode = InternalWalkMode.visitStatements | InternalWalkMode.visitExpressions;
-        this.tokens = {
-            comments: options.comments
-        };
-        if (this.tokens.comments?.length > 0) {
-            this.range = util.createBoundingRange(
-                ...this.tokens.comments
-            );
-        }
-    }
-
-    public readonly tokens: {
-        readonly comments: Token[];
-    };
-
-    public readonly kind = AstNodeKind.CommentStatement;
-
-    public readonly range: Range | undefined;
-
-    get text() {
-        return this.tokens.comments.map(x => x.text).join('\n');
-    }
-
-    transpile(state: BrsTranspileState) {
-        let result: TranspileResult = [];
-        for (let i = 0; i < this.tokens.comments.length; i++) {
-            let comment = this.tokens.comments[i];
-            if (i > 0) {
-                result.push(state.indent());
-            }
-            result.push(
-                state.transpileToken(comment)
-            );
-            //add newline for all except final comment
-            if (i < this.tokens.comments.length - 1) {
-                result.push(state.newline);
-            }
-        }
-        return result;
-    }
 
-    public getTypedef(state: TranspileState) {
-        return this.transpile(state as BrsTranspileState);
-    }
-
-    walk(visitor: WalkVisitor, options: WalkOptions) {
-        //nothing to walk
+    getLeadingTrivia(): Token[] {
+        return this.expression.getLeadingTrivia();
     }
 }
 
+
 export class ExitForStatement extends Statement {
     constructor(options?: {
         exitFor?: Token;
@@ -350,18 +302,20 @@ export class ExitForStatement extends Statement {
 
     public readonly kind = AstNodeKind.ExitForStatement;
 
-    public readonly range: Range;
+    public readonly range?: Range;
 
     transpile(state: BrsTranspileState) {
-        return [
-            this.tokens.exitFor ? state.transpileToken(this.tokens.exitFor) : 'exit for'
-        ];
+        return this.tokens.exitFor ? state.transpileToken(this.tokens.exitFor) : ['exit for'];
     }
 
     walk(visitor: WalkVisitor, options: WalkOptions) {
         //nothing to walk
     }
 
+    getLeadingTrivia(): Token[] {
+        return this.tokens.exitFor?.leadingTrivia ?? [];
+    }
+
 }
 
 export class ExitWhileStatement extends Statement {
@@ -381,17 +335,19 @@ export class ExitWhileStatement extends Statement {
 
     public readonly kind = AstNodeKind.ExitWhileStatement;
 
-    public readonly range: Range;
+    public readonly range?: Range;
 
     transpile(state: BrsTranspileState) {
-        return [
-            this.tokens.exitWhile ? state.transpileToken(this.tokens.exitWhile) : 'exit while'
-        ];
+        return this.tokens.exitWhile ? state.transpileToken(this.tokens.exitWhile) : ['exit while'];
     }
 
     walk(visitor: WalkVisitor, options: WalkOptions) {
         //nothing to walk
     }
+
+    getLeadingTrivia(): Token[] {
+        return this.tokens.exitWhile?.leadingTrivia ?? [];
+    }
 }
 
 export class FunctionStatement extends Statement implements TypedefProvider {
@@ -448,7 +404,14 @@ export class FunctionStatement extends Statement implements TypedefProvider {
     }
 
     getTypedef(state: BrsTranspileState) {
-        let result = [] as TranspileResult;
+        let result: TranspileResult = [];
+        for (let comment of util.getLeadingComments(this) ?? []) {
+            result.push(
+                comment.text,
+                state.newline,
+                state.indent()
+            );
+        }
         for (let annotation of this.annotations ?? []) {
             result.push(
                 ...annotation.getTypedef(state),
@@ -546,18 +509,11 @@ export class IfStatement extends Statement {
         if (thenNodes.length > 0) {
             results.push(thenNodes);
         }
-        results.push('\n');
-
         //else branch
-        if (this.tokens.else) {
+        if (this.elseBranch) {
             //else
-            results.push(
-                state.indent(),
-                state.transpileToken(this.tokens.else)
-            );
-        }
+            results.push(...state.transpileEndBlockToken(this.thenBranch, this.tokens.else, 'else'));
 
-        if (this.elseBranch) {
             if (isIfStatement(this.elseBranch)) {
                 //chained elseif
                 state.lineage.unshift(this.elseBranch);
@@ -584,19 +540,12 @@ export class IfStatement extends Statement {
                 if (body.length > 0) {
                     results.push(...body);
                 }
-                results.push('\n');
             }
         }
 
         //end if
-        results.push(state.indent());
-        if (this.tokens.endIf) {
-            results.push(
-                state.transpileToken(this.tokens.endIf)
-            );
-        } else {
-            results.push('end if');
-        }
+        results.push(...state.transpileEndBlockToken(this.elseBranch ?? this.thenBranch, this.tokens.endIf, 'end if'));
+
         return results;
     }
 
@@ -611,6 +560,10 @@ export class IfStatement extends Statement {
             walk(this, 'elseBranch', visitor, options);
         }
     }
+
+    getLeadingTrivia(): Token[] {
+        return this.tokens.if?.leadingTrivia ?? [];
+    }
 }
 
 export class IncrementStatement extends Statement {
@@ -650,6 +603,10 @@ export class IncrementStatement extends Statement {
             walk(this, 'value', visitor, options);
         }
     }
+
+    getLeadingTrivia(): Token[] {
+        return this.value?.getLeadingTrivia() ?? [];
+    }
 }
 
 /** Used to indent the current `print` position to the next 16-character-width output zone. */
@@ -722,6 +679,10 @@ export class PrintStatement extends Statement {
             walkArray(this.expressions, visitor, options, this, (item) => isExpression(item as any));
         }
     }
+
+    getLeadingTrivia(): Token[] {
+        return this.tokens.print?.leadingTrivia ?? [];
+    }
 }
 
 export class DimStatement extends Statement {
@@ -795,6 +756,10 @@ export class DimStatement extends Statement {
         }
         return type;
     }
+
+    getLeadingTrivia(): Token[] {
+        return this.tokens.dim?.leadingTrivia ?? [];
+    }
 }
 
 export class GotoStatement extends Statement {
@@ -833,6 +798,10 @@ export class GotoStatement extends Statement {
     walk(visitor: WalkVisitor, options: WalkOptions) {
         //nothing to walk
     }
+
+    getLeadingTrivia(): Token[] {
+        return this.tokens.goto?.leadingTrivia ?? [];
+    }
 }
 
 export class LabelStatement extends Statement {
@@ -916,6 +885,10 @@ export class ReturnStatement extends Statement {
             walk(this, 'value', visitor, options);
         }
     }
+
+    getLeadingTrivia(): Token[] {
+        return this.tokens.return?.leadingTrivia ?? [];
+    }
 }
 
 export class EndStatement extends Statement {
@@ -944,6 +917,10 @@ export class EndStatement extends Statement {
     walk(visitor: WalkVisitor, options: WalkOptions) {
         //nothing to walk
     }
+
+    getLeadingTrivia(): Token[] {
+        return this.tokens.end?.leadingTrivia ?? [];
+    }
 }
 
 export class StopStatement extends Statement {
@@ -971,6 +948,10 @@ export class StopStatement extends Statement {
     walk(visitor: WalkVisitor, options: WalkOptions) {
         //nothing to walk
     }
+
+    getLeadingTrivia(): Token[] {
+        return this.tokens.stop?.leadingTrivia ?? [];
+    }
 }
 
 export class ForStatement extends Statement {
@@ -1057,14 +1038,8 @@ export class ForStatement extends Statement {
         result.push(...this.body.transpile(state));
         state.lineage.shift();
 
-        // add new line before "end for"
-        result.push('\n');
-
         //end for
-        result.push(
-            state.indent(),
-            state.transpileToken(this.tokens.endFor, 'end for')
-        );
+        result.push(...state.transpileEndBlockToken(this.body, this.tokens.endFor, 'end for'));
 
         return result;
     }
@@ -1081,6 +1056,10 @@ export class ForStatement extends Statement {
             walk(this, 'body', visitor, options);
         }
     }
+
+    getLeadingTrivia(): Token[] {
+        return this.tokens.for?.leadingTrivia ?? [];
+    }
 }
 
 export class ForEachStatement extends Statement {
@@ -1149,14 +1128,9 @@ export class ForEachStatement extends Statement {
         result.push(...this.body.transpile(state));
         state.lineage.shift();
 
-        // add new line before "end for"
-        result.push('\n');
-
         //end for
-        result.push(
-            state.indent(),
-            state.transpileToken(this.tokens.endFor, 'end for')
-        );
+        result.push(...state.transpileEndBlockToken(this.body, this.tokens.endFor, 'end for'));
+
         return result;
     }
 
@@ -1168,6 +1142,10 @@ export class ForEachStatement extends Statement {
             walk(this, 'body', visitor, options);
         }
     }
+
+    getLeadingTrivia(): Token[] {
+        return this.tokens.forEach?.leadingTrivia ?? [];
+    }
 }
 
 export class WhileStatement extends Statement {
@@ -1219,14 +1197,8 @@ export class WhileStatement extends Statement {
         result.push(...this.body.transpile(state));
         state.lineage.shift();
 
-        //trailing newline only if we have body statements
-        result.push('\n');
-
         //end while
-        result.push(
-            state.indent(),
-            state.transpileToken(this.tokens.endWhile, 'end while')
-        );
+        result.push(...state.transpileEndBlockToken(this.body, this.tokens.endWhile, 'end while'));
 
         return result;
     }
@@ -1239,6 +1211,10 @@ export class WhileStatement extends Statement {
             walk(this, 'body', visitor, options);
         }
     }
+
+    getLeadingTrivia(): Token[] {
+        return this.tokens.while?.leadingTrivia ?? [];
+    }
 }
 
 export class DottedSetStatement extends Statement {
@@ -1310,6 +1286,10 @@ export class DottedSetStatement extends Statement {
         }));
         return result;
     }
+
+    getLeadingTrivia(): Token[] {
+        return this.obj.getLeadingTrivia();
+    }
 }
 
 export class IndexedSetStatement extends Statement {
@@ -1387,6 +1367,10 @@ export class IndexedSetStatement extends Statement {
             walk(this, 'value', visitor, options);
         }
     }
+
+    getLeadingTrivia(): Token[] {
+        return this.obj.getLeadingTrivia();
+    }
 }
 
 export class LibraryStatement extends Statement implements TypedefProvider {
@@ -1435,6 +1419,10 @@ export class LibraryStatement extends Statement implements TypedefProvider {
     walk(visitor: WalkVisitor, options: WalkOptions) {
         //nothing to walk
     }
+
+    getLeadingTrivia(): Token[] {
+        return this.tokens.library?.leadingTrivia ?? [];
+    }
 }
 
 export class NamespaceStatement extends Statement implements TypedefProvider {
@@ -1514,12 +1502,20 @@ export class NamespaceStatement extends Statement implements TypedefProvider {
         return this.body.transpile(state);
     }
 
-    getTypedef(state: BrsTranspileState): TranspileResult {
-        let result = [
-            'namespace ',
+    getTypedef(state: BrsTranspileState) {
+        let result: TranspileResult = [];
+        for (let comment of util.getLeadingComments(this) ?? []) {
+            result.push(
+                comment.text,
+                state.newline,
+                state.indent()
+            );
+        }
+
+        result.push('namespace ',
             ...this.getName(ParseMode.BrighterScript),
             state.newline
-        ] as TranspileResult;
+        );
         state.blockDepth++;
         result.push(
             ...this.body.getTypedef(state)
@@ -1616,6 +1612,10 @@ export class ImportStatement extends Statement implements TypedefProvider {
     walk(visitor: WalkVisitor, options: WalkOptions) {
         //nothing to walk
     }
+
+    getLeadingTrivia(): Token[] {
+        return this.tokens.import?.leadingTrivia ?? [];
+    }
 }
 
 export class InterfaceStatement extends Statement implements TypedefProvider {
@@ -1725,6 +1725,13 @@ export class InterfaceStatement extends Statement implements TypedefProvider {
 
     getTypedef(state: BrsTranspileState) {
         const result = [] as TranspileResult;
+        for (let comment of util.getLeadingComments(this) ?? []) {
+            result.push(
+                comment.text,
+                state.newline,
+                state.indent()
+            );
+        }
         for (let annotation of this.annotations ?? []) {
             result.push(
                 ...annotation.getTypedef(state),
@@ -1866,6 +1873,13 @@ export class InterfaceFieldStatement extends Statement implements TypedefProvide
 
     getTypedef(state: BrsTranspileState): TranspileResult {
         const result = [] as TranspileResult;
+        for (let comment of util.getLeadingComments(this) ?? []) {
+            result.push(
+                comment.text,
+                state.newline,
+                state.indent()
+            );
+        }
         for (let annotation of this.annotations ?? []) {
             result.push(
                 ...annotation.getTypedef(state),
@@ -1976,6 +1990,13 @@ export class InterfaceMethodStatement extends Statement implements TypedefProvid
 
     getTypedef(state: BrsTranspileState) {
         const result = [] as TranspileResult;
+        for (let comment of util.getLeadingComments(this) ?? []) {
+            result.push(
+                comment.text,
+                state.newline,
+                state.indent()
+            );
+        }
         for (let annotation of this.annotations ?? []) {
             result.push(
                 ...annotation.getTypedef(state),
@@ -2144,6 +2165,13 @@ export class ClassStatement extends Statement implements TypedefProvider {
 
     getTypedef(state: BrsTranspileState) {
         const result = [] as TranspileResult;
+        for (let comment of util.getLeadingComments(this) ?? []) {
+            result.push(
+                comment.text,
+                state.newline,
+                state.indent()
+            );
+        }
         for (let annotation of this.annotations ?? []) {
             result.push(
                 ...annotation.getTypedef(state),
@@ -2583,7 +2611,14 @@ export class MethodStatement extends FunctionStatement {
     }
 
     getTypedef(state: BrsTranspileState) {
-        const result = [] as TranspileResult;
+        const result: TranspileResult = [];
+        for (let comment of util.getLeadingComments(this) ?? []) {
+            result.push(
+                comment.text,
+                state.newline,
+                state.indent()
+            );
+        }
         for (let annotation of this.annotations ?? []) {
             result.push(
                 ...annotation.getTypedef(state),
@@ -2769,6 +2804,13 @@ export class FieldStatement extends Statement implements TypedefProvider {
     getTypedef(state: BrsTranspileState) {
         const result = [];
         if (this.tokens.name) {
+            for (let comment of util.getLeadingComments(this) ?? []) {
+                result.push(
+                    comment.text,
+                    state.newline,
+                    state.indent()
+                );
+            }
             for (let annotation of this.annotations ?? []) {
                 result.push(
                     ...annotation.getTypedef(state),
@@ -2860,6 +2902,10 @@ export class TryCatchStatement extends Statement {
             walk(this, 'catchStatement', visitor, options);
         }
     }
+
+    public getLeadingTrivia(): Token[] {
+        return this.tokens.try?.leadingTrivia ?? [];
+    }
 }
 
 export class CatchStatement extends Statement {
@@ -2906,6 +2952,10 @@ export class CatchStatement extends Statement {
             walk(this, 'catchBranch', visitor, options);
         }
     }
+
+    public getLeadingTrivia(): Token[] {
+        return this.tokens.catch?.leadingTrivia ?? [];
+    }
 }
 
 export class ThrowStatement extends Statement {
@@ -2957,6 +3007,10 @@ export class ThrowStatement extends Statement {
             walk(this, 'expression', visitor, options);
         }
     }
+
+    public getLeadingTrivia(): Token[] {
+        return this.tokens.throw?.leadingTrivia ?? [];
+    }
 }
 
 
@@ -2965,7 +3019,7 @@ export class EnumStatement extends Statement implements TypedefProvider {
         enum?: Token;
         name: Identifier;
         endEnum?: Token;
-        body: Array<EnumMemberStatement | CommentStatement>;
+        body: Array<EnumMemberStatement>;
     }) {
         super();
         this.tokens = {
@@ -2982,7 +3036,7 @@ export class EnumStatement extends Statement implements TypedefProvider {
         readonly name: Identifier;
         readonly endEnum?: Token;
     };
-    public readonly body: Array<EnumMemberStatement | CommentStatement>;
+    public readonly body: Array<EnumMemberStatement>;
 
     public readonly kind = AstNodeKind.EnumStatement;
 
@@ -3006,7 +3060,7 @@ export class EnumStatement extends Statement implements TypedefProvider {
     }
 
     public getLeadingTrivia(): Token[] {
-        return util.concatAnnotationLeadingTrivia(this, this.tokens.enum.leadingTrivia);
+        return util.concatAnnotationLeadingTrivia(this, this.tokens.enum?.leadingTrivia);
     }
 
     /**
@@ -3083,6 +3137,13 @@ export class EnumStatement extends Statement implements TypedefProvider {
 
     getTypedef(state: BrsTranspileState) {
         const result = [] as TranspileResult;
+        for (let comment of util.getLeadingComments(this) ?? []) {
+            result.push(
+                comment.text,
+                state.newline,
+                state.indent()
+            );
+        }
         for (let annotation of this.annotations ?? []) {
             result.push(
                 ...annotation.getTypedef(state),
@@ -3182,9 +3243,15 @@ export class EnumMemberStatement extends Statement implements TypedefProvider {
     }
 
     getTypedef(state: BrsTranspileState): TranspileResult {
-        const result = [
-            this.tokens.name.text
-        ] as TranspileResult;
+        const result: TranspileResult = [];
+        for (let comment of util.getLeadingComments(this) ?? []) {
+            result.push(
+                comment.text,
+                state.newline,
+                state.indent()
+            );
+        }
+        result.push(this.tokens.name.text);
         if (this.tokens.equals) {
             result.push(' ', this.tokens.equals.text, ' ');
             if (this.value) {
@@ -3244,7 +3311,7 @@ export class ConstStatement extends Statement implements TypedefProvider {
     }
 
     public getLeadingTrivia(): Token[] {
-        return util.concatAnnotationLeadingTrivia(this, this.tokens.const.leadingTrivia);
+        return util.concatAnnotationLeadingTrivia(this, this.tokens.const?.leadingTrivia);
     }
 
     /**
@@ -3272,7 +3339,15 @@ export class ConstStatement extends Statement implements TypedefProvider {
     }
 
     getTypedef(state: BrsTranspileState): TranspileResult {
-        return [
+        const result: TranspileResult = [];
+        for (let comment of util.getLeadingComments(this) ?? []) {
+            result.push(
+                comment.text,
+                state.newline,
+                state.indent()
+            );
+        }
+        result.push(
             this.tokens.const ? state.tokenToSourceNode(this.tokens.const) : 'const',
             ' ',
             state.tokenToSourceNode(this.tokens.name),
@@ -3280,7 +3355,8 @@ export class ConstStatement extends Statement implements TypedefProvider {
             this.tokens.equals ? state.tokenToSourceNode(this.tokens.equals) : '=',
             ' ',
             ...this.value.transpile(state)
-        ];
+        );
+        return result;
     }
 
     walk(visitor: WalkVisitor, options: WalkOptions) {
@@ -3330,4 +3406,8 @@ export class ContinueStatement extends Statement {
     walk(visitor: WalkVisitor, options: WalkOptions) {
         //nothing to walk
     }
+
+    public getLeadingTrivia(): Token[] {
+        return this.tokens.continue?.leadingTrivia ?? [];
+    }
 }
diff --git a/src/parser/TranspileState.ts b/src/parser/TranspileState.ts
index e4a75694..92f1299e 100644
--- a/src/parser/TranspileState.ts
+++ b/src/parser/TranspileState.ts
@@ -1,8 +1,19 @@
 import { SourceNode } from 'source-map';
 import type { Range } from 'vscode-languageserver';
 import type { BsConfig } from '../BsConfig';
-import type { TranspileResult } from '../interfaces';
+import { TokenKind } from '../lexer/TokenKind';
+import type { Token } from '../lexer/Token';
 import util from '../util';
+import type { TranspileResult } from '../interfaces';
+
+
+interface TranspileToken {
+    range?: Range;
+    text: string;
+    kind?: TokenKind;
+    leadingWhitespace?: string;
+    leadingTrivia?: Array<TranspileToken>;
+}
 
 /**
  * Holds the state of a transpile operation as it works its way through the transpile process
@@ -72,7 +83,7 @@ export class TranspileState {
      * because the entire token is passed by reference, instead of the raw string being copied to the parameter,
      * only to then be copied again for the SourceNode constructor
      */
-    public tokenToSourceNode(token: { range?: Range; text: string }) {
+    public tokenToSourceNode(token: TranspileToken) {
         return new SourceNode(
             //convert 0-based range line to 1-based SourceNode line
             token.range ? token.range.start.line + 1 : null,
@@ -83,21 +94,59 @@ export class TranspileState {
         );
     }
 
+    public transpileLeadingComments(token: TranspileToken) {
+        const leadingCommentsSourceNodes = [];
+        const leadingTrivia = (token?.leadingTrivia ?? []);
+        const justComments = leadingTrivia.filter(t => t.kind === TokenKind.Comment || t.kind === TokenKind.Newline);
+        let newLinesSinceComment = 0;
+
+        let transpiledCommentAlready = false;
+        for (const commentToken of justComments) {
+            if (commentToken.kind === TokenKind.Newline && !transpiledCommentAlready) {
+                continue;
+            }
+            if (commentToken.kind === TokenKind.Comment) {
+                if (leadingCommentsSourceNodes.length > 0) {
+                    leadingCommentsSourceNodes.push(this.indent());
+                }
+                leadingCommentsSourceNodes.push(this.tokenToSourceNode(commentToken));
+                newLinesSinceComment = 0;
+            } else {
+                newLinesSinceComment++;
+            }
+
+            if (newLinesSinceComment === 1 || newLinesSinceComment === 2) {
+                //new line that is not touching a previous new line
+                leadingCommentsSourceNodes.push(this.newline);
+            }
+            transpiledCommentAlready = true;
+        }
+        if (leadingCommentsSourceNodes.length > 0 && token.text) {
+            // indent in preparation for next text
+            leadingCommentsSourceNodes.push(this.indent());
+        }
+
+        return leadingCommentsSourceNodes;
+    }
+
+
     /**
      * Create a SourceNode from a token, accounting for missing range and multi-line text
      */
-    public transpileToken(token: { range?: Range; text: string }, defaultValue?: string) {
+    public transpileToken(token: TranspileToken, defaultValue?: string): TranspileResult {
         if (!token && defaultValue !== undefined) {
-            return new SourceNode(null, null, null, defaultValue);
+            return [new SourceNode(null, null, null, defaultValue)];
         }
+        const leadingCommentsSourceNodes = this.transpileLeadingComments(token);
+
         if (!token.range) {
-            return new SourceNode(null, null, null, token.text);
+            return [new SourceNode(null, null, null, [...leadingCommentsSourceNodes, 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, lines[0])
+                this.sourceNode(token, [...leadingCommentsSourceNodes, lines[0]])
             ] as Array<string | SourceNode>;
             for (let i = 1; i < lines.length; i++) {
                 code.push(
@@ -112,9 +161,31 @@ export class TranspileState {
                     )
                 );
             }
-            return new SourceNode(null, null, null, code);
+            return [new SourceNode(null, null, null, code)];
         } else {
-            return this.tokenToSourceNode(token);
+            return [...leadingCommentsSourceNodes, this.tokenToSourceNode(token)];
+        }
+    }
+
+    public transpileEndBlockToken(previousLocatable: { range?: Range }, endToken: Token, defaultValue: string, alwaysAddNewlineBeforeEndToken = true) {
+        const result = [];
+
+        if (util.hasLeadingComments(endToken)) {
+            // add comments before `end token` - they should be indented
+            if (util.isLeadingCommentOnSameLine(previousLocatable, endToken)) {
+                this.blockDepth++;
+                result.push(' ');
+            } else {
+                result.push(this.newline);
+                result.push(this.indent(1));
+            }
+            result.push(...this.transpileToken({ ...endToken, text: '' }));
+            this.blockDepth--;
+            result.push(this.indent());
+        } else if (alwaysAddNewlineBeforeEndToken) {
+            result.push(this.newline, this.indent());
         }
+        result.push(this.transpileToken({ ...endToken, leadingTrivia: [] }, defaultValue));
+        return result;
     }
 }
diff --git a/src/util.spec.ts b/src/util.spec.ts
index 62fbd834..6a29402a 100644
--- a/src/util.spec.ts
+++ b/src/util.spec.ts
@@ -1071,19 +1071,20 @@ describe('util', () => {
 
     describe('getTokenDocumentation', () => {
         it('should return a string of the comment', () => {
-            const { tokens, statements } = Parser.parse(`
+            const { statements } = Parser.parse(`
                 ' This is a comment.
                 ' it has two lines
                 function getOne() as integer
                     return 1
                 end function
             `);
-            const docs = util.getTokenDocumentation(tokens, (statements[1] as FunctionStatement).func.tokens.functionType);
+            const func = (statements[0] as FunctionStatement).func;
+            const docs = util.getTokenDocumentation(func);
             expect(docs).to.eql('This is a comment.\nit has two lines');
         });
 
         it('should pay attention to @param, @return, etc. (jsdoc tags)', () => {
-            const { tokens, statements } = Parser.parse(`
+            const { statements } = Parser.parse(`
                 ' Add 1 to a number
                 '
                 ' @public
@@ -1093,9 +1094,53 @@ describe('util', () => {
                     return num + 1
                 end function
             `);
-            const docs = util.getTokenDocumentation(tokens, (statements[1] as FunctionStatement).func.tokens.functionType);
+            const func = (statements[0] as FunctionStatement).func;
+            const docs = util.getTokenDocumentation(func);
             expect(docs).to.eql('Add 1 to a number\n\n\n_@public_\n\n_@param_ {integer} the number to add to\n\n_@return_ {integer} the result');
         });
+
+
+        it('only includes comments directly above token', () => {
+            const { statements } = Parser.parse(`
+                const abc = "ABC" ' comment at end of line
+
+                ' plus one
+                function addOne(num as integer) as integer
+                    return num + 1
+                end function
+            `);
+            const func = (statements[1] as FunctionStatement).func;
+            const docs = util.getNodeDocumentation(func);
+            expect(docs).to.eql('plus one');
+        });
+
+        it('allows jsdoc style comment blocks', () => {
+            const { statements } = Parser.parse(`
+                ' /**
+                '  plus one
+                ' */
+                function addOne(num as integer) as integer
+                    return num + 1
+                end function
+            `);
+            const func = (statements[0] as FunctionStatement).func;
+            const docs = util.getNodeDocumentation(func);
+            expect(docs).to.eql('plus one');
+        });
+
+        it('allows jsdoc style comment blocks with leading *', () => {
+            const { statements } = Parser.parse(`
+                ' /**
+                '  * plus one
+                '  */
+                function addOne(num as integer) as integer
+                    return num + 1
+                end function
+            `);
+            const func = (statements[0] as FunctionStatement).func;
+            const docs = util.getNodeDocumentation(func);
+            expect(docs).to.eql('plus one');
+        });
     });
 
     describe('truncate', () => {
diff --git a/src/util.ts b/src/util.ts
index 16bc2984..1725d4fb 100644
--- a/src/util.ts
+++ b/src/util.ts
@@ -24,7 +24,7 @@ import { VoidType } from './types/VoidType';
 import { ParseMode } from './parser/Parser';
 import type { CallExpression, CallfuncExpression, DottedGetExpression, FunctionParameterExpression, IndexedGetExpression, LiteralExpression, NewExpression, TypeExpression, VariableExpression, XmlAttributeGetExpression } from './parser/Expression';
 import { Logger, LogLevel } from './Logger';
-import type { Identifier, Locatable, Token } from './lexer/Token';
+import { isToken, type Identifier, type Locatable, type Token } from './lexer/Token';
 import { TokenKind } from './lexer/TokenKind';
 import { isAnyReferenceType, isBinaryExpression, isBooleanType, isBrsFile, isCallExpression, isCallfuncExpression, isDottedGetExpression, isDoubleType, isDynamicType, isEnumMemberType, isExpression, isFloatType, isIndexedGetExpression, isInvalidType, isLongIntegerType, isNumberType, isStringType, isTypeExpression, isTypedArrayExpression, isVariableExpression, isXmlAttributeGetExpression, isXmlFile } from './astUtils/reflection';
 import { WalkMode } from './astUtils/visitors';
@@ -630,40 +630,61 @@ export class Util {
     /**
      * Combine all the documentation found before a token (i.e. comment tokens)
      */
-    public getTokenDocumentation(tokens: Token[], token?: Token) {
+    public getTokenDocumentation(token?: Token | AstNode) {
+        const leadingTrivia = isToken(token) ? token.leadingTrivia : token?.getLeadingTrivia() ?? [];
+        const tokens = leadingTrivia?.filter(t => t.kind === TokenKind.Newline || t.kind === TokenKind.Comment);
         const comments = [] as Token[];
-        const idx = tokens?.indexOf(token);
-        if (!idx || idx === -1) {
-            return undefined;
-        }
-        for (let i = idx - 1; i >= 0; i--) {
+
+        let newLinesInRow = 0;
+        for (let i = tokens.length - 1; i >= 0; i--) {
             const token = tokens[i];
             //skip whitespace and newline chars
             if (token.kind === TokenKind.Comment) {
                 comments.push(token);
-            } else if (token.kind === TokenKind.Newline || token.kind === TokenKind.Whitespace) {
+                newLinesInRow = 0;
+            } else if (token.kind === TokenKind.Newline) {
                 //skip these tokens
-                continue;
+                newLinesInRow++;
 
+                if (newLinesInRow > 1) {
+                    // stop processing on empty line.
+                    break;
+                }
                 //any other token means there are no more comments
             } else {
                 break;
             }
         }
+        const jsDocCommentBlockLine = /(\/\*{2,}|\*{1,}\/)/i;
+        let usesjsDocCommentBlock = false;
         if (comments.length > 0) {
-            return comments.reverse().map(x => x.text.replace(/^('|rem)/i, '').trim()).map(line => {
-                if (line.startsWith('@')) {
-                    // Handle jsdoc/brightscriptdoc tags specially
-                    // make sure they are on their own markdown line, and add italics
-                    const firstSpaceIndex = line.indexOf(' ');
-                    if (firstSpaceIndex === -1) {
-                        return `\n_${line}_`;
+            return comments.reverse()
+                .map(x => x.text.replace(/^('|rem)/i, '').trim())
+                .filter(line => {
+                    if (jsDocCommentBlockLine.exec(line)) {
+                        usesjsDocCommentBlock = true;
+                        return false;
                     }
-                    const firstWord = line.substring(0, firstSpaceIndex);
-                    return `\n_${firstWord}_ ${line.substring(firstSpaceIndex + 1)}`;
-                }
-                return line;
-            }).join('\n');
+                    return true;
+                }).map(line => {
+                    if (usesjsDocCommentBlock) {
+                        if (line.startsWith('*')) {
+                            //remove jsDoc leading '*'
+                            line = line.slice(1).trim();
+                        }
+                    }
+                    if (line.startsWith('@')) {
+                        // Handle jsdoc/brightscriptdoc tags specially
+                        // make sure they are on their own markdown line, and add italics
+                        const firstSpaceIndex = line.indexOf(' ');
+                        if (firstSpaceIndex === -1) {
+                            return `\n_${line}_`;
+                        }
+                        const firstWord = line.substring(0, firstSpaceIndex);
+                        return `\n_${firstWord}_ ${line.substring(firstSpaceIndex + 1)}`;
+                    }
+                    return line;
+                }).join('\n');
         }
     }
 
@@ -674,8 +695,7 @@ export class Util {
         if (!node) {
             return;
         }
-        const leadingTrivia = node.getLeadingTrivia();
-        return this.getTokenDocumentation(leadingTrivia, leadingTrivia[leadingTrivia.length - 1]);
+        return this.getTokenDocumentation(node);
     }
 
     /**
@@ -1081,7 +1101,7 @@ export class Util {
      *  Gets the bounding range of a bunch of ranges or objects that have ranges
      *  TODO: this does a full iteration of the args. If the args were guaranteed to be in range order, we could optimize this
      */
-    public createBoundingRange(...locatables: Array<{ range?: Range } | Range>): Range | undefined {
+    public createBoundingRange(...locatables: Array<{ range?: Range } | Range | undefined>): Range | undefined {
         let startPosition: Position | undefined;
         let endPosition: Position | undefined;
 
@@ -1584,7 +1604,7 @@ export class Util {
     }
 
 
-    public concatAnnotationLeadingTrivia(stmt: Statement, otherTrivia: Token[]): Token[] {
+    public concatAnnotationLeadingTrivia(stmt: Statement, otherTrivia: Token[] = []): Token[] {
         return [...(stmt.annotations?.map(anno => anno.getLeadingTrivia()).flat() ?? []), ...otherTrivia];
     }
 
@@ -2079,6 +2099,25 @@ export class Util {
     public getAstNodeFriendlyName(node: AstNode) {
         return node?.kind.replace(/Statement|Expression/g, '');
     }
+
+
+    public hasLeadingComments(input: Token | AstNode) {
+        const leadingTrivia = isToken(input) ? input?.leadingTrivia : input?.getLeadingTrivia() ?? [];
+        return !!leadingTrivia.find(t => t.kind === TokenKind.Comment);
+    }
+
+    public getLeadingComments(input: Token | AstNode) {
+        const leadingTrivia = isToken(input) ? input?.leadingTrivia : input?.getLeadingTrivia() ?? [];
+        return leadingTrivia.filter(t => t.kind === TokenKind.Comment);
+    }
+
+    public isLeadingCommentOnSameLine(line: { range?: Range }, input: Token | AstNode) {
+        const leadingCommentRange = this.getLeadingComments(input)?.[0];
+        if (leadingCommentRange) {
+            return this.linesTouch(line, leadingCommentRange);
+        }
+        return false;
+    }
 }
 
 /**


Test patch:
diff --git a/src/parser/tests/Parser.spec.ts b/src/parser/tests/Parser.spec.ts
index 1ddac87e..351543c2 100644
--- a/src/parser/tests/Parser.spec.ts
+++ b/src/parser/tests/Parser.spec.ts
@@ -28,7 +28,7 @@ export function identifier(text: string) {
 /**
  * Test whether a range matches a group of elements with a `range`
  */
-export function rangeMatch(range: Range, elements: ({ range: Range })[]): boolean {
+export function rangeMatch(range: Range, elements: ({ range?: Range })[]): boolean {
     return range.start.line === elements[0].range.start.line &&
         range.start.character === elements[0].range.start.character &&
         range.end.line === elements[elements.length - 1].range.end.line &&
diff --git a/src/parser/tests/controlFlow/If.spec.ts b/src/parser/tests/controlFlow/If.spec.ts
index a26ae1bd..73ceee30 100644
--- a/src/parser/tests/controlFlow/If.spec.ts
+++ b/src/parser/tests/controlFlow/If.spec.ts
@@ -4,7 +4,7 @@ import { Parser } from '../../Parser';
 import { Lexer } from '../../../lexer/Lexer';
 import { TokenKind } from '../../../lexer/TokenKind';
 import { EOF, identifier, rangeMatch, token } from '../Parser.spec';
-import { isBlock, isCommentStatement, isFunctionStatement, isIfStatement } from '../../../astUtils/reflection';
+import { isBlock, isFunctionStatement, isIfStatement } from '../../../astUtils/reflection';
 import type { Block, FunctionStatement, IfStatement } from '../../Statement';
 
 describe('parser if statements', () => {
@@ -100,7 +100,7 @@ describe('parser if statements', () => {
             assert.fail('Missing single-line if inside if-then');
         }
         expect(ifs.elseBranch).to.exist;
-        if (!isBlock(ifs.elseBranch) || !isCommentStatement(ifs.elseBranch.statements[0])) {
+        if (!isBlock(ifs.elseBranch)) {
             assert.fail('Missing comment inside else branch');
         }
     });
diff --git a/src/parser/tests/expression/AssociativeArrayLiterals.spec.ts b/src/parser/tests/expression/AssociativeArrayLiterals.spec.ts
index 5de14028..dbb4e285 100644
--- a/src/parser/tests/expression/AssociativeArrayLiterals.spec.ts
+++ b/src/parser/tests/expression/AssociativeArrayLiterals.spec.ts
@@ -6,7 +6,7 @@ import { EOF, identifier, token } from '../Parser.spec';
 import { Range } from 'vscode-languageserver';
 import type { AssignmentStatement } from '../../Statement';
 import type { AALiteralExpression, AAMemberExpression } from '../../Expression';
-import { isAALiteralExpression, isAssignmentStatement, isCommentStatement, isDottedGetExpression, isLiteralExpression } from '../../../astUtils/reflection';
+import { isAALiteralExpression, isAssignmentStatement, isDottedGetExpression, isLiteralExpression } from '../../../astUtils/reflection';
 import { expectDiagnostics, expectDiagnosticsIncludes } from '../../../testHelpers.spec';
 import { DiagnosticMessages } from '../../../DiagnosticMessages';
 
@@ -195,14 +195,12 @@ describe('parser associative array literals', () => {
             }
         `);
         const commas = ((statements[0] as AssignmentStatement).value as AALiteralExpression).elements
-            .map(s => !isCommentStatement(s) && !!s.tokens.comma);
+            .map(s => !!s.tokens.comma);
         expect(commas).to.deep.equal([
             true, // p1
             true, // p2
-            false, // comment
             false, // p3
             false, // p4
-            false, // comment
             true // p5
         ]);
     });
diff --git a/src/parser/tests/statement/InterfaceStatement.spec.ts b/src/parser/tests/statement/InterfaceStatement.spec.ts
index 19146999..dcf6f5aa 100644
--- a/src/parser/tests/statement/InterfaceStatement.spec.ts
+++ b/src/parser/tests/statement/InterfaceStatement.spec.ts
@@ -84,7 +84,6 @@ describe('InterfaceStatement', () => {
                 prop as dynamic
             end interface
         `, `
-            'this comment was throwing exception during transpile
         `);
     });
 
diff --git a/src/parser/tests/statement/Misc.spec.ts b/src/parser/tests/statement/Misc.spec.ts
index 2ef50772..2770bb13 100644
--- a/src/parser/tests/statement/Misc.spec.ts
+++ b/src/parser/tests/statement/Misc.spec.ts
@@ -6,6 +6,7 @@ import { Range } from 'vscode-languageserver';
 import type { AAMemberExpression } from '../../Expression';
 import { expectZeroDiagnostics } from '../../../testHelpers.spec';
 import type { Statement } from '../../AstNode';
+import { isAAMemberExpression, isDottedSetStatement } from '../../../astUtils/reflection';
 
 describe('parser', () => {
     describe('`end` keyword', () => {
@@ -259,11 +260,34 @@ describe('parser', () => {
             `
         );
         let { diagnostics, statements } = Parser.parse(tokens) as any;
-        expect(diagnostics).to.exist.and.be.lengthOf(0, 'Error count should be 0');
-        expect(statements[0].func.body.statements[0].value.elements[0].text).to.equal('rem: 1');
-        expect(statements[0].func.body.statements[1].value.elements[1].text).to.equal('rem: 2');
-        expect(statements[0].func.body.statements[2].value.elements[0].text).to.equal('rem: 3: name: "bob"');
-        expect(statements[0].func.body.statements[3].tokens.name.text).to.equal('rem');
+        expectZeroDiagnostics(diagnostics);
+        const mainStatements = statements[0].func.body.statements;
+
+        // 1st AA has no elements - `rem` denotes comment
+        expect(mainStatements[0].value.elements.length).to.eq(0);
+        let commentBeforeClose = mainStatements[0].value.tokens.close.leadingTrivia[2];
+        expect(commentBeforeClose.kind).to.equal(TokenKind.Comment);
+        expect(commentBeforeClose.text).to.equal('rem: 1');
+
+        // 2nd AA has 1 element - `rem` after colon denotes comment, but first AA member is valid
+        expect(mainStatements[1].value.elements.length).to.eq(1);
+        expect(isAAMemberExpression(mainStatements[1].value.elements[0])).to.true;
+        expect(mainStatements[1].value.elements[0].tokens.key.text).to.equal('name');
+        expect(mainStatements[1].value.elements[0].value.tokens.value.text).to.equal('"bob"');
+        commentBeforeClose = mainStatements[1].value.tokens.close.leadingTrivia[2];
+        expect(commentBeforeClose.kind).to.equal(TokenKind.Comment);
+        expect(commentBeforeClose.text).to.equal('rem: 2');
+
+        // 3nd AA has no elements - `rem` colons are included in comments
+        expect(mainStatements[2].value.elements.length).to.eq(0);
+        commentBeforeClose = mainStatements[2].value.tokens.close.leadingTrivia[2];
+        expect(commentBeforeClose.kind).to.equal(TokenKind.Comment);
+        expect(commentBeforeClose.text).to.equal('rem: 3: name: "bob"');
+
+        // `rem` CAN be uses a property of an AA, when preceded by '.'
+        expect(isDottedSetStatement(mainStatements[3])).to.true;
+        expect(mainStatements[3].tokens.name.kind).to.equal(TokenKind.Identifier);
+        expect(mainStatements[3].tokens.name.text).to.equal('rem');
     });
 
     it('handles quoted AA keys', () => {