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

rokucommunity / brighterscript / #13342

25 Nov 2024 08:44PM UTC coverage: 89.053% (+2.2%) from 86.874%
#13342

push

web-flow
Merge 961502182 into c5674f5d8

7359 of 8712 branches covered (84.47%)

Branch coverage included in aggregate %.

55 of 64 new or added lines in 9 files covered. (85.94%)

544 existing lines in 54 files now uncovered.

9724 of 10471 relevant lines covered (92.87%)

1825.46 hits per line

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

92.01
/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, FunctionExpression, FunctionParameterExpression, LiteralExpression } from './Expression';
5
import { CallExpression, VariableExpression } from './Expression';
1✔
6
import { util } from '../util';
1✔
7
import type { Range } from 'vscode-languageserver';
8
import type { BrsTranspileState } from './BrsTranspileState';
9
import { ParseMode } from './Parser';
1✔
10
import type { WalkVisitor, WalkOptions } from '../astUtils/visitors';
11
import { InternalWalkMode, walk, createVisitor, WalkMode, walkArray } from '../astUtils/visitors';
1✔
12
import { isCallExpression, isCommentStatement, isEnumMemberStatement, isExpression, isExpressionStatement, isFieldStatement, isFunctionExpression, isFunctionStatement, isIfStatement, isInterfaceFieldStatement, isInterfaceMethodStatement, isInvalidType, isLiteralExpression, isMethodStatement, isNamespaceStatement, isTypedefProvider, isUnaryExpression, isVoidType } from '../astUtils/reflection';
1✔
13
import type { TranspileResult, TypedefProvider } from '../interfaces';
14
import { createInvalidLiteral, createMethodStatement, createToken } from '../astUtils/creators';
1✔
15
import { DynamicType } from '../types/DynamicType';
1✔
16
import type { BscType } from '../types/BscType';
17
import type { TranspileState } from './TranspileState';
18
import { SymbolTable } from '../SymbolTable';
1✔
19
import type { AstNode, Expression } from './AstNode';
20
import { Statement } from './AstNode';
1✔
21

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

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

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

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

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

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

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

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

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

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

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

95
            result.push(...statement.transpile(state));
415✔
96
        }
97
        return result;
294✔
98
    }
99

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

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

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

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

141
    public readonly range: Range | undefined;
142

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

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

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

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

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

196
    public readonly range: Range | undefined;
197

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

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

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

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

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

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

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

256
    public readonly range: Range | undefined;
257

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

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

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

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

291
    public range: Range | undefined;
292

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

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

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

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

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

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

343
    public readonly range: Range;
344

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

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

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

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

374
    public readonly range: Range;
375

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

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

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

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

404
    public readonly range: Range | undefined;
405

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

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

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

435
        return this.func.transpile(state, nameToken);
277✔
436
    }
437

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

616
    public readonly range: Range | undefined;
617

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

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

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

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

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

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

675
    public readonly range: Range | undefined;
676

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

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

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

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

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

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

767
        }
768
    }
769

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

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

798
    public readonly range: Range | undefined;
799

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

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

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

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

836
    public readonly range: Range | undefined;
837

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

843
        ];
844
    }
845

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

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

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

874
    public readonly range: Range | undefined;
875

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

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

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

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

917
    public readonly range: Range;
918

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

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

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

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

948
    public readonly range: Range;
949

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

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

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

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

993
    public readonly range: Range | undefined;
994

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

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

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

1037
        return result;
9✔
1038
    }
1039

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

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

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

1092
    public readonly range: Range | undefined;
1093

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

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

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

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

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

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

1173
    public readonly range: Range | undefined;
1174

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

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

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

1200
        return result;
4✔
1201
    }
1202

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

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

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

1245
    public readonly range: Range | undefined;
1246

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

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

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

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

1311
    public readonly range: Range | undefined;
1312

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

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

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

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

1386
    public readonly range: Range | undefined;
1387

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

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

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

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

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

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

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

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

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

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

1471
        return name;
2,720✔
1472
    }
1473

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

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

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

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

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

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

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

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

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

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

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

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

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

1618
    public range: Range | undefined;
1619

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

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

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

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

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

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

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

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

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

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

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

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

1781
    public range: Range | undefined;
1782

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

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

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

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

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

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

1841
}
1842

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

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

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

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

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

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

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

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

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

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

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

1984
        for (let statement of this.body) {
484✔
1985
            if (isMethodStatement(statement)) {
458✔
1986
                this.methods.push(statement);
266✔
1987
                this.memberMap[statement?.name?.text.toLowerCase()] = statement;
266!
1988
            } else if (isFieldStatement(statement)) {
192✔
1989
                this.fields.push(statement);
183✔
1990
                this.memberMap[statement.name?.text.toLowerCase()] = statement;
183!
1991
            }
1992
        }
1993

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

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

2012

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

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

2034
    public readonly range: Range | undefined;
2035

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

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

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

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

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

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

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

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

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

2176
    /**
2177
     * Determine if the specified field was declared in one of the ancestor classes
2178
     */
2179
    public isFieldDeclaredByAncestor(fieldName: string, ancestors: ClassStatement[]) {
UNCOV
2180
        let lowerFieldName = fieldName.toLowerCase();
×
UNCOV
2181
        for (let ancestor of ancestors) {
×
UNCOV
2182
            if (ancestor.memberMap[lowerFieldName]) {
×
UNCOV
2183
                return true;
×
2184
            }
2185
        }
UNCOV
2186
        return false;
×
2187
    }
2188

2189
    /**
2190
     * The builder is a function that assigns all of the methods and property names to a class instance.
2191
     * This needs to be a separate function so that child classes can call the builder from their parent
2192
     * without instantiating the parent constructor at that point in time.
2193
     */
2194
    private getTranspiledBuilder(state: BrsTranspileState) {
2195
        let result = [] as TranspileResult;
44✔
2196
        result.push(`function ${this.getBuilderName(this.getName(ParseMode.BrightScript)!)}()\n`);
44✔
2197
        state.blockDepth++;
44✔
2198
        //indent
2199
        result.push(state.indent());
44✔
2200

2201
        /**
2202
         * The lineage of this class. index 0 is a direct parent, index 1 is index 0's parent, etc...
2203
         */
2204
        let ancestors = this.getAncestors(state);
44✔
2205

2206
        //construct parent class or empty object
2207
        if (ancestors[0]) {
44✔
2208
            const ancestorNamespace = ancestors[0].findAncestor<NamespaceStatement>(isNamespaceStatement);
21✔
2209
            let fullyQualifiedClassName = util.getFullyQualifiedClassName(
21✔
2210
                ancestors[0].getName(ParseMode.BrighterScript)!,
2211
                ancestorNamespace?.getName(ParseMode.BrighterScript)
63✔
2212
            );
2213
            result.push(
21✔
2214
                'instance = ',
2215
                this.getBuilderName(fullyQualifiedClassName), '()');
2216
        } else {
2217
            //use an empty object.
2218
            result.push('instance = {}');
23✔
2219
        }
2220
        result.push(
44✔
2221
            state.newline,
2222
            state.indent()
2223
        );
2224
        let parentClassIndex = this.getParentClassIndex(state);
44✔
2225

2226
        let body = this.body;
44✔
2227
        //inject an empty "new" method if missing
2228
        if (!this.getConstructorFunction()) {
44✔
2229
            body = [
24✔
2230
                createMethodStatement('new', TokenKind.Sub),
2231
                ...this.body
2232
            ];
2233
        }
2234

2235
        for (let statement of body) {
44✔
2236
            //is field statement
2237
            if (isFieldStatement(statement)) {
67✔
2238
                //do nothing with class fields in this situation, they are handled elsewhere
2239
                continue;
11✔
2240

2241
                //methods
2242
            } else if (isMethodStatement(statement)) {
56!
2243

2244
                //store overridden parent methods as super{parentIndex}_{methodName}
2245
                if (
56✔
2246
                    //is override method
2247
                    statement.override ||
152✔
2248
                    //is constructor function in child class
2249
                    (statement.name.text.toLowerCase() === 'new' && ancestors[0])
2250
                ) {
2251
                    result.push(
25✔
2252
                        `instance.super${parentClassIndex}_${statement.name.text} = instance.${statement.name.text}`,
2253
                        state.newline,
2254
                        state.indent()
2255
                    );
2256
                }
2257

2258
                state.classStatement = this;
56✔
2259
                result.push(
56✔
2260
                    'instance.',
2261
                    state.transpileToken(statement.name),
2262
                    ' = ',
2263
                    ...statement.transpile(state),
2264
                    state.newline,
2265
                    state.indent()
2266
                );
2267
                delete state.classStatement;
56✔
2268
            } else {
2269
                //other random statements (probably just comments)
UNCOV
2270
                result.push(
×
2271
                    ...statement.transpile(state),
2272
                    state.newline,
2273
                    state.indent()
2274
                );
2275
            }
2276
        }
2277
        //return the instance
2278
        result.push('return instance\n');
44✔
2279
        state.blockDepth--;
44✔
2280
        result.push(state.indent());
44✔
2281
        result.push(`end function`);
44✔
2282
        return result;
44✔
2283
    }
2284

2285
    /**
2286
     * The class function is the function with the same name as the class. This is the function that
2287
     * consumers should call to create a new instance of that class.
2288
     * This invokes the builder, gets an instance of the class, then invokes the "new" function on that class.
2289
     */
2290
    private getTranspiledClassFunction(state: BrsTranspileState) {
2291
        let result = [] as TranspileResult;
44✔
2292
        const constructorFunction = this.getConstructorFunction();
44✔
2293
        const constructorParams = constructorFunction ? constructorFunction.func.parameters : [];
44✔
2294

2295
        result.push(
44✔
2296
            state.sourceNode(this.classKeyword, 'function'),
2297
            state.sourceNode(this.classKeyword, ' '),
2298
            state.sourceNode(this.name, this.getName(ParseMode.BrightScript)!),
2299
            `(`
2300
        );
2301
        let i = 0;
44✔
2302
        for (let param of constructorParams) {
44✔
2303
            if (i > 0) {
8✔
2304
                result.push(', ');
2✔
2305
            }
2306
            result.push(
8✔
2307
                param.transpile(state)
2308
            );
2309
            i++;
8✔
2310
        }
2311
        result.push(
44✔
2312
            ')',
2313
            '\n'
2314
        );
2315

2316
        state.blockDepth++;
44✔
2317
        result.push(state.indent());
44✔
2318
        result.push(`instance = ${this.getBuilderName(this.getName(ParseMode.BrightScript)!)}()\n`);
44✔
2319

2320
        result.push(state.indent());
44✔
2321
        result.push(`instance.new(`);
44✔
2322

2323
        //append constructor arguments
2324
        i = 0;
44✔
2325
        for (let param of constructorParams) {
44✔
2326
            if (i > 0) {
8✔
2327
                result.push(', ');
2✔
2328
            }
2329
            result.push(
8✔
2330
                state.transpileToken(param.name)
2331
            );
2332
            i++;
8✔
2333
        }
2334
        result.push(
44✔
2335
            ')',
2336
            '\n'
2337
        );
2338

2339
        result.push(state.indent());
44✔
2340
        result.push(`return instance\n`);
44✔
2341

2342
        state.blockDepth--;
44✔
2343
        result.push(state.indent());
44✔
2344
        result.push(`end function`);
44✔
2345
        return result;
44✔
2346
    }
2347

2348
    walk(visitor: WalkVisitor, options: WalkOptions) {
2349
        //visitor-less walk function to do parent linking
2350
        walk(this, 'parentClassName', null, options);
955✔
2351

2352
        if (options.walkMode & InternalWalkMode.walkStatements) {
955!
2353
            walkArray(this.body, visitor, options, this);
955✔
2354
        }
2355
    }
2356

2357
    public clone() {
2358
        return this.finalizeClone(
11✔
2359
            new ClassStatement(
2360
                util.cloneToken(this.classKeyword),
2361
                util.cloneToken(this.name),
2362
                this.body?.map(x => x?.clone()),
10✔
2363
                util.cloneToken(this.end),
2364
                util.cloneToken(this.extendsKeyword),
2365
                this.parentClassName?.clone()
33✔
2366
            ),
2367
            ['body', 'parentClassName']
2368
        );
2369
    }
2370
}
2371

2372
const accessModifiers = [
1✔
2373
    TokenKind.Public,
2374
    TokenKind.Protected,
2375
    TokenKind.Private
2376
];
2377
export class MethodStatement extends FunctionStatement {
1✔
2378
    constructor(
2379
        modifiers: Token | Token[],
2380
        name: Identifier,
2381
        func: FunctionExpression,
2382
        public override: Token
301✔
2383
    ) {
2384
        super(name, func);
301✔
2385
        if (modifiers) {
301✔
2386
            if (Array.isArray(modifiers)) {
35✔
2387
                this.modifiers.push(...modifiers);
4✔
2388
            } else {
2389
                this.modifiers.push(modifiers);
31✔
2390
            }
2391
        }
2392
        this.range = util.createBoundingRange(
301✔
2393
            ...(this.modifiers),
2394
            override,
2395
            func
2396
        );
2397
    }
2398

2399
    public modifiers: Token[] = [];
301✔
2400

2401
    public get accessModifier() {
2402
        return this.modifiers.find(x => accessModifiers.includes(x.kind));
100✔
2403
    }
2404

2405
    public readonly range: Range | undefined;
2406

2407
    /**
2408
     * Get the name of this method.
2409
     */
2410
    public getName(parseMode: ParseMode) {
2411
        return this.name.text;
1✔
2412
    }
2413

2414
    transpile(state: BrsTranspileState) {
2415
        if (this.name.text.toLowerCase() === 'new') {
56✔
2416
            this.ensureSuperConstructorCall(state);
44✔
2417
            //TODO we need to undo this at the bottom of this method
2418
            this.injectFieldInitializersForConstructor(state);
44✔
2419
        }
2420
        //TODO - remove type information from these methods because that doesn't work
2421
        //convert the `super` calls into the proper methods
2422
        const parentClassIndex = state.classStatement.getParentClassIndex(state);
56✔
2423
        const visitor = createVisitor({
56✔
2424
            VariableExpression: e => {
2425
                if (e.name.text.toLocaleLowerCase() === 'super') {
55✔
2426
                    state.editor.setProperty(e.name, 'text', `m.super${parentClassIndex}_new`);
21✔
2427
                }
2428
            },
2429
            DottedGetExpression: e => {
2430
                const beginningVariable = util.findBeginningVariableExpression(e);
25✔
2431
                const lowerName = beginningVariable?.getName(ParseMode.BrighterScript).toLowerCase();
25!
2432
                if (lowerName === 'super') {
25✔
2433
                    state.editor.setProperty(beginningVariable.name, 'text', 'm');
7✔
2434
                    state.editor.setProperty(e.name, 'text', `super${parentClassIndex}_${e.name.text}`);
7✔
2435
                }
2436
            }
2437
        });
2438
        const walkOptions: WalkOptions = { walkMode: WalkMode.visitExpressions };
56✔
2439
        for (const statement of this.func.body.statements) {
56✔
2440
            visitor(statement, undefined);
60✔
2441
            statement.walk(visitor, walkOptions);
60✔
2442
        }
2443
        return this.func.transpile(state);
56✔
2444
    }
2445

2446
    getTypedef(state: BrsTranspileState) {
2447
        const result = [] as TranspileResult;
23✔
2448
        for (let annotation of this.annotations ?? []) {
23✔
2449
            result.push(
2✔
2450
                ...annotation.getTypedef(state),
2451
                state.newline,
2452
                state.indent()
2453
            );
2454
        }
2455
        if (this.accessModifier) {
23✔
2456
            result.push(
8✔
2457
                this.accessModifier.text,
2458
                ' '
2459
            );
2460
        }
2461
        if (this.override) {
23✔
2462
            result.push('override ');
1✔
2463
        }
2464
        result.push(
23✔
2465
            ...this.func.getTypedef(state)
2466
        );
2467
        return result;
23✔
2468
    }
2469

2470
    /**
2471
     * All child classes must call the parent constructor. The type checker will warn users when they don't call it in their own class,
2472
     * but we still need to call it even if they have omitted it. This injects the super call if it's missing
2473
     */
2474
    private ensureSuperConstructorCall(state: BrsTranspileState) {
2475
        //if this class doesn't extend another class, quit here
2476
        if (state.classStatement!.getAncestors(state).length === 0) {
44✔
2477
            return;
23✔
2478
        }
2479

2480
        //check whether any calls to super exist
2481
        let containsSuperCall =
2482
            this.func.body.statements.findIndex((x) => {
21✔
2483
                //is a call statement
2484
                return isExpressionStatement(x) && isCallExpression(x.expression) &&
9✔
2485
                    //is a call to super
2486
                    util.findBeginningVariableExpression(x.expression.callee as any)?.name.text.toLowerCase() === 'super';
21!
2487
            }) !== -1;
2488

2489
        //if a call to super exists, quit here
2490
        if (containsSuperCall) {
21✔
2491
            return;
7✔
2492
        }
2493

2494
        //this is a child class, and the constructor doesn't contain a call to super. Inject one
2495
        const superCall = new ExpressionStatement(
14✔
2496
            new CallExpression(
2497
                new VariableExpression(
2498
                    {
2499
                        kind: TokenKind.Identifier,
2500
                        text: 'super',
2501
                        isReserved: false,
2502
                        range: state.classStatement!.name.range,
2503
                        leadingWhitespace: ''
2504
                    }
2505
                ),
2506
                {
2507
                    kind: TokenKind.LeftParen,
2508
                    text: '(',
2509
                    isReserved: false,
2510
                    range: state.classStatement!.name.range,
2511
                    leadingWhitespace: ''
2512
                },
2513
                {
2514
                    kind: TokenKind.RightParen,
2515
                    text: ')',
2516
                    isReserved: false,
2517
                    range: state.classStatement!.name.range,
2518
                    leadingWhitespace: ''
2519
                },
2520
                []
2521
            )
2522
        );
2523
        state.editor.arrayUnshift(this.func.body.statements, superCall);
14✔
2524
    }
2525

2526
    /**
2527
     * Inject field initializers at the top of the `new` function (after any present `super()` call)
2528
     */
2529
    private injectFieldInitializersForConstructor(state: BrsTranspileState) {
2530
        let startingIndex = state.classStatement!.hasParentClass() ? 1 : 0;
44✔
2531

2532
        let newStatements = [] as Statement[];
44✔
2533
        //insert the field initializers in order
2534
        for (let field of state.classStatement!.fields) {
44✔
2535
            let thisQualifiedName = { ...field.name };
11✔
2536
            thisQualifiedName.text = 'm.' + field.name?.text;
11!
2537
            if (field.initialValue) {
11✔
2538
                newStatements.push(
9✔
2539
                    new AssignmentStatement(field.equal, thisQualifiedName, field.initialValue)
2540
                );
2541
            } else {
2542
                //if there is no initial value, set the initial value to `invalid`
2543
                newStatements.push(
2✔
2544
                    new AssignmentStatement(
2545
                        createToken(TokenKind.Equal, '=', field.name?.range),
6!
2546
                        thisQualifiedName,
2547
                        createInvalidLiteral('invalid', field.name?.range)
6!
2548
                    )
2549
                );
2550
            }
2551
        }
2552
        state.editor.arraySplice(this.func.body.statements, startingIndex, 0, ...newStatements);
44✔
2553
    }
2554

2555
    walk(visitor: WalkVisitor, options: WalkOptions) {
2556
        if (options.walkMode & InternalWalkMode.walkExpressions) {
786✔
2557
            walk(this, 'func', visitor, options);
581✔
2558
        }
2559
    }
2560

2561
    public clone() {
2562
        return this.finalizeClone(
5✔
2563
            new MethodStatement(
2564
                this.modifiers?.map(m => util.cloneToken(m)),
1✔
2565
                util.cloneToken(this.name),
2566
                this.func?.clone(),
15✔
2567
                util.cloneToken(this.override)
2568
            ),
2569
            ['func']
2570
        );
2571
    }
2572
}
2573
/**
2574
 * @deprecated use `MethodStatement`
2575
 */
2576
export class ClassMethodStatement extends MethodStatement { }
1✔
2577

2578
export class FieldStatement extends Statement implements TypedefProvider {
1✔
2579

2580
    constructor(
2581
        readonly accessModifier?: Token,
183✔
2582
        readonly name?: Identifier,
183✔
2583
        readonly as?: Token,
183✔
2584
        readonly type?: Token,
183✔
2585
        readonly equal?: Token,
183✔
2586
        readonly initialValue?: Expression,
183✔
2587
        readonly optional?: Token
183✔
2588
    ) {
2589
        super();
183✔
2590
        this.range = util.createBoundingRange(
183✔
2591
            accessModifier,
2592
            name,
2593
            as,
2594
            type,
2595
            equal,
2596
            initialValue
2597
        );
2598
    }
2599

2600
    /**
2601
     * Derive a ValueKind from the type token, or the initial value.
2602
     * Defaults to `DynamicType`
2603
     */
2604
    getType() {
2605
        if (this.type) {
76✔
2606
            return util.tokenToBscType(this.type);
37✔
2607
        } else if (isLiteralExpression(this.initialValue)) {
39✔
2608
            return this.initialValue.type;
25✔
2609
        } else {
2610
            return new DynamicType();
14✔
2611
        }
2612
    }
2613

2614
    public readonly range: Range | undefined;
2615

2616
    public get isOptional() {
2617
        return !!this.optional;
17✔
2618
    }
2619

2620
    transpile(state: BrsTranspileState): TranspileResult {
UNCOV
2621
        throw new Error('transpile not implemented for ' + Object.getPrototypeOf(this).constructor.name);
×
2622
    }
2623

2624
    getTypedef(state: BrsTranspileState) {
2625
        const result = [] as TranspileResult;
10✔
2626
        if (this.name) {
10!
2627
            for (let annotation of this.annotations ?? []) {
10✔
2628
                result.push(
2✔
2629
                    ...annotation.getTypedef(state),
2630
                    state.newline,
2631
                    state.indent()
2632
                );
2633
            }
2634

2635
            let type = this.getType();
10✔
2636
            if (isInvalidType(type) || isVoidType(type)) {
10✔
2637
                type = new DynamicType();
1✔
2638
            }
2639

2640
            result.push(
10✔
2641
                this.accessModifier?.text ?? 'public',
60!
2642
                ' '
2643
            );
2644
            if (this.isOptional) {
10!
UNCOV
2645
                result.push(this.optional!.text, ' ');
×
2646
            }
2647
            result.push(this.name?.text,
10!
2648
                ' as ',
2649
                type.toTypeString()
2650
            );
2651
        }
2652
        return result;
10✔
2653
    }
2654

2655
    walk(visitor: WalkVisitor, options: WalkOptions) {
2656
        if (this.initialValue && options.walkMode & InternalWalkMode.walkExpressions) {
253✔
2657
            walk(this, 'initialValue', visitor, options);
71✔
2658
        }
2659
    }
2660

2661
    public clone() {
2662
        return this.finalizeClone(
4✔
2663
            new FieldStatement(
2664
                util.cloneToken(this.accessModifier),
2665
                util.cloneToken(this.name),
2666
                util.cloneToken(this.as),
2667
                util.cloneToken(this.type),
2668
                util.cloneToken(this.equal),
2669
                this.initialValue?.clone(),
12✔
2670
                util.cloneToken(this.optional)
2671
            ),
2672
            ['initialValue']
2673
        );
2674
    }
2675
}
2676

2677
/**
2678
 * @deprecated use `FieldStatement`
2679
 */
2680
export class ClassFieldStatement extends FieldStatement { }
1✔
2681

2682
export type MemberStatement = FieldStatement | MethodStatement;
2683

2684
/**
2685
 * @deprecated use `MemeberStatement`
2686
 */
2687
export type ClassMemberStatement = MemberStatement;
2688

2689
export class TryCatchStatement extends Statement {
1✔
2690
    constructor(
2691
        public tokens: {
31✔
2692
            try: Token;
2693
            endTry?: Token;
2694
        },
2695
        public tryBranch?: Block,
31✔
2696
        public catchStatement?: CatchStatement
31✔
2697
    ) {
2698
        super();
31✔
2699
        this.range = util.createBoundingRange(
31✔
2700
            tokens.try,
2701
            tryBranch,
2702
            catchStatement,
2703
            tokens.endTry
2704
        );
2705
    }
2706

2707
    public readonly range: Range | undefined;
2708

2709
    public transpile(state: BrsTranspileState): TranspileResult {
2710
        return [
3✔
2711
            state.transpileToken(this.tokens.try),
2712
            ...this.tryBranch!.transpile(state),
2713
            state.newline,
2714
            state.indent(),
2715
            ...(this.catchStatement?.transpile(state) ?? ['catch']),
18!
2716
            state.newline,
2717
            state.indent(),
2718
            state.transpileToken(this.tokens.endTry!)
2719
        ] as TranspileResult;
2720
    }
2721

2722
    public walk(visitor: WalkVisitor, options: WalkOptions) {
2723
        if (this.tryBranch && options.walkMode & InternalWalkMode.walkStatements) {
43✔
2724
            walk(this, 'tryBranch', visitor, options);
42✔
2725
            walk(this, 'catchStatement', visitor, options);
42✔
2726
        }
2727
    }
2728

2729
    public clone() {
2730
        return this.finalizeClone(
3✔
2731
            new TryCatchStatement(
2732
                {
2733
                    try: util.cloneToken(this.tokens.try),
2734
                    endTry: util.cloneToken(this.tokens.endTry)
2735
                },
2736
                this.tryBranch?.clone(),
9✔
2737
                this.catchStatement?.clone()
9✔
2738
            ),
2739
            ['tryBranch', 'catchStatement']
2740
        );
2741
    }
2742
}
2743

2744
export class CatchStatement extends Statement {
1✔
2745
    constructor(
2746
        public tokens: {
28✔
2747
            catch: Token;
2748
        },
2749
        public exceptionVariable?: Identifier,
28✔
2750
        public catchBranch?: Block
28✔
2751
    ) {
2752
        super();
28✔
2753
        this.range = util.createBoundingRange(
28✔
2754
            tokens.catch,
2755
            exceptionVariable,
2756
            catchBranch
2757
        );
2758
    }
2759

2760
    public range: Range | undefined;
2761

2762
    public transpile(state: BrsTranspileState): TranspileResult {
2763
        return [
3✔
2764
            state.transpileToken(this.tokens.catch),
2765
            ' ',
2766
            this.exceptionVariable?.text ?? 'e',
18!
2767
            ...(this.catchBranch?.transpile(state) ?? [])
18!
2768
        ];
2769
    }
2770

2771
    public walk(visitor: WalkVisitor, options: WalkOptions) {
2772
        if (this.catchBranch && options.walkMode & InternalWalkMode.walkStatements) {
39✔
2773
            walk(this, 'catchBranch', visitor, options);
38✔
2774
        }
2775
    }
2776

2777
    public clone() {
2778
        return this.finalizeClone(
2✔
2779
            new CatchStatement(
2780
                {
2781
                    catch: util.cloneToken(this.tokens.catch)
2782
                },
2783
                util.cloneToken(this.exceptionVariable),
2784
                this.catchBranch?.clone()
6✔
2785
            ),
2786
            ['catchBranch']
2787
        );
2788
    }
2789
}
2790

2791
export class ThrowStatement extends Statement {
1✔
2792
    constructor(
2793
        public throwToken: Token,
12✔
2794
        public expression?: Expression
12✔
2795
    ) {
2796
        super();
12✔
2797
        this.range = util.createBoundingRange(
12✔
2798
            throwToken,
2799
            expression
2800
        );
2801
    }
2802
    public range: Range | undefined;
2803

2804
    public transpile(state: BrsTranspileState) {
2805
        const result = [
4✔
2806
            state.transpileToken(this.throwToken),
2807
            ' '
2808
        ] as TranspileResult;
2809

2810
        //if we have an expression, transpile it
2811
        if (this.expression) {
4!
2812
            result.push(
4✔
2813
                ...this.expression.transpile(state)
2814
            );
2815

2816
            //no expression found. Rather than emit syntax errors, provide a generic error message
2817
        } else {
UNCOV
2818
            result.push('"An error has occurred"');
×
2819
        }
2820
        return result;
4✔
2821
    }
2822

2823
    public walk(visitor: WalkVisitor, options: WalkOptions) {
2824
        if (this.expression && options.walkMode & InternalWalkMode.walkExpressions) {
23✔
2825
            walk(this, 'expression', visitor, options);
16✔
2826
        }
2827
    }
2828

2829
    public clone() {
2830
        return this.finalizeClone(
2✔
2831
            new ThrowStatement(
2832
                util.cloneToken(this.throwToken),
2833
                this.expression?.clone()
6✔
2834
            ),
2835
            ['expression']
2836
        );
2837
    }
2838
}
2839

2840

2841
export class EnumStatement extends Statement implements TypedefProvider {
1✔
2842

2843
    constructor(
2844
        public tokens: {
120✔
2845
            enum: Token;
2846
            name: Identifier;
2847
            endEnum: Token;
2848
        },
2849
        public body: Array<EnumMemberStatement | CommentStatement>
120✔
2850
    ) {
2851
        super();
120✔
2852
        this.body = this.body ?? [];
120✔
2853
    }
2854

2855
    public get range(): Range | undefined {
2856
        return util.createBoundingRange(
15✔
2857
            this.tokens.enum,
2858
            this.tokens.name,
2859
            ...this.body,
2860
            this.tokens.endEnum
2861
        );
2862
    }
2863

2864
    /**
2865
     * Get the name of the wrapping namespace (if it exists)
2866
     * @deprecated use `.findAncestor(isNamespaceStatement)` instead.
2867
     */
2868
    public get namespaceName() {
UNCOV
2869
        return this.findAncestor<NamespaceStatement>(isNamespaceStatement)?.nameExpression;
×
2870
    }
2871

2872
    public getMembers() {
2873
        const result = [] as EnumMemberStatement[];
140✔
2874
        for (const statement of this.body) {
140✔
2875
            if (isEnumMemberStatement(statement)) {
304✔
2876
                result.push(statement);
291✔
2877
            }
2878
        }
2879
        return result;
140✔
2880
    }
2881

2882
    /**
2883
     * Get a map of member names and their values.
2884
     * All values are stored as their AST LiteralExpression representation (i.e. string enum values include the wrapping quotes)
2885
     */
2886
    public getMemberValueMap() {
2887
        const result = new Map<string, string>();
55✔
2888
        const members = this.getMembers();
55✔
2889
        let currentIntValue = 0;
55✔
2890
        for (const member of members) {
55✔
2891
            //if there is no value, assume an integer and increment the int counter
2892
            if (!member.value) {
141✔
2893
                result.set(member.name?.toLowerCase(), currentIntValue.toString());
32!
2894
                currentIntValue++;
32✔
2895

2896
                //if explicit integer value, use it and increment the int counter
2897
            } else if (isLiteralExpression(member.value) && member.value.token.kind === TokenKind.IntegerLiteral) {
109✔
2898
                //try parsing as integer literal, then as hex integer literal.
2899
                let tokenIntValue = util.parseInt(member.value.token.text) ?? util.parseInt(member.value.token.text.replace(/&h/i, '0x'));
31✔
2900
                if (tokenIntValue !== undefined) {
31!
2901
                    currentIntValue = tokenIntValue;
31✔
2902
                    currentIntValue++;
31✔
2903
                }
2904
                result.set(member.name?.toLowerCase(), member.value.token.text);
31!
2905

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

2910
                //all other values
2911
            } else {
2912
                result.set(member.name?.toLowerCase(), (member.value as LiteralExpression)?.token?.text ?? 'invalid');
77!
2913
            }
2914
        }
2915
        return result;
55✔
2916
    }
2917

2918
    public getMemberValue(name: string) {
2919
        return this.getMemberValueMap().get(name.toLowerCase());
52✔
2920
    }
2921

2922
    /**
2923
     * The name of the enum (without the namespace prefix)
2924
     */
2925
    public get name() {
2926
        return this.tokens.name?.text;
16!
2927
    }
2928

2929
    /**
2930
     * The name of the enum WITH its leading namespace (if applicable)
2931
     */
2932
    public get fullName() {
2933
        const name = this.tokens.name?.text;
293!
2934
        if (name) {
293!
2935
            const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
293✔
2936

2937
            if (namespace) {
293✔
2938
                let namespaceName = namespace.getName(ParseMode.BrighterScript);
143✔
2939
                return `${namespaceName}.${name}`;
143✔
2940
            } else {
2941
                return name;
150✔
2942
            }
2943
        } else {
2944
            //return undefined which will allow outside callers to know that this doesn't have a name
UNCOV
2945
            return undefined;
×
2946
        }
2947
    }
2948

2949
    transpile(state: BrsTranspileState) {
2950
        //enum declarations do not exist at runtime, so don't transpile anything...
2951
        return [];
23✔
2952
    }
2953

2954
    getTypedef(state: BrsTranspileState) {
2955
        const result = [] as TranspileResult;
1✔
2956
        for (let annotation of this.annotations ?? []) {
1!
UNCOV
2957
            result.push(
×
2958
                ...annotation.getTypedef(state),
2959
                state.newline,
2960
                state.indent()
2961
            );
2962
        }
2963
        result.push(
1✔
2964
            this.tokens.enum.text ?? 'enum',
3!
2965
            ' ',
2966
            this.tokens.name.text
2967
        );
2968
        result.push(state.newline);
1✔
2969
        state.blockDepth++;
1✔
2970
        for (const member of this.body) {
1✔
2971
            if (isTypedefProvider(member)) {
1!
2972
                result.push(
1✔
2973
                    state.indent(),
2974
                    ...member.getTypedef(state),
2975
                    state.newline
2976
                );
2977
            }
2978
        }
2979
        state.blockDepth--;
1✔
2980
        result.push(
1✔
2981
            state.indent(),
2982
            this.tokens.endEnum.text ?? 'end enum'
3!
2983
        );
2984
        return result;
1✔
2985
    }
2986

2987
    walk(visitor: WalkVisitor, options: WalkOptions) {
2988
        if (options.walkMode & InternalWalkMode.walkStatements) {
350!
2989
            walkArray(this.body, visitor, options, this);
350✔
2990

2991
        }
2992
    }
2993

2994
    public clone() {
2995
        return this.finalizeClone(
6✔
2996
            new EnumStatement(
2997
                {
2998
                    enum: util.cloneToken(this.tokens.enum),
2999
                    name: util.cloneToken(this.tokens.name),
3000
                    endEnum: util.cloneToken(this.tokens.endEnum)
3001
                },
3002
                this.body?.map(x => x?.clone())
6✔
3003
            ),
3004
            ['body']
3005
        );
3006
    }
3007
}
3008

3009
export class EnumMemberStatement extends Statement implements TypedefProvider {
1✔
3010

3011
    public constructor(
3012
        public tokens: {
201✔
3013
            name: Identifier;
3014
            equal?: Token;
3015
        },
3016
        public value?: Expression
201✔
3017
    ) {
3018
        super();
201✔
3019
    }
3020

3021
    /**
3022
     * The name of the member
3023
     */
3024
    public get name() {
3025
        return this.tokens.name.text;
551✔
3026
    }
3027

3028
    public get range() {
3029
        return util.createBoundingRange(
66✔
3030
            this.tokens.name,
3031
            this.tokens.equal,
3032
            this.value
3033
        );
3034
    }
3035

3036
    /**
3037
     * Get the value of this enum. Requires that `.parent` is set
3038
     */
3039
    public getValue() {
3040
        return (this.parent as EnumStatement).getMemberValue(this.name);
52✔
3041
    }
3042

3043
    public transpile(state: BrsTranspileState): TranspileResult {
UNCOV
3044
        return [];
×
3045
    }
3046

3047
    getTypedef(state: BrsTranspileState): TranspileResult {
3048
        const result = [
1✔
3049
            this.tokens.name.text
3050
        ] as TranspileResult;
3051
        if (this.tokens.equal) {
1!
UNCOV
3052
            result.push(' ', this.tokens.equal.text, ' ');
×
UNCOV
3053
            if (this.value) {
×
UNCOV
3054
                result.push(
×
3055
                    ...this.value.transpile(state)
3056
                );
3057
            }
3058
        }
3059
        return result;
1✔
3060
    }
3061

3062
    walk(visitor: WalkVisitor, options: WalkOptions) {
3063
        if (this.value && options.walkMode & InternalWalkMode.walkExpressions) {
475✔
3064
            walk(this, 'value', visitor, options);
290✔
3065
        }
3066
    }
3067

3068
    public clone() {
3069
        return this.finalizeClone(
3✔
3070
            new EnumMemberStatement(
3071
                {
3072
                    name: util.cloneToken(this.tokens.name),
3073
                    equal: util.cloneToken(this.tokens.equal)
3074
                },
3075
                this.value?.clone()
9✔
3076
            ),
3077
            ['value']
3078
        );
3079
    }
3080
}
3081

3082
export class ConstStatement extends Statement implements TypedefProvider {
1✔
3083

3084
    public constructor(
3085
        public tokens: {
57✔
3086
            const: Token;
3087
            name: Identifier;
3088
            equals: Token;
3089
        },
3090
        public value: Expression
57✔
3091
    ) {
3092
        super();
57✔
3093
        this.range = util.createBoundingRange(this.tokens.const, this.tokens.name, this.tokens.equals, this.value);
57✔
3094
    }
3095

3096
    public range: Range | undefined;
3097

3098
    public get name() {
3099
        return this.tokens.name.text;
5✔
3100
    }
3101

3102
    /**
3103
     * The name of the statement WITH its leading namespace (if applicable)
3104
     */
3105
    public get fullName() {
3106
        const name = this.tokens.name?.text;
75!
3107
        if (name) {
75!
3108
            const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
75✔
3109
            if (namespace) {
75✔
3110
                let namespaceName = namespace.getName(ParseMode.BrighterScript);
56✔
3111
                return `${namespaceName}.${name}`;
56✔
3112
            } else {
3113
                return name;
19✔
3114
            }
3115
        } else {
3116
            //return undefined which will allow outside callers to know that this doesn't have a name
UNCOV
3117
            return undefined;
×
3118
        }
3119
    }
3120

3121
    public transpile(state: BrsTranspileState): TranspileResult {
3122
        //const declarations don't exist at runtime, so just transpile empty
3123
        return [];
13✔
3124
    }
3125

3126
    getTypedef(state: BrsTranspileState): TranspileResult {
3127
        return [
3✔
3128
            state.tokenToSourceNode(this.tokens.const),
3129
            ' ',
3130
            state.tokenToSourceNode(this.tokens.name),
3131
            ' ',
3132
            state.tokenToSourceNode(this.tokens.equals),
3133
            ' ',
3134
            ...this.value.transpile(state)
3135
        ];
3136
    }
3137

3138
    walk(visitor: WalkVisitor, options: WalkOptions) {
3139
        if (this.value && options.walkMode & InternalWalkMode.walkExpressions) {
122✔
3140
            walk(this, 'value', visitor, options);
120✔
3141
        }
3142
    }
3143

3144
    public clone() {
3145
        return this.finalizeClone(
3✔
3146
            new ConstStatement(
3147
                {
3148
                    const: util.cloneToken(this.tokens.const),
3149
                    name: util.cloneToken(this.tokens.name),
3150
                    equals: util.cloneToken(this.tokens.equals)
3151
                },
3152
                this.value?.clone()
9✔
3153
            ),
3154
            ['value']
3155
        );
3156
    }
3157
}
3158

3159
export class ContinueStatement extends Statement {
1✔
3160
    constructor(
3161
        public tokens: {
13✔
3162
            continue: Token;
3163
            loopType: Token;
3164
        }
3165
    ) {
3166
        super();
13✔
3167
        this.range = util.createBoundingRange(
13✔
3168
            tokens.continue,
3169
            tokens.loopType
3170
        );
3171
    }
3172

3173
    public range: Range | undefined;
3174

3175
    transpile(state: BrsTranspileState) {
3176
        return [
3✔
3177
            state.sourceNode(this.tokens.continue, this.tokens.continue?.text ?? 'continue'),
18!
3178
            this.tokens.loopType?.leadingWhitespace ?? ' ',
18✔
3179
            state.sourceNode(this.tokens.continue, this.tokens.loopType?.text)
9✔
3180
        ];
3181
    }
3182

3183
    walk(visitor: WalkVisitor, options: WalkOptions) {
3184
        //nothing to walk
3185
    }
3186

3187
    public clone() {
3188
        return this.finalizeClone(
1✔
3189
            new ContinueStatement({
3190
                continue: util.cloneToken(this.tokens.continue),
3191
                loopType: util.cloneToken(this.tokens.loopType)
3192
            })
3193
        );
3194
    }
3195
}
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