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

rokucommunity / brighterscript / #13774

30 Jan 2024 04:02PM UTC coverage: 88.056% (-0.1%) from 88.176%
#13774

push

TwitchBronBron
0.65.19

5819 of 7088 branches covered (82.1%)

Branch coverage included in aggregate %.

8646 of 9339 relevant lines covered (92.58%)

1683.59 hits per line

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

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

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

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

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

51
    public symbolTable = new SymbolTable('Body', () => this.parent?.getSymbolTable());
3,728✔
52

53
    public get range() {
54
        //this needs to be a getter because the body has its statements pushed to it after being constructed
55
        return util.createBoundingRange(
9✔
56
            ...(this.statements ?? [])
27!
57
        );
58
    }
59

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

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

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

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

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

88
            result.push(...statement.transpile(state));
370✔
89
        }
90
        return result;
268✔
91
    }
92

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

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

115
export class AssignmentStatement extends Statement {
1✔
116
    constructor(
117
        readonly equals: Token,
848✔
118
        readonly name: Identifier,
848✔
119
        readonly value: Expression
848✔
120
    ) {
121
        super();
848✔
122
        this.range = util.createBoundingRange(name, equals, value);
848✔
123
    }
124

125
    public readonly range: Range;
126

127
    /**
128
     * Get the name of the wrapping namespace (if it exists)
129
     * @deprecated use `.findAncestor(isFunctionExpression)` instead.
130
     */
131
    public get containingFunction() {
132
        return this.findAncestor<FunctionExpression>(isFunctionExpression);
395✔
133
    }
134

135
    transpile(state: BrsTranspileState) {
136
        //if the value is a compound assignment, just transpile the expression itself
137
        if (CompoundAssignmentOperators.includes((this.value as BinaryExpression)?.operator?.kind)) {
217!
138
            return this.value.transpile(state);
20✔
139
        } else {
140
            return [
197✔
141
                state.transpileToken(this.name),
142
                ' ',
143
                state.transpileToken(this.equals),
144
                ' ',
145
                ...this.value.transpile(state)
146
            ];
147
        }
148
    }
149

150
    walk(visitor: WalkVisitor, options: WalkOptions) {
151
        if (options.walkMode & InternalWalkMode.walkExpressions) {
1,714✔
152
            walk(this, 'value', visitor, options);
1,315✔
153
        }
154
    }
155
}
156

157
export class Block extends Statement {
1✔
158
    constructor(
159
        readonly statements: Statement[],
1,831✔
160
        readonly startingRange: Range
1,831✔
161
    ) {
162
        super();
1,831✔
163
        this.range = util.createBoundingRange(
1,831✔
164
            { range: this.startingRange },
165
            ...(statements ?? [])
5,493!
166
        );
167
    }
168

169
    public readonly range: Range;
170

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

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

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

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

204
    walk(visitor: WalkVisitor, options: WalkOptions) {
205
        if (options.walkMode & InternalWalkMode.walkStatements) {
4,174✔
206
            walkArray(this.statements, visitor, options, this);
4,168✔
207
        }
208
    }
209
}
210

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

219
    public readonly range: Range;
220

221
    transpile(state: BrsTranspileState) {
222
        return this.expression.transpile(state);
44✔
223
    }
224

225
    walk(visitor: WalkVisitor, options: WalkOptions) {
226
        if (options.walkMode & InternalWalkMode.walkExpressions) {
737✔
227
            walk(this, 'expression', visitor, options);
551✔
228
        }
229
    }
230
}
231

232
export class CommentStatement extends Statement implements Expression, TypedefProvider {
1✔
233
    constructor(
234
        public comments: Token[]
240✔
235
    ) {
236
        super();
240✔
237
        this.visitMode = InternalWalkMode.visitStatements | InternalWalkMode.visitExpressions;
240✔
238
        if (this.comments?.length > 0) {
240!
239
            this.range = util.createBoundingRange(
239✔
240
                ...this.comments
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 = [];
98✔
253
        for (let i = 0; i < this.comments.length; i++) {
98✔
254
            let comment = this.comments[i];
99✔
255
            if (i > 0) {
99✔
256
                result.push(state.indent());
1✔
257
            }
258
            result.push(
99✔
259
                state.transpileToken(comment)
260
            );
261
            //add newline for all except final comment
262
            if (i < this.comments.length - 1) {
99✔
263
                result.push('\n');
1✔
264
            }
265
        }
266
        return result;
98✔
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: {
3✔
281
            exitFor: Token;
282
        }
283
    ) {
284
        super();
3✔
285
        this.range = this.tokens.exitFor.range;
3✔
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: {
6✔
305
            exitWhile: Token;
306
        }
307
    ) {
308
        super();
6✔
309
        this.range = this.tokens.exitWhile.range;
6✔
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,644✔
328
        public func: FunctionExpression
1,644✔
329
    ) {
330
        super();
1,644✔
331
        this.range = this.func.range;
1,644✔
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,827✔
341
        if (namespace) {
1,827✔
342
            let delimiter = parseMode === ParseMode.BrighterScript ? '.' : '_';
272✔
343
            let namespaceName = namespace.getName(parseMode);
272✔
344
            return namespaceName + delimiter + this.name?.text;
272✔
345
        } else {
346
            return this.name.text;
1,555✔
347
        }
348
    }
349

350
    /**
351
     * Get the name of the wrapping namespace (if it exists)
352
     * @deprecated use `.findAncestor(isNamespaceStatement)` instead.
353
     */
354
    public get namespaceName() {
355
        return this.findAncestor<NamespaceStatement>(isNamespaceStatement)?.nameExpression;
×
356
    }
357

358
    transpile(state: BrsTranspileState) {
359
        //create a fake token using the full transpiled name
360
        let nameToken = {
247✔
361
            ...this.name,
362
            text: this.getName(ParseMode.BrightScript)
363
        };
364

365
        return this.func.transpile(state, nameToken);
247✔
366
    }
367

368
    getTypedef(state: BrsTranspileState) {
369
        let result = [];
6✔
370
        for (let annotation of this.annotations ?? []) {
6✔
371
            result.push(
2✔
372
                ...annotation.getTypedef(state),
373
                state.newline,
374
                state.indent()
375
            );
376
        }
377

378
        result.push(
6✔
379
            ...this.func.getTypedef(state)
380
        );
381
        return result;
6✔
382
    }
383

384
    walk(visitor: WalkVisitor, options: WalkOptions) {
385
        if (options.walkMode & InternalWalkMode.walkExpressions) {
2,004✔
386
            walk(this, 'func', visitor, options);
1,978✔
387
        }
388
    }
389
}
390

391
export class IfStatement extends Statement {
1✔
392
    constructor(
393
        readonly tokens: {
183✔
394
            if: Token;
395
            then?: Token;
396
            else?: Token;
397
            endIf?: Token;
398
        },
399
        readonly condition: Expression,
183✔
400
        readonly thenBranch: Block,
183✔
401
        readonly elseBranch?: IfStatement | Block,
183✔
402
        readonly isInline?: boolean
183✔
403
    ) {
404
        super();
183✔
405
        this.range = util.createBoundingRange(
183✔
406
            tokens.if,
407
            condition,
408
            tokens.then,
409
            thenBranch,
410
            tokens.else,
411
            elseBranch,
412
            tokens.endIf
413
        );
414
    }
415
    public readonly range: Range;
416

417
    transpile(state: BrsTranspileState) {
418
        let results = [];
46✔
419
        //if   (already indented by block)
420
        results.push(state.transpileToken(this.tokens.if));
46✔
421
        results.push(' ');
46✔
422
        //conditions
423
        results.push(...this.condition.transpile(state));
46✔
424
        //then
425
        if (this.tokens.then) {
46✔
426
            results.push(' ');
36✔
427
            results.push(
36✔
428
                state.transpileToken(this.tokens.then)
429
            );
430
        }
431
        state.lineage.unshift(this);
46✔
432

433
        //if statement body
434
        let thenNodes = this.thenBranch.transpile(state);
46✔
435
        state.lineage.shift();
46✔
436
        if (thenNodes.length > 0) {
46✔
437
            results.push(thenNodes);
35✔
438
        }
439
        results.push('\n');
46✔
440

441
        //else branch
442
        if (this.tokens.else) {
46✔
443
            //else
444
            results.push(
27✔
445
                state.indent(),
446
                state.transpileToken(this.tokens.else)
447
            );
448
        }
449

450
        if (this.elseBranch) {
46✔
451
            if (isIfStatement(this.elseBranch)) {
27✔
452
                //chained elseif
453
                state.lineage.unshift(this.elseBranch);
18✔
454
                let body = this.elseBranch.transpile(state);
18✔
455
                state.lineage.shift();
18✔
456

457
                if (body.length > 0) {
18!
458
                    //zero or more spaces between the `else` and the `if`
459
                    results.push(this.elseBranch.tokens.if.leadingWhitespace);
18✔
460
                    results.push(...body);
18✔
461

462
                    // stop here because chained if will transpile the rest
463
                    return results;
18✔
464
                } else {
465
                    results.push('\n');
×
466
                }
467

468
            } else {
469
                //else body
470
                state.lineage.unshift(this.tokens.else);
9✔
471
                let body = this.elseBranch.transpile(state);
9✔
472
                state.lineage.shift();
9✔
473

474
                if (body.length > 0) {
9✔
475
                    results.push(...body);
7✔
476
                }
477
                results.push('\n');
9✔
478
            }
479
        }
480

481
        //end if
482
        results.push(state.indent());
28✔
483
        if (this.tokens.endIf) {
28!
484
            results.push(
28✔
485
                state.transpileToken(this.tokens.endIf)
486
            );
487
        } else {
488
            results.push('end if');
×
489
        }
490
        return results;
28✔
491
    }
492

493
    walk(visitor: WalkVisitor, options: WalkOptions) {
494
        if (options.walkMode & InternalWalkMode.walkExpressions) {
363✔
495
            walk(this, 'condition', visitor, options);
268✔
496
        }
497
        if (options.walkMode & InternalWalkMode.walkStatements) {
363✔
498
            walk(this, 'thenBranch', visitor, options);
361✔
499
        }
500
        if (this.elseBranch && options.walkMode & InternalWalkMode.walkStatements) {
363✔
501
            walk(this, 'elseBranch', visitor, options);
187✔
502
        }
503
    }
504
}
505

506
export class IncrementStatement extends Statement {
1✔
507
    constructor(
508
        readonly value: Expression,
16✔
509
        readonly operator: Token
16✔
510
    ) {
511
        super();
16✔
512
        this.range = util.createBoundingRange(
16✔
513
            value,
514
            operator
515
        );
516
    }
517

518
    public readonly range: Range;
519

520
    transpile(state: BrsTranspileState) {
521
        return [
7✔
522
            ...this.value.transpile(state),
523
            state.transpileToken(this.operator)
524
        ];
525
    }
526

527
    walk(visitor: WalkVisitor, options: WalkOptions) {
528
        if (options.walkMode & InternalWalkMode.walkExpressions) {
29✔
529
            walk(this, 'value', visitor, options);
23✔
530
        }
531
    }
532
}
533

534
/** Used to indent the current `print` position to the next 16-character-width output zone. */
535
export interface PrintSeparatorTab extends Token {
536
    kind: TokenKind.Comma;
537
}
538

539
/** Used to insert a single whitespace character at the current `print` position. */
540
export interface PrintSeparatorSpace extends Token {
541
    kind: TokenKind.Semicolon;
542
}
543

544
/**
545
 * Represents a `print` statement within BrightScript.
546
 */
547
export class PrintStatement extends Statement {
1✔
548
    /**
549
     * Creates a new internal representation of a BrightScript `print` statement.
550
     * @param tokens the tokens for this statement
551
     * @param tokens.print a print token
552
     * @param expressions an array of expressions or `PrintSeparator`s to be evaluated and printed.
553
     */
554
    constructor(
555
        readonly tokens: {
541✔
556
            print: Token;
557
        },
558
        readonly expressions: Array<Expression | PrintSeparatorTab | PrintSeparatorSpace>
541✔
559
    ) {
560
        super();
541✔
561
        this.range = util.createBoundingRange(
541✔
562
            tokens.print,
563
            ...(expressions ?? [])
1,623!
564
        );
565
    }
566

567
    public readonly range: Range;
568

569
    transpile(state: BrsTranspileState) {
570
        let result = [
162✔
571
            state.transpileToken(this.tokens.print),
572
            ' '
573
        ];
574
        for (let i = 0; i < this.expressions.length; i++) {
162✔
575
            const expressionOrSeparator: any = this.expressions[i];
226✔
576
            if (expressionOrSeparator.transpile) {
226✔
577
                result.push(...(expressionOrSeparator as ExpressionStatement).transpile(state));
200✔
578
            } else {
579
                result.push(
26✔
580
                    state.tokenToSourceNode(expressionOrSeparator)
581
                );
582
            }
583
            //if there's an expression after us, add a space
584
            if ((this.expressions[i + 1] as any)?.transpile) {
226✔
585
                result.push(' ');
38✔
586
            }
587
        }
588
        return result;
162✔
589
    }
590

591
    walk(visitor: WalkVisitor, options: WalkOptions) {
592
        if (options.walkMode & InternalWalkMode.walkExpressions) {
1,194✔
593
            //sometimes we have semicolon Tokens in the expressions list (should probably fix that...), so only walk the actual expressions
594
            walkArray(this.expressions, visitor, options, this, (item) => isExpression(item as any));
1,017✔
595
        }
596
    }
597
}
598

599
export class DimStatement extends Statement {
1✔
600
    constructor(
601
        public dimToken: Token,
39✔
602
        public identifier?: Identifier,
39✔
603
        public openingSquare?: Token,
39✔
604
        public dimensions?: Expression[],
39✔
605
        public closingSquare?: Token
39✔
606
    ) {
607
        super();
39✔
608
        this.range = util.createBoundingRange(
39✔
609
            dimToken,
610
            identifier,
611
            openingSquare,
612
            ...(dimensions ?? []),
117!
613
            closingSquare
614
        );
615
    }
616
    public range: Range;
617

618
    public transpile(state: BrsTranspileState) {
619
        let result = [
14✔
620
            state.transpileToken(this.dimToken),
621
            ' ',
622
            state.transpileToken(this.identifier),
623
            state.transpileToken(this.openingSquare)
624
        ];
625
        for (let i = 0; i < this.dimensions.length; i++) {
14✔
626
            if (i > 0) {
30✔
627
                result.push(', ');
16✔
628
            }
629
            result.push(
30✔
630
                ...this.dimensions[i].transpile(state)
631
            );
632
        }
633
        result.push(state.transpileToken(this.closingSquare));
14✔
634
        return result;
14✔
635
    }
636

637
    public walk(visitor: WalkVisitor, options: WalkOptions) {
638
        if (this.dimensions?.length > 0 && options.walkMode & InternalWalkMode.walkExpressions) {
75!
639
            walkArray(this.dimensions, visitor, options, this);
50✔
640

641
        }
642
    }
643
}
644

645
export class GotoStatement extends Statement {
1✔
646
    constructor(
647
        readonly tokens: {
8✔
648
            goto: Token;
649
            label: Token;
650
        }
651
    ) {
652
        super();
8✔
653
        this.range = util.createBoundingRange(
8✔
654
            tokens.goto,
655
            tokens.label
656
        );
657
    }
658

659
    public readonly range: Range;
660

661
    transpile(state: BrsTranspileState) {
662
        return [
1✔
663
            state.transpileToken(this.tokens.goto),
664
            ' ',
665
            state.transpileToken(this.tokens.label)
666
        ];
667
    }
668

669
    walk(visitor: WalkVisitor, options: WalkOptions) {
670
        //nothing to walk
671
    }
672
}
673

674
export class LabelStatement extends Statement {
1✔
675
    constructor(
676
        readonly tokens: {
8✔
677
            identifier: Token;
678
            colon: Token;
679
        }
680
    ) {
681
        super();
8✔
682
        this.range = util.createBoundingRange(
8✔
683
            tokens.identifier,
684
            tokens.colon
685
        );
686
    }
687

688
    public readonly range: Range;
689

690
    transpile(state: BrsTranspileState) {
691
        return [
1✔
692
            state.transpileToken(this.tokens.identifier),
693
            state.transpileToken(this.tokens.colon)
694

695
        ];
696
    }
697

698
    walk(visitor: WalkVisitor, options: WalkOptions) {
699
        //nothing to walk
700
    }
701
}
702

703
export class ReturnStatement extends Statement {
1✔
704
    constructor(
705
        readonly tokens: {
148✔
706
            return: Token;
707
        },
708
        readonly value?: Expression
148✔
709
    ) {
710
        super();
148✔
711
        this.range = util.createBoundingRange(
148✔
712
            tokens.return,
713
            value
714
        );
715
    }
716

717
    public readonly range: Range;
718

719
    transpile(state: BrsTranspileState) {
720
        let result = [];
11✔
721
        result.push(
11✔
722
            state.transpileToken(this.tokens.return)
723
        );
724
        if (this.value) {
11!
725
            result.push(' ');
11✔
726
            result.push(...this.value.transpile(state));
11✔
727
        }
728
        return result;
11✔
729
    }
730

731
    walk(visitor: WalkVisitor, options: WalkOptions) {
732
        if (options.walkMode & InternalWalkMode.walkExpressions) {
284✔
733
            walk(this, 'value', visitor, options);
226✔
734
        }
735
    }
736
}
737

738
export class EndStatement extends Statement {
1✔
739
    constructor(
740
        readonly tokens: {
7✔
741
            end: Token;
742
        }
743
    ) {
744
        super();
7✔
745
        this.range = tokens.end.range;
7✔
746
    }
747

748
    public readonly range: Range;
749

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

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

761
export class StopStatement extends Statement {
1✔
762
    constructor(
763
        readonly tokens: {
15✔
764
            stop: Token;
765
        }
766
    ) {
767
        super();
15✔
768
        this.range = tokens?.stop?.range;
15!
769
    }
770

771
    public readonly range: Range;
772

773
    transpile(state: BrsTranspileState) {
774
        return [
1✔
775
            state.transpileToken(this.tokens.stop)
776
        ];
777
    }
778

779
    walk(visitor: WalkVisitor, options: WalkOptions) {
780
        //nothing to walk
781
    }
782
}
783

784
export class ForStatement extends Statement {
1✔
785
    constructor(
786
        public forToken: Token,
27✔
787
        public counterDeclaration: AssignmentStatement,
27✔
788
        public toToken: Token,
27✔
789
        public finalValue: Expression,
27✔
790
        public body: Block,
27✔
791
        public endForToken: Token,
27✔
792
        public stepToken?: Token,
27✔
793
        public increment?: Expression
27✔
794
    ) {
795
        super();
27✔
796
        this.range = util.createBoundingRange(
27✔
797
            forToken,
798
            counterDeclaration,
799
            toToken,
800
            finalValue,
801
            stepToken,
802
            increment,
803
            body,
804
            endForToken
805
        );
806
    }
807

808
    public readonly range: Range;
809

810
    transpile(state: BrsTranspileState) {
811
        let result = [];
7✔
812
        //for
813
        result.push(
7✔
814
            state.transpileToken(this.forToken),
815
            ' '
816
        );
817
        //i=1
818
        result.push(
7✔
819
            ...this.counterDeclaration.transpile(state),
820
            ' '
821
        );
822
        //to
823
        result.push(
7✔
824
            state.transpileToken(this.toToken),
825
            ' '
826
        );
827
        //final value
828
        result.push(this.finalValue.transpile(state));
7✔
829
        //step
830
        if (this.stepToken) {
7✔
831
            result.push(
3✔
832
                ' ',
833
                state.transpileToken(this.stepToken),
834
                ' ',
835
                this.increment.transpile(state)
836
            );
837
        }
838
        //loop body
839
        state.lineage.unshift(this);
7✔
840
        result.push(...this.body.transpile(state));
7✔
841
        state.lineage.shift();
7✔
842

843
        // add new line before "end for"
844
        result.push('\n');
7✔
845

846
        //end for
847
        result.push(
7✔
848
            state.indent(),
849
            state.transpileToken(this.endForToken)
850
        );
851

852
        return result;
7✔
853
    }
854

855
    walk(visitor: WalkVisitor, options: WalkOptions) {
856
        if (options.walkMode & InternalWalkMode.walkStatements) {
62✔
857
            walk(this, 'counterDeclaration', visitor, options);
61✔
858
        }
859
        if (options.walkMode & InternalWalkMode.walkExpressions) {
62✔
860
            walk(this, 'finalValue', visitor, options);
43✔
861
            walk(this, 'increment', visitor, options);
43✔
862
        }
863
        if (options.walkMode & InternalWalkMode.walkStatements) {
62✔
864
            walk(this, 'body', visitor, options);
61✔
865
        }
866
    }
867
}
868

869
export class ForEachStatement extends Statement {
1✔
870
    constructor(
871
        readonly tokens: {
18✔
872
            forEach: Token;
873
            in: Token;
874
            endFor: Token;
875
        },
876
        readonly item: Token,
18✔
877
        readonly target: Expression,
18✔
878
        readonly body: Block
18✔
879
    ) {
880
        super();
18✔
881
        this.range = util.createBoundingRange(
18✔
882
            tokens.forEach,
883
            item,
884
            tokens.in,
885
            target,
886
            body,
887
            tokens.endFor
888
        );
889
    }
890

891
    public readonly range: Range;
892

893
    transpile(state: BrsTranspileState) {
894
        let result = [];
4✔
895
        //for each
896
        result.push(
4✔
897
            state.transpileToken(this.tokens.forEach),
898
            ' '
899
        );
900
        //item
901
        result.push(
4✔
902
            state.transpileToken(this.item),
903
            ' '
904
        );
905
        //in
906
        result.push(
4✔
907
            state.transpileToken(this.tokens.in),
908
            ' '
909
        );
910
        //target
911
        result.push(...this.target.transpile(state));
4✔
912
        //body
913
        state.lineage.unshift(this);
4✔
914
        result.push(...this.body.transpile(state));
4✔
915
        state.lineage.shift();
4✔
916

917
        // add new line before "end for"
918
        result.push('\n');
4✔
919

920
        //end for
921
        result.push(
4✔
922
            state.indent(),
923
            state.transpileToken(this.tokens.endFor)
924
        );
925
        return result;
4✔
926
    }
927

928
    walk(visitor: WalkVisitor, options: WalkOptions) {
929
        if (options.walkMode & InternalWalkMode.walkExpressions) {
37✔
930
            walk(this, 'target', visitor, options);
27✔
931
        }
932
        if (options.walkMode & InternalWalkMode.walkStatements) {
37✔
933
            walk(this, 'body', visitor, options);
36✔
934
        }
935
    }
936
}
937

938
export class WhileStatement extends Statement {
1✔
939
    constructor(
940
        readonly tokens: {
18✔
941
            while: Token;
942
            endWhile: Token;
943
        },
944
        readonly condition: Expression,
18✔
945
        readonly body: Block
18✔
946
    ) {
947
        super();
18✔
948
        this.range = util.createBoundingRange(
18✔
949
            tokens.while,
950
            condition,
951
            body,
952
            tokens.endWhile
953
        );
954
    }
955

956
    public readonly range: Range;
957

958
    transpile(state: BrsTranspileState) {
959
        let result = [];
3✔
960
        //while
961
        result.push(
3✔
962
            state.transpileToken(this.tokens.while),
963
            ' '
964
        );
965
        //condition
966
        result.push(
3✔
967
            ...this.condition.transpile(state)
968
        );
969
        state.lineage.unshift(this);
3✔
970
        //body
971
        result.push(...this.body.transpile(state));
3✔
972
        state.lineage.shift();
3✔
973

974
        //trailing newline only if we have body statements
975
        result.push('\n');
3✔
976

977
        //end while
978
        result.push(
3✔
979
            state.indent(),
980
            state.transpileToken(this.tokens.endWhile)
981
        );
982

983
        return result;
3✔
984
    }
985

986
    walk(visitor: WalkVisitor, options: WalkOptions) {
987
        if (options.walkMode & InternalWalkMode.walkExpressions) {
38✔
988
            walk(this, 'condition', visitor, options);
25✔
989
        }
990
        if (options.walkMode & InternalWalkMode.walkStatements) {
38✔
991
            walk(this, 'body', visitor, options);
37✔
992
        }
993
    }
994
}
995

996
export class DottedSetStatement extends Statement {
1✔
997
    constructor(
998
        readonly obj: Expression,
230✔
999
        readonly name: Identifier,
230✔
1000
        readonly value: Expression,
230✔
1001
        readonly dot?: Token
230✔
1002
    ) {
1003
        super();
230✔
1004
        this.range = util.createBoundingRange(
230✔
1005
            obj,
1006
            dot,
1007
            name,
1008
            value
1009
        );
1010
    }
1011

1012
    public readonly range: Range;
1013

1014
    transpile(state: BrsTranspileState) {
1015
        //if the value is a compound assignment, don't add the obj, dot, name, or operator...the expression will handle that
1016
        if (CompoundAssignmentOperators.includes((this.value as BinaryExpression)?.operator?.kind)) {
6!
1017
            return this.value.transpile(state);
1✔
1018
        } else {
1019
            return [
5✔
1020
                //object
1021
                ...this.obj.transpile(state),
1022
                this.dot ? state.tokenToSourceNode(this.dot) : '.',
5!
1023
                //name
1024
                state.transpileToken(this.name),
1025
                ' = ',
1026
                //right-hand-side of assignment
1027
                ...this.value.transpile(state)
1028
            ];
1029
        }
1030
    }
1031

1032
    walk(visitor: WalkVisitor, options: WalkOptions) {
1033
        if (options.walkMode & InternalWalkMode.walkExpressions) {
276✔
1034
            walk(this, 'obj', visitor, options);
253✔
1035
            walk(this, 'value', visitor, options);
253✔
1036
        }
1037
    }
1038
}
1039

1040
export class IndexedSetStatement extends Statement {
1✔
1041
    constructor(
1042
        readonly obj: Expression,
25✔
1043
        readonly index: Expression,
25✔
1044
        readonly value: Expression,
25✔
1045
        readonly openingSquare: Token,
25✔
1046
        readonly closingSquare: Token
25✔
1047
    ) {
1048
        super();
25✔
1049
        this.range = util.createBoundingRange(
25✔
1050
            obj,
1051
            openingSquare,
1052
            index,
1053
            closingSquare,
1054
            value
1055
        );
1056
    }
1057

1058
    public readonly range: Range;
1059

1060
    transpile(state: BrsTranspileState) {
1061
        //if the value is a component assignment, don't add the obj, index or operator...the expression will handle that
1062
        if (CompoundAssignmentOperators.includes((this.value as BinaryExpression)?.operator?.kind)) {
8!
1063
            return this.value.transpile(state);
1✔
1064
        } else {
1065
            return [
7✔
1066
                //obj
1067
                ...this.obj.transpile(state),
1068
                //   [
1069
                state.transpileToken(this.openingSquare),
1070
                //    index
1071
                ...this.index.transpile(state),
1072
                //         ]
1073
                state.transpileToken(this.closingSquare),
1074
                //           =
1075
                ' = ',
1076
                //             value
1077
                ...this.value.transpile(state)
1078
            ];
1079
        }
1080
    }
1081

1082
    walk(visitor: WalkVisitor, options: WalkOptions) {
1083
        if (options.walkMode & InternalWalkMode.walkExpressions) {
58✔
1084
            walk(this, 'obj', visitor, options);
43✔
1085
            walk(this, 'index', visitor, options);
43✔
1086
            walk(this, 'value', visitor, options);
43✔
1087
        }
1088
    }
1089
}
1090

1091
export class LibraryStatement extends Statement implements TypedefProvider {
1✔
1092
    constructor(
1093
        readonly tokens: {
12✔
1094
            library: Token;
1095
            filePath: Token | undefined;
1096
        }
1097
    ) {
1098
        super();
12✔
1099
        this.range = util.createBoundingRange(
12✔
1100
            this.tokens.library,
1101
            this.tokens.filePath
1102
        );
1103
    }
1104

1105
    public readonly range: Range;
1106

1107
    transpile(state: BrsTranspileState) {
1108
        let result = [];
1✔
1109
        result.push(
1✔
1110
            state.transpileToken(this.tokens.library)
1111
        );
1112
        //there will be a parse error if file path is missing, but let's prevent a runtime error just in case
1113
        if (this.tokens.filePath) {
1!
1114
            result.push(
1✔
1115
                ' ',
1116
                state.transpileToken(this.tokens.filePath)
1117
            );
1118
        }
1119
        return result;
1✔
1120
    }
1121

1122
    getTypedef(state: BrsTranspileState) {
1123
        return this.transpile(state);
×
1124
    }
1125

1126
    walk(visitor: WalkVisitor, options: WalkOptions) {
1127
        //nothing to walk
1128
    }
1129
}
1130

1131
export class NamespaceStatement extends Statement implements TypedefProvider {
1✔
1132
    constructor(
1133
        public keyword: Token,
212✔
1134
        // this should technically only be a VariableExpression or DottedGetExpression, but that can be enforced elsewhere
1135
        public nameExpression: NamespacedVariableNameExpression,
212✔
1136
        public body: Body,
212✔
1137
        public endKeyword: Token
212✔
1138
    ) {
1139
        super();
212✔
1140
        this.name = this.getName(ParseMode.BrighterScript);
212✔
1141
        this.symbolTable = new SymbolTable(`NamespaceStatement: '${this.name}'`, () => this.parent?.getSymbolTable());
212!
1142
    }
1143

1144
    /**
1145
     * The string name for this namespace
1146
     */
1147
    public name: string;
1148

1149
    public get range() {
1150
        return this.cacheRange();
104✔
1151
    }
1152
    private _range: Range;
1153

1154
    public cacheRange() {
1155
        if (!this._range) {
315✔
1156
            this._range = util.createBoundingRange(
211✔
1157
                this.keyword,
1158
                this.nameExpression,
1159
                this.body,
1160
                this.endKeyword
1161
            ) ?? interpolatedRange;
211!
1162
        }
1163
        return this._range;
315✔
1164
    }
1165

1166
    public getName(parseMode: ParseMode) {
1167
        const parentNamespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
2,158✔
1168
        let name = this.nameExpression.getName(parseMode);
2,158✔
1169

1170
        if (parentNamespace) {
2,158✔
1171
            const sep = parseMode === ParseMode.BrighterScript ? '.' : '_';
114✔
1172
            name = parentNamespace.getName(parseMode) + sep + name;
114✔
1173
        }
1174

1175
        return name;
2,158✔
1176
    }
1177

1178
    transpile(state: BrsTranspileState) {
1179
        //namespaces don't actually have any real content, so just transpile their bodies
1180
        return this.body.transpile(state);
28✔
1181
    }
1182

1183
    getTypedef(state: BrsTranspileState) {
1184
        let result = [
7✔
1185
            'namespace ',
1186
            ...this.getName(ParseMode.BrighterScript),
1187
            state.newline
1188
        ];
1189
        state.blockDepth++;
7✔
1190
        result.push(
7✔
1191
            ...this.body.getTypedef(state)
1192
        );
1193
        state.blockDepth--;
7✔
1194

1195
        result.push(
7✔
1196
            state.indent(),
1197
            'end namespace'
1198
        );
1199
        return result;
7✔
1200
    }
1201

1202
    walk(visitor: WalkVisitor, options: WalkOptions) {
1203
        if (options.walkMode & InternalWalkMode.walkExpressions) {
391✔
1204
            walk(this, 'nameExpression', visitor, options);
387✔
1205
        }
1206

1207
        if (this.body.statements.length > 0 && options.walkMode & InternalWalkMode.walkStatements) {
391✔
1208
            walk(this, 'body', visitor, options);
340✔
1209
        }
1210
    }
1211
}
1212

1213
export class ImportStatement extends Statement implements TypedefProvider {
1✔
1214
    constructor(
1215
        readonly importToken: Token,
35✔
1216
        readonly filePathToken: Token
35✔
1217
    ) {
1218
        super();
35✔
1219
        this.range = util.createBoundingRange(
35✔
1220
            importToken,
1221
            filePathToken
1222
        );
1223
        if (this.filePathToken) {
35✔
1224
            //remove quotes
1225
            this.filePath = this.filePathToken.text.replace(/"/g, '');
33✔
1226
            //adjust the range to exclude the quotes
1227
            this.filePathToken.range = util.createRange(
33✔
1228
                this.filePathToken.range.start.line,
1229
                this.filePathToken.range.start.character + 1,
1230
                this.filePathToken.range.end.line,
1231
                this.filePathToken.range.end.character - 1
1232
            );
1233
        }
1234
    }
1235
    public filePath: string;
1236
    public range: Range;
1237

1238
    transpile(state: BrsTranspileState) {
1239
        //The xml files are responsible for adding the additional script imports, but
1240
        //add the import statement as a comment just for debugging purposes
1241
        return [
2✔
1242
            `'`,
1243
            state.transpileToken(this.importToken),
1244
            ' ',
1245
            state.transpileToken(this.filePathToken)
1246
        ];
1247
    }
1248

1249
    /**
1250
     * Get the typedef for this statement
1251
     */
1252
    public getTypedef(state: BrsTranspileState) {
1253
        return [
3✔
1254
            this.importToken.text,
1255
            ' ',
1256
            //replace any `.bs` extension with `.brs`
1257
            this.filePathToken.text.replace(/\.bs"?$/i, '.brs"')
1258
        ];
1259
    }
1260

1261
    walk(visitor: WalkVisitor, options: WalkOptions) {
1262
        //nothing to walk
1263
    }
1264
}
1265

1266
export class InterfaceStatement extends Statement implements TypedefProvider {
1✔
1267
    constructor(
1268
        interfaceToken: Token,
1269
        name: Identifier,
1270
        extendsToken: Token,
1271
        public parentInterfaceName: NamespacedVariableNameExpression,
30✔
1272
        public body: Statement[],
30✔
1273
        endInterfaceToken: Token
1274
    ) {
1275
        super();
30✔
1276
        this.tokens.interface = interfaceToken;
30✔
1277
        this.tokens.name = name;
30✔
1278
        this.tokens.extends = extendsToken;
30✔
1279
        this.tokens.endInterface = endInterfaceToken;
30✔
1280
        this.range = util.createBoundingRange(
30✔
1281
            this.tokens.interface,
1282
            this.tokens.name,
1283
            this.tokens.extends,
1284
            this.parentInterfaceName,
1285
            ...this.body,
1286
            this.tokens.endInterface
1287
        );
1288
    }
1289

1290
    public tokens = {} as {
30✔
1291
        interface: Token;
1292
        name: Identifier;
1293
        extends: Token;
1294
        endInterface: Token;
1295
    };
1296

1297
    public range: Range;
1298

1299
    /**
1300
     * Get the name of the wrapping namespace (if it exists)
1301
     * @deprecated use `.findAncestor(isNamespaceStatement)` instead.
1302
     */
1303
    public get namespaceName() {
1304
        return this.findAncestor<NamespaceStatement>(isNamespaceStatement)?.nameExpression;
×
1305
    }
1306

1307
    public get fields() {
1308
        return this.body.filter(x => isInterfaceFieldStatement(x));
×
1309
    }
1310

1311
    public get methods() {
1312
        return this.body.filter(x => isInterfaceMethodStatement(x));
×
1313
    }
1314

1315
    /**
1316
     * The name of the interface WITH its leading namespace (if applicable)
1317
     */
1318
    public get fullName() {
1319
        const name = this.tokens.name?.text;
×
1320
        if (name) {
×
1321
            const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
×
1322
            if (namespace) {
×
1323
                let namespaceName = namespace.getName(ParseMode.BrighterScript);
×
1324
                return `${namespaceName}.${name}`;
×
1325
            } else {
1326
                return name;
×
1327
            }
1328
        } else {
1329
            //return undefined which will allow outside callers to know that this interface doesn't have a name
1330
            return undefined;
×
1331
        }
1332
    }
1333

1334
    /**
1335
     * The name of the interface (without the namespace prefix)
1336
     */
1337
    public get name() {
1338
        return this.tokens.name?.text;
5!
1339
    }
1340

1341
    /**
1342
     * Get the name of this expression based on the parse mode
1343
     */
1344
    public getName(parseMode: ParseMode) {
1345
        const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
5✔
1346
        if (namespace) {
5✔
1347
            let delimiter = parseMode === ParseMode.BrighterScript ? '.' : '_';
1!
1348
            let namespaceName = namespace.getName(parseMode);
1✔
1349
            return namespaceName + delimiter + this.name;
1✔
1350
        } else {
1351
            return this.name;
4✔
1352
        }
1353
    }
1354

1355
    public transpile(state: BrsTranspileState): TranspileResult {
1356
        //interfaces should completely disappear at runtime
1357
        return [];
10✔
1358
    }
1359

1360
    getTypedef(state: BrsTranspileState) {
1361
        const result = [] as TranspileResult;
6✔
1362
        for (let annotation of this.annotations ?? []) {
6✔
1363
            result.push(
1✔
1364
                ...annotation.getTypedef(state),
1365
                state.newline,
1366
                state.indent()
1367
            );
1368
        }
1369
        result.push(
6✔
1370
            this.tokens.interface.text,
1371
            ' ',
1372
            this.tokens.name.text
1373
        );
1374
        const parentInterfaceName = this.parentInterfaceName?.getName(ParseMode.BrighterScript);
6!
1375
        if (parentInterfaceName) {
6!
1376
            result.push(
×
1377
                ' extends ',
1378
                parentInterfaceName
1379
            );
1380
        }
1381
        const body = this.body ?? [];
6!
1382
        if (body.length > 0) {
6!
1383
            state.blockDepth++;
6✔
1384
        }
1385
        for (const statement of body) {
6✔
1386
            if (isInterfaceMethodStatement(statement) || isInterfaceFieldStatement(statement)) {
21✔
1387
                result.push(
20✔
1388
                    state.newline,
1389
                    state.indent(),
1390
                    ...statement.getTypedef(state)
1391
                );
1392
            } else {
1393
                result.push(
1✔
1394
                    state.newline,
1395
                    state.indent(),
1396
                    ...statement.transpile(state)
1397
                );
1398
            }
1399
        }
1400
        if (body.length > 0) {
6!
1401
            state.blockDepth--;
6✔
1402
        }
1403
        result.push(
6✔
1404
            state.newline,
1405
            state.indent(),
1406
            'end interface',
1407
            state.newline
1408
        );
1409
        return result;
6✔
1410
    }
1411

1412
    walk(visitor: WalkVisitor, options: WalkOptions) {
1413
        //visitor-less walk function to do parent linking
1414
        walk(this, 'parentInterfaceName', null, options);
60✔
1415

1416
        if (options.walkMode & InternalWalkMode.walkStatements) {
60!
1417
            walkArray(this.body, visitor, options, this);
60✔
1418
        }
1419
    }
1420
}
1421

1422
export class InterfaceFieldStatement extends Statement implements TypedefProvider {
1✔
1423
    public transpile(state: BrsTranspileState): TranspileResult {
1424
        throw new Error('Method not implemented.');
×
1425
    }
1426
    constructor(
1427
        nameToken: Identifier,
1428
        asToken: Token,
1429
        typeToken: Token,
1430
        public type: BscType,
40✔
1431
        optionalToken?: Token
1432
    ) {
1433
        super();
40✔
1434
        this.tokens.optional = optionalToken;
40✔
1435
        this.tokens.name = nameToken;
40✔
1436
        this.tokens.as = asToken;
40✔
1437
        this.tokens.type = typeToken;
40✔
1438
        this.range = util.createBoundingRange(
40✔
1439
            optionalToken,
1440
            nameToken,
1441
            asToken,
1442
            typeToken
1443
        );
1444
    }
1445

1446
    public range: Range;
1447

1448
    public tokens = {} as {
40✔
1449
        optional?: Token;
1450
        name: Identifier;
1451
        as: Token;
1452
        type: Token;
1453
    };
1454

1455
    public get name() {
1456
        return this.tokens.name.text;
×
1457
    }
1458

1459
    public get isOptional() {
1460
        return !!this.tokens.optional;
10✔
1461
    }
1462

1463
    walk(visitor: WalkVisitor, options: WalkOptions) {
1464
        //nothing to walk
1465
    }
1466

1467
    getTypedef(state: BrsTranspileState): (string | SourceNode)[] {
1468
        const result = [] as TranspileResult;
10✔
1469
        for (let annotation of this.annotations ?? []) {
10✔
1470
            result.push(
1✔
1471
                ...annotation.getTypedef(state),
1472
                state.newline,
1473
                state.indent()
1474
            );
1475
        }
1476
        if (this.isOptional) {
10!
1477
            result.push(
×
1478
                this.tokens.optional.text,
1479
                ' '
1480
            );
1481
        }
1482
        result.push(
10✔
1483
            this.tokens.name.text
1484
        );
1485
        if (this.tokens.type?.text?.length > 0) {
10!
1486
            result.push(
10✔
1487
                ' as ',
1488
                this.tokens.type.text
1489
            );
1490
        }
1491
        return result;
10✔
1492
    }
1493

1494
}
1495

1496
export class InterfaceMethodStatement extends Statement implements TypedefProvider {
1✔
1497
    public transpile(state: BrsTranspileState): TranspileResult {
1498
        throw new Error('Method not implemented.');
×
1499
    }
1500
    constructor(
1501
        functionTypeToken: Token,
1502
        nameToken: Identifier,
1503
        leftParen: Token,
1504
        public params: FunctionParameterExpression[],
11✔
1505
        rightParen: Token,
1506
        asToken?: Token,
1507
        returnTypeToken?: Token,
1508
        public returnType?: BscType,
11✔
1509
        optionalToken?: Token
1510
    ) {
1511
        super();
11✔
1512
        this.tokens.optional = optionalToken;
11✔
1513
        this.tokens.functionType = functionTypeToken;
11✔
1514
        this.tokens.name = nameToken;
11✔
1515
        this.tokens.leftParen = leftParen;
11✔
1516
        this.tokens.rightParen = rightParen;
11✔
1517
        this.tokens.as = asToken;
11✔
1518
        this.tokens.returnType = returnTypeToken;
11✔
1519
    }
1520

1521
    public get range() {
1522
        return util.createBoundingRange(
×
1523
            this.tokens.optional,
1524
            this.tokens.functionType,
1525
            this.tokens.name,
1526
            this.tokens.leftParen,
1527
            ...(this.params ?? []),
×
1528
            this.tokens.rightParen,
1529
            this.tokens.as,
1530
            this.tokens.returnType
1531
        );
1532
    }
1533

1534
    public tokens = {} as {
11✔
1535
        optional?: Token;
1536
        functionType: Token;
1537
        name: Identifier;
1538
        leftParen: Token;
1539
        rightParen: Token;
1540
        as: Token;
1541
        returnType: Token;
1542
    };
1543

1544
    public get isOptional() {
1545
        return !!this.tokens.optional;
10✔
1546
    }
1547

1548
    walk(visitor: WalkVisitor, options: WalkOptions) {
1549
        //nothing to walk
1550
    }
1551

1552
    getTypedef(state: BrsTranspileState) {
1553
        const result = [] as TranspileResult;
10✔
1554
        for (let annotation of this.annotations ?? []) {
10✔
1555
            result.push(
1✔
1556
                ...annotation.getTypedef(state),
1557
                state.newline,
1558
                state.indent()
1559
            );
1560
        }
1561

1562
        if (this.isOptional) {
10!
1563
            result.push(
×
1564
                this.tokens.optional.text,
1565
                ' '
1566
            );
1567
        }
1568

1569
        result.push(
10✔
1570
            this.tokens.functionType.text,
1571
            ' ',
1572
            this.tokens.name.text,
1573
            '('
1574
        );
1575
        const params = this.params ?? [];
10!
1576
        for (let i = 0; i < params.length; i++) {
10✔
1577
            if (i > 0) {
2✔
1578
                result.push(', ');
1✔
1579
            }
1580
            const param = params[i];
2✔
1581
            result.push(param.name.text);
2✔
1582
            if (param.typeToken?.text?.length > 0) {
2!
1583
                result.push(
2✔
1584
                    ' as ',
1585
                    param.typeToken.text
1586
                );
1587
            }
1588
        }
1589
        result.push(
10✔
1590
            ')'
1591
        );
1592
        if (this.tokens.returnType?.text.length > 0) {
10!
1593
            result.push(
10✔
1594
                ' as ',
1595
                this.tokens.returnType.text
1596
            );
1597
        }
1598
        return result;
10✔
1599
    }
1600
}
1601

1602
export class ClassStatement extends Statement implements TypedefProvider {
1✔
1603

1604
    constructor(
1605
        readonly classKeyword: Token,
441✔
1606
        /**
1607
         * The name of the class (without namespace prefix)
1608
         */
1609
        readonly name: Identifier,
441✔
1610
        public body: Statement[],
441✔
1611
        readonly end: Token,
441✔
1612
        readonly extendsKeyword?: Token,
441✔
1613
        readonly parentClassName?: NamespacedVariableNameExpression
441✔
1614
    ) {
1615
        super();
441✔
1616
        this.body = this.body ?? [];
441!
1617

1618
        for (let statement of this.body) {
441✔
1619
            if (isMethodStatement(statement)) {
412✔
1620
                this.methods.push(statement);
238✔
1621
                this.memberMap[statement?.name?.text.toLowerCase()] = statement;
238!
1622
            } else if (isFieldStatement(statement)) {
174✔
1623
                this.fields.push(statement);
166✔
1624
                this.memberMap[statement?.name?.text.toLowerCase()] = statement;
166!
1625
            }
1626
        }
1627

1628
        this.range = util.createBoundingRange(
441✔
1629
            classKeyword,
1630
            name,
1631
            extendsKeyword,
1632
            parentClassName,
1633
            ...(body ?? []),
1,323!
1634
            end
1635
        );
1636
    }
1637

1638
    /**
1639
     * Get the name of the wrapping namespace (if it exists)
1640
     * @deprecated use `.findAncestor(isNamespaceStatement)` instead.
1641
     */
1642
    public get namespaceName() {
1643
        return this.findAncestor<NamespaceStatement>(isNamespaceStatement)?.nameExpression;
×
1644
    }
1645

1646

1647
    public getName(parseMode: ParseMode) {
1648
        const name = this.name?.text;
888✔
1649
        if (name) {
888✔
1650
            const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
886✔
1651
            if (namespace) {
886✔
1652
                let namespaceName = namespace.getName(parseMode);
249✔
1653
                let separator = parseMode === ParseMode.BrighterScript ? '.' : '_';
249✔
1654
                return namespaceName + separator + name;
249✔
1655
            } else {
1656
                return name;
637✔
1657
            }
1658
        } else {
1659
            //return undefined which will allow outside callers to know that this class doesn't have a name
1660
            return undefined;
2✔
1661
        }
1662
    }
1663

1664
    public memberMap = {} as Record<string, MemberStatement>;
441✔
1665
    public methods = [] as MethodStatement[];
441✔
1666
    public fields = [] as FieldStatement[];
441✔
1667

1668
    public readonly range: Range;
1669

1670
    transpile(state: BrsTranspileState) {
1671
        let result = [];
43✔
1672
        //make the builder
1673
        result.push(...this.getTranspiledBuilder(state));
43✔
1674
        result.push(
43✔
1675
            '\n',
1676
            state.indent()
1677
        );
1678
        //make the class assembler (i.e. the public-facing class creator method)
1679
        result.push(...this.getTranspiledClassFunction(state));
43✔
1680
        return result;
43✔
1681
    }
1682

1683
    getTypedef(state: BrsTranspileState) {
1684
        const result = [] as TranspileResult;
15✔
1685
        for (let annotation of this.annotations ?? []) {
15!
1686
            result.push(
×
1687
                ...annotation.getTypedef(state),
1688
                state.newline,
1689
                state.indent()
1690
            );
1691
        }
1692
        result.push(
15✔
1693
            'class ',
1694
            this.name.text
1695
        );
1696
        if (this.extendsKeyword && this.parentClassName) {
15✔
1697
            const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
4✔
1698
            const fqName = util.getFullyQualifiedClassName(
4✔
1699
                this.parentClassName.getName(ParseMode.BrighterScript),
1700
                namespace?.getName(ParseMode.BrighterScript)
12✔
1701
            );
1702
            result.push(
4✔
1703
                ` extends ${fqName}`
1704
            );
1705
        }
1706
        result.push(state.newline);
15✔
1707
        state.blockDepth++;
15✔
1708

1709
        let body = this.body;
15✔
1710
        //inject an empty "new" method if missing
1711
        if (!this.getConstructorFunction()) {
15✔
1712
            const constructor = createMethodStatement('new', TokenKind.Sub);
11✔
1713
            constructor.parent = this;
11✔
1714
            //walk the constructor to set up parent links
1715
            constructor.link();
11✔
1716
            body = [
11✔
1717
                constructor,
1718
                ...this.body
1719
            ];
1720
        }
1721

1722
        for (const member of body) {
15✔
1723
            if (isTypedefProvider(member)) {
33!
1724
                result.push(
33✔
1725
                    state.indent(),
1726
                    ...member.getTypedef(state),
1727
                    state.newline
1728
                );
1729
            }
1730
        }
1731
        state.blockDepth--;
15✔
1732
        result.push(
15✔
1733
            state.indent(),
1734
            'end class'
1735
        );
1736
        return result;
15✔
1737
    }
1738

1739
    /**
1740
     * Find the parent index for this class's parent.
1741
     * For class inheritance, every class is given an index.
1742
     * The base class is index 0, its child is index 1, and so on.
1743
     */
1744
    public getParentClassIndex(state: BrsTranspileState) {
1745
        let myIndex = 0;
98✔
1746
        let stmt = this as ClassStatement;
98✔
1747
        while (stmt) {
98✔
1748
            if (stmt.parentClassName) {
159✔
1749
                const namespace = stmt.findAncestor<NamespaceStatement>(isNamespaceStatement);
62✔
1750
                //find the parent class
1751
                stmt = state.file.getClassFileLink(
62✔
1752
                    stmt.parentClassName.getName(ParseMode.BrighterScript),
1753
                    namespace?.getName(ParseMode.BrighterScript)
186✔
1754
                )?.item;
62✔
1755
                myIndex++;
62✔
1756
            } else {
1757
                break;
97✔
1758
            }
1759
        }
1760
        const result = myIndex - 1;
98✔
1761
        if (result >= 0) {
98✔
1762
            return result;
49✔
1763
        } else {
1764
            return null;
49✔
1765
        }
1766
    }
1767

1768
    public hasParentClass() {
1769
        return !!this.parentClassName;
43✔
1770
    }
1771

1772
    /**
1773
     * Get all ancestor classes, in closest-to-furthest order (i.e. 0 is parent, 1 is grandparent, etc...).
1774
     * This will return an empty array if no ancestors were found
1775
     */
1776
    public getAncestors(state: BrsTranspileState) {
1777
        let ancestors = [] as ClassStatement[];
86✔
1778
        let stmt = this as ClassStatement;
86✔
1779
        while (stmt) {
86✔
1780
            if (stmt.parentClassName) {
136✔
1781
                const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
52✔
1782
                stmt = state.file.getClassFileLink(
52✔
1783
                    stmt.parentClassName.getName(ParseMode.BrighterScript),
1784
                    namespace?.getName(ParseMode.BrighterScript)
156✔
1785
                )?.item;
52✔
1786
                ancestors.push(stmt);
52✔
1787
            } else {
1788
                break;
84✔
1789
            }
1790
        }
1791
        return ancestors;
86✔
1792
    }
1793

1794
    private getBuilderName(name: string) {
1795
        if (name.includes('.')) {
107✔
1796
            name = name.replace(/\./gi, '_');
6✔
1797
        }
1798
        return `__${name}_builder`;
107✔
1799
    }
1800

1801
    /**
1802
     * Get the constructor function for this class (if exists), or undefined if not exist
1803
     */
1804
    private getConstructorFunction() {
1805
        return this.body.find((stmt) => {
101✔
1806
            return (stmt as MethodStatement)?.name?.text?.toLowerCase() === 'new';
94!
1807
        }) as MethodStatement;
1808
    }
1809

1810
    /**
1811
     * Determine if the specified field was declared in one of the ancestor classes
1812
     */
1813
    public isFieldDeclaredByAncestor(fieldName: string, ancestors: ClassStatement[]) {
1814
        let lowerFieldName = fieldName.toLowerCase();
×
1815
        for (let ancestor of ancestors) {
×
1816
            if (ancestor.memberMap[lowerFieldName]) {
×
1817
                return true;
×
1818
            }
1819
        }
1820
        return false;
×
1821
    }
1822

1823
    /**
1824
     * The builder is a function that assigns all of the methods and property names to a class instance.
1825
     * This needs to be a separate function so that child classes can call the builder from their parent
1826
     * without instantiating the parent constructor at that point in time.
1827
     */
1828
    private getTranspiledBuilder(state: BrsTranspileState) {
1829
        let result = [];
43✔
1830
        result.push(`function ${this.getBuilderName(this.getName(ParseMode.BrightScript))}()\n`);
43✔
1831
        state.blockDepth++;
43✔
1832
        //indent
1833
        result.push(state.indent());
43✔
1834

1835
        /**
1836
         * The lineage of this class. index 0 is a direct parent, index 1 is index 0's parent, etc...
1837
         */
1838
        let ancestors = this.getAncestors(state);
43✔
1839

1840
        //construct parent class or empty object
1841
        if (ancestors[0]) {
43✔
1842
            const ancestorNamespace = ancestors[0].findAncestor<NamespaceStatement>(isNamespaceStatement);
21✔
1843
            let fullyQualifiedClassName = util.getFullyQualifiedClassName(
21✔
1844
                ancestors[0].getName(ParseMode.BrighterScript),
1845
                ancestorNamespace?.getName(ParseMode.BrighterScript)
63✔
1846
            );
1847
            result.push(
21✔
1848
                'instance = ',
1849
                this.getBuilderName(fullyQualifiedClassName), '()');
1850
        } else {
1851
            //use an empty object.
1852
            result.push('instance = {}');
22✔
1853
        }
1854
        result.push(
43✔
1855
            state.newline,
1856
            state.indent()
1857
        );
1858
        let parentClassIndex = this.getParentClassIndex(state);
43✔
1859

1860
        let body = this.body;
43✔
1861
        //inject an empty "new" method if missing
1862
        if (!this.getConstructorFunction()) {
43✔
1863
            body = [
24✔
1864
                createMethodStatement('new', TokenKind.Sub),
1865
                ...this.body
1866
            ];
1867
        }
1868

1869
        for (let statement of body) {
43✔
1870
            //is field statement
1871
            if (isFieldStatement(statement)) {
64✔
1872
                //do nothing with class fields in this situation, they are handled elsewhere
1873
                continue;
10✔
1874

1875
                //methods
1876
            } else if (isMethodStatement(statement)) {
54!
1877

1878
                //store overridden parent methods as super{parentIndex}_{methodName}
1879
                if (
54✔
1880
                    //is override method
1881
                    statement.override ||
147✔
1882
                    //is constructor function in child class
1883
                    (statement.name.text.toLowerCase() === 'new' && ancestors[0])
1884
                ) {
1885
                    result.push(
25✔
1886
                        `instance.super${parentClassIndex}_${statement.name.text} = instance.${statement.name.text}`,
1887
                        state.newline,
1888
                        state.indent()
1889
                    );
1890
                }
1891

1892
                state.classStatement = this;
54✔
1893
                result.push(
54✔
1894
                    'instance.',
1895
                    state.transpileToken(statement.name),
1896
                    ' = ',
1897
                    ...statement.transpile(state),
1898
                    state.newline,
1899
                    state.indent()
1900
                );
1901
                delete state.classStatement;
54✔
1902
            } else {
1903
                //other random statements (probably just comments)
1904
                result.push(
×
1905
                    ...statement.transpile(state),
1906
                    state.newline,
1907
                    state.indent()
1908
                );
1909
            }
1910
        }
1911
        //return the instance
1912
        result.push('return instance\n');
43✔
1913
        state.blockDepth--;
43✔
1914
        result.push(state.indent());
43✔
1915
        result.push(`end function`);
43✔
1916
        return result;
43✔
1917
    }
1918

1919
    /**
1920
     * The class function is the function with the same name as the class. This is the function that
1921
     * consumers should call to create a new instance of that class.
1922
     * This invokes the builder, gets an instance of the class, then invokes the "new" function on that class.
1923
     */
1924
    private getTranspiledClassFunction(state: BrsTranspileState) {
1925
        let result = [];
43✔
1926
        const constructorFunction = this.getConstructorFunction();
43✔
1927
        const constructorParams = constructorFunction ? constructorFunction.func.parameters : [];
43✔
1928

1929
        result.push(
43✔
1930
            state.sourceNode(this.classKeyword, 'function'),
1931
            state.sourceNode(this.classKeyword, ' '),
1932
            state.sourceNode(this.name, this.getName(ParseMode.BrightScript)),
1933
            `(`
1934
        );
1935
        let i = 0;
43✔
1936
        for (let param of constructorParams) {
43✔
1937
            if (i > 0) {
8✔
1938
                result.push(', ');
2✔
1939
            }
1940
            result.push(
8✔
1941
                param.transpile(state)
1942
            );
1943
            i++;
8✔
1944
        }
1945
        result.push(
43✔
1946
            ')',
1947
            '\n'
1948
        );
1949

1950
        state.blockDepth++;
43✔
1951
        result.push(state.indent());
43✔
1952
        result.push(`instance = ${this.getBuilderName(this.getName(ParseMode.BrightScript))}()\n`);
43✔
1953

1954
        result.push(state.indent());
43✔
1955
        result.push(`instance.new(`);
43✔
1956

1957
        //append constructor arguments
1958
        i = 0;
43✔
1959
        for (let param of constructorParams) {
43✔
1960
            if (i > 0) {
8✔
1961
                result.push(', ');
2✔
1962
            }
1963
            result.push(
8✔
1964
                state.transpileToken(param.name)
1965
            );
1966
            i++;
8✔
1967
        }
1968
        result.push(
43✔
1969
            ')',
1970
            '\n'
1971
        );
1972

1973
        result.push(state.indent());
43✔
1974
        result.push(`return instance\n`);
43✔
1975

1976
        state.blockDepth--;
43✔
1977
        result.push(state.indent());
43✔
1978
        result.push(`end function`);
43✔
1979
        return result;
43✔
1980
    }
1981

1982
    walk(visitor: WalkVisitor, options: WalkOptions) {
1983
        //visitor-less walk function to do parent linking
1984
        walk(this, 'parentClassName', null, options);
855✔
1985

1986
        if (options.walkMode & InternalWalkMode.walkStatements) {
855!
1987
            walkArray(this.body, visitor, options, this);
855✔
1988
        }
1989
    }
1990
}
1991

1992
const accessModifiers = [
1✔
1993
    TokenKind.Public,
1994
    TokenKind.Protected,
1995
    TokenKind.Private
1996
];
1997
export class MethodStatement extends FunctionStatement {
1✔
1998
    constructor(
1999
        modifiers: Token | Token[],
2000
        name: Identifier,
2001
        func: FunctionExpression,
2002
        public override: Token
273✔
2003
    ) {
2004
        super(name, func);
273✔
2005
        if (modifiers) {
273✔
2006
            if (Array.isArray(modifiers)) {
30!
2007
                this.modifiers.push(...modifiers);
×
2008
            } else {
2009
                this.modifiers.push(modifiers);
30✔
2010
            }
2011
        }
2012
        this.range = util.createBoundingRange(
273✔
2013
            ...(this.modifiers),
2014
            override,
2015
            func
2016
        );
2017
    }
2018

2019
    public modifiers: Token[] = [];
273✔
2020

2021
    public get accessModifier() {
2022
        return this.modifiers.find(x => accessModifiers.includes(x.kind));
100✔
2023
    }
2024

2025
    public readonly range: Range;
2026

2027
    /**
2028
     * Get the name of this method.
2029
     */
2030
    public getName(parseMode: ParseMode) {
2031
        return this.name.text;
7✔
2032
    }
2033

2034
    transpile(state: BrsTranspileState) {
2035
        if (this.name.text.toLowerCase() === 'new') {
54✔
2036
            this.ensureSuperConstructorCall(state);
43✔
2037
            //TODO we need to undo this at the bottom of this method
2038
            this.injectFieldInitializersForConstructor(state);
43✔
2039
        }
2040
        //TODO - remove type information from these methods because that doesn't work
2041
        //convert the `super` calls into the proper methods
2042
        const parentClassIndex = state.classStatement.getParentClassIndex(state);
54✔
2043
        const visitor = createVisitor({
54✔
2044
            VariableExpression: e => {
2045
                if (e.name.text.toLocaleLowerCase() === 'super') {
54✔
2046
                    state.editor.setProperty(e.name, 'text', `m.super${parentClassIndex}_new`);
21✔
2047
                }
2048
            },
2049
            DottedGetExpression: e => {
2050
                const beginningVariable = util.findBeginningVariableExpression(e);
24✔
2051
                const lowerName = beginningVariable?.getName(ParseMode.BrighterScript).toLowerCase();
24!
2052
                if (lowerName === 'super') {
24✔
2053
                    state.editor.setProperty(beginningVariable.name, 'text', 'm');
7✔
2054
                    state.editor.setProperty(e.name, 'text', `super${parentClassIndex}_${e.name.text}`);
7✔
2055
                }
2056
            }
2057
        });
2058
        const walkOptions: WalkOptions = { walkMode: WalkMode.visitExpressions };
54✔
2059
        for (const statement of this.func.body.statements) {
54✔
2060
            visitor(statement, undefined);
58✔
2061
            statement.walk(visitor, walkOptions);
58✔
2062
        }
2063
        return this.func.transpile(state);
54✔
2064
    }
2065

2066
    getTypedef(state: BrsTranspileState) {
2067
        const result = [] as Array<string | SourceNode>;
23✔
2068
        for (let annotation of this.annotations ?? []) {
23✔
2069
            result.push(
2✔
2070
                ...annotation.getTypedef(state),
2071
                state.newline,
2072
                state.indent()
2073
            );
2074
        }
2075
        if (this.accessModifier) {
23✔
2076
            result.push(
8✔
2077
                this.accessModifier.text,
2078
                ' '
2079
            );
2080
        }
2081
        if (this.override) {
23✔
2082
            result.push('override ');
1✔
2083
        }
2084
        result.push(
23✔
2085
            ...this.func.getTypedef(state)
2086
        );
2087
        return result;
23✔
2088
    }
2089

2090
    /**
2091
     * All child classes must call the parent constructor. The type checker will warn users when they don't call it in their own class,
2092
     * but we still need to call it even if they have omitted it. This injects the super call if it's missing
2093
     */
2094
    private ensureSuperConstructorCall(state: BrsTranspileState) {
2095
        //if this class doesn't extend another class, quit here
2096
        if (state.classStatement.getAncestors(state).length === 0) {
43✔
2097
            return;
22✔
2098
        }
2099

2100
        //check whether any calls to super exist
2101
        let containsSuperCall =
2102
            this.func.body.statements.findIndex((x) => {
21✔
2103
                //is a call statement
2104
                return isExpressionStatement(x) && isCallExpression(x.expression) &&
9✔
2105
                    //is a call to super
2106
                    util.findBeginningVariableExpression(x.expression.callee as any).name.text.toLowerCase() === 'super';
2107
            }) !== -1;
2108

2109
        //if a call to super exists, quit here
2110
        if (containsSuperCall) {
21✔
2111
            return;
7✔
2112
        }
2113

2114
        //this is a child class, and the constructor doesn't contain a call to super. Inject one
2115
        const superCall = new ExpressionStatement(
14✔
2116
            new CallExpression(
2117
                new VariableExpression(
2118
                    {
2119
                        kind: TokenKind.Identifier,
2120
                        text: 'super',
2121
                        isReserved: false,
2122
                        range: state.classStatement.name.range,
2123
                        leadingWhitespace: ''
2124
                    }
2125
                ),
2126
                {
2127
                    kind: TokenKind.LeftParen,
2128
                    text: '(',
2129
                    isReserved: false,
2130
                    range: state.classStatement.name.range,
2131
                    leadingWhitespace: ''
2132
                },
2133
                {
2134
                    kind: TokenKind.RightParen,
2135
                    text: ')',
2136
                    isReserved: false,
2137
                    range: state.classStatement.name.range,
2138
                    leadingWhitespace: ''
2139
                },
2140
                []
2141
            )
2142
        );
2143
        state.editor.arrayUnshift(this.func.body.statements, superCall);
14✔
2144
    }
2145

2146
    /**
2147
     * Inject field initializers at the top of the `new` function (after any present `super()` call)
2148
     */
2149
    private injectFieldInitializersForConstructor(state: BrsTranspileState) {
2150
        let startingIndex = state.classStatement.hasParentClass() ? 1 : 0;
43✔
2151

2152
        let newStatements = [] as Statement[];
43✔
2153
        //insert the field initializers in order
2154
        for (let field of state.classStatement.fields) {
43✔
2155
            let thisQualifiedName = { ...field.name };
10✔
2156
            thisQualifiedName.text = 'm.' + field.name.text;
10✔
2157
            if (field.initialValue) {
10✔
2158
                newStatements.push(
9✔
2159
                    new AssignmentStatement(field.equal, thisQualifiedName, field.initialValue)
2160
                );
2161
            } else {
2162
                //if there is no initial value, set the initial value to `invalid`
2163
                newStatements.push(
1✔
2164
                    new AssignmentStatement(
2165
                        createToken(TokenKind.Equal, '=', field.name.range),
2166
                        thisQualifiedName,
2167
                        createInvalidLiteral('invalid', field.name.range)
2168
                    )
2169
                );
2170
            }
2171
        }
2172
        state.editor.arraySplice(this.func.body.statements, startingIndex, 0, ...newStatements);
43✔
2173
    }
2174

2175
    walk(visitor: WalkVisitor, options: WalkOptions) {
2176
        if (options.walkMode & InternalWalkMode.walkExpressions) {
705✔
2177
            walk(this, 'func', visitor, options);
500✔
2178
        }
2179
    }
2180
}
2181
/**
2182
 * @deprecated use `MethodStatement`
2183
 */
2184
export class ClassMethodStatement extends MethodStatement { }
1✔
2185

2186
export class FieldStatement extends Statement implements TypedefProvider {
1✔
2187

2188
    constructor(
2189
        readonly accessModifier?: Token,
166✔
2190
        readonly name?: Identifier,
166✔
2191
        readonly as?: Token,
166✔
2192
        readonly type?: Token,
166✔
2193
        readonly equal?: Token,
166✔
2194
        readonly initialValue?: Expression,
166✔
2195
        readonly optional?: Token
166✔
2196
    ) {
2197
        super();
166✔
2198
        this.range = util.createBoundingRange(
166✔
2199
            accessModifier,
2200
            name,
2201
            as,
2202
            type,
2203
            equal,
2204
            initialValue
2205
        );
2206
    }
2207

2208
    /**
2209
     * Derive a ValueKind from the type token, or the initial value.
2210
     * Defaults to `DynamicType`
2211
     */
2212
    getType() {
2213
        if (this.type) {
73✔
2214
            return util.tokenToBscType(this.type);
34✔
2215
        } else if (isLiteralExpression(this.initialValue)) {
39✔
2216
            return this.initialValue.type;
25✔
2217
        } else {
2218
            return new DynamicType();
14✔
2219
        }
2220
    }
2221

2222
    public readonly range: Range;
2223

2224
    public get isOptional() {
2225
        return !!this.optional;
17✔
2226
    }
2227

2228
    transpile(state: BrsTranspileState): TranspileResult {
2229
        throw new Error('transpile not implemented for ' + Object.getPrototypeOf(this).constructor.name);
×
2230
    }
2231

2232
    getTypedef(state: BrsTranspileState) {
2233
        const result = [];
10✔
2234
        if (this.name) {
10!
2235
            for (let annotation of this.annotations ?? []) {
10✔
2236
                result.push(
2✔
2237
                    ...annotation.getTypedef(state),
2238
                    state.newline,
2239
                    state.indent()
2240
                );
2241
            }
2242

2243
            let type = this.getType();
10✔
2244
            if (isInvalidType(type) || isVoidType(type)) {
10✔
2245
                type = new DynamicType();
1✔
2246
            }
2247

2248
            result.push(
10✔
2249
                this.accessModifier?.text ?? 'public',
60!
2250
                ' '
2251
            );
2252
            if (this.isOptional) {
10!
2253
                result.push(this.optional.text, ' ');
×
2254
            }
2255
            result.push(this.name?.text,
10!
2256
                ' as ',
2257
                type.toTypeString()
2258
            );
2259
        }
2260
        return result;
10✔
2261
    }
2262

2263
    walk(visitor: WalkVisitor, options: WalkOptions) {
2264
        if (this.initialValue && options.walkMode & InternalWalkMode.walkExpressions) {
223✔
2265
            walk(this, 'initialValue', visitor, options);
60✔
2266
        }
2267
    }
2268
}
2269
/**
2270
 * @deprecated use `FieldStatement`
2271
 */
2272
export class ClassFieldStatement extends FieldStatement { }
1✔
2273

2274
export type MemberStatement = FieldStatement | MethodStatement;
2275

2276
/**
2277
 * @deprecated use `MemeberStatement`
2278
 */
2279
export type ClassMemberStatement = MemberStatement;
2280

2281
export class TryCatchStatement extends Statement {
1✔
2282
    constructor(
2283
        public tokens: {
24✔
2284
            try: Token;
2285
            endTry?: Token;
2286
        },
2287
        public tryBranch?: Block,
24✔
2288
        public catchStatement?: CatchStatement
24✔
2289
    ) {
2290
        super();
24✔
2291
        this.range = util.createBoundingRange(
24✔
2292
            tokens.try,
2293
            tryBranch,
2294
            catchStatement,
2295
            tokens.endTry
2296
        );
2297
    }
2298

2299
    public readonly range: Range;
2300

2301
    public transpile(state: BrsTranspileState): TranspileResult {
2302
        return [
2✔
2303
            state.transpileToken(this.tokens.try),
2304
            ...this.tryBranch.transpile(state),
2305
            state.newline,
2306
            state.indent(),
2307
            ...(this.catchStatement?.transpile(state) ?? ['catch']),
12!
2308
            state.newline,
2309
            state.indent(),
2310
            state.transpileToken(this.tokens.endTry)
2311
        ];
2312
    }
2313

2314
    public walk(visitor: WalkVisitor, options: WalkOptions) {
2315
        if (this.tryBranch && options.walkMode & InternalWalkMode.walkStatements) {
31!
2316
            walk(this, 'tryBranch', visitor, options);
31✔
2317
            walk(this, 'catchStatement', visitor, options);
31✔
2318
        }
2319
    }
2320
}
2321

2322
export class CatchStatement extends Statement {
1✔
2323
    constructor(
2324
        public tokens: {
22✔
2325
            catch: Token;
2326
        },
2327
        public exceptionVariable?: Identifier,
22✔
2328
        public catchBranch?: Block
22✔
2329
    ) {
2330
        super();
22✔
2331
        this.range = util.createBoundingRange(
22✔
2332
            tokens.catch,
2333
            exceptionVariable,
2334
            catchBranch
2335
        );
2336
    }
2337

2338
    public range: Range;
2339

2340
    public transpile(state: BrsTranspileState): TranspileResult {
2341
        return [
2✔
2342
            state.transpileToken(this.tokens.catch),
2343
            ' ',
2344
            this.exceptionVariable?.text ?? 'e',
12!
2345
            ...(this.catchBranch?.transpile(state) ?? [])
12!
2346
        ];
2347
    }
2348

2349
    public walk(visitor: WalkVisitor, options: WalkOptions) {
2350
        if (this.catchBranch && options.walkMode & InternalWalkMode.walkStatements) {
29!
2351
            walk(this, 'catchBranch', visitor, options);
29✔
2352
        }
2353
    }
2354
}
2355

2356
export class ThrowStatement extends Statement {
1✔
2357
    constructor(
2358
        public throwToken: Token,
6✔
2359
        public expression?: Expression
6✔
2360
    ) {
2361
        super();
6✔
2362
        this.range = util.createBoundingRange(
6✔
2363
            throwToken,
2364
            expression
2365
        );
2366
    }
2367
    public range: Range;
2368

2369
    public transpile(state: BrsTranspileState) {
2370
        const result = [
2✔
2371
            state.transpileToken(this.throwToken),
2372
            ' '
2373
        ];
2374

2375
        //if we have an expression, transpile it
2376
        if (this.expression) {
2!
2377
            result.push(
2✔
2378
                ...this.expression.transpile(state)
2379
            );
2380

2381
            //no expression found. Rather than emit syntax errors, provide a generic error message
2382
        } else {
2383
            result.push('"An error has occurred"');
×
2384
        }
2385
        return result;
2✔
2386
    }
2387

2388
    public walk(visitor: WalkVisitor, options: WalkOptions) {
2389
        if (this.expression && options.walkMode & InternalWalkMode.walkExpressions) {
11✔
2390
            walk(this, 'expression', visitor, options);
7✔
2391
        }
2392
    }
2393
}
2394

2395

2396
export class EnumStatement extends Statement implements TypedefProvider {
1✔
2397

2398
    constructor(
2399
        public tokens: {
94✔
2400
            enum: Token;
2401
            name: Identifier;
2402
            endEnum: Token;
2403
        },
2404
        public body: Array<EnumMemberStatement | CommentStatement>
94✔
2405
    ) {
2406
        super();
94✔
2407
        this.body = this.body ?? [];
94!
2408
    }
2409

2410
    public get range(): Range {
2411
        return util.createBoundingRange(
7✔
2412
            this.tokens.enum,
2413
            this.tokens.name,
2414
            ...this.body,
2415
            this.tokens.endEnum
2416
        );
2417
    }
2418

2419
    /**
2420
     * Get the name of the wrapping namespace (if it exists)
2421
     * @deprecated use `.findAncestor(isNamespaceStatement)` instead.
2422
     */
2423
    public get namespaceName() {
2424
        return this.findAncestor<NamespaceStatement>(isNamespaceStatement)?.nameExpression;
×
2425
    }
2426

2427
    public getMembers() {
2428
        const result = [] as EnumMemberStatement[];
125✔
2429
        for (const statement of this.body) {
125✔
2430
            if (isEnumMemberStatement(statement)) {
281✔
2431
                result.push(statement);
268✔
2432
            }
2433
        }
2434
        return result;
125✔
2435
    }
2436

2437
    /**
2438
     * Get a map of member names and their values.
2439
     * All values are stored as their AST LiteralExpression representation (i.e. string enum values include the wrapping quotes)
2440
     */
2441
    public getMemberValueMap() {
2442
        const result = new Map<string, string>();
48✔
2443
        const members = this.getMembers();
48✔
2444
        let currentIntValue = 0;
48✔
2445
        for (const member of members) {
48✔
2446
            //if there is no value, assume an integer and increment the int counter
2447
            if (!member.value) {
128✔
2448
                result.set(member.name?.toLowerCase(), currentIntValue.toString());
32!
2449
                currentIntValue++;
32✔
2450

2451
                //if explicit integer value, use it and increment the int counter
2452
            } else if (isLiteralExpression(member.value) && member.value.token.kind === TokenKind.IntegerLiteral) {
96✔
2453
                //try parsing as integer literal, then as hex integer literal.
2454
                let tokenIntValue = util.parseInt(member.value.token.text) ?? util.parseInt(member.value.token.text.replace(/&h/i, '0x'));
31✔
2455
                if (tokenIntValue !== undefined) {
31!
2456
                    currentIntValue = tokenIntValue;
31✔
2457
                    currentIntValue++;
31✔
2458
                }
2459
                result.set(member.name?.toLowerCase(), member.value.token.text);
31!
2460

2461
                //simple unary expressions (like `-1`)
2462
            } else if (isUnaryExpression(member.value) && isLiteralExpression(member.value.right)) {
65✔
2463
                result.set(member.name?.toLowerCase(), member.value.operator.text + member.value.right.token.text);
1!
2464

2465
                //all other values
2466
            } else {
2467
                result.set(member.name?.toLowerCase(), (member.value as LiteralExpression)?.token?.text ?? 'invalid');
64!
2468
            }
2469
        }
2470
        return result;
48✔
2471
    }
2472

2473
    public getMemberValue(name: string) {
2474
        return this.getMemberValueMap().get(name.toLowerCase());
45✔
2475
    }
2476

2477
    /**
2478
     * The name of the enum (without the namespace prefix)
2479
     */
2480
    public get name() {
2481
        return this.tokens.name?.text;
16!
2482
    }
2483

2484
    /**
2485
     * The name of the enum WITH its leading namespace (if applicable)
2486
     */
2487
    public get fullName() {
2488
        const name = this.tokens.name?.text;
264!
2489
        if (name) {
264!
2490
            const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
264✔
2491

2492
            if (namespace) {
264✔
2493
                let namespaceName = namespace.getName(ParseMode.BrighterScript);
126✔
2494
                return `${namespaceName}.${name}`;
126✔
2495
            } else {
2496
                return name;
138✔
2497
            }
2498
        } else {
2499
            //return undefined which will allow outside callers to know that this doesn't have a name
2500
            return undefined;
×
2501
        }
2502
    }
2503

2504
    transpile(state: BrsTranspileState) {
2505
        //enum declarations do not exist at runtime, so don't transpile anything...
2506
        return [];
19✔
2507
    }
2508

2509
    getTypedef(state: BrsTranspileState) {
2510
        const result = [] as TranspileResult;
1✔
2511
        for (let annotation of this.annotations ?? []) {
1!
2512
            result.push(
×
2513
                ...annotation.getTypedef(state),
2514
                state.newline,
2515
                state.indent()
2516
            );
2517
        }
2518
        result.push(
1✔
2519
            this.tokens.enum.text ?? 'enum',
3!
2520
            ' ',
2521
            this.tokens.name.text
2522
        );
2523
        result.push(state.newline);
1✔
2524
        state.blockDepth++;
1✔
2525
        for (const member of this.body) {
1✔
2526
            if (isTypedefProvider(member)) {
1!
2527
                result.push(
1✔
2528
                    state.indent(),
2529
                    ...member.getTypedef(state),
2530
                    state.newline
2531
                );
2532
            }
2533
        }
2534
        state.blockDepth--;
1✔
2535
        result.push(
1✔
2536
            state.indent(),
2537
            this.tokens.endEnum.text ?? 'end enum'
3!
2538
        );
2539
        return result;
1✔
2540
    }
2541

2542
    walk(visitor: WalkVisitor, options: WalkOptions) {
2543
        if (options.walkMode & InternalWalkMode.walkStatements) {
278!
2544
            walkArray(this.body, visitor, options, this);
278✔
2545

2546
        }
2547
    }
2548
}
2549

2550
export class EnumMemberStatement extends Statement implements TypedefProvider {
1✔
2551

2552
    public constructor(
2553
        public tokens: {
176✔
2554
            name: Identifier;
2555
            equal?: Token;
2556
        },
2557
        public value?: Expression
176✔
2558
    ) {
2559
        super();
176✔
2560
    }
2561

2562
    /**
2563
     * The name of the member
2564
     */
2565
    public get name() {
2566
        return this.tokens.name.text;
503✔
2567
    }
2568

2569
    public get range() {
2570
        return util.createBoundingRange(
52✔
2571
            this.tokens.name,
2572
            this.tokens.equal,
2573
            this.value
2574
        );
2575
    }
2576

2577
    /**
2578
     * Get the value of this enum. Requires that `.parent` is set
2579
     */
2580
    public getValue() {
2581
        return (this.parent as EnumStatement).getMemberValue(this.name);
45✔
2582
    }
2583

2584
    public transpile(state: BrsTranspileState): TranspileResult {
2585
        return [];
×
2586
    }
2587

2588
    getTypedef(state: BrsTranspileState): (string | SourceNode)[] {
2589
        const result = [
1✔
2590
            this.tokens.name.text
2591
        ] as TranspileResult;
2592
        if (this.tokens.equal) {
1!
2593
            result.push(' ', this.tokens.equal.text, ' ');
×
2594
            if (this.value) {
×
2595
                result.push(
×
2596
                    ...this.value.transpile(state)
2597
                );
2598
            }
2599
        }
2600
        return result;
1✔
2601
    }
2602

2603
    walk(visitor: WalkVisitor, options: WalkOptions) {
2604
        if (this.value && options.walkMode & InternalWalkMode.walkExpressions) {
385✔
2605
            walk(this, 'value', visitor, options);
213✔
2606
        }
2607
    }
2608
}
2609

2610
export class ConstStatement extends Statement implements TypedefProvider {
1✔
2611

2612
    public constructor(
2613
        public tokens: {
39✔
2614
            const: Token;
2615
            name: Identifier;
2616
            equals: Token;
2617
        },
2618
        public value: Expression
39✔
2619
    ) {
2620
        super();
39✔
2621
        this.range = util.createBoundingRange(this.tokens.const, this.tokens.name, this.tokens.equals, this.value);
39✔
2622
    }
2623

2624
    public range: Range;
2625

2626
    public get name() {
2627
        return this.tokens.name.text;
3✔
2628
    }
2629

2630
    /**
2631
     * The name of the statement WITH its leading namespace (if applicable)
2632
     */
2633
    public get fullName() {
2634
        const name = this.tokens.name?.text;
69!
2635
        if (name) {
69!
2636
            const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
69✔
2637
            if (namespace) {
69✔
2638
                let namespaceName = namespace.getName(ParseMode.BrighterScript);
53✔
2639
                return `${namespaceName}.${name}`;
53✔
2640
            } else {
2641
                return name;
16✔
2642
            }
2643
        } else {
2644
            //return undefined which will allow outside callers to know that this doesn't have a name
2645
            return undefined;
×
2646
        }
2647
    }
2648

2649
    public transpile(state: BrsTranspileState): TranspileResult {
2650
        //const declarations don't exist at runtime, so just transpile empty
2651
        return [];
10✔
2652
    }
2653

2654
    getTypedef(state: BrsTranspileState): (string | SourceNode)[] {
2655
        return [
3✔
2656
            state.tokenToSourceNode(this.tokens.const),
2657
            ' ',
2658
            state.tokenToSourceNode(this.tokens.name),
2659
            ' ',
2660
            state.tokenToSourceNode(this.tokens.equals),
2661
            ' ',
2662
            ...this.value.transpile(state)
2663
        ];
2664
    }
2665

2666
    walk(visitor: WalkVisitor, options: WalkOptions) {
2667
        if (this.value && options.walkMode & InternalWalkMode.walkExpressions) {
83✔
2668
            walk(this, 'value', visitor, options);
81✔
2669
        }
2670
    }
2671
}
2672

2673
export class ContinueStatement extends Statement {
1✔
2674
    constructor(
2675
        public tokens: {
10✔
2676
            continue: Token;
2677
            loopType: Token;
2678
        }
2679
    ) {
2680
        super();
10✔
2681
        this.range = util.createBoundingRange(
10✔
2682
            tokens.continue,
2683
            tokens.loopType
2684
        );
2685
    }
2686

2687
    public range: Range;
2688

2689
    transpile(state: BrsTranspileState) {
2690
        return [
2✔
2691
            state.sourceNode(this.tokens.continue, this.tokens.continue?.text ?? 'continue'),
12!
2692
            this.tokens.loopType?.leadingWhitespace ?? ' ',
12!
2693
            state.sourceNode(this.tokens.continue, this.tokens.loopType?.text)
6!
2694
        ];
2695
    }
2696
    walk(visitor: WalkVisitor, options: WalkOptions) {
2697
        //nothing to walk
2698
    }
2699
}
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