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

rokucommunity / brighterscript / #15046

03 Oct 2022 01:55PM UTC coverage: 87.532% (-0.3%) from 87.808%
#15046

push

TwitchBronBron
0.59.0

5452 of 6706 branches covered (81.3%)

Branch coverage included in aggregate %.

8259 of 8958 relevant lines covered (92.2%)

1521.92 hits per line

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

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

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

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

42
/**
43
 * This is a top-level statement. Consider this the root of the AST
44
 */
45
export class Body extends Statement implements TypedefProvider {
1✔
46
    constructor(
47
        public statements: Statement[] = []
3,391✔
48
    ) {
49
        super();
3,391✔
50
    }
51

52
    public symbolTable = new SymbolTable();
3,391✔
53

54
    public get range() {
55
        return util.createRangeFromPositions(
8✔
56
            this.statements[0]?.range.start ?? Position.create(0, 0),
48!
57
            this.statements[this.statements.length - 1]?.range.end ?? Position.create(0, 0)
48!
58
        );
59
    }
60

61
    transpile(state: BrsTranspileState) {
62
        let result = [] as TranspileResult;
224✔
63
        for (let i = 0; i < this.statements.length; i++) {
224✔
64
            let statement = this.statements[i];
302✔
65
            let previousStatement = this.statements[i - 1];
302✔
66
            let nextStatement = this.statements[i + 1];
302✔
67

68
            if (!previousStatement) {
302✔
69
                //this is the first statement. do nothing related to spacing and newlines
70

71
                //if comment is on same line as prior sibling
72
            } else if (isCommentStatement(statement) && previousStatement && statement.range.start.line === previousStatement.range.end.line) {
80✔
73
                result.push(
3✔
74
                    ' '
75
                );
76

77
                //add double newline if this is a comment, and next is a function
78
            } else if (isCommentStatement(statement) && nextStatement && isFunctionStatement(nextStatement)) {
77✔
79
                result.push('\n\n');
1✔
80

81
                //add double newline if is function not preceeded by a comment
82
            } else if (isFunctionStatement(statement) && previousStatement && !(isCommentStatement(previousStatement))) {
76✔
83
                result.push('\n\n');
43✔
84
            } else {
85
                //separate statements by a single newline
86
                result.push('\n');
33✔
87
            }
88

89
            result.push(...statement.transpile(state));
302✔
90
        }
91
        return result;
224✔
92
    }
93

94
    getTypedef(state: BrsTranspileState) {
95
        let result = [];
27✔
96
        for (const statement of this.statements) {
27✔
97
            //if the current statement supports generating typedef, call it
98
            if (isTypedefProvider(statement)) {
37!
99
                result.push(
37✔
100
                    state.indent(),
101
                    ...statement.getTypedef(state),
102
                    state.newline
103
                );
104
            }
105
        }
106
        return result;
27✔
107
    }
108

109
    walk(visitor: WalkVisitor, options: WalkOptions) {
110
        if (options.walkMode & InternalWalkMode.walkStatements) {
774!
111
            walkArray(this.statements, visitor, options, this);
774✔
112
        }
113
    }
114
}
115

116
export class AssignmentStatement extends Statement {
1✔
117
    constructor(
118
        readonly equals: Token,
811✔
119
        readonly name: Identifier,
811✔
120
        readonly value: Expression,
811✔
121
        readonly containingFunction: FunctionExpression
811✔
122
    ) {
123
        super();
811✔
124
        this.range = util.createRangeFromPositions(this.name.range.start, this.value.range.end);
811✔
125
    }
126

127
    public readonly range: Range;
128

129
    transpile(state: BrsTranspileState) {
130
        //if the value is a compound assignment, just transpile the expression itself
131
        if (CompoundAssignmentOperators.includes((this.value as BinaryExpression)?.operator?.kind)) {
202!
132
            return this.value.transpile(state);
20✔
133
        } else {
134
            return [
182✔
135
                state.transpileToken(this.name),
136
                ' ',
137
                state.transpileToken(this.equals),
138
                ' ',
139
                ...this.value.transpile(state)
140
            ];
141
        }
142
    }
143

144
    walk(visitor: WalkVisitor, options: WalkOptions) {
145
        if (options.walkMode & InternalWalkMode.walkExpressions) {
748✔
146
            walk(this, 'value', visitor, options);
373✔
147
        }
148
    }
149
}
150

151
export class Block extends Statement {
1✔
152
    constructor(
153
        readonly statements: Statement[],
1,651✔
154
        readonly startingRange: Range
1,651✔
155
    ) {
156
        super();
1,651✔
157
        this.range = util.createRangeFromPositions(
1,651✔
158
            this.startingRange.start,
159
            this.statements.length
160
                ? this.statements[this.statements.length - 1].range.end
1,651✔
161
                : this.startingRange.start
162
        );
163
    }
164

165
    public readonly range: Range;
166

167
    public symbolTable = new SymbolTable();
1,651✔
168

169
    transpile(state: BrsTranspileState) {
170
        state.blockDepth++;
337✔
171
        let results = [] as TranspileResult;
337✔
172
        for (let i = 0; i < this.statements.length; i++) {
337✔
173
            let previousStatement = this.statements[i - 1];
529✔
174
            let statement = this.statements[i];
529✔
175

176
            //if comment is on same line as parent
177
            if (isCommentStatement(statement) &&
529✔
178
                (util.linesTouch(state.lineage[0], statement) || util.linesTouch(previousStatement, statement))
179
            ) {
180
                results.push(' ');
65✔
181

182
                //is not a comment
183
            } else {
184
                //add a newline and indent
185
                results.push(
464✔
186
                    state.newline,
187
                    state.indent()
188
                );
189
            }
190

191
            //push block onto parent list
192
            state.lineage.unshift(this);
529✔
193
            results.push(
529✔
194
                ...statement.transpile(state)
195
            );
196
            state.lineage.shift();
529✔
197
        }
198
        state.blockDepth--;
337✔
199
        return results;
337✔
200
    }
201

202
    walk(visitor: WalkVisitor, options: WalkOptions) {
203
        if (options.walkMode & InternalWalkMode.walkStatements) {
1,773✔
204
            walkArray(this.statements, visitor, options, this);
1,767✔
205
        }
206
    }
207
}
208

209
export class ExpressionStatement extends Statement {
1✔
210
    constructor(
211
        readonly expression: Expression
212✔
212
    ) {
213
        super();
212✔
214
        this.range = this.expression.range;
212✔
215
    }
216

217
    public readonly range: Range;
218

219
    transpile(state: BrsTranspileState) {
220
        return this.expression.transpile(state);
37✔
221
    }
222

223
    walk(visitor: WalkVisitor, options: WalkOptions) {
224
        if (options.walkMode & InternalWalkMode.walkExpressions) {
321✔
225
            walk(this, 'expression', visitor, options);
162✔
226
        }
227
    }
228
}
229

230
export class CommentStatement extends Statement implements Expression, TypedefProvider {
1✔
231
    constructor(
232
        public comments: Token[]
237✔
233
    ) {
234
        super();
237✔
235
        this.visitMode = InternalWalkMode.visitStatements | InternalWalkMode.visitExpressions;
237✔
236
        if (this.comments?.length > 0) {
237!
237

238
            this.range = util.createRangeFromPositions(
236✔
239
                this.comments[0].range.start,
240
                this.comments[this.comments.length - 1].range.end
241
            );
242
        }
243
    }
244

245
    public range: Range;
246

247
    get text() {
248
        return this.comments.map(x => x.text).join('\n');
39✔
249
    }
250

251
    transpile(state: BrsTranspileState) {
252
        let result = [];
96✔
253
        for (let i = 0; i < this.comments.length; i++) {
96✔
254
            let comment = this.comments[i];
97✔
255
            if (i > 0) {
97✔
256
                result.push(state.indent());
1✔
257
            }
258
            result.push(
97✔
259
                state.transpileToken(comment)
260
            );
261
            //add newline for all except final comment
262
            if (i < this.comments.length - 1) {
97✔
263
                result.push('\n');
1✔
264
            }
265
        }
266
        return result;
96✔
267
    }
268

269
    public getTypedef(state: TranspileState) {
270
        return this.transpile(state as BrsTranspileState);
×
271
    }
272

273
    walk(visitor: WalkVisitor, options: WalkOptions) {
274
        //nothing to walk
275
    }
276
}
277

278
export class ExitForStatement extends Statement {
1✔
279
    constructor(
280
        readonly tokens: {
2✔
281
            exitFor: Token;
282
        }
283
    ) {
284
        super();
2✔
285
        this.range = this.tokens.exitFor.range;
2✔
286
    }
287

288
    public readonly range: Range;
289

290
    transpile(state: BrsTranspileState) {
291
        return [
1✔
292
            state.transpileToken(this.tokens.exitFor)
293
        ];
294
    }
295

296
    walk(visitor: WalkVisitor, options: WalkOptions) {
297
        //nothing to walk
298
    }
299

300
}
301

302
export class ExitWhileStatement extends Statement {
1✔
303
    constructor(
304
        readonly tokens: {
5✔
305
            exitWhile: Token;
306
        }
307
    ) {
308
        super();
5✔
309
        this.range = this.tokens.exitWhile.range;
5✔
310
    }
311

312
    public readonly range: Range;
313

314
    transpile(state: BrsTranspileState) {
315
        return [
2✔
316
            state.transpileToken(this.tokens.exitWhile)
317
        ];
318
    }
319

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

325
export class FunctionStatement extends Statement implements TypedefProvider {
1✔
326
    constructor(
327
        public name: Identifier,
1,470✔
328
        public func: FunctionExpression
1,470✔
329
    ) {
330
        super();
1,470✔
331
        this.range = this.func.range;
1,470✔
332
    }
333

334
    public readonly range: Range;
335

336
    /**
337
     * Get the name of this expression based on the parse mode
338
     */
339
    public getName(parseMode: ParseMode) {
340
        const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
1,584✔
341
        if (namespace) {
1,584✔
342
            let delimiter = parseMode === ParseMode.BrighterScript ? '.' : '_';
216✔
343
            let namespaceName = namespace.getName(parseMode);
216✔
344
            return namespaceName + delimiter + this.name?.text;
216✔
345
        } else {
346
            return this.name.text;
1,368✔
347
        }
348
    }
349

350
    transpile(state: BrsTranspileState) {
351
        //create a fake token using the full transpiled name
352
        let nameToken = {
222✔
353
            ...this.name,
354
            text: this.getName(ParseMode.BrightScript)
355
        };
356

357
        return this.func.transpile(state, nameToken);
222✔
358
    }
359

360
    getTypedef(state: BrsTranspileState) {
361
        let result = [];
5✔
362
        for (let annotation of this.annotations ?? []) {
5✔
363
            result.push(
2✔
364
                ...annotation.getTypedef(state),
365
                state.newline,
366
                state.indent()
367
            );
368
        }
369

370
        result.push(
5✔
371
            ...this.func.getTypedef(state, this.name)
372
        );
373
        return result;
5✔
374
    }
375

376
    walk(visitor: WalkVisitor, options: WalkOptions) {
377
        if (options.walkMode & InternalWalkMode.walkExpressions) {
621✔
378
            walk(this, 'func', visitor, options);
605✔
379
        }
380
    }
381
}
382

383
export class IfStatement extends Statement {
1✔
384
    constructor(
385
        readonly tokens: {
170✔
386
            if: Token;
387
            then?: Token;
388
            else?: Token;
389
            endIf?: Token;
390
        },
391
        readonly condition: Expression,
170✔
392
        readonly thenBranch: Block,
170✔
393
        readonly elseBranch?: IfStatement | Block,
170✔
394
        readonly isInline?: boolean
170✔
395
    ) {
396
        super();
170✔
397
        this.range = util.createRangeFromPositions(
170✔
398
            this.tokens.if.range.start,
399
            (this.tokens.endIf ?? this.elseBranch ?? this.thenBranch).range.end
1,020✔
400
        );
401
    }
402
    public readonly range: Range;
403

404
    transpile(state: BrsTranspileState) {
405
        let results = [];
43✔
406
        //if   (already indented by block)
407
        results.push(state.transpileToken(this.tokens.if));
43✔
408
        results.push(' ');
43✔
409
        //conditions
410
        results.push(...this.condition.transpile(state));
43✔
411
        //then
412
        if (this.tokens.then) {
43✔
413
            results.push(' ');
34✔
414
            results.push(
34✔
415
                state.transpileToken(this.tokens.then)
416
            );
417
        }
418
        state.lineage.unshift(this);
43✔
419

420
        //if statement body
421
        let thenNodes = this.thenBranch.transpile(state);
43✔
422
        state.lineage.shift();
43✔
423
        if (thenNodes.length > 0) {
43✔
424
            results.push(thenNodes);
32✔
425
        }
426
        results.push('\n');
43✔
427

428
        //else branch
429
        if (this.tokens.else) {
43✔
430
            //else
431
            results.push(
26✔
432
                state.indent(),
433
                state.transpileToken(this.tokens.else)
434
            );
435
        }
436

437
        if (this.elseBranch) {
43✔
438
            if (isIfStatement(this.elseBranch)) {
26✔
439
                //chained elseif
440
                state.lineage.unshift(this.elseBranch);
18✔
441
                let body = this.elseBranch.transpile(state);
18✔
442
                state.lineage.shift();
18✔
443

444
                if (body.length > 0) {
18!
445
                    //zero or more spaces between the `else` and the `if`
446
                    results.push(this.elseBranch.tokens.if.leadingWhitespace);
18✔
447
                    results.push(...body);
18✔
448

449
                    // stop here because chained if will transpile the rest
450
                    return results;
18✔
451
                } else {
452
                    results.push('\n');
×
453
                }
454

455
            } else {
456
                //else body
457
                state.lineage.unshift(this.elseBranch);
8✔
458
                let body = this.elseBranch.transpile(state);
8✔
459
                state.lineage.shift();
8✔
460

461
                if (body.length > 0) {
8✔
462
                    results.push(...body);
6✔
463
                }
464
                results.push('\n');
8✔
465
            }
466
        }
467

468
        //end if
469
        results.push(state.indent());
25✔
470
        if (this.tokens.endIf) {
25!
471
            results.push(
25✔
472
                state.transpileToken(this.tokens.endIf)
473
            );
474
        } else {
475
            results.push('end if');
×
476
        }
477
        return results;
25✔
478
    }
479

480
    walk(visitor: WalkVisitor, options: WalkOptions) {
481
        if (options.walkMode & InternalWalkMode.walkExpressions) {
153✔
482
            walk(this, 'condition', visitor, options);
71✔
483
        }
484
        if (options.walkMode & InternalWalkMode.walkStatements) {
153✔
485
            walk(this, 'thenBranch', visitor, options);
151✔
486
        }
487
        if (this.elseBranch && options.walkMode & InternalWalkMode.walkStatements) {
153✔
488
            walk(this, 'elseBranch', visitor, options);
81✔
489
        }
490
    }
491
}
492

493
export class IncrementStatement extends Statement {
1✔
494
    constructor(
495
        readonly value: Expression,
15✔
496
        readonly operator: Token
15✔
497
    ) {
498
        super();
15✔
499
        this.range = util.createRangeFromPositions(this.value.range.start, this.operator.range.end);
15✔
500
    }
501

502
    public readonly range: Range;
503

504
    transpile(state: BrsTranspileState) {
505
        return [
7✔
506
            ...this.value.transpile(state),
507
            state.transpileToken(this.operator)
508
        ];
509
    }
510

511
    walk(visitor: WalkVisitor, options: WalkOptions) {
512
        if (options.walkMode & InternalWalkMode.walkExpressions) {
12✔
513
            walk(this, 'value', visitor, options);
7✔
514
        }
515
    }
516
}
517

518
/** Used to indent the current `print` position to the next 16-character-width output zone. */
519
export interface PrintSeparatorTab extends Token {
520
    kind: TokenKind.Comma;
521
}
522

523
/** Used to insert a single whitespace character at the current `print` position. */
524
export interface PrintSeparatorSpace extends Token {
525
    kind: TokenKind.Semicolon;
526
}
527

528
/**
529
 * Represents a `print` statement within BrightScript.
530
 */
531
export class PrintStatement extends Statement {
1✔
532
    /**
533
     * Creates a new internal representation of a BrightScript `print` statement.
534
     * @param expressions an array of expressions or `PrintSeparator`s to be
535
     *                    evaluated and printed.
536
     */
537
    constructor(
538
        readonly tokens: {
484✔
539
            print: Token;
540
        },
541
        readonly expressions: Array<Expression | PrintSeparatorTab | PrintSeparatorSpace>
484✔
542
    ) {
543
        super();
484✔
544
        this.range = util.createRangeFromPositions(
484✔
545
            this.tokens.print.range.start,
546
            this.expressions.length
547
                ? this.expressions[this.expressions.length - 1].range.end
484✔
548
                : this.tokens.print.range.end
549
        );
550
    }
551

552
    public readonly range: Range;
553

554
    transpile(state: BrsTranspileState) {
555
        let result = [
147✔
556
            state.transpileToken(this.tokens.print),
557
            ' '
558
        ];
559
        for (let i = 0; i < this.expressions.length; i++) {
147✔
560
            const expressionOrSeparator: any = this.expressions[i];
211✔
561
            if (expressionOrSeparator.transpile) {
211✔
562
                result.push(...(expressionOrSeparator as ExpressionStatement).transpile(state));
185✔
563
            } else {
564
                result.push(
26✔
565
                    state.tokenToSourceNode(expressionOrSeparator)
566
                );
567
            }
568
            //if there's an expression after us, add a space
569
            if ((this.expressions[i + 1] as any)?.transpile) {
211✔
570
                result.push(' ');
38✔
571
            }
572
        }
573
        return result;
147✔
574
    }
575

576
    walk(visitor: WalkVisitor, options: WalkOptions) {
577
        if (options.walkMode & InternalWalkMode.walkExpressions) {
551✔
578
            //sometimes we have semicolon Tokens in the expressions list (should probably fix that...), so only walk the actual expressions
579
            walkArray(this.expressions, visitor, options, this, (item) => isExpression(item as any));
338✔
580
        }
581
    }
582
}
583

584
export class DimStatement extends Statement {
1✔
585
    constructor(
586
        public dimToken: Token,
38✔
587
        public identifier?: Identifier,
38✔
588
        public openingSquare?: Token,
38✔
589
        public dimensions?: Expression[],
38✔
590
        public closingSquare?: Token
38✔
591
    ) {
592
        super();
38✔
593
        this.range = util.createRangeFromPositions(
38✔
594
            this.dimToken.range.start,
595
            (this.closingSquare ?? this.dimensions[this.dimensions.length - 1] ?? this.openingSquare ?? this.identifier ?? this.dimToken).range.end
456✔
596
        );
597
    }
598
    public range: Range;
599

600
    public transpile(state: BrsTranspileState) {
601
        let result = [
14✔
602
            state.transpileToken(this.dimToken),
603
            ' ',
604
            state.transpileToken(this.identifier),
605
            state.transpileToken(this.openingSquare)
606
        ];
607
        for (let i = 0; i < this.dimensions.length; i++) {
14✔
608
            if (i > 0) {
30✔
609
                result.push(', ');
16✔
610
            }
611
            result.push(
30✔
612
                ...this.dimensions[i].transpile(state)
613
            );
614
        }
615
        result.push(state.transpileToken(this.closingSquare));
14✔
616
        return result;
14✔
617
    }
618

619
    public walk(visitor: WalkVisitor, options: WalkOptions) {
620
        if (this.dimensions?.length > 0 && options.walkMode & InternalWalkMode.walkExpressions) {
34!
621
            walkArray(this.dimensions, visitor, options, this);
15✔
622

623
        }
624
    }
625
}
626

627
export class GotoStatement extends Statement {
1✔
628
    constructor(
629
        readonly tokens: {
7✔
630
            goto: Token;
631
            label: Token;
632
        }
633
    ) {
634
        super();
7✔
635
        this.range = util.createRangeFromPositions(this.tokens.goto.range.start, this.tokens.label.range.end);
7✔
636
    }
637

638
    public readonly range: Range;
639

640
    transpile(state: BrsTranspileState) {
641
        return [
1✔
642
            state.transpileToken(this.tokens.goto),
643
            ' ',
644
            state.transpileToken(this.tokens.label)
645
        ];
646
    }
647

648
    walk(visitor: WalkVisitor, options: WalkOptions) {
649
        //nothing to walk
650
    }
651
}
652

653
export class LabelStatement extends Statement {
1✔
654
    constructor(
655
        readonly tokens: {
7✔
656
            identifier: Token;
657
            colon: Token;
658
        }
659
    ) {
660
        super();
7✔
661
        this.range = util.createRangeFromPositions(this.tokens.identifier.range.start, this.tokens.colon.range.end);
7✔
662
    }
663

664
    public readonly range: Range;
665

666
    transpile(state: BrsTranspileState) {
667
        return [
1✔
668
            state.transpileToken(this.tokens.identifier),
669
            state.transpileToken(this.tokens.colon)
670

671
        ];
672
    }
673

674
    walk(visitor: WalkVisitor, options: WalkOptions) {
675
        //nothing to walk
676
    }
677
}
678

679
export class ReturnStatement extends Statement {
1✔
680
    constructor(
681
        readonly tokens: {
130✔
682
            return: Token;
683
        },
684
        readonly value?: Expression
130✔
685
    ) {
686
        super();
130✔
687
        this.range = util.createRangeFromPositions(
130✔
688
            this.tokens.return.range.start,
689
            this.value?.range.end || this.tokens.return.range.end
532✔
690
        );
691
    }
692

693
    public readonly range: Range;
694

695
    transpile(state: BrsTranspileState) {
696
        let result = [];
8✔
697
        result.push(
8✔
698
            state.transpileToken(this.tokens.return)
699
        );
700
        if (this.value) {
8!
701
            result.push(' ');
8✔
702
            result.push(...this.value.transpile(state));
8✔
703
        }
704
        return result;
8✔
705
    }
706

707
    walk(visitor: WalkVisitor, options: WalkOptions) {
708
        if (options.walkMode & InternalWalkMode.walkExpressions) {
104✔
709
            walk(this, 'value', visitor, options);
62✔
710
        }
711
    }
712
}
713

714
export class EndStatement extends Statement {
1✔
715
    constructor(
716
        readonly tokens: {
5✔
717
            end: Token;
718
        }
719
    ) {
720
        super();
5✔
721
        this.range = util.createRangeFromPositions(this.tokens.end.range.start, this.tokens.end.range.end);
5✔
722
    }
723

724
    public readonly range: Range;
725

726
    transpile(state: BrsTranspileState) {
727
        return [
1✔
728
            state.transpileToken(this.tokens.end)
729
        ];
730
    }
731

732
    walk(visitor: WalkVisitor, options: WalkOptions) {
733
        //nothing to walk
734
    }
735
}
736

737
export class StopStatement extends Statement {
1✔
738
    constructor(
739
        readonly tokens: {
14✔
740
            stop: Token;
741
        }
742
    ) {
743
        super();
14✔
744
        this.range = util.createRangeFromPositions(this.tokens.stop.range.start, this.tokens.stop.range.end);
14✔
745
    }
746

747
    public readonly range: Range;
748

749
    transpile(state: BrsTranspileState) {
750
        return [
1✔
751
            state.transpileToken(this.tokens.stop)
752
        ];
753
    }
754

755
    walk(visitor: WalkVisitor, options: WalkOptions) {
756
        //nothing to walk
757
    }
758
}
759

760
export class ForStatement extends Statement {
1✔
761
    constructor(
762
        public forToken: Token,
26✔
763
        public counterDeclaration: AssignmentStatement,
26✔
764
        public toToken: Token,
26✔
765
        public finalValue: Expression,
26✔
766
        public body: Block,
26✔
767
        public endForToken: Token,
26✔
768
        public stepToken?: Token,
26✔
769
        public increment?: Expression
26✔
770
    ) {
771
        super();
26✔
772
        const lastRange = this.endForToken?.range ?? body.range;
26✔
773
        this.range = util.createRangeFromPositions(this.forToken.range.start, lastRange.end);
26✔
774
    }
775

776
    public readonly range: Range;
777

778
    transpile(state: BrsTranspileState) {
779
        let result = [];
7✔
780
        //for
781
        result.push(
7✔
782
            state.transpileToken(this.forToken),
783
            ' '
784
        );
785
        //i=1
786
        result.push(
7✔
787
            ...this.counterDeclaration.transpile(state),
788
            ' '
789
        );
790
        //to
791
        result.push(
7✔
792
            state.transpileToken(this.toToken),
793
            ' '
794
        );
795
        //final value
796
        result.push(this.finalValue.transpile(state));
7✔
797
        //step
798
        if (this.stepToken) {
7✔
799
            result.push(
3✔
800
                ' ',
801
                state.transpileToken(this.stepToken),
802
                ' ',
803
                this.increment.transpile(state)
804
            );
805
        }
806
        //loop body
807
        state.lineage.unshift(this);
7✔
808
        result.push(...this.body.transpile(state));
7✔
809
        state.lineage.shift();
7✔
810

811
        // add new line before "end for"
812
        result.push('\n');
7✔
813

814
        //end for
815
        result.push(
7✔
816
            state.indent(),
817
            state.transpileToken(this.endForToken)
818
        );
819

820
        return result;
7✔
821
    }
822

823
    walk(visitor: WalkVisitor, options: WalkOptions) {
824
        if (options.walkMode & InternalWalkMode.walkStatements) {
34✔
825
            walk(this, 'counterDeclaration', visitor, options);
33✔
826
        }
827
        if (options.walkMode & InternalWalkMode.walkExpressions) {
34✔
828
            walk(this, 'finalValue', visitor, options);
16✔
829
            walk(this, 'increment', visitor, options);
16✔
830
        }
831
        if (options.walkMode & InternalWalkMode.walkStatements) {
34✔
832
            walk(this, 'body', visitor, options);
33✔
833
        }
834
    }
835
}
836

837
export class ForEachStatement extends Statement {
1✔
838
    constructor(
839
        readonly tokens: {
17✔
840
            forEach: Token;
841
            in: Token;
842
            endFor: Token;
843
        },
844
        readonly item: Token,
17✔
845
        readonly target: Expression,
17✔
846
        readonly body: Block
17✔
847
    ) {
848
        super();
17✔
849
        const lastRange = this.tokens.endFor?.range ?? body.range;
17!
850
        this.range = util.createRangeFromPositions(this.tokens.forEach.range.start, lastRange.end);
17✔
851
    }
852

853
    public readonly range: Range;
854

855
    transpile(state: BrsTranspileState) {
856
        let result = [];
4✔
857
        //for each
858
        result.push(
4✔
859
            state.transpileToken(this.tokens.forEach),
860
            ' '
861
        );
862
        //item
863
        result.push(
4✔
864
            state.transpileToken(this.item),
865
            ' '
866
        );
867
        //in
868
        result.push(
4✔
869
            state.transpileToken(this.tokens.in),
870
            ' '
871
        );
872
        //target
873
        result.push(...this.target.transpile(state));
4✔
874
        //body
875
        state.lineage.unshift(this);
4✔
876
        result.push(...this.body.transpile(state));
4✔
877
        state.lineage.shift();
4✔
878

879
        // add new line before "end for"
880
        result.push('\n');
4✔
881

882
        //end for
883
        result.push(
4✔
884
            state.indent(),
885
            state.transpileToken(this.tokens.endFor)
886
        );
887
        return result;
4✔
888
    }
889

890
    walk(visitor: WalkVisitor, options: WalkOptions) {
891
        if (options.walkMode & InternalWalkMode.walkExpressions) {
18✔
892
            walk(this, 'target', visitor, options);
9✔
893
        }
894
        if (options.walkMode & InternalWalkMode.walkStatements) {
18✔
895
            walk(this, 'body', visitor, options);
17✔
896
        }
897
    }
898
}
899

900
export class WhileStatement extends Statement {
1✔
901
    constructor(
902
        readonly tokens: {
16✔
903
            while: Token;
904
            endWhile: Token;
905
        },
906
        readonly condition: Expression,
16✔
907
        readonly body: Block
16✔
908
    ) {
909
        super();
16✔
910
        const lastRange = this.tokens.endWhile?.range ?? body.range;
16✔
911
        this.range = util.createRangeFromPositions(this.tokens.while.range.start, lastRange.end);
16✔
912
    }
913

914
    public readonly range: Range;
915

916
    transpile(state: BrsTranspileState) {
917
        let result = [];
3✔
918
        //while
919
        result.push(
3✔
920
            state.transpileToken(this.tokens.while),
921
            ' '
922
        );
923
        //condition
924
        result.push(
3✔
925
            ...this.condition.transpile(state)
926
        );
927
        state.lineage.unshift(this);
3✔
928
        //body
929
        result.push(...this.body.transpile(state));
3✔
930
        state.lineage.shift();
3✔
931

932
        //trailing newline only if we have body statements
933
        result.push('\n');
3✔
934

935
        //end while
936
        result.push(
3✔
937
            state.indent(),
938
            state.transpileToken(this.tokens.endWhile)
939
        );
940

941
        return result;
3✔
942
    }
943

944
    walk(visitor: WalkVisitor, options: WalkOptions) {
945
        if (options.walkMode & InternalWalkMode.walkExpressions) {
17✔
946
            walk(this, 'condition', visitor, options);
6✔
947
        }
948
        if (options.walkMode & InternalWalkMode.walkStatements) {
17✔
949
            walk(this, 'body', visitor, options);
16✔
950
        }
951
    }
952
}
953

954
export class DottedSetStatement extends Statement {
1✔
955
    constructor(
956
        readonly obj: Expression,
222✔
957
        readonly name: Identifier,
222✔
958
        readonly value: Expression
222✔
959
    ) {
960
        super();
222✔
961
        this.range = util.createRangeFromPositions(this.obj.range.start, this.value.range.end);
222✔
962
    }
963

964
    public readonly range: Range;
965

966
    transpile(state: BrsTranspileState) {
967
        //if the value is a compound assignment, don't add the obj, dot, name, or operator...the expression will handle that
968
        if (CompoundAssignmentOperators.includes((this.value as BinaryExpression)?.operator?.kind)) {
4!
969
            return this.value.transpile(state);
1✔
970
        } else {
971
            return [
3✔
972
                //object
973
                ...this.obj.transpile(state),
974
                '.',
975
                //name
976
                state.transpileToken(this.name),
977
                ' = ',
978
                //right-hand-side of assignment
979
                ...this.value.transpile(state)
980
            ];
981
        }
982
    }
983

984
    walk(visitor: WalkVisitor, options: WalkOptions) {
985
        if (options.walkMode & InternalWalkMode.walkExpressions) {
32✔
986
            walk(this, 'obj', visitor, options);
17✔
987
            walk(this, 'value', visitor, options);
17✔
988
        }
989
    }
990
}
991

992
export class IndexedSetStatement extends Statement {
1✔
993
    constructor(
994
        readonly obj: Expression,
16✔
995
        readonly index: Expression,
16✔
996
        readonly value: Expression,
16✔
997
        readonly openingSquare: Token,
16✔
998
        readonly closingSquare: Token
16✔
999
    ) {
1000
        super();
16✔
1001
        this.range = util.createRangeFromPositions(this.obj.range.start, this.value.range.end);
16✔
1002
    }
1003

1004
    public readonly range: Range;
1005

1006
    transpile(state: BrsTranspileState) {
1007
        //if the value is a component assignment, don't add the obj, index or operator...the expression will handle that
1008
        if (CompoundAssignmentOperators.includes((this.value as BinaryExpression)?.operator?.kind)) {
4!
1009
            return this.value.transpile(state);
1✔
1010
        } else {
1011
            return [
3✔
1012
                //obj
1013
                ...this.obj.transpile(state),
1014
                //   [
1015
                state.transpileToken(this.openingSquare),
1016
                //    index
1017
                ...this.index.transpile(state),
1018
                //         ]
1019
                state.transpileToken(this.closingSquare),
1020
                //           =
1021
                ' = ',
1022
                //             value
1023
                ...this.value.transpile(state)
1024
            ];
1025
        }
1026
    }
1027

1028
    walk(visitor: WalkVisitor, options: WalkOptions) {
1029
        if (options.walkMode & InternalWalkMode.walkExpressions) {
12✔
1030
            walk(this, 'obj', visitor, options);
6✔
1031
            walk(this, 'index', visitor, options);
6✔
1032
            walk(this, 'value', visitor, options);
6✔
1033
        }
1034
    }
1035
}
1036

1037
export class LibraryStatement extends Statement implements TypedefProvider {
1✔
1038
    constructor(
1039
        readonly tokens: {
11✔
1040
            library: Token;
1041
            filePath: Token | undefined;
1042
        }
1043
    ) {
1044
        super();
11✔
1045
        this.range = util.createRangeFromPositions(
11✔
1046
            this.tokens.library.range.start,
1047
            this.tokens.filePath ? this.tokens.filePath.range.end : this.tokens.library.range.end
11✔
1048
        );
1049
    }
1050

1051
    public readonly range: Range;
1052

1053
    transpile(state: BrsTranspileState) {
1054
        let result = [];
1✔
1055
        result.push(
1✔
1056
            state.transpileToken(this.tokens.library)
1057
        );
1058
        //there will be a parse error if file path is missing, but let's prevent a runtime error just in case
1059
        if (this.tokens.filePath) {
1!
1060
            result.push(
1✔
1061
                ' ',
1062
                state.transpileToken(this.tokens.filePath)
1063
            );
1064
        }
1065
        return result;
1✔
1066
    }
1067

1068
    getTypedef(state: BrsTranspileState) {
1069
        return this.transpile(state);
×
1070
    }
1071

1072
    walk(visitor: WalkVisitor, options: WalkOptions) {
1073
        //nothing to walk
1074
    }
1075
}
1076

1077
export class NamespaceStatement extends Statement implements TypedefProvider {
1✔
1078
    constructor(
1079
        public keyword: Token,
150✔
1080
        //this should technically only be a VariableExpression or DottedGetExpression, but that can be enforced elsewhere
1081
        public nameExpression: NamespacedVariableNameExpression,
150✔
1082
        public body: Body,
150✔
1083
        public endKeyword: Token
150✔
1084
    ) {
1085
        super();
150✔
1086
        this.name = this.getName(ParseMode.BrighterScript);
150✔
1087
    }
1088

1089
    public getSymbolTable() {
1090
        return this.body.symbolTable;
625✔
1091
    }
1092

1093
    /**
1094
     * The string name for this namespace
1095
     */
1096
    public name: string;
1097

1098
    public get range() {
1099
        return this.cacheRange();
72✔
1100
    }
1101
    private _range: Range;
1102

1103
    public cacheRange() {
1104
        if (!this._range) {
221✔
1105
            this._range = util.createBoundingRange(
149✔
1106
                this.keyword,
1107
                this.nameExpression,
1108
                this.body,
1109
                this.endKeyword
1110
            ) ?? interpolatedRange;
149!
1111
        }
1112
        return this._range;
221✔
1113
    }
1114

1115
    public getName(parseMode: ParseMode) {
1116
        return this.nameExpression.getName(parseMode);
1,058✔
1117
    }
1118

1119
    transpile(state: BrsTranspileState) {
1120
        //namespaces don't actually have any real content, so just transpile their bodies
1121
        return this.body.transpile(state);
16✔
1122
    }
1123

1124
    getTypedef(state: BrsTranspileState) {
1125
        let result = [
7✔
1126
            'namespace ',
1127
            ...this.getName(ParseMode.BrighterScript),
1128
            state.newline
1129
        ];
1130
        state.blockDepth++;
7✔
1131
        result.push(
7✔
1132
            ...this.body.getTypedef(state)
1133
        );
1134
        state.blockDepth--;
7✔
1135

1136
        result.push(
7✔
1137
            state.indent(),
1138
            'end namespace'
1139
        );
1140
        return result;
7✔
1141
    }
1142

1143
    walk(visitor: WalkVisitor, options: WalkOptions) {
1144
        if (options.walkMode & InternalWalkMode.walkExpressions) {
105!
1145
            walk(this, 'nameExpression', visitor, options);
105✔
1146
        }
1147
        if (this.body.statements.length > 0 && options.walkMode & InternalWalkMode.walkStatements) {
105✔
1148
            walk(this, 'body', visitor, options);
100✔
1149
        }
1150
    }
1151
}
1152

1153
export class ImportStatement extends Statement implements TypedefProvider {
1✔
1154
    constructor(
1155
        readonly importToken: Token,
31✔
1156
        readonly filePathToken: Token
31✔
1157
    ) {
1158
        super();
31✔
1159
        this.range = util.createRangeFromPositions(
31✔
1160
            importToken.range.start,
1161
            (filePathToken ?? importToken).range.end
93✔
1162
        );
1163
        if (this.filePathToken) {
31✔
1164
            //remove quotes
1165
            this.filePath = this.filePathToken.text.replace(/"/g, '');
29✔
1166
            //adjust the range to exclude the quotes
1167
            this.filePathToken.range = util.createRange(
29✔
1168
                this.filePathToken.range.start.line,
1169
                this.filePathToken.range.start.character + 1,
1170
                this.filePathToken.range.end.line,
1171
                this.filePathToken.range.end.character - 1
1172
            );
1173
        }
1174
    }
1175
    public filePath: string;
1176
    public range: Range;
1177

1178
    transpile(state: BrsTranspileState) {
1179
        //The xml files are responsible for adding the additional script imports, but
1180
        //add the import statement as a comment just for debugging purposes
1181
        return [
1✔
1182
            `'`,
1183
            state.transpileToken(this.importToken),
1184
            ' ',
1185
            state.transpileToken(this.filePathToken)
1186
        ];
1187
    }
1188

1189
    /**
1190
     * Get the typedef for this statement
1191
     */
1192
    public getTypedef(state: BrsTranspileState) {
1193
        return [
3✔
1194
            this.importToken.text,
1195
            ' ',
1196
            //replace any `.bs` extension with `.brs`
1197
            this.filePathToken.text.replace(/\.bs"?$/i, '.brs"')
1198
        ];
1199
    }
1200

1201
    walk(visitor: WalkVisitor, options: WalkOptions) {
1202
        //nothing to walk
1203
    }
1204
}
1205

1206
export class InterfaceStatement extends Statement implements TypedefProvider {
1✔
1207
    constructor(
1208
        interfaceToken: Token,
1209
        name: Identifier,
1210
        extendsToken: Token,
1211
        public parentInterfaceName: NamespacedVariableNameExpression,
14✔
1212
        public body: Statement[],
14✔
1213
        endInterfaceToken: Token
1214
    ) {
1215
        super();
14✔
1216
        this.tokens.interface = interfaceToken;
14✔
1217
        this.tokens.name = name;
14✔
1218
        this.tokens.extends = extendsToken;
14✔
1219
        this.tokens.endInterface = endInterfaceToken;
14✔
1220
        this.range = util.createBoundingRange(
14✔
1221
            this.tokens.interface,
1222
            this.tokens.name,
1223
            this.tokens.extends,
1224
            this.parentInterfaceName,
1225
            ...this.body,
1226
            this.tokens.endInterface
1227
        );
1228
    }
1229

1230
    public tokens = {} as {
14✔
1231
        interface: Token;
1232
        name: Identifier;
1233
        extends: Token;
1234
        endInterface: Token;
1235
    };
1236

1237
    public range: Range;
1238

1239
    public get fields() {
1240
        return this.body.filter(x => isInterfaceFieldStatement(x));
×
1241
    }
1242

1243
    public get methods() {
1244
        return this.body.filter(x => isInterfaceMethodStatement(x));
×
1245
    }
1246

1247
    /**
1248
     * The name of the interface WITH its leading namespace (if applicable)
1249
     */
1250
    public get fullName() {
1251
        const name = this.tokens.name?.text;
×
1252
        if (name) {
×
1253
            const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
×
1254
            if (namespace) {
×
1255
                let namespaceName = namespace.getName(ParseMode.BrighterScript);
×
1256
                return `${namespaceName}.${name}`;
×
1257
            } else {
1258
                return name;
×
1259
            }
1260
        } else {
1261
            //return undefined which will allow outside callers to know that this interface doesn't have a name
1262
            return undefined;
×
1263
        }
1264
    }
1265

1266
    /**
1267
     * The name of the interface (without the namespace prefix)
1268
     */
1269
    public get name() {
1270
        return this.tokens.name?.text;
4!
1271
    }
1272

1273
    /**
1274
     * Get the name of this expression based on the parse mode
1275
     */
1276
    public getName(parseMode: ParseMode) {
1277
        const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
4✔
1278
        if (namespace) {
4✔
1279
            let delimiter = parseMode === ParseMode.BrighterScript ? '.' : '_';
1!
1280
            let namespaceName = namespace.getName(parseMode);
1✔
1281
            return namespaceName + delimiter + this.name;
1✔
1282
        } else {
1283
            return this.name;
3✔
1284
        }
1285
    }
1286

1287
    public transpile(state: BrsTranspileState): TranspileResult {
1288
        //interfaces should completely disappear at runtime
1289
        return [];
2✔
1290
    }
1291

1292
    getTypedef(state: BrsTranspileState) {
1293
        const result = [] as TranspileResult;
4✔
1294
        for (let annotation of this.annotations ?? []) {
4✔
1295
            result.push(
1✔
1296
                ...annotation.getTypedef(state),
1297
                state.newline,
1298
                state.indent()
1299
            );
1300
        }
1301
        result.push(
4✔
1302
            this.tokens.interface.text,
1303
            ' ',
1304
            this.tokens.name.text
1305
        );
1306
        const parentInterfaceName = this.parentInterfaceName?.getName(ParseMode.BrighterScript);
4!
1307
        if (parentInterfaceName) {
4!
1308
            result.push(
×
1309
                ' extends ',
1310
                parentInterfaceName
1311
            );
1312
        }
1313
        const body = this.body ?? [];
4!
1314
        if (body.length > 0) {
4!
1315
            state.blockDepth++;
4✔
1316
        }
1317
        for (const statement of body) {
4✔
1318
            if (isInterfaceMethodStatement(statement) || isInterfaceFieldStatement(statement)) {
18✔
1319
                result.push(
17✔
1320
                    state.newline,
1321
                    state.indent(),
1322
                    ...statement.getTypedef(state)
1323
                );
1324
            } else {
1325
                result.push(
1✔
1326
                    state.newline,
1327
                    state.indent(),
1328
                    ...statement.transpile(state)
1329
                );
1330
            }
1331
        }
1332
        if (body.length > 0) {
4!
1333
            state.blockDepth--;
4✔
1334
        }
1335
        result.push(
4✔
1336
            state.newline,
1337
            state.indent(),
1338
            'end interface',
1339
            state.newline
1340
        );
1341
        return result;
4✔
1342
    }
1343

1344
    walk(visitor: WalkVisitor, options: WalkOptions) {
1345
        if (options.walkMode & InternalWalkMode.walkStatements) {
12!
1346
            walkArray(this.body, visitor, options, this);
12✔
1347

1348
        }
1349
    }
1350
}
1351

1352
export class InterfaceFieldStatement extends Statement implements TypedefProvider {
1✔
1353
    public transpile(state: BrsTranspileState): TranspileResult {
1354
        throw new Error('Method not implemented.');
×
1355
    }
1356
    constructor(
1357
        nameToken: Identifier,
1358
        asToken: Token,
1359
        typeToken: Token,
1360
        public type: BscType
18✔
1361
    ) {
1362
        super();
18✔
1363
        this.tokens.name = nameToken;
18✔
1364
        this.tokens.as = asToken;
18✔
1365
        this.tokens.type = typeToken;
18✔
1366
    }
1367
    public get range() {
1368
        return util.createRangeFromPositions(
×
1369
            this.tokens.name.range.start,
1370
            (this.tokens.type ?? this.tokens.as ?? this.tokens.name).range.end
×
1371
        );
1372
    }
1373

1374
    public tokens = {} as {
18✔
1375
        name: Identifier;
1376
        as: Token;
1377
        type: Token;
1378
    };
1379

1380
    public get name() {
1381
        return this.tokens.name.text;
×
1382
    }
1383

1384
    walk(visitor: WalkVisitor, options: WalkOptions) {
1385
        //nothing to walk
1386
    }
1387

1388
    getTypedef(state: BrsTranspileState): (string | SourceNode)[] {
1389
        const result = [] as TranspileResult;
8✔
1390
        for (let annotation of this.annotations ?? []) {
8✔
1391
            result.push(
1✔
1392
                ...annotation.getTypedef(state),
1393
                state.newline,
1394
                state.indent()
1395
            );
1396
        }
1397

1398
        result.push(
8✔
1399
            this.tokens.name.text
1400
        );
1401
        if (this.tokens.type?.text?.length > 0) {
8!
1402
            result.push(
8✔
1403
                ' as ',
1404
                this.tokens.type.text
1405
            );
1406
        }
1407
        return result;
8✔
1408
    }
1409

1410
}
1411

1412
export class InterfaceMethodStatement extends Statement implements TypedefProvider {
1✔
1413
    public transpile(state: BrsTranspileState): TranspileResult {
1414
        throw new Error('Method not implemented.');
×
1415
    }
1416
    constructor(
1417
        functionTypeToken: Token,
1418
        nameToken: Identifier,
1419
        leftParen: Token,
1420
        public params: FunctionParameterExpression[],
9✔
1421
        rightParen: Token,
1422
        asToken?: Token,
1423
        returnTypeToken?: Token,
1424
        public returnType?: BscType
9✔
1425
    ) {
1426
        super();
9✔
1427
        this.tokens.functionType = functionTypeToken;
9✔
1428
        this.tokens.name = nameToken;
9✔
1429
        this.tokens.leftParen = leftParen;
9✔
1430
        this.tokens.rightParen = rightParen;
9✔
1431
        this.tokens.as = asToken;
9✔
1432
        this.tokens.returnType = returnTypeToken;
9✔
1433
    }
1434

1435
    public get range() {
1436
        return util.createRangeFromPositions(
×
1437
            this.tokens.name.range.start,
1438
            (
1439
                this.tokens.returnType ??
×
1440
                this.tokens.as ??
×
1441
                this.tokens.rightParen ??
×
1442
                this.params?.[this.params?.length - 1] ??
×
1443
                this.tokens.leftParen ??
×
1444
                this.tokens.name ??
×
1445
                this.tokens.functionType
1446
            ).range.end
1447
        );
1448
    }
1449

1450
    public tokens = {} as {
9✔
1451
        functionType: Token;
1452
        name: Identifier;
1453
        leftParen: Token;
1454
        rightParen: Token;
1455
        as: Token;
1456
        returnType: Token;
1457
    };
1458

1459
    walk(visitor: WalkVisitor, options: WalkOptions) {
1460
        //nothing to walk
1461
    }
1462

1463
    getTypedef(state: BrsTranspileState) {
1464
        const result = [] as TranspileResult;
9✔
1465
        for (let annotation of this.annotations ?? []) {
9✔
1466
            result.push(
1✔
1467
                ...annotation.getTypedef(state),
1468
                state.newline,
1469
                state.indent()
1470
            );
1471
        }
1472

1473
        result.push(
9✔
1474
            this.tokens.functionType.text,
1475
            ' ',
1476
            this.tokens.name.text,
1477
            '('
1478
        );
1479
        const params = this.params ?? [];
9!
1480
        for (let i = 0; i < params.length; i++) {
9✔
1481
            if (i > 0) {
×
1482
                result.push(', ');
×
1483
            }
1484
            const param = params[i];
×
1485
            result.push(param.name.text);
×
1486
            if (param.typeToken?.text?.length > 0) {
×
1487
                result.push(
×
1488
                    ' as ',
1489
                    param.typeToken.text
1490
                );
1491
            }
1492
        }
1493
        result.push(
9✔
1494
            ')'
1495
        );
1496
        if (this.tokens.returnType?.text.length > 0) {
9!
1497
            result.push(
9✔
1498
                ' as ',
1499
                this.tokens.returnType.text
1500
            );
1501
        }
1502
        return result;
9✔
1503
    }
1504
}
1505

1506
export class ClassStatement extends Statement implements TypedefProvider {
1✔
1507

1508
    constructor(
1509
        readonly classKeyword: Token,
399✔
1510
        /**
1511
         * The name of the class (without namespace prefix)
1512
         */
1513
        readonly name: Identifier,
399✔
1514
        public body: Statement[],
399✔
1515
        readonly end: Token,
399✔
1516
        readonly extendsKeyword?: Token,
399✔
1517
        readonly parentClassName?: NamespacedVariableNameExpression
399✔
1518
    ) {
1519
        super();
399✔
1520
        this.body = this.body ?? [];
399!
1521

1522
        for (let statement of this.body) {
399✔
1523
            if (isMethodStatement(statement)) {
368✔
1524
                this.methods.push(statement);
215✔
1525
                this.memberMap[statement?.name?.text.toLowerCase()] = statement;
215!
1526
            } else if (isFieldStatement(statement)) {
153✔
1527
                this.fields.push(statement);
145✔
1528
                this.memberMap[statement?.name?.text.toLowerCase()] = statement;
145!
1529
            }
1530
        }
1531

1532
        this.range = util.createRangeFromPositions(this.classKeyword.range.start, this.end.range.end);
399✔
1533
    }
1534

1535
    public getName(parseMode: ParseMode) {
1536
        const name = this.name?.text;
691✔
1537
        if (name) {
691✔
1538
            const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
689✔
1539
            if (namespace) {
689✔
1540
                let namespaceName = namespace.getName(parseMode);
166✔
1541
                let separator = parseMode === ParseMode.BrighterScript ? '.' : '_';
166✔
1542
                return namespaceName + separator + name;
166✔
1543
            } else {
1544
                return name;
523✔
1545
            }
1546
        } else {
1547
            //return undefined which will allow outside callers to know that this class doesn't have a name
1548
            return undefined;
2✔
1549
        }
1550
    }
1551

1552
    public memberMap = {} as Record<string, MemberStatement>;
399✔
1553
    public methods = [] as MethodStatement[];
399✔
1554
    public fields = [] as FieldStatement[];
399✔
1555

1556
    public readonly range: Range;
1557

1558
    transpile(state: BrsTranspileState) {
1559
        let result = [];
30✔
1560
        //make the builder
1561
        result.push(...this.getTranspiledBuilder(state));
30✔
1562
        result.push(
30✔
1563
            '\n',
1564
            state.indent()
1565
        );
1566
        //make the class assembler (i.e. the public-facing class creator method)
1567
        result.push(...this.getTranspiledClassFunction(state));
30✔
1568
        return result;
30✔
1569
    }
1570

1571
    getTypedef(state: BrsTranspileState) {
1572
        const result = [] as TranspileResult;
15✔
1573
        for (let annotation of this.annotations ?? []) {
15!
1574
            result.push(
×
1575
                ...annotation.getTypedef(state),
1576
                state.newline,
1577
                state.indent()
1578
            );
1579
        }
1580
        result.push(
15✔
1581
            'class ',
1582
            this.name.text
1583
        );
1584
        if (this.extendsKeyword && this.parentClassName) {
15✔
1585
            const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
4✔
1586
            const fqName = util.getFullyQualifiedClassName(
4✔
1587
                this.parentClassName.getName(ParseMode.BrighterScript),
1588
                namespace?.getName(ParseMode.BrighterScript)
12✔
1589
            );
1590
            result.push(
4✔
1591
                ` extends ${fqName}`
1592
            );
1593
        }
1594
        result.push(state.newline);
15✔
1595
        state.blockDepth++;
15✔
1596

1597
        let body = this.body;
15✔
1598
        //inject an empty "new" method if missing
1599
        if (!this.getConstructorFunction()) {
15✔
1600
            body = [
11✔
1601
                createMethodStatement('new', TokenKind.Sub),
1602
                ...this.body
1603
            ];
1604
        }
1605

1606
        for (const member of body) {
15✔
1607
            if (isTypedefProvider(member)) {
33!
1608
                result.push(
33✔
1609
                    state.indent(),
1610
                    ...member.getTypedef(state),
1611
                    state.newline
1612
                );
1613
            }
1614
        }
1615
        state.blockDepth--;
15✔
1616
        result.push(
15✔
1617
            state.indent(),
1618
            'end class'
1619
        );
1620
        return result;
15✔
1621
    }
1622

1623
    /**
1624
     * Find the parent index for this class's parent.
1625
     * For class inheritance, every class is given an index.
1626
     * The base class is index 0, its child is index 1, and so on.
1627
     */
1628
    public getParentClassIndex(state: BrsTranspileState) {
1629
        let myIndex = 0;
72✔
1630
        let stmt = this as ClassStatement;
72✔
1631
        while (stmt) {
72✔
1632
            if (stmt.parentClassName) {
115✔
1633
                const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
44✔
1634
                //find the parent class
1635
                stmt = state.file.getClassFileLink(
44✔
1636
                    stmt.parentClassName.getName(ParseMode.BrighterScript),
1637
                    namespace?.getName(ParseMode.BrighterScript)
132✔
1638
                )?.item;
44✔
1639
                myIndex++;
44✔
1640
            } else {
1641
                break;
71✔
1642
            }
1643
        }
1644
        const result = myIndex - 1;
72✔
1645
        if (result >= 0) {
72✔
1646
            return result;
37✔
1647
        } else {
1648
            return null;
35✔
1649
        }
1650
    }
1651

1652
    public hasParentClass() {
1653
        return !!this.parentClassName;
30✔
1654
    }
1655

1656
    /**
1657
     * Get all ancestor classes, in closest-to-furthest order (i.e. 0 is parent, 1 is grandparent, etc...).
1658
     * This will return an empty array if no ancestors were found
1659
     */
1660
    public getAncestors(state: BrsTranspileState) {
1661
        let ancestors = [] as ClassStatement[];
60✔
1662
        let stmt = this as ClassStatement;
60✔
1663
        while (stmt) {
60✔
1664
            if (stmt.parentClassName) {
96✔
1665
                const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
36✔
1666
                stmt = state.file.getClassFileLink(
36✔
1667
                    stmt.parentClassName.getName(ParseMode.BrighterScript),
1668
                    namespace?.getName(ParseMode.BrighterScript)
108✔
1669
                )?.item;
36!
1670
                ancestors.push(stmt);
36✔
1671
            } else {
1672
                break;
60✔
1673
            }
1674
        }
1675
        return ancestors;
60✔
1676
    }
1677

1678
    private getBuilderName(name: string) {
1679
        if (name.includes('.')) {
75✔
1680
            name = name.replace(/\./gi, '_');
2✔
1681
        }
1682
        return `__${name}_builder`;
75✔
1683
    }
1684

1685
    /**
1686
     * Get the constructor function for this class (if exists), or undefined if not exist
1687
     */
1688
    private getConstructorFunction() {
1689
        return this.body.find((stmt) => {
75✔
1690
            return (stmt as MethodStatement)?.name?.text?.toLowerCase() === 'new';
74!
1691
        }) as MethodStatement;
1692
    }
1693

1694
    /**
1695
     * Determine if the specified field was declared in one of the ancestor classes
1696
     */
1697
    public isFieldDeclaredByAncestor(fieldName: string, ancestors: ClassStatement[]) {
1698
        let lowerFieldName = fieldName.toLowerCase();
×
1699
        for (let ancestor of ancestors) {
×
1700
            if (ancestor.memberMap[lowerFieldName]) {
×
1701
                return true;
×
1702
            }
1703
        }
1704
        return false;
×
1705
    }
1706

1707
    /**
1708
     * The builder is a function that assigns all of the methods and property names to a class instance.
1709
     * This needs to be a separate function so that child classes can call the builder from their parent
1710
     * without instantiating the parent constructor at that point in time.
1711
     */
1712
    private getTranspiledBuilder(state: BrsTranspileState) {
1713
        let result = [];
30✔
1714
        result.push(`function ${this.getBuilderName(this.getName(ParseMode.BrightScript))}()\n`);
30✔
1715
        state.blockDepth++;
30✔
1716
        //indent
1717
        result.push(state.indent());
30✔
1718

1719
        /**
1720
         * The lineage of this class. index 0 is a direct parent, index 1 is index 0's parent, etc...
1721
         */
1722
        let ancestors = this.getAncestors(state);
30✔
1723

1724
        //construct parent class or empty object
1725
        if (ancestors[0]) {
30✔
1726
            const ancestorNamespace = ancestors[0].findAncestor<NamespaceStatement>(isNamespaceStatement);
15✔
1727
            let fullyQualifiedClassName = util.getFullyQualifiedClassName(
15✔
1728
                ancestors[0].getName(ParseMode.BrighterScript),
1729
                ancestorNamespace?.getName(ParseMode.BrighterScript)
45✔
1730
            );
1731
            result.push(
15✔
1732
                'instance = ',
1733
                this.getBuilderName(fullyQualifiedClassName), '()');
1734
        } else {
1735
            //use an empty object.
1736
            result.push('instance = {}');
15✔
1737
        }
1738
        result.push(
30✔
1739
            state.newline,
1740
            state.indent()
1741
        );
1742
        let parentClassIndex = this.getParentClassIndex(state);
30✔
1743

1744
        let body = this.body;
30✔
1745
        //inject an empty "new" method if missing
1746
        if (!this.getConstructorFunction()) {
30✔
1747
            body = [
18✔
1748
                createMethodStatement('new', TokenKind.Sub),
1749
                ...this.body
1750
            ];
1751
        }
1752

1753
        for (let statement of body) {
30✔
1754
            //is field statement
1755
            if (isFieldStatement(statement)) {
48✔
1756
                //do nothing with class fields in this situation, they are handled elsewhere
1757
                continue;
7✔
1758

1759
                //methods
1760
            } else if (isMethodStatement(statement)) {
41!
1761

1762
                //store overridden parent methods as super{parentIndex}_{methodName}
1763
                if (
41✔
1764
                    //is override method
1765
                    statement.override ||
108✔
1766
                    //is constructor function in child class
1767
                    (statement.name.text.toLowerCase() === 'new' && ancestors[0])
1768
                ) {
1769
                    result.push(
19✔
1770
                        `instance.super${parentClassIndex}_${statement.name.text} = instance.${statement.name.text}`,
1771
                        state.newline,
1772
                        state.indent()
1773
                    );
1774
                }
1775

1776
                state.classStatement = this;
41✔
1777
                result.push(
41✔
1778
                    'instance.',
1779
                    state.transpileToken(statement.name),
1780
                    ' = ',
1781
                    ...statement.transpile(state),
1782
                    state.newline,
1783
                    state.indent()
1784
                );
1785
                delete state.classStatement;
41✔
1786
            } else {
1787
                //other random statements (probably just comments)
1788
                result.push(
×
1789
                    ...statement.transpile(state),
1790
                    state.newline,
1791
                    state.indent()
1792
                );
1793
            }
1794
        }
1795
        //return the instance
1796
        result.push('return instance\n');
30✔
1797
        state.blockDepth--;
30✔
1798
        result.push(state.indent());
30✔
1799
        result.push(`end function`);
30✔
1800
        return result;
30✔
1801
    }
1802

1803
    /**
1804
     * The class function is the function with the same name as the class. This is the function that
1805
     * consumers should call to create a new instance of that class.
1806
     * This invokes the builder, gets an instance of the class, then invokes the "new" function on that class.
1807
     */
1808
    private getTranspiledClassFunction(state: BrsTranspileState) {
1809
        let result = [];
30✔
1810
        const constructorFunction = this.getConstructorFunction();
30✔
1811
        const constructorParams = constructorFunction ? constructorFunction.func.parameters : [];
30✔
1812

1813
        result.push(
30✔
1814
            state.sourceNode(this.classKeyword, 'function'),
1815
            state.sourceNode(this.classKeyword, ' '),
1816
            state.sourceNode(this.name, this.getName(ParseMode.BrightScript)),
1817
            `(`
1818
        );
1819
        let i = 0;
30✔
1820
        for (let param of constructorParams) {
30✔
1821
            if (i > 0) {
7✔
1822
                result.push(', ');
2✔
1823
            }
1824
            result.push(
7✔
1825
                param.transpile(state)
1826
            );
1827
            i++;
7✔
1828
        }
1829
        result.push(
30✔
1830
            ')',
1831
            '\n'
1832
        );
1833

1834
        state.blockDepth++;
30✔
1835
        result.push(state.indent());
30✔
1836
        result.push(`instance = ${this.getBuilderName(this.getName(ParseMode.BrightScript))}()\n`);
30✔
1837

1838
        result.push(state.indent());
30✔
1839
        result.push(`instance.new(`);
30✔
1840

1841
        //append constructor arguments
1842
        i = 0;
30✔
1843
        for (let param of constructorParams) {
30✔
1844
            if (i > 0) {
7✔
1845
                result.push(', ');
2✔
1846
            }
1847
            result.push(
7✔
1848
                state.transpileToken(param.name)
1849
            );
1850
            i++;
7✔
1851
        }
1852
        result.push(
30✔
1853
            ')',
1854
            '\n'
1855
        );
1856

1857
        result.push(state.indent());
30✔
1858
        result.push(`return instance\n`);
30✔
1859

1860
        state.blockDepth--;
30✔
1861
        result.push(state.indent());
30✔
1862
        result.push(`end function`);
30✔
1863
        return result;
30✔
1864
    }
1865

1866
    walk(visitor: WalkVisitor, options: WalkOptions) {
1867
        if (options.walkMode & InternalWalkMode.walkStatements) {
177!
1868
            walkArray(this.body, visitor, options, this);
177✔
1869
        }
1870
    }
1871
}
1872

1873
const accessModifiers = [
1✔
1874
    TokenKind.Public,
1875
    TokenKind.Protected,
1876
    TokenKind.Private
1877
];
1878
export class MethodStatement extends FunctionStatement {
1✔
1879
    constructor(
1880
        modifiers: Token | Token[],
1881
        name: Identifier,
1882
        func: FunctionExpression,
1883
        public override: Token
244✔
1884
    ) {
1885
        super(name, func);
244✔
1886
        if (modifiers) {
244✔
1887
            if (Array.isArray(modifiers)) {
30!
1888
                this.modifiers.push(...modifiers);
×
1889
            } else {
1890
                this.modifiers.push(modifiers);
30✔
1891
            }
1892
        }
1893
        this.range = util.createRangeFromPositions(
244✔
1894
            (this.accessModifier ?? this.func).range.start,
732✔
1895
            this.func.range.end
1896
        );
1897
    }
1898

1899
    public modifiers: Token[] = [];
244✔
1900

1901
    public get accessModifier() {
1902
        return this.modifiers.find(x => accessModifiers.includes(x.kind));
334✔
1903
    }
1904

1905
    public readonly range: Range;
1906

1907
    /**
1908
     * Get the name of this method.
1909
     */
1910
    public getName(parseMode: ParseMode) {
1911
        return this.name.text;
7✔
1912
    }
1913

1914
    transpile(state: BrsTranspileState) {
1915
        if (this.name.text.toLowerCase() === 'new') {
41✔
1916
            this.ensureSuperConstructorCall(state);
30✔
1917
            //TODO we need to undo this at the bottom of this method
1918
            this.injectFieldInitializersForConstructor(state);
30✔
1919
        }
1920
        //TODO - remove type information from these methods because that doesn't work
1921
        //convert the `super` calls into the proper methods
1922
        const parentClassIndex = state.classStatement.getParentClassIndex(state);
41✔
1923
        const visitor = createVisitor({
41✔
1924
            VariableExpression: e => {
1925
                if (e.name.text.toLocaleLowerCase() === 'super') {
37✔
1926
                    state.editor.setProperty(e.name, 'text', `m.super${parentClassIndex}_new`);
15✔
1927
                }
1928
            },
1929
            DottedGetExpression: e => {
1930
                const beginningVariable = util.findBeginningVariableExpression(e);
12✔
1931
                const lowerName = beginningVariable?.getName(ParseMode.BrighterScript).toLowerCase();
12!
1932
                if (lowerName === 'super') {
12✔
1933
                    state.editor.setProperty(beginningVariable.name, 'text', 'm');
7✔
1934
                    state.editor.setProperty(e.name, 'text', `super${parentClassIndex}_${e.name.text}`);
7✔
1935
                }
1936
            }
1937
        });
1938
        const walkOptions: WalkOptions = { walkMode: WalkMode.visitExpressions };
41✔
1939
        for (const statement of this.func.body.statements) {
41✔
1940
            visitor(statement, undefined);
41✔
1941
            statement.walk(visitor, walkOptions);
41✔
1942
        }
1943
        return this.func.transpile(state);
41✔
1944
    }
1945

1946
    getTypedef(state: BrsTranspileState) {
1947
        const result = [] as string[];
23✔
1948
        for (let annotation of this.annotations ?? []) {
23✔
1949
            result.push(
2✔
1950
                ...annotation.getTypedef(state),
1951
                state.newline,
1952
                state.indent()
1953
            );
1954
        }
1955
        if (this.accessModifier) {
23✔
1956
            result.push(
8✔
1957
                this.accessModifier.text,
1958
                ' '
1959
            );
1960
        }
1961
        if (this.override) {
23✔
1962
            result.push('override ');
1✔
1963
        }
1964
        result.push(
23✔
1965
            ...this.func.getTypedef(state, this.name)
1966
        );
1967
        return result;
23✔
1968
    }
1969

1970
    /**
1971
     * All child classes must call the parent constructor. The type checker will warn users when they don't call it in their own class,
1972
     * but we still need to call it even if they have omitted it. This injects the super call if it's missing
1973
     */
1974
    private ensureSuperConstructorCall(state: BrsTranspileState) {
1975
        //if this class doesn't extend another class, quit here
1976
        if (state.classStatement.getAncestors(state).length === 0) {
30✔
1977
            return;
15✔
1978
        }
1979

1980
        //if the first statement is a call to super, quit here
1981
        let firstStatement = this.func.body.statements[0];
15✔
1982
        if (
15✔
1983
            //is a call statement
1984
            isExpressionStatement(firstStatement) && isCallExpression(firstStatement.expression) &&
23✔
1985
            //is a call to super
1986
            util.findBeginningVariableExpression(firstStatement?.expression.callee as any).name.text.toLowerCase() === 'super'
12!
1987
        ) {
1988
            return;
4✔
1989
        }
1990

1991
        //this is a child class, and the first statement isn't a call to super. Inject one
1992
        const superCall = new ExpressionStatement(
11✔
1993
            new CallExpression(
1994
                new VariableExpression(
1995
                    {
1996
                        kind: TokenKind.Identifier,
1997
                        text: 'super',
1998
                        isReserved: false,
1999
                        range: state.classStatement.name.range,
2000
                        leadingWhitespace: ''
2001
                    }
2002
                ),
2003
                {
2004
                    kind: TokenKind.LeftParen,
2005
                    text: '(',
2006
                    isReserved: false,
2007
                    range: state.classStatement.name.range,
2008
                    leadingWhitespace: ''
2009
                },
2010
                {
2011
                    kind: TokenKind.RightParen,
2012
                    text: ')',
2013
                    isReserved: false,
2014
                    range: state.classStatement.name.range,
2015
                    leadingWhitespace: ''
2016
                },
2017
                []
2018
            )
2019
        );
2020
        state.editor.arrayUnshift(this.func.body.statements, superCall);
11✔
2021
    }
2022

2023
    /**
2024
     * Inject field initializers at the top of the `new` function (after any present `super()` call)
2025
     */
2026
    private injectFieldInitializersForConstructor(state: BrsTranspileState) {
2027
        let startingIndex = state.classStatement.hasParentClass() ? 1 : 0;
30✔
2028

2029
        let newStatements = [] as Statement[];
30✔
2030
        //insert the field initializers in order
2031
        for (let field of state.classStatement.fields) {
30✔
2032
            let thisQualifiedName = { ...field.name };
7✔
2033
            thisQualifiedName.text = 'm.' + field.name.text;
7✔
2034
            if (field.initialValue) {
7✔
2035
                newStatements.push(
6✔
2036
                    new AssignmentStatement(field.equal, thisQualifiedName, field.initialValue, this.func)
2037
                );
2038
            } else {
2039
                //if there is no initial value, set the initial value to `invalid`
2040
                newStatements.push(
1✔
2041
                    new AssignmentStatement(
2042
                        createToken(TokenKind.Equal, '=', field.name.range),
2043
                        thisQualifiedName,
2044
                        createInvalidLiteral('invalid', field.name.range),
2045
                        this.func
2046
                    )
2047
                );
2048
            }
2049
        }
2050
        state.editor.arraySplice(this.func.body.statements, startingIndex, 0, ...newStatements);
30✔
2051
    }
2052

2053
    walk(visitor: WalkVisitor, options: WalkOptions) {
2054
        if (options.walkMode & InternalWalkMode.walkExpressions) {
112✔
2055
            walk(this, 'func', visitor, options);
86✔
2056
        }
2057
    }
2058
}
2059
/**
2060
 * @deprecated use `MethodStatement`
2061
 */
2062
export class ClassMethodStatement extends MethodStatement { }
1✔
2063

2064
export class FieldStatement extends Statement implements TypedefProvider {
1✔
2065

2066
    constructor(
2067
        readonly accessModifier?: Token,
145✔
2068
        readonly name?: Identifier,
145✔
2069
        readonly as?: Token,
145✔
2070
        readonly type?: Token,
145✔
2071
        readonly equal?: Token,
145✔
2072
        readonly initialValue?: Expression
145✔
2073
    ) {
2074
        super();
145✔
2075
        this.range = util.createRangeFromPositions(
145✔
2076
            (this.accessModifier ?? this.name).range.start,
435✔
2077
            (this.initialValue ?? this.type ?? this.as ?? this.name).range.end
1,305✔
2078
        );
2079
    }
2080

2081
    /**
2082
     * Derive a ValueKind from the type token, or the initial value.
2083
     * Defaults to `DynamicType`
2084
     */
2085
    getType() {
2086
        if (this.type) {
63✔
2087
            return util.tokenToBscType(this.type);
29✔
2088
        } else if (isLiteralExpression(this.initialValue)) {
34✔
2089
            return this.initialValue.type;
24✔
2090
        } else {
2091
            return new DynamicType();
10✔
2092
        }
2093
    }
2094

2095
    public readonly range: Range;
2096

2097
    transpile(state: BrsTranspileState): TranspileResult {
2098
        throw new Error('transpile not implemented for ' + Object.getPrototypeOf(this).constructor.name);
×
2099
    }
2100

2101
    getTypedef(state: BrsTranspileState) {
2102
        const result = [];
10✔
2103
        if (this.name) {
10!
2104
            for (let annotation of this.annotations ?? []) {
10✔
2105
                result.push(
2✔
2106
                    ...annotation.getTypedef(state),
2107
                    state.newline,
2108
                    state.indent()
2109
                );
2110
            }
2111

2112
            let type = this.getType();
10✔
2113
            if (isInvalidType(type) || isVoidType(type)) {
10✔
2114
                type = new DynamicType();
1✔
2115
            }
2116

2117
            result.push(
10✔
2118
                this.accessModifier?.text ?? 'public',
60!
2119
                ' ',
2120
                this.name?.text,
30!
2121
                ' as ',
2122
                type.toTypeString()
2123
            );
2124
        }
2125
        return result;
10✔
2126
    }
2127

2128
    walk(visitor: WalkVisitor, options: WalkOptions) {
2129
        if (this.initialValue && options.walkMode & InternalWalkMode.walkExpressions) {
45✔
2130
            walk(this, 'initialValue', visitor, options);
18✔
2131
        }
2132
    }
2133
}
2134
/**
2135
 * @deprecated use `FieldStatement`
2136
 */
2137
export class ClassFieldStatement extends FieldStatement { }
1✔
2138

2139
export type MemberStatement = FieldStatement | MethodStatement;
2140

2141
/**
2142
 * @deprecated use `MemeberStatement`
2143
 */
2144
export type ClassMemberStatement = MemberStatement;
2145

2146
export class TryCatchStatement extends Statement {
1✔
2147
    constructor(
2148
        public tokens: {
23✔
2149
            try: Token;
2150
            endTry?: Token;
2151
        },
2152
        public tryBranch?: Block,
23✔
2153
        public catchStatement?: CatchStatement
23✔
2154
    ) {
2155
        super();
23✔
2156
    }
2157

2158
    public get range() {
2159
        return util.createRangeFromPositions(
20✔
2160
            this.tokens.try.range.start,
2161
            (this.tokens.endTry ?? this.catchStatement ?? this.tryBranch ?? this.tokens.try).range.end
180!
2162
        );
2163
    }
2164

2165
    public transpile(state: BrsTranspileState): TranspileResult {
2166
        return [
2✔
2167
            state.transpileToken(this.tokens.try),
2168
            ...this.tryBranch.transpile(state),
2169
            state.newline,
2170
            state.indent(),
2171
            ...(this.catchStatement?.transpile(state) ?? ['catch']),
12!
2172
            state.newline,
2173
            state.indent(),
2174
            state.transpileToken(this.tokens.endTry)
2175
        ];
2176
    }
2177

2178
    public walk(visitor: WalkVisitor, options: WalkOptions) {
2179
        if (this.tryBranch && options.walkMode & InternalWalkMode.walkStatements) {
6!
2180
            walk(this, 'tryBranch', visitor, options);
6✔
2181
            walk(this, 'catchStatement', visitor, options);
6✔
2182
        }
2183
    }
2184
}
2185

2186
export class CatchStatement extends Statement {
1✔
2187
    constructor(
2188
        public tokens: {
21✔
2189
            catch: Token;
2190
        },
2191
        public exceptionVariable?: Identifier,
21✔
2192
        public catchBranch?: Block
21✔
2193
    ) {
2194
        super();
21✔
2195
    }
2196

2197
    public get range() {
2198
        return util.createRangeFromPositions(
1✔
2199
            this.tokens.catch.range.start,
2200
            (this.catchBranch ?? this.exceptionVariable ?? this.tokens.catch).range.end
6!
2201
        );
2202
    }
2203

2204
    public transpile(state: BrsTranspileState): TranspileResult {
2205
        return [
2✔
2206
            state.transpileToken(this.tokens.catch),
2207
            ' ',
2208
            this.exceptionVariable?.text ?? 'e',
12!
2209
            ...(this.catchBranch?.transpile(state) ?? [])
12!
2210
        ];
2211
    }
2212

2213
    public walk(visitor: WalkVisitor, options: WalkOptions) {
2214
        if (this.catchBranch && options.walkMode & InternalWalkMode.walkStatements) {
6!
2215
            walk(this, 'catchBranch', visitor, options);
6✔
2216
        }
2217
    }
2218
}
2219

2220
export class ThrowStatement extends Statement {
1✔
2221
    constructor(
2222
        public throwToken: Token,
5✔
2223
        public expression?: Expression
5✔
2224
    ) {
2225
        super();
5✔
2226
        this.range = util.createRangeFromPositions(
5✔
2227
            this.throwToken.range.start,
2228
            (this.expression ?? this.throwToken).range.end
15✔
2229
        );
2230
    }
2231
    public range: Range;
2232

2233
    public transpile(state: BrsTranspileState) {
2234
        const result = [
2✔
2235
            state.transpileToken(this.throwToken),
2236
            ' '
2237
        ];
2238

2239
        //if we have an expression, transpile it
2240
        if (this.expression) {
2!
2241
            result.push(
2✔
2242
                ...this.expression.transpile(state)
2243
            );
2244

2245
            //no expression found. Rather than emit syntax errors, provide a generic error message
2246
        } else {
2247
            result.push('"An error has occurred"');
×
2248
        }
2249
        return result;
2✔
2250
    }
2251

2252
    public walk(visitor: WalkVisitor, options: WalkOptions) {
2253
        if (this.expression && options.walkMode & InternalWalkMode.walkExpressions) {
4✔
2254
            walk(this, 'expression', visitor, options);
2✔
2255
        }
2256
    }
2257
}
2258

2259

2260
export class EnumStatement extends Statement implements TypedefProvider {
1✔
2261

2262
    constructor(
2263
        public tokens: {
70✔
2264
            enum: Token;
2265
            name: Identifier;
2266
            endEnum: Token;
2267
        },
2268
        public body: Array<EnumMemberStatement | CommentStatement>
70✔
2269
    ) {
2270
        super();
70✔
2271
        this.body = this.body ?? [];
70!
2272
    }
2273

2274
    public get range() {
2275
        return util.createRangeFromPositions(
×
2276
            this.tokens.enum.range.start ?? Position.create(0, 0),
×
2277
            (this.tokens.endEnum ?? this.tokens.name ?? this.tokens.enum).range.end
×
2278
        );
2279
    }
2280

2281
    public getMembers() {
2282
        const result = [] as EnumMemberStatement[];
117✔
2283
        for (const statement of this.body) {
117✔
2284
            if (isEnumMemberStatement(statement)) {
287✔
2285
                result.push(statement);
272✔
2286
            }
2287
        }
2288
        return result;
117✔
2289
    }
2290

2291
    /**
2292
     * Get a map of member names and their values.
2293
     * All values are stored as their AST LiteralExpression representation (i.e. string enum values include the wrapping quotes)
2294
     */
2295
    public getMemberValueMap() {
2296
        const result = new Map<string, string>();
38✔
2297
        const members = this.getMembers();
38✔
2298
        let currentIntValue = 0;
38✔
2299
        for (const member of members) {
38✔
2300
            //if there is no value, assume an integer and increment the int counter
2301
            if (!member.value) {
111✔
2302
                result.set(member.name?.toLowerCase(), currentIntValue.toString());
32!
2303
                currentIntValue++;
32✔
2304

2305
                //if explicit integer value, use it and increment the int counter
2306
            } else if (isLiteralExpression(member.value) && member.value.token.kind === TokenKind.IntegerLiteral) {
79✔
2307
                //try parsing as integer literal, then as hex integer literal.
2308
                let tokenIntValue = util.parseInt(member.value.token.text) ?? util.parseInt(member.value.token.text.replace(/&h/i, '0x'));
21✔
2309
                if (tokenIntValue !== undefined) {
21!
2310
                    currentIntValue = tokenIntValue;
21✔
2311
                    currentIntValue++;
21✔
2312
                }
2313
                result.set(member.name?.toLowerCase(), member.value.token.text);
21!
2314

2315
                //simple unary expressions (like `-1`)
2316
            } else if (isUnaryExpression(member.value) && isLiteralExpression(member.value.right)) {
58✔
2317
                result.set(member.name?.toLowerCase(), member.value.operator.text + member.value.right.token.text);
1!
2318

2319
                //all other values
2320
            } else {
2321
                result.set(member.name?.toLowerCase(), (member.value as LiteralExpression)?.token?.text ?? 'invalid');
57!
2322
            }
2323
        }
2324
        return result;
38✔
2325
    }
2326

2327
    public getMemberValue(name: string) {
2328
        return this.getMemberValueMap().get(name.toLowerCase());
35✔
2329
    }
2330

2331
    /**
2332
     * The name of the enum (without the namespace prefix)
2333
     */
2334
    public get name() {
2335
        return this.tokens.name?.text;
16!
2336
    }
2337

2338
    /**
2339
     * The name of the enum WITH its leading namespace (if applicable)
2340
     */
2341
    public get fullName() {
2342
        const name = this.tokens.name?.text;
191!
2343
        if (name) {
191!
2344
            const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
191✔
2345

2346
            if (namespace) {
191✔
2347
                let namespaceName = namespace.getName(ParseMode.BrighterScript);
65✔
2348
                return `${namespaceName}.${name}`;
65✔
2349
            } else {
2350
                return name;
126✔
2351
            }
2352
        } else {
2353
            //return undefined which will allow outside callers to know that this doesn't have a name
2354
            return undefined;
×
2355
        }
2356
    }
2357

2358
    transpile(state: BrsTranspileState) {
2359
        //enum declarations do not exist at runtime, so don't transpile anything...
2360
        return [];
12✔
2361
    }
2362

2363
    getTypedef(state: BrsTranspileState) {
2364
        const result = [] as TranspileResult;
×
2365
        for (let annotation of this.annotations ?? []) {
×
2366
            result.push(
×
2367
                ...annotation.getTypedef(state),
2368
                state.newline,
2369
                state.indent()
2370
            );
2371
        }
2372
        result.push(
×
2373
            this.tokens.enum.text ?? 'enum',
×
2374
            ' ',
2375
            this.tokens.name.text
2376
        );
2377
        result.push(state.newline);
×
2378
        state.blockDepth++;
×
2379
        for (const member of this.body) {
×
2380
            if (isTypedefProvider(member)) {
×
2381
                result.push(
×
2382
                    state.indent(),
2383
                    ...member.getTypedef(state),
2384
                    state.newline
2385
                );
2386
            }
2387
        }
2388
        state.blockDepth--;
×
2389
        result.push(
×
2390
            state.indent(),
2391
            this.tokens.endEnum.text ?? 'end enum'
×
2392
        );
2393
        return result;
×
2394
    }
2395

2396
    walk(visitor: WalkVisitor, options: WalkOptions) {
2397
        if (options.walkMode & InternalWalkMode.walkStatements) {
47!
2398
            walkArray(this.body, visitor, options, this);
47✔
2399

2400
        }
2401
    }
2402
}
2403

2404
export class EnumMemberStatement extends Statement implements TypedefProvider {
1✔
2405

2406
    public constructor(
2407
        public tokens: {
138✔
2408
            name: Identifier;
2409
            equal?: Token;
2410
        },
2411
        public value?: Expression
138✔
2412
    ) {
2413
        super();
138✔
2414
    }
2415

2416
    /**
2417
     * The name of the member
2418
     */
2419
    public get name() {
2420
        return this.tokens.name.text;
278✔
2421
    }
2422

2423
    public get range() {
2424
        return util.createRangeFromPositions(
40✔
2425
            (this.tokens.name ?? this.tokens.equal ?? this.value).range.start ?? Position.create(0, 0),
360!
2426
            (this.value ?? this.tokens.equal ?? this.tokens.name).range.end
240!
2427
        );
2428
    }
2429

2430
    public transpile(state: BrsTranspileState): TranspileResult {
2431
        return [];
×
2432
    }
2433

2434
    getTypedef(state: BrsTranspileState): (string | SourceNode)[] {
2435
        const result = [
×
2436
            this.tokens.name.text
2437
        ] as TranspileResult;
2438
        if (this.tokens.equal) {
×
2439
            result.push(' ', this.tokens.equal.text, ' ');
×
2440
            if (this.value) {
×
2441
                result.push(
×
2442
                    ...this.value.transpile(state)
2443
                );
2444
            }
2445
        }
2446
        return result;
×
2447
    }
2448

2449
    walk(visitor: WalkVisitor, options: WalkOptions) {
2450
        if (this.value && options.walkMode & InternalWalkMode.walkExpressions) {
86✔
2451
            walk(this, 'value', visitor, options);
47✔
2452
        }
2453
    }
2454
}
2455

2456
export class ConstStatement extends Statement implements TypedefProvider {
1✔
2457

2458
    public constructor(
2459
        public tokens: {
25✔
2460
            const: Token;
2461
            name: Identifier;
2462
            equals: Token;
2463
        },
2464
        public value: Expression
25✔
2465
    ) {
2466
        super();
25✔
2467
        this.range = util.createBoundingRange(this.tokens.const, this.tokens.name, this.tokens.equals, this.value);
25✔
2468
    }
2469

2470
    public range: Range;
2471

2472
    public get name() {
2473
        return this.tokens.name.text;
3✔
2474
    }
2475

2476
    /**
2477
     * The name of the statement WITH its leading namespace (if applicable)
2478
     */
2479
    public get fullName() {
2480
        const name = this.tokens.name?.text;
49!
2481
        if (name) {
49!
2482
            const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
49✔
2483
            if (namespace) {
49✔
2484
                let namespaceName = namespace.getName(ParseMode.BrighterScript);
33✔
2485
                return `${namespaceName}.${name}`;
33✔
2486
            } else {
2487
                return name;
16✔
2488
            }
2489
        } else {
2490
            //return undefined which will allow outside callers to know that this doesn't have a name
2491
            return undefined;
×
2492
        }
2493
    }
2494

2495
    public transpile(state: BrsTranspileState): TranspileResult {
2496
        //const declarations don't exist at runtime, so just transpile empty
2497
        return [];
8✔
2498
    }
2499

2500
    getTypedef(state: BrsTranspileState): (string | SourceNode)[] {
2501
        return [
3✔
2502
            state.tokenToSourceNode(this.tokens.const),
2503
            ' ',
2504
            state.tokenToSourceNode(this.tokens.name),
2505
            ' ',
2506
            state.tokenToSourceNode(this.tokens.equals),
2507
            ' ',
2508
            ...this.value.transpile(state)
2509
        ];
2510
    }
2511

2512
    walk(visitor: WalkVisitor, options: WalkOptions) {
2513
        if (this.value && options.walkMode & InternalWalkMode.walkExpressions) {
28!
2514
            walk(this, 'value', visitor, options);
28✔
2515
        }
2516
    }
2517
}
2518

2519
export class ContinueStatement extends Statement {
1✔
2520
    constructor(
2521
        public tokens: {
8✔
2522
            continue: Token;
2523
            loopType: Token;
2524
        }
2525
    ) {
2526
        super();
8✔
2527
    }
2528

2529
    public get range() {
2530
        return this.tokens.continue.range;
9✔
2531
    }
2532

2533
    transpile(state: BrsTranspileState) {
2534
        return [
2✔
2535
            state.sourceNode(this.tokens.continue, this.tokens.continue?.text ?? 'continue'),
12!
2536
            this.tokens.loopType?.leadingWhitespace ?? ' ',
12!
2537
            state.sourceNode(this.tokens.continue, this.tokens.loopType?.text)
6!
2538
        ];
2539
    }
2540
    walk(visitor: WalkVisitor, options: WalkOptions) {
2541
        //nothing to walk
2542
    }
2543
}
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