• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

rokucommunity / brighterscript / #15021

11 Dec 2025 06:16PM UTC coverage: 88.959% (-0.002%) from 88.961%
#15021

push

web-flow
Backports TypeStatement syntax from v1 to v0 (#1600)

7877 of 9338 branches covered (84.35%)

Branch coverage included in aggregate %.

51 of 54 new or added lines in 6 files covered. (94.44%)

1 existing line in 1 file now uncovered.

10090 of 10859 relevant lines covered (92.92%)

1900.91 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

91.88
/src/parser/Statement.ts
1
/* eslint-disable no-bitwise */
2
import type { Token, Identifier } from '../lexer/Token';
3
import { CompoundAssignmentOperators, TokenKind } from '../lexer/TokenKind';
1✔
4
import type { BinaryExpression, NamespacedVariableNameExpression, FunctionParameterExpression, LiteralExpression } from './Expression';
5
import { FunctionExpression } from './Expression';
1✔
6
import { CallExpression, VariableExpression } from './Expression';
1✔
7
import { util } from '../util';
1✔
8
import type { Range } from 'vscode-languageserver';
9
import type { BrsTranspileState } from './BrsTranspileState';
10
import { ParseMode } from './Parser';
1✔
11
import type { WalkVisitor, WalkOptions } from '../astUtils/visitors';
12
import { InternalWalkMode, walk, createVisitor, WalkMode, walkArray } from '../astUtils/visitors';
1✔
13
import { isCallExpression, isCommentStatement, isEnumMemberStatement, isExpression, isExpressionStatement, isFieldStatement, isFunctionExpression, isFunctionStatement, isIfStatement, isInterfaceFieldStatement, isInterfaceMethodStatement, isInvalidType, isLiteralExpression, isMethodStatement, isNamespaceStatement, isTypedefProvider, isUnaryExpression, isVoidType } from '../astUtils/reflection';
1✔
14
import type { TranspileResult, TypedefProvider } from '../interfaces';
15
import { createIdentifier, createInvalidLiteral, createMethodStatement, createToken } from '../astUtils/creators';
1✔
16
import { DynamicType } from '../types/DynamicType';
1✔
17
import type { BscType } from '../types/BscType';
18
import type { TranspileState } from './TranspileState';
19
import { SymbolTable } from '../SymbolTable';
1✔
20
import type { AstNode, Expression } from './AstNode';
21
import { Statement } from './AstNode';
1✔
22

23
export class EmptyStatement extends Statement {
1✔
24
    constructor(
25
        /**
26
         * Create a negative range to indicate this is an interpolated location
27
         */
28
        public range: Range = undefined
6✔
29
    ) {
30
        super();
6✔
31
    }
32

33
    transpile(state: BrsTranspileState) {
34
        return [];
2✔
35
    }
36
    walk(visitor: WalkVisitor, options: WalkOptions) {
37
        //nothing to walk
38
    }
39

40
    public clone() {
41
        return this.finalizeClone(
1✔
42
            new EmptyStatement(
43
                util.cloneRange(this.range)
44
            )
45
        );
46
    }
47
}
48

49
/**
50
 * This is a top-level statement. Consider this the root of the AST
51
 */
52
export class Body extends Statement implements TypedefProvider {
1✔
53
    constructor(
54
        public statements: Statement[] = []
4,842✔
55
    ) {
56
        super();
4,842✔
57
    }
58

59
    public symbolTable = new SymbolTable('Body', () => this.parent?.getSymbolTable());
4,842✔
60

61
    public get range() {
62
        //this needs to be a getter because the body has its statements pushed to it after being constructed
63
        return util.createBoundingRange(
17✔
64
            ...(this.statements ?? [])
51!
65
        );
66
    }
67

68
    transpile(state: BrsTranspileState) {
69
        let result = [] as TranspileResult;
326✔
70
        for (let i = 0; i < this.statements.length; i++) {
326✔
71
            let statement = this.statements[i];
479✔
72
            let previousStatement = this.statements[i - 1];
479✔
73
            let nextStatement = this.statements[i + 1];
479✔
74

75
            if (!previousStatement) {
479✔
76
                //this is the first statement. do nothing related to spacing and newlines
77

78
                //if comment is on same line as prior sibling
79
            } else if (isCommentStatement(statement) && previousStatement && statement.range?.start.line === previousStatement.range?.end.line) {
155✔
80
                result.push(
4✔
81
                    ' '
82
                );
83

84
                //add double newline if this is a comment, and next is a function
85
            } else if (isCommentStatement(statement) && nextStatement && isFunctionStatement(nextStatement)) {
151✔
86
                result.push(state.newline, state.newline);
1✔
87

88
                //add double newline if is function not preceeded by a comment
89
            } else if (isFunctionStatement(statement) && previousStatement && !(isCommentStatement(previousStatement))) {
150✔
90
                result.push(state.newline, state.newline);
78✔
91
            } else {
92
                //separate statements by a single newline
93
                result.push(state.newline);
72✔
94
            }
95

96
            result.push(...statement.transpile(state));
479✔
97
        }
98
        return result;
326✔
99
    }
100

101
    getTypedef(state: BrsTranspileState): TranspileResult {
102
        let result = [] as TranspileResult;
31✔
103
        for (const statement of this.statements) {
31✔
104
            //if the current statement supports generating typedef, call it
105
            if (isTypedefProvider(statement)) {
45!
106
                result.push(
45✔
107
                    state.indent(),
108
                    ...statement.getTypedef(state),
109
                    state.newline
110
                );
111
            }
112
        }
113
        return result;
31✔
114
    }
115

116
    walk(visitor: WalkVisitor, options: WalkOptions) {
117
        if (options.walkMode & InternalWalkMode.walkStatements) {
4,328!
118
            walkArray(this.statements, visitor, options, this);
4,328✔
119
        }
120
    }
121

122
    public clone() {
123
        return this.finalizeClone(
134✔
124
            new Body(
125
                this.statements?.map(s => s?.clone())
139✔
126
            ),
127
            ['statements']
128
        );
129
    }
130
}
131

132
export class AssignmentStatement extends Statement {
1✔
133
    constructor(
134
        readonly equals: Token,
995✔
135
        readonly name: Identifier,
995✔
136
        readonly value: Expression
995✔
137
    ) {
138
        super();
995✔
139
        this.range = util.createBoundingRange(name, equals, value);
995✔
140
    }
141

142
    public readonly range: Range | undefined;
143

144
    /**
145
     * Get the name of the wrapping namespace (if it exists)
146
     * @deprecated use `.findAncestor(isFunctionExpression)` instead.
147
     */
148
    public get containingFunction() {
149
        return this.findAncestor<FunctionExpression>(isFunctionExpression);
463✔
150
    }
151

152
    transpile(state: BrsTranspileState) {
153
        //if the value is a compound assignment, just transpile the expression itself
154
        if (CompoundAssignmentOperators.includes((this.value as BinaryExpression)?.operator?.kind)) {
272!
155
            return this.value.transpile(state);
21✔
156
        } else {
157
            return [
251✔
158
                state.transpileToken(this.name),
159
                ' ',
160
                state.transpileToken(this.equals),
161
                ' ',
162
                ...this.value.transpile(state)
163
            ];
164
        }
165
    }
166

167
    walk(visitor: WalkVisitor, options: WalkOptions) {
168
        if (options.walkMode & InternalWalkMode.walkExpressions) {
2,279✔
169
            walk(this, 'value', visitor, options);
1,814✔
170
        }
171
    }
172

173
    public clone() {
174
        return this.finalizeClone(
15✔
175
            new AssignmentStatement(
176
                util.cloneToken(this.equals),
177
                util.cloneToken(this.name),
178
                this.value?.clone()
45✔
179
            ),
180
            ['value']
181
        );
182
    }
183
}
184

185
export class Block extends Statement {
1✔
186
    constructor(
187
        readonly statements: Statement[],
2,438✔
188
        readonly startingRange?: Range
2,438✔
189
    ) {
190
        super();
2,438✔
191
        this.range = util.createBoundingRange(
2,438✔
192
            { range: this.startingRange },
193
            ...(statements ?? [])
7,314✔
194
        );
195
    }
196

197
    public readonly range: Range | undefined;
198

199
    transpile(state: BrsTranspileState) {
200
        state.blockDepth++;
506✔
201
        let results = [] as TranspileResult;
506✔
202
        for (let i = 0; i < this.statements.length; i++) {
506✔
203
            let previousStatement = this.statements[i - 1];
761✔
204
            let statement = this.statements[i];
761✔
205

206
            //if comment is on same line as parent
207
            if (isCommentStatement(statement) &&
761✔
208
                (util.linesTouch(state.lineage[0], statement) || util.linesTouch(previousStatement, statement))
209
            ) {
210
                results.push(' ');
65✔
211

212
                //is not a comment
213
            } else {
214
                //add a newline and indent
215
                results.push(
696✔
216
                    state.newline,
217
                    state.indent()
218
                );
219
            }
220

221
            //push block onto parent list
222
            state.lineage.unshift(this);
761✔
223
            results.push(
761✔
224
                ...statement.transpile(state)
225
            );
226
            state.lineage.shift();
761✔
227
        }
228
        state.blockDepth--;
506✔
229
        return results;
506✔
230
    }
231

232
    walk(visitor: WalkVisitor, options: WalkOptions) {
233
        if (options.walkMode & InternalWalkMode.walkStatements) {
5,819✔
234
            walkArray(this.statements, visitor, options, this);
5,813✔
235
        }
236
    }
237

238
    public clone() {
239
        return this.finalizeClone(
121✔
240
            new Block(
241
                this.statements?.map(s => s?.clone()),
110✔
242
                util.cloneRange(this.startingRange)
243
            ),
244
            ['statements']
245
        );
246
    }
247
}
248

249
export class ExpressionStatement extends Statement {
1✔
250
    constructor(
251
        readonly expression: Expression
336✔
252
    ) {
253
        super();
336✔
254
        this.range = this.expression?.range;
336✔
255
    }
256

257
    public readonly range: Range | undefined;
258

259
    transpile(state: BrsTranspileState) {
260
        return this.expression.transpile(state);
52✔
261
    }
262

263
    walk(visitor: WalkVisitor, options: WalkOptions) {
264
        if (options.walkMode & InternalWalkMode.walkExpressions) {
984✔
265
            walk(this, 'expression', visitor, options);
742✔
266
        }
267
    }
268

269
    public clone() {
270
        return this.finalizeClone(
13✔
271
            new ExpressionStatement(
272
                this.expression?.clone()
39✔
273
            ),
274
            ['expression']
275
        );
276
    }
277
}
278

279
export class CommentStatement extends Statement implements Expression, TypedefProvider {
1✔
280
    constructor(
281
        public comments: Token[]
267✔
282
    ) {
283
        super();
267✔
284
        this.visitMode = InternalWalkMode.visitStatements | InternalWalkMode.visitExpressions;
267✔
285
        if (this.comments?.length > 0) {
267✔
286
            this.range = util.createBoundingRange(
265✔
287
                ...this.comments
288
            );
289
        }
290
    }
291

292
    public range: Range | undefined;
293

294
    get text() {
295
        return this.comments.map(x => x.text).join('\n');
39✔
296
    }
297

298
    transpile(state: BrsTranspileState) {
299
        let result = [] as TranspileResult;
105✔
300
        for (let i = 0; i < this.comments.length; i++) {
105✔
301
            let comment = this.comments[i];
106✔
302
            if (i > 0) {
106✔
303
                result.push(state.indent());
1✔
304
            }
305
            result.push(
106✔
306
                state.transpileToken(comment)
307
            );
308
            //add newline for all except final comment
309
            if (i < this.comments.length - 1) {
106✔
310
                result.push(state.newline);
1✔
311
            }
312
        }
313
        return result;
105✔
314
    }
315

316
    public getTypedef(state: TranspileState) {
317
        return this.transpile(state as BrsTranspileState);
×
318
    }
319

320
    walk(visitor: WalkVisitor, options: WalkOptions) {
321
        //nothing to walk
322
    }
323

324
    public clone() {
325
        return this.finalizeClone(
4✔
326
            new CommentStatement(
327
                this.comments?.map(x => util.cloneToken(x))
3✔
328
            ),
329
            ['comments' as any]
330
        );
331
    }
332
}
333

334
export class ExitForStatement extends Statement {
1✔
335
    constructor(
336
        readonly tokens: {
6✔
337
            exitFor: Token;
338
        }
339
    ) {
340
        super();
6✔
341
        this.range = this.tokens.exitFor.range;
6✔
342
    }
343

344
    public readonly range: Range;
345

346
    transpile(state: BrsTranspileState) {
347
        return [
2✔
348
            state.transpileToken(this.tokens.exitFor)
349
        ];
350
    }
351

352
    walk(visitor: WalkVisitor, options: WalkOptions) {
353
        //nothing to walk
354
    }
355

356
    public clone() {
357
        return this.finalizeClone(
1✔
358
            new ExitForStatement({
359
                exitFor: util.cloneToken(this.tokens.exitFor)
360
            })
361
        );
362
    }
363
}
364

365
export class ExitWhileStatement extends Statement {
1✔
366
    constructor(
367
        readonly tokens: {
9✔
368
            exitWhile: Token;
369
        }
370
    ) {
371
        super();
9✔
372
        this.range = this.tokens.exitWhile.range;
9✔
373
    }
374

375
    public readonly range: Range;
376

377
    transpile(state: BrsTranspileState) {
378
        return [
3✔
379
            state.transpileToken(this.tokens.exitWhile)
380
        ];
381
    }
382

383
    walk(visitor: WalkVisitor, options: WalkOptions) {
384
        //nothing to walk
385
    }
386

387
    public clone() {
388
        return this.finalizeClone(
1✔
389
            new ExitWhileStatement({
390
                exitWhile: util.cloneToken(this.tokens.exitWhile)
391
            })
392
        );
393
    }
394
}
395

396
export class FunctionStatement extends Statement implements TypedefProvider {
1✔
397
    constructor(
398
        public name: Identifier,
2,142✔
399
        public func: FunctionExpression
2,142✔
400
    ) {
401
        super();
2,142✔
402
        this.range = this.func?.range;
2,142✔
403
    }
404

405
    public readonly range: Range | undefined;
406

407
    /**
408
     * Get the name of this expression based on the parse mode
409
     */
410
    public getName(parseMode: ParseMode) {
411
        const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
2,185✔
412
        if (namespace) {
2,185✔
413
            let delimiter = parseMode === ParseMode.BrighterScript ? '.' : '_';
304✔
414
            let namespaceName = namespace.getName(parseMode);
304✔
415
            return namespaceName + delimiter + this.name?.text;
304✔
416
        } else {
417
            return this.name.text;
1,881✔
418
        }
419
    }
420

421
    /**
422
     * Get the name of the wrapping namespace (if it exists)
423
     * @deprecated use `.findAncestor(isNamespaceStatement)` instead.
424
     */
425
    public get namespaceName() {
426
        return this.findAncestor<NamespaceStatement>(isNamespaceStatement)?.nameExpression;
×
427
    }
428

429
    transpile(state: BrsTranspileState) {
430
        //create a fake token using the full transpiled name
431
        let nameToken = {
305✔
432
            ...this.name,
433
            text: this.getName(ParseMode.BrightScript)
434
        };
435

436
        return this.func.transpile(state, nameToken);
305✔
437
    }
438

439
    getTypedef(state: BrsTranspileState) {
440
        let result = [] as TranspileResult;
9✔
441
        for (let annotation of this.annotations ?? []) {
9✔
442
            result.push(
2✔
443
                ...annotation.getTypedef(state),
444
                state.newline,
445
                state.indent()
446
            );
447
        }
448

449
        result.push(
9✔
450
            ...this.func.getTypedef(state)
451
        );
452
        return result;
9✔
453
    }
454

455
    walk(visitor: WalkVisitor, options: WalkOptions) {
456
        if (options.walkMode & InternalWalkMode.walkExpressions) {
2,934✔
457
            walk(this, 'func', visitor, options);
2,907✔
458
        }
459
    }
460

461
    public clone() {
462
        return this.finalizeClone(
103✔
463
            new FunctionStatement(
464
                util.cloneToken(this.name),
465
                this.func?.clone()
309✔
466
            ),
467
            ['func']
468
        );
469
    }
470
}
471

472
export class IfStatement extends Statement {
1✔
473
    constructor(
474
        readonly tokens: {
244✔
475
            if: Token;
476
            then?: Token;
477
            else?: Token;
478
            endIf?: Token;
479
        },
480
        readonly condition: Expression,
244✔
481
        readonly thenBranch: Block,
244✔
482
        readonly elseBranch?: IfStatement | Block,
244✔
483
        readonly isInline?: boolean
244✔
484
    ) {
485
        super();
244✔
486
        this.range = util.createBoundingRange(
244✔
487
            tokens.if,
488
            condition,
489
            tokens.then,
490
            thenBranch,
491
            tokens.else,
492
            elseBranch,
493
            tokens.endIf
494
        );
495
    }
496
    public readonly range: Range | undefined;
497

498
    transpile(state: BrsTranspileState) {
499
        let results = [] as TranspileResult;
75✔
500
        //if   (already indented by block)
501
        results.push(state.transpileToken(this.tokens.if));
75✔
502
        results.push(' ');
75✔
503
        //conditions
504
        results.push(...this.condition.transpile(state));
75✔
505
        //then
506
        if (this.tokens.then) {
75✔
507
            results.push(' ');
64✔
508
            results.push(
64✔
509
                state.transpileToken(this.tokens.then)
510
            );
511
        }
512
        state.lineage.unshift(this);
75✔
513

514
        //if statement body
515
        let thenNodes = this.thenBranch.transpile(state);
75✔
516
        state.lineage.shift();
75✔
517
        if (thenNodes.length > 0) {
75✔
518
            results.push(thenNodes);
64✔
519
        }
520
        results.push('\n');
75✔
521

522
        //else branch
523
        if (this.tokens.else) {
75✔
524
            //else
525
            results.push(
56✔
526
                state.indent(),
527
                state.transpileToken(this.tokens.else)
528
            );
529
        }
530

531
        if (this.elseBranch) {
75✔
532
            if (isIfStatement(this.elseBranch)) {
56✔
533
                //chained elseif
534
                state.lineage.unshift(this.elseBranch);
19✔
535
                let body = this.elseBranch.transpile(state);
19✔
536
                state.lineage.shift();
19✔
537

538
                if (body.length > 0) {
19!
539
                    //zero or more spaces between the `else` and the `if`
540
                    results.push(this.elseBranch.tokens.if.leadingWhitespace!);
19✔
541
                    results.push(...body);
19✔
542

543
                    // stop here because chained if will transpile the rest
544
                    return results;
19✔
545
                } else {
546
                    results.push('\n');
×
547
                }
548

549
            } else {
550
                //else body
551
                state.lineage.unshift(this.tokens.else!);
37✔
552
                let body = this.elseBranch.transpile(state);
37✔
553
                state.lineage.shift();
37✔
554

555
                if (body.length > 0) {
37✔
556
                    results.push(...body);
35✔
557
                }
558
                results.push('\n');
37✔
559
            }
560
        }
561

562
        //end if
563
        results.push(state.indent());
56✔
564
        if (this.tokens.endIf) {
56!
565
            results.push(
56✔
566
                state.transpileToken(this.tokens.endIf)
567
            );
568
        } else {
569
            results.push('end if');
×
570
        }
571
        return results;
56✔
572
    }
573

574
    walk(visitor: WalkVisitor, options: WalkOptions) {
575
        if (options.walkMode & InternalWalkMode.walkExpressions) {
545✔
576
            walk(this, 'condition', visitor, options);
428✔
577
        }
578
        if (options.walkMode & InternalWalkMode.walkStatements) {
545✔
579
            walk(this, 'thenBranch', visitor, options);
543✔
580
        }
581
        if (this.elseBranch && options.walkMode & InternalWalkMode.walkStatements) {
545✔
582
            walk(this, 'elseBranch', visitor, options);
308✔
583
        }
584
    }
585

586
    public clone() {
587
        return this.finalizeClone(
5✔
588
            new IfStatement(
589
                {
590
                    if: util.cloneToken(this.tokens.if),
591
                    else: util.cloneToken(this.tokens.else),
592
                    endIf: util.cloneToken(this.tokens.endIf),
593
                    then: util.cloneToken(this.tokens.then)
594
                },
595
                this.condition?.clone(),
15✔
596
                this.thenBranch?.clone(),
15✔
597
                this.elseBranch?.clone(),
15✔
598
                this.isInline
599
            ),
600
            ['condition', 'thenBranch', 'elseBranch']
601
        );
602
    }
603
}
604

605
export class IncrementStatement extends Statement {
1✔
606
    constructor(
607
        readonly value: Expression,
21✔
608
        readonly operator: Token
21✔
609
    ) {
610
        super();
21✔
611
        this.range = util.createBoundingRange(
21✔
612
            value,
613
            operator
614
        );
615
    }
616

617
    public readonly range: Range | undefined;
618

619
    transpile(state: BrsTranspileState) {
620
        return [
8✔
621
            ...this.value.transpile(state),
622
            state.transpileToken(this.operator)
623
        ];
624
    }
625

626
    walk(visitor: WalkVisitor, options: WalkOptions) {
627
        if (options.walkMode & InternalWalkMode.walkExpressions) {
39✔
628
            walk(this, 'value', visitor, options);
32✔
629
        }
630
    }
631

632
    public clone() {
633
        return this.finalizeClone(
2✔
634
            new IncrementStatement(
635
                this.value?.clone(),
6✔
636
                util.cloneToken(this.operator)
637
            ),
638
            ['value']
639
        );
640
    }
641
}
642

643
/** Used to indent the current `print` position to the next 16-character-width output zone. */
644
export interface PrintSeparatorTab extends Token {
645
    kind: TokenKind.Comma;
646
}
647

648
/** Used to insert a single whitespace character at the current `print` position. */
649
export interface PrintSeparatorSpace extends Token {
650
    kind: TokenKind.Semicolon;
651
}
652

653
/**
654
 * Represents a `print` statement within BrightScript.
655
 */
656
export class PrintStatement extends Statement {
1✔
657
    /**
658
     * Creates a new internal representation of a BrightScript `print` statement.
659
     * @param tokens the tokens for this statement
660
     * @param tokens.print a print token
661
     * @param expressions an array of expressions or `PrintSeparator`s to be evaluated and printed.
662
     */
663
    constructor(
664
        readonly tokens: {
739✔
665
            print: Token;
666
        },
667
        readonly expressions: Array<Expression | PrintSeparatorTab | PrintSeparatorSpace>
739✔
668
    ) {
669
        super();
739✔
670
        this.range = util.createBoundingRange(
739✔
671
            tokens.print,
672
            ...(expressions ?? [])
2,217✔
673
        );
674
    }
675

676
    public readonly range: Range | undefined;
677

678
    transpile(state: BrsTranspileState) {
679
        let result = [
206✔
680
            state.transpileToken(this.tokens.print),
681
            ' '
682
        ] as TranspileResult;
683
        for (let i = 0; i < this.expressions.length; i++) {
206✔
684
            const expressionOrSeparator: any = this.expressions[i];
270✔
685
            if (expressionOrSeparator.transpile) {
270✔
686
                result.push(...(expressionOrSeparator as ExpressionStatement).transpile(state));
244✔
687
            } else {
688
                result.push(
26✔
689
                    state.tokenToSourceNode(expressionOrSeparator)
690
                );
691
            }
692
            //if there's an expression after us, add a space
693
            if ((this.expressions[i + 1] as any)?.transpile) {
270✔
694
                result.push(' ');
38✔
695
            }
696
        }
697
        return result;
206✔
698
    }
699

700
    walk(visitor: WalkVisitor, options: WalkOptions) {
701
        if (options.walkMode & InternalWalkMode.walkExpressions) {
1,781✔
702
            //sometimes we have semicolon Tokens in the expressions list (should probably fix that...), so only walk the actual expressions
703
            walkArray(this.expressions as AstNode[], visitor, options, this, (item) => isExpression(item as any));
1,594✔
704
        }
705
    }
706

707
    public clone() {
708
        return this.finalizeClone(
47✔
709
            new PrintStatement(
710
                {
711
                    print: util.cloneToken(this.tokens.print)
712
                },
713
                this.expressions?.map(e => {
141✔
714
                    if (isExpression(e as any)) {
47✔
715
                        return (e as Expression).clone();
46✔
716
                    } else {
717
                        return util.cloneToken(e as Token);
1✔
718
                    }
719
                })
720
            ),
721
            ['expressions' as any]
722
        );
723
    }
724
}
725

726
export class DimStatement extends Statement {
1✔
727
    constructor(
728
        public dimToken: Token,
46✔
729
        public identifier?: Identifier,
46✔
730
        public openingSquare?: Token,
46✔
731
        public dimensions?: Expression[],
46✔
732
        public closingSquare?: Token
46✔
733
    ) {
734
        super();
46✔
735
        this.range = util.createBoundingRange(
46✔
736
            dimToken,
737
            identifier,
738
            openingSquare,
739
            ...(dimensions ?? []),
138✔
740
            closingSquare
741
        );
742
    }
743
    public range: Range | undefined;
744

745
    public transpile(state: BrsTranspileState) {
746
        let result = [
15✔
747
            state.transpileToken(this.dimToken),
748
            ' ',
749
            state.transpileToken(this.identifier!),
750
            state.transpileToken(this.openingSquare!)
751
        ] as TranspileResult;
752
        for (let i = 0; i < this.dimensions!.length; i++) {
15✔
753
            if (i > 0) {
32✔
754
                result.push(', ');
17✔
755
            }
756
            result.push(
32✔
757
                ...this.dimensions![i].transpile(state)
758
            );
759
        }
760
        result.push(state.transpileToken(this.closingSquare!));
15✔
761
        return result;
15✔
762
    }
763

764
    public walk(visitor: WalkVisitor, options: WalkOptions) {
765
        if (this.dimensions?.length !== undefined && this.dimensions?.length > 0 && options.walkMode & InternalWalkMode.walkExpressions) {
98!
766
            walkArray(this.dimensions, visitor, options, this);
71✔
767

768
        }
769
    }
770

771
    public clone() {
772
        return this.finalizeClone(
3✔
773
            new DimStatement(
774
                util.cloneToken(this.dimToken),
775
                util.cloneToken(this.identifier),
776
                util.cloneToken(this.openingSquare),
777
                this.dimensions?.map(e => e?.clone()),
5✔
778
                util.cloneToken(this.closingSquare)
779
            ),
780
            ['dimensions']
781
        );
782
    }
783
}
784

785
export class GotoStatement extends Statement {
1✔
786
    constructor(
787
        readonly tokens: {
12✔
788
            goto: Token;
789
            label: Token;
790
        }
791
    ) {
792
        super();
12✔
793
        this.range = util.createBoundingRange(
12✔
794
            tokens.goto,
795
            tokens.label
796
        );
797
    }
798

799
    public readonly range: Range | undefined;
800

801
    transpile(state: BrsTranspileState) {
802
        return [
2✔
803
            state.transpileToken(this.tokens.goto),
804
            ' ',
805
            state.transpileToken(this.tokens.label)
806
        ];
807
    }
808

809
    walk(visitor: WalkVisitor, options: WalkOptions) {
810
        //nothing to walk
811
    }
812

813
    public clone() {
814
        return this.finalizeClone(
1✔
815
            new GotoStatement({
816
                goto: util.cloneToken(this.tokens.goto),
817
                label: util.cloneToken(this.tokens.label)
818
            })
819
        );
820
    }
821
}
822

823
export class LabelStatement extends Statement {
1✔
824
    constructor(
825
        readonly tokens: {
12✔
826
            identifier: Token;
827
            colon: Token;
828
        }
829
    ) {
830
        super();
12✔
831
        this.range = util.createBoundingRange(
12✔
832
            tokens.identifier,
833
            tokens.colon
834
        );
835
    }
836

837
    public readonly range: Range | undefined;
838

839
    transpile(state: BrsTranspileState) {
840
        return [
2✔
841
            state.transpileToken(this.tokens.identifier),
842
            state.transpileToken(this.tokens.colon)
843

844
        ];
845
    }
846

847
    walk(visitor: WalkVisitor, options: WalkOptions) {
848
        //nothing to walk
849
    }
850

851
    public clone() {
852
        return this.finalizeClone(
1✔
853
            new LabelStatement({
854
                identifier: util.cloneToken(this.tokens.identifier),
855
                colon: util.cloneToken(this.tokens.colon)
856
            })
857
        );
858
    }
859
}
860

861
export class ReturnStatement extends Statement {
1✔
862
    constructor(
863
        readonly tokens: {
220✔
864
            return: Token;
865
        },
866
        readonly value?: Expression
220✔
867
    ) {
868
        super();
220✔
869
        this.range = util.createBoundingRange(
220✔
870
            tokens.return,
871
            value
872
        );
873
    }
874

875
    public readonly range: Range | undefined;
876

877
    transpile(state: BrsTranspileState) {
878
        let result = [] as TranspileResult;
18✔
879
        result.push(
18✔
880
            state.transpileToken(this.tokens.return)
881
        );
882
        if (this.value) {
18✔
883
            result.push(' ');
17✔
884
            result.push(...this.value.transpile(state));
17✔
885
        }
886
        return result;
18✔
887
    }
888

889
    walk(visitor: WalkVisitor, options: WalkOptions) {
890
        if (options.walkMode & InternalWalkMode.walkExpressions) {
488✔
891
            walk(this, 'value', visitor, options);
402✔
892
        }
893
    }
894

895
    public clone() {
896
        return this.finalizeClone(
3✔
897
            new ReturnStatement(
898
                {
899
                    return: util.cloneToken(this.tokens.return)
900
                },
901
                this.value?.clone()
9✔
902
            ),
903
            ['value']
904
        );
905
    }
906
}
907

908
export class EndStatement extends Statement {
1✔
909
    constructor(
910
        readonly tokens: {
10✔
911
            end: Token;
912
        }
913
    ) {
914
        super();
10✔
915
        this.range = tokens.end.range;
10✔
916
    }
917

918
    public readonly range: Range;
919

920
    transpile(state: BrsTranspileState) {
921
        return [
2✔
922
            state.transpileToken(this.tokens.end)
923
        ];
924
    }
925

926
    walk(visitor: WalkVisitor, options: WalkOptions) {
927
        //nothing to walk
928
    }
929

930
    public clone() {
931
        return this.finalizeClone(
1✔
932
            new EndStatement({
933
                end: util.cloneToken(this.tokens.end)
934
            })
935
        );
936
    }
937
}
938

939
export class StopStatement extends Statement {
1✔
940
    constructor(
941
        readonly tokens: {
18✔
942
            stop: Token;
943
        }
944
    ) {
945
        super();
18✔
946
        this.range = tokens?.stop?.range;
18!
947
    }
948

949
    public readonly range: Range;
950

951
    transpile(state: BrsTranspileState) {
952
        return [
2✔
953
            state.transpileToken(this.tokens.stop)
954
        ];
955
    }
956

957
    walk(visitor: WalkVisitor, options: WalkOptions) {
958
        //nothing to walk
959
    }
960

961
    public clone() {
962
        return this.finalizeClone(
1✔
963
            new StopStatement({
964
                stop: util.cloneToken(this.tokens.stop)
965
            })
966
        );
967
    }
968
}
969

970
export class ForStatement extends Statement {
1✔
971
    constructor(
972
        public forToken: Token,
39✔
973
        public counterDeclaration: AssignmentStatement,
39✔
974
        public toToken: Token,
39✔
975
        public finalValue: Expression,
39✔
976
        public body: Block,
39✔
977
        public endForToken: Token,
39✔
978
        public stepToken?: Token,
39✔
979
        public increment?: Expression
39✔
980
    ) {
981
        super();
39✔
982
        this.range = util.createBoundingRange(
39✔
983
            forToken,
984
            counterDeclaration,
985
            toToken,
986
            finalValue,
987
            stepToken,
988
            increment,
989
            body,
990
            endForToken
991
        );
992
    }
993

994
    public readonly range: Range | undefined;
995

996
    transpile(state: BrsTranspileState) {
997
        let result = [] as TranspileResult;
9✔
998
        //for
999
        result.push(
9✔
1000
            state.transpileToken(this.forToken),
1001
            ' '
1002
        );
1003
        //i=1
1004
        result.push(
9✔
1005
            ...this.counterDeclaration.transpile(state),
1006
            ' '
1007
        );
1008
        //to
1009
        result.push(
9✔
1010
            state.transpileToken(this.toToken),
1011
            ' '
1012
        );
1013
        //final value
1014
        result.push(this.finalValue.transpile(state));
9✔
1015
        //step
1016
        if (this.stepToken) {
9✔
1017
            result.push(
4✔
1018
                ' ',
1019
                state.transpileToken(this.stepToken),
1020
                ' ',
1021
                this.increment!.transpile(state)
1022
            );
1023
        }
1024
        //loop body
1025
        state.lineage.unshift(this);
9✔
1026
        result.push(...this.body.transpile(state));
9✔
1027
        state.lineage.shift();
9✔
1028

1029
        // add new line before "end for"
1030
        result.push('\n');
9✔
1031

1032
        //end for
1033
        result.push(
9✔
1034
            state.indent(),
1035
            state.transpileToken(this.endForToken)
1036
        );
1037

1038
        return result;
9✔
1039
    }
1040

1041
    walk(visitor: WalkVisitor, options: WalkOptions) {
1042
        if (options.walkMode & InternalWalkMode.walkStatements) {
85✔
1043
            walk(this, 'counterDeclaration', visitor, options);
84✔
1044
        }
1045
        if (options.walkMode & InternalWalkMode.walkExpressions) {
85✔
1046
            walk(this, 'finalValue', visitor, options);
64✔
1047
            walk(this, 'increment', visitor, options);
64✔
1048
        }
1049
        if (options.walkMode & InternalWalkMode.walkStatements) {
85✔
1050
            walk(this, 'body', visitor, options);
84✔
1051
        }
1052
    }
1053

1054
    public clone() {
1055
        return this.finalizeClone(
5✔
1056
            new ForStatement(
1057
                util.cloneToken(this.forToken),
1058
                this.counterDeclaration?.clone(),
15✔
1059
                util.cloneToken(this.toToken),
1060
                this.finalValue?.clone(),
15✔
1061
                this.body?.clone(),
15✔
1062
                util.cloneToken(this.endForToken),
1063
                util.cloneToken(this.stepToken),
1064
                this.increment?.clone()
15✔
1065
            ),
1066
            ['counterDeclaration', 'finalValue', 'body', 'increment']
1067
        );
1068
    }
1069
}
1070

1071
export class ForEachStatement extends Statement {
1✔
1072
    constructor(
1073
        readonly tokens: {
23✔
1074
            forEach: Token;
1075
            in: Token;
1076
            endFor: Token;
1077
        },
1078
        readonly item: Token,
23✔
1079
        readonly target: Expression,
23✔
1080
        readonly body: Block
23✔
1081
    ) {
1082
        super();
23✔
1083
        this.range = util.createBoundingRange(
23✔
1084
            tokens.forEach,
1085
            item,
1086
            tokens.in,
1087
            target,
1088
            body,
1089
            tokens.endFor
1090
        );
1091
    }
1092

1093
    public readonly range: Range | undefined;
1094

1095
    transpile(state: BrsTranspileState) {
1096
        let result = [] as TranspileResult;
5✔
1097
        //for each
1098
        result.push(
5✔
1099
            state.transpileToken(this.tokens.forEach),
1100
            ' '
1101
        );
1102
        //item
1103
        result.push(
5✔
1104
            state.transpileToken(this.item),
1105
            ' '
1106
        );
1107
        //in
1108
        result.push(
5✔
1109
            state.transpileToken(this.tokens.in),
1110
            ' '
1111
        );
1112
        //target
1113
        result.push(...this.target.transpile(state));
5✔
1114
        //body
1115
        state.lineage.unshift(this);
5✔
1116
        result.push(...this.body.transpile(state));
5✔
1117
        state.lineage.shift();
5✔
1118

1119
        // add new line before "end for"
1120
        result.push('\n');
5✔
1121

1122
        //end for
1123
        result.push(
5✔
1124
            state.indent(),
1125
            state.transpileToken(this.tokens.endFor)
1126
        );
1127
        return result;
5✔
1128
    }
1129

1130
    walk(visitor: WalkVisitor, options: WalkOptions) {
1131
        if (options.walkMode & InternalWalkMode.walkExpressions) {
48✔
1132
            walk(this, 'target', visitor, options);
37✔
1133
        }
1134
        if (options.walkMode & InternalWalkMode.walkStatements) {
48✔
1135
            walk(this, 'body', visitor, options);
47✔
1136
        }
1137
    }
1138

1139
    public clone() {
1140
        return this.finalizeClone(
2✔
1141
            new ForEachStatement(
1142
                {
1143
                    forEach: util.cloneToken(this.tokens.forEach),
1144
                    in: util.cloneToken(this.tokens.in),
1145
                    endFor: util.cloneToken(this.tokens.endFor)
1146
                },
1147
                util.cloneToken(this.item),
1148
                this.target?.clone(),
6✔
1149
                this.body?.clone()
6✔
1150
            ),
1151
            ['target', 'body']
1152
        );
1153
    }
1154
}
1155

1156
export class WhileStatement extends Statement {
1✔
1157
    constructor(
1158
        readonly tokens: {
27✔
1159
            while: Token;
1160
            endWhile: Token;
1161
        },
1162
        readonly condition: Expression,
27✔
1163
        readonly body: Block
27✔
1164
    ) {
1165
        super();
27✔
1166
        this.range = util.createBoundingRange(
27✔
1167
            tokens.while,
1168
            condition,
1169
            body,
1170
            tokens.endWhile
1171
        );
1172
    }
1173

1174
    public readonly range: Range | undefined;
1175

1176
    transpile(state: BrsTranspileState) {
1177
        let result = [] as TranspileResult;
4✔
1178
        //while
1179
        result.push(
4✔
1180
            state.transpileToken(this.tokens.while),
1181
            ' '
1182
        );
1183
        //condition
1184
        result.push(
4✔
1185
            ...this.condition.transpile(state)
1186
        );
1187
        state.lineage.unshift(this);
4✔
1188
        //body
1189
        result.push(...this.body.transpile(state));
4✔
1190
        state.lineage.shift();
4✔
1191

1192
        //trailing newline only if we have body statements
1193
        result.push('\n');
4✔
1194

1195
        //end while
1196
        result.push(
4✔
1197
            state.indent(),
1198
            state.transpileToken(this.tokens.endWhile)
1199
        );
1200

1201
        return result;
4✔
1202
    }
1203

1204
    walk(visitor: WalkVisitor, options: WalkOptions) {
1205
        if (options.walkMode & InternalWalkMode.walkExpressions) {
52✔
1206
            walk(this, 'condition', visitor, options);
38✔
1207
        }
1208
        if (options.walkMode & InternalWalkMode.walkStatements) {
52✔
1209
            walk(this, 'body', visitor, options);
51✔
1210
        }
1211
    }
1212

1213
    public clone() {
1214
        return this.finalizeClone(
4✔
1215
            new WhileStatement(
1216
                {
1217
                    while: util.cloneToken(this.tokens.while),
1218
                    endWhile: util.cloneToken(this.tokens.endWhile)
1219
                },
1220
                this.condition?.clone(),
12✔
1221
                this.body?.clone()
12✔
1222
            ),
1223
            ['condition', 'body']
1224
        );
1225
    }
1226
}
1227

1228
export class DottedSetStatement extends Statement {
1✔
1229
    constructor(
1230
        readonly obj: Expression,
250✔
1231
        readonly name: Identifier,
250✔
1232
        readonly value: Expression,
250✔
1233
        readonly dot?: Token,
250✔
1234
        readonly equals?: Token
250✔
1235
    ) {
1236
        super();
250✔
1237
        this.range = util.createBoundingRange(
250✔
1238
            obj,
1239
            dot,
1240
            name,
1241
            equals,
1242
            value
1243
        );
1244
    }
1245

1246
    public readonly range: Range | undefined;
1247

1248
    transpile(state: BrsTranspileState) {
1249
        //if the value is a compound assignment, don't add the obj, dot, name, or operator...the expression will handle that
1250
        if (CompoundAssignmentOperators.includes((this.value as BinaryExpression)?.operator?.kind)) {
16!
1251
            return this.value.transpile(state);
2✔
1252
        } else {
1253
            return [
14✔
1254
                //object
1255
                ...this.obj.transpile(state),
1256
                this.dot ? state.tokenToSourceNode(this.dot) : '.',
14✔
1257
                //name
1258
                state.transpileToken(this.name),
1259
                ' ',
1260
                state.transpileToken(this.equals, '='),
1261
                ' ',
1262
                //right-hand-side of assignment
1263
                ...this.value.transpile(state)
1264
            ];
1265
        }
1266
    }
1267

1268
    walk(visitor: WalkVisitor, options: WalkOptions) {
1269
        if (options.walkMode & InternalWalkMode.walkExpressions) {
332✔
1270
            walk(this, 'obj', visitor, options);
301✔
1271
            walk(this, 'value', visitor, options);
301✔
1272
        }
1273
    }
1274

1275
    public clone() {
1276
        return this.finalizeClone(
2✔
1277
            new DottedSetStatement(
1278
                this.obj?.clone(),
6✔
1279
                util.cloneToken(this.name),
1280
                this.value?.clone(),
6✔
1281
                util.cloneToken(this.dot),
1282
                util.cloneToken(this.equals)
1283
            ),
1284
            ['obj', 'value']
1285
        );
1286
    }
1287
}
1288

1289
export class IndexedSetStatement extends Statement {
1✔
1290
    constructor(
1291
        readonly obj: Expression,
55✔
1292
        readonly index: Expression,
55✔
1293
        readonly value: Expression,
55✔
1294
        readonly openingSquare: Token,
55✔
1295
        readonly closingSquare: Token,
55✔
1296
        readonly additionalIndexes?: Expression[],
55✔
1297
        readonly equals?: Token
55✔
1298
    ) {
1299
        super();
55✔
1300
        this.additionalIndexes ??= [];
55✔
1301
        this.range = util.createBoundingRange(
55✔
1302
            obj,
1303
            openingSquare,
1304
            index,
1305
            closingSquare,
1306
            equals,
1307
            value,
1308
            ...this.additionalIndexes
1309
        );
1310
    }
1311

1312
    public readonly range: Range | undefined;
1313

1314
    transpile(state: BrsTranspileState) {
1315
        //if the value is a component assignment, don't add the obj, index or operator...the expression will handle that
1316
        if (CompoundAssignmentOperators.includes((this.value as BinaryExpression)?.operator?.kind)) {
21!
1317
            return this.value.transpile(state);
2✔
1318
        } else {
1319
            const result = [];
19✔
1320
            result.push(
19✔
1321
                //obj
1322
                ...this.obj.transpile(state),
1323
                //   [
1324
                state.transpileToken(this.openingSquare)
1325
            );
1326
            const indexes = [this.index, ...this.additionalIndexes ?? []];
19!
1327
            for (let i = 0; i < indexes.length; i++) {
19✔
1328
                //add comma between indexes
1329
                if (i > 0) {
20✔
1330
                    result.push(', ');
1✔
1331
                }
1332
                let index = indexes[i];
20✔
1333
                result.push(
20✔
1334
                    ...(index?.transpile(state) ?? [])
120!
1335
                );
1336
            }
1337
            result.push(
19✔
1338
                state.transpileToken(this.closingSquare),
1339
                ' ',
1340
                state.transpileToken(this.equals, '='),
1341
                ' ',
1342
                ...this.value.transpile(state)
1343
            );
1344
            return result;
19✔
1345
        }
1346
    }
1347

1348
    walk(visitor: WalkVisitor, options: WalkOptions) {
1349
        if (options.walkMode & InternalWalkMode.walkExpressions) {
130✔
1350
            walk(this, 'obj', visitor, options);
106✔
1351
            walk(this, 'index', visitor, options);
106✔
1352
            walkArray(this.additionalIndexes, visitor, options, this);
106✔
1353
            walk(this, 'value', visitor, options);
106✔
1354
        }
1355
    }
1356

1357
    public clone() {
1358
        return this.finalizeClone(
6✔
1359
            new IndexedSetStatement(
1360
                this.obj?.clone(),
18✔
1361
                this.index?.clone(),
18✔
1362
                this.value?.clone(),
18✔
1363
                util.cloneToken(this.openingSquare),
1364
                util.cloneToken(this.closingSquare),
1365
                this.additionalIndexes?.map(e => e?.clone()),
2✔
1366
                util.cloneToken(this.equals)
1367
            ),
1368
            ['obj', 'index', 'value', 'additionalIndexes']
1369
        );
1370
    }
1371
}
1372

1373
export class LibraryStatement extends Statement implements TypedefProvider {
1✔
1374
    constructor(
1375
        readonly tokens: {
17✔
1376
            library: Token;
1377
            filePath: Token | undefined;
1378
        }
1379
    ) {
1380
        super();
17✔
1381
        this.range = util.createBoundingRange(
17✔
1382
            this.tokens?.library,
51✔
1383
            this.tokens?.filePath
51✔
1384
        );
1385
    }
1386

1387
    public readonly range: Range | undefined;
1388

1389
    transpile(state: BrsTranspileState) {
1390
        let result = [] as TranspileResult;
2✔
1391
        result.push(
2✔
1392
            state.transpileToken(this.tokens.library)
1393
        );
1394
        //there will be a parse error if file path is missing, but let's prevent a runtime error just in case
1395
        if (this.tokens.filePath) {
2!
1396
            result.push(
2✔
1397
                ' ',
1398
                state.transpileToken(this.tokens.filePath)
1399
            );
1400
        }
1401
        return result;
2✔
1402
    }
1403

1404
    getTypedef(state: BrsTranspileState) {
1405
        return this.transpile(state);
×
1406
    }
1407

1408
    walk(visitor: WalkVisitor, options: WalkOptions) {
1409
        //nothing to walk
1410
    }
1411

1412
    public clone() {
1413
        return this.finalizeClone(
2✔
1414
            new LibraryStatement(
1415
                this.tokens === undefined ? undefined : {
2✔
1416
                    library: util.cloneToken(this.tokens?.library),
3!
1417
                    filePath: util.cloneToken(this.tokens?.filePath)
3!
1418
                }
1419
            )
1420
        );
1421
    }
1422
}
1423

1424
export class NamespaceStatement extends Statement implements TypedefProvider {
1✔
1425
    constructor(
1426
        public keyword: Token,
264✔
1427
        // this should technically only be a VariableExpression or DottedGetExpression, but that can be enforced elsewhere
1428
        public nameExpression: NamespacedVariableNameExpression,
264✔
1429
        public body: Body,
264✔
1430
        public endKeyword: Token
264✔
1431
    ) {
1432
        super();
264✔
1433
        this.symbolTable = new SymbolTable(`NamespaceStatement: '${this.name}'`, () => this.parent?.getSymbolTable());
264!
1434
    }
1435

1436
    /**
1437
     * The string name for this namespace
1438
     */
1439
    public get name(): string {
1440
        return this.getName(ParseMode.BrighterScript);
912✔
1441
    }
1442

1443
    public get range() {
1444
        return this.cacheRange();
157✔
1445
    }
1446
    private _range: Range | undefined;
1447

1448
    public cacheRange() {
1449
        if (!this._range) {
420✔
1450
            this._range = util.createBoundingRange(
265✔
1451
                this.keyword,
1452
                this.nameExpression,
1453
                this.body,
1454
                this.endKeyword
1455
            );
1456
        }
1457
        return this._range;
420✔
1458
    }
1459

1460
    public getName(parseMode: ParseMode) {
1461
        const parentNamespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
3,176✔
1462
        let name = this.nameExpression?.getName?.(parseMode);
3,176✔
1463
        if (!name) {
3,176✔
1464
            return name;
1✔
1465
        }
1466

1467
        if (parentNamespace) {
3,175✔
1468
            const sep = parseMode === ParseMode.BrighterScript ? '.' : '_';
148✔
1469
            name = parentNamespace.getName(parseMode) + sep + name;
148✔
1470
        }
1471

1472
        return name;
3,175✔
1473
    }
1474

1475
    transpile(state: BrsTranspileState) {
1476
        //namespaces don't actually have any real content, so just transpile their bodies
1477
        return this.body.transpile(state);
36✔
1478
    }
1479

1480
    getTypedef(state: BrsTranspileState): TranspileResult {
1481
        let result = [
8✔
1482
            'namespace ',
1483
            ...this.getName(ParseMode.BrighterScript),
1484
            state.newline
1485
        ] as TranspileResult;
1486
        state.blockDepth++;
8✔
1487
        result.push(
8✔
1488
            ...this.body.getTypedef(state)
1489
        );
1490
        state.blockDepth--;
8✔
1491

1492
        result.push(
8✔
1493
            state.indent(),
1494
            'end namespace'
1495
        );
1496
        return result;
8✔
1497
    }
1498

1499
    walk(visitor: WalkVisitor, options: WalkOptions) {
1500
        if (options.walkMode & InternalWalkMode.walkExpressions) {
527✔
1501
            walk(this, 'nameExpression', visitor, options);
523✔
1502
        }
1503

1504
        if (this.body.statements.length > 0 && options.walkMode & InternalWalkMode.walkStatements) {
527✔
1505
            walk(this, 'body', visitor, options);
457✔
1506
        }
1507
    }
1508

1509
    public clone() {
1510
        const clone = this.finalizeClone(
3✔
1511
            new NamespaceStatement(
1512
                util.cloneToken(this.keyword),
1513
                this.nameExpression?.clone(),
9✔
1514
                this.body?.clone(),
9✔
1515
                util.cloneToken(this.endKeyword)
1516
            ),
1517
            ['nameExpression', 'body']
1518
        );
1519
        clone.cacheRange();
3✔
1520
        return clone;
3✔
1521
    }
1522
}
1523

1524
export class ImportStatement extends Statement implements TypedefProvider {
1✔
1525
    constructor(
1526
        readonly importToken: Token,
47✔
1527
        readonly filePathToken: Token | undefined
47✔
1528
    ) {
1529
        super();
47✔
1530
        this.range = util.createBoundingRange(
47✔
1531
            importToken,
1532
            filePathToken
1533
        );
1534
        if (this.filePathToken) {
47✔
1535
            //remove quotes
1536
            this.filePath = this.filePathToken.text.replace(/"/g, '');
45✔
1537
            if (this.filePathToken.range) {
45✔
1538
                //adjust the range to exclude the quotes
1539
                this.filePathToken.range = util.createRange(
42✔
1540
                    this.filePathToken.range.start.line,
1541
                    this.filePathToken.range.start.character + 1,
1542
                    this.filePathToken.range.end.line,
1543
                    this.filePathToken.range.end.character - 1
1544
                );
1545
            }
1546
        }
1547
    }
1548
    public filePath: string | undefined;
1549
    public range: Range | undefined;
1550

1551
    transpile(state: BrsTranspileState) {
1552
        //The xml files are responsible for adding the additional script imports, but
1553
        //add the import statement as a comment just for debugging purposes
1554
        return [
3✔
1555
            `'`,
1556
            state.transpileToken(this.importToken),
1557
            ' ',
1558
            state.transpileToken(this.filePathToken!)
1559
        ];
1560
    }
1561

1562
    /**
1563
     * Get the typedef for this statement
1564
     */
1565
    public getTypedef(state: BrsTranspileState) {
1566
        return [
3✔
1567
            this.importToken.text,
1568
            ' ',
1569
            //replace any `.bs` extension with `.brs`
1570
            this.filePathToken!.text.replace(/\.bs"?$/i, '.brs"')
1571
        ];
1572
    }
1573

1574
    walk(visitor: WalkVisitor, options: WalkOptions) {
1575
        //nothing to walk
1576
    }
1577

1578
    public clone() {
1579
        return this.finalizeClone(
1✔
1580
            new ImportStatement(
1581
                util.cloneToken(this.importToken),
1582
                util.cloneToken(this.filePathToken)
1583
            )
1584
        );
1585
    }
1586
}
1587

1588
export class InterfaceStatement extends Statement implements TypedefProvider {
1✔
1589
    constructor(
1590
        interfaceToken: Token,
1591
        name: Identifier,
1592
        extendsToken: Token,
1593
        public parentInterfaceName: NamespacedVariableNameExpression,
64✔
1594
        public body: Statement[],
64✔
1595
        endInterfaceToken: Token
1596
    ) {
1597
        super();
64✔
1598
        this.tokens.interface = interfaceToken;
64✔
1599
        this.tokens.name = name;
64✔
1600
        this.tokens.extends = extendsToken;
64✔
1601
        this.tokens.endInterface = endInterfaceToken;
64✔
1602
        this.range = util.createBoundingRange(
64✔
1603
            this.tokens.interface,
1604
            this.tokens.name,
1605
            this.tokens.extends,
1606
            this.parentInterfaceName,
1607
            ...this.body ?? [],
192✔
1608
            this.tokens.endInterface
1609
        );
1610
    }
1611

1612
    public tokens = {} as {
64✔
1613
        interface: Token;
1614
        name: Identifier;
1615
        extends: Token;
1616
        endInterface: Token;
1617
    };
1618

1619
    public range: Range | undefined;
1620

1621
    /**
1622
     * Get the name of the wrapping namespace (if it exists)
1623
     * @deprecated use `.findAncestor(isNamespaceStatement)` instead.
1624
     */
1625
    public get namespaceName() {
1626
        return this.findAncestor<NamespaceStatement>(isNamespaceStatement)?.nameExpression;
×
1627
    }
1628

1629
    public get fields() {
1630
        return this.body.filter(x => isInterfaceFieldStatement(x));
×
1631
    }
1632

1633
    public get methods() {
1634
        return this.body.filter(x => isInterfaceMethodStatement(x));
×
1635
    }
1636

1637
    /**
1638
     * The name of the interface WITH its leading namespace (if applicable)
1639
     */
1640
    public get fullName() {
1641
        const name = this.tokens.name?.text;
×
1642
        if (name) {
×
1643
            const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
×
1644
            if (namespace) {
×
1645
                let namespaceName = namespace.getName(ParseMode.BrighterScript);
×
1646
                return `${namespaceName}.${name}`;
×
1647
            } else {
1648
                return name;
×
1649
            }
1650
        } else {
1651
            //return undefined which will allow outside callers to know that this interface doesn't have a name
1652
            return undefined;
×
1653
        }
1654
    }
1655

1656
    /**
1657
     * The name of the interface (without the namespace prefix)
1658
     */
1659
    public get name() {
1660
        return this.tokens.name?.text;
5!
1661
    }
1662

1663
    /**
1664
     * Get the name of this expression based on the parse mode
1665
     */
1666
    public getName(parseMode: ParseMode) {
1667
        const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
5✔
1668
        if (namespace) {
5✔
1669
            let delimiter = parseMode === ParseMode.BrighterScript ? '.' : '_';
1!
1670
            let namespaceName = namespace.getName(parseMode);
1✔
1671
            return namespaceName + delimiter + this.name;
1✔
1672
        } else {
1673
            return this.name;
4✔
1674
        }
1675
    }
1676

1677
    public transpile(state: BrsTranspileState): TranspileResult {
1678
        //interfaces should completely disappear at runtime
1679
        return [];
11✔
1680
    }
1681

1682
    getTypedef(state: BrsTranspileState) {
1683
        const result = [] as TranspileResult;
6✔
1684
        for (let annotation of this.annotations ?? []) {
6✔
1685
            result.push(
1✔
1686
                ...annotation.getTypedef(state),
1687
                state.newline,
1688
                state.indent()
1689
            );
1690
        }
1691
        result.push(
6✔
1692
            this.tokens.interface.text,
1693
            ' ',
1694
            this.tokens.name.text
1695
        );
1696
        const parentInterfaceName = this.parentInterfaceName?.getName(ParseMode.BrighterScript);
6!
1697
        if (parentInterfaceName) {
6!
1698
            result.push(
×
1699
                ' extends ',
1700
                parentInterfaceName
1701
            );
1702
        }
1703
        const body = this.body ?? [];
6!
1704
        if (body.length > 0) {
6!
1705
            state.blockDepth++;
6✔
1706
        }
1707
        for (const statement of body) {
6✔
1708
            if (isInterfaceMethodStatement(statement) || isInterfaceFieldStatement(statement)) {
21✔
1709
                result.push(
20✔
1710
                    state.newline,
1711
                    state.indent(),
1712
                    ...statement.getTypedef(state)
1713
                );
1714
            } else {
1715
                result.push(
1✔
1716
                    state.newline,
1717
                    state.indent(),
1718
                    ...statement.transpile(state)
1719
                );
1720
            }
1721
        }
1722
        if (body.length > 0) {
6!
1723
            state.blockDepth--;
6✔
1724
        }
1725
        result.push(
6✔
1726
            state.newline,
1727
            state.indent(),
1728
            'end interface',
1729
            state.newline
1730
        );
1731
        return result;
6✔
1732
    }
1733

1734
    walk(visitor: WalkVisitor, options: WalkOptions) {
1735
        //visitor-less walk function to do parent linking
1736
        walk(this, 'parentInterfaceName', null, options);
113✔
1737

1738
        if (options.walkMode & InternalWalkMode.walkStatements) {
113!
1739
            walkArray(this.body, visitor, options, this);
113✔
1740
        }
1741
    }
1742

1743
    public clone() {
1744
        return this.finalizeClone(
8✔
1745
            new InterfaceStatement(
1746
                util.cloneToken(this.tokens.interface),
1747
                util.cloneToken(this.tokens.name),
1748
                util.cloneToken(this.tokens.extends),
1749
                this.parentInterfaceName?.clone(),
24✔
1750
                this.body?.map(x => x?.clone()),
8✔
1751
                util.cloneToken(this.tokens.endInterface)
1752
            ),
1753
            ['parentInterfaceName', 'body']
1754
        );
1755
    }
1756
}
1757

1758
export class InterfaceFieldStatement extends Statement implements TypedefProvider {
1✔
1759
    public transpile(state: BrsTranspileState): TranspileResult {
1760
        throw new Error('Method not implemented.');
×
1761
    }
1762
    constructor(
1763
        nameToken: Identifier,
1764
        asToken: Token,
1765
        typeToken: Token,
1766
        public type: BscType,
61✔
1767
        optionalToken?: Token
1768
    ) {
1769
        super();
61✔
1770
        this.tokens.optional = optionalToken;
61✔
1771
        this.tokens.name = nameToken;
61✔
1772
        this.tokens.as = asToken;
61✔
1773
        this.tokens.type = typeToken;
61✔
1774
        this.range = util.createBoundingRange(
61✔
1775
            optionalToken,
1776
            nameToken,
1777
            asToken,
1778
            typeToken
1779
        );
1780
    }
1781

1782
    public range: Range | undefined;
1783

1784
    public tokens = {} as {
61✔
1785
        optional?: Token;
1786
        name: Identifier;
1787
        as: Token;
1788
        type: Token;
1789
    };
1790

1791
    public get name() {
1792
        return this.tokens.name.text;
×
1793
    }
1794

1795
    public get isOptional() {
1796
        return !!this.tokens.optional;
10✔
1797
    }
1798

1799
    walk(visitor: WalkVisitor, options: WalkOptions) {
1800
        //nothing to walk
1801
    }
1802

1803
    getTypedef(state: BrsTranspileState): TranspileResult {
1804
        const result = [] as TranspileResult;
10✔
1805
        for (let annotation of this.annotations ?? []) {
10✔
1806
            result.push(
1✔
1807
                ...annotation.getTypedef(state),
1808
                state.newline,
1809
                state.indent()
1810
            );
1811
        }
1812
        if (this.isOptional) {
10!
1813
            result.push(
×
1814
                this.tokens.optional!.text,
1815
                ' '
1816
            );
1817
        }
1818
        result.push(
10✔
1819
            this.tokens.name.text
1820
        );
1821
        if (this.tokens.type?.text?.length > 0) {
10!
1822
            result.push(
10✔
1823
                ' as ',
1824
                this.tokens.type.text
1825
            );
1826
        }
1827
        return result;
10✔
1828
    }
1829

1830
    public clone() {
1831
        return this.finalizeClone(
4✔
1832
            new InterfaceFieldStatement(
1833
                util.cloneToken(this.tokens.name),
1834
                util.cloneToken(this.tokens.as),
1835
                util.cloneToken(this.tokens.type),
1836
                this.type?.clone(),
12✔
1837
                util.cloneToken(this.tokens.optional)
1838
            )
1839
        );
1840
    }
1841

1842
}
1843

1844
export class InterfaceMethodStatement extends Statement implements TypedefProvider {
1✔
1845
    public transpile(state: BrsTranspileState): TranspileResult {
1846
        throw new Error('Method not implemented.');
×
1847
    }
1848
    constructor(
1849
        functionTypeToken: Token,
1850
        nameToken: Identifier,
1851
        leftParen: Token,
1852
        public params: FunctionParameterExpression[],
23✔
1853
        rightParen: Token,
1854
        asToken?: Token,
1855
        returnTypeToken?: Token,
1856
        public returnType?: BscType,
23✔
1857
        optionalToken?: Token
1858
    ) {
1859
        super();
23✔
1860
        this.tokens.optional = optionalToken;
23✔
1861
        this.tokens.functionType = functionTypeToken;
23✔
1862
        this.tokens.name = nameToken;
23✔
1863
        this.tokens.leftParen = leftParen;
23✔
1864
        this.tokens.rightParen = rightParen;
23✔
1865
        this.tokens.as = asToken;
23✔
1866
        this.tokens.returnType = returnTypeToken;
23✔
1867
    }
1868

1869
    public get range() {
1870
        return util.createBoundingRange(
2✔
1871
            this.tokens.optional,
1872
            this.tokens.functionType,
1873
            this.tokens.name,
1874
            this.tokens.leftParen,
1875
            ...(this.params ?? []),
6!
1876
            this.tokens.rightParen,
1877
            this.tokens.as,
1878
            this.tokens.returnType
1879
        );
1880
    }
1881

1882
    public tokens = {} as {
23✔
1883
        optional?: Token;
1884
        functionType: Token;
1885
        name: Identifier;
1886
        leftParen: Token;
1887
        rightParen: Token;
1888
        as: Token | undefined;
1889
        returnType: Token | undefined;
1890
    };
1891

1892
    public get isOptional() {
1893
        return !!this.tokens.optional;
10✔
1894
    }
1895

1896
    walk(visitor: WalkVisitor, options: WalkOptions) {
1897
        //nothing to walk
1898
    }
1899

1900
    getTypedef(state: BrsTranspileState) {
1901
        const result = [] as TranspileResult;
10✔
1902
        for (let annotation of this.annotations ?? []) {
10✔
1903
            result.push(
1✔
1904
                ...annotation.getTypedef(state),
1905
                state.newline,
1906
                state.indent()
1907
            );
1908
        }
1909

1910
        if (this.isOptional) {
10!
1911
            result.push(
×
1912
                this.tokens.optional!.text,
1913
                ' '
1914
            );
1915
        }
1916

1917
        result.push(
10✔
1918
            this.tokens.functionType.text,
1919
            ' ',
1920
            this.tokens.name.text,
1921
            '('
1922
        );
1923
        const params = this.params ?? [];
10!
1924
        for (let i = 0; i < params.length; i++) {
10✔
1925
            if (i > 0) {
2✔
1926
                result.push(', ');
1✔
1927
            }
1928
            const param = params[i];
2✔
1929
            result.push(param.name.text);
2✔
1930
            const typeToken = param.typeToken;
2✔
1931
            if (typeToken && typeToken.text.length > 0) {
2!
1932
                result.push(
2✔
1933
                    ' as ',
1934
                    typeToken.text
1935
                );
1936
            }
1937
        }
1938
        result.push(
10✔
1939
            ')'
1940
        );
1941
        const returnTypeToken = this.tokens.returnType;
10✔
1942
        if (returnTypeToken && returnTypeToken.text.length > 0) {
10!
1943
            result.push(
10✔
1944
                ' as ',
1945
                returnTypeToken.text
1946
            );
1947
        }
1948
        return result;
10✔
1949
    }
1950

1951
    public clone() {
1952
        return this.finalizeClone(
3✔
1953
            new InterfaceMethodStatement(
1954
                util.cloneToken(this.tokens.functionType),
1955
                util.cloneToken(this.tokens.name),
1956
                util.cloneToken(this.tokens.leftParen),
1957
                this.params?.map(p => p?.clone()),
3✔
1958
                util.cloneToken(this.tokens.rightParen),
1959
                util.cloneToken(this.tokens.as),
1960
                util.cloneToken(this.tokens.returnType),
1961
                this.returnType?.clone(),
9✔
1962
                util.cloneToken(this.tokens.optional)
1963
            ),
1964
            ['params']
1965
        );
1966
    }
1967
}
1968

1969
export class ClassStatement extends Statement implements TypedefProvider {
1✔
1970

1971
    constructor(
1972
        readonly classKeyword: Token,
498✔
1973
        /**
1974
         * The name of the class (without namespace prefix)
1975
         */
1976
        readonly name: Identifier,
498✔
1977
        public body: Statement[],
498✔
1978
        readonly end: Token,
498✔
1979
        readonly extendsKeyword?: Token,
498✔
1980
        readonly parentClassName?: NamespacedVariableNameExpression
498✔
1981
    ) {
1982
        super();
498✔
1983
        this.body = this.body ?? [];
498✔
1984

1985
        for (let statement of this.body) {
498✔
1986
            if (isMethodStatement(statement)) {
467✔
1987
                this.methods.push(statement);
271✔
1988
                this.memberMap[statement?.name?.text.toLowerCase()] = statement;
271!
1989
            } else if (isFieldStatement(statement)) {
196✔
1990
                this.fields.push(statement);
187✔
1991
                this.memberMap[statement.name?.text.toLowerCase()] = statement;
187!
1992
            }
1993
        }
1994

1995
        this.range = util.createBoundingRange(
498✔
1996
            classKeyword,
1997
            name,
1998
            extendsKeyword,
1999
            parentClassName,
2000
            ...(body ?? []),
1,494✔
2001
            end
2002
        );
2003
    }
2004

2005
    /**
2006
     * Get the name of the wrapping namespace (if it exists)
2007
     * @deprecated use `.findAncestor(isNamespaceStatement)` instead.
2008
     */
2009
    public get namespaceName() {
2010
        return this.findAncestor<NamespaceStatement>(isNamespaceStatement)?.nameExpression;
×
2011
    }
2012

2013

2014
    public getName(parseMode: ParseMode) {
2015
        const name = this.name?.text;
879✔
2016
        if (name) {
879✔
2017
            const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
877✔
2018
            if (namespace) {
877✔
2019
                let namespaceName = namespace.getName(parseMode);
236✔
2020
                let separator = parseMode === ParseMode.BrighterScript ? '.' : '_';
236✔
2021
                return namespaceName + separator + name;
236✔
2022
            } else {
2023
                return name;
641✔
2024
            }
2025
        } else {
2026
            //return undefined which will allow outside callers to know that this class doesn't have a name
2027
            return undefined;
2✔
2028
        }
2029
    }
2030

2031
    public memberMap = {} as Record<string, MemberStatement>;
498✔
2032
    public methods = [] as MethodStatement[];
498✔
2033
    public fields = [] as FieldStatement[];
498✔
2034

2035
    public readonly range: Range | undefined;
2036

2037
    transpile(state: BrsTranspileState) {
2038
        let result = [] as TranspileResult;
48✔
2039

2040
        const className = this.getName(ParseMode.BrightScript).replace(/\./g, '_');
48✔
2041
        const ancestors = this.getAncestors(state);
48✔
2042
        const body = this.getTranspiledClassBody(ancestors);
48✔
2043

2044
        //make the methods
2045
        result.push(...this.getTranspiledMethods(state, className, body));
48✔
2046
        //make the builder
2047
        result.push(...this.getTranspiledBuilder(state, className, ancestors, body));
48✔
2048
        result.push('\n', state.indent());
48✔
2049
        //make the class assembler (i.e. the public-facing class creator method)
2050
        result.push(...this.getTranspiledClassFunction(state, className));
48✔
2051

2052
        return result;
48✔
2053
    }
2054

2055
    getTypedef(state: BrsTranspileState) {
2056
        const result = [] as TranspileResult;
15✔
2057
        for (let annotation of this.annotations ?? []) {
15!
2058
            result.push(
×
2059
                ...annotation.getTypedef(state),
2060
                state.newline,
2061
                state.indent()
2062
            );
2063
        }
2064
        result.push(
15✔
2065
            'class ',
2066
            this.name.text
2067
        );
2068
        if (this.extendsKeyword && this.parentClassName) {
15✔
2069
            const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
4✔
2070
            const fqName = util.getFullyQualifiedClassName(
4✔
2071
                this.parentClassName.getName(ParseMode.BrighterScript),
2072
                namespace?.getName(ParseMode.BrighterScript)
12✔
2073
            );
2074
            result.push(
4✔
2075
                ` extends ${fqName}`
2076
            );
2077
        }
2078
        result.push(state.newline);
15✔
2079
        state.blockDepth++;
15✔
2080

2081
        let body = this.body;
15✔
2082
        //inject an empty "new" method if missing
2083
        if (!this.getConstructorFunction()) {
15✔
2084
            const constructor = createMethodStatement('new', TokenKind.Sub);
11✔
2085
            constructor.parent = this;
11✔
2086
            //walk the constructor to set up parent links
2087
            constructor.link();
11✔
2088
            body = [
11✔
2089
                constructor,
2090
                ...this.body
2091
            ];
2092
        }
2093

2094
        for (const member of body) {
15✔
2095
            if (isTypedefProvider(member)) {
33!
2096
                result.push(
33✔
2097
                    state.indent(),
2098
                    ...member.getTypedef(state),
2099
                    state.newline
2100
                );
2101
            }
2102
        }
2103
        state.blockDepth--;
15✔
2104
        result.push(
15✔
2105
            state.indent(),
2106
            'end class'
2107
        );
2108
        return result;
15✔
2109
    }
2110

2111
    /**
2112
     * Find the parent index for this class's parent.
2113
     * For class inheritance, every class is given an index.
2114
     * The base class is index 0, its child is index 1, and so on.
2115
     */
2116
    public getParentClassIndex(state: BrsTranspileState) {
2117
        let myIndex = 0;
109✔
2118
        let stmt = this as ClassStatement;
109✔
2119
        while (stmt) {
109✔
2120
            if (stmt.parentClassName) {
182✔
2121
                const namespace = stmt.findAncestor<NamespaceStatement>(isNamespaceStatement);
74✔
2122
                //find the parent class
2123
                stmt = state.file.getClassFileLink(
74✔
2124
                    stmt.parentClassName.getName(ParseMode.BrighterScript),
2125
                    namespace?.getName(ParseMode.BrighterScript)
222✔
2126
                )?.item;
74✔
2127
                myIndex++;
74✔
2128
            } else {
2129
                break;
108✔
2130
            }
2131
        }
2132
        const result = myIndex - 1;
109✔
2133
        if (result >= 0) {
109✔
2134
            return result;
55✔
2135
        } else {
2136
            return null;
54✔
2137
        }
2138
    }
2139

2140
    public hasParentClass() {
2141
        return !!this.parentClassName;
48✔
2142
    }
2143

2144
    /**
2145
     * Get all ancestor classes, in closest-to-furthest order (i.e. 0 is parent, 1 is grandparent, etc...).
2146
     * This will return an empty array if no ancestors were found
2147
     */
2148
    public getAncestors(state: BrsTranspileState) {
2149
        let ancestors = [] as ClassStatement[];
122✔
2150
        let stmt = this as ClassStatement;
122✔
2151
        while (stmt) {
122✔
2152
            if (stmt.parentClassName) {
210✔
2153
                const namespace = stmt.findAncestor<NamespaceStatement>(isNamespaceStatement);
88✔
2154
                stmt = state.file.getClassFileLink(
88✔
2155
                    stmt.parentClassName.getName(ParseMode.BrighterScript),
2156
                    namespace?.getName(ParseMode.BrighterScript)
264✔
2157
                )?.item;
88!
2158
                ancestors.push(stmt);
88✔
2159
            } else {
2160
                break;
122✔
2161
            }
2162
        }
2163
        return ancestors;
122✔
2164
    }
2165

2166
    private getBuilderName(transpiledClassName: string) {
2167
        return `__${transpiledClassName}_builder`;
120✔
2168
    }
2169

2170
    private getMethodIdentifier(transpiledClassName: string, statement: MethodStatement) {
2171
        return { ...statement.name, text: `__${transpiledClassName}_method_${statement.name.text}` };
120✔
2172
    }
2173

2174
    /**
2175
     * Get the constructor function for this class (if exists), or undefined if not exist
2176
     */
2177
    private getConstructorFunction() {
2178
        return this.body.find((stmt) => {
151✔
2179
            return (stmt as MethodStatement)?.name?.text?.toLowerCase() === 'new';
132!
2180
        }) as MethodStatement;
2181
    }
2182

2183
    /**
2184
     * Return the parameters for the first constructor function for this class
2185
     * @param ancestors The list of ancestors for this class
2186
     * @returns The parameters for the first constructor function for this class
2187
     */
2188
    private getConstructorParams(ancestors: ClassStatement[]) {
2189
        for (let ancestor of ancestors) {
41✔
2190
            const ctor = ancestor?.getConstructorFunction();
40!
2191
            if (ctor) {
40✔
2192
                return ctor.func.parameters;
16✔
2193
            }
2194
        }
2195
        return [];
25✔
2196
    }
2197

2198
    /**
2199
     * Determine if the specified field was declared in one of the ancestor classes
2200
     */
2201
    public isFieldDeclaredByAncestor(fieldName: string, ancestors: ClassStatement[]) {
2202
        let lowerFieldName = fieldName.toLowerCase();
×
2203
        for (let ancestor of ancestors) {
×
2204
            if (ancestor.memberMap[lowerFieldName]) {
×
2205
                return true;
×
2206
            }
2207
        }
2208
        return false;
×
2209
    }
2210

2211
    /**
2212
     * The builder is a function that assigns all of the methods and property names to a class instance.
2213
     * This needs to be a separate function so that child classes can call the builder from their parent
2214
     * without instantiating the parent constructor at that point in time.
2215
     */
2216
    private getTranspiledBuilder(state: BrsTranspileState, transpiledClassName: string, ancestors: ClassStatement[], body: Statement[]) {
2217
        let result = [] as TranspileResult;
48✔
2218
        result.push(`function ${this.getBuilderName(transpiledClassName)}()\n`);
48✔
2219
        state.blockDepth++;
48✔
2220
        //indent
2221
        result.push(state.indent());
48✔
2222

2223
        //construct parent class or empty object
2224
        if (ancestors[0]) {
48✔
2225
            const ancestorNamespace = ancestors[0].findAncestor<NamespaceStatement>(isNamespaceStatement);
24✔
2226
            let fullyQualifiedClassName = util.getFullyQualifiedClassName(
24✔
2227
                ancestors[0].getName(ParseMode.BrighterScript)!,
2228
                ancestorNamespace?.getName(ParseMode.BrighterScript)
72✔
2229
            );
2230
            result.push(`instance = ${this.getBuilderName(fullyQualifiedClassName.replace(/\./g, '_'))}()`);
24✔
2231
        } else {
2232
            //use an empty object.
2233
            result.push('instance = {}');
24✔
2234
        }
2235
        result.push(
48✔
2236
            state.newline,
2237
            state.indent()
2238
        );
2239
        let parentClassIndex = this.getParentClassIndex(state);
48✔
2240

2241
        for (let statement of body) {
48✔
2242
            //is field statement
2243
            if (isFieldStatement(statement)) {
71✔
2244
                //do nothing with class fields in this situation, they are handled elsewhere
2245
                continue;
11✔
2246

2247
                //methods
2248
            } else if (isMethodStatement(statement)) {
60!
2249

2250
                //store overridden parent methods as super{parentIndex}_{methodName}
2251
                if (
60✔
2252
                    //is override method
2253
                    statement.override ||
164✔
2254
                    //is constructor function in child class
2255
                    (statement.name.text.toLowerCase() === 'new' && ancestors[0])
2256
                ) {
2257
                    result.push(
28✔
2258
                        `instance.super${parentClassIndex}_${statement.name.text} = instance.${statement.name.text}`,
2259
                        state.newline,
2260
                        state.indent()
2261
                    );
2262
                }
2263

2264
                state.classStatement = this;
60✔
2265
                result.push(
60✔
2266
                    'instance.',
2267
                    state.transpileToken(statement.name),
2268
                    ' = ',
2269
                    state.transpileToken(this.getMethodIdentifier(transpiledClassName, statement)),
2270
                    state.newline,
2271
                    state.indent()
2272
                );
2273
                delete state.classStatement;
60✔
2274
            } else {
2275
                //other random statements (probably just comments)
2276
                result.push(
×
2277
                    ...statement.transpile(state),
2278
                    state.newline,
2279
                    state.indent()
2280
                );
2281
            }
2282
        }
2283
        //return the instance
2284
        result.push('return instance\n');
48✔
2285
        state.blockDepth--;
48✔
2286
        result.push(state.indent());
48✔
2287
        result.push(`end function`);
48✔
2288
        return result;
48✔
2289
    }
2290

2291
    /**
2292
     * Returns a copy of the class' body, with the constructor function added if it doesn't exist.
2293
     */
2294
    private getTranspiledClassBody(ancestors: ClassStatement[]): Statement[] {
2295
        const body = [];
48✔
2296
        body.push(...this.body);
48✔
2297

2298
        //inject an empty "new" method if missing
2299
        if (!this.getConstructorFunction()) {
48✔
2300
            if (ancestors.length === 0) {
26✔
2301
                body.unshift(createMethodStatement('new', TokenKind.Sub));
11✔
2302
            } else {
2303
                const params = this.getConstructorParams(ancestors);
15✔
2304
                const call = new ExpressionStatement(
15✔
2305
                    new CallExpression(
2306
                        new VariableExpression(createToken(TokenKind.Identifier, 'super')),
2307
                        createToken(TokenKind.LeftParen),
2308
                        createToken(TokenKind.RightParen),
2309
                        params.map(x => new VariableExpression(x.name))
6✔
2310
                    )
2311
                );
2312
                body.unshift(
15✔
2313
                    new MethodStatement(
2314
                        [],
2315
                        createIdentifier('new'),
2316
                        new FunctionExpression(
2317
                            params.map(x => x.clone()),
6✔
2318
                            new Block([call]),
2319
                            createToken(TokenKind.Sub),
2320
                            createToken(TokenKind.EndSub),
2321
                            createToken(TokenKind.LeftParen),
2322
                            createToken(TokenKind.RightParen)
2323
                        ),
2324
                        null
2325
                    )
2326
                );
2327
            }
2328
        }
2329

2330
        return body;
48✔
2331
    }
2332

2333
    /**
2334
     * These are the methods that are defined in this class. They are transpiled outside of the class body
2335
     * to ensure they don't appear as "$anon_#" in stack traces and crash logs.
2336
     */
2337
    private getTranspiledMethods(state: BrsTranspileState, transpiledClassName: string, body: Statement[]) {
2338
        let result = [] as TranspileResult;
48✔
2339
        for (let statement of body) {
48✔
2340
            if (isMethodStatement(statement)) {
71✔
2341
                state.classStatement = this;
60✔
2342
                result.push(
60✔
2343
                    ...statement.transpile(state, this.getMethodIdentifier(transpiledClassName, statement)),
2344
                    state.newline,
2345
                    state.indent()
2346
                );
2347
                delete state.classStatement;
60✔
2348
            }
2349
        }
2350
        return result;
48✔
2351
    }
2352

2353
    /**
2354
     * The class function is the function with the same name as the class. This is the function that
2355
     * consumers should call to create a new instance of that class.
2356
     * This invokes the builder, gets an instance of the class, then invokes the "new" function on that class.
2357
     */
2358
    private getTranspiledClassFunction(state: BrsTranspileState, transpiledClassName: string) {
2359
        let result = [] as TranspileResult;
48✔
2360

2361
        const constructorFunction = this.getConstructorFunction();
48✔
2362
        let constructorParams = [];
48✔
2363
        if (constructorFunction) {
48✔
2364
            constructorParams = constructorFunction.func.parameters;
22✔
2365
        } else {
2366
            constructorParams = this.getConstructorParams(this.getAncestors(state));
26✔
2367
        }
2368

2369
        result.push(
48✔
2370
            state.sourceNode(this.classKeyword, 'function'),
2371
            state.sourceNode(this.classKeyword, ' '),
2372
            state.sourceNode(this.name, this.getName(ParseMode.BrightScript)!),
2373
            `(`
2374
        );
2375
        let i = 0;
48✔
2376
        for (let param of constructorParams) {
48✔
2377
            if (i > 0) {
17✔
2378
                result.push(', ');
4✔
2379
            }
2380
            result.push(
17✔
2381
                param.transpile(state)
2382
            );
2383
            i++;
17✔
2384
        }
2385
        result.push(
48✔
2386
            ')',
2387
            '\n'
2388
        );
2389

2390
        state.blockDepth++;
48✔
2391
        result.push(state.indent());
48✔
2392
        result.push(`instance = ${this.getBuilderName(transpiledClassName)}()\n`);
48✔
2393

2394
        result.push(state.indent());
48✔
2395
        result.push(`instance.new(`);
48✔
2396

2397
        //append constructor arguments
2398
        i = 0;
48✔
2399
        for (let param of constructorParams) {
48✔
2400
            if (i > 0) {
17✔
2401
                result.push(', ');
4✔
2402
            }
2403
            result.push(
17✔
2404
                state.transpileToken(param.name)
2405
            );
2406
            i++;
17✔
2407
        }
2408
        result.push(
48✔
2409
            ')',
2410
            '\n'
2411
        );
2412

2413
        result.push(state.indent());
48✔
2414
        result.push(`return instance\n`);
48✔
2415

2416
        state.blockDepth--;
48✔
2417
        result.push(state.indent());
48✔
2418
        result.push(`end function`);
48✔
2419
        return result;
48✔
2420
    }
2421

2422
    walk(visitor: WalkVisitor, options: WalkOptions) {
2423
        //visitor-less walk function to do parent linking
2424
        walk(this, 'parentClassName', null, options);
979✔
2425

2426
        if (options.walkMode & InternalWalkMode.walkStatements) {
979!
2427
            walkArray(this.body, visitor, options, this);
979✔
2428
        }
2429
    }
2430

2431
    public clone() {
2432
        return this.finalizeClone(
11✔
2433
            new ClassStatement(
2434
                util.cloneToken(this.classKeyword),
2435
                util.cloneToken(this.name),
2436
                this.body?.map(x => x?.clone()),
10✔
2437
                util.cloneToken(this.end),
2438
                util.cloneToken(this.extendsKeyword),
2439
                this.parentClassName?.clone()
33✔
2440
            ),
2441
            ['body', 'parentClassName']
2442
        );
2443
    }
2444
}
2445

2446
const accessModifiers = [
1✔
2447
    TokenKind.Public,
2448
    TokenKind.Protected,
2449
    TokenKind.Private
2450
];
2451
export class MethodStatement extends FunctionStatement {
1✔
2452
    constructor(
2453
        modifiers: Token | Token[],
2454
        name: Identifier,
2455
        func: FunctionExpression,
2456
        public override: Token
308✔
2457
    ) {
2458
        super(name, func);
308✔
2459
        if (modifiers) {
308✔
2460
            if (Array.isArray(modifiers)) {
50✔
2461
                this.modifiers.push(...modifiers);
19✔
2462
            } else {
2463
                this.modifiers.push(modifiers);
31✔
2464
            }
2465
        }
2466
        this.range = util.createBoundingRange(
308✔
2467
            ...(this.modifiers),
2468
            override,
2469
            func
2470
        );
2471
    }
2472

2473
    public modifiers: Token[] = [];
308✔
2474

2475
    public get accessModifier() {
2476
        return this.modifiers.find(x => accessModifiers.includes(x.kind));
102✔
2477
    }
2478

2479
    public readonly range: Range | undefined;
2480

2481
    /**
2482
     * Get the name of this method.
2483
     */
2484
    public getName(parseMode: ParseMode) {
2485
        return this.name.text;
1✔
2486
    }
2487

2488
    transpile(state: BrsTranspileState, name?: Identifier) {
2489
        if (this.name.text.toLowerCase() === 'new') {
60✔
2490
            this.ensureSuperConstructorCall(state);
48✔
2491
            //TODO we need to undo this at the bottom of this method
2492
            this.injectFieldInitializersForConstructor(state);
48✔
2493
        }
2494
        //TODO - remove type information from these methods because that doesn't work
2495
        //convert the `super` calls into the proper methods
2496
        const parentClassIndex = state.classStatement.getParentClassIndex(state);
60✔
2497
        const visitor = createVisitor({
60✔
2498
            VariableExpression: e => {
2499
                if (e.name.text.toLocaleLowerCase() === 'super') {
67✔
2500
                    state.editor.setProperty(e.name, 'text', `m.super${parentClassIndex}_new`);
24✔
2501
                }
2502
            },
2503
            DottedGetExpression: e => {
2504
                const beginningVariable = util.findBeginningVariableExpression(e);
25✔
2505
                const lowerName = beginningVariable?.getName(ParseMode.BrighterScript).toLowerCase();
25!
2506
                if (lowerName === 'super') {
25✔
2507
                    state.editor.setProperty(beginningVariable.name, 'text', 'm');
7✔
2508
                    state.editor.setProperty(e.name, 'text', `super${parentClassIndex}_${e.name.text}`);
7✔
2509
                }
2510
            }
2511
        });
2512
        const walkOptions: WalkOptions = { walkMode: WalkMode.visitExpressions };
60✔
2513
        for (const statement of this.func.body.statements) {
60✔
2514
            visitor(statement, undefined);
64✔
2515
            statement.walk(visitor, walkOptions);
64✔
2516
        }
2517
        return this.func.transpile(state, name);
60✔
2518
    }
2519

2520
    getTypedef(state: BrsTranspileState) {
2521
        const result = [] as TranspileResult;
23✔
2522
        for (let annotation of this.annotations ?? []) {
23✔
2523
            result.push(
2✔
2524
                ...annotation.getTypedef(state),
2525
                state.newline,
2526
                state.indent()
2527
            );
2528
        }
2529
        if (this.accessModifier) {
23✔
2530
            result.push(
8✔
2531
                this.accessModifier.text,
2532
                ' '
2533
            );
2534
        }
2535
        if (this.override) {
23✔
2536
            result.push('override ');
1✔
2537
        }
2538
        result.push(
23✔
2539
            ...this.func.getTypedef(state)
2540
        );
2541
        return result;
23✔
2542
    }
2543

2544
    /**
2545
     * All child classes must call the parent constructor. The type checker will warn users when they don't call it in their own class,
2546
     * but we still need to call it even if they have omitted it. This injects the super call if it's missing
2547
     */
2548
    private ensureSuperConstructorCall(state: BrsTranspileState) {
2549
        //if this class doesn't extend another class, quit here
2550
        if (state.classStatement!.getAncestors(state).length === 0) {
48✔
2551
            return;
24✔
2552
        }
2553

2554
        //check whether any calls to super exist
2555
        let containsSuperCall =
2556
            this.func.body.statements.findIndex((x) => {
24✔
2557
                //is a call statement
2558
                return isExpressionStatement(x) && isCallExpression(x.expression) &&
25✔
2559
                    //is a call to super
2560
                    util.findBeginningVariableExpression(x.expression.callee as any)?.name.text.toLowerCase() === 'super';
69!
2561
            }) !== -1;
2562

2563
        //if a call to super exists, quit here
2564
        if (containsSuperCall) {
24✔
2565
            return;
23✔
2566
        }
2567

2568
        //this is a child class, and the constructor doesn't contain a call to super. Inject one
2569
        const superCall = new ExpressionStatement(
1✔
2570
            new CallExpression(
2571
                new VariableExpression(
2572
                    {
2573
                        kind: TokenKind.Identifier,
2574
                        text: 'super',
2575
                        isReserved: false,
2576
                        range: state.classStatement!.name.range,
2577
                        leadingWhitespace: ''
2578
                    }
2579
                ),
2580
                {
2581
                    kind: TokenKind.LeftParen,
2582
                    text: '(',
2583
                    isReserved: false,
2584
                    range: state.classStatement!.name.range,
2585
                    leadingWhitespace: ''
2586
                },
2587
                {
2588
                    kind: TokenKind.RightParen,
2589
                    text: ')',
2590
                    isReserved: false,
2591
                    range: state.classStatement!.name.range,
2592
                    leadingWhitespace: ''
2593
                },
2594
                []
2595
            )
2596
        );
2597
        state.editor.arrayUnshift(this.func.body.statements, superCall);
1✔
2598
    }
2599

2600
    /**
2601
     * Inject field initializers at the top of the `new` function (after any present `super()` call)
2602
     */
2603
    private injectFieldInitializersForConstructor(state: BrsTranspileState) {
2604
        let startingIndex = state.classStatement!.hasParentClass() ? 1 : 0;
48✔
2605

2606
        let newStatements = [] as Statement[];
48✔
2607
        //insert the field initializers in order
2608
        for (let field of state.classStatement!.fields) {
48✔
2609
            let thisQualifiedName = { ...field.name };
11✔
2610
            thisQualifiedName.text = 'm.' + field.name?.text;
11!
2611
            if (field.initialValue) {
11✔
2612
                newStatements.push(
9✔
2613
                    new AssignmentStatement(field.equal, thisQualifiedName, field.initialValue)
2614
                );
2615
            } else {
2616
                //if there is no initial value, set the initial value to `invalid`
2617
                newStatements.push(
2✔
2618
                    new AssignmentStatement(
2619
                        createToken(TokenKind.Equal, '=', field.name?.range),
6!
2620
                        thisQualifiedName,
2621
                        createInvalidLiteral('invalid', field.name?.range)
6!
2622
                    )
2623
                );
2624
            }
2625
        }
2626
        state.editor.arraySplice(this.func.body.statements, startingIndex, 0, ...newStatements);
48✔
2627
    }
2628

2629
    walk(visitor: WalkVisitor, options: WalkOptions) {
2630
        if (options.walkMode & InternalWalkMode.walkExpressions) {
795✔
2631
            walk(this, 'func', visitor, options);
590✔
2632
        }
2633
    }
2634

2635
    public clone() {
2636
        return this.finalizeClone(
5✔
2637
            new MethodStatement(
2638
                this.modifiers?.map(m => util.cloneToken(m)),
1✔
2639
                util.cloneToken(this.name),
2640
                this.func?.clone(),
15✔
2641
                util.cloneToken(this.override)
2642
            ),
2643
            ['func']
2644
        );
2645
    }
2646
}
2647
/**
2648
 * @deprecated use `MethodStatement`
2649
 */
2650
export class ClassMethodStatement extends MethodStatement { }
1✔
2651

2652
export class FieldStatement extends Statement implements TypedefProvider {
1✔
2653

2654
    constructor(
2655
        readonly accessModifier?: Token,
187✔
2656
        readonly name?: Identifier,
187✔
2657
        readonly as?: Token,
187✔
2658
        readonly type?: Token,
187✔
2659
        readonly equal?: Token,
187✔
2660
        readonly initialValue?: Expression,
187✔
2661
        readonly optional?: Token
187✔
2662
    ) {
2663
        super();
187✔
2664
        this.range = util.createBoundingRange(
187✔
2665
            accessModifier,
2666
            name,
2667
            as,
2668
            type,
2669
            equal,
2670
            initialValue
2671
        );
2672
    }
2673

2674
    /**
2675
     * Derive a ValueKind from the type token, or the initial value.
2676
     * Defaults to `DynamicType`
2677
     */
2678
    getType() {
2679
        if (this.type) {
77✔
2680
            return util.tokenToBscType(this.type);
38✔
2681
        } else if (isLiteralExpression(this.initialValue)) {
39✔
2682
            return this.initialValue.type;
25✔
2683
        } else {
2684
            return new DynamicType();
14✔
2685
        }
2686
    }
2687

2688
    public readonly range: Range | undefined;
2689

2690
    public get isOptional() {
2691
        return !!this.optional;
17✔
2692
    }
2693

2694
    transpile(state: BrsTranspileState): TranspileResult {
2695
        throw new Error('transpile not implemented for ' + Object.getPrototypeOf(this).constructor.name);
×
2696
    }
2697

2698
    getTypedef(state: BrsTranspileState) {
2699
        const result = [] as TranspileResult;
10✔
2700
        if (this.name) {
10!
2701
            for (let annotation of this.annotations ?? []) {
10✔
2702
                result.push(
2✔
2703
                    ...annotation.getTypedef(state),
2704
                    state.newline,
2705
                    state.indent()
2706
                );
2707
            }
2708

2709
            let type = this.getType();
10✔
2710
            if (isInvalidType(type) || isVoidType(type)) {
10✔
2711
                type = new DynamicType();
1✔
2712
            }
2713

2714
            result.push(
10✔
2715
                this.accessModifier?.text ?? 'public',
60!
2716
                ' '
2717
            );
2718
            if (this.isOptional) {
10!
2719
                result.push(this.optional!.text, ' ');
×
2720
            }
2721
            result.push(this.name?.text,
10!
2722
                ' as ',
2723
                type.toTypeString()
2724
            );
2725
        }
2726
        return result;
10✔
2727
    }
2728

2729
    walk(visitor: WalkVisitor, options: WalkOptions) {
2730
        if (this.initialValue && options.walkMode & InternalWalkMode.walkExpressions) {
258✔
2731
            walk(this, 'initialValue', visitor, options);
71✔
2732
        }
2733
    }
2734

2735
    public clone() {
2736
        return this.finalizeClone(
4✔
2737
            new FieldStatement(
2738
                util.cloneToken(this.accessModifier),
2739
                util.cloneToken(this.name),
2740
                util.cloneToken(this.as),
2741
                util.cloneToken(this.type),
2742
                util.cloneToken(this.equal),
2743
                this.initialValue?.clone(),
12✔
2744
                util.cloneToken(this.optional)
2745
            ),
2746
            ['initialValue']
2747
        );
2748
    }
2749
}
2750

2751
/**
2752
 * @deprecated use `FieldStatement`
2753
 */
2754
export class ClassFieldStatement extends FieldStatement { }
1✔
2755

2756
export type MemberStatement = FieldStatement | MethodStatement;
2757

2758
/**
2759
 * @deprecated use `MemeberStatement`
2760
 */
2761
export type ClassMemberStatement = MemberStatement;
2762

2763
export class TryCatchStatement extends Statement {
1✔
2764
    constructor(
2765
        public tokens: {
31✔
2766
            try: Token;
2767
            endTry?: Token;
2768
        },
2769
        public tryBranch?: Block,
31✔
2770
        public catchStatement?: CatchStatement
31✔
2771
    ) {
2772
        super();
31✔
2773
        this.range = util.createBoundingRange(
31✔
2774
            tokens.try,
2775
            tryBranch,
2776
            catchStatement,
2777
            tokens.endTry
2778
        );
2779
    }
2780

2781
    public readonly range: Range | undefined;
2782

2783
    public transpile(state: BrsTranspileState): TranspileResult {
2784
        return [
3✔
2785
            state.transpileToken(this.tokens.try),
2786
            ...this.tryBranch!.transpile(state),
2787
            state.newline,
2788
            state.indent(),
2789
            ...(this.catchStatement?.transpile(state) ?? ['catch']),
18!
2790
            state.newline,
2791
            state.indent(),
2792
            state.transpileToken(this.tokens.endTry!)
2793
        ] as TranspileResult;
2794
    }
2795

2796
    public walk(visitor: WalkVisitor, options: WalkOptions) {
2797
        if (this.tryBranch && options.walkMode & InternalWalkMode.walkStatements) {
43✔
2798
            walk(this, 'tryBranch', visitor, options);
42✔
2799
            walk(this, 'catchStatement', visitor, options);
42✔
2800
        }
2801
    }
2802

2803
    public clone() {
2804
        return this.finalizeClone(
3✔
2805
            new TryCatchStatement(
2806
                {
2807
                    try: util.cloneToken(this.tokens.try),
2808
                    endTry: util.cloneToken(this.tokens.endTry)
2809
                },
2810
                this.tryBranch?.clone(),
9✔
2811
                this.catchStatement?.clone()
9✔
2812
            ),
2813
            ['tryBranch', 'catchStatement']
2814
        );
2815
    }
2816
}
2817

2818
export class CatchStatement extends Statement {
1✔
2819
    constructor(
2820
        public tokens: {
28✔
2821
            catch: Token;
2822
        },
2823
        public exceptionVariable?: Identifier,
28✔
2824
        public catchBranch?: Block
28✔
2825
    ) {
2826
        super();
28✔
2827
        this.range = util.createBoundingRange(
28✔
2828
            tokens.catch,
2829
            exceptionVariable,
2830
            catchBranch
2831
        );
2832
    }
2833

2834
    public range: Range | undefined;
2835

2836
    public transpile(state: BrsTranspileState): TranspileResult {
2837
        return [
3✔
2838
            state.transpileToken(this.tokens.catch),
2839
            ' ',
2840
            this.exceptionVariable?.text ?? 'e',
18!
2841
            ...(this.catchBranch?.transpile(state) ?? [])
18!
2842
        ];
2843
    }
2844

2845
    public walk(visitor: WalkVisitor, options: WalkOptions) {
2846
        if (this.catchBranch && options.walkMode & InternalWalkMode.walkStatements) {
39✔
2847
            walk(this, 'catchBranch', visitor, options);
38✔
2848
        }
2849
    }
2850

2851
    public clone() {
2852
        return this.finalizeClone(
2✔
2853
            new CatchStatement(
2854
                {
2855
                    catch: util.cloneToken(this.tokens.catch)
2856
                },
2857
                util.cloneToken(this.exceptionVariable),
2858
                this.catchBranch?.clone()
6✔
2859
            ),
2860
            ['catchBranch']
2861
        );
2862
    }
2863
}
2864

2865
export class ThrowStatement extends Statement {
1✔
2866
    constructor(
2867
        public throwToken: Token,
12✔
2868
        public expression?: Expression
12✔
2869
    ) {
2870
        super();
12✔
2871
        this.range = util.createBoundingRange(
12✔
2872
            throwToken,
2873
            expression
2874
        );
2875
    }
2876
    public range: Range | undefined;
2877

2878
    public transpile(state: BrsTranspileState) {
2879
        const result = [
4✔
2880
            state.transpileToken(this.throwToken),
2881
            ' '
2882
        ] as TranspileResult;
2883

2884
        //if we have an expression, transpile it
2885
        if (this.expression) {
4!
2886
            result.push(
4✔
2887
                ...this.expression.transpile(state)
2888
            );
2889

2890
            //no expression found. Rather than emit syntax errors, provide a generic error message
2891
        } else {
2892
            result.push('"An error has occurred"');
×
2893
        }
2894
        return result;
4✔
2895
    }
2896

2897
    public walk(visitor: WalkVisitor, options: WalkOptions) {
2898
        if (this.expression && options.walkMode & InternalWalkMode.walkExpressions) {
23✔
2899
            walk(this, 'expression', visitor, options);
16✔
2900
        }
2901
    }
2902

2903
    public clone() {
2904
        return this.finalizeClone(
2✔
2905
            new ThrowStatement(
2906
                util.cloneToken(this.throwToken),
2907
                this.expression?.clone()
6✔
2908
            ),
2909
            ['expression']
2910
        );
2911
    }
2912
}
2913

2914

2915
export class EnumStatement extends Statement implements TypedefProvider {
1✔
2916

2917
    constructor(
2918
        public tokens: {
125✔
2919
            enum: Token;
2920
            name: Identifier;
2921
            endEnum: Token;
2922
        },
2923
        public body: Array<EnumMemberStatement | CommentStatement>
125✔
2924
    ) {
2925
        super();
125✔
2926
        this.body = this.body ?? [];
125✔
2927
    }
2928

2929
    public get range(): Range | undefined {
2930
        return util.createBoundingRange(
15✔
2931
            this.tokens.enum,
2932
            this.tokens.name,
2933
            ...this.body,
2934
            this.tokens.endEnum
2935
        );
2936
    }
2937

2938
    /**
2939
     * Get the name of the wrapping namespace (if it exists)
2940
     * @deprecated use `.findAncestor(isNamespaceStatement)` instead.
2941
     */
2942
    public get namespaceName() {
2943
        return this.findAncestor<NamespaceStatement>(isNamespaceStatement)?.nameExpression;
×
2944
    }
2945

2946
    public getMembers() {
2947
        const result = [] as EnumMemberStatement[];
152✔
2948
        for (const statement of this.body) {
152✔
2949
            if (isEnumMemberStatement(statement)) {
327✔
2950
                result.push(statement);
314✔
2951
            }
2952
        }
2953
        return result;
152✔
2954
    }
2955

2956
    /**
2957
     * Get a map of member names and their values.
2958
     * All values are stored as their AST LiteralExpression representation (i.e. string enum values include the wrapping quotes)
2959
     */
2960
    public getMemberValueMap() {
2961
        const result = new Map<string, string>();
62✔
2962
        const members = this.getMembers();
62✔
2963
        let currentIntValue = 0;
62✔
2964
        for (const member of members) {
62✔
2965
            //if there is no value, assume an integer and increment the int counter
2966
            if (!member.value) {
155✔
2967
                result.set(member.name?.toLowerCase(), currentIntValue.toString());
32!
2968
                currentIntValue++;
32✔
2969

2970
                //if explicit integer value, use it and increment the int counter
2971
            } else if (isLiteralExpression(member.value) && member.value.token.kind === TokenKind.IntegerLiteral) {
123✔
2972
                //try parsing as integer literal, then as hex integer literal.
2973
                let tokenIntValue = util.parseInt(member.value.token.text) ?? util.parseInt(member.value.token.text.replace(/&h/i, '0x'));
31✔
2974
                if (tokenIntValue !== undefined) {
31!
2975
                    currentIntValue = tokenIntValue;
31✔
2976
                    currentIntValue++;
31✔
2977
                }
2978
                result.set(member.name?.toLowerCase(), member.value.token.text);
31!
2979

2980
                //simple unary expressions (like `-1`)
2981
            } else if (isUnaryExpression(member.value) && isLiteralExpression(member.value.right)) {
92✔
2982
                result.set(member.name?.toLowerCase(), member.value.operator.text + member.value.right.token.text);
1!
2983

2984
                //all other values
2985
            } else {
2986
                result.set(member.name?.toLowerCase(), (member.value as LiteralExpression)?.token?.text ?? 'invalid');
91!
2987
            }
2988
        }
2989
        return result;
62✔
2990
    }
2991

2992
    public getMemberValue(name: string) {
2993
        return this.getMemberValueMap().get(name.toLowerCase());
59✔
2994
    }
2995

2996
    /**
2997
     * The name of the enum (without the namespace prefix)
2998
     */
2999
    public get name() {
3000
        return this.tokens.name?.text;
16!
3001
    }
3002

3003
    /**
3004
     * The name of the enum WITH its leading namespace (if applicable)
3005
     */
3006
    public get fullName() {
3007
        const name = this.tokens.name?.text;
349!
3008
        if (name) {
349!
3009
            const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
349✔
3010

3011
            if (namespace) {
349✔
3012
                let namespaceName = namespace.getName(ParseMode.BrighterScript);
163✔
3013
                return `${namespaceName}.${name}`;
163✔
3014
            } else {
3015
                return name;
186✔
3016
            }
3017
        } else {
3018
            //return undefined which will allow outside callers to know that this doesn't have a name
3019
            return undefined;
×
3020
        }
3021
    }
3022

3023
    transpile(state: BrsTranspileState) {
3024
        //enum declarations do not exist at runtime, so don't transpile anything...
3025
        return [];
24✔
3026
    }
3027

3028
    getTypedef(state: BrsTranspileState) {
3029
        const result = [] as TranspileResult;
1✔
3030
        for (let annotation of this.annotations ?? []) {
1!
3031
            result.push(
×
3032
                ...annotation.getTypedef(state),
3033
                state.newline,
3034
                state.indent()
3035
            );
3036
        }
3037
        result.push(
1✔
3038
            this.tokens.enum.text ?? 'enum',
3!
3039
            ' ',
3040
            this.tokens.name.text
3041
        );
3042
        result.push(state.newline);
1✔
3043
        state.blockDepth++;
1✔
3044
        for (const member of this.body) {
1✔
3045
            if (isTypedefProvider(member)) {
1!
3046
                result.push(
1✔
3047
                    state.indent(),
3048
                    ...member.getTypedef(state),
3049
                    state.newline
3050
                );
3051
            }
3052
        }
3053
        state.blockDepth--;
1✔
3054
        result.push(
1✔
3055
            state.indent(),
3056
            this.tokens.endEnum.text ?? 'end enum'
3!
3057
        );
3058
        return result;
1✔
3059
    }
3060

3061
    walk(visitor: WalkVisitor, options: WalkOptions) {
3062
        if (options.walkMode & InternalWalkMode.walkStatements) {
382!
3063
            walkArray(this.body, visitor, options, this);
382✔
3064

3065
        }
3066
    }
3067

3068
    public clone() {
3069
        return this.finalizeClone(
6✔
3070
            new EnumStatement(
3071
                {
3072
                    enum: util.cloneToken(this.tokens.enum),
3073
                    name: util.cloneToken(this.tokens.name),
3074
                    endEnum: util.cloneToken(this.tokens.endEnum)
3075
                },
3076
                this.body?.map(x => x?.clone())
6✔
3077
            ),
3078
            ['body']
3079
        );
3080
    }
3081
}
3082

3083
export class EnumMemberStatement extends Statement implements TypedefProvider {
1✔
3084

3085
    public constructor(
3086
        public tokens: {
210✔
3087
            name: Identifier;
3088
            equal?: Token;
3089
        },
3090
        public value?: Expression
210✔
3091
    ) {
3092
        super();
210✔
3093
    }
3094

3095
    /**
3096
     * The name of the member
3097
     */
3098
    public get name() {
3099
        return this.tokens.name.text;
610✔
3100
    }
3101

3102
    public get range() {
3103
        return util.createBoundingRange(
66✔
3104
            this.tokens.name,
3105
            this.tokens.equal,
3106
            this.value
3107
        );
3108
    }
3109

3110
    /**
3111
     * Get the value of this enum. Requires that `.parent` is set
3112
     */
3113
    public getValue() {
3114
        return (this.parent as EnumStatement).getMemberValue(this.name);
59✔
3115
    }
3116

3117
    public transpile(state: BrsTranspileState): TranspileResult {
3118
        return [];
×
3119
    }
3120

3121
    getTypedef(state: BrsTranspileState): TranspileResult {
3122
        const result = [
1✔
3123
            this.tokens.name.text
3124
        ] as TranspileResult;
3125
        if (this.tokens.equal) {
1!
3126
            result.push(' ', this.tokens.equal.text, ' ');
×
3127
            if (this.value) {
×
3128
                result.push(
×
3129
                    ...this.value.transpile(state)
3130
                );
3131
            }
3132
        }
3133
        return result;
1✔
3134
    }
3135

3136
    walk(visitor: WalkVisitor, options: WalkOptions) {
3137
        if (this.value && options.walkMode & InternalWalkMode.walkExpressions) {
512✔
3138
            walk(this, 'value', visitor, options);
327✔
3139
        }
3140
    }
3141

3142
    public clone() {
3143
        return this.finalizeClone(
3✔
3144
            new EnumMemberStatement(
3145
                {
3146
                    name: util.cloneToken(this.tokens.name),
3147
                    equal: util.cloneToken(this.tokens.equal)
3148
                },
3149
                this.value?.clone()
9✔
3150
            ),
3151
            ['value']
3152
        );
3153
    }
3154
}
3155

3156
export class ConstStatement extends Statement implements TypedefProvider {
1✔
3157

3158
    public constructor(
3159
        public tokens: {
85✔
3160
            const: Token;
3161
            name: Identifier;
3162
            equals: Token;
3163
        },
3164
        public value: Expression
85✔
3165
    ) {
3166
        super();
85✔
3167
        this.range = util.createBoundingRange(this.tokens.const, this.tokens.name, this.tokens.equals, this.value);
85✔
3168
    }
3169

3170
    public range: Range | undefined;
3171

3172
    public get name() {
3173
        return this.tokens.name.text;
5✔
3174
    }
3175

3176
    /**
3177
     * The name of the statement WITH its leading namespace (if applicable)
3178
     */
3179
    public get fullName() {
3180
        const name = this.tokens.name?.text;
131!
3181
        if (name) {
131!
3182
            const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
131✔
3183
            if (namespace) {
131✔
3184
                let namespaceName = namespace.getName(ParseMode.BrighterScript);
98✔
3185
                return `${namespaceName}.${name}`;
98✔
3186
            } else {
3187
                return name;
33✔
3188
            }
3189
        } else {
3190
            //return undefined which will allow outside callers to know that this doesn't have a name
3191
            return undefined;
×
3192
        }
3193
    }
3194

3195
    public transpile(state: BrsTranspileState): TranspileResult {
3196
        //const declarations don't exist at runtime, so just transpile empty
3197
        return [];
35✔
3198
    }
3199

3200
    getTypedef(state: BrsTranspileState): TranspileResult {
3201
        return [
3✔
3202
            state.tokenToSourceNode(this.tokens.const),
3203
            ' ',
3204
            state.tokenToSourceNode(this.tokens.name),
3205
            ' ',
3206
            state.tokenToSourceNode(this.tokens.equals),
3207
            ' ',
3208
            ...this.value.transpile(state)
3209
        ];
3210
    }
3211

3212
    walk(visitor: WalkVisitor, options: WalkOptions) {
3213
        if (this.value && options.walkMode & InternalWalkMode.walkExpressions) {
200✔
3214
            walk(this, 'value', visitor, options);
198✔
3215
        }
3216
    }
3217

3218
    public clone() {
3219
        return this.finalizeClone(
3✔
3220
            new ConstStatement(
3221
                {
3222
                    const: util.cloneToken(this.tokens.const),
3223
                    name: util.cloneToken(this.tokens.name),
3224
                    equals: util.cloneToken(this.tokens.equals)
3225
                },
3226
                this.value?.clone()
9✔
3227
            ),
3228
            ['value']
3229
        );
3230
    }
3231
}
3232

3233
export class ContinueStatement extends Statement {
1✔
3234
    constructor(
3235
        public tokens: {
13✔
3236
            continue: Token;
3237
            loopType: Token;
3238
        }
3239
    ) {
3240
        super();
13✔
3241
        this.range = util.createBoundingRange(
13✔
3242
            tokens.continue,
3243
            tokens.loopType
3244
        );
3245
    }
3246

3247
    public range: Range | undefined;
3248

3249
    transpile(state: BrsTranspileState) {
3250
        return [
3✔
3251
            state.sourceNode(this.tokens.continue, this.tokens.continue?.text ?? 'continue'),
18!
3252
            this.tokens.loopType?.leadingWhitespace ?? ' ',
18✔
3253
            state.sourceNode(this.tokens.continue, this.tokens.loopType?.text)
9✔
3254
        ];
3255
    }
3256

3257
    walk(visitor: WalkVisitor, options: WalkOptions) {
3258
        //nothing to walk
3259
    }
3260

3261
    public clone() {
3262
        return this.finalizeClone(
1✔
3263
            new ContinueStatement({
3264
                continue: util.cloneToken(this.tokens.continue),
3265
                loopType: util.cloneToken(this.tokens.loopType)
3266
            })
3267
        );
3268
    }
3269
}
3270

3271
export class TypecastStatement extends Statement {
1✔
3272
    constructor(options: {
3273
        typecast?: Token;
3274
        obj: Token;
3275
        as?: Token;
3276
        type: Token;
3277
    }
3278
    ) {
3279
        super();
5✔
3280
        this.tokens = {
5✔
3281
            typecast: options.typecast,
3282
            obj: options.obj,
3283
            as: options.as,
3284
            type: options.type
3285
        };
3286
        this.range = util.createBoundingRange(
5✔
3287
            this.tokens.typecast,
3288
            this.tokens.obj,
3289
            this.tokens.as,
3290
            this.tokens.type
3291
        );
3292
    }
3293

3294
    public readonly tokens: {
3295
        readonly typecast?: Token;
3296
        readonly obj: Token;
3297
        readonly as?: Token;
3298
        readonly type: Token;
3299
    };
3300

3301
    public readonly typecastExpression: Expression;
3302

3303
    public readonly range: Range;
3304

3305
    transpile(state: BrsTranspileState) {
3306
        return [];
1✔
3307
    }
3308

3309
    walk(visitor: WalkVisitor, options: WalkOptions) {
3310
        //nothing to walk
3311
    }
3312

3313
    public clone() {
3314
        return this.finalizeClone(
×
3315
            new TypecastStatement({
3316
                typecast: util.cloneToken(this.tokens.typecast),
3317
                obj: util.cloneToken(this.tokens.obj),
3318
                as: util.cloneToken(this.tokens.as),
3319
                type: util.cloneToken(this.tokens.type)
3320
            })
3321
        );
3322
    }
3323
}
3324

3325
export class AliasStatement extends Statement {
1✔
3326
    constructor(options: {
3327
        alias?: Token;
3328
        name: Token;
3329
        equals?: Token;
3330
        value: Token;
3331
    }
3332
    ) {
3333
        super();
2✔
3334
        this.tokens = {
2✔
3335
            alias: options.alias,
3336
            name: options.name,
3337
            equals: options.equals,
3338
            value: options.value
3339
        };
3340
        this.range = util.createBoundingRange(
2✔
3341
            this.tokens.alias,
3342
            this.tokens.name,
3343
            this.tokens.equals,
3344
            this.tokens.value
3345
        );
3346
    }
3347

3348
    public readonly tokens: {
3349
        readonly alias?: Token;
3350
        readonly name: Token;
3351
        readonly equals?: Token;
3352
        readonly value: Token;
3353
    };
3354

3355
    public readonly range: Range;
3356

3357
    transpile(state: BrsTranspileState) {
3358
        return [];
×
3359
    }
3360

3361
    walk(visitor: WalkVisitor, options: WalkOptions) {
3362
        //nothing to walk
3363
    }
3364

3365

3366
    public clone() {
3367
        return this.finalizeClone(
×
3368
            new AliasStatement({
3369
                alias: util.cloneToken(this.tokens.alias),
3370
                name: util.cloneToken(this.tokens.name),
3371
                equals: util.cloneToken(this.tokens.equals),
3372
                value: util.cloneToken(this.tokens.value)
3373
            })
3374
        );
3375
    }
3376
}
3377

3378
export class TypeStatement extends Statement {
1✔
3379
    constructor(options: {
3380
        type?: Token;
3381
        name: Token;
3382
        equals?: Token;
3383
        value: Token;
3384
    }
3385
    ) {
3386
        super();
10✔
3387
        this.tokens = {
10✔
3388
            type: options.type,
3389
            name: options.name,
3390
            equals: options.equals,
3391
            value: options.value
3392
        };
3393
        this.range = util.createBoundingRange(
10✔
3394
            this.tokens.type,
3395
            this.tokens.name,
3396
            this.tokens.equals,
3397
            this.tokens.value
3398
        );
3399
    }
3400

3401
    public readonly tokens: {
3402
        readonly type?: Token;
3403
        readonly name: Token;
3404
        readonly equals?: Token;
3405
        readonly value: Token;
3406
    };
3407

3408
    public readonly range: Range;
3409

3410
    transpile(state: BrsTranspileState) {
3411
        return [];
2✔
3412
    }
3413

3414
    walk(visitor: WalkVisitor, options: WalkOptions) {
3415
        //nothing to walk
3416
    }
3417

3418
    public get fullName() {
3419
        const name = this.tokens.name?.text;
5!
3420
        if (name) {
5!
3421
            const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
5✔
3422
            if (namespace) {
5✔
3423
                let namespaceName = namespace.getName(ParseMode.BrighterScript);
3✔
3424
                return `${namespaceName}.${name}`;
3✔
3425
            } else {
3426
                return name;
2✔
3427
            }
3428
        } else {
3429
            //return undefined which will allow outside callers to know that this doesn't have a name
NEW
3430
            return undefined;
×
3431
        }
3432
    }
3433

3434
    public clone() {
NEW
3435
        return this.finalizeClone(
×
3436
            new TypeStatement({
3437
                type: util.cloneToken(this.tokens.type),
3438
                name: util.cloneToken(this.tokens.name),
3439
                equals: util.cloneToken(this.tokens.equals),
3440
                value: util.cloneToken(this.tokens.value)
3441
            })
3442
        );
3443
    }
3444
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc