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

rokucommunity / brighterscript / #14030

17 Mar 2025 03:35PM UTC coverage: 89.102% (-0.02%) from 89.12%
#14030

push

web-flow
Adds Alias statement syntax from v1 to v0 (#1430)

7498 of 8869 branches covered (84.54%)

Branch coverage included in aggregate %.

21 of 24 new or added lines in 5 files covered. (87.5%)

1 existing line in 1 file now uncovered.

9844 of 10594 relevant lines covered (92.92%)

1835.67 hits per line

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

91.9
/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,619✔
55
    ) {
56
        super();
4,619✔
57
    }
58

59
    public symbolTable = new SymbolTable('Body', () => this.parent?.getSymbolTable());
4,619✔
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;
297✔
70
        for (let i = 0; i < this.statements.length; i++) {
297✔
71
            let statement = this.statements[i];
422✔
72
            let previousStatement = this.statements[i - 1];
422✔
73
            let nextStatement = this.statements[i + 1];
422✔
74

75
            if (!previousStatement) {
422✔
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) {
127✔
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)) {
123✔
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))) {
122✔
90
                result.push(state.newline, state.newline);
64✔
91
            } else {
92
                //separate statements by a single newline
93
                result.push(state.newline);
58✔
94
            }
95

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

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

116
    walk(visitor: WalkVisitor, options: WalkOptions) {
117
        if (options.walkMode & InternalWalkMode.walkStatements) {
4,083!
118
            walkArray(this.statements, visitor, options, this);
4,083✔
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,
977✔
135
        readonly name: Identifier,
977✔
136
        readonly value: Expression
977✔
137
    ) {
138
        super();
977✔
139
        this.range = util.createBoundingRange(name, equals, value);
977✔
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);
446✔
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)) {
265!
155
            return this.value.transpile(state);
21✔
156
        } else {
157
            return [
244✔
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,220✔
169
            walk(this, 'value', visitor, options);
1,771✔
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,345✔
188
        readonly startingRange?: Range
2,345✔
189
    ) {
190
        super();
2,345✔
191
        this.range = util.createBoundingRange(
2,345✔
192
            { range: this.startingRange },
193
            ...(statements ?? [])
7,035✔
194
        );
195
    }
196

197
    public readonly range: Range | undefined;
198

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

206
            //if comment is on same line as parent
207
            if (isCommentStatement(statement) &&
734✔
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(
669✔
216
                    state.newline,
217
                    state.indent()
218
                );
219
            }
220

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

232
    walk(visitor: WalkVisitor, options: WalkOptions) {
233
        if (options.walkMode & InternalWalkMode.walkStatements) {
5,542✔
234
            walkArray(this.statements, visitor, options, this);
5,536✔
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
302✔
252
    ) {
253
        super();
302✔
254
        this.range = this.expression?.range;
302✔
255
    }
256

257
    public readonly range: Range | undefined;
258

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

263
    walk(visitor: WalkVisitor, options: WalkOptions) {
264
        if (options.walkMode & InternalWalkMode.walkExpressions) {
906✔
265
            walk(this, 'expression', visitor, options);
685✔
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[]
263✔
282
    ) {
283
        super();
263✔
284
        this.visitMode = InternalWalkMode.visitStatements | InternalWalkMode.visitExpressions;
263✔
285
        if (this.comments?.length > 0) {
263✔
286
            this.range = util.createBoundingRange(
261✔
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,056✔
399
        public func: FunctionExpression
2,056✔
400
    ) {
401
        super();
2,056✔
402
        this.range = this.func?.range;
2,056✔
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,026✔
412
        if (namespace) {
2,026✔
413
            let delimiter = parseMode === ParseMode.BrighterScript ? '.' : '_';
296✔
414
            let namespaceName = namespace.getName(parseMode);
296✔
415
            return namespaceName + delimiter + this.name?.text;
296✔
416
        } else {
417
            return this.name.text;
1,730✔
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 = {
279✔
432
            ...this.name,
433
            text: this.getName(ParseMode.BrightScript)
434
        };
435

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

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

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

455
    walk(visitor: WalkVisitor, options: WalkOptions) {
456
        if (options.walkMode & InternalWalkMode.walkExpressions) {
2,745✔
457
            walk(this, 'func', visitor, options);
2,718✔
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: {
242✔
475
            if: Token;
476
            then?: Token;
477
            else?: Token;
478
            endIf?: Token;
479
        },
480
        readonly condition: Expression,
242✔
481
        readonly thenBranch: Block,
242✔
482
        readonly elseBranch?: IfStatement | Block,
242✔
483
        readonly isInline?: boolean
242✔
484
    ) {
485
        super();
242✔
486
        this.range = util.createBoundingRange(
242✔
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) {
542✔
576
            walk(this, 'condition', visitor, options);
425✔
577
        }
578
        if (options.walkMode & InternalWalkMode.walkStatements) {
542✔
579
            walk(this, 'thenBranch', visitor, options);
540✔
580
        }
581
        if (this.elseBranch && options.walkMode & InternalWalkMode.walkStatements) {
542✔
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: {
717✔
665
            print: Token;
666
        },
667
        readonly expressions: Array<Expression | PrintSeparatorTab | PrintSeparatorSpace>
717✔
668
    ) {
669
        super();
717✔
670
        this.range = util.createBoundingRange(
717✔
671
            tokens.print,
672
            ...(expressions ?? [])
2,151✔
673
        );
674
    }
675

676
    public readonly range: Range | undefined;
677

678
    transpile(state: BrsTranspileState) {
679
        let result = [
193✔
680
            state.transpileToken(this.tokens.print),
681
            ' '
682
        ] as TranspileResult;
683
        for (let i = 0; i < this.expressions.length; i++) {
193✔
684
            const expressionOrSeparator: any = this.expressions[i];
257✔
685
            if (expressionOrSeparator.transpile) {
257✔
686
                result.push(...(expressionOrSeparator as ExpressionStatement).transpile(state));
231✔
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) {
257✔
694
                result.push(' ');
38✔
695
            }
696
        }
697
        return result;
193✔
698
    }
699

700
    walk(visitor: WalkVisitor, options: WalkOptions) {
701
        if (options.walkMode & InternalWalkMode.walkExpressions) {
1,716✔
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,543✔
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: {
190✔
864
            return: Token;
865
        },
866
        readonly value?: Expression
190✔
867
    ) {
868
        super();
190✔
869
        this.range = util.createBoundingRange(
190✔
870
            tokens.return,
871
            value
872
        );
873
    }
874

875
    public readonly range: Range | undefined;
876

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

889
    walk(visitor: WalkVisitor, options: WalkOptions) {
890
        if (options.walkMode & InternalWalkMode.walkExpressions) {
398✔
891
            walk(this, 'value', visitor, options);
337✔
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,
246✔
1427
        // this should technically only be a VariableExpression or DottedGetExpression, but that can be enforced elsewhere
1428
        public nameExpression: NamespacedVariableNameExpression,
246✔
1429
        public body: Body,
246✔
1430
        public endKeyword: Token
246✔
1431
    ) {
1432
        super();
246✔
1433
        this.symbolTable = new SymbolTable(`NamespaceStatement: '${this.name}'`, () => this.parent?.getSymbolTable());
246!
1434
    }
1435

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

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

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

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

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

1472
        return name;
2,730✔
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);
30✔
1478
    }
1479

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

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

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

1504
        if (this.body.statements.length > 0 && options.walkMode & InternalWalkMode.walkStatements) {
485✔
1505
            walk(this, 'body', visitor, options);
415✔
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,
41✔
1527
        readonly filePathToken: Token | undefined
41✔
1528
    ) {
1529
        super();
41✔
1530
        this.range = util.createBoundingRange(
41✔
1531
            importToken,
1532
            filePathToken
1533
        );
1534
        if (this.filePathToken) {
41✔
1535
            //remove quotes
1536
            this.filePath = this.filePathToken.text.replace(/"/g, '');
39✔
1537
            if (this.filePathToken.range) {
39✔
1538
                //adjust the range to exclude the quotes
1539
                this.filePathToken.range = util.createRange(
36✔
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,
60✔
1594
        public body: Statement[],
60✔
1595
        endInterfaceToken: Token
1596
    ) {
1597
        super();
60✔
1598
        this.tokens.interface = interfaceToken;
60✔
1599
        this.tokens.name = name;
60✔
1600
        this.tokens.extends = extendsToken;
60✔
1601
        this.tokens.endInterface = endInterfaceToken;
60✔
1602
        this.range = util.createBoundingRange(
60✔
1603
            this.tokens.interface,
1604
            this.tokens.name,
1605
            this.tokens.extends,
1606
            this.parentInterfaceName,
1607
            ...this.body ?? [],
180✔
1608
            this.tokens.endInterface
1609
        );
1610
    }
1611

1612
    public tokens = {} as {
60✔
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);
109✔
1737

1738
        if (options.walkMode & InternalWalkMode.walkStatements) {
109!
1739
            walkArray(this.body, visitor, options, this);
109✔
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,
58✔
1767
        optionalToken?: Token
1768
    ) {
1769
        super();
58✔
1770
        this.tokens.optional = optionalToken;
58✔
1771
        this.tokens.name = nameToken;
58✔
1772
        this.tokens.as = asToken;
58✔
1773
        this.tokens.type = typeToken;
58✔
1774
        this.range = util.createBoundingRange(
58✔
1775
            optionalToken,
1776
            nameToken,
1777
            asToken,
1778
            typeToken
1779
        );
1780
    }
1781

1782
    public range: Range | undefined;
1783

1784
    public tokens = {} as {
58✔
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,
494✔
1973
        /**
1974
         * The name of the class (without namespace prefix)
1975
         */
1976
        readonly name: Identifier,
494✔
1977
        public body: Statement[],
494✔
1978
        readonly end: Token,
494✔
1979
        readonly extendsKeyword?: Token,
494✔
1980
        readonly parentClassName?: NamespacedVariableNameExpression
494✔
1981
    ) {
1982
        super();
494✔
1983
        this.body = this.body ?? [];
494✔
1984

1985
        for (let statement of this.body) {
494✔
1986
            if (isMethodStatement(statement)) {
464✔
1987
                this.methods.push(statement);
270✔
1988
                this.memberMap[statement?.name?.text.toLowerCase()] = statement;
270!
1989
            } else if (isFieldStatement(statement)) {
194✔
1990
                this.fields.push(statement);
185✔
1991
                this.memberMap[statement.name?.text.toLowerCase()] = statement;
185!
1992
            }
1993
        }
1994

1995
        this.range = util.createBoundingRange(
494✔
1996
            classKeyword,
1997
            name,
1998
            extendsKeyword,
1999
            parentClassName,
2000
            ...(body ?? []),
1,482✔
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;
924✔
2016
        if (name) {
924✔
2017
            const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
922✔
2018
            if (namespace) {
922✔
2019
                let namespaceName = namespace.getName(parseMode);
246✔
2020
                let separator = parseMode === ParseMode.BrighterScript ? '.' : '_';
246✔
2021
                return namespaceName + separator + name;
246✔
2022
            } else {
2023
                return name;
676✔
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>;
494✔
2032
    public methods = [] as MethodStatement[];
494✔
2033
    public fields = [] as FieldStatement[];
494✔
2034

2035
    public readonly range: Range | undefined;
2036

2037
    transpile(state: BrsTranspileState) {
2038
        let result = [] as TranspileResult;
48✔
2039
        //make the builder
2040
        result.push(...this.getTranspiledBuilder(state));
48✔
2041
        result.push(
48✔
2042
            '\n',
2043
            state.indent()
2044
        );
2045
        //make the class assembler (i.e. the public-facing class creator method)
2046
        result.push(...this.getTranspiledClassFunction(state));
48✔
2047
        return result;
48✔
2048
    }
2049

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

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

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

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

2135
    public hasParentClass() {
2136
        return !!this.parentClassName;
48✔
2137
    }
2138

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

2161
    private getBuilderName(name: string) {
2162
        if (name.includes('.')) {
120✔
2163
            name = name.replace(/\./gi, '_');
6✔
2164
        }
2165
        return `__${name}_builder`;
120✔
2166
    }
2167

2168
    /**
2169
     * Get the constructor function for this class (if exists), or undefined if not exist
2170
     */
2171
    private getConstructorFunction() {
2172
        return this.body.find((stmt) => {
151✔
2173
            return (stmt as MethodStatement)?.name?.text?.toLowerCase() === 'new';
132!
2174
        }) as MethodStatement;
2175
    }
2176

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

2192
    /**
2193
     * Determine if the specified field was declared in one of the ancestor classes
2194
     */
2195
    public isFieldDeclaredByAncestor(fieldName: string, ancestors: ClassStatement[]) {
2196
        let lowerFieldName = fieldName.toLowerCase();
×
2197
        for (let ancestor of ancestors) {
×
2198
            if (ancestor.memberMap[lowerFieldName]) {
×
2199
                return true;
×
2200
            }
2201
        }
2202
        return false;
×
2203
    }
2204

2205
    /**
2206
     * The builder is a function that assigns all of the methods and property names to a class instance.
2207
     * This needs to be a separate function so that child classes can call the builder from their parent
2208
     * without instantiating the parent constructor at that point in time.
2209
     */
2210
    private getTranspiledBuilder(state: BrsTranspileState) {
2211
        let result = [] as TranspileResult;
48✔
2212
        result.push(`function ${this.getBuilderName(this.getName(ParseMode.BrightScript)!)}()\n`);
48✔
2213
        state.blockDepth++;
48✔
2214
        //indent
2215
        result.push(state.indent());
48✔
2216

2217
        /**
2218
         * The lineage of this class. index 0 is a direct parent, index 1 is index 0's parent, etc...
2219
         */
2220
        let ancestors = this.getAncestors(state);
48✔
2221

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

2242
        let body = this.body;
48✔
2243
        //inject an empty "new" method if missing
2244
        if (!this.getConstructorFunction()) {
48✔
2245
            if (ancestors.length === 0) {
26✔
2246
                body = [
11✔
2247
                    createMethodStatement('new', TokenKind.Sub),
2248
                    ...this.body
2249
                ];
2250
            } else {
2251
                const params = this.getConstructorParams(ancestors);
15✔
2252
                const call = new ExpressionStatement(
15✔
2253
                    new CallExpression(
2254
                        new VariableExpression(createToken(TokenKind.Identifier, 'super')),
2255
                        createToken(TokenKind.LeftParen),
2256
                        createToken(TokenKind.RightParen),
2257
                        params.map(x => new VariableExpression(x.name))
6✔
2258
                    )
2259
                );
2260
                body = [
15✔
2261
                    new MethodStatement(
2262
                        [],
2263
                        createIdentifier('new'),
2264
                        new FunctionExpression(
2265
                            params.map(x => x.clone()),
6✔
2266
                            new Block([call]),
2267
                            createToken(TokenKind.Sub),
2268
                            createToken(TokenKind.EndSub),
2269
                            createToken(TokenKind.LeftParen),
2270
                            createToken(TokenKind.RightParen)
2271
                        ),
2272
                        null
2273
                    ),
2274
                    ...this.body
2275
                ];
2276
            }
2277
        }
2278

2279
        for (let statement of body) {
48✔
2280
            //is field statement
2281
            if (isFieldStatement(statement)) {
71✔
2282
                //do nothing with class fields in this situation, they are handled elsewhere
2283
                continue;
11✔
2284

2285
                //methods
2286
            } else if (isMethodStatement(statement)) {
60!
2287

2288
                //store overridden parent methods as super{parentIndex}_{methodName}
2289
                if (
60✔
2290
                    //is override method
2291
                    statement.override ||
164✔
2292
                    //is constructor function in child class
2293
                    (statement.name.text.toLowerCase() === 'new' && ancestors[0])
2294
                ) {
2295
                    result.push(
28✔
2296
                        `instance.super${parentClassIndex}_${statement.name.text} = instance.${statement.name.text}`,
2297
                        state.newline,
2298
                        state.indent()
2299
                    );
2300
                }
2301

2302
                state.classStatement = this;
60✔
2303
                result.push(
60✔
2304
                    'instance.',
2305
                    state.transpileToken(statement.name),
2306
                    ' = ',
2307
                    ...statement.transpile(state),
2308
                    state.newline,
2309
                    state.indent()
2310
                );
2311
                delete state.classStatement;
60✔
2312
            } else {
2313
                //other random statements (probably just comments)
2314
                result.push(
×
2315
                    ...statement.transpile(state),
2316
                    state.newline,
2317
                    state.indent()
2318
                );
2319
            }
2320
        }
2321
        //return the instance
2322
        result.push('return instance\n');
48✔
2323
        state.blockDepth--;
48✔
2324
        result.push(state.indent());
48✔
2325
        result.push(`end function`);
48✔
2326
        return result;
48✔
2327
    }
2328

2329
    /**
2330
     * The class function is the function with the same name as the class. This is the function that
2331
     * consumers should call to create a new instance of that class.
2332
     * This invokes the builder, gets an instance of the class, then invokes the "new" function on that class.
2333
     */
2334
    private getTranspiledClassFunction(state: BrsTranspileState) {
2335
        let result = [] as TranspileResult;
48✔
2336

2337
        const constructorFunction = this.getConstructorFunction();
48✔
2338
        let constructorParams = [];
48✔
2339
        if (constructorFunction) {
48✔
2340
            constructorParams = constructorFunction.func.parameters;
22✔
2341
        } else {
2342
            constructorParams = this.getConstructorParams(this.getAncestors(state));
26✔
2343
        }
2344

2345
        result.push(
48✔
2346
            state.sourceNode(this.classKeyword, 'function'),
2347
            state.sourceNode(this.classKeyword, ' '),
2348
            state.sourceNode(this.name, this.getName(ParseMode.BrightScript)!),
2349
            `(`
2350
        );
2351
        let i = 0;
48✔
2352
        for (let param of constructorParams) {
48✔
2353
            if (i > 0) {
17✔
2354
                result.push(', ');
4✔
2355
            }
2356
            result.push(
17✔
2357
                param.transpile(state)
2358
            );
2359
            i++;
17✔
2360
        }
2361
        result.push(
48✔
2362
            ')',
2363
            '\n'
2364
        );
2365

2366
        state.blockDepth++;
48✔
2367
        result.push(state.indent());
48✔
2368
        result.push(`instance = ${this.getBuilderName(this.getName(ParseMode.BrightScript)!)}()\n`);
48✔
2369

2370
        result.push(state.indent());
48✔
2371
        result.push(`instance.new(`);
48✔
2372

2373
        //append constructor arguments
2374
        i = 0;
48✔
2375
        for (let param of constructorParams) {
48✔
2376
            if (i > 0) {
17✔
2377
                result.push(', ');
4✔
2378
            }
2379
            result.push(
17✔
2380
                state.transpileToken(param.name)
2381
            );
2382
            i++;
17✔
2383
        }
2384
        result.push(
48✔
2385
            ')',
2386
            '\n'
2387
        );
2388

2389
        result.push(state.indent());
48✔
2390
        result.push(`return instance\n`);
48✔
2391

2392
        state.blockDepth--;
48✔
2393
        result.push(state.indent());
48✔
2394
        result.push(`end function`);
48✔
2395
        return result;
48✔
2396
    }
2397

2398
    walk(visitor: WalkVisitor, options: WalkOptions) {
2399
        //visitor-less walk function to do parent linking
2400
        walk(this, 'parentClassName', null, options);
973✔
2401

2402
        if (options.walkMode & InternalWalkMode.walkStatements) {
973!
2403
            walkArray(this.body, visitor, options, this);
973✔
2404
        }
2405
    }
2406

2407
    public clone() {
2408
        return this.finalizeClone(
11✔
2409
            new ClassStatement(
2410
                util.cloneToken(this.classKeyword),
2411
                util.cloneToken(this.name),
2412
                this.body?.map(x => x?.clone()),
10✔
2413
                util.cloneToken(this.end),
2414
                util.cloneToken(this.extendsKeyword),
2415
                this.parentClassName?.clone()
33✔
2416
            ),
2417
            ['body', 'parentClassName']
2418
        );
2419
    }
2420
}
2421

2422
const accessModifiers = [
1✔
2423
    TokenKind.Public,
2424
    TokenKind.Protected,
2425
    TokenKind.Private
2426
];
2427
export class MethodStatement extends FunctionStatement {
1✔
2428
    constructor(
2429
        modifiers: Token | Token[],
2430
        name: Identifier,
2431
        func: FunctionExpression,
2432
        public override: Token
307✔
2433
    ) {
2434
        super(name, func);
307✔
2435
        if (modifiers) {
307✔
2436
            if (Array.isArray(modifiers)) {
50✔
2437
                this.modifiers.push(...modifiers);
19✔
2438
            } else {
2439
                this.modifiers.push(modifiers);
31✔
2440
            }
2441
        }
2442
        this.range = util.createBoundingRange(
307✔
2443
            ...(this.modifiers),
2444
            override,
2445
            func
2446
        );
2447
    }
2448

2449
    public modifiers: Token[] = [];
307✔
2450

2451
    public get accessModifier() {
2452
        return this.modifiers.find(x => accessModifiers.includes(x.kind));
102✔
2453
    }
2454

2455
    public readonly range: Range | undefined;
2456

2457
    /**
2458
     * Get the name of this method.
2459
     */
2460
    public getName(parseMode: ParseMode) {
2461
        return this.name.text;
1✔
2462
    }
2463

2464
    transpile(state: BrsTranspileState) {
2465
        if (this.name.text.toLowerCase() === 'new') {
60✔
2466
            this.ensureSuperConstructorCall(state);
48✔
2467
            //TODO we need to undo this at the bottom of this method
2468
            this.injectFieldInitializersForConstructor(state);
48✔
2469
        }
2470
        //TODO - remove type information from these methods because that doesn't work
2471
        //convert the `super` calls into the proper methods
2472
        const parentClassIndex = state.classStatement.getParentClassIndex(state);
60✔
2473
        const visitor = createVisitor({
60✔
2474
            VariableExpression: e => {
2475
                if (e.name.text.toLocaleLowerCase() === 'super') {
67✔
2476
                    state.editor.setProperty(e.name, 'text', `m.super${parentClassIndex}_new`);
24✔
2477
                }
2478
            },
2479
            DottedGetExpression: e => {
2480
                const beginningVariable = util.findBeginningVariableExpression(e);
25✔
2481
                const lowerName = beginningVariable?.getName(ParseMode.BrighterScript).toLowerCase();
25!
2482
                if (lowerName === 'super') {
25✔
2483
                    state.editor.setProperty(beginningVariable.name, 'text', 'm');
7✔
2484
                    state.editor.setProperty(e.name, 'text', `super${parentClassIndex}_${e.name.text}`);
7✔
2485
                }
2486
            }
2487
        });
2488
        const walkOptions: WalkOptions = { walkMode: WalkMode.visitExpressions };
60✔
2489
        for (const statement of this.func.body.statements) {
60✔
2490
            visitor(statement, undefined);
64✔
2491
            statement.walk(visitor, walkOptions);
64✔
2492
        }
2493
        return this.func.transpile(state);
60✔
2494
    }
2495

2496
    getTypedef(state: BrsTranspileState) {
2497
        const result = [] as TranspileResult;
23✔
2498
        for (let annotation of this.annotations ?? []) {
23✔
2499
            result.push(
2✔
2500
                ...annotation.getTypedef(state),
2501
                state.newline,
2502
                state.indent()
2503
            );
2504
        }
2505
        if (this.accessModifier) {
23✔
2506
            result.push(
8✔
2507
                this.accessModifier.text,
2508
                ' '
2509
            );
2510
        }
2511
        if (this.override) {
23✔
2512
            result.push('override ');
1✔
2513
        }
2514
        result.push(
23✔
2515
            ...this.func.getTypedef(state)
2516
        );
2517
        return result;
23✔
2518
    }
2519

2520
    /**
2521
     * All child classes must call the parent constructor. The type checker will warn users when they don't call it in their own class,
2522
     * but we still need to call it even if they have omitted it. This injects the super call if it's missing
2523
     */
2524
    private ensureSuperConstructorCall(state: BrsTranspileState) {
2525
        //if this class doesn't extend another class, quit here
2526
        if (state.classStatement!.getAncestors(state).length === 0) {
48✔
2527
            return;
24✔
2528
        }
2529

2530
        //check whether any calls to super exist
2531
        let containsSuperCall =
2532
            this.func.body.statements.findIndex((x) => {
24✔
2533
                //is a call statement
2534
                return isExpressionStatement(x) && isCallExpression(x.expression) &&
25✔
2535
                    //is a call to super
2536
                    util.findBeginningVariableExpression(x.expression.callee as any)?.name.text.toLowerCase() === 'super';
69!
2537
            }) !== -1;
2538

2539
        //if a call to super exists, quit here
2540
        if (containsSuperCall) {
24✔
2541
            return;
23✔
2542
        }
2543

2544
        //this is a child class, and the constructor doesn't contain a call to super. Inject one
2545
        const superCall = new ExpressionStatement(
1✔
2546
            new CallExpression(
2547
                new VariableExpression(
2548
                    {
2549
                        kind: TokenKind.Identifier,
2550
                        text: 'super',
2551
                        isReserved: false,
2552
                        range: state.classStatement!.name.range,
2553
                        leadingWhitespace: ''
2554
                    }
2555
                ),
2556
                {
2557
                    kind: TokenKind.LeftParen,
2558
                    text: '(',
2559
                    isReserved: false,
2560
                    range: state.classStatement!.name.range,
2561
                    leadingWhitespace: ''
2562
                },
2563
                {
2564
                    kind: TokenKind.RightParen,
2565
                    text: ')',
2566
                    isReserved: false,
2567
                    range: state.classStatement!.name.range,
2568
                    leadingWhitespace: ''
2569
                },
2570
                []
2571
            )
2572
        );
2573
        state.editor.arrayUnshift(this.func.body.statements, superCall);
1✔
2574
    }
2575

2576
    /**
2577
     * Inject field initializers at the top of the `new` function (after any present `super()` call)
2578
     */
2579
    private injectFieldInitializersForConstructor(state: BrsTranspileState) {
2580
        let startingIndex = state.classStatement!.hasParentClass() ? 1 : 0;
48✔
2581

2582
        let newStatements = [] as Statement[];
48✔
2583
        //insert the field initializers in order
2584
        for (let field of state.classStatement!.fields) {
48✔
2585
            let thisQualifiedName = { ...field.name };
11✔
2586
            thisQualifiedName.text = 'm.' + field.name?.text;
11!
2587
            if (field.initialValue) {
11✔
2588
                newStatements.push(
9✔
2589
                    new AssignmentStatement(field.equal, thisQualifiedName, field.initialValue)
2590
                );
2591
            } else {
2592
                //if there is no initial value, set the initial value to `invalid`
2593
                newStatements.push(
2✔
2594
                    new AssignmentStatement(
2595
                        createToken(TokenKind.Equal, '=', field.name?.range),
6!
2596
                        thisQualifiedName,
2597
                        createInvalidLiteral('invalid', field.name?.range)
6!
2598
                    )
2599
                );
2600
            }
2601
        }
2602
        state.editor.arraySplice(this.func.body.statements, startingIndex, 0, ...newStatements);
48✔
2603
    }
2604

2605
    walk(visitor: WalkVisitor, options: WalkOptions) {
2606
        if (options.walkMode & InternalWalkMode.walkExpressions) {
794✔
2607
            walk(this, 'func', visitor, options);
589✔
2608
        }
2609
    }
2610

2611
    public clone() {
2612
        return this.finalizeClone(
5✔
2613
            new MethodStatement(
2614
                this.modifiers?.map(m => util.cloneToken(m)),
1✔
2615
                util.cloneToken(this.name),
2616
                this.func?.clone(),
15✔
2617
                util.cloneToken(this.override)
2618
            ),
2619
            ['func']
2620
        );
2621
    }
2622
}
2623
/**
2624
 * @deprecated use `MethodStatement`
2625
 */
2626
export class ClassMethodStatement extends MethodStatement { }
1✔
2627

2628
export class FieldStatement extends Statement implements TypedefProvider {
1✔
2629

2630
    constructor(
2631
        readonly accessModifier?: Token,
185✔
2632
        readonly name?: Identifier,
185✔
2633
        readonly as?: Token,
185✔
2634
        readonly type?: Token,
185✔
2635
        readonly equal?: Token,
185✔
2636
        readonly initialValue?: Expression,
185✔
2637
        readonly optional?: Token
185✔
2638
    ) {
2639
        super();
185✔
2640
        this.range = util.createBoundingRange(
185✔
2641
            accessModifier,
2642
            name,
2643
            as,
2644
            type,
2645
            equal,
2646
            initialValue
2647
        );
2648
    }
2649

2650
    /**
2651
     * Derive a ValueKind from the type token, or the initial value.
2652
     * Defaults to `DynamicType`
2653
     */
2654
    getType() {
2655
        if (this.type) {
76✔
2656
            return util.tokenToBscType(this.type);
37✔
2657
        } else if (isLiteralExpression(this.initialValue)) {
39✔
2658
            return this.initialValue.type;
25✔
2659
        } else {
2660
            return new DynamicType();
14✔
2661
        }
2662
    }
2663

2664
    public readonly range: Range | undefined;
2665

2666
    public get isOptional() {
2667
        return !!this.optional;
17✔
2668
    }
2669

2670
    transpile(state: BrsTranspileState): TranspileResult {
2671
        throw new Error('transpile not implemented for ' + Object.getPrototypeOf(this).constructor.name);
×
2672
    }
2673

2674
    getTypedef(state: BrsTranspileState) {
2675
        const result = [] as TranspileResult;
10✔
2676
        if (this.name) {
10!
2677
            for (let annotation of this.annotations ?? []) {
10✔
2678
                result.push(
2✔
2679
                    ...annotation.getTypedef(state),
2680
                    state.newline,
2681
                    state.indent()
2682
                );
2683
            }
2684

2685
            let type = this.getType();
10✔
2686
            if (isInvalidType(type) || isVoidType(type)) {
10✔
2687
                type = new DynamicType();
1✔
2688
            }
2689

2690
            result.push(
10✔
2691
                this.accessModifier?.text ?? 'public',
60!
2692
                ' '
2693
            );
2694
            if (this.isOptional) {
10!
2695
                result.push(this.optional!.text, ' ');
×
2696
            }
2697
            result.push(this.name?.text,
10!
2698
                ' as ',
2699
                type.toTypeString()
2700
            );
2701
        }
2702
        return result;
10✔
2703
    }
2704

2705
    walk(visitor: WalkVisitor, options: WalkOptions) {
2706
        if (this.initialValue && options.walkMode & InternalWalkMode.walkExpressions) {
255✔
2707
            walk(this, 'initialValue', visitor, options);
71✔
2708
        }
2709
    }
2710

2711
    public clone() {
2712
        return this.finalizeClone(
4✔
2713
            new FieldStatement(
2714
                util.cloneToken(this.accessModifier),
2715
                util.cloneToken(this.name),
2716
                util.cloneToken(this.as),
2717
                util.cloneToken(this.type),
2718
                util.cloneToken(this.equal),
2719
                this.initialValue?.clone(),
12✔
2720
                util.cloneToken(this.optional)
2721
            ),
2722
            ['initialValue']
2723
        );
2724
    }
2725
}
2726

2727
/**
2728
 * @deprecated use `FieldStatement`
2729
 */
2730
export class ClassFieldStatement extends FieldStatement { }
1✔
2731

2732
export type MemberStatement = FieldStatement | MethodStatement;
2733

2734
/**
2735
 * @deprecated use `MemeberStatement`
2736
 */
2737
export type ClassMemberStatement = MemberStatement;
2738

2739
export class TryCatchStatement extends Statement {
1✔
2740
    constructor(
2741
        public tokens: {
31✔
2742
            try: Token;
2743
            endTry?: Token;
2744
        },
2745
        public tryBranch?: Block,
31✔
2746
        public catchStatement?: CatchStatement
31✔
2747
    ) {
2748
        super();
31✔
2749
        this.range = util.createBoundingRange(
31✔
2750
            tokens.try,
2751
            tryBranch,
2752
            catchStatement,
2753
            tokens.endTry
2754
        );
2755
    }
2756

2757
    public readonly range: Range | undefined;
2758

2759
    public transpile(state: BrsTranspileState): TranspileResult {
2760
        return [
3✔
2761
            state.transpileToken(this.tokens.try),
2762
            ...this.tryBranch!.transpile(state),
2763
            state.newline,
2764
            state.indent(),
2765
            ...(this.catchStatement?.transpile(state) ?? ['catch']),
18!
2766
            state.newline,
2767
            state.indent(),
2768
            state.transpileToken(this.tokens.endTry!)
2769
        ] as TranspileResult;
2770
    }
2771

2772
    public walk(visitor: WalkVisitor, options: WalkOptions) {
2773
        if (this.tryBranch && options.walkMode & InternalWalkMode.walkStatements) {
43✔
2774
            walk(this, 'tryBranch', visitor, options);
42✔
2775
            walk(this, 'catchStatement', visitor, options);
42✔
2776
        }
2777
    }
2778

2779
    public clone() {
2780
        return this.finalizeClone(
3✔
2781
            new TryCatchStatement(
2782
                {
2783
                    try: util.cloneToken(this.tokens.try),
2784
                    endTry: util.cloneToken(this.tokens.endTry)
2785
                },
2786
                this.tryBranch?.clone(),
9✔
2787
                this.catchStatement?.clone()
9✔
2788
            ),
2789
            ['tryBranch', 'catchStatement']
2790
        );
2791
    }
2792
}
2793

2794
export class CatchStatement extends Statement {
1✔
2795
    constructor(
2796
        public tokens: {
28✔
2797
            catch: Token;
2798
        },
2799
        public exceptionVariable?: Identifier,
28✔
2800
        public catchBranch?: Block
28✔
2801
    ) {
2802
        super();
28✔
2803
        this.range = util.createBoundingRange(
28✔
2804
            tokens.catch,
2805
            exceptionVariable,
2806
            catchBranch
2807
        );
2808
    }
2809

2810
    public range: Range | undefined;
2811

2812
    public transpile(state: BrsTranspileState): TranspileResult {
2813
        return [
3✔
2814
            state.transpileToken(this.tokens.catch),
2815
            ' ',
2816
            this.exceptionVariable?.text ?? 'e',
18!
2817
            ...(this.catchBranch?.transpile(state) ?? [])
18!
2818
        ];
2819
    }
2820

2821
    public walk(visitor: WalkVisitor, options: WalkOptions) {
2822
        if (this.catchBranch && options.walkMode & InternalWalkMode.walkStatements) {
39✔
2823
            walk(this, 'catchBranch', visitor, options);
38✔
2824
        }
2825
    }
2826

2827
    public clone() {
2828
        return this.finalizeClone(
2✔
2829
            new CatchStatement(
2830
                {
2831
                    catch: util.cloneToken(this.tokens.catch)
2832
                },
2833
                util.cloneToken(this.exceptionVariable),
2834
                this.catchBranch?.clone()
6✔
2835
            ),
2836
            ['catchBranch']
2837
        );
2838
    }
2839
}
2840

2841
export class ThrowStatement extends Statement {
1✔
2842
    constructor(
2843
        public throwToken: Token,
12✔
2844
        public expression?: Expression
12✔
2845
    ) {
2846
        super();
12✔
2847
        this.range = util.createBoundingRange(
12✔
2848
            throwToken,
2849
            expression
2850
        );
2851
    }
2852
    public range: Range | undefined;
2853

2854
    public transpile(state: BrsTranspileState) {
2855
        const result = [
4✔
2856
            state.transpileToken(this.throwToken),
2857
            ' '
2858
        ] as TranspileResult;
2859

2860
        //if we have an expression, transpile it
2861
        if (this.expression) {
4!
2862
            result.push(
4✔
2863
                ...this.expression.transpile(state)
2864
            );
2865

2866
            //no expression found. Rather than emit syntax errors, provide a generic error message
2867
        } else {
2868
            result.push('"An error has occurred"');
×
2869
        }
2870
        return result;
4✔
2871
    }
2872

2873
    public walk(visitor: WalkVisitor, options: WalkOptions) {
2874
        if (this.expression && options.walkMode & InternalWalkMode.walkExpressions) {
23✔
2875
            walk(this, 'expression', visitor, options);
16✔
2876
        }
2877
    }
2878

2879
    public clone() {
2880
        return this.finalizeClone(
2✔
2881
            new ThrowStatement(
2882
                util.cloneToken(this.throwToken),
2883
                this.expression?.clone()
6✔
2884
            ),
2885
            ['expression']
2886
        );
2887
    }
2888
}
2889

2890

2891
export class EnumStatement extends Statement implements TypedefProvider {
1✔
2892

2893
    constructor(
2894
        public tokens: {
121✔
2895
            enum: Token;
2896
            name: Identifier;
2897
            endEnum: Token;
2898
        },
2899
        public body: Array<EnumMemberStatement | CommentStatement>
121✔
2900
    ) {
2901
        super();
121✔
2902
        this.body = this.body ?? [];
121✔
2903
    }
2904

2905
    public get range(): Range | undefined {
2906
        return util.createBoundingRange(
15✔
2907
            this.tokens.enum,
2908
            this.tokens.name,
2909
            ...this.body,
2910
            this.tokens.endEnum
2911
        );
2912
    }
2913

2914
    /**
2915
     * Get the name of the wrapping namespace (if it exists)
2916
     * @deprecated use `.findAncestor(isNamespaceStatement)` instead.
2917
     */
2918
    public get namespaceName() {
2919
        return this.findAncestor<NamespaceStatement>(isNamespaceStatement)?.nameExpression;
×
2920
    }
2921

2922
    public getMembers() {
2923
        const result = [] as EnumMemberStatement[];
141✔
2924
        for (const statement of this.body) {
141✔
2925
            if (isEnumMemberStatement(statement)) {
305✔
2926
                result.push(statement);
292✔
2927
            }
2928
        }
2929
        return result;
141✔
2930
    }
2931

2932
    /**
2933
     * Get a map of member names and their values.
2934
     * All values are stored as their AST LiteralExpression representation (i.e. string enum values include the wrapping quotes)
2935
     */
2936
    public getMemberValueMap() {
2937
        const result = new Map<string, string>();
55✔
2938
        const members = this.getMembers();
55✔
2939
        let currentIntValue = 0;
55✔
2940
        for (const member of members) {
55✔
2941
            //if there is no value, assume an integer and increment the int counter
2942
            if (!member.value) {
141✔
2943
                result.set(member.name?.toLowerCase(), currentIntValue.toString());
32!
2944
                currentIntValue++;
32✔
2945

2946
                //if explicit integer value, use it and increment the int counter
2947
            } else if (isLiteralExpression(member.value) && member.value.token.kind === TokenKind.IntegerLiteral) {
109✔
2948
                //try parsing as integer literal, then as hex integer literal.
2949
                let tokenIntValue = util.parseInt(member.value.token.text) ?? util.parseInt(member.value.token.text.replace(/&h/i, '0x'));
31✔
2950
                if (tokenIntValue !== undefined) {
31!
2951
                    currentIntValue = tokenIntValue;
31✔
2952
                    currentIntValue++;
31✔
2953
                }
2954
                result.set(member.name?.toLowerCase(), member.value.token.text);
31!
2955

2956
                //simple unary expressions (like `-1`)
2957
            } else if (isUnaryExpression(member.value) && isLiteralExpression(member.value.right)) {
78✔
2958
                result.set(member.name?.toLowerCase(), member.value.operator.text + member.value.right.token.text);
1!
2959

2960
                //all other values
2961
            } else {
2962
                result.set(member.name?.toLowerCase(), (member.value as LiteralExpression)?.token?.text ?? 'invalid');
77!
2963
            }
2964
        }
2965
        return result;
55✔
2966
    }
2967

2968
    public getMemberValue(name: string) {
2969
        return this.getMemberValueMap().get(name.toLowerCase());
52✔
2970
    }
2971

2972
    /**
2973
     * The name of the enum (without the namespace prefix)
2974
     */
2975
    public get name() {
2976
        return this.tokens.name?.text;
16!
2977
    }
2978

2979
    /**
2980
     * The name of the enum WITH its leading namespace (if applicable)
2981
     */
2982
    public get fullName() {
2983
        const name = this.tokens.name?.text;
329!
2984
        if (name) {
329!
2985
            const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
329✔
2986

2987
            if (namespace) {
329✔
2988
                let namespaceName = namespace.getName(ParseMode.BrighterScript);
143✔
2989
                return `${namespaceName}.${name}`;
143✔
2990
            } else {
2991
                return name;
186✔
2992
            }
2993
        } else {
2994
            //return undefined which will allow outside callers to know that this doesn't have a name
2995
            return undefined;
×
2996
        }
2997
    }
2998

2999
    transpile(state: BrsTranspileState) {
3000
        //enum declarations do not exist at runtime, so don't transpile anything...
3001
        return [];
23✔
3002
    }
3003

3004
    getTypedef(state: BrsTranspileState) {
3005
        const result = [] as TranspileResult;
1✔
3006
        for (let annotation of this.annotations ?? []) {
1!
3007
            result.push(
×
3008
                ...annotation.getTypedef(state),
3009
                state.newline,
3010
                state.indent()
3011
            );
3012
        }
3013
        result.push(
1✔
3014
            this.tokens.enum.text ?? 'enum',
3!
3015
            ' ',
3016
            this.tokens.name.text
3017
        );
3018
        result.push(state.newline);
1✔
3019
        state.blockDepth++;
1✔
3020
        for (const member of this.body) {
1✔
3021
            if (isTypedefProvider(member)) {
1!
3022
                result.push(
1✔
3023
                    state.indent(),
3024
                    ...member.getTypedef(state),
3025
                    state.newline
3026
                );
3027
            }
3028
        }
3029
        state.blockDepth--;
1✔
3030
        result.push(
1✔
3031
            state.indent(),
3032
            this.tokens.endEnum.text ?? 'end enum'
3!
3033
        );
3034
        return result;
1✔
3035
    }
3036

3037
    walk(visitor: WalkVisitor, options: WalkOptions) {
3038
        if (options.walkMode & InternalWalkMode.walkStatements) {
361!
3039
            walkArray(this.body, visitor, options, this);
361✔
3040

3041
        }
3042
    }
3043

3044
    public clone() {
3045
        return this.finalizeClone(
6✔
3046
            new EnumStatement(
3047
                {
3048
                    enum: util.cloneToken(this.tokens.enum),
3049
                    name: util.cloneToken(this.tokens.name),
3050
                    endEnum: util.cloneToken(this.tokens.endEnum)
3051
                },
3052
                this.body?.map(x => x?.clone())
6✔
3053
            ),
3054
            ['body']
3055
        );
3056
    }
3057
}
3058

3059
export class EnumMemberStatement extends Statement implements TypedefProvider {
1✔
3060

3061
    public constructor(
3062
        public tokens: {
202✔
3063
            name: Identifier;
3064
            equal?: Token;
3065
        },
3066
        public value?: Expression
202✔
3067
    ) {
3068
        super();
202✔
3069
    }
3070

3071
    /**
3072
     * The name of the member
3073
     */
3074
    public get name() {
3075
        return this.tokens.name.text;
561✔
3076
    }
3077

3078
    public get range() {
3079
        return util.createBoundingRange(
66✔
3080
            this.tokens.name,
3081
            this.tokens.equal,
3082
            this.value
3083
        );
3084
    }
3085

3086
    /**
3087
     * Get the value of this enum. Requires that `.parent` is set
3088
     */
3089
    public getValue() {
3090
        return (this.parent as EnumStatement).getMemberValue(this.name);
52✔
3091
    }
3092

3093
    public transpile(state: BrsTranspileState): TranspileResult {
3094
        return [];
×
3095
    }
3096

3097
    getTypedef(state: BrsTranspileState): TranspileResult {
3098
        const result = [
1✔
3099
            this.tokens.name.text
3100
        ] as TranspileResult;
3101
        if (this.tokens.equal) {
1!
3102
            result.push(' ', this.tokens.equal.text, ' ');
×
3103
            if (this.value) {
×
3104
                result.push(
×
3105
                    ...this.value.transpile(state)
3106
                );
3107
            }
3108
        }
3109
        return result;
1✔
3110
    }
3111

3112
    walk(visitor: WalkVisitor, options: WalkOptions) {
3113
        if (this.value && options.walkMode & InternalWalkMode.walkExpressions) {
486✔
3114
            walk(this, 'value', visitor, options);
301✔
3115
        }
3116
    }
3117

3118
    public clone() {
3119
        return this.finalizeClone(
3✔
3120
            new EnumMemberStatement(
3121
                {
3122
                    name: util.cloneToken(this.tokens.name),
3123
                    equal: util.cloneToken(this.tokens.equal)
3124
                },
3125
                this.value?.clone()
9✔
3126
            ),
3127
            ['value']
3128
        );
3129
    }
3130
}
3131

3132
export class ConstStatement extends Statement implements TypedefProvider {
1✔
3133

3134
    public constructor(
3135
        public tokens: {
57✔
3136
            const: Token;
3137
            name: Identifier;
3138
            equals: Token;
3139
        },
3140
        public value: Expression
57✔
3141
    ) {
3142
        super();
57✔
3143
        this.range = util.createBoundingRange(this.tokens.const, this.tokens.name, this.tokens.equals, this.value);
57✔
3144
    }
3145

3146
    public range: Range | undefined;
3147

3148
    public get name() {
3149
        return this.tokens.name.text;
5✔
3150
    }
3151

3152
    /**
3153
     * The name of the statement WITH its leading namespace (if applicable)
3154
     */
3155
    public get fullName() {
3156
        const name = this.tokens.name?.text;
75!
3157
        if (name) {
75!
3158
            const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
75✔
3159
            if (namespace) {
75✔
3160
                let namespaceName = namespace.getName(ParseMode.BrighterScript);
56✔
3161
                return `${namespaceName}.${name}`;
56✔
3162
            } else {
3163
                return name;
19✔
3164
            }
3165
        } else {
3166
            //return undefined which will allow outside callers to know that this doesn't have a name
3167
            return undefined;
×
3168
        }
3169
    }
3170

3171
    public transpile(state: BrsTranspileState): TranspileResult {
3172
        //const declarations don't exist at runtime, so just transpile empty
3173
        return [];
13✔
3174
    }
3175

3176
    getTypedef(state: BrsTranspileState): TranspileResult {
3177
        return [
3✔
3178
            state.tokenToSourceNode(this.tokens.const),
3179
            ' ',
3180
            state.tokenToSourceNode(this.tokens.name),
3181
            ' ',
3182
            state.tokenToSourceNode(this.tokens.equals),
3183
            ' ',
3184
            ...this.value.transpile(state)
3185
        ];
3186
    }
3187

3188
    walk(visitor: WalkVisitor, options: WalkOptions) {
3189
        if (this.value && options.walkMode & InternalWalkMode.walkExpressions) {
122✔
3190
            walk(this, 'value', visitor, options);
120✔
3191
        }
3192
    }
3193

3194
    public clone() {
3195
        return this.finalizeClone(
3✔
3196
            new ConstStatement(
3197
                {
3198
                    const: util.cloneToken(this.tokens.const),
3199
                    name: util.cloneToken(this.tokens.name),
3200
                    equals: util.cloneToken(this.tokens.equals)
3201
                },
3202
                this.value?.clone()
9✔
3203
            ),
3204
            ['value']
3205
        );
3206
    }
3207
}
3208

3209
export class ContinueStatement extends Statement {
1✔
3210
    constructor(
3211
        public tokens: {
13✔
3212
            continue: Token;
3213
            loopType: Token;
3214
        }
3215
    ) {
3216
        super();
13✔
3217
        this.range = util.createBoundingRange(
13✔
3218
            tokens.continue,
3219
            tokens.loopType
3220
        );
3221
    }
3222

3223
    public range: Range | undefined;
3224

3225
    transpile(state: BrsTranspileState) {
3226
        return [
3✔
3227
            state.sourceNode(this.tokens.continue, this.tokens.continue?.text ?? 'continue'),
18!
3228
            this.tokens.loopType?.leadingWhitespace ?? ' ',
18✔
3229
            state.sourceNode(this.tokens.continue, this.tokens.loopType?.text)
9✔
3230
        ];
3231
    }
3232

3233
    walk(visitor: WalkVisitor, options: WalkOptions) {
3234
        //nothing to walk
3235
    }
3236

3237
    public clone() {
3238
        return this.finalizeClone(
1✔
3239
            new ContinueStatement({
3240
                continue: util.cloneToken(this.tokens.continue),
3241
                loopType: util.cloneToken(this.tokens.loopType)
3242
            })
3243
        );
3244
    }
3245
}
3246

3247
export class TypecastStatement extends Statement {
1✔
3248
    constructor(options: {
3249
        typecast?: Token;
3250
        obj: Token;
3251
        as?: Token;
3252
        type: Token;
3253
    }
3254
    ) {
3255
        super();
5✔
3256
        this.tokens = {
5✔
3257
            typecast: options.typecast,
3258
            obj: options.obj,
3259
            as: options.as,
3260
            type: options.type
3261
        };
3262
        this.range = util.createBoundingRange(
5✔
3263
            this.tokens.typecast,
3264
            this.tokens.obj,
3265
            this.tokens.as,
3266
            this.tokens.type
3267
        );
3268
    }
3269

3270
    public readonly tokens: {
3271
        readonly typecast?: Token;
3272
        readonly obj: Token;
3273
        readonly as?: Token;
3274
        readonly type: Token;
3275
    };
3276

3277
    public readonly typecastExpression: Expression;
3278

3279
    public readonly range: Range;
3280

3281
    transpile(state: BrsTranspileState) {
3282
        return [];
1✔
3283
    }
3284

3285
    walk(visitor: WalkVisitor, options: WalkOptions) {
3286
        //nothing to walk
3287
    }
3288

3289
    public clone() {
3290
        return this.finalizeClone(
×
3291
            new TypecastStatement({
3292
                typecast: util.cloneToken(this.tokens.typecast),
3293
                obj: util.cloneToken(this.tokens.obj),
3294
                as: util.cloneToken(this.tokens.as),
3295
                type: util.cloneToken(this.tokens.type)
3296
            })
3297
        );
3298
    }
3299
}
3300

3301
export class AliasStatement extends Statement {
1✔
3302
    constructor(options: {
3303
        alias?: Token;
3304
        name: Token;
3305
        equals?: Token;
3306
        value: Token;
3307
    }
3308
    ) {
3309
        super();
2✔
3310
        this.tokens = {
2✔
3311
            alias: options.alias,
3312
            name: options.name,
3313
            equals: options.equals,
3314
            value: options.value
3315
        };
3316
        this.range = util.createBoundingRange(
2✔
3317
            this.tokens.alias,
3318
            this.tokens.name,
3319
            this.tokens.equals,
3320
            this.tokens.value
3321
        );
3322
    }
3323

3324
    public readonly tokens: {
3325
        readonly alias?: Token;
3326
        readonly name: Token;
3327
        readonly equals?: Token;
3328
        readonly value: Token;
3329
    };
3330

3331
    public readonly range: Range;
3332

3333
    transpile(state: BrsTranspileState) {
NEW
3334
        return [];
×
3335
    }
3336

3337
    walk(visitor: WalkVisitor, options: WalkOptions) {
3338
        //nothing to walk
3339
    }
3340

3341

3342
    public clone() {
NEW
3343
        return this.finalizeClone(
×
3344
            new AliasStatement({
3345
                alias: util.cloneToken(this.tokens.alias),
3346
                name: util.cloneToken(this.tokens.name),
3347
                equals: util.cloneToken(this.tokens.equals),
3348
                value: util.cloneToken(this.tokens.value)
3349
            })
3350
        );
3351
    }
3352
}
3353

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