From 607bf8f60be3b50820176cc0efe53aec2ac633eb Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Fri, 29 May 2026 14:01:32 -0400 Subject: [PATCH] Guard transpileComments against undefined trivia tokens The lexer emits a token's `leadingTrivia` as `undefined` (rather than `[]`) when no whitespace, newlines, or comments precede it. An `@annotation` whose `at` token has no preceding trivia therefore has `leadingTrivia === undefined`, which `AnnotationExpression.transpile` forwards directly to `state.transpileComments(this.leadingTrivia)`, crashing with `TypeError: Cannot read properties of undefined (reading 'filter')`. Match the existing guard in `transpileLeadingComments` and the nullish-tolerant helpers in `util.ts` (`hasLeadingComments`, `getLeadingComments`) by returning early when `tokens` is nullish. --- src/files/BrsFile.spec.ts | 15 +++++++++++++++ src/parser/TranspileState.ts | 3 +++ 2 files changed, 18 insertions(+) diff --git a/src/files/BrsFile.spec.ts b/src/files/BrsFile.spec.ts index 238589590..b5d81f5a9 100644 --- a/src/files/BrsFile.spec.ts +++ b/src/files/BrsFile.spec.ts @@ -3628,6 +3628,21 @@ describe('BrsFile', () => { `, undefined, 'source/main.bs'); }); + it('does not crash when annotation has undefined leadingTrivia', async () => { + //the lexer emits leadingTrivia as `undefined` (rather than `[]`) when a token has no preceding trivia, + //so an `@annotation` whose `at` token has no preceding whitespace/comments/newlines produces + //`at.leadingTrivia === undefined`. `AnnotationExpression.transpile` forwards that directly to + //`state.transpileComments`, which must tolerate a nullish input rather than crashing on `.filter` + //of undefined. Feed source that starts at column 0 with no leading newline to ensure no trivia + //is attached to the `@` token. + program.setFile('source/main.bs', `@annotation\nsub main()\n print "main"\nend sub`); + program.validate(); + expectZeroDiagnostics(program); + //this should not throw + const result = await program.getTranspiledFileContents('source/main.bs'); + expect(result.code).to.include('sub main()'); + }); + it('includes annotation comments for class', async () => { await testTranspile(` 'comment1 diff --git a/src/parser/TranspileState.ts b/src/parser/TranspileState.ts index fced4e45d..97d31b6c9 100644 --- a/src/parser/TranspileState.ts +++ b/src/parser/TranspileState.ts @@ -141,6 +141,9 @@ export class TranspileState { public transpileComments(tokens: TranspileToken[], prepNextLine = false): Array { const leadingCommentsSourceNodes = []; + if (!tokens) { + return leadingCommentsSourceNodes; + } const justComments = tokens.filter(t => t.kind === TokenKind.Comment || t.kind === TokenKind.Newline); let newLinesSinceComment = 0;