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

rokucommunity / brighterscript / #15903

11 May 2026 06:41PM UTC coverage: 86.896% (-2.2%) from 89.094%
#15903

push

web-flow
Merge 70dfd6181 into ce68f5cb7

15597 of 18958 branches covered (82.27%)

Branch coverage included in aggregate %.

9 of 9 new or added lines in 3 files covered. (100.0%)

955 existing lines in 53 files now uncovered.

16351 of 17808 relevant lines covered (91.82%)

27326.16 hits per line

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

88.14
/src/parser/Statement.ts
1
/* eslint-disable no-bitwise */
2
import type { Token, Identifier } from '../lexer/Token';
3
import { TokenKind } from '../lexer/TokenKind';
1✔
4
import type { DottedGetExpression, FunctionParameterExpression, LiteralExpression, TypecastExpression, TypeExpression } from './Expression';
5
import { FunctionExpression } from './Expression';
1✔
6
import { CallExpression, VariableExpression } from './Expression';
1✔
7
import { util } from '../util';
1✔
8
import type { Location } from 'vscode-languageserver';
9
import type { BrsTranspileState } from './BrsTranspileState';
10
import { ParseMode } from './Parser';
1✔
11
import type { WalkVisitor, WalkOptions } from '../astUtils/visitors';
12
import { InternalWalkMode, walk, createVisitor, WalkMode, walkArray } from '../astUtils/visitors';
1✔
13
import { isCallExpression, isCatchStatement, isConditionalCompileStatement, isEnumMemberStatement, isEnumStatement, isExpressionStatement, isFieldStatement, isForEachStatement, isForStatement, isFunctionExpression, isFunctionStatement, isIfStatement, isInterfaceFieldStatement, isInterfaceMethodStatement, isInvalidType, isLiteralExpression, isMethodStatement, isNamespaceStatement, isPrintSeparatorExpression, isTryCatchStatement, isTypedefProvider, isUnaryExpression, isUninitializedType, isVoidType, isWhileStatement } from '../astUtils/reflection';
1✔
14
import type { GetTypeOptions } from '../interfaces';
15
import { TypeChainEntry, type TranspileResult, type TypedefProvider } from '../interfaces';
1✔
16
import { createIdentifier, createInvalidLiteral, createMethodStatement, createToken } from '../astUtils/creators';
1✔
17
import { DynamicType } from '../types/DynamicType';
1✔
18
import type { BscType } from '../types/BscType';
19
import { SymbolTable } from '../SymbolTable';
1✔
20
import type { Expression } from './AstNode';
21
import { AstNodeKind, Statement } from './AstNode';
1✔
22
import { ClassType } from '../types/ClassType';
1✔
23
import { EnumMemberType, EnumType } from '../types/EnumType';
1✔
24
import { NamespaceType } from '../types/NamespaceType';
1✔
25
import { InterfaceType } from '../types/InterfaceType';
1✔
26
import { VoidType } from '../types/VoidType';
1✔
27
import { TypedFunctionType } from '../types/TypedFunctionType';
1✔
28
import { ArrayType } from '../types/ArrayType';
1✔
29
import { SymbolTypeFlag } from '../SymbolTypeFlag';
1✔
30
import brsDocParser from './BrightScriptDocParser';
1✔
31
import { ArrayDefaultTypeReferenceType } from '../types/ReferenceType';
1✔
32
export class EmptyStatement extends Statement {
1✔
33
    constructor(options?: { range?: Location }
34
    ) {
35
        super();
6✔
36
        this.location = undefined;
6✔
37
    }
38
    /**
39
     * Create a negative range to indicate this is an interpolated location
40
     */
41
    public readonly location?: Location;
42

43
    public readonly kind = AstNodeKind.EmptyStatement;
6✔
44

45
    transpile(state: BrsTranspileState) {
46
        return [];
2✔
47
    }
48
    walk(visitor: WalkVisitor, options: WalkOptions) {
49
        //nothing to walk
50
    }
51

52
    public clone() {
53
        return this.finalizeClone(
1✔
54
            new EmptyStatement({
55
                range: util.cloneLocation(this.location)
56
            })
57
        );
58
    }
59
}
60

61
/**
62
 * This is a top-level statement. Consider this the root of the AST
63
 */
64
export class Body extends Statement implements TypedefProvider {
1✔
65
    constructor(options?: {
66
        statements?: Statement[];
67
    }) {
68
        super();
10,134✔
69
        this.statements = options?.statements ?? [];
10,134!
70
    }
71

72
    public readonly statements: Statement[] = [];
10,134✔
73
    public readonly kind = AstNodeKind.Body;
10,134✔
74

75
    public readonly symbolTable = new SymbolTable('Body', () => this.parent?.getSymbolTable());
44,168✔
76

77
    public get location() {
78
        if (!this._location) {
1,119✔
79
            //this needs to be a getter because the body has its statements pushed to it after being constructed
80
            this._location = util.createBoundingLocation(
930✔
81
                ...(this.statements ?? [])
2,790✔
82
            );
83
        }
84
        return this._location;
1,119✔
85
    }
86
    public set location(value) {
UNCOV
87
        this._location = value;
×
88
    }
89
    private _location: Location;
90

91
    transpile(state: BrsTranspileState) {
92
        let result: TranspileResult = state.transpileAnnotations(this);
936✔
93
        for (let i = 0; i < this.statements.length; i++) {
936✔
94
            let statement = this.statements[i];
2,062✔
95
            let previousStatement = this.statements[i - 1];
2,062✔
96
            let nextStatement = this.statements[i + 1];
2,062✔
97

98
            if (!previousStatement) {
2,062✔
99
                //this is the first statement. do nothing related to spacing and newlines
100

101
                //if comment is on same line as prior sibling
102
            } else if (util.hasLeadingComments(statement) && previousStatement && util.getLeadingComments(statement)?.[0]?.location?.range?.start.line === previousStatement.location?.range?.end.line) {
1,131!
103
                result.push(
8✔
104
                    ' '
105
                );
106
                //add double newline if this is a comment, and next is a function
107
            } else if (util.hasLeadingComments(statement) && nextStatement && isFunctionStatement(nextStatement)) {
1,123✔
108
                result.push(state.newline, state.newline);
445✔
109

110
                //add double newline if is function not preceeded by a comment
111
            } else if (isFunctionStatement(statement) && previousStatement && !util.hasLeadingComments(statement)) {
678✔
112
                result.push(state.newline, state.newline);
125✔
113
            } else {
114
                //separate statements by a single newline
115
                result.push(state.newline);
553✔
116
            }
117

118
            result.push(...statement.transpile(state));
2,062✔
119
        }
120
        return result;
936✔
121
    }
122

123
    getTypedef(state: BrsTranspileState): TranspileResult {
124
        let result = [] as TranspileResult;
48✔
125
        for (const statement of this.statements) {
48✔
126
            //if the current statement supports generating typedef, call it
127
            if (isTypedefProvider(statement)) {
89!
128
                result.push(
89✔
129
                    state.indent(),
130
                    ...statement.getTypedef(state),
131
                    state.newline
132
                );
133
            }
134
        }
135
        return result;
48✔
136
    }
137

138
    walk(visitor: WalkVisitor, options: WalkOptions) {
139
        if (options.walkMode & InternalWalkMode.walkStatements) {
22,080!
140
            walkArray(this.statements, visitor, options, this);
22,080✔
141
        }
142
    }
143

144
    public clone() {
145
        return this.finalizeClone(
138✔
146
            new Body({
147
                statements: this.statements?.map(s => s?.clone())
144✔
148
            }),
149
            ['statements']
150
        );
151
    }
152
}
153

154
export class AssignmentStatement extends Statement {
1✔
155
    constructor(options: {
156
        name: Identifier;
157
        equals?: Token;
158
        value: Expression;
159
        as?: Token;
160
        typeExpression?: TypeExpression;
161
    }) {
162
        super();
1,970✔
163
        this.value = options.value;
1,970✔
164
        this.tokens = {
1,970✔
165
            equals: options.equals,
166
            name: options.name,
167
            as: options.as
168
        };
169
        this.typeExpression = options.typeExpression;
1,970✔
170
        this.location = util.createBoundingLocation(util.createBoundingLocationFromTokens(this.tokens), this.value);
1,970✔
171
    }
172

173
    public readonly tokens: {
174
        readonly equals?: Token;
175
        readonly name: Identifier;
176
        readonly as?: Token;
177
    };
178

179
    public readonly value: Expression;
180

181
    public readonly typeExpression?: TypeExpression;
182

183
    public readonly kind = AstNodeKind.AssignmentStatement;
1,970✔
184

185
    public readonly location: Location | undefined;
186

187
    transpile(state: BrsTranspileState) {
188
        return [
726✔
189
            state.transpileToken(this.tokens.name),
190
            ' ',
191
            state.transpileToken(this.tokens.equals ?? createToken(TokenKind.Equal), '='),
2,178!
192
            ' ',
193
            ...this.value.transpile(state)
194
        ];
195
    }
196

197
    walk(visitor: WalkVisitor, options: WalkOptions) {
198
        if (options.walkMode & InternalWalkMode.walkExpressions) {
8,891✔
199
            walk(this, 'typeExpression', visitor, options);
8,730✔
200
            walk(this, 'value', visitor, options);
8,730✔
201
        }
202
    }
203

204
    getType(options: GetTypeOptions) {
205
        const variableTypeFromCode = this.typeExpression?.getType({ ...options, typeChain: undefined });
1,931✔
206
        const docs = brsDocParser.parseNode(this);
1,931✔
207
        const variableTypeFromDocs = docs?.getTypeTagBscType(options);
1,931!
208
        const variableType = util.chooseTypeFromCodeOrDocComment(variableTypeFromCode, variableTypeFromDocs, options) ?? this.value.getType({ ...options, typeChain: undefined });
1,931✔
209

210
        // Note: compound assignments (eg. +=) are internally dealt with via the RHS being a BinaryExpression
211
        // so this.value will be a BinaryExpression, and BinaryExpressions can figure out their own types
212
        options.typeChain?.push(new TypeChainEntry({ name: this.tokens.name.text, type: variableType, data: options.data, location: this.tokens.name?.location, astNode: this }));
1,931!
213
        return variableType;
1,931✔
214
    }
215

216
    get leadingTrivia(): Token[] {
217
        return this.tokens.name.leadingTrivia;
11,076✔
218
    }
219

220
    public clone() {
221
        return this.finalizeClone(
18✔
222
            new AssignmentStatement({
223
                name: util.cloneToken(this.tokens.name),
224
                value: this.value?.clone(),
54✔
225
                as: util.cloneToken(this.tokens.as),
226
                equals: util.cloneToken(this.tokens.equals),
227
                typeExpression: this.typeExpression?.clone()
54!
228
            }),
229
            ['value', 'typeExpression']
230
        );
231
    }
232
}
233

234
export class AugmentedAssignmentStatement extends Statement {
1✔
235
    constructor(options: {
236
        item: Expression;
237
        operator: Token;
238
        value: Expression;
239
    }) {
240
        super();
108✔
241
        this.value = options.value;
108✔
242
        this.tokens = {
108✔
243
            operator: options.operator
244
        };
245
        this.item = options.item;
108✔
246
        this.value = options.value;
108✔
247
        this.location = util.createBoundingLocation(this.item, util.createBoundingLocationFromTokens(this.tokens), this.value);
108✔
248
    }
249

250
    public readonly tokens: {
251
        readonly operator?: Token;
252
    };
253

254
    public readonly item: Expression;
255

256
    public readonly value: Expression;
257

258
    public readonly kind = AstNodeKind.AugmentedAssignmentStatement;
108✔
259

260
    public readonly location: Location | undefined;
261

262
    transpile(state: BrsTranspileState) {
263
        return [
35✔
264
            this.item.transpile(state),
265
            ' ',
266
            state.transpileToken(this.tokens.operator),
267
            ' ',
268
            this.value.transpile(state)
269
        ];
270
    }
271

272
    walk(visitor: WalkVisitor, options: WalkOptions) {
273
        if (options.walkMode & InternalWalkMode.walkExpressions) {
499✔
274
            walk(this, 'item', visitor, options);
490✔
275
            walk(this, 'value', visitor, options);
490✔
276
        }
277
    }
278

279
    getType(options: GetTypeOptions) {
UNCOV
280
        const variableType = util.binaryOperatorResultType(this.item.getType(options), this.tokens.operator, this.value.getType(options));
×
281

282
        //const variableType = this.typeExpression?.getType({ ...options, typeChain: undefined }) ?? this.value.getType({ ...options, typeChain: undefined });
283

284
        // Note: compound assignments (eg. +=) are internally dealt with via the RHS being a BinaryExpression
285
        // so this.value will be a BinaryExpression, and BinaryExpressions can figure out their own types
286
        // options.typeChain?.push(new TypeChainEntry({ name: this.tokens.name.text, type: variableType, data: options.data, range: this.tokens.name.range, astNode: this }));
UNCOV
287
        return variableType;
×
288
    }
289

290
    get leadingTrivia(): Token[] {
291
        return this.item.leadingTrivia;
497✔
292
    }
293

294
    public clone() {
295
        return this.finalizeClone(
2✔
296
            new AugmentedAssignmentStatement({
297
                item: this.item?.clone(),
6!
298
                operator: util.cloneToken(this.tokens.operator),
299
                value: this.value?.clone()
6!
300
            }),
301
            ['item', 'value']
302
        );
303
    }
304
}
305

306
export class Block extends Statement {
1✔
307
    constructor(options: {
308
        statements: Statement[];
309
    }) {
310
        super();
9,028✔
311
        this.statements = options.statements;
9,028✔
312
        this.symbolTable = new SymbolTable('Block', () => this.parent.getSymbolTable());
63,020✔
313
    }
314

315
    public readonly statements: Statement[];
316

317
    public readonly kind = AstNodeKind.Block;
9,028✔
318

319
    private buildLocation(): Location {
320
        if (this.statements?.length > 0) {
4,593✔
321
            return util.createBoundingLocation(...this.statements ?? []);
4,310!
322
        }
323
        let lastBitBefore: Location;
324
        let firstBitAfter: Location;
325

326
        if (isFunctionExpression(this.parent)) {
283✔
327
            lastBitBefore = util.createBoundingLocation(
130✔
328
                this.parent.tokens.functionType,
329
                this.parent.tokens.leftParen,
330
                ...(this.parent.parameters ?? []),
390!
331
                this.parent.tokens.rightParen,
332
                this.parent.tokens.as,
333
                this.parent.returnTypeExpression
334
            );
335
            firstBitAfter = this.parent.tokens.endFunctionType?.location;
130!
336
        } else if (isIfStatement(this.parent)) {
153✔
337
            if (this.parent.thenBranch === this) {
18✔
338
                lastBitBefore = util.createBoundingLocation(
16✔
339
                    this.parent.tokens.then,
340
                    this.parent.condition
341
                );
342
                firstBitAfter = util.createBoundingLocation(
16✔
343
                    this.parent.tokens.else,
344
                    this.parent.elseBranch,
345
                    this.parent.tokens.endIf
346
                );
347
            } else if (this.parent.elseBranch === this) {
2!
348
                lastBitBefore = this.parent.tokens.else?.location;
2!
349
                firstBitAfter = this.parent.tokens.endIf?.location;
2!
350
            }
351
        } else if (isConditionalCompileStatement(this.parent)) {
135✔
352
            if (this.parent.thenBranch === this) {
3!
353
                lastBitBefore = util.createBoundingLocation(
3✔
354
                    this.parent.tokens.condition,
355
                    this.parent.tokens.not,
356
                    this.parent.tokens.hashIf
357
                );
358
                firstBitAfter = util.createBoundingLocation(
3✔
359
                    this.parent.tokens.hashElse,
360
                    this.parent.elseBranch,
361
                    this.parent.tokens.hashEndIf
362
                );
UNCOV
363
            } else if (this.parent.elseBranch === this) {
×
UNCOV
364
                lastBitBefore = this.parent.tokens.hashElse?.location;
×
UNCOV
365
                firstBitAfter = this.parent.tokens.hashEndIf?.location;
×
366
            }
367
        } else if (isForStatement(this.parent)) {
132✔
368
            lastBitBefore = util.createBoundingLocation(
4✔
369
                this.parent.increment,
370
                this.parent.tokens.step,
371
                this.parent.finalValue,
372
                this.parent.tokens.to,
373
                this.parent.counterDeclaration,
374
                this.parent.tokens.for
375
            );
376
            firstBitAfter = this.parent.tokens.endFor?.location;
4!
377
        } else if (isForEachStatement(this.parent)) {
128✔
378
            lastBitBefore = util.createBoundingLocation(
3✔
379
                this.parent.target,
380
                this.parent.tokens.in,
381
                this.parent.tokens.item,
382
                this.parent.tokens.forEach
383
            );
384
            firstBitAfter = this.parent.tokens.endFor?.location;
3!
385
        } else if (isWhileStatement(this.parent)) {
125✔
386
            lastBitBefore = util.createBoundingLocation(
2✔
387
                this.parent.condition,
388
                this.parent.tokens.while
389
            );
390
            firstBitAfter = this.parent.tokens.endWhile?.location;
2!
391
        } else if (isTryCatchStatement(this.parent)) {
123✔
392
            lastBitBefore = util.createBoundingLocation(
1✔
393
                this.parent.tokens.try
394
            );
395
            firstBitAfter = util.createBoundingLocation(
1✔
396
                this.parent.tokens.endTry,
397
                this.parent.catchStatement
398
            );
399
        } else if (isCatchStatement(this.parent) && isTryCatchStatement(this.parent?.parent)) {
122!
400
            lastBitBefore = util.createBoundingLocation(
6✔
401
                this.parent.tokens.catch,
402
                this.parent.exceptionVariableExpression
403
            );
404
            firstBitAfter = this.parent.parent.tokens.endTry?.location;
6!
405
        }
406
        if (lastBitBefore?.range && firstBitAfter?.range) {
283✔
407
            return util.createLocation(
141✔
408
                lastBitBefore.range.end.line,
409
                lastBitBefore.range.end.character,
410
                firstBitAfter.range.start.line,
411
                firstBitAfter.range.start.character,
412
                lastBitBefore.uri ?? firstBitAfter.uri
423✔
413
            );
414
        }
415
    }
416

417
    public get location() {
418
        if (!this._location) {
6,315✔
419
            //this needs to be a getter because the body has its statements pushed to it after being constructed
420
            this._location = this.buildLocation();
4,593✔
421
        }
422
        return this._location;
6,315✔
423
    }
424
    public set location(value) {
425
        this._location = value;
20✔
426
    }
427
    private _location: Location;
428

429
    transpile(state: BrsTranspileState) {
430
        state.blockDepth++;
5,581✔
431
        let results = [] as TranspileResult;
5,581✔
432
        for (let i = 0; i < this.statements.length; i++) {
5,581✔
433
            let previousStatement = this.statements[i - 1];
6,681✔
434
            let statement = this.statements[i];
6,681✔
435
            //is not a comment
436
            //if comment is on same line as parent
437
            if (util.isLeadingCommentOnSameLine(state.lineage[0]?.location, statement) ||
6,681!
438
                util.isLeadingCommentOnSameLine(previousStatement?.location, statement)
19,995✔
439
            ) {
440
                results.push(' ');
50✔
441

442
                //is not a comment
443
            } else {
444
                //add a newline and indent
445
                results.push(
6,631✔
446
                    state.newline,
447
                    state.indent()
448
                );
449
            }
450

451
            //push block onto parent list
452
            state.lineage.unshift(this);
6,681✔
453
            results.push(
6,681✔
454
                ...statement.transpile(state)
455
            );
456
            state.lineage.shift();
6,681✔
457
        }
458
        state.blockDepth--;
5,581✔
459
        return results;
5,581✔
460
    }
461

462
    public get leadingTrivia(): Token[] {
463
        return this.statements[0]?.leadingTrivia ?? [];
14,664✔
464
    }
465

466
    walk(visitor: WalkVisitor, options: WalkOptions) {
467
        if (options.walkMode & InternalWalkMode.walkStatements) {
41,036✔
468
            walkArray(this.statements, visitor, options, this);
41,030✔
469
        }
470
    }
471

472
    public clone() {
473
        return this.finalizeClone(
128✔
474
            new Block({
475
                statements: this.statements?.map(s => s?.clone())
119✔
476
            }),
477
            ['statements']
478
        );
479
    }
480
}
481

482
export class ExpressionStatement extends Statement {
1✔
483
    constructor(options: {
484
        expression: Expression;
485
    }) {
486
        super();
1,072✔
487
        this.expression = options.expression;
1,072✔
488
        this.location = this.expression?.location;
1,072✔
489
    }
490
    public readonly expression: Expression;
491
    public readonly kind = AstNodeKind.ExpressionStatement;
1,072✔
492

493
    public readonly location: Location | undefined;
494

495
    transpile(state: BrsTranspileState) {
496
        return [
72✔
497
            state.transpileAnnotations(this),
498
            this.expression.transpile(state)
499
        ];
500
    }
501

502
    getTypedef(state: BrsTranspileState): TranspileResult {
503
        //ExpressionStatements should not be included in typedefs
504
        //as they represent code execution which is not part of the type definition
UNCOV
505
        return [];
×
506
    }
507

508
    walk(visitor: WalkVisitor, options: WalkOptions) {
509
        if (options.walkMode & InternalWalkMode.walkExpressions) {
4,390✔
510
            walk(this, 'expression', visitor, options);
4,359✔
511
        }
512
    }
513

514
    get leadingTrivia(): Token[] {
515
        return this.expression.leadingTrivia;
4,866✔
516
    }
517

518
    public clone() {
519
        return this.finalizeClone(
14✔
520
            new ExpressionStatement({
521
                expression: this.expression?.clone()
42✔
522
            }),
523
            ['expression']
524
        );
525
    }
526
}
527

528
export class ExitStatement extends Statement {
1✔
529
    constructor(options?: {
530
        exit?: Token;
531
        loopType: Token;
532
    }) {
533
        super();
27✔
534
        this.tokens = {
27✔
535
            exit: options?.exit,
81!
536
            loopType: options.loopType
537
        };
538
        this.location = util.createBoundingLocation(
27✔
539
            this.tokens.exit,
540
            this.tokens.loopType
541
        );
542
    }
543

544
    public readonly tokens: {
545
        readonly exit: Token;
546
        readonly loopType?: Token;
547
    };
548

549
    public readonly kind = AstNodeKind.ExitStatement;
27✔
550

551
    public readonly location?: Location;
552

553
    transpile(state: BrsTranspileState) {
554
        return [
11✔
555
            state.transpileToken(this.tokens.exit, 'exit'),
556
            this.tokens.loopType?.leadingWhitespace ?? ' ',
66!
557
            state.transpileToken(this.tokens.loopType)
558
        ];
559
    }
560

561
    walk(visitor: WalkVisitor, options: WalkOptions) {
562
        //nothing to walk
563
    }
564

565
    get leadingTrivia(): Token[] {
566
        return this.tokens.exit?.leadingTrivia;
124!
567
    }
568

569
    public clone() {
570
        return this.finalizeClone(
2✔
571
            new ExitStatement({
572
                loopType: util.cloneToken(this.tokens.loopType),
573
                exit: util.cloneToken(this.tokens.exit)
574
            })
575
        );
576
    }
577
}
578

579
export class FunctionStatement extends Statement implements TypedefProvider {
1✔
580
    constructor(options: {
581
        name: Identifier;
582
        func: FunctionExpression;
583
    }) {
584
        super();
5,290✔
585
        this.tokens = {
5,290✔
586
            name: options.name
587
        };
588
        this.func = options.func;
5,290✔
589
        if (this.func) {
5,290✔
590
            this.func.symbolTable.name += `: '${this.tokens.name?.text}'`;
5,288!
591
        }
592

593
        this.location = this.func?.location;
5,290✔
594
    }
595

596
    public readonly tokens: {
597
        readonly name: Identifier;
598
    };
599
    public readonly func: FunctionExpression;
600

601
    public readonly kind = AstNodeKind.FunctionStatement as AstNodeKind;
5,290✔
602

603
    public readonly location: Location | undefined;
604

605
    /**
606
     * Get the name of this expression based on the parse mode
607
     */
608
    public getName(parseMode: ParseMode) {
609
        const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
17,494✔
610
        if (namespace) {
17,494✔
611
            let delimiter = parseMode === ParseMode.BrighterScript ? '.' : '_';
3,394✔
612
            let namespaceName = namespace.getName(parseMode);
3,394✔
613
            return namespaceName + delimiter + this.tokens.name?.text;
3,394!
614
        } else {
615
            return this.tokens.name.text;
14,100✔
616
        }
617
    }
618

619
    public get leadingTrivia(): Token[] {
620
        return this.func.leadingTrivia;
23,179✔
621
    }
622

623
    transpile(state: BrsTranspileState): TranspileResult {
624
        //create a fake token using the full transpiled name
625
        let nameToken = {
1,772✔
626
            ...this.tokens.name,
627
            text: this.getName(ParseMode.BrightScript)
628
        };
629

630
        return [
1,772✔
631
            ...state.transpileAnnotations(this),
632
            ...this.func.transpile(state, nameToken)
633
        ];
634
    }
635

636
    getTypedef(state: BrsTranspileState) {
637
        let result: TranspileResult = [];
50✔
638
        for (let comment of util.getLeadingComments(this) ?? []) {
50!
639
            result.push(
182✔
640
                comment.text,
641
                state.newline,
642
                state.indent()
643
            );
644
        }
645
        for (let annotation of this.annotations ?? []) {
50✔
646
            result.push(
2✔
647
                ...annotation.getTypedef(state),
648
                state.newline,
649
                state.indent()
650
            );
651
        }
652

653
        result.push(
50✔
654
            ...this.func.getTypedef(state)
655
        );
656
        return result;
50✔
657
    }
658

659
    walk(visitor: WalkVisitor, options: WalkOptions) {
660
        if (options.walkMode & InternalWalkMode.walkExpressions) {
24,882✔
661
            walk(this, 'func', visitor, options);
21,714✔
662
        }
663
    }
664

665
    getType(options: GetTypeOptions) {
666
        const funcExprType = this.func.getType(options);
4,784✔
667
        funcExprType.setName(this.getName(ParseMode.BrighterScript));
4,784✔
668
        return funcExprType;
4,784✔
669
    }
670

671
    public clone() {
672
        return this.finalizeClone(
108✔
673
            new FunctionStatement({
674
                func: this.func?.clone(),
324✔
675
                name: util.cloneToken(this.tokens.name)
676
            }),
677
            ['func']
678
        );
679
    }
680
}
681

682
export class IfStatement extends Statement {
1✔
683
    constructor(options: {
684
        if?: Token;
685
        then?: Token;
686
        else?: Token;
687
        endIf?: Token;
688

689
        condition: Expression;
690
        thenBranch: Block;
691
        elseBranch?: IfStatement | Block;
692
    }) {
693
        super();
2,760✔
694
        this.condition = options.condition;
2,760✔
695
        this.thenBranch = options.thenBranch;
2,760✔
696
        this.elseBranch = options.elseBranch;
2,760✔
697

698
        this.tokens = {
2,760✔
699
            if: options.if,
700
            then: options.then,
701
            else: options.else,
702
            endIf: options.endIf
703
        };
704

705
        this.location = util.createBoundingLocation(
2,760✔
706
            util.createBoundingLocationFromTokens(this.tokens),
707
            this.condition,
708
            this.thenBranch,
709
            this.elseBranch
710
        );
711
    }
712

713
    readonly tokens: {
714
        readonly if?: Token;
715
        readonly then?: Token;
716
        readonly else?: Token;
717
        readonly endIf?: Token;
718
    };
719
    public readonly condition: Expression;
720
    public readonly thenBranch: Block;
721
    public readonly elseBranch?: IfStatement | Block;
722

723
    public readonly kind = AstNodeKind.IfStatement;
2,760✔
724

725
    public readonly location: Location | undefined;
726

727
    get isInline() {
728
        const allLeadingTrivia = [
12✔
729
            ...this.thenBranch.leadingTrivia,
730
            ...this.thenBranch.statements.map(s => s.leadingTrivia).flat(),
16✔
731
            ...(this.tokens.else?.leadingTrivia ?? []),
72✔
732
            ...(this.tokens.endIf?.leadingTrivia ?? [])
72✔
733
        ];
734

735
        const hasNewline = allLeadingTrivia.find(t => t.kind === TokenKind.Newline);
29✔
736
        return !hasNewline;
12✔
737
    }
738

739
    transpile(state: BrsTranspileState) {
740
        let results = [] as TranspileResult;
2,751✔
741
        //if   (already indented by block)
742
        results.push(state.transpileToken(this.tokens.if ?? createToken(TokenKind.If)));
2,751!
743
        results.push(' ');
2,751✔
744
        //conditions
745
        results.push(...this.condition.transpile(state));
2,751✔
746
        //then
747
        if (this.tokens.then) {
2,751✔
748
            results.push(' ');
2,294✔
749
            results.push(
2,294✔
750
                state.transpileToken(this.tokens.then, 'then')
751
            );
752
        }
753
        state.lineage.unshift(this);
2,751✔
754

755
        //if statement body
756
        let thenNodes = this.thenBranch.transpile(state);
2,751✔
757
        state.lineage.shift();
2,751✔
758
        if (thenNodes.length > 0) {
2,751✔
759
            results.push(thenNodes);
2,740✔
760
        }
761
        //else branch
762
        if (this.elseBranch) {
2,751✔
763
            //else
764
            results.push(...state.transpileEndBlockToken(this.thenBranch, this.tokens.else, 'else'));
2,286✔
765

766
            if (isIfStatement(this.elseBranch)) {
2,286✔
767
                //chained elseif
768
                state.lineage.unshift(this.elseBranch);
1,357✔
769
                let body = this.elseBranch.transpile(state);
1,357✔
770
                state.lineage.shift();
1,357✔
771

772
                if (body.length > 0) {
1,357!
773
                    //zero or more spaces between the `else` and the `if`
774
                    results.push(this.elseBranch.tokens.if.leadingWhitespace!);
1,357✔
775
                    results.push(...body);
1,357✔
776

777
                    // stop here because chained if will transpile the rest
778
                    return results;
1,357✔
779
                } else {
UNCOV
780
                    results.push('\n');
×
781
                }
782

783
            } else {
784
                //else body
785
                state.lineage.unshift(this.tokens.else!);
929✔
786
                let body = this.elseBranch.transpile(state);
929✔
787
                state.lineage.shift();
929✔
788

789
                if (body.length > 0) {
929✔
790
                    results.push(...body);
927✔
791
                }
792
            }
793
        }
794

795
        //end if
796
        results.push(...state.transpileEndBlockToken(this.elseBranch ?? this.thenBranch, this.tokens.endIf, 'end if'));
1,394✔
797

798
        return results;
1,394✔
799
    }
800

801
    walk(visitor: WalkVisitor, options: WalkOptions) {
802
        if (options.walkMode & InternalWalkMode.walkExpressions) {
11,563✔
803
            walk(this, 'condition', visitor, options);
11,546✔
804
        }
805
        if (options.walkMode & InternalWalkMode.walkStatements) {
11,563✔
806
            walk(this, 'thenBranch', visitor, options);
11,561✔
807
        }
808
        if (this.elseBranch && options.walkMode & InternalWalkMode.walkStatements) {
11,563✔
809
            walk(this, 'elseBranch', visitor, options);
9,109✔
810
        }
811
    }
812

813
    get leadingTrivia(): Token[] {
814
        return this.tokens.if?.leadingTrivia ?? [];
3,880!
815
    }
816

817
    get endTrivia(): Token[] {
818
        return this.tokens.endIf?.leadingTrivia ?? [];
19✔
819
    }
820

821
    public clone() {
822
        return this.finalizeClone(
5✔
823
            new IfStatement({
824
                if: util.cloneToken(this.tokens.if),
825
                else: util.cloneToken(this.tokens.else),
826
                endIf: util.cloneToken(this.tokens.endIf),
827
                then: util.cloneToken(this.tokens.then),
828
                condition: this.condition?.clone(),
15✔
829
                thenBranch: this.thenBranch?.clone(),
15✔
830
                elseBranch: this.elseBranch?.clone()
15✔
831
            }),
832
            ['condition', 'thenBranch', 'elseBranch']
833
        );
834
    }
835
}
836

837
export class IncrementStatement extends Statement {
1✔
838
    constructor(options: {
839
        value: Expression;
840
        operator: Token;
841
    }) {
842
        super();
29✔
843
        this.value = options.value;
29✔
844
        this.tokens = {
29✔
845
            operator: options.operator
846
        };
847
        this.location = util.createBoundingLocation(
29✔
848
            this.value,
849
            this.tokens.operator
850
        );
851
    }
852

853
    public readonly value: Expression;
854
    public readonly tokens: {
855
        readonly operator: Token;
856
    };
857

858
    public readonly kind = AstNodeKind.IncrementStatement;
29✔
859

860
    public readonly location: Location | undefined;
861

862
    transpile(state: BrsTranspileState) {
863
        return [
6✔
864
            ...this.value.transpile(state),
865
            state.transpileToken(this.tokens.operator)
866
        ];
867
    }
868

869
    walk(visitor: WalkVisitor, options: WalkOptions) {
870
        if (options.walkMode & InternalWalkMode.walkExpressions) {
94✔
871
            walk(this, 'value', visitor, options);
93✔
872
        }
873
    }
874

875
    get leadingTrivia(): Token[] {
876
        return this.value?.leadingTrivia ?? [];
94!
877
    }
878

879
    public clone() {
880
        return this.finalizeClone(
2✔
881
            new IncrementStatement({
882
                value: this.value?.clone(),
6✔
883
                operator: util.cloneToken(this.tokens.operator)
884
            }),
885
            ['value']
886
        );
887
    }
888
}
889

890
/**
891
 * Represents a `print` statement within BrightScript.
892
 */
893
export class PrintStatement extends Statement {
1✔
894
    /**
895
     * Creates a new internal representation of a BrightScript `print` statement.
896
     * @param options the options for this statement
897
     * @param options.print a print token
898
     * @param options.expressions an array of expressions to be evaluated and printed. Wrap PrintSeparator tokens (`;` or `,`) in `PrintSeparatorExpression`
899
     */
900
    constructor(options: {
901
        print?: Token;
902
        expressions: Array<Expression>;
903
    }) {
904
        super();
1,570✔
905
        this.tokens = {
1,570✔
906
            print: options.print
907
        };
908
        this.expressions = options.expressions;
1,570✔
909
        this.location = util.createBoundingLocation(
1,570✔
910
            this.tokens.print,
911
            ...(this.expressions ?? [])
4,710✔
912
        );
913
    }
914

915
    public readonly tokens: {
916
        readonly print?: Token;
917
    };
918

919
    public readonly expressions: Array<Expression>;
920

921
    public readonly kind = AstNodeKind.PrintStatement;
1,570✔
922

923
    public readonly location: Location | undefined;
924

925
    transpile(state: BrsTranspileState) {
926
        let result = [
258✔
927
            state.transpileToken(this.tokens.print, 'print')
928
        ] as TranspileResult;
929

930
        //if the first expression has no leading whitespace, add a single space between the `print` and the expression
931
        if (this.expressions.length > 0 && !this.expressions[0].leadingTrivia.find(t => t?.kind === TokenKind.Whitespace)) {
258✔
932
            result.push(' ');
11✔
933
        }
934

935
        // eslint-disable-next-line @typescript-eslint/prefer-for-of
936
        for (let i = 0; i < this.expressions.length; i++) {
258✔
937
            const expression = this.expressions[i];
322✔
938
            let leadingWhitespace = expression.leadingTrivia.find(t => t?.kind === TokenKind.Whitespace)?.text;
322✔
939
            if (leadingWhitespace) {
322✔
940
                result.push(leadingWhitespace);
271✔
941
                //if the previous expression was NOT a separator, and this one is not also, add a space between them
942
            } else if (i > 0 && !isPrintSeparatorExpression(this.expressions[i - 1]) && !isPrintSeparatorExpression(expression) && !leadingWhitespace) {
51✔
943
                result.push(' ');
6✔
944
            }
945

946
            result.push(
322✔
947
                ...expression.transpile(state)
948
            );
949
        }
950
        return result;
258✔
951
    }
952

953
    walk(visitor: WalkVisitor, options: WalkOptions) {
954
        if (options.walkMode & InternalWalkMode.walkExpressions) {
7,682✔
955
            walkArray(this.expressions, visitor, options, this);
7,582✔
956
        }
957
    }
958

959
    get leadingTrivia(): Token[] {
960
        return this.tokens.print?.leadingTrivia ?? [];
9,623✔
961
    }
962

963
    public clone() {
964
        return this.finalizeClone(
49✔
965
            new PrintStatement({
966
                print: util.cloneToken(this.tokens.print),
967
                expressions: this.expressions?.map(e => e?.clone())
49✔
968
            }),
969
            ['expressions' as any]
970
        );
971
    }
972
}
973

974
export class DimStatement extends Statement {
1✔
975
    constructor(options: {
976
        dim?: Token;
977
        name: Identifier;
978
        openingSquare?: Token;
979
        dimensions: Expression[];
980
        closingSquare?: Token;
981
    }) {
982
        super();
46✔
983
        this.tokens = {
46✔
984
            dim: options?.dim,
138!
985
            name: options.name,
986
            openingSquare: options.openingSquare,
987
            closingSquare: options.closingSquare
988
        };
989
        this.dimensions = options.dimensions;
46✔
990
        this.location = util.createBoundingLocation(
46✔
991
            options.dim,
992
            options.name,
993
            options.openingSquare,
994
            ...(this.dimensions ?? []),
138✔
995
            options.closingSquare
996
        );
997
    }
998

999
    public readonly tokens: {
1000
        readonly dim?: Token;
1001
        readonly name: Identifier;
1002
        readonly openingSquare?: Token;
1003
        readonly closingSquare?: Token;
1004
    };
1005
    public readonly dimensions: Expression[];
1006

1007
    public readonly kind = AstNodeKind.DimStatement;
46✔
1008

1009
    public readonly location: Location | undefined;
1010

1011
    public transpile(state: BrsTranspileState) {
1012
        let result: TranspileResult = [
15✔
1013
            state.transpileToken(this.tokens.dim, 'dim'),
1014
            ' ',
1015
            state.transpileToken(this.tokens.name),
1016
            state.transpileToken(this.tokens.openingSquare, '[')
1017
        ];
1018
        for (let i = 0; i < this.dimensions.length; i++) {
15✔
1019
            if (i > 0) {
32✔
1020
                result.push(', ');
17✔
1021
            }
1022
            result.push(
32✔
1023
                ...this.dimensions![i].transpile(state)
1024
            );
1025
        }
1026
        result.push(state.transpileToken(this.tokens.closingSquare, ']'));
15✔
1027
        return result;
15✔
1028
    }
1029

1030
    public walk(visitor: WalkVisitor, options: WalkOptions) {
1031
        if (this.dimensions?.length !== undefined && this.dimensions?.length > 0 && options.walkMode & InternalWalkMode.walkExpressions) {
148!
1032
            walkArray(this.dimensions, visitor, options, this);
133✔
1033

1034
        }
1035
    }
1036

1037
    public getType(options: GetTypeOptions): BscType {
1038
        const numDimensions = this.dimensions?.length ?? 1;
18!
1039
        let type = new ArrayType();
18✔
1040
        for (let i = 0; i < numDimensions - 1; i++) {
18✔
1041
            type = new ArrayType(type);
17✔
1042
        }
1043
        return type;
18✔
1044
    }
1045

1046
    get leadingTrivia(): Token[] {
1047
        return this.tokens.dim?.leadingTrivia ?? [];
137!
1048
    }
1049

1050
    public clone() {
1051
        return this.finalizeClone(
3✔
1052
            new DimStatement({
1053
                dim: util.cloneToken(this.tokens.dim),
1054
                name: util.cloneToken(this.tokens.name),
1055
                openingSquare: util.cloneToken(this.tokens.openingSquare),
1056
                dimensions: this.dimensions?.map(e => e?.clone()),
5✔
1057
                closingSquare: util.cloneToken(this.tokens.closingSquare)
1058
            }),
1059
            ['dimensions']
1060
        );
1061
    }
1062
}
1063

1064
export class GotoStatement extends Statement {
1✔
1065
    constructor(options: {
1066
        goto?: Token;
1067
        label: Token;
1068
    }) {
1069
        super();
13✔
1070
        this.tokens = {
13✔
1071
            goto: options.goto,
1072
            label: options.label
1073
        };
1074
        this.location = util.createBoundingLocation(
13✔
1075
            this.tokens.goto,
1076
            this.tokens.label
1077
        );
1078
    }
1079

1080
    public readonly tokens: {
1081
        readonly goto?: Token;
1082
        readonly label: Token;
1083
    };
1084

1085
    public readonly kind = AstNodeKind.GotoStatement;
13✔
1086

1087
    public readonly location: Location | undefined;
1088

1089
    transpile(state: BrsTranspileState) {
1090
        return [
2✔
1091
            state.transpileToken(this.tokens.goto, 'goto'),
1092
            ' ',
1093
            state.transpileToken(this.tokens.label)
1094
        ];
1095
    }
1096

1097
    walk(visitor: WalkVisitor, options: WalkOptions) {
1098
        //nothing to walk
1099
    }
1100

1101
    get leadingTrivia(): Token[] {
1102
        return this.tokens.goto?.leadingTrivia ?? [];
19!
1103
    }
1104

1105
    public clone() {
1106
        return this.finalizeClone(
1✔
1107
            new GotoStatement({
1108
                goto: util.cloneToken(this.tokens.goto),
1109
                label: util.cloneToken(this.tokens.label)
1110
            })
1111
        );
1112
    }
1113
}
1114

1115
export class LabelStatement extends Statement {
1✔
1116
    constructor(options: {
1117
        name: Token;
1118
        colon?: Token;
1119
    }) {
1120
        super();
13✔
1121
        this.tokens = {
13✔
1122
            name: options.name,
1123
            colon: options.colon
1124
        };
1125
        this.location = util.createBoundingLocation(
13✔
1126
            this.tokens.name,
1127
            this.tokens.colon
1128
        );
1129
    }
1130
    public readonly tokens: {
1131
        readonly name: Token;
1132
        readonly colon: Token;
1133
    };
1134
    public readonly kind = AstNodeKind.LabelStatement;
13✔
1135

1136
    public readonly location: Location | undefined;
1137

1138
    public get leadingTrivia(): Token[] {
1139
        return this.tokens.name.leadingTrivia;
29✔
1140
    }
1141

1142
    transpile(state: BrsTranspileState) {
1143
        return [
2✔
1144
            state.transpileToken(this.tokens.name),
1145
            state.transpileToken(this.tokens.colon, ':')
1146

1147
        ];
1148
    }
1149

1150
    walk(visitor: WalkVisitor, options: WalkOptions) {
1151
        //nothing to walk
1152
    }
1153

1154
    public clone() {
1155
        return this.finalizeClone(
1✔
1156
            new LabelStatement({
1157
                name: util.cloneToken(this.tokens.name),
1158
                colon: util.cloneToken(this.tokens.colon)
1159
            })
1160
        );
1161
    }
1162
}
1163

1164
export class ReturnStatement extends Statement {
1✔
1165
    constructor(options?: {
1166
        return?: Token;
1167
        value?: Expression;
1168
    }) {
1169
        super();
4,362✔
1170
        this.tokens = {
4,362✔
1171
            return: options?.return
13,086!
1172
        };
1173
        this.value = options?.value;
4,362!
1174
        this.location = util.createBoundingLocation(
4,362✔
1175
            this.tokens.return,
1176
            this.value
1177
        );
1178
    }
1179

1180
    public readonly tokens: {
1181
        readonly return?: Token;
1182
    };
1183
    public readonly value?: Expression;
1184
    public readonly kind = AstNodeKind.ReturnStatement;
4,362✔
1185

1186
    public readonly location: Location | undefined;
1187

1188
    transpile(state: BrsTranspileState) {
1189
        let result = [] as TranspileResult;
4,087✔
1190
        result.push(
4,087✔
1191
            state.transpileToken(this.tokens.return, 'return')
1192
        );
1193
        if (this.value) {
4,087✔
1194
            result.push(' ');
4,082✔
1195
            result.push(...this.value.transpile(state));
4,082✔
1196
        }
1197
        return result;
4,087✔
1198
    }
1199

1200
    walk(visitor: WalkVisitor, options: WalkOptions) {
1201
        if (options.walkMode & InternalWalkMode.walkExpressions) {
19,124✔
1202
            walk(this, 'value', visitor, options);
19,099✔
1203
        }
1204
    }
1205

1206
    get leadingTrivia(): Token[] {
1207
        return this.tokens.return?.leadingTrivia ?? [];
13,281!
1208
    }
1209

1210
    public clone() {
1211
        return this.finalizeClone(
3✔
1212
            new ReturnStatement({
1213
                return: util.cloneToken(this.tokens.return),
1214
                value: this.value?.clone()
9✔
1215
            }),
1216
            ['value']
1217
        );
1218
    }
1219
}
1220

1221
export class EndStatement extends Statement {
1✔
1222
    constructor(options?: {
1223
        end?: Token;
1224
    }) {
1225
        super();
11✔
1226
        this.tokens = {
11✔
1227
            end: options?.end
33!
1228
        };
1229
        this.location = this.tokens.end?.location;
11!
1230
    }
1231
    public readonly tokens: {
1232
        readonly end?: Token;
1233
    };
1234
    public readonly kind = AstNodeKind.EndStatement;
11✔
1235

1236
    public readonly location: Location;
1237

1238
    transpile(state: BrsTranspileState) {
1239
        return [
2✔
1240
            state.transpileToken(this.tokens.end, 'end')
1241
        ];
1242
    }
1243

1244
    walk(visitor: WalkVisitor, options: WalkOptions) {
1245
        //nothing to walk
1246
    }
1247

1248
    get leadingTrivia(): Token[] {
1249
        return this.tokens.end?.leadingTrivia ?? [];
19!
1250
    }
1251

1252
    public clone() {
1253
        return this.finalizeClone(
1✔
1254
            new EndStatement({
1255
                end: util.cloneToken(this.tokens.end)
1256
            })
1257
        );
1258
    }
1259
}
1260

1261
export class StopStatement extends Statement {
1✔
1262
    constructor(options?: {
1263
        stop?: Token;
1264
    }) {
1265
        super();
19✔
1266
        this.tokens = { stop: options?.stop };
19!
1267
        this.location = this.tokens?.stop?.location;
19!
1268
    }
1269
    public readonly tokens: {
1270
        readonly stop?: Token;
1271
    };
1272

1273
    public readonly kind = AstNodeKind.StopStatement;
19✔
1274

1275
    public readonly location: Location;
1276

1277
    transpile(state: BrsTranspileState) {
1278
        return [
2✔
1279
            state.transpileToken(this.tokens.stop, 'stop')
1280
        ];
1281
    }
1282

1283
    walk(visitor: WalkVisitor, options: WalkOptions) {
1284
        //nothing to walk
1285
    }
1286

1287
    get leadingTrivia(): Token[] {
1288
        return this.tokens.stop?.leadingTrivia ?? [];
29!
1289
    }
1290

1291
    public clone() {
1292
        return this.finalizeClone(
1✔
1293
            new StopStatement({
1294
                stop: util.cloneToken(this.tokens.stop)
1295
            })
1296
        );
1297
    }
1298
}
1299

1300
export class ForStatement extends Statement {
1✔
1301
    constructor(options: {
1302
        for?: Token;
1303
        counterDeclaration: AssignmentStatement;
1304
        to?: Token;
1305
        finalValue: Expression;
1306
        body: Block;
1307
        endFor?: Token;
1308
        step?: Token;
1309
        increment?: Expression;
1310
    }) {
1311
        super();
53✔
1312
        this.tokens = {
53✔
1313
            for: options.for,
1314
            to: options.to,
1315
            endFor: options.endFor,
1316
            step: options.step
1317
        };
1318
        this.counterDeclaration = options.counterDeclaration;
53✔
1319
        this.finalValue = options.finalValue;
53✔
1320
        this.body = options.body;
53✔
1321
        this.increment = options.increment;
53✔
1322

1323
        this.location = util.createBoundingLocation(
53✔
1324
            this.tokens.for,
1325
            this.counterDeclaration,
1326
            this.tokens.to,
1327
            this.finalValue,
1328
            this.tokens.step,
1329
            this.increment,
1330
            this.body,
1331
            this.tokens.endFor
1332
        );
1333
    }
1334

1335
    public readonly tokens: {
1336
        readonly for?: Token;
1337
        readonly to?: Token;
1338
        readonly endFor?: Token;
1339
        readonly step?: Token;
1340
    };
1341

1342
    public readonly counterDeclaration: AssignmentStatement;
1343
    public readonly finalValue: Expression;
1344
    public readonly body: Block;
1345
    public readonly increment?: Expression;
1346

1347
    public readonly kind = AstNodeKind.ForStatement;
53✔
1348

1349
    public readonly location: Location | undefined;
1350

1351
    transpile(state: BrsTranspileState) {
1352
        let result = [] as TranspileResult;
11✔
1353
        //for
1354
        result.push(
11✔
1355
            state.transpileToken(this.tokens.for, 'for'),
1356
            ' '
1357
        );
1358
        //i=1
1359
        result.push(
11✔
1360
            ...this.counterDeclaration.transpile(state),
1361
            ' '
1362
        );
1363
        //to
1364
        result.push(
11✔
1365
            state.transpileToken(this.tokens.to, 'to'),
1366
            ' '
1367
        );
1368
        //final value
1369
        result.push(this.finalValue.transpile(state));
11✔
1370
        //step
1371
        if (this.increment) {
11✔
1372
            result.push(
4✔
1373
                ' ',
1374
                state.transpileToken(this.tokens.step, 'step'),
1375
                ' ',
1376
                this.increment!.transpile(state)
1377
            );
1378
        }
1379
        //loop body
1380
        state.lineage.unshift(this);
11✔
1381
        result.push(...this.body.transpile(state));
11✔
1382
        state.lineage.shift();
11✔
1383

1384
        //end for
1385
        result.push(...state.transpileEndBlockToken(this.body, this.tokens.endFor, 'end for'));
11✔
1386

1387
        return result;
11✔
1388
    }
1389

1390
    walk(visitor: WalkVisitor, options: WalkOptions) {
1391
        if (options.walkMode & InternalWalkMode.walkStatements) {
190✔
1392
            walk(this, 'counterDeclaration', visitor, options);
189✔
1393
        }
1394
        if (options.walkMode & InternalWalkMode.walkExpressions) {
190✔
1395
            walk(this, 'finalValue', visitor, options);
186✔
1396
            walk(this, 'increment', visitor, options);
186✔
1397
        }
1398
        if (options.walkMode & InternalWalkMode.walkStatements) {
190✔
1399
            walk(this, 'body', visitor, options);
189✔
1400
        }
1401
    }
1402

1403
    get leadingTrivia(): Token[] {
1404
        return this.tokens.for?.leadingTrivia ?? [];
208!
1405
    }
1406

1407
    public get endTrivia(): Token[] {
UNCOV
1408
        return this.tokens.endFor?.leadingTrivia ?? [];
×
1409
    }
1410

1411
    public clone() {
1412
        return this.finalizeClone(
5✔
1413
            new ForStatement({
1414
                for: util.cloneToken(this.tokens.for),
1415
                counterDeclaration: this.counterDeclaration?.clone(),
15✔
1416
                to: util.cloneToken(this.tokens.to),
1417
                finalValue: this.finalValue?.clone(),
15✔
1418
                body: this.body?.clone(),
15✔
1419
                endFor: util.cloneToken(this.tokens.endFor),
1420
                step: util.cloneToken(this.tokens.step),
1421
                increment: this.increment?.clone()
15✔
1422
            }),
1423
            ['counterDeclaration', 'finalValue', 'body', 'increment']
1424
        );
1425
    }
1426
}
1427

1428
export class ForEachStatement extends Statement {
1✔
1429
    constructor(options: {
1430
        forEach?: Token;
1431
        item: Token;
1432
        as?: Token;
1433
        typeExpression?: TypeExpression;
1434
        in?: Token;
1435
        target: Expression;
1436
        body: Block;
1437
        endFor?: Token;
1438
    }) {
1439
        super();
77✔
1440
        this.tokens = {
77✔
1441
            forEach: options.forEach,
1442
            item: options.item,
1443
            as: options.as,
1444
            in: options.in,
1445
            endFor: options.endFor
1446
        };
1447
        this.body = options.body;
77✔
1448
        this.target = options.target;
77✔
1449
        this.typeExpression = options.typeExpression;
77✔
1450

1451
        this.location = util.createBoundingLocation(
77✔
1452
            this.tokens.forEach,
1453
            this.tokens.item,
1454
            this.tokens.as,
1455
            this.typeExpression,
1456
            this.tokens.in,
1457
            this.target,
1458
            this.body,
1459
            this.tokens.endFor
1460
        );
1461
    }
1462

1463
    public readonly tokens: {
1464
        readonly forEach?: Token;
1465
        readonly item: Token;
1466
        readonly as: Token;
1467
        readonly in?: Token;
1468
        readonly endFor?: Token;
1469
    };
1470
    public readonly body: Block;
1471
    public readonly target: Expression;
1472
    public readonly typeExpression?: TypeExpression;
1473

1474
    public readonly kind = AstNodeKind.ForEachStatement;
77✔
1475

1476
    public readonly location: Location | undefined;
1477

1478
    transpile(state: BrsTranspileState) {
1479
        let result = [] as TranspileResult;
6✔
1480
        //for each
1481
        result.push(
6✔
1482
            state.transpileToken(this.tokens.forEach, 'for each'),
1483
            ' '
1484
        );
1485
        //item
1486
        result.push(
6✔
1487
            state.transpileToken(this.tokens.item),
1488
            ' '
1489
        );
1490
        //in
1491
        result.push(
6✔
1492
            state.transpileToken(this.tokens.in, 'in'),
1493
            ' '
1494
        );
1495
        //target
1496
        result.push(...this.target.transpile(state));
6✔
1497
        //body
1498
        state.lineage.unshift(this);
6✔
1499
        result.push(...this.body.transpile(state));
6✔
1500
        state.lineage.shift();
6✔
1501

1502
        //end for
1503
        result.push(...state.transpileEndBlockToken(this.body, this.tokens.endFor, 'end for'));
6✔
1504

1505
        return result;
6✔
1506
    }
1507

1508
    walk(visitor: WalkVisitor, options: WalkOptions) {
1509
        if (options.walkMode & InternalWalkMode.walkExpressions) {
396✔
1510
            walk(this, 'target', visitor, options);
389✔
1511
        }
1512
        if (options.walkMode & InternalWalkMode.walkStatements) {
396✔
1513
            walk(this, 'body', visitor, options);
395✔
1514
        }
1515
    }
1516

1517
    public getType(options: GetTypeOptions): BscType {
1518
        // Used for hovers on the statement 'item' token
1519
        return this.getLoopVariableType(options);
64✔
1520
    }
1521

1522
    getLoopVariableType(options: GetTypeOptions): BscType {
1523
        if (this.typeExpression) {
168✔
1524
            return this.typeExpression.getType(options);
22✔
1525
        }
1526
        //register the for loop variable
1527
        const loopTargetType = this.target.getType({ flags: SymbolTypeFlag.runtime });
146✔
1528
        const loopVarType = new ArrayDefaultTypeReferenceType(loopTargetType);
146✔
1529

1530
        return loopVarType;
146✔
1531
    }
1532

1533
    get leadingTrivia(): Token[] {
1534
        return this.tokens.forEach?.leadingTrivia ?? [];
472!
1535
    }
1536

1537
    public get endTrivia(): Token[] {
1538
        return this.tokens.endFor?.leadingTrivia ?? [];
1!
1539
    }
1540

1541
    public clone() {
1542
        return this.finalizeClone(
2✔
1543
            new ForEachStatement({
1544
                forEach: util.cloneToken(this.tokens.forEach),
1545
                in: util.cloneToken(this.tokens.in),
1546
                as: util.cloneToken(this.tokens.as),
1547
                typeExpression: this.typeExpression?.clone(),
6!
1548
                endFor: util.cloneToken(this.tokens.endFor),
1549
                item: util.cloneToken(this.tokens.item),
1550
                target: this.target?.clone(),
6✔
1551
                body: this.body?.clone()
6✔
1552
            }),
1553
            ['target', 'body']
1554
        );
1555
    }
1556
}
1557

1558
export class WhileStatement extends Statement {
1✔
1559
    constructor(options: {
1560
        while?: Token;
1561
        endWhile?: Token;
1562
        condition: Expression;
1563
        body: Block;
1564
    }) {
1565
        super();
38✔
1566
        this.tokens = {
38✔
1567
            while: options.while,
1568
            endWhile: options.endWhile
1569
        };
1570
        this.body = options.body;
38✔
1571
        this.condition = options.condition;
38✔
1572
        this.location = util.createBoundingLocation(
38✔
1573
            this.tokens.while,
1574
            this.condition,
1575
            this.body,
1576
            this.tokens.endWhile
1577
        );
1578
    }
1579

1580
    public readonly tokens: {
1581
        readonly while?: Token;
1582
        readonly endWhile?: Token;
1583
    };
1584
    public readonly condition: Expression;
1585
    public readonly body: Block;
1586

1587
    public readonly kind = AstNodeKind.WhileStatement;
38✔
1588

1589
    public readonly location: Location | undefined;
1590

1591
    transpile(state: BrsTranspileState) {
1592
        let result = [] as TranspileResult;
8✔
1593
        //while
1594
        result.push(
8✔
1595
            state.transpileToken(this.tokens.while, 'while'),
1596
            ' '
1597
        );
1598
        //condition
1599
        result.push(
8✔
1600
            ...this.condition.transpile(state)
1601
        );
1602
        state.lineage.unshift(this);
8✔
1603
        //body
1604
        result.push(...this.body.transpile(state));
8✔
1605
        state.lineage.shift();
8✔
1606

1607
        //end while
1608
        result.push(...state.transpileEndBlockToken(this.body, this.tokens.endWhile, 'end while'));
8✔
1609

1610
        return result;
8✔
1611
    }
1612

1613
    walk(visitor: WalkVisitor, options: WalkOptions) {
1614
        if (options.walkMode & InternalWalkMode.walkExpressions) {
128✔
1615
            walk(this, 'condition', visitor, options);
125✔
1616
        }
1617
        if (options.walkMode & InternalWalkMode.walkStatements) {
128✔
1618
            walk(this, 'body', visitor, options);
127✔
1619
        }
1620
    }
1621

1622
    get leadingTrivia(): Token[] {
1623
        return this.tokens.while?.leadingTrivia ?? [];
113!
1624
    }
1625

1626
    public get endTrivia(): Token[] {
1627
        return this.tokens.endWhile?.leadingTrivia ?? [];
1!
1628
    }
1629

1630
    public clone() {
1631
        return this.finalizeClone(
4✔
1632
            new WhileStatement({
1633
                while: util.cloneToken(this.tokens.while),
1634
                endWhile: util.cloneToken(this.tokens.endWhile),
1635
                condition: this.condition?.clone(),
12✔
1636
                body: this.body?.clone()
12✔
1637
            }),
1638
            ['condition', 'body']
1639
        );
1640
    }
1641
}
1642

1643
export class DottedSetStatement extends Statement {
1✔
1644
    constructor(options: {
1645
        obj: Expression;
1646
        name: Identifier;
1647
        value: Expression;
1648
        dot?: Token;
1649
        equals?: Token;
1650
    }) {
1651
        super();
322✔
1652
        this.tokens = {
322✔
1653
            name: options.name,
1654
            dot: options.dot,
1655
            equals: options.equals
1656
        };
1657
        this.obj = options.obj;
322✔
1658
        this.value = options.value;
322✔
1659
        this.location = util.createBoundingLocation(
322✔
1660
            this.obj,
1661
            this.tokens.dot,
1662
            this.tokens.equals,
1663
            this.tokens.name,
1664
            this.value
1665
        );
1666
    }
1667
    public readonly tokens: {
1668
        readonly name: Identifier;
1669
        readonly equals?: Token;
1670
        readonly dot?: Token;
1671
    };
1672

1673
    public readonly obj: Expression;
1674
    public readonly value: Expression;
1675

1676
    public readonly kind = AstNodeKind.DottedSetStatement;
322✔
1677

1678
    public readonly location: Location | undefined;
1679

1680
    transpile(state: BrsTranspileState) {
1681
        //if the value is a compound assignment, don't add the obj, dot, name, or operator...the expression will handle that
1682
        return [
16✔
1683
            //object
1684
            ...this.obj.transpile(state),
1685
            this.tokens.dot ? state.tokenToSourceNode(this.tokens.dot) : '.',
16✔
1686
            //name
1687
            state.transpileToken(this.tokens.name),
1688
            ' ',
1689
            state.transpileToken(this.tokens.equals, '='),
1690
            ' ',
1691
            //right-hand-side of assignment
1692
            ...this.value.transpile(state)
1693
        ];
1694

1695
    }
1696

1697
    walk(visitor: WalkVisitor, options: WalkOptions) {
1698
        if (options.walkMode & InternalWalkMode.walkExpressions) {
891✔
1699
            walk(this, 'obj', visitor, options);
889✔
1700
            walk(this, 'value', visitor, options);
889✔
1701
        }
1702
    }
1703

1704
    getType(options: GetTypeOptions) {
1705
        const objType = this.obj?.getType(options);
107!
1706
        const result = objType?.getMemberType(this.tokens.name?.text, options);
107!
1707
        options.typeChain?.push(new TypeChainEntry({
107✔
1708
            name: this.tokens.name?.text,
318!
1709
            type: result, data: options.data,
1710
            location: this.tokens.name?.location,
318!
1711
            astNode: this
1712
        }));
1713
        return result;
107✔
1714
    }
1715

1716
    get leadingTrivia(): Token[] {
1717
        return this.obj.leadingTrivia;
932✔
1718
    }
1719

1720
    public clone() {
1721
        return this.finalizeClone(
2✔
1722
            new DottedSetStatement({
1723
                obj: this.obj?.clone(),
6✔
1724
                dot: util.cloneToken(this.tokens.dot),
1725
                name: util.cloneToken(this.tokens.name),
1726
                equals: util.cloneToken(this.tokens.equals),
1727
                value: this.value?.clone()
6✔
1728
            }),
1729
            ['obj', 'value']
1730
        );
1731
    }
1732
}
1733

1734
export class IndexedSetStatement extends Statement {
1✔
1735
    constructor(options: {
1736
        obj: Expression;
1737
        indexes: Expression[];
1738
        value: Expression;
1739
        openingSquare?: Token;
1740
        closingSquare?: Token;
1741
        equals?: Token;
1742
    }) {
1743
        super();
47✔
1744
        this.tokens = {
47✔
1745
            openingSquare: options.openingSquare,
1746
            closingSquare: options.closingSquare,
1747
            equals: options.equals
1748
        };
1749
        this.obj = options.obj;
47✔
1750
        this.indexes = options.indexes ?? [];
47✔
1751
        this.value = options.value;
47✔
1752
        this.location = util.createBoundingLocation(
47✔
1753
            this.obj,
1754
            this.tokens.openingSquare,
1755
            ...this.indexes,
1756
            this.tokens.closingSquare,
1757
            this.value
1758
        );
1759
    }
1760

1761
    public readonly tokens: {
1762
        readonly openingSquare?: Token;
1763
        readonly closingSquare?: Token;
1764
        readonly equals?: Token;
1765
    };
1766
    public readonly obj: Expression;
1767
    public readonly indexes: Expression[];
1768
    public readonly value: Expression;
1769

1770
    public readonly kind = AstNodeKind.IndexedSetStatement;
47✔
1771

1772
    public readonly location: Location | undefined;
1773

1774
    transpile(state: BrsTranspileState) {
1775
        const result = [];
17✔
1776
        result.push(
17✔
1777
            //obj
1778
            ...this.obj.transpile(state),
1779
            //   [
1780
            state.transpileToken(this.tokens.openingSquare, '[')
1781
        );
1782
        for (let i = 0; i < this.indexes.length; i++) {
17✔
1783
            //add comma between indexes
1784
            if (i > 0) {
18✔
1785
                result.push(', ');
1✔
1786
            }
1787
            let index = this.indexes[i];
18✔
1788
            result.push(
18✔
1789
                ...(index?.transpile(state) ?? [])
108!
1790
            );
1791
        }
1792
        result.push(
17✔
1793
            state.transpileToken(this.tokens.closingSquare, ']'),
1794
            ' ',
1795
            state.transpileToken(this.tokens.equals, '='),
1796
            ' ',
1797
            ...this.value.transpile(state)
1798
        );
1799
        return result;
17✔
1800

1801
    }
1802

1803
    walk(visitor: WalkVisitor, options: WalkOptions) {
1804
        if (options.walkMode & InternalWalkMode.walkExpressions) {
166✔
1805
            walk(this, 'obj', visitor, options);
165✔
1806
            walkArray(this.indexes, visitor, options, this);
165✔
1807
            walk(this, 'value', visitor, options);
165✔
1808
        }
1809
    }
1810

1811
    get leadingTrivia(): Token[] {
1812
        return this.obj.leadingTrivia;
175✔
1813
    }
1814

1815
    public clone() {
1816
        return this.finalizeClone(
6✔
1817
            new IndexedSetStatement({
1818
                obj: this.obj?.clone(),
18✔
1819
                openingSquare: util.cloneToken(this.tokens.openingSquare),
1820
                indexes: this.indexes?.map(x => x?.clone()),
7✔
1821
                closingSquare: util.cloneToken(this.tokens.closingSquare),
1822
                equals: util.cloneToken(this.tokens.equals),
1823
                value: this.value?.clone()
18✔
1824
            }),
1825
            ['obj', 'indexes', 'value']
1826
        );
1827
    }
1828
}
1829

1830
export class LibraryStatement extends Statement implements TypedefProvider {
1✔
1831
    constructor(options: {
1832
        library: Token;
1833
        filePath?: Token;
1834
    }) {
1835
        super();
16✔
1836
        this.tokens = {
16✔
1837
            library: options?.library,
48!
1838
            filePath: options?.filePath
48!
1839
        };
1840
        this.location = util.createBoundingLocation(
16✔
1841
            this.tokens.library,
1842
            this.tokens.filePath
1843
        );
1844
    }
1845
    public readonly tokens: {
1846
        readonly library: Token;
1847
        readonly filePath?: Token;
1848
    };
1849

1850
    public readonly kind = AstNodeKind.LibraryStatement;
16✔
1851

1852
    public readonly location: Location | undefined;
1853

1854
    transpile(state: BrsTranspileState) {
1855
        let result = [] as TranspileResult;
2✔
1856
        result.push(
2✔
1857
            state.transpileToken(this.tokens.library)
1858
        );
1859
        //there will be a parse error if file path is missing, but let's prevent a runtime error just in case
1860
        if (this.tokens.filePath) {
2!
1861
            result.push(
2✔
1862
                ' ',
1863
                state.transpileToken(this.tokens.filePath)
1864
            );
1865
        }
1866
        return result;
2✔
1867
    }
1868

1869
    getTypedef(state: BrsTranspileState) {
UNCOV
1870
        return this.transpile(state);
×
1871
    }
1872

1873
    walk(visitor: WalkVisitor, options: WalkOptions) {
1874
        //nothing to walk
1875
    }
1876

1877
    get leadingTrivia(): Token[] {
1878
        return this.tokens.library?.leadingTrivia ?? [];
26!
1879
    }
1880

1881
    public clone() {
1882
        return this.finalizeClone(
1✔
1883
            new LibraryStatement({
1884
                library: util.cloneToken(this.tokens?.library),
3!
1885
                filePath: util.cloneToken(this.tokens?.filePath)
3!
1886
            })
1887
        );
1888
    }
1889
}
1890

1891
export class NamespaceStatement extends Statement implements TypedefProvider {
1✔
1892
    constructor(options: {
1893
        namespace?: Token;
1894
        nameExpression: VariableExpression | DottedGetExpression;
1895
        body: Body;
1896
        endNamespace?: Token;
1897
    }) {
1898
        super();
762✔
1899
        this.tokens = {
762✔
1900
            namespace: options.namespace,
1901
            endNamespace: options.endNamespace
1902
        };
1903
        this.nameExpression = options.nameExpression;
762✔
1904
        this.body = options.body;
762✔
1905
        this.symbolTable = new SymbolTable(`NamespaceStatement: '${this.name}'`, () => this.getRoot()?.getSymbolTable());
8,577!
1906
    }
1907

1908
    public readonly tokens: {
1909
        readonly namespace?: Token;
1910
        readonly endNamespace?: Token;
1911
    };
1912

1913
    public readonly nameExpression: VariableExpression | DottedGetExpression;
1914
    public readonly body: Body;
1915

1916
    public readonly kind = AstNodeKind.NamespaceStatement;
762✔
1917

1918
    /**
1919
     * The string name for this namespace
1920
     */
1921
    public get name(): string {
1922
        return this.getName(ParseMode.BrighterScript);
2,798✔
1923
    }
1924

1925
    public get location() {
1926
        return this.cacheLocation();
579✔
1927
    }
1928
    private _location: Location | undefined;
1929

1930
    public cacheLocation() {
1931
        if (!this._location) {
1,339✔
1932
            this._location = util.createBoundingLocation(
762✔
1933
                this.tokens.namespace,
1934
                this.nameExpression,
1935
                this.body,
1936
                this.tokens.endNamespace
1937
            );
1938
        }
1939
        return this._location;
1,339✔
1940
    }
1941

1942
    public getName(parseMode: ParseMode) {
1943
        const sep = parseMode === ParseMode.BrighterScript ? '.' : '_';
13,595✔
1944
        let name = util.getAllDottedGetPartsAsString(this.nameExpression, parseMode);
13,595✔
1945
        if ((this.parent as Body)?.parent?.kind === AstNodeKind.NamespaceStatement) {
13,595✔
1946
            name = (this.parent.parent as NamespaceStatement).getName(parseMode) + sep + name;
544✔
1947
        }
1948
        return name;
13,595✔
1949
    }
1950

1951
    public get leadingTrivia(): Token[] {
1952
        return this.tokens.namespace?.leadingTrivia;
2,093!
1953
    }
1954

1955
    public get endTrivia(): Token[] {
UNCOV
1956
        return this.tokens.endNamespace?.leadingTrivia;
×
1957
    }
1958

1959
    public getNameParts() {
1960
        let parts = util.getAllDottedGetParts(this.nameExpression);
2,235✔
1961

1962
        if ((this.parent as Body)?.parent?.kind === AstNodeKind.NamespaceStatement) {
2,235!
1963
            parts = (this.parent.parent as NamespaceStatement).getNameParts().concat(parts);
110✔
1964
        }
1965
        return parts;
2,235✔
1966
    }
1967

1968
    transpile(state: BrsTranspileState) {
1969
        //namespaces don't actually have any real content, so just transpile their bodies
1970
        return [
83✔
1971
            state.transpileAnnotations(this),
1972
            state.transpileLeadingComments(this.tokens.namespace),
1973
            this.body.transpile(state),
1974
            state.transpileLeadingComments(this.tokens.endNamespace)
1975
        ];
1976
    }
1977

1978
    getTypedef(state: BrsTranspileState) {
1979
        let result: TranspileResult = [];
10✔
1980
        for (let comment of util.getLeadingComments(this) ?? []) {
10!
UNCOV
1981
            result.push(
×
1982
                comment.text,
1983
                state.newline,
1984
                state.indent()
1985
            );
1986
        }
1987

1988
        result.push('namespace ',
10✔
1989
            ...this.getName(ParseMode.BrighterScript),
1990
            state.newline
1991
        );
1992
        state.blockDepth++;
10✔
1993
        result.push(
10✔
1994
            ...this.body.getTypedef(state)
1995
        );
1996
        state.blockDepth--;
10✔
1997

1998
        result.push(
10✔
1999
            state.indent(),
2000
            'end namespace'
2001
        );
2002
        return result;
10✔
2003
    }
2004

2005
    walk(visitor: WalkVisitor, options: WalkOptions) {
2006
        if (options.walkMode & InternalWalkMode.walkExpressions) {
4,423✔
2007
            walk(this, 'nameExpression', visitor, options);
3,083✔
2008
        }
2009

2010
        if (this.body.statements.length > 0 && options.walkMode & InternalWalkMode.walkStatements) {
4,423✔
2011
            walk(this, 'body', visitor, options);
4,192✔
2012
        }
2013
    }
2014

2015
    getType(options: GetTypeOptions) {
2016
        const resultType = new NamespaceType(this.name);
1,265✔
2017
        return resultType;
1,265✔
2018
    }
2019

2020
    public clone() {
2021
        const clone = this.finalizeClone(
3✔
2022
            new NamespaceStatement({
2023
                namespace: util.cloneToken(this.tokens.namespace),
2024
                nameExpression: this.nameExpression?.clone(),
9✔
2025
                body: this.body?.clone(),
9✔
2026
                endNamespace: util.cloneToken(this.tokens.endNamespace)
2027
            }),
2028
            ['nameExpression', 'body']
2029
        );
2030
        clone.cacheLocation();
3✔
2031
        return clone;
3✔
2032
    }
2033
}
2034

2035
export class ImportStatement extends Statement implements TypedefProvider {
1✔
2036
    constructor(options: {
2037
        import?: Token;
2038
        path?: Token;
2039
    }) {
2040
        super();
235✔
2041
        this.tokens = {
235✔
2042
            import: options.import,
2043
            path: options.path
2044
        };
2045
        this.location = util.createBoundingLocation(
235✔
2046
            this.tokens.import,
2047
            this.tokens.path
2048
        );
2049
        if (this.tokens.path) {
235✔
2050
            //remove quotes
2051
            this.filePath = this.tokens.path.text.replace(/"/g, '');
233✔
2052
            if (this.tokens.path?.location?.range) {
233!
2053
                //adjust the range to exclude the quotes
2054
                this.tokens.path.location = util.createLocation(
229✔
2055
                    this.tokens.path.location.range.start.line,
2056
                    this.tokens.path.location.range.start.character + 1,
2057
                    this.tokens.path.location.range.end.line,
2058
                    this.tokens.path.location.range.end.character - 1,
2059
                    this.tokens.path.location.uri
2060
                );
2061
            }
2062
        }
2063
    }
2064

2065
    public readonly tokens: {
2066
        readonly import?: Token;
2067
        readonly path: Token;
2068
    };
2069

2070
    public readonly kind = AstNodeKind.ImportStatement;
235✔
2071

2072
    public readonly location: Location;
2073

2074
    public readonly filePath: string;
2075

2076
    transpile(state: BrsTranspileState) {
2077
        //The xml files are responsible for adding the additional script imports, but
2078
        //add the import statement as a comment just for debugging purposes
2079
        return [
13✔
2080
            state.transpileToken(this.tokens.import, 'import', true),
2081
            ' ',
2082
            state.transpileToken(this.tokens.path)
2083
        ];
2084
    }
2085

2086
    /**
2087
     * Get the typedef for this statement
2088
     */
2089
    public getTypedef(state: BrsTranspileState) {
2090
        return [
3✔
2091
            this.tokens.import?.text ?? 'import',
18!
2092
            ' ',
2093
            //replace any `.bs` extension with `.brs`
2094
            this.tokens.path.text.replace(/\.bs"?$/i, '.brs"')
2095
        ];
2096
    }
2097

2098
    walk(visitor: WalkVisitor, options: WalkOptions) {
2099
        //nothing to walk
2100
    }
2101

2102
    get leadingTrivia(): Token[] {
2103
        return this.tokens.import?.leadingTrivia ?? [];
635!
2104
    }
2105

2106
    public clone() {
2107
        return this.finalizeClone(
1✔
2108
            new ImportStatement({
2109
                import: util.cloneToken(this.tokens.import),
2110
                path: util.cloneToken(this.tokens.path)
2111
            })
2112
        );
2113
    }
2114
}
2115

2116
export class InterfaceStatement extends Statement implements TypedefProvider {
1✔
2117
    constructor(options: {
2118
        interface: Token;
2119
        name: Identifier;
2120
        extends?: Token;
2121
        parentInterfaceName?: TypeExpression;
2122
        body: Statement[];
2123
        endInterface?: Token;
2124
    }) {
2125
        super();
255✔
2126
        this.tokens = {
255✔
2127
            interface: options.interface,
2128
            name: options.name,
2129
            extends: options.extends,
2130
            endInterface: options.endInterface
2131
        };
2132
        this.parentInterfaceName = options.parentInterfaceName;
255✔
2133
        this.body = options.body;
255✔
2134
        this.location = util.createBoundingLocation(
255✔
2135
            this.tokens.interface,
2136
            this.tokens.name,
2137
            this.tokens.extends,
2138
            this.parentInterfaceName,
2139
            ...this.body ?? [],
765✔
2140
            this.tokens.endInterface
2141
        );
2142
    }
2143
    public readonly parentInterfaceName?: TypeExpression;
2144
    public readonly body: Statement[];
2145

2146
    public readonly kind = AstNodeKind.InterfaceStatement;
255✔
2147

2148
    public readonly tokens = {} as {
255✔
2149
        readonly interface?: Token;
2150
        readonly name: Identifier;
2151
        readonly extends?: Token;
2152
        readonly endInterface?: Token;
2153
    };
2154

2155
    public readonly location: Location | undefined;
2156

2157
    public get fields(): InterfaceFieldStatement[] {
2158
        return this.body.filter(x => isInterfaceFieldStatement(x)) as InterfaceFieldStatement[];
318✔
2159
    }
2160

2161
    public get methods(): InterfaceMethodStatement[] {
2162
        return this.body.filter(x => isInterfaceMethodStatement(x)) as InterfaceMethodStatement[];
313✔
2163
    }
2164

2165

2166
    public hasParentInterface() {
UNCOV
2167
        return !!this.parentInterfaceName;
×
2168
    }
2169

2170
    public get leadingTrivia(): Token[] {
2171
        return this.tokens.interface?.leadingTrivia;
673!
2172
    }
2173

2174
    public get endTrivia(): Token[] {
UNCOV
2175
        return this.tokens.endInterface?.leadingTrivia;
×
2176
    }
2177

2178

2179
    /**
2180
     * The name of the interface WITH its leading namespace (if applicable)
2181
     */
2182
    public get fullName() {
UNCOV
2183
        const name = this.tokens.name?.text;
×
UNCOV
2184
        if (name) {
×
UNCOV
2185
            const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
×
UNCOV
2186
            if (namespace) {
×
UNCOV
2187
                let namespaceName = namespace.getName(ParseMode.BrighterScript);
×
UNCOV
2188
                return `${namespaceName}.${name}`;
×
2189
            } else {
UNCOV
2190
                return name;
×
2191
            }
2192
        } else {
2193
            //return undefined which will allow outside callers to know that this interface doesn't have a name
UNCOV
2194
            return undefined;
×
2195
        }
2196
    }
2197

2198
    /**
2199
     * The name of the interface (without the namespace prefix)
2200
     */
2201
    public get name() {
2202
        return this.tokens.name?.text;
225!
2203
    }
2204

2205
    /**
2206
     * Get the name of this expression based on the parse mode
2207
     */
2208
    public getName(parseMode: ParseMode) {
2209
        const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
225✔
2210
        if (namespace) {
225✔
2211
            let delimiter = parseMode === ParseMode.BrighterScript ? '.' : '_';
35!
2212
            let namespaceName = namespace.getName(parseMode);
35✔
2213
            return namespaceName + delimiter + this.name;
35✔
2214
        } else {
2215
            return this.name;
190✔
2216
        }
2217
    }
2218

2219
    public transpile(state: BrsTranspileState): TranspileResult {
2220
        //interfaces should completely disappear at runtime
2221
        return [
28✔
2222
            state.transpileLeadingComments(this.tokens.interface)
2223
        ];
2224
    }
2225

2226
    getTypedef(state: BrsTranspileState) {
2227
        const result = [] as TranspileResult;
7✔
2228
        for (let comment of util.getLeadingComments(this) ?? []) {
7!
UNCOV
2229
            result.push(
×
2230
                comment.text,
2231
                state.newline,
2232
                state.indent()
2233
            );
2234
        }
2235
        for (let annotation of this.annotations ?? []) {
7✔
2236
            result.push(
1✔
2237
                ...annotation.getTypedef(state),
2238
                state.newline,
2239
                state.indent()
2240
            );
2241
        }
2242
        result.push(
7✔
2243
            this.tokens.interface.text,
2244
            ' ',
2245
            this.tokens.name.text
2246
        );
2247
        const parentInterfaceName = this.parentInterfaceName?.getName();
7!
2248
        if (parentInterfaceName) {
7!
UNCOV
2249
            result.push(
×
2250
                ' extends ',
2251
                parentInterfaceName
2252
            );
2253
        }
2254
        const body = this.body ?? [];
7!
2255
        if (body.length > 0) {
7!
2256
            state.blockDepth++;
7✔
2257
        }
2258
        for (const statement of body) {
7✔
2259
            if (isInterfaceMethodStatement(statement) || isInterfaceFieldStatement(statement)) {
22!
2260
                result.push(
22✔
2261
                    state.newline,
2262
                    state.indent(),
2263
                    ...statement.getTypedef(state)
2264
                );
2265
            } else {
UNCOV
2266
                result.push(
×
2267
                    state.newline,
2268
                    state.indent(),
2269
                    ...statement.transpile(state)
2270
                );
2271
            }
2272
        }
2273
        if (body.length > 0) {
7!
2274
            state.blockDepth--;
7✔
2275
        }
2276
        result.push(
7✔
2277
            state.newline,
2278
            state.indent(),
2279
            'end interface',
2280
            state.newline
2281
        );
2282
        return result;
7✔
2283
    }
2284

2285
    walk(visitor: WalkVisitor, options: WalkOptions) {
2286
        //visitor-less walk function to do parent linking
2287
        walk(this, 'parentInterfaceName', null, options);
1,200✔
2288

2289
        if (options.walkMode & InternalWalkMode.walkStatements) {
1,200!
2290
            walkArray(this.body, visitor, options, this);
1,200✔
2291
        }
2292
    }
2293

2294
    getType(options: GetTypeOptions) {
2295
        const superIface = this.parentInterfaceName?.getType(options) as InterfaceType;
216✔
2296

2297
        const resultType = new InterfaceType(this.getName(ParseMode.BrighterScript), superIface);
216✔
2298
        for (const statement of this.methods) {
216✔
2299
            const memberType = statement?.getType({ ...options, typeChain: undefined }); // no typechain info needed
38!
2300
            const flag = statement.isOptional ? SymbolTypeFlag.runtime | SymbolTypeFlag.optional : SymbolTypeFlag.runtime;
38✔
2301
            resultType.addMember(statement?.tokens.name?.text, { definingNode: statement }, memberType, flag);
38!
2302
        }
2303
        for (const statement of this.fields) {
216✔
2304
            const memberType = statement?.getType({ ...options, typeChain: undefined }); // no typechain info needed
270!
2305
            const flag = statement.isOptional ? SymbolTypeFlag.runtime | SymbolTypeFlag.optional : SymbolTypeFlag.runtime;
270✔
2306
            resultType.addMember(statement?.tokens.name?.text, { definingNode: statement, isInstance: true }, memberType, flag);
270!
2307
        }
2308
        options.typeChain?.push(new TypeChainEntry({
216✔
2309
            name: this.getName(ParseMode.BrighterScript),
2310
            type: resultType,
2311
            data: options.data,
2312
            astNode: this
2313
        }));
2314
        return resultType;
216✔
2315
    }
2316

2317
    public clone() {
2318
        return this.finalizeClone(
8✔
2319
            new InterfaceStatement({
2320
                interface: util.cloneToken(this.tokens.interface),
2321
                name: util.cloneToken(this.tokens.name),
2322
                extends: util.cloneToken(this.tokens.extends),
2323
                parentInterfaceName: this.parentInterfaceName?.clone(),
24✔
2324
                body: this.body?.map(x => x?.clone()),
9✔
2325
                endInterface: util.cloneToken(this.tokens.endInterface)
2326
            }),
2327
            ['parentInterfaceName', 'body']
2328
        );
2329
    }
2330
}
2331

2332
export class InterfaceFieldStatement extends Statement implements TypedefProvider {
1✔
2333
    public transpile(state: BrsTranspileState): TranspileResult {
UNCOV
2334
        throw new Error('Method not implemented.');
×
2335
    }
2336
    constructor(options: {
2337
        name: Identifier;
2338
        as?: Token;
2339
        typeExpression?: TypeExpression;
2340
        optional?: Token;
2341
    }) {
2342
        super();
297✔
2343
        this.tokens = {
297✔
2344
            optional: options.optional,
2345
            name: options.name,
2346
            as: options.as
2347
        };
2348
        this.typeExpression = options.typeExpression;
297✔
2349
        this.location = util.createBoundingLocation(
297✔
2350
            this.tokens.optional,
2351
            this.tokens.name,
2352
            this.tokens.as,
2353
            this.typeExpression
2354
        );
2355
    }
2356

2357
    public readonly kind = AstNodeKind.InterfaceFieldStatement;
297✔
2358

2359
    public readonly typeExpression?: TypeExpression;
2360

2361
    public readonly location: Location | undefined;
2362

2363
    public readonly tokens: {
2364
        readonly name: Identifier;
2365
        readonly as: Token;
2366
        readonly optional?: Token;
2367
    };
2368

2369
    public get leadingTrivia(): Token[] {
2370
        return this.tokens.optional?.leadingTrivia ?? this.tokens.name.leadingTrivia;
803✔
2371
    }
2372

2373
    public get name() {
UNCOV
2374
        return this.tokens.name.text;
×
2375
    }
2376

2377
    public get isOptional() {
2378
        return !!this.tokens.optional;
290✔
2379
    }
2380

2381
    walk(visitor: WalkVisitor, options: WalkOptions) {
2382
        if (options.walkMode & InternalWalkMode.walkExpressions) {
2,018✔
2383
            walk(this, 'typeExpression', visitor, options);
1,701✔
2384
        }
2385
    }
2386

2387
    getTypedef(state: BrsTranspileState): TranspileResult {
2388
        const result = [] as TranspileResult;
12✔
2389
        for (let comment of util.getLeadingComments(this) ?? []) {
12!
UNCOV
2390
            result.push(
×
2391
                comment.text,
2392
                state.newline,
2393
                state.indent()
2394
            );
2395
        }
2396
        for (let annotation of this.annotations ?? []) {
12✔
2397
            result.push(
1✔
2398
                ...annotation.getTypedef(state),
2399
                state.newline,
2400
                state.indent()
2401
            );
2402
        }
2403
        if (this.isOptional) {
12✔
2404
            result.push(
1✔
2405
                this.tokens.optional!.text,
2406
                ' '
2407
            );
2408
        }
2409
        result.push(
12✔
2410
            this.tokens.name.text
2411
        );
2412

2413
        if (this.typeExpression) {
12!
2414
            result.push(
12✔
2415
                ' as ',
2416
                ...this.typeExpression.getTypedef(state)
2417
            );
2418
        }
2419
        return result;
12✔
2420
    }
2421

2422
    public getType(options: GetTypeOptions): BscType {
2423
        return this.typeExpression?.getType(options) ?? DynamicType.instance;
281✔
2424
    }
2425

2426
    public clone() {
2427
        return this.finalizeClone(
4✔
2428
            new InterfaceFieldStatement({
2429
                name: util.cloneToken(this.tokens.name),
2430
                as: util.cloneToken(this.tokens.as),
2431
                typeExpression: this.typeExpression?.clone(),
12✔
2432
                optional: util.cloneToken(this.tokens.optional)
2433
            })
2434
        );
2435
    }
2436

2437
}
2438

2439
//TODO: there is much that is similar with this and FunctionExpression.
2440
//It would be nice to refactor this so there is less duplicated code
2441
export class InterfaceMethodStatement extends Statement implements TypedefProvider {
1✔
2442
    public transpile(state: BrsTranspileState): TranspileResult {
UNCOV
2443
        throw new Error('Method not implemented.');
×
2444
    }
2445
    constructor(options: {
2446
        functionType?: Token;
2447
        name: Identifier;
2448
        leftParen?: Token;
2449
        params?: FunctionParameterExpression[];
2450
        rightParen?: Token;
2451
        as?: Token;
2452
        returnTypeExpression?: TypeExpression;
2453
        optional?: Token;
2454
    }) {
2455
        super();
57✔
2456
        this.tokens = {
57✔
2457
            optional: options.optional,
2458
            functionType: options.functionType,
2459
            name: options.name,
2460
            leftParen: options.leftParen,
2461
            rightParen: options.rightParen,
2462
            as: options.as
2463
        };
2464
        this.params = options.params ?? [];
57✔
2465
        this.returnTypeExpression = options.returnTypeExpression;
57✔
2466
    }
2467

2468
    public readonly kind = AstNodeKind.InterfaceMethodStatement;
57✔
2469

2470
    public get location() {
2471
        return util.createBoundingLocation(
88✔
2472
            this.tokens.optional,
2473
            this.tokens.functionType,
2474
            this.tokens.name,
2475
            this.tokens.leftParen,
2476
            ...(this.params ?? []),
264!
2477
            this.tokens.rightParen,
2478
            this.tokens.as,
2479
            this.returnTypeExpression
2480
        );
2481
    }
2482
    /**
2483
     * Get the name of this method.
2484
     */
2485
    public getName(parseMode: ParseMode) {
2486
        return this.tokens.name.text;
38✔
2487
    }
2488

2489
    public readonly tokens: {
2490
        readonly optional?: Token;
2491
        readonly functionType: Token;
2492
        readonly name: Identifier;
2493
        readonly leftParen?: Token;
2494
        readonly rightParen?: Token;
2495
        readonly as?: Token;
2496
    };
2497

2498
    public readonly params: FunctionParameterExpression[];
2499
    public readonly returnTypeExpression?: TypeExpression;
2500

2501
    public get isOptional() {
2502
        return !!this.tokens.optional;
51✔
2503
    }
2504

2505
    public get leadingTrivia(): Token[] {
2506
        return this.tokens.optional?.leadingTrivia ?? this.tokens.functionType.leadingTrivia;
118✔
2507
    }
2508

2509
    walk(visitor: WalkVisitor, options: WalkOptions) {
2510
        if (options.walkMode & InternalWalkMode.walkExpressions) {
287✔
2511
            walk(this, 'returnTypeExpression', visitor, options);
251✔
2512
        }
2513
    }
2514

2515
    getTypedef(state: BrsTranspileState) {
2516
        const result = [] as TranspileResult;
10✔
2517
        for (let comment of util.getLeadingComments(this) ?? []) {
10!
2518
            result.push(
1✔
2519
                comment.text,
2520
                state.newline,
2521
                state.indent()
2522
            );
2523
        }
2524
        for (let annotation of this.annotations ?? []) {
10✔
2525
            result.push(
1✔
2526
                ...annotation.getTypedef(state),
2527
                state.newline,
2528
                state.indent()
2529
            );
2530
        }
2531
        if (this.isOptional) {
10!
UNCOV
2532
            result.push(
×
2533
                this.tokens.optional!.text,
2534
                ' '
2535
            );
2536
        }
2537
        result.push(
10✔
2538
            this.tokens.functionType?.text ?? 'function',
60!
2539
            ' ',
2540
            this.tokens.name.text,
2541
            '('
2542
        );
2543
        const params = this.params ?? [];
10!
2544
        for (let i = 0; i < params.length; i++) {
10✔
2545
            if (i > 0) {
2✔
2546
                result.push(', ');
1✔
2547
            }
2548
            const param = params[i];
2✔
2549
            result.push(param.tokens.name.text);
2✔
2550
            if (param.typeExpression) {
2!
2551
                result.push(
2✔
2552
                    ' as ',
2553
                    ...param.typeExpression.getTypedef(state)
2554
                );
2555
            }
2556
        }
2557
        result.push(
10✔
2558
            ')'
2559
        );
2560
        if (this.returnTypeExpression) {
10!
2561
            result.push(
10✔
2562
                ' as ',
2563
                ...this.returnTypeExpression.getTypedef(state)
2564
            );
2565
        }
2566
        return result;
10✔
2567
    }
2568

2569
    public getType(options: GetTypeOptions): TypedFunctionType {
2570
        //if there's a defined return type, use that
2571
        let returnType = this.returnTypeExpression?.getType(options);
38✔
2572
        const isSub = this.tokens.functionType?.kind === TokenKind.Sub || !returnType;
38!
2573
        //if we don't have a return type and this is a sub, set the return type to `void`. else use `dynamic`
2574
        if (!returnType) {
38✔
2575
            returnType = isSub ? VoidType.instance : DynamicType.instance;
10!
2576
        }
2577
        const resultType = new TypedFunctionType(returnType);
38✔
2578
        resultType.isSub = isSub;
38✔
2579
        for (let param of this.params) {
38✔
2580
            resultType.addParameter(param.tokens.name.text, param.getType(options), !!param.defaultValue);
23✔
2581
        }
2582
        if (options.typeChain) {
38!
2583
            // need Interface type for type chain
UNCOV
2584
            this.parent?.getType(options);
×
2585
        }
2586
        let funcName = this.getName(ParseMode.BrighterScript);
38✔
2587
        resultType.setName(funcName);
38✔
2588
        options.typeChain?.push(new TypeChainEntry({ name: resultType.name, type: resultType, data: options.data, astNode: this }));
38!
2589
        return resultType;
38✔
2590
    }
2591

2592
    public clone() {
2593
        return this.finalizeClone(
4✔
2594
            new InterfaceMethodStatement({
2595
                optional: util.cloneToken(this.tokens.optional),
2596
                functionType: util.cloneToken(this.tokens.functionType),
2597
                name: util.cloneToken(this.tokens.name),
2598
                leftParen: util.cloneToken(this.tokens.leftParen),
2599
                params: this.params?.map(p => p?.clone()),
3✔
2600
                rightParen: util.cloneToken(this.tokens.rightParen),
2601
                as: util.cloneToken(this.tokens.as),
2602
                returnTypeExpression: this.returnTypeExpression?.clone()
12✔
2603
            }),
2604
            ['params']
2605
        );
2606
    }
2607
}
2608

2609
export class ClassStatement extends Statement implements TypedefProvider {
1✔
2610
    constructor(options: {
2611
        class?: Token;
2612
        /**
2613
         * The name of the class (without namespace prefix)
2614
         */
2615
        name: Identifier;
2616
        body: Statement[];
2617
        endClass?: Token;
2618
        extends?: Token;
2619
        parentClassName?: TypeExpression;
2620
    }) {
2621
        super();
758✔
2622
        this.body = options.body ?? [];
758✔
2623
        this.tokens = {
758✔
2624
            name: options.name,
2625
            class: options.class,
2626
            endClass: options.endClass,
2627
            extends: options.extends
2628
        };
2629
        this.parentClassName = options.parentClassName;
758✔
2630
        this.symbolTable = new SymbolTable(`ClassStatement: '${this.tokens.name?.text}'`, () => this.parent?.getSymbolTable());
2,694!
2631

2632
        for (let statement of this.body) {
758✔
2633
            if (isMethodStatement(statement)) {
757✔
2634
                this.methods.push(statement);
390✔
2635
                this.memberMap[statement?.tokens.name?.text.toLowerCase()] = statement;
390!
2636
            } else if (isFieldStatement(statement)) {
367✔
2637
                this.fields.push(statement);
366✔
2638
                this.memberMap[statement?.tokens.name?.text.toLowerCase()] = statement;
366!
2639
            }
2640
        }
2641

2642
        this.location = util.createBoundingLocation(
758✔
2643
            this.parentClassName,
2644
            ...(this.body ?? []),
2,274!
2645
            util.createBoundingLocationFromTokens(this.tokens)
2646
        );
2647
    }
2648

2649
    public readonly kind = AstNodeKind.ClassStatement;
758✔
2650

2651

2652
    public readonly tokens: {
2653
        readonly class?: Token;
2654
        /**
2655
         * The name of the class (without namespace prefix)
2656
         */
2657
        readonly name: Identifier;
2658
        readonly endClass?: Token;
2659
        readonly extends?: Token;
2660
    };
2661
    public readonly body: Statement[];
2662
    public readonly parentClassName: TypeExpression;
2663

2664

2665
    public getName(parseMode: ParseMode) {
2666
        const name = this.tokens.name?.text;
2,539✔
2667
        if (name) {
2,539✔
2668
            const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
2,538✔
2669
            if (namespace) {
2,538✔
2670
                let namespaceName = namespace.getName(parseMode);
945✔
2671
                let separator = parseMode === ParseMode.BrighterScript ? '.' : '_';
945✔
2672
                return namespaceName + separator + name;
945✔
2673
            } else {
2674
                return name;
1,593✔
2675
            }
2676
        } else {
2677
            //return undefined which will allow outside callers to know that this class doesn't have a name
2678
            return undefined;
1✔
2679
        }
2680
    }
2681

2682
    public get leadingTrivia(): Token[] {
2683
        return this.tokens.class?.leadingTrivia;
1,470!
2684
    }
2685

2686
    public get endTrivia(): Token[] {
UNCOV
2687
        return this.tokens.endClass?.leadingTrivia ?? [];
×
2688
    }
2689

2690
    public readonly memberMap = {} as Record<string, MemberStatement>;
758✔
2691
    public readonly methods = [] as MethodStatement[];
758✔
2692
    public readonly fields = [] as FieldStatement[];
758✔
2693

2694
    public readonly location: Location | undefined;
2695

2696
    transpile(state: BrsTranspileState) {
2697
        let result = [] as TranspileResult;
59✔
2698

2699
        const className = this.getName(ParseMode.BrightScript).replace(/\./g, '_');
59✔
2700
        const ancestors = this.getAncestors(state);
59✔
2701
        const body = this.getTranspiledClassBody(ancestors);
59✔
2702

2703
        //make the methods
2704
        result.push(...this.getTranspiledMethods(state, className, body));
59✔
2705
        //make the builder
2706
        result.push(...this.getTranspiledBuilder(state, className, ancestors, body));
59✔
2707
        result.push('\n', state.indent());
59✔
2708
        //make the class assembler (i.e. the public-facing class creator method)
2709
        result.push(...this.getTranspiledClassFunction(state, className));
59✔
2710

2711
        return result;
59✔
2712
    }
2713

2714
    getTypedef(state: BrsTranspileState) {
2715
        const result = [] as TranspileResult;
15✔
2716
        for (let comment of util.getLeadingComments(this) ?? []) {
15!
UNCOV
2717
            result.push(
×
2718
                comment.text,
2719
                state.newline,
2720
                state.indent()
2721
            );
2722
        }
2723
        for (let annotation of this.annotations ?? []) {
15!
UNCOV
2724
            result.push(
×
2725
                ...annotation.getTypedef(state),
2726
                state.newline,
2727
                state.indent()
2728
            );
2729
        }
2730
        result.push(
15✔
2731
            'class ',
2732
            this.tokens.name.text
2733
        );
2734
        if (this.parentClassName) {
15✔
2735
            const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
4✔
2736
            const fqName = util.getFullyQualifiedClassName(
4✔
2737
                this.parentClassName.getName(),
2738
                namespace?.getName(ParseMode.BrighterScript)
12✔
2739
            );
2740
            result.push(
4✔
2741
                ` extends ${fqName}`
2742
            );
2743
        }
2744
        result.push(state.newline);
15✔
2745
        state.blockDepth++;
15✔
2746

2747
        let body = this.body;
15✔
2748
        //inject an empty "new" method if missing
2749
        if (!this.getConstructorFunction()) {
15✔
2750
            const constructor = createMethodStatement('new', TokenKind.Sub);
11✔
2751
            constructor.parent = this;
11✔
2752
            //walk the constructor to set up parent links
2753
            constructor.link();
11✔
2754
            body = [
11✔
2755
                constructor,
2756
                ...this.body
2757
            ];
2758
        }
2759

2760
        for (const member of body) {
15✔
2761
            if (isTypedefProvider(member)) {
35!
2762
                result.push(
35✔
2763
                    state.indent(),
2764
                    ...member.getTypedef(state),
2765
                    state.newline
2766
                );
2767
            }
2768
        }
2769
        state.blockDepth--;
15✔
2770
        result.push(
15✔
2771
            state.indent(),
2772
            'end class'
2773
        );
2774
        return result;
15✔
2775
    }
2776

2777
    /**
2778
     * Find the parent index for this class's parent.
2779
     * For class inheritance, every class is given an index.
2780
     * The base class is index 0, its child is index 1, and so on.
2781
     */
2782
    public getParentClassIndex(state: BrsTranspileState) {
2783
        let myIndex = 0;
135✔
2784
        let stmt = this as ClassStatement;
135✔
2785
        while (stmt) {
135✔
2786
            if (stmt.parentClassName) {
208✔
2787
                const namespace = stmt.findAncestor<NamespaceStatement>(isNamespaceStatement);
74✔
2788
                //find the parent class
2789
                stmt = state.file.getClassFileLink(
74✔
2790
                    stmt.parentClassName.getName(),
2791
                    namespace?.getName(ParseMode.BrighterScript)
222✔
2792
                )?.item;
74✔
2793
                myIndex++;
74✔
2794
            } else {
2795
                break;
134✔
2796
            }
2797
        }
2798
        const result = myIndex - 1;
135✔
2799
        if (result >= 0) {
135✔
2800
            return result;
55✔
2801
        } else {
2802
            return null;
80✔
2803
        }
2804
    }
2805

2806
    public hasParentClass() {
2807
        return !!this.parentClassName;
316✔
2808
    }
2809

2810
    /**
2811
     * Get all ancestor classes, in closest-to-furthest order (i.e. 0 is parent, 1 is grandparent, etc...).
2812
     * This will return an empty array if no ancestors were found
2813
     */
2814
    public getAncestors(state: BrsTranspileState) {
2815
        let ancestors = [] as ClassStatement[];
153✔
2816
        let stmt = this as ClassStatement;
153✔
2817
        while (stmt) {
153✔
2818
            if (stmt.parentClassName) {
241✔
2819
                const namespace = stmt.findAncestor<NamespaceStatement>(isNamespaceStatement);
88✔
2820
                stmt = state.file.getClassFileLink(
88✔
2821
                    stmt.parentClassName.getName(),
2822
                    namespace?.getName(ParseMode.BrighterScript)
264✔
2823
                )?.item;
88!
2824
                ancestors.push(stmt);
88✔
2825
            } else {
2826
                break;
153✔
2827
            }
2828
        }
2829
        return ancestors;
153✔
2830
    }
2831

2832
    private getBuilderName(transpiledClassName: string) {
2833
        return `__${transpiledClassName}_builder`;
142✔
2834
    }
2835

2836
    private getMethodIdentifier(transpiledClassName: string, statement: MethodStatement) {
2837
        return { ...statement.tokens.name, text: `__${transpiledClassName}_method_${statement.tokens.name.text}` };
150✔
2838
    }
2839

2840
    public getConstructorType() {
2841
        const constructorType = this.getConstructorFunction()?.getType({ flags: SymbolTypeFlag.runtime }) ?? new TypedFunctionType(null);
140✔
2842
        constructorType.returnType = this.getType({ flags: SymbolTypeFlag.runtime });
140✔
2843
        return constructorType;
140✔
2844
    }
2845

2846
    /**
2847
     * Get the constructor function for this class (if exists), or undefined if not exist
2848
     */
2849
    private getConstructorFunction() {
2850
        return this.body.find((stmt) => {
313✔
2851
            return (stmt as MethodStatement)?.tokens.name?.text?.toLowerCase() === 'new';
262!
2852
        }) as MethodStatement;
2853
    }
2854

2855
    /**
2856
     * Return the parameters for the first constructor function for this class
2857
     * @param ancestors The list of ancestors for this class
2858
     * @returns The parameters for the first constructor function for this class
2859
     */
2860
    private getConstructorParams(ancestors: ClassStatement[]) {
2861
        for (let ancestor of ancestors) {
50✔
2862
            const ctor = ancestor?.getConstructorFunction();
40!
2863
            if (ctor) {
40✔
2864
                return ctor.func.parameters;
16✔
2865
            }
2866
        }
2867
        return [];
34✔
2868
    }
2869

2870
    /**
2871
     * Determine if the specified field was declared in one of the ancestor classes
2872
     */
2873
    public isFieldDeclaredByAncestor(fieldName: string, ancestors: ClassStatement[]) {
UNCOV
2874
        let lowerFieldName = fieldName.toLowerCase();
×
UNCOV
2875
        for (let ancestor of ancestors) {
×
UNCOV
2876
            if (ancestor.memberMap[lowerFieldName]) {
×
UNCOV
2877
                return true;
×
2878
            }
2879
        }
UNCOV
2880
        return false;
×
2881
    }
2882

2883
    /**
2884
     * The builder is a function that assigns all of the methods and property names to a class instance.
2885
     * This needs to be a separate function so that child classes can call the builder from their parent
2886
     * without instantiating the parent constructor at that point in time.
2887
     */
2888
    private getTranspiledBuilder(state: BrsTranspileState, transpiledClassName: string, ancestors: ClassStatement[], body: Statement[]) {
2889
        let result = [] as TranspileResult;
59✔
2890
        result.push(`function ${this.getBuilderName(transpiledClassName)}()\n`);
59✔
2891
        state.blockDepth++;
59✔
2892
        //indent
2893
        result.push(state.indent());
59✔
2894

2895
        //construct parent class or empty object
2896
        if (ancestors[0]) {
59✔
2897
            const ancestorNamespace = ancestors[0].findAncestor<NamespaceStatement>(isNamespaceStatement);
24✔
2898
            let fullyQualifiedClassName = util.getFullyQualifiedClassName(
24✔
2899
                ancestors[0].getName(ParseMode.BrighterScript)!,
2900
                ancestorNamespace?.getName(ParseMode.BrighterScript)
72✔
2901
            );
2902
            result.push(`instance = ${this.getBuilderName(fullyQualifiedClassName.replace(/\./g, '_'))}()`);
24✔
2903
        } else {
2904
            //use an empty object.
2905
            result.push('instance = {}');
35✔
2906
        }
2907
        result.push(
59✔
2908
            state.newline,
2909
            state.indent()
2910
        );
2911
        let parentClassIndex = this.getParentClassIndex(state);
59✔
2912

2913
        for (let statement of body) {
59✔
2914
            //is field statement
2915
            if (isFieldStatement(statement)) {
90✔
2916
                //do nothing with class fields in this situation, they are handled elsewhere
2917
                continue;
15✔
2918

2919
                //methods
2920
            } else if (isMethodStatement(statement)) {
75!
2921

2922
                //store overridden parent methods as super{parentIndex}_{methodName}
2923
                if (
75✔
2924
                    //is override method
2925
                    statement.tokens.override ||
205✔
2926
                    //is constructor function in child class
2927
                    (statement.tokens.name.text.toLowerCase() === 'new' && ancestors[0])
2928
                ) {
2929
                    result.push(
28✔
2930
                        `instance.super${parentClassIndex}_${statement.tokens.name.text} = instance.${statement.tokens.name.text}`,
2931
                        state.newline,
2932
                        state.indent()
2933
                    );
2934
                }
2935

2936
                state.classStatement = this;
75✔
2937
                state.skipLeadingComments = true;
75✔
2938
                //add leading comments
2939
                if (statement.leadingTrivia.filter(token => token.kind === TokenKind.Comment).length > 0) {
85✔
2940
                    result.push(
2✔
2941
                        ...state.transpileComments(statement.leadingTrivia),
2942
                        state.indent()
2943
                    );
2944
                }
2945
                result.push(
75✔
2946
                    'instance.',
2947
                    state.transpileToken(statement.tokens.name),
2948
                    ' = ',
2949
                    state.transpileToken(this.getMethodIdentifier(transpiledClassName, statement)),
2950
                    state.newline,
2951
                    state.indent()
2952
                );
2953
                state.skipLeadingComments = false;
75✔
2954
                delete state.classStatement;
75✔
2955
            } else {
2956
                //other random statements (probably just comments)
UNCOV
2957
                result.push(
×
2958
                    ...statement.transpile(state),
2959
                    state.newline,
2960
                    state.indent()
2961
                );
2962
            }
2963
        }
2964
        //return the instance
2965
        result.push('return instance\n');
59✔
2966
        state.blockDepth--;
59✔
2967
        result.push(state.indent());
59✔
2968
        result.push(`end function`);
59✔
2969
        return result;
59✔
2970
    }
2971

2972
    /**
2973
     * Returns a copy of the class' body, with the constructor function added if it doesn't exist.
2974
     */
2975
    private getTranspiledClassBody(ancestors: ClassStatement[]): Statement[] {
2976
        const body = [];
59✔
2977
        body.push(...this.body);
59✔
2978

2979
        //inject an empty "new" method if missing
2980
        if (!this.getConstructorFunction()) {
59✔
2981
            if (ancestors.length === 0) {
35✔
2982
                body.unshift(createMethodStatement('new', TokenKind.Sub));
20✔
2983
            } else {
2984
                const params = this.getConstructorParams(ancestors);
15✔
2985
                const call = new ExpressionStatement({
15✔
2986
                    expression: new CallExpression({
2987
                        callee: new VariableExpression({
2988
                            name: createToken(TokenKind.Identifier, 'super')
2989
                        }),
2990
                        openingParen: createToken(TokenKind.LeftParen),
2991
                        args: params.map(x => new VariableExpression({
6✔
2992
                            name: x.tokens.name
2993
                        })),
2994
                        closingParen: createToken(TokenKind.RightParen)
2995
                    })
2996
                });
2997
                body.unshift(
15✔
2998
                    new MethodStatement({
2999
                        modifiers: [],
3000
                        name: createIdentifier('new'),
3001
                        func: new FunctionExpression({
3002
                            parameters: params.map(x => x.clone()),
6✔
3003
                            body: new Block({ statements: [call] }),
3004
                            functionType: createToken(TokenKind.Sub),
3005
                            endFunctionType: createToken(TokenKind.EndSub),
3006
                            leftParen: createToken(TokenKind.LeftParen),
3007
                            rightParen: createToken(TokenKind.RightParen)
3008
                        }),
3009
                        override: null
3010
                    })
3011
                );
3012
            }
3013
        }
3014

3015
        return body;
59✔
3016
    }
3017

3018
    /**
3019
     * These are the methods that are defined in this class. They are transpiled outside of the class body
3020
     * to ensure they don't appear as "$anon_#" in stack traces and crash logs.
3021
     */
3022
    private getTranspiledMethods(state: BrsTranspileState, transpiledClassName: string, body: Statement[]) {
3023
        let result = [] as TranspileResult;
59✔
3024
        for (let statement of body) {
59✔
3025
            if (isMethodStatement(statement)) {
90✔
3026
                state.classStatement = this;
75✔
3027
                result.push(
75✔
3028
                    ...statement.transpile(state, this.getMethodIdentifier(transpiledClassName, statement)),
3029
                    state.newline,
3030
                    state.indent()
3031
                );
3032
                delete state.classStatement;
75✔
3033
            }
3034
        }
3035
        return result;
59✔
3036
    }
3037

3038
    /**
3039
     * The class function is the function with the same name as the class. This is the function that
3040
     * consumers should call to create a new instance of that class.
3041
     * This invokes the builder, gets an instance of the class, then invokes the "new" function on that class.
3042
     */
3043
    private getTranspiledClassFunction(state: BrsTranspileState, transpiledClassName: string) {
3044
        let result: TranspileResult = state.transpileAnnotations(this);
59✔
3045

3046
        const constructorFunction = this.getConstructorFunction();
59✔
3047
        let constructorParams = [];
59✔
3048
        if (constructorFunction) {
59✔
3049
            constructorParams = constructorFunction.func.parameters;
24✔
3050
        } else {
3051
            constructorParams = this.getConstructorParams(this.getAncestors(state));
35✔
3052
        }
3053

3054
        result.push(
59✔
3055
            state.transpileLeadingComments(this.tokens.class),
3056
            state.sourceNode(this.tokens.class, 'function'),
3057
            state.sourceNode(this.tokens.class, ' '),
3058
            state.sourceNode(this.tokens.name, this.getName(ParseMode.BrightScript)),
3059
            `(`
3060
        );
3061
        let i = 0;
59✔
3062
        for (let param of constructorParams) {
59✔
3063
            if (i > 0) {
17✔
3064
                result.push(', ');
4✔
3065
            }
3066
            result.push(
17✔
3067
                param.transpile(state)
3068
            );
3069
            i++;
17✔
3070
        }
3071
        result.push(
59✔
3072
            ')',
3073
            '\n'
3074
        );
3075

3076
        state.blockDepth++;
59✔
3077
        result.push(state.indent());
59✔
3078
        result.push(`instance = ${this.getBuilderName(transpiledClassName)}()\n`);
59✔
3079

3080
        result.push(state.indent());
59✔
3081
        result.push(`instance.new(`);
59✔
3082

3083
        //append constructor arguments
3084
        i = 0;
59✔
3085
        for (let param of constructorParams) {
59✔
3086
            if (i > 0) {
17✔
3087
                result.push(', ');
4✔
3088
            }
3089
            result.push(
17✔
3090
                state.transpileToken(param.tokens.name)
3091
            );
3092
            i++;
17✔
3093
        }
3094
        result.push(
59✔
3095
            ')',
3096
            '\n'
3097
        );
3098

3099
        result.push(state.indent());
59✔
3100
        result.push(`return instance\n`);
59✔
3101

3102
        state.blockDepth--;
59✔
3103
        result.push(state.indent());
59✔
3104
        result.push(`end function`);
59✔
3105
        return result;
59✔
3106
    }
3107

3108
    walk(visitor: WalkVisitor, options: WalkOptions) {
3109
        //visitor-less walk function to do parent linking
3110
        walk(this, 'parentClassName', null, options);
3,157✔
3111

3112
        if (options.walkMode & InternalWalkMode.walkStatements) {
3,157!
3113
            walkArray(this.body, visitor, options, this);
3,157✔
3114
        }
3115
    }
3116

3117
    getType(options: GetTypeOptions) {
3118
        const superClass = this.parentClassName?.getType(options) as ClassType;
607✔
3119

3120
        const resultType = new ClassType(this.getName(ParseMode.BrighterScript), superClass);
607✔
3121

3122
        for (const statement of this.methods) {
607✔
3123
            const funcType = statement?.func.getType({ ...options, typeChain: undefined }); //no typechain needed
314!
3124
            let flag = SymbolTypeFlag.runtime;
314✔
3125
            if (statement.accessModifier?.kind === TokenKind.Private) {
314✔
3126
                flag |= SymbolTypeFlag.private;
9✔
3127
            }
3128
            if (statement.accessModifier?.kind === TokenKind.Protected) {
314✔
3129
                flag |= SymbolTypeFlag.protected;
8✔
3130
            }
3131
            resultType.addMember(statement?.tokens.name?.text, { definingNode: statement }, funcType, flag);
314!
3132
        }
3133
        for (const statement of this.fields) {
607✔
3134
            const fieldType = statement.getType({ ...options, typeChain: undefined }); //no typechain needed
310✔
3135
            let flag = SymbolTypeFlag.runtime;
310✔
3136
            if (statement.isOptional) {
310✔
3137
                flag |= SymbolTypeFlag.optional;
7✔
3138
            }
3139
            if (statement.tokens.accessModifier?.kind === TokenKind.Private) {
310✔
3140
                flag |= SymbolTypeFlag.private;
20✔
3141
            }
3142
            if (statement.tokens.accessModifier?.kind === TokenKind.Protected) {
310✔
3143
                flag |= SymbolTypeFlag.protected;
9✔
3144
            }
3145
            resultType.addMember(statement?.tokens.name?.text, { definingNode: statement, isInstance: true }, fieldType, flag);
310!
3146
        }
3147
        options.typeChain?.push(new TypeChainEntry({ name: resultType.name, type: resultType, data: options.data, astNode: this }));
607✔
3148
        return resultType;
607✔
3149
    }
3150

3151
    public clone() {
3152
        return this.finalizeClone(
11✔
3153
            new ClassStatement({
3154
                class: util.cloneToken(this.tokens.class),
3155
                name: util.cloneToken(this.tokens.name),
3156
                body: this.body?.map(x => x?.clone()),
10✔
3157
                endClass: util.cloneToken(this.tokens.endClass),
3158
                extends: util.cloneToken(this.tokens.extends),
3159
                parentClassName: this.parentClassName?.clone()
33✔
3160
            }),
3161
            ['body', 'parentClassName']
3162
        );
3163
    }
3164
}
3165

3166
const accessModifiers = [
1✔
3167
    TokenKind.Public,
3168
    TokenKind.Protected,
3169
    TokenKind.Private
3170
];
3171
export class MethodStatement extends FunctionStatement {
1✔
3172
    constructor(
3173
        options: {
3174
            modifiers?: Token | Token[];
3175
            name: Identifier;
3176
            func: FunctionExpression;
3177
            override?: Token;
3178
        }
3179
    ) {
3180
        super(options);
436✔
3181
        if (options.modifiers) {
436✔
3182
            if (Array.isArray(options.modifiers)) {
56✔
3183
                this.modifiers.push(...options.modifiers);
19✔
3184
            } else {
3185
                this.modifiers.push(options.modifiers);
37✔
3186
            }
3187
        }
3188
        this.tokens = {
436✔
3189
            ...this.tokens,
3190
            override: options.override
3191
        };
3192
        this.location = util.createBoundingLocation(
436✔
3193
            ...(this.modifiers),
3194
            util.createBoundingLocationFromTokens(this.tokens),
3195
            this.func
3196
        );
3197
    }
3198

3199
    public readonly kind = AstNodeKind.MethodStatement as AstNodeKind;
436✔
3200

3201
    public readonly modifiers: Token[] = [];
436✔
3202

3203
    public readonly tokens: {
3204
        readonly name: Identifier;
3205
        readonly override?: Token;
3206
    };
3207

3208
    public get accessModifier() {
3209
        return this.modifiers.find(x => accessModifiers.includes(x.kind));
746✔
3210
    }
3211

3212
    public readonly location: Location | undefined;
3213

3214
    /**
3215
     * Get the name of this method.
3216
     */
3217
    public getName(parseMode: ParseMode) {
3218
        return this.tokens.name.text;
399✔
3219
    }
3220

3221
    public get leadingTrivia(): Token[] {
3222
        return this.func.leadingTrivia;
885✔
3223
    }
3224

3225
    transpile(state: BrsTranspileState, name?: Identifier) {
3226
        if (this.tokens.name.text.toLowerCase() === 'new') {
75✔
3227
            this.ensureSuperConstructorCall(state);
59✔
3228
            //TODO we need to undo this at the bottom of this method
3229
            this.injectFieldInitializersForConstructor(state);
59✔
3230
        }
3231
        //TODO - remove type information from these methods because that doesn't work
3232
        //convert the `super` calls into the proper methods
3233
        const parentClassIndex = state.classStatement.getParentClassIndex(state);
75✔
3234
        const visitor = createVisitor({
75✔
3235
            VariableExpression: e => {
3236
                if (e.tokens.name.text.toLocaleLowerCase() === 'super') {
78✔
3237
                    state.editor.setProperty(e.tokens.name, 'text', `m.super${parentClassIndex}_new`);
24✔
3238
                }
3239
            },
3240
            DottedGetExpression: e => {
3241
                const beginningVariable = util.findBeginningVariableExpression(e);
32✔
3242
                const lowerName = beginningVariable?.getName(ParseMode.BrighterScript).toLowerCase();
32!
3243
                if (lowerName === 'super') {
32✔
3244
                    state.editor.setProperty(beginningVariable.tokens.name, 'text', 'm');
7✔
3245
                    state.editor.setProperty(e.tokens.name, 'text', `super${parentClassIndex}_${e.tokens.name.text}`);
7✔
3246
                }
3247
            }
3248
        });
3249
        const walkOptions: WalkOptions = { walkMode: WalkMode.visitExpressions };
75✔
3250
        for (const statement of this.func.body.statements) {
75✔
3251
            visitor(statement, undefined);
72✔
3252
            statement.walk(visitor, walkOptions);
72✔
3253
        }
3254
        return this.func.transpile(state, name);
75✔
3255
    }
3256

3257
    getTypedef(state: BrsTranspileState) {
3258
        const result: TranspileResult = [];
23✔
3259
        for (let comment of util.getLeadingComments(this) ?? []) {
23!
UNCOV
3260
            result.push(
×
3261
                comment.text,
3262
                state.newline,
3263
                state.indent()
3264
            );
3265
        }
3266
        for (let annotation of this.annotations ?? []) {
23✔
3267
            result.push(
2✔
3268
                ...annotation.getTypedef(state),
3269
                state.newline,
3270
                state.indent()
3271
            );
3272
        }
3273
        if (this.accessModifier) {
23✔
3274
            result.push(
8✔
3275
                this.accessModifier.text,
3276
                ' '
3277
            );
3278
        }
3279
        if (this.tokens.override) {
23✔
3280
            result.push('override ');
1✔
3281
        }
3282
        result.push(
23✔
3283
            ...this.func.getTypedef(state)
3284
        );
3285
        return result;
23✔
3286
    }
3287

3288
    /**
3289
     * All child classes must call the parent constructor. The type checker will warn users when they don't call it in their own class,
3290
     * but we still need to call it even if they have omitted it. This injects the super call if it's missing
3291
     */
3292
    private ensureSuperConstructorCall(state: BrsTranspileState) {
3293
        //if this class doesn't extend another class, quit here
3294
        if (state.classStatement!.getAncestors(state).length === 0) {
59✔
3295
            return;
35✔
3296
        }
3297

3298
        //check whether any calls to super exist
3299
        let containsSuperCall =
3300
            this.func.body.statements.findIndex((x) => {
24✔
3301
                //is a call statement
3302
                return isExpressionStatement(x) && isCallExpression(x.expression) &&
24✔
3303
                    //is a call to super
3304
                    util.findBeginningVariableExpression(x.expression.callee as any).tokens.name?.text.toLowerCase() === 'super';
69!
3305
            }) !== -1;
3306

3307
        //if a call to super exists, quit here
3308
        if (containsSuperCall) {
24✔
3309
            return;
23✔
3310
        }
3311

3312
        //this is a child class, and the constructor doesn't contain a call to super. Inject one
3313
        const superCall = new ExpressionStatement({
1✔
3314
            expression: new CallExpression({
3315
                callee: new VariableExpression({
3316
                    name: {
3317
                        kind: TokenKind.Identifier,
3318
                        text: 'super',
3319
                        isReserved: false,
3320
                        location: state.classStatement.tokens.name.location,
3321
                        leadingWhitespace: '',
3322
                        leadingTrivia: []
3323
                    }
3324
                }),
3325
                openingParen: {
3326
                    kind: TokenKind.LeftParen,
3327
                    text: '(',
3328
                    isReserved: false,
3329
                    location: state.classStatement.tokens.name.location,
3330
                    leadingWhitespace: '',
3331
                    leadingTrivia: []
3332
                },
3333
                closingParen: {
3334
                    kind: TokenKind.RightParen,
3335
                    text: ')',
3336
                    isReserved: false,
3337
                    location: state.classStatement.tokens.name.location,
3338
                    leadingWhitespace: '',
3339
                    leadingTrivia: []
3340
                },
3341
                args: []
3342
            })
3343
        });
3344
        state.editor.arrayUnshift(this.func.body.statements, superCall);
1✔
3345
    }
3346

3347
    /**
3348
     * Inject field initializers at the top of the `new` function (after any present `super()` call)
3349
     */
3350
    private injectFieldInitializersForConstructor(state: BrsTranspileState) {
3351
        let startingIndex = state.classStatement!.hasParentClass() ? 1 : 0;
59✔
3352

3353
        let newStatements = [] as Statement[];
59✔
3354
        //insert the field initializers in order
3355
        for (let field of state.classStatement!.fields) {
59✔
3356
            let thisQualifiedName = { ...field.tokens.name };
15✔
3357
            thisQualifiedName.text = 'm.' + field.tokens.name?.text;
15!
3358
            const fieldAssignment = field.initialValue
15✔
3359
                ? new AssignmentStatement({
15✔
3360
                    equals: field.tokens.equals,
3361
                    name: thisQualifiedName,
3362
                    value: field.initialValue
3363
                })
3364
                : new AssignmentStatement({
3365
                    equals: createToken(TokenKind.Equal, '=', field.tokens.name.location),
3366
                    name: thisQualifiedName,
3367
                    //if there is no initial value, set the initial value to `invalid`
3368
                    value: createInvalidLiteral('invalid', field.tokens.name.location)
3369
                });
3370
            // Add parent so namespace lookups work
3371
            fieldAssignment.parent = state.classStatement;
15✔
3372
            newStatements.push(fieldAssignment);
15✔
3373
        }
3374
        state.editor.arraySplice(this.func.body.statements, startingIndex, 0, ...newStatements);
59✔
3375
    }
3376

3377
    walk(visitor: WalkVisitor, options: WalkOptions) {
3378
        if (options.walkMode & InternalWalkMode.walkExpressions) {
2,455✔
3379
            walk(this, 'func', visitor, options);
1,932✔
3380
        }
3381
    }
3382

3383
    public clone() {
3384
        return this.finalizeClone(
5✔
3385
            new MethodStatement({
3386
                modifiers: this.modifiers?.map(m => util.cloneToken(m)),
1✔
3387
                name: util.cloneToken(this.tokens.name),
3388
                func: this.func?.clone(),
15✔
3389
                override: util.cloneToken(this.tokens.override)
3390
            }),
3391
            ['func']
3392
        );
3393
    }
3394
}
3395

3396
export class FieldStatement extends Statement implements TypedefProvider {
1✔
3397
    constructor(options: {
3398
        accessModifier?: Token;
3399
        name: Identifier;
3400
        as?: Token;
3401
        typeExpression?: TypeExpression;
3402
        equals?: Token;
3403
        initialValue?: Expression;
3404
        optional?: Token;
3405
    }) {
3406
        super();
366✔
3407
        this.tokens = {
366✔
3408
            accessModifier: options.accessModifier,
3409
            name: options.name,
3410
            as: options.as,
3411
            equals: options.equals,
3412
            optional: options.optional
3413
        };
3414
        this.typeExpression = options.typeExpression;
366✔
3415
        this.initialValue = options.initialValue;
366✔
3416

3417
        this.location = util.createBoundingLocation(
366✔
3418
            util.createBoundingLocationFromTokens(this.tokens),
3419
            this.typeExpression,
3420
            this.initialValue
3421
        );
3422
    }
3423

3424
    public readonly tokens: {
3425
        readonly accessModifier?: Token;
3426
        readonly name: Identifier;
3427
        readonly as?: Token;
3428
        readonly equals?: Token;
3429
        readonly optional?: Token;
3430
    };
3431

3432
    public readonly typeExpression?: TypeExpression;
3433
    public readonly initialValue?: Expression;
3434

3435
    public readonly kind = AstNodeKind.FieldStatement;
366✔
3436

3437
    /**
3438
     * Derive a ValueKind from the type token, or the initial value.
3439
     * Defaults to `DynamicType`
3440
     */
3441
    getType(options: GetTypeOptions) {
3442
        let initialValueType = this.initialValue?.getType({ ...options, flags: SymbolTypeFlag.runtime });
360✔
3443

3444
        if (isInvalidType(initialValueType) || isVoidType(initialValueType) || isUninitializedType(initialValueType)) {
360✔
3445
            initialValueType = undefined;
4✔
3446
        }
3447

3448
        return this.typeExpression?.getType({ ...options, flags: SymbolTypeFlag.typetime }) ??
360✔
3449
            util.getDefaultTypeFromValueType(initialValueType) ??
360✔
3450
            DynamicType.instance;
3451
    }
3452

3453
    public readonly location: Location | undefined;
3454

3455
    public get leadingTrivia(): Token[] {
3456
        return this.tokens.accessModifier?.leadingTrivia ?? this.tokens.optional?.leadingTrivia ?? this.tokens.name.leadingTrivia;
743✔
3457
    }
3458

3459
    public get isOptional() {
3460
        return !!this.tokens.optional;
329✔
3461
    }
3462

3463
    transpile(state: BrsTranspileState): TranspileResult {
UNCOV
3464
        throw new Error('transpile not implemented for ' + Object.getPrototypeOf(this).constructor.name);
×
3465
    }
3466

3467
    getTypedef(state: BrsTranspileState) {
3468
        const result = [];
12✔
3469
        if (this.tokens.name) {
12!
3470
            for (let comment of util.getLeadingComments(this) ?? []) {
12!
UNCOV
3471
                result.push(
×
3472
                    comment.text,
3473
                    state.newline,
3474
                    state.indent()
3475
                );
3476
            }
3477
            for (let annotation of this.annotations ?? []) {
12✔
3478
                result.push(
2✔
3479
                    ...annotation.getTypedef(state),
3480
                    state.newline,
3481
                    state.indent()
3482
                );
3483
            }
3484

3485
            let type = this.getType({ flags: SymbolTypeFlag.typetime });
12✔
3486
            if (isInvalidType(type) || isVoidType(type) || isUninitializedType(type)) {
12!
UNCOV
3487
                type = DynamicType.instance;
×
3488
            }
3489

3490
            result.push(
12✔
3491
                this.tokens.accessModifier?.text ?? 'public',
72✔
3492
                ' '
3493
            );
3494
            if (this.isOptional) {
12!
UNCOV
3495
                result.push(this.tokens.optional.text, ' ');
×
3496
            }
3497
            result.push(this.tokens.name?.text,
12!
3498
                ' as ',
3499
                type.toTypeString()
3500
            );
3501
        }
3502
        return result;
12✔
3503
    }
3504

3505
    walk(visitor: WalkVisitor, options: WalkOptions) {
3506
        if (options.walkMode & InternalWalkMode.walkExpressions) {
1,925✔
3507
            walk(this, 'typeExpression', visitor, options);
1,615✔
3508
            walk(this, 'initialValue', visitor, options);
1,615✔
3509
        }
3510
    }
3511

3512
    public clone() {
3513
        return this.finalizeClone(
4✔
3514
            new FieldStatement({
3515
                accessModifier: util.cloneToken(this.tokens.accessModifier),
3516
                name: util.cloneToken(this.tokens.name),
3517
                as: util.cloneToken(this.tokens.as),
3518
                typeExpression: this.typeExpression?.clone(),
12✔
3519
                equals: util.cloneToken(this.tokens.equals),
3520
                initialValue: this.initialValue?.clone(),
12✔
3521
                optional: util.cloneToken(this.tokens.optional)
3522
            }),
3523
            ['initialValue']
3524
        );
3525
    }
3526
}
3527

3528
export type MemberStatement = FieldStatement | MethodStatement;
3529

3530
export class TryCatchStatement extends Statement {
1✔
3531
    constructor(options?: {
3532
        try?: Token;
3533
        endTry?: Token;
3534
        tryBranch?: Block;
3535
        catchStatement?: CatchStatement;
3536
    }) {
3537
        super();
46✔
3538
        this.tokens = {
46✔
3539
            try: options.try,
3540
            endTry: options.endTry
3541
        };
3542
        this.tryBranch = options.tryBranch;
46✔
3543
        this.catchStatement = options.catchStatement;
46✔
3544
        this.location = util.createBoundingLocation(
46✔
3545
            this.tokens.try,
3546
            this.tryBranch,
3547
            this.catchStatement,
3548
            this.tokens.endTry
3549
        );
3550
    }
3551

3552
    public readonly tokens: {
3553
        readonly try?: Token;
3554
        readonly endTry?: Token;
3555
    };
3556

3557
    public readonly tryBranch: Block;
3558
    public readonly catchStatement: CatchStatement;
3559

3560
    public readonly kind = AstNodeKind.TryCatchStatement;
46✔
3561

3562
    public readonly location: Location | undefined;
3563

3564
    public transpile(state: BrsTranspileState): TranspileResult {
3565
        return [
7✔
3566
            state.transpileToken(this.tokens.try, 'try'),
3567
            ...this.tryBranch.transpile(state),
3568
            state.newline,
3569
            state.indent(),
3570
            ...(this.catchStatement?.transpile(state) ?? ['catch']),
42!
3571
            state.newline,
3572
            state.indent(),
3573
            state.transpileToken(this.tokens.endTry!, 'end try')
3574
        ];
3575
    }
3576

3577
    public walk(visitor: WalkVisitor, options: WalkOptions) {
3578
        if (this.tryBranch && options.walkMode & InternalWalkMode.walkStatements) {
134!
3579
            walk(this, 'tryBranch', visitor, options);
134✔
3580
            walk(this, 'catchStatement', visitor, options);
134✔
3581
        }
3582
    }
3583

3584
    public get leadingTrivia(): Token[] {
3585
        return this.tokens.try?.leadingTrivia ?? [];
131!
3586
    }
3587

3588
    public get endTrivia(): Token[] {
3589
        return this.tokens.endTry?.leadingTrivia ?? [];
1!
3590
    }
3591

3592
    public clone() {
3593
        return this.finalizeClone(
3✔
3594
            new TryCatchStatement({
3595
                try: util.cloneToken(this.tokens.try),
3596
                endTry: util.cloneToken(this.tokens.endTry),
3597
                tryBranch: this.tryBranch?.clone(),
9✔
3598
                catchStatement: this.catchStatement?.clone()
9✔
3599
            }),
3600
            ['tryBranch', 'catchStatement']
3601
        );
3602
    }
3603
}
3604

3605
export class CatchStatement extends Statement {
1✔
3606
    constructor(options?: {
3607
        catch?: Token;
3608
        exceptionVariableExpression?: Expression;
3609
        catchBranch?: Block;
3610
    }) {
3611
        super();
43✔
3612
        this.tokens = {
43✔
3613
            catch: options?.catch
129!
3614
        };
3615
        this.exceptionVariableExpression = options?.exceptionVariableExpression;
43!
3616
        this.catchBranch = options?.catchBranch;
43!
3617
        this.location = util.createBoundingLocation(
43✔
3618
            this.tokens.catch,
3619
            this.exceptionVariableExpression,
3620
            this.catchBranch
3621
        );
3622
    }
3623

3624
    public readonly tokens: {
3625
        readonly catch?: Token;
3626
    };
3627

3628
    public readonly exceptionVariableExpression?: Expression;
3629

3630
    public readonly catchBranch?: Block;
3631

3632
    public readonly kind = AstNodeKind.CatchStatement;
43✔
3633

3634
    public readonly location: Location | undefined;
3635

3636
    public transpile(state: BrsTranspileState): TranspileResult {
3637
        return [
7✔
3638
            state.transpileToken(this.tokens.catch, 'catch'),
3639
            ' ',
3640
            this.exceptionVariableExpression?.transpile(state) ?? [
42✔
3641
                //use the variable named `e` if it doesn't exist in this function body. otherwise use '__bsc_error' just to make sure we're out of the way
3642
                this.getSymbolTable()?.hasSymbol('e', SymbolTypeFlag.runtime)
6!
3643
                    ? '__bsc_error'
2✔
3644
                    : 'e'
3645
            ],
3646
            ...(this.catchBranch?.transpile(state) ?? [])
42!
3647
        ];
3648
    }
3649

3650
    public walk(visitor: WalkVisitor, options: WalkOptions) {
3651
        if (this.catchBranch && options.walkMode & InternalWalkMode.walkStatements) {
131!
3652
            walk(this, 'catchBranch', visitor, options);
131✔
3653
        }
3654
    }
3655

3656
    public get leadingTrivia(): Token[] {
3657
        return this.tokens.catch?.leadingTrivia ?? [];
83!
3658
    }
3659

3660
    public clone() {
3661
        return this.finalizeClone(
2✔
3662
            new CatchStatement({
3663
                catch: util.cloneToken(this.tokens.catch),
3664
                exceptionVariableExpression: this.exceptionVariableExpression?.clone(),
6!
3665
                catchBranch: this.catchBranch?.clone()
6✔
3666
            }),
3667
            ['catchBranch']
3668
        );
3669
    }
3670
}
3671

3672
export class ThrowStatement extends Statement {
1✔
3673
    constructor(options?: {
3674
        throw?: Token;
3675
        expression?: Expression;
3676
    }) {
3677
        super();
14✔
3678
        this.tokens = {
14✔
3679
            throw: options.throw
3680
        };
3681
        this.expression = options.expression;
14✔
3682
        this.location = util.createBoundingLocation(
14✔
3683
            this.tokens.throw,
3684
            this.expression
3685
        );
3686
    }
3687

3688
    public readonly tokens: {
3689
        readonly throw?: Token;
3690
    };
3691
    public readonly expression?: Expression;
3692

3693
    public readonly kind = AstNodeKind.ThrowStatement;
14✔
3694

3695
    public readonly location: Location | undefined;
3696

3697
    public transpile(state: BrsTranspileState) {
3698
        const result = [
5✔
3699
            state.transpileToken(this.tokens.throw, 'throw'),
3700
            ' '
3701
        ] as TranspileResult;
3702

3703
        //if we have an expression, transpile it
3704
        if (this.expression) {
5✔
3705
            result.push(
4✔
3706
                ...this.expression.transpile(state)
3707
            );
3708

3709
            //no expression found. Rather than emit syntax errors, provide a generic error message
3710
        } else {
3711
            result.push('"User-specified exception"');
1✔
3712
        }
3713
        return result;
5✔
3714
    }
3715

3716
    public walk(visitor: WalkVisitor, options: WalkOptions) {
3717
        if (this.expression && options.walkMode & InternalWalkMode.walkExpressions) {
38✔
3718
            walk(this, 'expression', visitor, options);
30✔
3719
        }
3720
    }
3721

3722
    public get leadingTrivia(): Token[] {
3723
        return this.tokens.throw?.leadingTrivia ?? [];
54!
3724
    }
3725

3726
    public clone() {
3727
        return this.finalizeClone(
2✔
3728
            new ThrowStatement({
3729
                throw: util.cloneToken(this.tokens.throw),
3730
                expression: this.expression?.clone()
6✔
3731
            }),
3732
            ['expression']
3733
        );
3734
    }
3735
}
3736

3737

3738
export class EnumStatement extends Statement implements TypedefProvider {
1✔
3739
    constructor(options: {
3740
        enum?: Token;
3741
        name: Identifier;
3742
        endEnum?: Token;
3743
        body: Array<EnumMemberStatement>;
3744
    }) {
3745
        super();
207✔
3746
        this.tokens = {
207✔
3747
            enum: options.enum,
3748
            name: options.name,
3749
            endEnum: options.endEnum
3750
        };
3751
        this.symbolTable = new SymbolTable('Enum');
207✔
3752
        this.body = options.body ?? [];
207✔
3753
    }
3754

3755
    public readonly tokens: {
3756
        readonly enum?: Token;
3757
        readonly name: Identifier;
3758
        readonly endEnum?: Token;
3759
    };
3760
    public readonly body: Array<EnumMemberStatement>;
3761

3762
    public readonly kind = AstNodeKind.EnumStatement;
207✔
3763

3764
    public get location(): Location | undefined {
3765
        return util.createBoundingLocation(
204✔
3766
            this.tokens.enum,
3767
            this.tokens.name,
3768
            ...this.body,
3769
            this.tokens.endEnum
3770
        );
3771
    }
3772

3773
    public getMembers() {
3774
        const result = [] as EnumMemberStatement[];
433✔
3775
        for (const statement of this.body) {
433✔
3776
            if (isEnumMemberStatement(statement)) {
872!
3777
                result.push(statement);
872✔
3778
            }
3779
        }
3780
        return result;
433✔
3781
    }
3782

3783
    public get leadingTrivia(): Token[] {
3784
        return this.tokens.enum?.leadingTrivia;
580!
3785
    }
3786

3787
    public get endTrivia(): Token[] {
UNCOV
3788
        return this.tokens.endEnum?.leadingTrivia ?? [];
×
3789
    }
3790

3791
    /**
3792
     * Get a map of member names and their values.
3793
     * All values are stored as their AST LiteralExpression representation (i.e. string enum values include the wrapping quotes)
3794
     */
3795
    public getMemberValueMap() {
3796
        const result = new Map<string, string>();
91✔
3797
        const members = this.getMembers();
91✔
3798
        let currentIntValue = 0;
91✔
3799
        for (const member of members) {
91✔
3800
            //if there is no value, assume an integer and increment the int counter
3801
            if (!member.value) {
201✔
3802
                result.set(member.name?.toLowerCase(), currentIntValue.toString());
34!
3803
                currentIntValue++;
34✔
3804

3805
                //if explicit integer value, use it and increment the int counter
3806
            } else if (isLiteralExpression(member.value) && member.value.tokens.value.kind === TokenKind.IntegerLiteral) {
167✔
3807
                //try parsing as integer literal, then as hex integer literal.
3808
                let tokenIntValue = util.parseInt(member.value.tokens.value.text) ?? util.parseInt(member.value.tokens.value.text.replace(/&h/i, '0x'));
30✔
3809
                if (tokenIntValue !== undefined) {
30!
3810
                    currentIntValue = tokenIntValue;
30✔
3811
                    currentIntValue++;
30✔
3812
                }
3813
                result.set(member.name?.toLowerCase(), member.value.tokens.value.text);
30!
3814

3815
                //simple unary expressions (like `-1`)
3816
            } else if (isUnaryExpression(member.value) && isLiteralExpression(member.value.right)) {
137✔
3817
                result.set(member.name?.toLowerCase(), member.value.tokens.operator.text + member.value.right.tokens.value.text);
1!
3818

3819
                //all other values
3820
            } else {
3821
                result.set(member.name?.toLowerCase(), (member.value as LiteralExpression)?.tokens?.value?.text ?? 'invalid');
136!
3822
            }
3823
        }
3824
        return result;
91✔
3825
    }
3826

3827
    public getMemberValue(name: string) {
3828
        return this.getMemberValueMap().get(name.toLowerCase());
88✔
3829
    }
3830

3831
    /**
3832
     * The name of the enum (without the namespace prefix)
3833
     */
3834
    public get name() {
3835
        return this.tokens.name?.text;
1!
3836
    }
3837

3838
    /**
3839
     * The name of the enum WITH its leading namespace (if applicable)
3840
     */
3841
    public get fullName() {
3842
        const name = this.tokens.name?.text;
947!
3843
        if (name) {
947!
3844
            const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
947✔
3845

3846
            if (namespace) {
947✔
3847
                let namespaceName = namespace.getName(ParseMode.BrighterScript);
397✔
3848
                return `${namespaceName}.${name}`;
397✔
3849
            } else {
3850
                return name;
550✔
3851
            }
3852
        } else {
3853
            //return undefined which will allow outside callers to know that this doesn't have a name
UNCOV
3854
            return undefined;
×
3855
        }
3856
    }
3857

3858
    transpile(state: BrsTranspileState) {
3859
        //enum declarations don't exist at runtime, so just transpile comments and trivia
3860
        return [
34✔
3861
            state.transpileAnnotations(this),
3862
            state.transpileLeadingComments(this.tokens.enum)
3863
        ];
3864
    }
3865

3866
    getTypedef(state: BrsTranspileState) {
3867
        const result = [] as TranspileResult;
1✔
3868
        for (let comment of util.getLeadingComments(this) ?? []) {
1!
UNCOV
3869
            result.push(
×
3870
                comment.text,
3871
                state.newline,
3872
                state.indent()
3873
            );
3874
        }
3875
        for (let annotation of this.annotations ?? []) {
1!
UNCOV
3876
            result.push(
×
3877
                ...annotation.getTypedef(state),
3878
                state.newline,
3879
                state.indent()
3880
            );
3881
        }
3882
        result.push(
1✔
3883
            this.tokens.enum?.text ?? 'enum',
6!
3884
            ' ',
3885
            this.tokens.name.text
3886
        );
3887
        result.push(state.newline);
1✔
3888
        state.blockDepth++;
1✔
3889
        for (const member of this.body) {
1✔
3890
            if (isTypedefProvider(member)) {
1!
3891
                result.push(
1✔
3892
                    state.indent(),
3893
                    ...member.getTypedef(state),
3894
                    state.newline
3895
                );
3896
            }
3897
        }
3898
        state.blockDepth--;
1✔
3899
        result.push(
1✔
3900
            state.indent(),
3901
            this.tokens.endEnum?.text ?? 'end enum'
6!
3902
        );
3903
        return result;
1✔
3904
    }
3905

3906
    walk(visitor: WalkVisitor, options: WalkOptions) {
3907
        if (options.walkMode & InternalWalkMode.walkStatements) {
1,384!
3908
            walkArray(this.body, visitor, options, this);
1,384✔
3909

3910
        }
3911
    }
3912

3913
    getType(options: GetTypeOptions) {
3914
        const members = this.getMembers();
171✔
3915

3916
        const resultType = new EnumType(
171✔
3917
            this.fullName,
3918
            members[0]?.getType(options).underlyingType
513✔
3919
        );
3920
        resultType.pushMemberProvider(() => this.getSymbolTable());
702✔
3921
        for (const statement of members) {
171✔
3922
            const memberType = statement.getType({ ...options, typeChain: undefined });
334✔
3923
            memberType.parentEnumType = resultType;
334✔
3924
            resultType.addMember(statement?.tokens?.name?.text, { definingNode: statement }, memberType, SymbolTypeFlag.runtime);
334!
3925
        }
3926
        return resultType;
171✔
3927
    }
3928

3929
    public clone() {
3930
        return this.finalizeClone(
6✔
3931
            new EnumStatement({
3932
                enum: util.cloneToken(this.tokens.enum),
3933
                name: util.cloneToken(this.tokens.name),
3934
                endEnum: util.cloneToken(this.tokens.endEnum),
3935
                body: this.body?.map(x => x?.clone())
4✔
3936
            }),
3937
            ['body']
3938
        );
3939
    }
3940
}
3941

3942
export class EnumMemberStatement extends Statement implements TypedefProvider {
1✔
3943
    public constructor(options: {
3944
        name: Identifier;
3945
        equals?: Token;
3946
        value?: Expression;
3947
    }) {
3948
        super();
391✔
3949
        this.tokens = {
391✔
3950
            name: options.name,
3951
            equals: options.equals
3952
        };
3953
        this.value = options.value;
391✔
3954
    }
3955

3956
    public readonly tokens: {
3957
        readonly name: Identifier;
3958
        readonly equals?: Token;
3959
    };
3960
    public readonly value?: Expression;
3961

3962
    public readonly kind = AstNodeKind.EnumMemberStatement;
391✔
3963

3964
    public get location() {
3965
        return util.createBoundingLocation(
646✔
3966
            this.tokens.name,
3967
            this.tokens.equals,
3968
            this.value
3969
        );
3970
    }
3971

3972
    /**
3973
     * The name of the member
3974
     */
3975
    public get name() {
3976
        return this.tokens.name.text;
549✔
3977
    }
3978

3979
    public get leadingTrivia(): Token[] {
3980
        return this.tokens.name.leadingTrivia;
1,573✔
3981
    }
3982

3983
    /**
3984
     * Get the value of this enum. Requires that `.parent` is set
3985
     */
3986
    public getValue() {
3987
        if (isEnumStatement(this.parent)) {
9!
3988
            return (this.parent as EnumStatement)?.getMemberValue(this.name);
9!
3989
        }
UNCOV
3990
        return undefined;
×
3991
    }
3992

3993

3994
    public transpile(state: BrsTranspileState): TranspileResult {
UNCOV
3995
        return [];
×
3996
    }
3997

3998
    getTypedef(state: BrsTranspileState): TranspileResult {
3999
        const result: TranspileResult = [];
1✔
4000
        for (let comment of util.getLeadingComments(this) ?? []) {
1!
UNCOV
4001
            result.push(
×
4002
                comment.text,
4003
                state.newline,
4004
                state.indent()
4005
            );
4006
        }
4007
        result.push(this.tokens.name.text);
1✔
4008
        if (this.tokens.equals) {
1!
UNCOV
4009
            result.push(' ', this.tokens.equals.text, ' ');
×
UNCOV
4010
            if (this.value) {
×
UNCOV
4011
                result.push(
×
4012
                    ...this.value.transpile(state)
4013
                );
4014
            }
4015
        }
4016
        return result;
1✔
4017
    }
4018

4019
    walk(visitor: WalkVisitor, options: WalkOptions) {
4020
        if (this.value && options.walkMode & InternalWalkMode.walkExpressions) {
3,214✔
4021
            walk(this, 'value', visitor, options);
1,585✔
4022
        }
4023
    }
4024

4025
    getType(options: GetTypeOptions) {
4026
        return new EnumMemberType(
502✔
4027
            (this.parent as EnumStatement)?.fullName,
1,506!
4028
            this.tokens?.name?.text,
3,012!
4029
            this.value?.getType(options)
1,506✔
4030
        );
4031
    }
4032

4033
    public clone() {
4034
        return this.finalizeClone(
3✔
4035
            new EnumMemberStatement({
4036
                name: util.cloneToken(this.tokens.name),
4037
                equals: util.cloneToken(this.tokens.equals),
4038
                value: this.value?.clone()
9✔
4039
            }),
4040
            ['value']
4041
        );
4042
    }
4043
}
4044

4045
export class ConstStatement extends Statement implements TypedefProvider {
1✔
4046
    public constructor(options: {
4047
        const?: Token;
4048
        name: Identifier;
4049
        equals?: Token;
4050
        value: Expression;
4051
    }) {
4052
        super();
228✔
4053
        this.tokens = {
228✔
4054
            const: options.const,
4055
            name: options.name,
4056
            equals: options.equals
4057
        };
4058
        this.value = options.value;
228✔
4059
        this.location = util.createBoundingLocation(this.tokens.const, this.tokens.name, this.tokens.equals, this.value);
228✔
4060
    }
4061

4062
    public readonly tokens: {
4063
        readonly const: Token;
4064
        readonly name: Identifier;
4065
        readonly equals: Token;
4066
    };
4067
    public readonly value: Expression;
4068

4069
    public readonly kind = AstNodeKind.ConstStatement;
228✔
4070

4071
    public readonly location: Location | undefined;
4072

4073
    public get name() {
UNCOV
4074
        return this.tokens.name.text;
×
4075
    }
4076

4077
    public get leadingTrivia(): Token[] {
4078
        return this.tokens.const?.leadingTrivia;
701!
4079
    }
4080

4081
    /**
4082
     * The name of the statement WITH its leading namespace (if applicable)
4083
     */
4084
    public get fullName() {
4085
        const name = this.tokens.name?.text;
1,124!
4086
        if (name) {
1,124!
4087
            const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
1,124✔
4088
            if (namespace) {
1,124✔
4089
                let namespaceName = namespace.getName(ParseMode.BrighterScript);
931✔
4090
                return `${namespaceName}.${name}`;
931✔
4091
            } else {
4092
                return name;
193✔
4093
            }
4094
        } else {
4095
            //return undefined which will allow outside callers to know that this doesn't have a name
UNCOV
4096
            return undefined;
×
4097
        }
4098
    }
4099

4100
    public transpile(state: BrsTranspileState): TranspileResult {
4101
        //const declarations don't exist at runtime, so just transpile comments and trivia
4102
        return [
51✔
4103
            state.transpileAnnotations(this),
4104
            state.transpileLeadingComments(this.tokens.const)
4105
        ];
4106
    }
4107

4108
    getTypedef(state: BrsTranspileState): TranspileResult {
4109
        const result: TranspileResult = [];
3✔
4110
        for (let comment of util.getLeadingComments(this) ?? []) {
3!
UNCOV
4111
            result.push(
×
4112
                comment.text,
4113
                state.newline,
4114
                state.indent()
4115
            );
4116
        }
4117
        result.push(
3✔
4118
            this.tokens.const ? state.tokenToSourceNode(this.tokens.const) : 'const',
3!
4119
            ' ',
4120
            state.tokenToSourceNode(this.tokens.name),
4121
            ' ',
4122
            this.tokens.equals ? state.tokenToSourceNode(this.tokens.equals) : '=',
3!
4123
            ' ',
4124
            ...this.value.transpile(state)
4125
        );
4126
        return result;
3✔
4127
    }
4128

4129
    walk(visitor: WalkVisitor, options: WalkOptions) {
4130
        if (this.value && options.walkMode & InternalWalkMode.walkExpressions) {
1,686✔
4131
            walk(this, 'value', visitor, options);
1,336✔
4132
        }
4133
    }
4134

4135
    getType(options: GetTypeOptions) {
4136
        return this.value.getType(options);
217✔
4137
    }
4138

4139
    public clone() {
4140
        return this.finalizeClone(
3✔
4141
            new ConstStatement({
4142
                const: util.cloneToken(this.tokens.const),
4143
                name: util.cloneToken(this.tokens.name),
4144
                equals: util.cloneToken(this.tokens.equals),
4145
                value: this.value?.clone()
9✔
4146
            }),
4147
            ['value']
4148
        );
4149
    }
4150
}
4151

4152
export class ContinueStatement extends Statement {
1✔
4153
    constructor(options: {
4154
        continue?: Token;
4155
        loopType: Token;
4156
    }
4157
    ) {
4158
        super();
13✔
4159
        this.tokens = {
13✔
4160
            continue: options.continue,
4161
            loopType: options.loopType
4162
        };
4163
        this.location = util.createBoundingLocation(
13✔
4164
            this.tokens.continue,
4165
            this.tokens.loopType
4166
        );
4167
    }
4168

4169
    public readonly tokens: {
4170
        readonly continue?: Token;
4171
        readonly loopType: Token;
4172
    };
4173

4174
    public readonly kind = AstNodeKind.ContinueStatement;
13✔
4175

4176
    public readonly location: Location | undefined;
4177

4178
    transpile(state: BrsTranspileState) {
4179
        return [
3✔
4180
            state.sourceNode(this.tokens.continue, this.tokens.continue?.text ?? 'continue'),
18!
4181
            this.tokens.loopType?.leadingWhitespace ?? ' ',
18✔
4182
            state.sourceNode(this.tokens.continue, this.tokens.loopType?.text)
9✔
4183
        ];
4184
    }
4185

4186
    walk(visitor: WalkVisitor, options: WalkOptions) {
4187
        //nothing to walk
4188
    }
4189

4190
    public get leadingTrivia(): Token[] {
4191
        return this.tokens.continue?.leadingTrivia ?? [];
86!
4192
    }
4193

4194
    public clone() {
4195
        return this.finalizeClone(
1✔
4196
            new ContinueStatement({
4197
                continue: util.cloneToken(this.tokens.continue),
4198
                loopType: util.cloneToken(this.tokens.loopType)
4199
            })
4200
        );
4201
    }
4202
}
4203

4204
export class TypecastStatement extends Statement {
1✔
4205
    constructor(options: {
4206
        typecast?: Token;
4207
        typecastExpression: TypecastExpression;
4208
    }
4209
    ) {
4210
        super();
33✔
4211
        this.tokens = {
33✔
4212
            typecast: options.typecast
4213
        };
4214
        this.typecastExpression = options.typecastExpression;
33✔
4215
        this.location = util.createBoundingLocation(
33✔
4216
            this.tokens.typecast,
4217
            this.typecastExpression
4218
        );
4219
    }
4220

4221
    public readonly tokens: {
4222
        readonly typecast?: Token;
4223
    };
4224

4225
    public readonly typecastExpression: TypecastExpression;
4226

4227
    public readonly kind = AstNodeKind.TypecastStatement;
33✔
4228

4229
    public readonly location: Location;
4230

4231
    transpile(state: BrsTranspileState) {
4232
        //the typecast statement is a comment just for debugging purposes
4233
        return [
2✔
4234
            state.transpileToken(this.tokens.typecast, 'typecast', true),
4235
            ' ',
4236
            this.typecastExpression.obj.transpile(state),
4237
            ' ',
4238
            state.transpileToken(this.typecastExpression.tokens.as, 'as'),
4239
            ' ',
4240
            this.typecastExpression.typeExpression.getName(ParseMode.BrighterScript)
4241
        ];
4242
    }
4243

4244
    walk(visitor: WalkVisitor, options: WalkOptions) {
4245
        if (options.walkMode & InternalWalkMode.walkExpressions) {
203✔
4246
            walk(this, 'typecastExpression', visitor, options);
184✔
4247
        }
4248
    }
4249

4250
    get leadingTrivia(): Token[] {
4251
        return this.tokens.typecast?.leadingTrivia ?? [];
167!
4252
    }
4253

4254
    getType(options: GetTypeOptions): BscType {
4255
        return this.typecastExpression.getType(options);
26✔
4256
    }
4257

4258
    public clone() {
4259
        return this.finalizeClone(
1✔
4260
            new TypecastStatement({
4261
                typecast: util.cloneToken(this.tokens.typecast),
4262
                typecastExpression: this.typecastExpression?.clone()
3!
4263
            }),
4264
            ['typecastExpression']
4265
        );
4266
    }
4267
}
4268

4269
export class ConditionalCompileErrorStatement extends Statement {
1✔
4270
    constructor(options: {
4271
        hashError?: Token;
4272
        message: Token;
4273
    }) {
4274
        super();
11✔
4275
        this.tokens = {
11✔
4276
            hashError: options.hashError,
4277
            message: options.message
4278
        };
4279
        this.location = util.createBoundingLocation(util.createBoundingLocationFromTokens(this.tokens));
11✔
4280
    }
4281

4282
    public readonly tokens: {
4283
        readonly hashError?: Token;
4284
        readonly message: Token;
4285
    };
4286

4287

4288
    public readonly kind = AstNodeKind.ConditionalCompileErrorStatement;
11✔
4289

4290
    public readonly location: Location | undefined;
4291

4292
    transpile(state: BrsTranspileState) {
4293
        return [
3✔
4294
            state.transpileToken(this.tokens.hashError, '#error'),
4295
            ' ',
4296
            state.transpileToken(this.tokens.message)
4297

4298
        ];
4299
    }
4300

4301
    walk(visitor: WalkVisitor, options: WalkOptions) {
4302
        // nothing to walk
4303
    }
4304

4305
    get leadingTrivia(): Token[] {
4306
        return this.tokens.hashError.leadingTrivia ?? [];
12!
4307
    }
4308

4309
    public clone() {
4310
        return this.finalizeClone(
1✔
4311
            new ConditionalCompileErrorStatement({
4312
                hashError: util.cloneToken(this.tokens.hashError),
4313
                message: util.cloneToken(this.tokens.message)
4314
            })
4315
        );
4316
    }
4317
}
4318

4319
export class AliasStatement extends Statement {
1✔
4320
    constructor(options: {
4321
        alias?: Token;
4322
        name: Token;
4323
        equals?: Token;
4324
        value: VariableExpression | DottedGetExpression;
4325
    }
4326
    ) {
4327
        super();
34✔
4328
        this.tokens = {
34✔
4329
            alias: options.alias,
4330
            name: options.name,
4331
            equals: options.equals
4332
        };
4333
        this.value = options.value;
34✔
4334
        this.location = util.createBoundingLocation(
34✔
4335
            this.tokens.alias,
4336
            this.tokens.name,
4337
            this.tokens.equals,
4338
            this.value
4339
        );
4340
    }
4341

4342
    public readonly tokens: {
4343
        readonly alias?: Token;
4344
        readonly name: Token;
4345
        readonly equals?: Token;
4346
    };
4347

4348
    public readonly value: Expression;
4349

4350
    public readonly kind = AstNodeKind.AliasStatement;
34✔
4351

4352
    public readonly location: Location;
4353

4354
    transpile(state: BrsTranspileState) {
4355
        //transpile to a comment just for debugging purposes
4356
        return [
12✔
4357
            state.transpileToken(this.tokens.alias, 'alias', true),
4358
            ' ',
4359
            state.transpileToken(this.tokens.name),
4360
            ' ',
4361
            state.transpileToken(this.tokens.equals, '='),
4362
            ' ',
4363
            this.value.transpile(state)
4364
        ];
4365
    }
4366

4367
    walk(visitor: WalkVisitor, options: WalkOptions) {
4368
        if (options.walkMode & InternalWalkMode.walkExpressions) {
258✔
4369
            walk(this, 'value', visitor, options);
206✔
4370
        }
4371
    }
4372

4373
    get leadingTrivia(): Token[] {
4374
        return this.tokens.alias?.leadingTrivia ?? [];
121!
4375
    }
4376

4377
    getType(options: GetTypeOptions): BscType {
4378
        return this.value.getType(options);
1✔
4379
    }
4380

4381
    public clone() {
4382
        return this.finalizeClone(
1✔
4383
            new AliasStatement({
4384
                alias: util.cloneToken(this.tokens.alias),
4385
                name: util.cloneToken(this.tokens.name),
4386
                equals: util.cloneToken(this.tokens.equals),
4387
                value: this.value?.clone()
3!
4388
            }),
4389
            ['value']
4390
        );
4391
    }
4392
}
4393

4394
export class ConditionalCompileStatement extends Statement {
1✔
4395
    constructor(options: {
4396
        hashIf?: Token;
4397
        not?: Token;
4398
        condition: Token;
4399
        hashElse?: Token;
4400
        hashEndIf?: Token;
4401
        thenBranch: Block;
4402
        elseBranch?: ConditionalCompileStatement | Block;
4403
    }) {
4404
        super();
58✔
4405
        this.thenBranch = options.thenBranch;
58✔
4406
        this.elseBranch = options.elseBranch;
58✔
4407

4408
        this.tokens = {
58✔
4409
            hashIf: options.hashIf,
4410
            not: options.not,
4411
            condition: options.condition,
4412
            hashElse: options.hashElse,
4413
            hashEndIf: options.hashEndIf
4414
        };
4415

4416
        this.location = util.createBoundingLocation(
58✔
4417
            util.createBoundingLocationFromTokens(this.tokens),
4418
            this.thenBranch,
4419
            this.elseBranch
4420
        );
4421
    }
4422

4423
    readonly tokens: {
4424
        readonly hashIf?: Token;
4425
        readonly not?: Token;
4426
        readonly condition: Token;
4427
        readonly hashElse?: Token;
4428
        readonly hashEndIf?: Token;
4429
    };
4430
    public readonly thenBranch: Block;
4431
    public readonly elseBranch?: ConditionalCompileStatement | Block;
4432

4433
    public readonly kind = AstNodeKind.ConditionalCompileStatement;
58✔
4434

4435
    public readonly location: Location | undefined;
4436

4437
    transpile(state: BrsTranspileState) {
4438
        let results = [] as TranspileResult;
6✔
4439
        //if   (already indented by block)
4440
        if (!state.conditionalCompileStatement) {
6✔
4441
            // only transpile the #if in the case when we're not in a conditionalCompileStatement already
4442
            results.push(state.transpileToken(this.tokens.hashIf ?? createToken(TokenKind.HashIf)));
3!
4443
        }
4444

4445
        results.push(' ');
6✔
4446
        //conditions
4447
        if (this.tokens.not) {
6✔
4448
            results.push('not');
2✔
4449
            results.push(' ');
2✔
4450
        }
4451
        results.push(state.transpileToken(this.tokens.condition));
6✔
4452
        state.lineage.unshift(this);
6✔
4453

4454
        //if statement body
4455
        let thenNodes = this.thenBranch.transpile(state);
6✔
4456
        state.lineage.shift();
6✔
4457
        if (thenNodes.length > 0) {
6!
4458
            results.push(thenNodes);
6✔
4459
        }
4460
        //else branch
4461
        if (this.elseBranch) {
6!
4462
            const elseIsCC = isConditionalCompileStatement(this.elseBranch);
6✔
4463
            const endBlockToken = elseIsCC ? (this.elseBranch as ConditionalCompileStatement).tokens.hashIf ?? createToken(TokenKind.HashElseIf) : this.tokens.hashElse;
6!
4464
            //else
4465

4466
            results.push(...state.transpileEndBlockToken(this.thenBranch, endBlockToken, createToken(TokenKind.HashElse).text));
6✔
4467

4468
            if (elseIsCC) {
6✔
4469
                //chained else if
4470
                state.lineage.unshift(this.elseBranch);
3✔
4471

4472
                // transpile following #if with knowledge of current
4473
                const existingCCStmt = state.conditionalCompileStatement;
3✔
4474
                state.conditionalCompileStatement = this;
3✔
4475
                let body = this.elseBranch.transpile(state);
3✔
4476
                state.conditionalCompileStatement = existingCCStmt;
3✔
4477

4478
                state.lineage.shift();
3✔
4479

4480
                if (body.length > 0) {
3!
4481
                    //zero or more spaces between the `else` and the `if`
4482
                    results.push(...body);
3✔
4483

4484
                    // stop here because chained if will transpile the rest
4485
                    return results;
3✔
4486
                } else {
UNCOV
4487
                    results.push('\n');
×
4488
                }
4489

4490
            } else {
4491
                //else body
4492
                state.lineage.unshift(this.tokens.hashElse!);
3✔
4493
                let body = this.elseBranch.transpile(state);
3✔
4494
                state.lineage.shift();
3✔
4495

4496
                if (body.length > 0) {
3!
4497
                    results.push(...body);
3✔
4498
                }
4499
            }
4500
        }
4501

4502
        //end if
4503
        results.push(...state.transpileEndBlockToken(this.elseBranch ?? this.thenBranch, this.tokens.hashEndIf, '#end if'));
3!
4504

4505
        return results;
3✔
4506
    }
4507

4508
    walk(visitor: WalkVisitor, options: WalkOptions) {
4509
        if (options.walkMode & InternalWalkMode.walkStatements) {
211!
4510
            const bsConsts = options.bsConsts ?? this.getBsConsts();
211✔
4511
            let conditionTrue = bsConsts?.get(this.tokens.condition.text.toLowerCase());
211✔
4512
            if (this.tokens.not) {
211✔
4513
                // flips the boolean value
4514
                conditionTrue = !conditionTrue;
25✔
4515
            }
4516
            const walkFalseBlocks = options.walkMode & InternalWalkMode.visitFalseConditionalCompilationBlocks;
211✔
4517
            if (conditionTrue || walkFalseBlocks) {
211✔
4518
                walk(this, 'thenBranch', visitor, options);
170✔
4519
            }
4520
            if (this.elseBranch && (!conditionTrue || walkFalseBlocks)) {
211✔
4521
                walk(this, 'elseBranch', visitor, options);
71✔
4522
            }
4523
        }
4524
    }
4525

4526
    get leadingTrivia(): Token[] {
4527
        return this.tokens.hashIf?.leadingTrivia ?? [];
175!
4528
    }
4529

4530
    public clone() {
4531
        return this.finalizeClone(
1✔
4532
            new ConditionalCompileStatement({
4533
                hashIf: util.cloneToken(this.tokens.hashIf),
4534
                not: util.cloneToken(this.tokens.not),
4535
                condition: util.cloneToken(this.tokens.condition),
4536
                hashElse: util.cloneToken(this.tokens.hashElse),
4537
                hashEndIf: util.cloneToken(this.tokens.hashEndIf),
4538
                thenBranch: this.thenBranch?.clone(),
3!
4539
                elseBranch: this.elseBranch?.clone()
3!
4540
            }),
4541
            ['thenBranch', 'elseBranch']
4542
        );
4543
    }
4544

4545
    public getBranchStatementIndex(stmt: Statement) {
UNCOV
4546
        if (this.thenBranch === stmt) {
×
UNCOV
4547
            return 0;
×
UNCOV
4548
        } else if (this.elseBranch === stmt) {
×
UNCOV
4549
            return 1;
×
4550
        }
UNCOV
4551
        return -1;
×
4552
    }
4553
}
4554

4555

4556
export class ConditionalCompileConstStatement extends Statement {
1✔
4557
    constructor(options: {
4558
        hashConst?: Token;
4559
        assignment: AssignmentStatement;
4560
    }) {
4561
        super();
19✔
4562
        this.tokens = {
19✔
4563
            hashConst: options.hashConst
4564
        };
4565
        this.assignment = options.assignment;
19✔
4566
        this.location = util.createBoundingLocation(util.createBoundingLocationFromTokens(this.tokens), this.assignment);
19✔
4567
    }
4568

4569
    public readonly tokens: {
4570
        readonly hashConst?: Token;
4571
    };
4572

4573
    public readonly assignment: AssignmentStatement;
4574

4575
    public readonly kind = AstNodeKind.ConditionalCompileConstStatement;
19✔
4576

4577
    public readonly location: Location | undefined;
4578

4579
    transpile(state: BrsTranspileState) {
4580
        return [
3✔
4581
            state.transpileToken(this.tokens.hashConst, '#const'),
4582
            ' ',
4583
            state.transpileToken(this.assignment.tokens.name),
4584
            ' ',
4585
            state.transpileToken(this.assignment.tokens.equals, '='),
4586
            ' ',
4587
            ...this.assignment.value.transpile(state)
4588
        ];
4589

4590
    }
4591

4592
    walk(visitor: WalkVisitor, options: WalkOptions) {
4593
        // nothing to walk
4594
    }
4595

4596

4597
    get leadingTrivia(): Token[] {
4598
        return this.tokens.hashConst.leadingTrivia ?? [];
78!
4599
    }
4600

4601
    public clone() {
4602
        return this.finalizeClone(
1✔
4603
            new ConditionalCompileConstStatement({
4604
                hashConst: util.cloneToken(this.tokens.hashConst),
4605
                assignment: this.assignment?.clone()
3!
4606
            }),
4607
            ['assignment']
4608
        );
4609
    }
4610
}
4611

4612

4613
export class TypeStatement extends Statement {
1✔
4614
    constructor(options: {
4615
        type?: Token;
4616
        name: Token;
4617
        equals?: Token;
4618
        value: TypeExpression;
4619
    }
4620
    ) {
4621
        super();
53✔
4622
        this.tokens = {
53✔
4623
            type: options.type,
4624
            name: options.name,
4625
            equals: options.equals
4626
        };
4627
        this.value = options.value;
53✔
4628
        this.location = util.createBoundingLocation(
53✔
4629
            this.tokens.type,
4630
            this.tokens.name,
4631
            this.tokens.equals,
4632
            this.value
4633
        );
4634
    }
4635

4636
    public readonly tokens: {
4637
        readonly type?: Token;
4638
        readonly name: Token;
4639
        readonly equals?: Token;
4640
    };
4641

4642
    public readonly value: TypeExpression;
4643

4644
    public readonly kind = AstNodeKind.TypeStatement;
53✔
4645

4646
    public readonly location: Location;
4647

4648
    transpile(state: BrsTranspileState) {
4649
        //type statements have no runtime representation, so they're stripped entirely
4650
        return [];
2✔
4651
    }
4652

4653
    walk(visitor: WalkVisitor, options: WalkOptions) {
4654
        if (options.walkMode & InternalWalkMode.walkExpressions) {
339✔
4655
            walk(this, 'value', visitor, options);
289✔
4656
        }
4657
    }
4658

4659
    get leadingTrivia(): Token[] {
4660
        return this.tokens.type?.leadingTrivia ?? [];
143!
4661
    }
4662

4663
    getType(options: GetTypeOptions): BscType {
4664
        return this.value.getType(options);
45✔
4665
    }
4666

4667
    public clone() {
UNCOV
4668
        return this.finalizeClone(
×
4669
            new TypeStatement({
4670
                type: util.cloneToken(this.tokens.type),
4671
                name: util.cloneToken(this.tokens.name),
4672
                equals: util.cloneToken(this.tokens.equals),
4673
                value: this.value?.clone()
×
4674
            }),
4675
            ['value']
4676
        );
4677
    }
4678
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc