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

rokucommunity / brighterscript / #14231

23 Apr 2025 04:25PM UTC coverage: 87.082% (-0.006%) from 87.088%
#14231

push

web-flow
Merge 965d70740 into 99ec33ad4

13478 of 16355 branches covered (82.41%)

Branch coverage included in aggregate %.

164 of 181 new or added lines in 10 files covered. (90.61%)

116 existing lines in 9 files now uncovered.

14525 of 15802 relevant lines covered (91.92%)

21149.27 hits per line

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

87.75
/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, 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
export class EmptyStatement extends Statement {
1✔
32
    constructor(options?: { range?: Location }
33
    ) {
34
        super();
6✔
35
        this.location = undefined;
6✔
36
    }
37
    /**
38
     * Create a negative range to indicate this is an interpolated location
39
     */
40
    public readonly location?: Location;
41

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

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

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

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

71
    public readonly statements: Statement[] = [];
8,534✔
72
    public readonly kind = AstNodeKind.Body;
8,534✔
73

74
    public readonly symbolTable = new SymbolTable('Body', () => this.parent?.getSymbolTable());
31,769✔
75

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

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

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

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

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

117
            result.push(...statement.transpile(state));
1,658✔
118
        }
119
        return result;
761✔
120
    }
121

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

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

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

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

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

178
    public readonly value: Expression;
179

180
    public readonly typeExpression?: TypeExpression;
181

182
    public readonly kind = AstNodeKind.AssignmentStatement;
1,666✔
183

184
    public readonly location: Location | undefined;
185

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

196
    walk(visitor: WalkVisitor, options: WalkOptions) {
197
        if (options.walkMode & InternalWalkMode.walkExpressions) {
7,271✔
198
            walk(this, 'typeExpression', visitor, options);
7,132✔
199
            walk(this, 'value', visitor, options);
7,132✔
200
        }
201
    }
202

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

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

215
    get leadingTrivia(): Token[] {
216
        return this.tokens.name.leadingTrivia;
8,745✔
217
    }
218

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

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

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

253
    public readonly item: Expression;
254

255
    public readonly value: Expression;
256

257
    public readonly kind = AstNodeKind.AugmentedAssignmentStatement;
105✔
258

259
    public readonly location: Location | undefined;
260

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

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

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

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

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

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

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

305
export class Block extends Statement {
1✔
306
    constructor(options: {
307
        statements: Statement[];
308
    }) {
309
        super();
7,367✔
310
        this.statements = options.statements;
7,367✔
311
        this.symbolTable = new SymbolTable('Block', () => this.parent.getSymbolTable());
42,863✔
312
    }
313

314
    public readonly statements: Statement[];
315

316
    public readonly kind = AstNodeKind.Block;
7,367✔
317

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

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

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

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

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

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

461
    public get leadingTrivia(): Token[] {
462
        return this.statements[0]?.leadingTrivia ?? [];
11,520✔
463
    }
464

465
    walk(visitor: WalkVisitor, options: WalkOptions) {
466
        if (options.walkMode & InternalWalkMode.walkStatements) {
33,061✔
467
            walkArray(this.statements, visitor, options, this);
33,055✔
468
        }
469
    }
470

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

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

492
    public readonly location: Location | undefined;
493

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

501
    walk(visitor: WalkVisitor, options: WalkOptions) {
502
        if (options.walkMode & InternalWalkMode.walkExpressions) {
3,669✔
503
            walk(this, 'expression', visitor, options);
3,640✔
504
        }
505
    }
506

507
    get leadingTrivia(): Token[] {
508
        return this.expression.leadingTrivia;
3,817✔
509
    }
510

511
    public clone() {
512
        return this.finalizeClone(
14✔
513
            new ExpressionStatement({
514
                expression: this.expression?.clone()
42✔
515
            }),
516
            ['expression']
517
        );
518
    }
519
}
520

521
export class ExitStatement extends Statement {
1✔
522
    constructor(options?: {
523
        exit?: Token;
524
        loopType: Token;
525
    }) {
526
        super();
26✔
527
        this.tokens = {
26✔
528
            exit: options?.exit,
78!
529
            loopType: options.loopType
530
        };
531
        this.location = util.createBoundingLocation(
26✔
532
            this.tokens.exit,
533
            this.tokens.loopType
534
        );
535
    }
536

537
    public readonly tokens: {
538
        readonly exit: Token;
539
        readonly loopType?: Token;
540
    };
541

542
    public readonly kind = AstNodeKind.ExitStatement;
26✔
543

544
    public readonly location?: Location;
545

546
    transpile(state: BrsTranspileState) {
547
        return [
11✔
548
            state.transpileToken(this.tokens.exit, 'exit'),
549
            this.tokens.loopType?.leadingWhitespace ?? ' ',
66!
550
            state.transpileToken(this.tokens.loopType)
551
        ];
552
    }
553

554
    walk(visitor: WalkVisitor, options: WalkOptions) {
555
        //nothing to walk
556
    }
557

558
    get leadingTrivia(): Token[] {
559
        return this.tokens.exit?.leadingTrivia;
124!
560
    }
561

562
    public clone() {
563
        return this.finalizeClone(
2✔
564
            new ExitStatement({
565
                loopType: util.cloneToken(this.tokens.loopType),
566
                exit: util.cloneToken(this.tokens.exit)
567
            })
568
        );
569
    }
570
}
571

572
export class FunctionStatement extends Statement implements TypedefProvider {
1✔
573
    constructor(options: {
574
        name: Identifier;
575
        func: FunctionExpression;
576
    }) {
577
        super();
4,389✔
578
        this.tokens = {
4,389✔
579
            name: options.name
580
        };
581
        this.func = options.func;
4,389✔
582
        if (this.func) {
4,389✔
583
            this.func.symbolTable.name += `: '${this.tokens.name?.text}'`;
4,387!
584
        }
585

586
        this.location = this.func?.location;
4,389✔
587
    }
588

589
    public readonly tokens: {
590
        readonly name: Identifier;
591
    };
592
    public readonly func: FunctionExpression;
593

594
    public readonly kind = AstNodeKind.FunctionStatement as AstNodeKind;
4,389✔
595

596
    public readonly location: Location | undefined;
597

598
    /**
599
     * Get the name of this expression based on the parse mode
600
     */
601
    public getName(parseMode: ParseMode) {
602
        const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
13,860✔
603
        if (namespace) {
13,860✔
604
            let delimiter = parseMode === ParseMode.BrighterScript ? '.' : '_';
3,215✔
605
            let namespaceName = namespace.getName(parseMode);
3,215✔
606
            return namespaceName + delimiter + this.tokens.name?.text;
3,215!
607
        } else {
608
            return this.tokens.name.text;
10,645✔
609
        }
610
    }
611

612
    public get leadingTrivia(): Token[] {
613
        return this.func.leadingTrivia;
18,267✔
614
    }
615

616
    transpile(state: BrsTranspileState): TranspileResult {
617
        //create a fake token using the full transpiled name
618
        let nameToken = {
1,439✔
619
            ...this.tokens.name,
620
            text: this.getName(ParseMode.BrightScript)
621
        };
622

623
        return [
1,439✔
624
            ...state.transpileAnnotations(this),
625
            ...this.func.transpile(state, nameToken)
626
        ];
627
    }
628

629
    getTypedef(state: BrsTranspileState) {
630
        let result: TranspileResult = [];
39✔
631
        for (let comment of util.getLeadingComments(this) ?? []) {
39!
632
            result.push(
154✔
633
                comment.text,
634
                state.newline,
635
                state.indent()
636
            );
637
        }
638
        for (let annotation of this.annotations ?? []) {
39✔
639
            result.push(
2✔
640
                ...annotation.getTypedef(state),
641
                state.newline,
642
                state.indent()
643
            );
644
        }
645

646
        result.push(
39✔
647
            ...this.func.getTypedef(state)
648
        );
649
        return result;
39✔
650
    }
651

652
    walk(visitor: WalkVisitor, options: WalkOptions) {
653
        if (options.walkMode & InternalWalkMode.walkExpressions) {
19,331✔
654
            walk(this, 'func', visitor, options);
17,313✔
655
        }
656
    }
657

658
    getType(options: GetTypeOptions) {
659
        const funcExprType = this.func.getType(options);
3,799✔
660
        funcExprType.setName(this.getName(ParseMode.BrighterScript));
3,799✔
661
        return funcExprType;
3,799✔
662
    }
663

664
    public clone() {
665
        return this.finalizeClone(
107✔
666
            new FunctionStatement({
667
                func: this.func?.clone(),
321✔
668
                name: util.cloneToken(this.tokens.name)
669
            }),
670
            ['func']
671
        );
672
    }
673
}
674

675
export class IfStatement extends Statement {
1✔
676
    constructor(options: {
677
        if?: Token;
678
        then?: Token;
679
        else?: Token;
680
        endIf?: Token;
681

682
        condition: Expression;
683
        thenBranch: Block;
684
        elseBranch?: IfStatement | Block;
685
    }) {
686
        super();
2,234✔
687
        this.condition = options.condition;
2,234✔
688
        this.thenBranch = options.thenBranch;
2,234✔
689
        this.elseBranch = options.elseBranch;
2,234✔
690

691
        this.tokens = {
2,234✔
692
            if: options.if,
693
            then: options.then,
694
            else: options.else,
695
            endIf: options.endIf
696
        };
697

698
        this.location = util.createBoundingLocation(
2,234✔
699
            util.createBoundingLocationFromTokens(this.tokens),
700
            this.condition,
701
            this.thenBranch,
702
            this.elseBranch
703
        );
704
    }
705

706
    readonly tokens: {
707
        readonly if?: Token;
708
        readonly then?: Token;
709
        readonly else?: Token;
710
        readonly endIf?: Token;
711
    };
712
    public readonly condition: Expression;
713
    public readonly thenBranch: Block;
714
    public readonly elseBranch?: IfStatement | Block;
715

716
    public readonly kind = AstNodeKind.IfStatement;
2,234✔
717

718
    public readonly location: Location | undefined;
719

720
    get isInline() {
721
        const allLeadingTrivia = [
12✔
722
            ...this.thenBranch.leadingTrivia,
723
            ...this.thenBranch.statements.map(s => s.leadingTrivia).flat(),
16✔
724
            ...(this.tokens.else?.leadingTrivia ?? []),
72✔
725
            ...(this.tokens.endIf?.leadingTrivia ?? [])
72✔
726
        ];
727

728
        const hasNewline = allLeadingTrivia.find(t => t.kind === TokenKind.Newline);
29✔
729
        return !hasNewline;
12✔
730
    }
731

732
    transpile(state: BrsTranspileState) {
733
        let results = [] as TranspileResult;
2,247✔
734
        //if   (already indented by block)
735
        results.push(state.transpileToken(this.tokens.if ?? createToken(TokenKind.If)));
2,247!
736
        results.push(' ');
2,247✔
737
        //conditions
738
        results.push(...this.condition.transpile(state));
2,247✔
739
        //then
740
        if (this.tokens.then) {
2,247✔
741
            results.push(' ');
1,874✔
742
            results.push(
1,874✔
743
                state.transpileToken(this.tokens.then, 'then')
744
            );
745
        }
746
        state.lineage.unshift(this);
2,247✔
747

748
        //if statement body
749
        let thenNodes = this.thenBranch.transpile(state);
2,247✔
750
        state.lineage.shift();
2,247✔
751
        if (thenNodes.length > 0) {
2,247✔
752
            results.push(thenNodes);
2,236✔
753
        }
754
        //else branch
755
        if (this.elseBranch) {
2,247✔
756
            //else
757
            results.push(...state.transpileEndBlockToken(this.thenBranch, this.tokens.else, 'else'));
1,866✔
758

759
            if (isIfStatement(this.elseBranch)) {
1,866✔
760
                //chained elseif
761
                state.lineage.unshift(this.elseBranch);
1,105✔
762
                let body = this.elseBranch.transpile(state);
1,105✔
763
                state.lineage.shift();
1,105✔
764

765
                if (body.length > 0) {
1,105!
766
                    //zero or more spaces between the `else` and the `if`
767
                    results.push(this.elseBranch.tokens.if.leadingWhitespace!);
1,105✔
768
                    results.push(...body);
1,105✔
769

770
                    // stop here because chained if will transpile the rest
771
                    return results;
1,105✔
772
                } else {
UNCOV
773
                    results.push('\n');
×
774
                }
775

776
            } else {
777
                //else body
778
                state.lineage.unshift(this.tokens.else!);
761✔
779
                let body = this.elseBranch.transpile(state);
761✔
780
                state.lineage.shift();
761✔
781

782
                if (body.length > 0) {
761✔
783
                    results.push(...body);
759✔
784
                }
785
            }
786
        }
787

788
        //end if
789
        results.push(...state.transpileEndBlockToken(this.elseBranch ?? this.thenBranch, this.tokens.endIf, 'end if'));
1,142✔
790

791
        return results;
1,142✔
792
    }
793

794
    walk(visitor: WalkVisitor, options: WalkOptions) {
795
        if (options.walkMode & InternalWalkMode.walkExpressions) {
9,382✔
796
            walk(this, 'condition', visitor, options);
9,365✔
797
        }
798
        if (options.walkMode & InternalWalkMode.walkStatements) {
9,382✔
799
            walk(this, 'thenBranch', visitor, options);
9,380✔
800
        }
801
        if (this.elseBranch && options.walkMode & InternalWalkMode.walkStatements) {
9,382✔
802
            walk(this, 'elseBranch', visitor, options);
7,385✔
803
        }
804
    }
805

806
    get leadingTrivia(): Token[] {
807
        return this.tokens.if?.leadingTrivia ?? [];
3,114!
808
    }
809

810
    get endTrivia(): Token[] {
811
        return this.tokens.endIf?.leadingTrivia ?? [];
1!
812
    }
813

814
    public clone() {
815
        return this.finalizeClone(
5✔
816
            new IfStatement({
817
                if: util.cloneToken(this.tokens.if),
818
                else: util.cloneToken(this.tokens.else),
819
                endIf: util.cloneToken(this.tokens.endIf),
820
                then: util.cloneToken(this.tokens.then),
821
                condition: this.condition?.clone(),
15✔
822
                thenBranch: this.thenBranch?.clone(),
15✔
823
                elseBranch: this.elseBranch?.clone()
15✔
824
            }),
825
            ['condition', 'thenBranch', 'elseBranch']
826
        );
827
    }
828

829
    public getBranchStatementIndex(stmt: Statement) {
830
        if (this.thenBranch === stmt) {
1,319!
NEW
831
            return 0;
×
832
        } else if (this.elseBranch === stmt) {
1,319!
833
            return 1;
1,319✔
834
        }
NEW
835
        return -1;
×
836
    }
837
}
838

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

855
    public readonly value: Expression;
856
    public readonly tokens: {
857
        readonly operator: Token;
858
    };
859

860
    public readonly kind = AstNodeKind.IncrementStatement;
29✔
861

862
    public readonly location: Location | undefined;
863

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

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

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

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

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

917
    public readonly tokens: {
918
        readonly print?: Token;
919
    };
920

921
    public readonly expressions: Array<Expression>;
922

923
    public readonly kind = AstNodeKind.PrintStatement;
1,344✔
924

925
    public readonly location: Location | undefined;
926

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

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

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

948
            result.push(
302✔
949
                ...expression.transpile(state)
950
            );
951
        }
952
        return result;
238✔
953
    }
954

955
    walk(visitor: WalkVisitor, options: WalkOptions) {
956
        if (options.walkMode & InternalWalkMode.walkExpressions) {
6,330✔
957
            walkArray(this.expressions, visitor, options, this);
6,258✔
958
        }
959
    }
960

961
    get leadingTrivia(): Token[] {
962
        return this.tokens.print?.leadingTrivia ?? [];
7,935✔
963
    }
964

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

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

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

1009
    public readonly kind = AstNodeKind.DimStatement;
46✔
1010

1011
    public readonly location: Location | undefined;
1012

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

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

1036
        }
1037
    }
1038

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

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

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

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

1082
    public readonly tokens: {
1083
        readonly goto?: Token;
1084
        readonly label: Token;
1085
    };
1086

1087
    public readonly kind = AstNodeKind.GotoStatement;
13✔
1088

1089
    public readonly location: Location | undefined;
1090

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

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

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

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

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

1138
    public readonly location: Location | undefined;
1139

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

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

1149
        ];
1150
    }
1151

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

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

1166
export class ReturnStatement extends Statement {
1✔
1167
    constructor(options?: {
1168
        return?: Token;
1169
        value?: Expression;
1170
    }) {
1171
        super();
3,451✔
1172
        this.tokens = {
3,451✔
1173
            return: options?.return
10,353!
1174
        };
1175
        this.value = options?.value;
3,451!
1176
        this.location = util.createBoundingLocation(
3,451✔
1177
            this.tokens.return,
1178
            this.value
1179
        );
1180
    }
1181

1182
    public readonly tokens: {
1183
        readonly return?: Token;
1184
    };
1185
    public readonly value?: Expression;
1186
    public readonly kind = AstNodeKind.ReturnStatement;
3,451✔
1187

1188
    public readonly location: Location | undefined;
1189

1190
    transpile(state: BrsTranspileState) {
1191
        let result = [] as TranspileResult;
3,314✔
1192
        result.push(
3,314✔
1193
            state.transpileToken(this.tokens.return, 'return')
1194
        );
1195
        if (this.value) {
3,314✔
1196
            result.push(' ');
3,310✔
1197
            result.push(...this.value.transpile(state));
3,310✔
1198
        }
1199
        return result;
3,314✔
1200
    }
1201

1202
    walk(visitor: WalkVisitor, options: WalkOptions) {
1203
        if (options.walkMode & InternalWalkMode.walkExpressions) {
15,195✔
1204
            walk(this, 'value', visitor, options);
15,172✔
1205
        }
1206
    }
1207

1208
    get leadingTrivia(): Token[] {
1209
        return this.tokens.return?.leadingTrivia ?? [];
10,403!
1210
    }
1211

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

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

1238
    public readonly location: Location;
1239

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

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

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

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

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

1275
    public readonly kind = AstNodeKind.StopStatement;
19✔
1276

1277
    public readonly location: Location;
1278

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

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

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

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

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

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

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

1344
    public readonly counterDeclaration: AssignmentStatement;
1345
    public readonly finalValue: Expression;
1346
    public readonly body: Block;
1347
    public readonly increment?: Expression;
1348

1349
    public readonly kind = AstNodeKind.ForStatement;
47✔
1350

1351
    public readonly location: Location | undefined;
1352

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

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

1389
        return result;
11✔
1390
    }
1391

1392
    walk(visitor: WalkVisitor, options: WalkOptions) {
1393
        if (options.walkMode & InternalWalkMode.walkStatements) {
152✔
1394
            walk(this, 'counterDeclaration', visitor, options);
151✔
1395
        }
1396
        if (options.walkMode & InternalWalkMode.walkExpressions) {
152✔
1397
            walk(this, 'finalValue', visitor, options);
148✔
1398
            walk(this, 'increment', visitor, options);
148✔
1399
        }
1400
        if (options.walkMode & InternalWalkMode.walkStatements) {
152✔
1401
            walk(this, 'body', visitor, options);
151✔
1402
        }
1403
    }
1404

1405
    get leadingTrivia(): Token[] {
1406
        return this.tokens.for?.leadingTrivia ?? [];
152!
1407
    }
1408

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

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

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

1449
        this.location = util.createBoundingLocation(
43✔
1450
            this.tokens.forEach,
1451
            this.tokens.item,
1452
            this.tokens.in,
1453
            this.target,
1454
            this.body,
1455
            this.tokens.endFor
1456
        );
1457
    }
1458

1459
    public readonly tokens: {
1460
        readonly forEach?: Token;
1461
        readonly item: Token;
1462
        readonly in?: Token;
1463
        readonly endFor?: Token;
1464
    };
1465
    public readonly body: Block;
1466
    public readonly target: Expression;
1467

1468
    public readonly kind = AstNodeKind.ForEachStatement;
43✔
1469

1470
    public readonly location: Location | undefined;
1471

1472
    transpile(state: BrsTranspileState) {
1473
        let result = [] as TranspileResult;
5✔
1474
        //for each
1475
        result.push(
5✔
1476
            state.transpileToken(this.tokens.forEach, 'for each'),
1477
            ' '
1478
        );
1479
        //item
1480
        result.push(
5✔
1481
            state.transpileToken(this.tokens.item),
1482
            ' '
1483
        );
1484
        //in
1485
        result.push(
5✔
1486
            state.transpileToken(this.tokens.in, 'in'),
1487
            ' '
1488
        );
1489
        //target
1490
        result.push(...this.target.transpile(state));
5✔
1491
        //body
1492
        state.lineage.unshift(this);
5✔
1493
        result.push(...this.body.transpile(state));
5✔
1494
        state.lineage.shift();
5✔
1495

1496
        //end for
1497
        result.push(...state.transpileEndBlockToken(this.body, this.tokens.endFor, 'end for'));
5✔
1498

1499
        return result;
5✔
1500
    }
1501

1502
    walk(visitor: WalkVisitor, options: WalkOptions) {
1503
        if (options.walkMode & InternalWalkMode.walkExpressions) {
197✔
1504
            walk(this, 'target', visitor, options);
190✔
1505
        }
1506
        if (options.walkMode & InternalWalkMode.walkStatements) {
197✔
1507
            walk(this, 'body', visitor, options);
196✔
1508
        }
1509
    }
1510

1511
    getType(options: GetTypeOptions): BscType {
1512
        return this.getSymbolTable().getSymbolType(this.tokens.item.text, { ...options, statementIndex: this.statementIndex });
31✔
1513
    }
1514

1515
    get leadingTrivia(): Token[] {
1516
        return this.tokens.forEach?.leadingTrivia ?? [];
197!
1517
    }
1518

1519
    public get endTrivia(): Token[] {
1520
        return this.tokens.endFor?.leadingTrivia ?? [];
1!
1521
    }
1522

1523
    public clone() {
1524
        return this.finalizeClone(
2✔
1525
            new ForEachStatement({
1526
                forEach: util.cloneToken(this.tokens.forEach),
1527
                in: util.cloneToken(this.tokens.in),
1528
                endFor: util.cloneToken(this.tokens.endFor),
1529
                item: util.cloneToken(this.tokens.item),
1530
                target: this.target?.clone(),
6✔
1531
                body: this.body?.clone()
6✔
1532
            }),
1533
            ['target', 'body']
1534
        );
1535
    }
1536
}
1537

1538
export class WhileStatement extends Statement {
1✔
1539
    constructor(options: {
1540
        while?: Token;
1541
        endWhile?: Token;
1542
        condition: Expression;
1543
        body: Block;
1544
    }) {
1545
        super();
38✔
1546
        this.tokens = {
38✔
1547
            while: options.while,
1548
            endWhile: options.endWhile
1549
        };
1550
        this.body = options.body;
38✔
1551
        this.condition = options.condition;
38✔
1552
        this.location = util.createBoundingLocation(
38✔
1553
            this.tokens.while,
1554
            this.condition,
1555
            this.body,
1556
            this.tokens.endWhile
1557
        );
1558
    }
1559

1560
    public readonly tokens: {
1561
        readonly while?: Token;
1562
        readonly endWhile?: Token;
1563
    };
1564
    public readonly condition: Expression;
1565
    public readonly body: Block;
1566

1567
    public readonly kind = AstNodeKind.WhileStatement;
38✔
1568

1569
    public readonly location: Location | undefined;
1570

1571
    transpile(state: BrsTranspileState) {
1572
        let result = [] as TranspileResult;
8✔
1573
        //while
1574
        result.push(
8✔
1575
            state.transpileToken(this.tokens.while, 'while'),
1576
            ' '
1577
        );
1578
        //condition
1579
        result.push(
8✔
1580
            ...this.condition.transpile(state)
1581
        );
1582
        state.lineage.unshift(this);
8✔
1583
        //body
1584
        result.push(...this.body.transpile(state));
8✔
1585
        state.lineage.shift();
8✔
1586

1587
        //end while
1588
        result.push(...state.transpileEndBlockToken(this.body, this.tokens.endWhile, 'end while'));
8✔
1589

1590
        return result;
8✔
1591
    }
1592

1593
    walk(visitor: WalkVisitor, options: WalkOptions) {
1594
        if (options.walkMode & InternalWalkMode.walkExpressions) {
128✔
1595
            walk(this, 'condition', visitor, options);
125✔
1596
        }
1597
        if (options.walkMode & InternalWalkMode.walkStatements) {
128✔
1598
            walk(this, 'body', visitor, options);
127✔
1599
        }
1600
    }
1601

1602
    get leadingTrivia(): Token[] {
1603
        return this.tokens.while?.leadingTrivia ?? [];
113!
1604
    }
1605

1606
    public get endTrivia(): Token[] {
1607
        return this.tokens.endWhile?.leadingTrivia ?? [];
1!
1608
    }
1609

1610
    public clone() {
1611
        return this.finalizeClone(
4✔
1612
            new WhileStatement({
1613
                while: util.cloneToken(this.tokens.while),
1614
                endWhile: util.cloneToken(this.tokens.endWhile),
1615
                condition: this.condition?.clone(),
12✔
1616
                body: this.body?.clone()
12✔
1617
            }),
1618
            ['condition', 'body']
1619
        );
1620
    }
1621
}
1622

1623
export class DottedSetStatement extends Statement {
1✔
1624
    constructor(options: {
1625
        obj: Expression;
1626
        name: Identifier;
1627
        value: Expression;
1628
        dot?: Token;
1629
        equals?: Token;
1630
    }) {
1631
        super();
318✔
1632
        this.tokens = {
318✔
1633
            name: options.name,
1634
            dot: options.dot,
1635
            equals: options.equals
1636
        };
1637
        this.obj = options.obj;
318✔
1638
        this.value = options.value;
318✔
1639
        this.location = util.createBoundingLocation(
318✔
1640
            this.obj,
1641
            this.tokens.dot,
1642
            this.tokens.equals,
1643
            this.tokens.name,
1644
            this.value
1645
        );
1646
    }
1647
    public readonly tokens: {
1648
        readonly name: Identifier;
1649
        readonly equals?: Token;
1650
        readonly dot?: Token;
1651
    };
1652

1653
    public readonly obj: Expression;
1654
    public readonly value: Expression;
1655

1656
    public readonly kind = AstNodeKind.DottedSetStatement;
318✔
1657

1658
    public readonly location: Location | undefined;
1659

1660
    transpile(state: BrsTranspileState) {
1661
        //if the value is a compound assignment, don't add the obj, dot, name, or operator...the expression will handle that
1662
        return [
15✔
1663
            //object
1664
            ...this.obj.transpile(state),
1665
            this.tokens.dot ? state.tokenToSourceNode(this.tokens.dot) : '.',
15✔
1666
            //name
1667
            state.transpileToken(this.tokens.name),
1668
            ' ',
1669
            state.transpileToken(this.tokens.equals, '='),
1670
            ' ',
1671
            //right-hand-side of assignment
1672
            ...this.value.transpile(state)
1673
        ];
1674

1675
    }
1676

1677
    walk(visitor: WalkVisitor, options: WalkOptions) {
1678
        if (options.walkMode & InternalWalkMode.walkExpressions) {
863✔
1679
            walk(this, 'obj', visitor, options);
861✔
1680
            walk(this, 'value', visitor, options);
861✔
1681
        }
1682
    }
1683

1684
    getType(options: GetTypeOptions) {
1685
        const objType = this.obj?.getType(options);
103!
1686
        const result = objType?.getMemberType(this.tokens.name?.text, options);
103!
1687
        options.typeChain?.push(new TypeChainEntry({
103✔
1688
            name: this.tokens.name?.text,
306!
1689
            type: result, data: options.data,
1690
            location: this.tokens.name?.location,
306!
1691
            astNode: this
1692
        }));
1693
        return result;
103✔
1694
    }
1695

1696
    get leadingTrivia(): Token[] {
1697
        return this.obj.leadingTrivia;
892✔
1698
    }
1699

1700
    public clone() {
1701
        return this.finalizeClone(
2✔
1702
            new DottedSetStatement({
1703
                obj: this.obj?.clone(),
6✔
1704
                dot: util.cloneToken(this.tokens.dot),
1705
                name: util.cloneToken(this.tokens.name),
1706
                equals: util.cloneToken(this.tokens.equals),
1707
                value: this.value?.clone()
6✔
1708
            }),
1709
            ['obj', 'value']
1710
        );
1711
    }
1712
}
1713

1714
export class IndexedSetStatement extends Statement {
1✔
1715
    constructor(options: {
1716
        obj: Expression;
1717
        indexes: Expression[];
1718
        value: Expression;
1719
        openingSquare?: Token;
1720
        closingSquare?: Token;
1721
        equals?: Token;
1722
    }) {
1723
        super();
47✔
1724
        this.tokens = {
47✔
1725
            openingSquare: options.openingSquare,
1726
            closingSquare: options.closingSquare,
1727
            equals: options.equals
1728
        };
1729
        this.obj = options.obj;
47✔
1730
        this.indexes = options.indexes ?? [];
47✔
1731
        this.value = options.value;
47✔
1732
        this.location = util.createBoundingLocation(
47✔
1733
            this.obj,
1734
            this.tokens.openingSquare,
1735
            ...this.indexes,
1736
            this.tokens.closingSquare,
1737
            this.value
1738
        );
1739
    }
1740

1741
    public readonly tokens: {
1742
        readonly openingSquare?: Token;
1743
        readonly closingSquare?: Token;
1744
        readonly equals?: Token;
1745
    };
1746
    public readonly obj: Expression;
1747
    public readonly indexes: Expression[];
1748
    public readonly value: Expression;
1749

1750
    public readonly kind = AstNodeKind.IndexedSetStatement;
47✔
1751

1752
    public readonly location: Location | undefined;
1753

1754
    transpile(state: BrsTranspileState) {
1755
        const result = [];
17✔
1756
        result.push(
17✔
1757
            //obj
1758
            ...this.obj.transpile(state),
1759
            //   [
1760
            state.transpileToken(this.tokens.openingSquare, '[')
1761
        );
1762
        for (let i = 0; i < this.indexes.length; i++) {
17✔
1763
            //add comma between indexes
1764
            if (i > 0) {
18✔
1765
                result.push(', ');
1✔
1766
            }
1767
            let index = this.indexes[i];
18✔
1768
            result.push(
18✔
1769
                ...(index?.transpile(state) ?? [])
108!
1770
            );
1771
        }
1772
        result.push(
17✔
1773
            state.transpileToken(this.tokens.closingSquare, ']'),
1774
            ' ',
1775
            state.transpileToken(this.tokens.equals, '='),
1776
            ' ',
1777
            ...this.value.transpile(state)
1778
        );
1779
        return result;
17✔
1780

1781
    }
1782

1783
    walk(visitor: WalkVisitor, options: WalkOptions) {
1784
        if (options.walkMode & InternalWalkMode.walkExpressions) {
166✔
1785
            walk(this, 'obj', visitor, options);
165✔
1786
            walkArray(this.indexes, visitor, options, this);
165✔
1787
            walk(this, 'value', visitor, options);
165✔
1788
        }
1789
    }
1790

1791
    get leadingTrivia(): Token[] {
1792
        return this.obj.leadingTrivia;
175✔
1793
    }
1794

1795
    public clone() {
1796
        return this.finalizeClone(
6✔
1797
            new IndexedSetStatement({
1798
                obj: this.obj?.clone(),
18✔
1799
                openingSquare: util.cloneToken(this.tokens.openingSquare),
1800
                indexes: this.indexes?.map(x => x?.clone()),
7✔
1801
                closingSquare: util.cloneToken(this.tokens.closingSquare),
1802
                equals: util.cloneToken(this.tokens.equals),
1803
                value: this.value?.clone()
18✔
1804
            }),
1805
            ['obj', 'indexes', 'value']
1806
        );
1807
    }
1808
}
1809

1810
export class LibraryStatement extends Statement implements TypedefProvider {
1✔
1811
    constructor(options: {
1812
        library: Token;
1813
        filePath?: Token;
1814
    }) {
1815
        super();
16✔
1816
        this.tokens = {
16✔
1817
            library: options?.library,
48!
1818
            filePath: options?.filePath
48!
1819
        };
1820
        this.location = util.createBoundingLocation(
16✔
1821
            this.tokens.library,
1822
            this.tokens.filePath
1823
        );
1824
    }
1825
    public readonly tokens: {
1826
        readonly library: Token;
1827
        readonly filePath?: Token;
1828
    };
1829

1830
    public readonly kind = AstNodeKind.LibraryStatement;
16✔
1831

1832
    public readonly location: Location | undefined;
1833

1834
    transpile(state: BrsTranspileState) {
1835
        let result = [] as TranspileResult;
2✔
1836
        result.push(
2✔
1837
            state.transpileToken(this.tokens.library)
1838
        );
1839
        //there will be a parse error if file path is missing, but let's prevent a runtime error just in case
1840
        if (this.tokens.filePath) {
2!
1841
            result.push(
2✔
1842
                ' ',
1843
                state.transpileToken(this.tokens.filePath)
1844
            );
1845
        }
1846
        return result;
2✔
1847
    }
1848

1849
    getTypedef(state: BrsTranspileState) {
UNCOV
1850
        return this.transpile(state);
×
1851
    }
1852

1853
    walk(visitor: WalkVisitor, options: WalkOptions) {
1854
        //nothing to walk
1855
    }
1856

1857
    get leadingTrivia(): Token[] {
1858
        return this.tokens.library?.leadingTrivia ?? [];
26!
1859
    }
1860

1861
    public clone() {
1862
        return this.finalizeClone(
1✔
1863
            new LibraryStatement({
1864
                library: util.cloneToken(this.tokens?.library),
3!
1865
                filePath: util.cloneToken(this.tokens?.filePath)
3!
1866
            })
1867
        );
1868
    }
1869
}
1870

1871
export class NamespaceStatement extends Statement implements TypedefProvider {
1✔
1872
    constructor(options: {
1873
        namespace?: Token;
1874
        nameExpression: VariableExpression | DottedGetExpression;
1875
        body: Body;
1876
        endNamespace?: Token;
1877
    }) {
1878
        super();
652✔
1879
        this.tokens = {
652✔
1880
            namespace: options.namespace,
1881
            endNamespace: options.endNamespace
1882
        };
1883
        this.nameExpression = options.nameExpression;
652✔
1884
        this.body = options.body;
652✔
1885
        this.symbolTable = new SymbolTable(`NamespaceStatement: '${this.name}'`, () => this.getRoot()?.getSymbolTable());
6,164!
1886
    }
1887

1888
    public readonly tokens: {
1889
        readonly namespace?: Token;
1890
        readonly endNamespace?: Token;
1891
    };
1892

1893
    public readonly nameExpression: VariableExpression | DottedGetExpression;
1894
    public readonly body: Body;
1895

1896
    public readonly kind = AstNodeKind.NamespaceStatement;
652✔
1897

1898
    /**
1899
     * The string name for this namespace
1900
     */
1901
    public get name(): string {
1902
        return this.getName(ParseMode.BrighterScript);
2,447✔
1903
    }
1904

1905
    public get location() {
1906
        return this.cacheLocation();
530✔
1907
    }
1908
    private _location: Location | undefined;
1909

1910
    public cacheLocation() {
1911
        if (!this._location) {
1,180✔
1912
            this._location = util.createBoundingLocation(
652✔
1913
                this.tokens.namespace,
1914
                this.nameExpression,
1915
                this.body,
1916
                this.tokens.endNamespace
1917
            );
1918
        }
1919
        return this._location;
1,180✔
1920
    }
1921

1922
    public getName(parseMode: ParseMode) {
1923
        const sep = parseMode === ParseMode.BrighterScript ? '.' : '_';
8,784✔
1924
        let name = util.getAllDottedGetPartsAsString(this.nameExpression, parseMode);
8,784✔
1925
        if ((this.parent as Body)?.parent?.kind === AstNodeKind.NamespaceStatement) {
8,784✔
1926
            name = (this.parent.parent as NamespaceStatement).getName(parseMode) + sep + name;
358✔
1927
        }
1928
        return name;
8,784✔
1929
    }
1930

1931
    public get leadingTrivia(): Token[] {
1932
        return this.tokens.namespace?.leadingTrivia;
1,901!
1933
    }
1934

1935
    public get endTrivia(): Token[] {
UNCOV
1936
        return this.tokens.endNamespace?.leadingTrivia;
×
1937
    }
1938

1939
    public getNameParts() {
1940
        let parts = util.getAllDottedGetParts(this.nameExpression);
1,466✔
1941

1942
        if ((this.parent as Body)?.parent?.kind === AstNodeKind.NamespaceStatement) {
1,466!
1943
            parts = (this.parent.parent as NamespaceStatement).getNameParts().concat(parts);
67✔
1944
        }
1945
        return parts;
1,466✔
1946
    }
1947

1948
    transpile(state: BrsTranspileState) {
1949
        //namespaces don't actually have any real content, so just transpile their bodies
1950
        return [
63✔
1951
            state.transpileAnnotations(this),
1952
            state.transpileLeadingComments(this.tokens.namespace),
1953
            this.body.transpile(state),
1954
            state.transpileLeadingComments(this.tokens.endNamespace)
1955
        ];
1956
    }
1957

1958
    getTypedef(state: BrsTranspileState) {
1959
        let result: TranspileResult = [];
7✔
1960
        for (let comment of util.getLeadingComments(this) ?? []) {
7!
UNCOV
1961
            result.push(
×
1962
                comment.text,
1963
                state.newline,
1964
                state.indent()
1965
            );
1966
        }
1967

1968
        result.push('namespace ',
7✔
1969
            ...this.getName(ParseMode.BrighterScript),
1970
            state.newline
1971
        );
1972
        state.blockDepth++;
7✔
1973
        result.push(
7✔
1974
            ...this.body.getTypedef(state)
1975
        );
1976
        state.blockDepth--;
7✔
1977

1978
        result.push(
7✔
1979
            state.indent(),
1980
            'end namespace'
1981
        );
1982
        return result;
7✔
1983
    }
1984

1985
    walk(visitor: WalkVisitor, options: WalkOptions) {
1986
        if (options.walkMode & InternalWalkMode.walkExpressions) {
3,318✔
1987
            walk(this, 'nameExpression', visitor, options);
2,719✔
1988
        }
1989

1990
        if (this.body.statements.length > 0 && options.walkMode & InternalWalkMode.walkStatements) {
3,318✔
1991
            walk(this, 'body', visitor, options);
3,113✔
1992
        }
1993
    }
1994

1995
    getType(options: GetTypeOptions) {
1996
        const resultType = new NamespaceType(this.name);
1,141✔
1997
        return resultType;
1,141✔
1998
    }
1999

2000
    public clone() {
2001
        const clone = this.finalizeClone(
3✔
2002
            new NamespaceStatement({
2003
                namespace: util.cloneToken(this.tokens.namespace),
2004
                nameExpression: this.nameExpression?.clone(),
9✔
2005
                body: this.body?.clone(),
9✔
2006
                endNamespace: util.cloneToken(this.tokens.endNamespace)
2007
            }),
2008
            ['nameExpression', 'body']
2009
        );
2010
        clone.cacheLocation();
3✔
2011
        return clone;
3✔
2012
    }
2013
}
2014

2015
export class ImportStatement extends Statement implements TypedefProvider {
1✔
2016
    constructor(options: {
2017
        import?: Token;
2018
        path?: Token;
2019
    }) {
2020
        super();
215✔
2021
        this.tokens = {
215✔
2022
            import: options.import,
2023
            path: options.path
2024
        };
2025
        this.location = util.createBoundingLocation(
215✔
2026
            this.tokens.import,
2027
            this.tokens.path
2028
        );
2029
        if (this.tokens.path) {
215✔
2030
            //remove quotes
2031
            this.filePath = this.tokens.path.text.replace(/"/g, '');
213✔
2032
            if (this.tokens.path?.location?.range) {
213!
2033
                //adjust the range to exclude the quotes
2034
                this.tokens.path.location = util.createLocation(
209✔
2035
                    this.tokens.path.location.range.start.line,
2036
                    this.tokens.path.location.range.start.character + 1,
2037
                    this.tokens.path.location.range.end.line,
2038
                    this.tokens.path.location.range.end.character - 1,
2039
                    this.tokens.path.location.uri
2040
                );
2041
            }
2042
        }
2043
    }
2044

2045
    public readonly tokens: {
2046
        readonly import?: Token;
2047
        readonly path: Token;
2048
    };
2049

2050
    public readonly kind = AstNodeKind.ImportStatement;
215✔
2051

2052
    public readonly location: Location;
2053

2054
    public readonly filePath: string;
2055

2056
    transpile(state: BrsTranspileState) {
2057
        //The xml files are responsible for adding the additional script imports, but
2058
        //add the import statement as a comment just for debugging purposes
2059
        return [
13✔
2060
            state.transpileToken(this.tokens.import, 'import', true),
2061
            ' ',
2062
            state.transpileToken(this.tokens.path)
2063
        ];
2064
    }
2065

2066
    /**
2067
     * Get the typedef for this statement
2068
     */
2069
    public getTypedef(state: BrsTranspileState) {
2070
        return [
3✔
2071
            this.tokens.import?.text ?? 'import',
18!
2072
            ' ',
2073
            //replace any `.bs` extension with `.brs`
2074
            this.tokens.path.text.replace(/\.bs"?$/i, '.brs"')
2075
        ];
2076
    }
2077

2078
    walk(visitor: WalkVisitor, options: WalkOptions) {
2079
        //nothing to walk
2080
    }
2081

2082
    get leadingTrivia(): Token[] {
2083
        return this.tokens.import?.leadingTrivia ?? [];
600!
2084
    }
2085

2086
    public clone() {
2087
        return this.finalizeClone(
1✔
2088
            new ImportStatement({
2089
                import: util.cloneToken(this.tokens.import),
2090
                path: util.cloneToken(this.tokens.path)
2091
            })
2092
        );
2093
    }
2094
}
2095

2096
export class InterfaceStatement extends Statement implements TypedefProvider {
1✔
2097
    constructor(options: {
2098
        interface: Token;
2099
        name: Identifier;
2100
        extends?: Token;
2101
        parentInterfaceName?: TypeExpression;
2102
        body: Statement[];
2103
        endInterface?: Token;
2104
    }) {
2105
        super();
184✔
2106
        this.tokens = {
184✔
2107
            interface: options.interface,
2108
            name: options.name,
2109
            extends: options.extends,
2110
            endInterface: options.endInterface
2111
        };
2112
        this.parentInterfaceName = options.parentInterfaceName;
184✔
2113
        this.body = options.body;
184✔
2114
        this.location = util.createBoundingLocation(
184✔
2115
            this.tokens.interface,
2116
            this.tokens.name,
2117
            this.tokens.extends,
2118
            this.parentInterfaceName,
2119
            ...this.body ?? [],
552✔
2120
            this.tokens.endInterface
2121
        );
2122
    }
2123
    public readonly parentInterfaceName?: TypeExpression;
2124
    public readonly body: Statement[];
2125

2126
    public readonly kind = AstNodeKind.InterfaceStatement;
184✔
2127

2128
    public readonly tokens = {} as {
184✔
2129
        readonly interface?: Token;
2130
        readonly name: Identifier;
2131
        readonly extends?: Token;
2132
        readonly endInterface?: Token;
2133
    };
2134

2135
    public readonly location: Location | undefined;
2136

2137
    public get fields(): InterfaceFieldStatement[] {
2138
        return this.body.filter(x => isInterfaceFieldStatement(x)) as InterfaceFieldStatement[];
234✔
2139
    }
2140

2141
    public get methods(): InterfaceMethodStatement[] {
2142
        return this.body.filter(x => isInterfaceMethodStatement(x)) as InterfaceMethodStatement[];
229✔
2143
    }
2144

2145

2146
    public hasParentInterface() {
UNCOV
2147
        return !!this.parentInterfaceName;
×
2148
    }
2149

2150
    public get leadingTrivia(): Token[] {
2151
        return this.tokens.interface?.leadingTrivia;
473!
2152
    }
2153

2154
    public get endTrivia(): Token[] {
UNCOV
2155
        return this.tokens.endInterface?.leadingTrivia;
×
2156
    }
2157

2158

2159
    /**
2160
     * The name of the interface WITH its leading namespace (if applicable)
2161
     */
2162
    public get fullName() {
UNCOV
2163
        const name = this.tokens.name?.text;
×
UNCOV
2164
        if (name) {
×
2165
            const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
×
UNCOV
2166
            if (namespace) {
×
UNCOV
2167
                let namespaceName = namespace.getName(ParseMode.BrighterScript);
×
UNCOV
2168
                return `${namespaceName}.${name}`;
×
2169
            } else {
UNCOV
2170
                return name;
×
2171
            }
2172
        } else {
2173
            //return undefined which will allow outside callers to know that this interface doesn't have a name
2174
            return undefined;
×
2175
        }
2176
    }
2177

2178
    /**
2179
     * The name of the interface (without the namespace prefix)
2180
     */
2181
    public get name() {
2182
        return this.tokens.name?.text;
163!
2183
    }
2184

2185
    /**
2186
     * Get the name of this expression based on the parse mode
2187
     */
2188
    public getName(parseMode: ParseMode) {
2189
        const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
163✔
2190
        if (namespace) {
163✔
2191
            let delimiter = parseMode === ParseMode.BrighterScript ? '.' : '_';
24!
2192
            let namespaceName = namespace.getName(parseMode);
24✔
2193
            return namespaceName + delimiter + this.name;
24✔
2194
        } else {
2195
            return this.name;
139✔
2196
        }
2197
    }
2198

2199
    public transpile(state: BrsTranspileState): TranspileResult {
2200
        //interfaces should completely disappear at runtime
2201
        return [
14✔
2202
            state.transpileLeadingComments(this.tokens.interface)
2203
        ];
2204
    }
2205

2206
    getTypedef(state: BrsTranspileState) {
2207
        const result = [] as TranspileResult;
7✔
2208
        for (let comment of util.getLeadingComments(this) ?? []) {
7!
UNCOV
2209
            result.push(
×
2210
                comment.text,
2211
                state.newline,
2212
                state.indent()
2213
            );
2214
        }
2215
        for (let annotation of this.annotations ?? []) {
7✔
2216
            result.push(
1✔
2217
                ...annotation.getTypedef(state),
2218
                state.newline,
2219
                state.indent()
2220
            );
2221
        }
2222
        result.push(
7✔
2223
            this.tokens.interface.text,
2224
            ' ',
2225
            this.tokens.name.text
2226
        );
2227
        const parentInterfaceName = this.parentInterfaceName?.getName();
7!
2228
        if (parentInterfaceName) {
7!
UNCOV
2229
            result.push(
×
2230
                ' extends ',
2231
                parentInterfaceName
2232
            );
2233
        }
2234
        const body = this.body ?? [];
7!
2235
        if (body.length > 0) {
7!
2236
            state.blockDepth++;
7✔
2237
        }
2238
        for (const statement of body) {
7✔
2239
            if (isInterfaceMethodStatement(statement) || isInterfaceFieldStatement(statement)) {
22!
2240
                result.push(
22✔
2241
                    state.newline,
2242
                    state.indent(),
2243
                    ...statement.getTypedef(state)
2244
                );
2245
            } else {
UNCOV
2246
                result.push(
×
2247
                    state.newline,
2248
                    state.indent(),
2249
                    ...statement.transpile(state)
2250
                );
2251
            }
2252
        }
2253
        if (body.length > 0) {
7!
2254
            state.blockDepth--;
7✔
2255
        }
2256
        result.push(
7✔
2257
            state.newline,
2258
            state.indent(),
2259
            'end interface',
2260
            state.newline
2261
        );
2262
        return result;
7✔
2263
    }
2264

2265
    walk(visitor: WalkVisitor, options: WalkOptions) {
2266
        //visitor-less walk function to do parent linking
2267
        walk(this, 'parentInterfaceName', null, options);
814✔
2268

2269
        if (options.walkMode & InternalWalkMode.walkStatements) {
814!
2270
            walkArray(this.body, visitor, options, this);
814✔
2271
        }
2272
    }
2273

2274
    getType(options: GetTypeOptions) {
2275
        const superIface = this.parentInterfaceName?.getType(options) as InterfaceType;
154✔
2276

2277
        const resultType = new InterfaceType(this.getName(ParseMode.BrighterScript), superIface);
154✔
2278
        for (const statement of this.methods) {
154✔
2279
            const memberType = statement?.getType({ ...options, typeChain: undefined }); // no typechain info needed
28!
2280
            const flag = statement.isOptional ? SymbolTypeFlag.runtime | SymbolTypeFlag.optional : SymbolTypeFlag.runtime;
28✔
2281
            resultType.addMember(statement?.tokens.name?.text, { definingNode: statement }, memberType, flag);
28!
2282
        }
2283
        for (const statement of this.fields) {
154✔
2284
            const memberType = statement?.getType({ ...options, typeChain: undefined }); // no typechain info needed
196!
2285
            const flag = statement.isOptional ? SymbolTypeFlag.runtime | SymbolTypeFlag.optional : SymbolTypeFlag.runtime;
196✔
2286
            resultType.addMember(statement?.tokens.name?.text, { definingNode: statement, isInstance: true }, memberType, flag);
196!
2287
        }
2288
        options.typeChain?.push(new TypeChainEntry({
154✔
2289
            name: this.getName(ParseMode.BrighterScript),
2290
            type: resultType,
2291
            data: options.data,
2292
            astNode: this
2293
        }));
2294
        return resultType;
154✔
2295
    }
2296

2297
    public clone() {
2298
        return this.finalizeClone(
8✔
2299
            new InterfaceStatement({
2300
                interface: util.cloneToken(this.tokens.interface),
2301
                name: util.cloneToken(this.tokens.name),
2302
                extends: util.cloneToken(this.tokens.extends),
2303
                parentInterfaceName: this.parentInterfaceName?.clone(),
24✔
2304
                body: this.body?.map(x => x?.clone()),
9✔
2305
                endInterface: util.cloneToken(this.tokens.endInterface)
2306
            }),
2307
            ['parentInterfaceName', 'body']
2308
        );
2309
    }
2310
}
2311

2312
export class InterfaceFieldStatement extends Statement implements TypedefProvider {
1✔
2313
    public transpile(state: BrsTranspileState): TranspileResult {
UNCOV
2314
        throw new Error('Method not implemented.');
×
2315
    }
2316
    constructor(options: {
2317
        name: Identifier;
2318
        as?: Token;
2319
        typeExpression?: TypeExpression;
2320
        optional?: Token;
2321
    }) {
2322
        super();
215✔
2323
        this.tokens = {
215✔
2324
            optional: options.optional,
2325
            name: options.name,
2326
            as: options.as
2327
        };
2328
        this.typeExpression = options.typeExpression;
215✔
2329
        this.location = util.createBoundingLocation(
215✔
2330
            this.tokens.optional,
2331
            this.tokens.name,
2332
            this.tokens.as,
2333
            this.typeExpression
2334
        );
2335
    }
2336

2337
    public readonly kind = AstNodeKind.InterfaceFieldStatement;
215✔
2338

2339
    public readonly typeExpression?: TypeExpression;
2340

2341
    public readonly location: Location | undefined;
2342

2343
    public readonly tokens: {
2344
        readonly name: Identifier;
2345
        readonly as: Token;
2346
        readonly optional?: Token;
2347
    };
2348

2349
    public get leadingTrivia(): Token[] {
2350
        return this.tokens.optional?.leadingTrivia ?? this.tokens.name.leadingTrivia;
581✔
2351
    }
2352

2353
    public get name() {
UNCOV
2354
        return this.tokens.name.text;
×
2355
    }
2356

2357
    public get isOptional() {
2358
        return !!this.tokens.optional;
216✔
2359
    }
2360

2361
    walk(visitor: WalkVisitor, options: WalkOptions) {
2362
        if (options.walkMode & InternalWalkMode.walkExpressions) {
1,387✔
2363
            walk(this, 'typeExpression', visitor, options);
1,204✔
2364
        }
2365
    }
2366

2367
    getTypedef(state: BrsTranspileState): TranspileResult {
2368
        const result = [] as TranspileResult;
12✔
2369
        for (let comment of util.getLeadingComments(this) ?? []) {
12!
UNCOV
2370
            result.push(
×
2371
                comment.text,
2372
                state.newline,
2373
                state.indent()
2374
            );
2375
        }
2376
        for (let annotation of this.annotations ?? []) {
12✔
2377
            result.push(
1✔
2378
                ...annotation.getTypedef(state),
2379
                state.newline,
2380
                state.indent()
2381
            );
2382
        }
2383
        if (this.isOptional) {
12✔
2384
            result.push(
1✔
2385
                this.tokens.optional!.text,
2386
                ' '
2387
            );
2388
        }
2389
        result.push(
12✔
2390
            this.tokens.name.text
2391
        );
2392

2393
        if (this.typeExpression) {
12!
2394
            result.push(
12✔
2395
                ' as ',
2396
                ...this.typeExpression.getTypedef(state)
2397
            );
2398
        }
2399
        return result;
12✔
2400
    }
2401

2402
    public getType(options: GetTypeOptions): BscType {
2403
        return this.typeExpression?.getType(options) ?? DynamicType.instance;
207✔
2404
    }
2405

2406
    public clone() {
2407
        return this.finalizeClone(
4✔
2408
            new InterfaceFieldStatement({
2409
                name: util.cloneToken(this.tokens.name),
2410
                as: util.cloneToken(this.tokens.as),
2411
                typeExpression: this.typeExpression?.clone(),
12✔
2412
                optional: util.cloneToken(this.tokens.optional)
2413
            })
2414
        );
2415
    }
2416

2417
}
2418

2419
//TODO: there is much that is similar with this and FunctionExpression.
2420
//It would be nice to refactor this so there is less duplicated code
2421
export class InterfaceMethodStatement extends Statement implements TypedefProvider {
1✔
2422
    public transpile(state: BrsTranspileState): TranspileResult {
UNCOV
2423
        throw new Error('Method not implemented.');
×
2424
    }
2425
    constructor(options: {
2426
        functionType?: Token;
2427
        name: Identifier;
2428
        leftParen?: Token;
2429
        params?: FunctionParameterExpression[];
2430
        rightParen?: Token;
2431
        as?: Token;
2432
        returnTypeExpression?: TypeExpression;
2433
        optional?: Token;
2434
    }) {
2435
        super();
46✔
2436
        this.tokens = {
46✔
2437
            optional: options.optional,
2438
            functionType: options.functionType,
2439
            name: options.name,
2440
            leftParen: options.leftParen,
2441
            rightParen: options.rightParen,
2442
            as: options.as
2443
        };
2444
        this.params = options.params ?? [];
46✔
2445
        this.returnTypeExpression = options.returnTypeExpression;
46✔
2446
    }
2447

2448
    public readonly kind = AstNodeKind.InterfaceMethodStatement;
46✔
2449

2450
    public get location() {
2451
        return util.createBoundingLocation(
76✔
2452
            this.tokens.optional,
2453
            this.tokens.functionType,
2454
            this.tokens.name,
2455
            this.tokens.leftParen,
2456
            ...(this.params ?? []),
228!
2457
            this.tokens.rightParen,
2458
            this.tokens.as,
2459
            this.returnTypeExpression
2460
        );
2461
    }
2462
    /**
2463
     * Get the name of this method.
2464
     */
2465
    public getName(parseMode: ParseMode) {
2466
        return this.tokens.name.text;
28✔
2467
    }
2468

2469
    public readonly tokens: {
2470
        readonly optional?: Token;
2471
        readonly functionType: Token;
2472
        readonly name: Identifier;
2473
        readonly leftParen?: Token;
2474
        readonly rightParen?: Token;
2475
        readonly as?: Token;
2476
    };
2477

2478
    public readonly params: FunctionParameterExpression[];
2479
    public readonly returnTypeExpression?: TypeExpression;
2480

2481
    public get isOptional() {
2482
        return !!this.tokens.optional;
41✔
2483
    }
2484

2485
    public get leadingTrivia(): Token[] {
2486
        return this.tokens.optional?.leadingTrivia ?? this.tokens.functionType.leadingTrivia;
88✔
2487
    }
2488

2489
    walk(visitor: WalkVisitor, options: WalkOptions) {
2490
        if (options.walkMode & InternalWalkMode.walkExpressions) {
215✔
2491
            walk(this, 'returnTypeExpression', visitor, options);
190✔
2492
        }
2493
    }
2494

2495
    getTypedef(state: BrsTranspileState) {
2496
        const result = [] as TranspileResult;
10✔
2497
        for (let comment of util.getLeadingComments(this) ?? []) {
10!
2498
            result.push(
1✔
2499
                comment.text,
2500
                state.newline,
2501
                state.indent()
2502
            );
2503
        }
2504
        for (let annotation of this.annotations ?? []) {
10✔
2505
            result.push(
1✔
2506
                ...annotation.getTypedef(state),
2507
                state.newline,
2508
                state.indent()
2509
            );
2510
        }
2511
        if (this.isOptional) {
10!
UNCOV
2512
            result.push(
×
2513
                this.tokens.optional!.text,
2514
                ' '
2515
            );
2516
        }
2517
        result.push(
10✔
2518
            this.tokens.functionType?.text ?? 'function',
60!
2519
            ' ',
2520
            this.tokens.name.text,
2521
            '('
2522
        );
2523
        const params = this.params ?? [];
10!
2524
        for (let i = 0; i < params.length; i++) {
10✔
2525
            if (i > 0) {
2✔
2526
                result.push(', ');
1✔
2527
            }
2528
            const param = params[i];
2✔
2529
            result.push(param.tokens.name.text);
2✔
2530
            if (param.typeExpression) {
2!
2531
                result.push(
2✔
2532
                    ' as ',
2533
                    ...param.typeExpression.getTypedef(state)
2534
                );
2535
            }
2536
        }
2537
        result.push(
10✔
2538
            ')'
2539
        );
2540
        if (this.returnTypeExpression) {
10!
2541
            result.push(
10✔
2542
                ' as ',
2543
                ...this.returnTypeExpression.getTypedef(state)
2544
            );
2545
        }
2546
        return result;
10✔
2547
    }
2548

2549
    public getType(options: GetTypeOptions): TypedFunctionType {
2550
        //if there's a defined return type, use that
2551
        let returnType = this.returnTypeExpression?.getType(options);
28✔
2552
        const isSub = this.tokens.functionType?.kind === TokenKind.Sub || !returnType;
28!
2553
        //if we don't have a return type and this is a sub, set the return type to `void`. else use `dynamic`
2554
        if (!returnType) {
28✔
2555
            returnType = isSub ? VoidType.instance : DynamicType.instance;
10!
2556
        }
2557
        const resultType = new TypedFunctionType(returnType);
28✔
2558
        resultType.isSub = isSub;
28✔
2559
        for (let param of this.params) {
28✔
2560
            resultType.addParameter(param.tokens.name.text, param.getType(options), !!param.defaultValue);
7✔
2561
        }
2562
        if (options.typeChain) {
28!
2563
            // need Interface type for type chain
UNCOV
2564
            this.parent?.getType(options);
×
2565
        }
2566
        let funcName = this.getName(ParseMode.BrighterScript);
28✔
2567
        resultType.setName(funcName);
28✔
2568
        options.typeChain?.push(new TypeChainEntry({ name: resultType.name, type: resultType, data: options.data, astNode: this }));
28!
2569
        return resultType;
28✔
2570
    }
2571

2572
    public clone() {
2573
        return this.finalizeClone(
4✔
2574
            new InterfaceMethodStatement({
2575
                optional: util.cloneToken(this.tokens.optional),
2576
                functionType: util.cloneToken(this.tokens.functionType),
2577
                name: util.cloneToken(this.tokens.name),
2578
                leftParen: util.cloneToken(this.tokens.leftParen),
2579
                params: this.params?.map(p => p?.clone()),
3✔
2580
                rightParen: util.cloneToken(this.tokens.rightParen),
2581
                as: util.cloneToken(this.tokens.as),
2582
                returnTypeExpression: this.returnTypeExpression?.clone()
12✔
2583
            }),
2584
            ['params']
2585
        );
2586
    }
2587
}
2588

2589
export class ClassStatement extends Statement implements TypedefProvider {
1✔
2590
    constructor(options: {
2591
        class?: Token;
2592
        /**
2593
         * The name of the class (without namespace prefix)
2594
         */
2595
        name: Identifier;
2596
        body: Statement[];
2597
        endClass?: Token;
2598
        extends?: Token;
2599
        parentClassName?: TypeExpression;
2600
    }) {
2601
        super();
708✔
2602
        this.body = options.body ?? [];
708✔
2603
        this.tokens = {
708✔
2604
            name: options.name,
2605
            class: options.class,
2606
            endClass: options.endClass,
2607
            extends: options.extends
2608
        };
2609
        this.parentClassName = options.parentClassName;
708✔
2610
        this.symbolTable = new SymbolTable(`ClassStatement: '${this.tokens.name?.text}'`, () => this.parent?.getSymbolTable());
1,731!
2611

2612
        for (let statement of this.body) {
708✔
2613
            if (isMethodStatement(statement)) {
718✔
2614
                this.methods.push(statement);
373✔
2615
                this.memberMap[statement?.tokens.name?.text.toLowerCase()] = statement;
373!
2616
            } else if (isFieldStatement(statement)) {
345✔
2617
                this.fields.push(statement);
344✔
2618
                this.memberMap[statement?.tokens.name?.text.toLowerCase()] = statement;
344!
2619
            }
2620
        }
2621

2622
        this.location = util.createBoundingLocation(
708✔
2623
            this.parentClassName,
2624
            ...(this.body ?? []),
2,124!
2625
            util.createBoundingLocationFromTokens(this.tokens)
2626
        );
2627
    }
2628

2629
    public readonly kind = AstNodeKind.ClassStatement;
708✔
2630

2631

2632
    public readonly tokens: {
2633
        readonly class?: Token;
2634
        /**
2635
         * The name of the class (without namespace prefix)
2636
         */
2637
        readonly name: Identifier;
2638
        readonly endClass?: Token;
2639
        readonly extends?: Token;
2640
    };
2641
    public readonly body: Statement[];
2642
    public readonly parentClassName: TypeExpression;
2643

2644

2645
    public getName(parseMode: ParseMode) {
2646
        const name = this.tokens.name?.text;
2,394✔
2647
        if (name) {
2,394✔
2648
            const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
2,392✔
2649
            if (namespace) {
2,392✔
2650
                let namespaceName = namespace.getName(parseMode);
885✔
2651
                let separator = parseMode === ParseMode.BrighterScript ? '.' : '_';
885✔
2652
                return namespaceName + separator + name;
885✔
2653
            } else {
2654
                return name;
1,507✔
2655
            }
2656
        } else {
2657
            //return undefined which will allow outside callers to know that this class doesn't have a name
2658
            return undefined;
2✔
2659
        }
2660
    }
2661

2662
    public get leadingTrivia(): Token[] {
2663
        return this.tokens.class?.leadingTrivia;
1,355!
2664
    }
2665

2666
    public get endTrivia(): Token[] {
UNCOV
2667
        return this.tokens.endClass?.leadingTrivia ?? [];
×
2668
    }
2669

2670
    public readonly memberMap = {} as Record<string, MemberStatement>;
708✔
2671
    public readonly methods = [] as MethodStatement[];
708✔
2672
    public readonly fields = [] as FieldStatement[];
708✔
2673

2674
    public readonly location: Location | undefined;
2675

2676
    transpile(state: BrsTranspileState) {
2677
        let result = [] as TranspileResult;
54✔
2678
        //make the builder
2679
        result.push(...this.getTranspiledBuilder(state));
54✔
2680
        result.push(
54✔
2681
            '\n',
2682
            state.indent()
2683
        );
2684
        //make the class assembler (i.e. the public-facing class creator method)
2685
        result.push(...this.getTranspiledClassFunction(state));
54✔
2686
        return result;
54✔
2687
    }
2688

2689
    getTypedef(state: BrsTranspileState) {
2690
        const result = [] as TranspileResult;
15✔
2691
        for (let comment of util.getLeadingComments(this) ?? []) {
15!
UNCOV
2692
            result.push(
×
2693
                comment.text,
2694
                state.newline,
2695
                state.indent()
2696
            );
2697
        }
2698
        for (let annotation of this.annotations ?? []) {
15!
UNCOV
2699
            result.push(
×
2700
                ...annotation.getTypedef(state),
2701
                state.newline,
2702
                state.indent()
2703
            );
2704
        }
2705
        result.push(
15✔
2706
            'class ',
2707
            this.tokens.name.text
2708
        );
2709
        if (this.parentClassName) {
15✔
2710
            const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
4✔
2711
            const fqName = util.getFullyQualifiedClassName(
4✔
2712
                this.parentClassName.getName(),
2713
                namespace?.getName(ParseMode.BrighterScript)
12✔
2714
            );
2715
            result.push(
4✔
2716
                ` extends ${fqName}`
2717
            );
2718
        }
2719
        result.push(state.newline);
15✔
2720
        state.blockDepth++;
15✔
2721

2722
        let body = this.body;
15✔
2723
        //inject an empty "new" method if missing
2724
        if (!this.getConstructorFunction()) {
15✔
2725
            const constructor = createMethodStatement('new', TokenKind.Sub);
11✔
2726
            constructor.parent = this;
11✔
2727
            //walk the constructor to set up parent links
2728
            constructor.link();
11✔
2729
            body = [
11✔
2730
                constructor,
2731
                ...this.body
2732
            ];
2733
        }
2734

2735
        for (const member of body) {
15✔
2736
            if (isTypedefProvider(member)) {
35!
2737
                result.push(
35✔
2738
                    state.indent(),
2739
                    ...member.getTypedef(state),
2740
                    state.newline
2741
                );
2742
            }
2743
        }
2744
        state.blockDepth--;
15✔
2745
        result.push(
15✔
2746
            state.indent(),
2747
            'end class'
2748
        );
2749
        return result;
15✔
2750
    }
2751

2752
    /**
2753
     * Find the parent index for this class's parent.
2754
     * For class inheritance, every class is given an index.
2755
     * The base class is index 0, its child is index 1, and so on.
2756
     */
2757
    public getParentClassIndex(state: BrsTranspileState) {
2758
        let myIndex = 0;
124✔
2759
        let stmt = this as ClassStatement;
124✔
2760
        while (stmt) {
124✔
2761
            if (stmt.parentClassName) {
185✔
2762
                const namespace = stmt.findAncestor<NamespaceStatement>(isNamespaceStatement);
62✔
2763
                //find the parent class
2764
                stmt = state.file.getClassFileLink(
62✔
2765
                    stmt.parentClassName.getName(),
2766
                    namespace?.getName(ParseMode.BrighterScript)
186✔
2767
                )?.item;
62✔
2768
                myIndex++;
62✔
2769
            } else {
2770
                break;
123✔
2771
            }
2772
        }
2773
        const result = myIndex - 1;
124✔
2774
        if (result >= 0) {
124✔
2775
            return result;
49✔
2776
        } else {
2777
            return null;
75✔
2778
        }
2779
    }
2780

2781
    public hasParentClass() {
2782
        return !!this.parentClassName;
296✔
2783
    }
2784

2785
    /**
2786
     * Get all ancestor classes, in closest-to-furthest order (i.e. 0 is parent, 1 is grandparent, etc...).
2787
     * This will return an empty array if no ancestors were found
2788
     */
2789
    public getAncestors(state: BrsTranspileState) {
2790
        let ancestors = [] as ClassStatement[];
139✔
2791
        let stmt = this as ClassStatement;
139✔
2792
        while (stmt) {
139✔
2793
            if (stmt.parentClassName) {
209✔
2794
                const namespace = stmt.findAncestor<NamespaceStatement>(isNamespaceStatement);
70✔
2795
                stmt = state.file.getClassFileLink(
70✔
2796
                    stmt.parentClassName.getName(),
2797
                    namespace?.getName(ParseMode.BrighterScript)
210✔
2798
                )?.item;
70!
2799
                ancestors.push(stmt);
70✔
2800
            } else {
2801
                break;
139✔
2802
            }
2803
        }
2804
        return ancestors;
139✔
2805
    }
2806

2807
    private getBuilderName(name: string) {
2808
        if (name.includes('.')) {
129✔
2809
            name = name.replace(/\./gi, '_');
3✔
2810
        }
2811
        return `__${name}_builder`;
129✔
2812
    }
2813

2814
    public getConstructorType() {
2815
        const constructorType = this.getConstructorFunction()?.getType({ flags: SymbolTypeFlag.runtime }) ?? new TypedFunctionType(null);
131✔
2816
        constructorType.returnType = this.getType({ flags: SymbolTypeFlag.runtime });
131✔
2817
        return constructorType;
131✔
2818
    }
2819

2820
    /**
2821
     * Get the constructor function for this class (if exists), or undefined if not exist
2822
     */
2823
    private getConstructorFunction() {
2824
        return this.body.find((stmt) => {
282✔
2825
            return (stmt as MethodStatement)?.tokens.name?.text?.toLowerCase() === 'new';
247!
2826
        }) as MethodStatement;
2827
    }
2828

2829
    /**
2830
     * Return the parameters for the first constructor function for this class
2831
     * @param ancestors The list of ancestors for this class
2832
     * @returns The parameters for the first constructor function for this class
2833
     */
2834
    private getConstructorParams(ancestors: ClassStatement[]) {
2835
        for (let ancestor of ancestors) {
43✔
2836
            const ctor = ancestor?.getConstructorFunction();
28!
2837
            if (ctor) {
28✔
2838
                return ctor.func.parameters;
10✔
2839
            }
2840
        }
2841
        return [];
33✔
2842
    }
2843

2844
    /**
2845
     * Determine if the specified field was declared in one of the ancestor classes
2846
     */
2847
    public isFieldDeclaredByAncestor(fieldName: string, ancestors: ClassStatement[]) {
UNCOV
2848
        let lowerFieldName = fieldName.toLowerCase();
×
UNCOV
2849
        for (let ancestor of ancestors) {
×
UNCOV
2850
            if (ancestor.memberMap[lowerFieldName]) {
×
UNCOV
2851
                return true;
×
2852
            }
2853
        }
UNCOV
2854
        return false;
×
2855
    }
2856

2857
    /**
2858
     * The builder is a function that assigns all of the methods and property names to a class instance.
2859
     * This needs to be a separate function so that child classes can call the builder from their parent
2860
     * without instantiating the parent constructor at that point in time.
2861
     */
2862
    private getTranspiledBuilder(state: BrsTranspileState) {
2863
        let result = [] as TranspileResult;
54✔
2864
        result.push(`function ${this.getBuilderName(this.getName(ParseMode.BrightScript)!)}()\n`);
54✔
2865
        state.blockDepth++;
54✔
2866
        //indent
2867
        result.push(state.indent());
54✔
2868

2869
        /**
2870
         * The lineage of this class. index 0 is a direct parent, index 1 is index 0's parent, etc...
2871
         */
2872
        let ancestors = this.getAncestors(state);
54✔
2873

2874
        //construct parent class or empty object
2875
        if (ancestors[0]) {
54✔
2876
            const ancestorNamespace = ancestors[0].findAncestor<NamespaceStatement>(isNamespaceStatement);
21✔
2877
            let fullyQualifiedClassName = util.getFullyQualifiedClassName(
21✔
2878
                ancestors[0].getName(ParseMode.BrighterScript)!,
2879
                ancestorNamespace?.getName(ParseMode.BrighterScript)
63✔
2880
            );
2881
            result.push(
21✔
2882
                'instance = ',
2883
                this.getBuilderName(fullyQualifiedClassName), '()');
2884
        } else {
2885
            //use an empty object.
2886
            result.push('instance = {}');
33✔
2887
        }
2888
        result.push(
54✔
2889
            state.newline,
2890
            state.indent()
2891
        );
2892
        let parentClassIndex = this.getParentClassIndex(state);
54✔
2893

2894
        let body = this.body;
54✔
2895
        //inject an empty "new" method if missing
2896
        if (!this.getConstructorFunction()) {
54✔
2897
            if (ancestors.length === 0) {
31✔
2898
                body = [
19✔
2899
                    createMethodStatement('new', TokenKind.Sub),
2900
                    ...this.body
2901
                ];
2902
            } else {
2903
                const params = this.getConstructorParams(ancestors);
12✔
2904
                const call = new ExpressionStatement({
12✔
2905
                    expression: new CallExpression({
2906
                        callee: new VariableExpression({
2907
                            name: createToken(TokenKind.Identifier, 'super')
2908
                        }),
2909
                        openingParen: createToken(TokenKind.LeftParen),
2910
                        args: params.map(x => new VariableExpression({
6✔
2911
                            name: util.cloneToken(x.tokens.name)
2912
                        })),
2913
                        closingParen: createToken(TokenKind.RightParen)
2914
                    })
2915
                });
2916
                body = [
12✔
2917
                    new MethodStatement({
2918
                        name: createIdentifier('new'),
2919
                        func: new FunctionExpression({
2920
                            parameters: params.map(x => x.clone()),
6✔
2921
                            body: new Block({
2922
                                statements: [call]
2923
                            }),
2924
                            functionType: createToken(TokenKind.Sub),
2925
                            endFunctionType: createToken(TokenKind.EndSub),
2926
                            leftParen: createToken(TokenKind.LeftParen),
2927
                            rightParen: createToken(TokenKind.RightParen)
2928
                        })
2929
                    }),
2930
                    ...this.body
2931
                ];
2932
            }
2933
        }
2934

2935
        for (let statement of body) {
54✔
2936
            //is field statement
2937
            if (isFieldStatement(statement)) {
84✔
2938
                //do nothing with class fields in this situation, they are handled elsewhere
2939
                continue;
15✔
2940

2941
                //methods
2942
            } else if (isMethodStatement(statement)) {
69!
2943

2944
                //store overridden parent methods as super{parentIndex}_{methodName}
2945
                if (
69✔
2946
                    //is override method
2947
                    statement.tokens.override ||
188✔
2948
                    //is constructor function in child class
2949
                    (statement.tokens.name.text.toLowerCase() === 'new' && ancestors[0])
2950
                ) {
2951
                    result.push(
25✔
2952
                        `instance.super${parentClassIndex}_${statement.tokens.name.text} = instance.${statement.tokens.name.text}`,
2953
                        state.newline,
2954
                        state.indent()
2955
                    );
2956
                }
2957

2958
                state.classStatement = this;
69✔
2959
                state.skipLeadingComments = true;
69✔
2960
                //add leading comments
2961
                if (statement.leadingTrivia.filter(token => token.kind === TokenKind.Comment).length > 0) {
82✔
2962
                    result.push(
2✔
2963
                        ...state.transpileComments(statement.leadingTrivia),
2964
                        state.indent()
2965
                    );
2966
                }
2967
                result.push(
69✔
2968
                    'instance.',
2969
                    state.transpileToken(statement.tokens.name),
2970
                    ' = ',
2971
                    ...statement.transpile(state),
2972
                    state.newline,
2973
                    state.indent()
2974
                );
2975
                state.skipLeadingComments = false;
69✔
2976
                delete state.classStatement;
69✔
2977
            } else {
2978
                //other random statements (probably just comments)
UNCOV
2979
                result.push(
×
2980
                    ...statement.transpile(state),
2981
                    state.newline,
2982
                    state.indent()
2983
                );
2984
            }
2985
        }
2986
        //return the instance
2987
        result.push('return instance\n');
54✔
2988
        state.blockDepth--;
54✔
2989
        result.push(state.indent());
54✔
2990
        result.push(`end function`);
54✔
2991
        return result;
54✔
2992
    }
2993

2994
    /**
2995
     * The class function is the function with the same name as the class. This is the function that
2996
     * consumers should call to create a new instance of that class.
2997
     * This invokes the builder, gets an instance of the class, then invokes the "new" function on that class.
2998
     */
2999
    private getTranspiledClassFunction(state: BrsTranspileState) {
3000
        let result: TranspileResult = state.transpileAnnotations(this);
54✔
3001
        const constructorFunction = this.getConstructorFunction();
54✔
3002
        let constructorParams = [];
54✔
3003
        if (constructorFunction) {
54✔
3004
            constructorParams = constructorFunction.func.parameters;
23✔
3005
        } else {
3006
            constructorParams = this.getConstructorParams(this.getAncestors(state));
31✔
3007
        }
3008

3009
        result.push(
54✔
3010
            state.transpileLeadingComments(this.tokens.class),
3011
            state.sourceNode(this.tokens.class, 'function'),
3012
            state.sourceNode(this.tokens.class, ' '),
3013
            state.sourceNode(this.tokens.name, this.getName(ParseMode.BrightScript)),
3014
            `(`
3015
        );
3016
        let i = 0;
54✔
3017
        for (let param of constructorParams) {
54✔
3018
            if (i > 0) {
17✔
3019
                result.push(', ');
4✔
3020
            }
3021
            result.push(
17✔
3022
                param.transpile(state)
3023
            );
3024
            i++;
17✔
3025
        }
3026
        result.push(
54✔
3027
            ')',
3028
            '\n'
3029
        );
3030

3031
        state.blockDepth++;
54✔
3032
        result.push(state.indent());
54✔
3033
        result.push(`instance = ${this.getBuilderName(this.getName(ParseMode.BrightScript)!)}()\n`);
54✔
3034

3035
        result.push(state.indent());
54✔
3036
        result.push(`instance.new(`);
54✔
3037

3038
        //append constructor arguments
3039
        i = 0;
54✔
3040
        for (let param of constructorParams) {
54✔
3041
            if (i > 0) {
17✔
3042
                result.push(', ');
4✔
3043
            }
3044
            result.push(
17✔
3045
                state.transpileToken(param.tokens.name)
3046
            );
3047
            i++;
17✔
3048
        }
3049
        result.push(
54✔
3050
            ')',
3051
            '\n'
3052
        );
3053

3054
        result.push(state.indent());
54✔
3055
        result.push(`return instance\n`);
54✔
3056

3057
        state.blockDepth--;
54✔
3058
        result.push(state.indent());
54✔
3059
        result.push(`end function`);
54✔
3060
        return result;
54✔
3061
    }
3062

3063
    walk(visitor: WalkVisitor, options: WalkOptions) {
3064
        //visitor-less walk function to do parent linking
3065
        walk(this, 'parentClassName', null, options);
2,772✔
3066

3067
        if (options.walkMode & InternalWalkMode.walkStatements) {
2,772!
3068
            walkArray(this.body, visitor, options, this);
2,772✔
3069
        }
3070
    }
3071

3072
    getType(options: GetTypeOptions) {
3073
        const superClass = this.parentClassName?.getType(options) as ClassType;
561✔
3074

3075
        const resultType = new ClassType(this.getName(ParseMode.BrighterScript), superClass);
561✔
3076

3077
        for (const statement of this.methods) {
561✔
3078
            const funcType = statement?.func.getType({ ...options, typeChain: undefined }); //no typechain needed
297!
3079
            let flag = SymbolTypeFlag.runtime;
297✔
3080
            if (statement.accessModifier?.kind === TokenKind.Private) {
297✔
3081
                flag |= SymbolTypeFlag.private;
9✔
3082
            }
3083
            if (statement.accessModifier?.kind === TokenKind.Protected) {
297✔
3084
                flag |= SymbolTypeFlag.protected;
8✔
3085
            }
3086
            resultType.addMember(statement?.tokens.name?.text, { definingNode: statement }, funcType, flag);
297!
3087
        }
3088
        for (const statement of this.fields) {
561✔
3089
            const fieldType = statement.getType({ ...options, typeChain: undefined }); //no typechain needed
283✔
3090
            let flag = SymbolTypeFlag.runtime;
283✔
3091
            if (statement.isOptional) {
283✔
3092
                flag |= SymbolTypeFlag.optional;
7✔
3093
            }
3094
            if (statement.tokens.accessModifier?.kind === TokenKind.Private) {
283✔
3095
                flag |= SymbolTypeFlag.private;
20✔
3096
            }
3097
            if (statement.tokens.accessModifier?.kind === TokenKind.Protected) {
283✔
3098
                flag |= SymbolTypeFlag.protected;
9✔
3099
            }
3100
            resultType.addMember(statement?.tokens.name?.text, { definingNode: statement, isInstance: true }, fieldType, flag);
283!
3101
        }
3102
        options.typeChain?.push(new TypeChainEntry({ name: resultType.name, type: resultType, data: options.data, astNode: this }));
561✔
3103
        return resultType;
561✔
3104
    }
3105

3106
    public clone() {
3107
        return this.finalizeClone(
11✔
3108
            new ClassStatement({
3109
                class: util.cloneToken(this.tokens.class),
3110
                name: util.cloneToken(this.tokens.name),
3111
                body: this.body?.map(x => x?.clone()),
11✔
3112
                endClass: util.cloneToken(this.tokens.endClass),
3113
                extends: util.cloneToken(this.tokens.extends),
3114
                parentClassName: this.parentClassName?.clone()
33✔
3115
            }),
3116
            ['body', 'parentClassName']
3117
        );
3118
    }
3119
}
3120

3121
const accessModifiers = [
1✔
3122
    TokenKind.Public,
3123
    TokenKind.Protected,
3124
    TokenKind.Private
3125
];
3126
export class MethodStatement extends FunctionStatement {
1✔
3127
    constructor(
3128
        options: {
3129
            modifiers?: Token | Token[];
3130
            name: Identifier;
3131
            func: FunctionExpression;
3132
            override?: Token;
3133
        }
3134
    ) {
3135
        super(options);
415✔
3136
        if (options.modifiers) {
415✔
3137
            if (Array.isArray(options.modifiers)) {
40✔
3138
                this.modifiers.push(...options.modifiers);
4✔
3139
            } else {
3140
                this.modifiers.push(options.modifiers);
36✔
3141
            }
3142
        }
3143
        this.tokens = {
415✔
3144
            ...this.tokens,
3145
            override: options.override
3146
        };
3147
        this.location = util.createBoundingLocation(
415✔
3148
            ...(this.modifiers),
3149
            util.createBoundingLocationFromTokens(this.tokens),
3150
            this.func
3151
        );
3152
    }
3153

3154
    public readonly kind = AstNodeKind.MethodStatement as AstNodeKind;
415✔
3155

3156
    public readonly modifiers: Token[] = [];
415✔
3157

3158
    public readonly tokens: {
3159
        readonly name: Identifier;
3160
        readonly override?: Token;
3161
    };
3162

3163
    public get accessModifier() {
3164
        return this.modifiers.find(x => accessModifiers.includes(x.kind));
704✔
3165
    }
3166

3167
    public readonly location: Location | undefined;
3168

3169
    /**
3170
     * Get the name of this method.
3171
     */
3172
    public getName(parseMode: ParseMode) {
3173
        return this.tokens.name.text;
376✔
3174
    }
3175

3176
    public get leadingTrivia(): Token[] {
3177
        return this.func.leadingTrivia;
834✔
3178
    }
3179

3180
    transpile(state: BrsTranspileState) {
3181
        if (this.tokens.name.text.toLowerCase() === 'new') {
69✔
3182
            this.ensureSuperConstructorCall(state);
54✔
3183
            //TODO we need to undo this at the bottom of this method
3184
            this.injectFieldInitializersForConstructor(state);
54✔
3185
        }
3186
        //TODO - remove type information from these methods because that doesn't work
3187
        //convert the `super` calls into the proper methods
3188
        const parentClassIndex = state.classStatement.getParentClassIndex(state);
69✔
3189
        const visitor = createVisitor({
69✔
3190
            VariableExpression: e => {
3191
                if (e.tokens.name.text.toLocaleLowerCase() === 'super') {
73✔
3192
                    state.editor.setProperty(e.tokens.name, 'text', `m.super${parentClassIndex}_new`);
21✔
3193
                }
3194
            },
3195
            DottedGetExpression: e => {
3196
                const beginningVariable = util.findBeginningVariableExpression(e);
30✔
3197
                const lowerName = beginningVariable?.getName(ParseMode.BrighterScript).toLowerCase();
30!
3198
                if (lowerName === 'super') {
30✔
3199
                    state.editor.setProperty(beginningVariable.tokens.name, 'text', 'm');
7✔
3200
                    state.editor.setProperty(e.tokens.name, 'text', `super${parentClassIndex}_${e.tokens.name.text}`);
7✔
3201
                }
3202
            }
3203
        });
3204
        const walkOptions: WalkOptions = { walkMode: WalkMode.visitExpressions };
69✔
3205
        for (const statement of this.func.body.statements) {
69✔
3206
            visitor(statement, undefined);
67✔
3207
            statement.walk(visitor, walkOptions);
67✔
3208
        }
3209
        return this.func.transpile(state);
69✔
3210
    }
3211

3212
    getTypedef(state: BrsTranspileState) {
3213
        const result: TranspileResult = [];
23✔
3214
        for (let comment of util.getLeadingComments(this) ?? []) {
23!
UNCOV
3215
            result.push(
×
3216
                comment.text,
3217
                state.newline,
3218
                state.indent()
3219
            );
3220
        }
3221
        for (let annotation of this.annotations ?? []) {
23✔
3222
            result.push(
2✔
3223
                ...annotation.getTypedef(state),
3224
                state.newline,
3225
                state.indent()
3226
            );
3227
        }
3228
        if (this.accessModifier) {
23✔
3229
            result.push(
8✔
3230
                this.accessModifier.text,
3231
                ' '
3232
            );
3233
        }
3234
        if (this.tokens.override) {
23✔
3235
            result.push('override ');
1✔
3236
        }
3237
        result.push(
23✔
3238
            ...this.func.getTypedef(state)
3239
        );
3240
        return result;
23✔
3241
    }
3242

3243
    /**
3244
     * All child classes must call the parent constructor. The type checker will warn users when they don't call it in their own class,
3245
     * but we still need to call it even if they have omitted it. This injects the super call if it's missing
3246
     */
3247
    private ensureSuperConstructorCall(state: BrsTranspileState) {
3248
        //if this class doesn't extend another class, quit here
3249
        if (state.classStatement!.getAncestors(state).length === 0) {
54✔
3250
            return;
33✔
3251
        }
3252

3253
        //check whether any calls to super exist
3254
        let containsSuperCall =
3255
            this.func.body.statements.findIndex((x) => {
21✔
3256
                //is a call statement
3257
                return isExpressionStatement(x) && isCallExpression(x.expression) &&
21✔
3258
                    //is a call to super
3259
                    util.findBeginningVariableExpression(x.expression.callee as any).tokens.name?.text.toLowerCase() === 'super';
60!
3260
            }) !== -1;
3261

3262
        //if a call to super exists, quit here
3263
        if (containsSuperCall) {
21✔
3264
            return;
20✔
3265
        }
3266

3267
        //this is a child class, and the constructor doesn't contain a call to super. Inject one
3268
        const superCall = new ExpressionStatement({
1✔
3269
            expression: new CallExpression({
3270
                callee: new VariableExpression({
3271
                    name: {
3272
                        kind: TokenKind.Identifier,
3273
                        text: 'super',
3274
                        isReserved: false,
3275
                        location: state.classStatement.tokens.name.location,
3276
                        leadingWhitespace: '',
3277
                        leadingTrivia: []
3278
                    }
3279
                }),
3280
                openingParen: {
3281
                    kind: TokenKind.LeftParen,
3282
                    text: '(',
3283
                    isReserved: false,
3284
                    location: state.classStatement.tokens.name.location,
3285
                    leadingWhitespace: '',
3286
                    leadingTrivia: []
3287
                },
3288
                closingParen: {
3289
                    kind: TokenKind.RightParen,
3290
                    text: ')',
3291
                    isReserved: false,
3292
                    location: state.classStatement.tokens.name.location,
3293
                    leadingWhitespace: '',
3294
                    leadingTrivia: []
3295
                },
3296
                args: []
3297
            })
3298
        });
3299
        state.editor.arrayUnshift(this.func.body.statements, superCall);
1✔
3300
    }
3301

3302
    /**
3303
     * Inject field initializers at the top of the `new` function (after any present `super()` call)
3304
     */
3305
    private injectFieldInitializersForConstructor(state: BrsTranspileState) {
3306
        let startingIndex = state.classStatement!.hasParentClass() ? 1 : 0;
54✔
3307

3308
        let newStatements = [] as Statement[];
54✔
3309
        //insert the field initializers in order
3310
        for (let field of state.classStatement!.fields) {
54✔
3311
            let thisQualifiedName = { ...field.tokens.name };
15✔
3312
            thisQualifiedName.text = 'm.' + field.tokens.name?.text;
15!
3313
            const fieldAssignment = field.initialValue
15✔
3314
                ? new AssignmentStatement({
15✔
3315
                    equals: field.tokens.equals,
3316
                    name: thisQualifiedName,
3317
                    value: field.initialValue
3318
                })
3319
                : new AssignmentStatement({
3320
                    equals: createToken(TokenKind.Equal, '=', field.tokens.name.location),
3321
                    name: thisQualifiedName,
3322
                    //if there is no initial value, set the initial value to `invalid`
3323
                    value: createInvalidLiteral('invalid', field.tokens.name.location)
3324
                });
3325
            // Add parent so namespace lookups work
3326
            fieldAssignment.parent = state.classStatement;
15✔
3327
            newStatements.push(fieldAssignment);
15✔
3328
        }
3329
        state.editor.arraySplice(this.func.body.statements, startingIndex, 0, ...newStatements);
54✔
3330
    }
3331

3332
    walk(visitor: WalkVisitor, options: WalkOptions) {
3333
        if (options.walkMode & InternalWalkMode.walkExpressions) {
2,270✔
3334
            walk(this, 'func', visitor, options);
1,819✔
3335
        }
3336
    }
3337

3338
    public clone() {
3339
        return this.finalizeClone(
5✔
3340
            new MethodStatement({
3341
                modifiers: this.modifiers?.map(m => util.cloneToken(m)),
1✔
3342
                name: util.cloneToken(this.tokens.name),
3343
                func: this.func?.clone(),
15✔
3344
                override: util.cloneToken(this.tokens.override)
3345
            }),
3346
            ['func']
3347
        );
3348
    }
3349
}
3350

3351
export class FieldStatement extends Statement implements TypedefProvider {
1✔
3352
    constructor(options: {
3353
        accessModifier?: Token;
3354
        name: Identifier;
3355
        as?: Token;
3356
        typeExpression?: TypeExpression;
3357
        equals?: Token;
3358
        initialValue?: Expression;
3359
        optional?: Token;
3360
    }) {
3361
        super();
344✔
3362
        this.tokens = {
344✔
3363
            accessModifier: options.accessModifier,
3364
            name: options.name,
3365
            as: options.as,
3366
            equals: options.equals,
3367
            optional: options.optional
3368
        };
3369
        this.typeExpression = options.typeExpression;
344✔
3370
        this.initialValue = options.initialValue;
344✔
3371

3372
        this.location = util.createBoundingLocation(
344✔
3373
            util.createBoundingLocationFromTokens(this.tokens),
3374
            this.typeExpression,
3375
            this.initialValue
3376
        );
3377
    }
3378

3379
    public readonly tokens: {
3380
        readonly accessModifier?: Token;
3381
        readonly name: Identifier;
3382
        readonly as?: Token;
3383
        readonly equals?: Token;
3384
        readonly optional?: Token;
3385
    };
3386

3387
    public readonly typeExpression?: TypeExpression;
3388
    public readonly initialValue?: Expression;
3389

3390
    public readonly kind = AstNodeKind.FieldStatement;
344✔
3391

3392
    /**
3393
     * Derive a ValueKind from the type token, or the initial value.
3394
     * Defaults to `DynamicType`
3395
     */
3396
    getType(options: GetTypeOptions) {
3397
        let initialValueType = this.initialValue?.getType({ ...options, flags: SymbolTypeFlag.runtime });
333✔
3398

3399
        if (isInvalidType(initialValueType) || isVoidType(initialValueType) || isUninitializedType(initialValueType)) {
333✔
3400
            initialValueType = undefined;
4✔
3401
        }
3402

3403
        return this.typeExpression?.getType({ ...options, flags: SymbolTypeFlag.typetime }) ??
333✔
3404
            util.getDefaultTypeFromValueType(initialValueType) ??
333✔
3405
            DynamicType.instance;
3406
    }
3407

3408
    public readonly location: Location | undefined;
3409

3410
    public get leadingTrivia(): Token[] {
3411
        return this.tokens.accessModifier?.leadingTrivia ?? this.tokens.optional?.leadingTrivia ?? this.tokens.name.leadingTrivia;
671✔
3412
    }
3413

3414
    public get isOptional() {
3415
        return !!this.tokens.optional;
302✔
3416
    }
3417

3418
    transpile(state: BrsTranspileState): TranspileResult {
UNCOV
3419
        throw new Error('transpile not implemented for ' + Object.getPrototypeOf(this).constructor.name);
×
3420
    }
3421

3422
    getTypedef(state: BrsTranspileState) {
3423
        const result = [];
12✔
3424
        if (this.tokens.name) {
12!
3425
            for (let comment of util.getLeadingComments(this) ?? []) {
12!
UNCOV
3426
                result.push(
×
3427
                    comment.text,
3428
                    state.newline,
3429
                    state.indent()
3430
                );
3431
            }
3432
            for (let annotation of this.annotations ?? []) {
12✔
3433
                result.push(
2✔
3434
                    ...annotation.getTypedef(state),
3435
                    state.newline,
3436
                    state.indent()
3437
                );
3438
            }
3439

3440
            let type = this.getType({ flags: SymbolTypeFlag.typetime });
12✔
3441
            if (isInvalidType(type) || isVoidType(type) || isUninitializedType(type)) {
12!
UNCOV
3442
                type = DynamicType.instance;
×
3443
            }
3444

3445
            result.push(
12✔
3446
                this.tokens.accessModifier?.text ?? 'public',
72✔
3447
                ' '
3448
            );
3449
            if (this.isOptional) {
12!
UNCOV
3450
                result.push(this.tokens.optional.text, ' ');
×
3451
            }
3452
            result.push(this.tokens.name?.text,
12!
3453
                ' as ',
3454
                type.toTypeString()
3455
            );
3456
        }
3457
        return result;
12✔
3458
    }
3459

3460
    walk(visitor: WalkVisitor, options: WalkOptions) {
3461
        if (options.walkMode & InternalWalkMode.walkExpressions) {
1,687✔
3462
            walk(this, 'typeExpression', visitor, options);
1,473✔
3463
            walk(this, 'initialValue', visitor, options);
1,473✔
3464
        }
3465
    }
3466

3467
    public clone() {
3468
        return this.finalizeClone(
5✔
3469
            new FieldStatement({
3470
                accessModifier: util.cloneToken(this.tokens.accessModifier),
3471
                name: util.cloneToken(this.tokens.name),
3472
                as: util.cloneToken(this.tokens.as),
3473
                typeExpression: this.typeExpression?.clone(),
15✔
3474
                equals: util.cloneToken(this.tokens.equals),
3475
                initialValue: this.initialValue?.clone(),
15✔
3476
                optional: util.cloneToken(this.tokens.optional)
3477
            }),
3478
            ['initialValue']
3479
        );
3480
    }
3481
}
3482

3483
export type MemberStatement = FieldStatement | MethodStatement;
3484

3485
export class TryCatchStatement extends Statement {
1✔
3486
    constructor(options?: {
3487
        try?: Token;
3488
        endTry?: Token;
3489
        tryBranch?: Block;
3490
        catchStatement?: CatchStatement;
3491
    }) {
3492
        super();
40✔
3493
        this.tokens = {
40✔
3494
            try: options.try,
3495
            endTry: options.endTry
3496
        };
3497
        this.tryBranch = options.tryBranch;
40✔
3498
        this.catchStatement = options.catchStatement;
40✔
3499
        this.location = util.createBoundingLocation(
40✔
3500
            this.tokens.try,
3501
            this.tryBranch,
3502
            this.catchStatement,
3503
            this.tokens.endTry
3504
        );
3505
    }
3506

3507
    public readonly tokens: {
3508
        readonly try?: Token;
3509
        readonly endTry?: Token;
3510
    };
3511

3512
    public readonly tryBranch: Block;
3513
    public readonly catchStatement: CatchStatement;
3514

3515
    public readonly kind = AstNodeKind.TryCatchStatement;
40✔
3516

3517
    public readonly location: Location | undefined;
3518

3519
    public transpile(state: BrsTranspileState): TranspileResult {
3520
        return [
7✔
3521
            state.transpileToken(this.tokens.try, 'try'),
3522
            ...this.tryBranch.transpile(state),
3523
            state.newline,
3524
            state.indent(),
3525
            ...(this.catchStatement?.transpile(state) ?? ['catch']),
42!
3526
            state.newline,
3527
            state.indent(),
3528
            state.transpileToken(this.tokens.endTry!, 'end try')
3529
        ];
3530
    }
3531

3532
    public walk(visitor: WalkVisitor, options: WalkOptions) {
3533
        if (this.tryBranch && options.walkMode & InternalWalkMode.walkStatements) {
93!
3534
            walk(this, 'tryBranch', visitor, options);
93✔
3535
            walk(this, 'catchStatement', visitor, options);
93✔
3536
        }
3537
    }
3538

3539
    public get leadingTrivia(): Token[] {
3540
        return this.tokens.try?.leadingTrivia ?? [];
88!
3541
    }
3542

3543
    public get endTrivia(): Token[] {
3544
        return this.tokens.endTry?.leadingTrivia ?? [];
1!
3545
    }
3546

3547
    public clone() {
3548
        return this.finalizeClone(
3✔
3549
            new TryCatchStatement({
3550
                try: util.cloneToken(this.tokens.try),
3551
                endTry: util.cloneToken(this.tokens.endTry),
3552
                tryBranch: this.tryBranch?.clone(),
9✔
3553
                catchStatement: this.catchStatement?.clone()
9✔
3554
            }),
3555
            ['tryBranch', 'catchStatement']
3556
        );
3557
    }
3558
}
3559

3560
export class CatchStatement extends Statement {
1✔
3561
    constructor(options?: {
3562
        catch?: Token;
3563
        exceptionVariableExpression?: Expression;
3564
        catchBranch?: Block;
3565
    }) {
3566
        super();
37✔
3567
        this.tokens = {
37✔
3568
            catch: options?.catch
111!
3569
        };
3570
        this.exceptionVariableExpression = options?.exceptionVariableExpression;
37!
3571
        this.catchBranch = options?.catchBranch;
37!
3572
        this.location = util.createBoundingLocation(
37✔
3573
            this.tokens.catch,
3574
            this.exceptionVariableExpression,
3575
            this.catchBranch
3576
        );
3577
    }
3578

3579
    public readonly tokens: {
3580
        readonly catch?: Token;
3581
    };
3582

3583
    public readonly exceptionVariableExpression?: Expression;
3584

3585
    public readonly catchBranch?: Block;
3586

3587
    public readonly kind = AstNodeKind.CatchStatement;
37✔
3588

3589
    public readonly location: Location | undefined;
3590

3591
    public transpile(state: BrsTranspileState): TranspileResult {
3592
        return [
7✔
3593
            state.transpileToken(this.tokens.catch, 'catch'),
3594
            ' ',
3595
            this.exceptionVariableExpression?.transpile(state) ?? [
42✔
3596
                //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
3597
                this.getSymbolTable()?.hasSymbol('e', SymbolTypeFlag.runtime)
6!
3598
                    ? '__bsc_error'
2✔
3599
                    : 'e'
3600
            ],
3601
            ...(this.catchBranch?.transpile(state) ?? [])
42!
3602
        ];
3603
    }
3604

3605
    public walk(visitor: WalkVisitor, options: WalkOptions) {
3606
        if (this.catchBranch && options.walkMode & InternalWalkMode.walkStatements) {
90!
3607
            walk(this, 'catchBranch', visitor, options);
90✔
3608
        }
3609
    }
3610

3611
    public get leadingTrivia(): Token[] {
3612
        return this.tokens.catch?.leadingTrivia ?? [];
50!
3613
    }
3614

3615
    public clone() {
3616
        return this.finalizeClone(
2✔
3617
            new CatchStatement({
3618
                catch: util.cloneToken(this.tokens.catch),
3619
                exceptionVariableExpression: this.exceptionVariableExpression?.clone(),
6!
3620
                catchBranch: this.catchBranch?.clone()
6✔
3621
            }),
3622
            ['catchBranch']
3623
        );
3624
    }
3625
}
3626

3627
export class ThrowStatement extends Statement {
1✔
3628
    constructor(options?: {
3629
        throw?: Token;
3630
        expression?: Expression;
3631
    }) {
3632
        super();
14✔
3633
        this.tokens = {
14✔
3634
            throw: options.throw
3635
        };
3636
        this.expression = options.expression;
14✔
3637
        this.location = util.createBoundingLocation(
14✔
3638
            this.tokens.throw,
3639
            this.expression
3640
        );
3641
    }
3642

3643
    public readonly tokens: {
3644
        readonly throw?: Token;
3645
    };
3646
    public readonly expression?: Expression;
3647

3648
    public readonly kind = AstNodeKind.ThrowStatement;
14✔
3649

3650
    public readonly location: Location | undefined;
3651

3652
    public transpile(state: BrsTranspileState) {
3653
        const result = [
5✔
3654
            state.transpileToken(this.tokens.throw, 'throw'),
3655
            ' '
3656
        ] as TranspileResult;
3657

3658
        //if we have an expression, transpile it
3659
        if (this.expression) {
5✔
3660
            result.push(
4✔
3661
                ...this.expression.transpile(state)
3662
            );
3663

3664
            //no expression found. Rather than emit syntax errors, provide a generic error message
3665
        } else {
3666
            result.push('"User-specified exception"');
1✔
3667
        }
3668
        return result;
5✔
3669
    }
3670

3671
    public walk(visitor: WalkVisitor, options: WalkOptions) {
3672
        if (this.expression && options.walkMode & InternalWalkMode.walkExpressions) {
38✔
3673
            walk(this, 'expression', visitor, options);
30✔
3674
        }
3675
    }
3676

3677
    public get leadingTrivia(): Token[] {
3678
        return this.tokens.throw?.leadingTrivia ?? [];
54!
3679
    }
3680

3681
    public clone() {
3682
        return this.finalizeClone(
2✔
3683
            new ThrowStatement({
3684
                throw: util.cloneToken(this.tokens.throw),
3685
                expression: this.expression?.clone()
6✔
3686
            }),
3687
            ['expression']
3688
        );
3689
    }
3690
}
3691

3692

3693
export class EnumStatement extends Statement implements TypedefProvider {
1✔
3694
    constructor(options: {
3695
        enum?: Token;
3696
        name: Identifier;
3697
        endEnum?: Token;
3698
        body: Array<EnumMemberStatement>;
3699
    }) {
3700
        super();
187✔
3701
        this.tokens = {
187✔
3702
            enum: options.enum,
3703
            name: options.name,
3704
            endEnum: options.endEnum
3705
        };
3706
        this.symbolTable = new SymbolTable('Enum');
187✔
3707
        this.body = options.body ?? [];
187✔
3708
    }
3709

3710
    public readonly tokens: {
3711
        readonly enum?: Token;
3712
        readonly name: Identifier;
3713
        readonly endEnum?: Token;
3714
    };
3715
    public readonly body: Array<EnumMemberStatement>;
3716

3717
    public readonly kind = AstNodeKind.EnumStatement;
187✔
3718

3719
    public get location(): Location | undefined {
3720
        return util.createBoundingLocation(
175✔
3721
            this.tokens.enum,
3722
            this.tokens.name,
3723
            ...this.body,
3724
            this.tokens.endEnum
3725
        );
3726
    }
3727

3728
    public getMembers() {
3729
        const result = [] as EnumMemberStatement[];
365✔
3730
        for (const statement of this.body) {
365✔
3731
            if (isEnumMemberStatement(statement)) {
763!
3732
                result.push(statement);
763✔
3733
            }
3734
        }
3735
        return result;
365✔
3736
    }
3737

3738
    public get leadingTrivia(): Token[] {
3739
        return this.tokens.enum?.leadingTrivia;
527!
3740
    }
3741

3742
    public get endTrivia(): Token[] {
UNCOV
3743
        return this.tokens.endEnum?.leadingTrivia ?? [];
×
3744
    }
3745

3746
    /**
3747
     * Get a map of member names and their values.
3748
     * All values are stored as their AST LiteralExpression representation (i.e. string enum values include the wrapping quotes)
3749
     */
3750
    public getMemberValueMap() {
3751
        const result = new Map<string, string>();
59✔
3752
        const members = this.getMembers();
59✔
3753
        let currentIntValue = 0;
59✔
3754
        for (const member of members) {
59✔
3755
            //if there is no value, assume an integer and increment the int counter
3756
            if (!member.value) {
148✔
3757
                result.set(member.name?.toLowerCase(), currentIntValue.toString());
33!
3758
                currentIntValue++;
33✔
3759

3760
                //if explicit integer value, use it and increment the int counter
3761
            } else if (isLiteralExpression(member.value) && member.value.tokens.value.kind === TokenKind.IntegerLiteral) {
115✔
3762
                //try parsing as integer literal, then as hex integer literal.
3763
                let tokenIntValue = util.parseInt(member.value.tokens.value.text) ?? util.parseInt(member.value.tokens.value.text.replace(/&h/i, '0x'));
29✔
3764
                if (tokenIntValue !== undefined) {
29!
3765
                    currentIntValue = tokenIntValue;
29✔
3766
                    currentIntValue++;
29✔
3767
                }
3768
                result.set(member.name?.toLowerCase(), member.value.tokens.value.text);
29!
3769

3770
                //simple unary expressions (like `-1`)
3771
            } else if (isUnaryExpression(member.value) && isLiteralExpression(member.value.right)) {
86✔
3772
                result.set(member.name?.toLowerCase(), member.value.tokens.operator.text + member.value.right.tokens.value.text);
1!
3773

3774
                //all other values
3775
            } else {
3776
                result.set(member.name?.toLowerCase(), (member.value as LiteralExpression)?.tokens?.value?.text ?? 'invalid');
85!
3777
            }
3778
        }
3779
        return result;
59✔
3780
    }
3781

3782
    public getMemberValue(name: string) {
3783
        return this.getMemberValueMap().get(name.toLowerCase());
56✔
3784
    }
3785

3786
    /**
3787
     * The name of the enum (without the namespace prefix)
3788
     */
3789
    public get name() {
3790
        return this.tokens.name?.text;
1!
3791
    }
3792

3793
    /**
3794
     * The name of the enum WITH its leading namespace (if applicable)
3795
     */
3796
    public get fullName() {
3797
        const name = this.tokens.name?.text;
739!
3798
        if (name) {
739!
3799
            const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
739✔
3800

3801
            if (namespace) {
739✔
3802
                let namespaceName = namespace.getName(ParseMode.BrighterScript);
224✔
3803
                return `${namespaceName}.${name}`;
224✔
3804
            } else {
3805
                return name;
515✔
3806
            }
3807
        } else {
3808
            //return undefined which will allow outside callers to know that this doesn't have a name
UNCOV
3809
            return undefined;
×
3810
        }
3811
    }
3812

3813
    transpile(state: BrsTranspileState) {
3814
        //enum declarations don't exist at runtime, so just transpile comments and trivia
3815
        return [
28✔
3816
            state.transpileAnnotations(this),
3817
            state.transpileLeadingComments(this.tokens.enum)
3818
        ];
3819
    }
3820

3821
    getTypedef(state: BrsTranspileState) {
3822
        const result = [] as TranspileResult;
1✔
3823
        for (let comment of util.getLeadingComments(this) ?? []) {
1!
UNCOV
3824
            result.push(
×
3825
                comment.text,
3826
                state.newline,
3827
                state.indent()
3828
            );
3829
        }
3830
        for (let annotation of this.annotations ?? []) {
1!
UNCOV
3831
            result.push(
×
3832
                ...annotation.getTypedef(state),
3833
                state.newline,
3834
                state.indent()
3835
            );
3836
        }
3837
        result.push(
1✔
3838
            this.tokens.enum?.text ?? 'enum',
6!
3839
            ' ',
3840
            this.tokens.name.text
3841
        );
3842
        result.push(state.newline);
1✔
3843
        state.blockDepth++;
1✔
3844
        for (const member of this.body) {
1✔
3845
            if (isTypedefProvider(member)) {
1!
3846
                result.push(
1✔
3847
                    state.indent(),
3848
                    ...member.getTypedef(state),
3849
                    state.newline
3850
                );
3851
            }
3852
        }
3853
        state.blockDepth--;
1✔
3854
        result.push(
1✔
3855
            state.indent(),
3856
            this.tokens.endEnum?.text ?? 'end enum'
6!
3857
        );
3858
        return result;
1✔
3859
    }
3860

3861
    walk(visitor: WalkVisitor, options: WalkOptions) {
3862
        if (options.walkMode & InternalWalkMode.walkStatements) {
1,162!
3863
            walkArray(this.body, visitor, options, this);
1,162✔
3864

3865
        }
3866
    }
3867

3868
    getType(options: GetTypeOptions) {
3869
        const members = this.getMembers();
153✔
3870

3871
        const resultType = new EnumType(
153✔
3872
            this.fullName,
3873
            members[0]?.getType(options).underlyingType
459✔
3874
        );
3875
        resultType.pushMemberProvider(() => this.getSymbolTable());
646✔
3876
        for (const statement of members) {
153✔
3877
            const memberType = statement.getType({ ...options, typeChain: undefined });
306✔
3878
            memberType.parentEnumType = resultType;
306✔
3879
            resultType.addMember(statement?.tokens?.name?.text, { definingNode: statement }, memberType, SymbolTypeFlag.runtime);
306!
3880
        }
3881
        return resultType;
153✔
3882
    }
3883

3884
    public clone() {
3885
        return this.finalizeClone(
6✔
3886
            new EnumStatement({
3887
                enum: util.cloneToken(this.tokens.enum),
3888
                name: util.cloneToken(this.tokens.name),
3889
                endEnum: util.cloneToken(this.tokens.endEnum),
3890
                body: this.body?.map(x => x?.clone())
4✔
3891
            }),
3892
            ['body']
3893
        );
3894
    }
3895
}
3896

3897
export class EnumMemberStatement extends Statement implements TypedefProvider {
1✔
3898
    public constructor(options: {
3899
        name: Identifier;
3900
        equals?: Token;
3901
        value?: Expression;
3902
    }) {
3903
        super();
360✔
3904
        this.tokens = {
360✔
3905
            name: options.name,
3906
            equals: options.equals
3907
        };
3908
        this.value = options.value;
360✔
3909
    }
3910

3911
    public readonly tokens: {
3912
        readonly name: Identifier;
3913
        readonly equals?: Token;
3914
    };
3915
    public readonly value?: Expression;
3916

3917
    public readonly kind = AstNodeKind.EnumMemberStatement;
360✔
3918

3919
    public get location() {
3920
        return util.createBoundingLocation(
592✔
3921
            this.tokens.name,
3922
            this.tokens.equals,
3923
            this.value
3924
        );
3925
    }
3926

3927
    /**
3928
     * The name of the member
3929
     */
3930
    public get name() {
3931
        return this.tokens.name.text;
447✔
3932
    }
3933

3934
    public get leadingTrivia(): Token[] {
3935
        return this.tokens.name.leadingTrivia;
1,429✔
3936
    }
3937

3938
    public transpile(state: BrsTranspileState): TranspileResult {
UNCOV
3939
        return [];
×
3940
    }
3941

3942
    getTypedef(state: BrsTranspileState): TranspileResult {
3943
        const result: TranspileResult = [];
1✔
3944
        for (let comment of util.getLeadingComments(this) ?? []) {
1!
UNCOV
3945
            result.push(
×
3946
                comment.text,
3947
                state.newline,
3948
                state.indent()
3949
            );
3950
        }
3951
        result.push(this.tokens.name.text);
1✔
3952
        if (this.tokens.equals) {
1!
UNCOV
3953
            result.push(' ', this.tokens.equals.text, ' ');
×
UNCOV
3954
            if (this.value) {
×
3955
                result.push(
×
3956
                    ...this.value.transpile(state)
3957
                );
3958
            }
3959
        }
3960
        return result;
1✔
3961
    }
3962

3963
    walk(visitor: WalkVisitor, options: WalkOptions) {
3964
        if (this.value && options.walkMode & InternalWalkMode.walkExpressions) {
2,808✔
3965
            walk(this, 'value', visitor, options);
1,352✔
3966
        }
3967
    }
3968

3969
    getType(options: GetTypeOptions) {
3970
        return new EnumMemberType(
456✔
3971
            (this.parent as EnumStatement)?.fullName,
1,368!
3972
            this.tokens?.name?.text,
2,736!
3973
            this.value?.getType(options)
1,368✔
3974
        );
3975
    }
3976

3977
    public clone() {
3978
        return this.finalizeClone(
3✔
3979
            new EnumMemberStatement({
3980
                name: util.cloneToken(this.tokens.name),
3981
                equals: util.cloneToken(this.tokens.equals),
3982
                value: this.value?.clone()
9✔
3983
            }),
3984
            ['value']
3985
        );
3986
    }
3987
}
3988

3989
export class ConstStatement extends Statement implements TypedefProvider {
1✔
3990
    public constructor(options: {
3991
        const?: Token;
3992
        name: Identifier;
3993
        equals?: Token;
3994
        value: Expression;
3995
    }) {
3996
        super();
163✔
3997
        this.tokens = {
163✔
3998
            const: options.const,
3999
            name: options.name,
4000
            equals: options.equals
4001
        };
4002
        this.value = options.value;
163✔
4003
        this.location = util.createBoundingLocation(this.tokens.const, this.tokens.name, this.tokens.equals, this.value);
163✔
4004
    }
4005

4006
    public readonly tokens: {
4007
        readonly const: Token;
4008
        readonly name: Identifier;
4009
        readonly equals: Token;
4010
    };
4011
    public readonly value: Expression;
4012

4013
    public readonly kind = AstNodeKind.ConstStatement;
163✔
4014

4015
    public readonly location: Location | undefined;
4016

4017
    public get name() {
UNCOV
4018
        return this.tokens.name.text;
×
4019
    }
4020

4021
    public get leadingTrivia(): Token[] {
4022
        return this.tokens.const?.leadingTrivia;
487!
4023
    }
4024

4025
    /**
4026
     * The name of the statement WITH its leading namespace (if applicable)
4027
     */
4028
    public get fullName() {
4029
        const name = this.tokens.name?.text;
64!
4030
        if (name) {
64!
4031
            const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
64✔
4032
            if (namespace) {
64✔
4033
                let namespaceName = namespace.getName(ParseMode.BrighterScript);
31✔
4034
                return `${namespaceName}.${name}`;
31✔
4035
            } else {
4036
                return name;
33✔
4037
            }
4038
        } else {
4039
            //return undefined which will allow outside callers to know that this doesn't have a name
UNCOV
4040
            return undefined;
×
4041
        }
4042
    }
4043

4044
    public transpile(state: BrsTranspileState): TranspileResult {
4045
        //const declarations don't exist at runtime, so just transpile comments and trivia
4046
        return [
27✔
4047
            state.transpileAnnotations(this),
4048
            state.transpileLeadingComments(this.tokens.const)
4049
        ];
4050
    }
4051

4052
    getTypedef(state: BrsTranspileState): TranspileResult {
4053
        const result: TranspileResult = [];
3✔
4054
        for (let comment of util.getLeadingComments(this) ?? []) {
3!
UNCOV
4055
            result.push(
×
4056
                comment.text,
4057
                state.newline,
4058
                state.indent()
4059
            );
4060
        }
4061
        result.push(
3✔
4062
            this.tokens.const ? state.tokenToSourceNode(this.tokens.const) : 'const',
3!
4063
            ' ',
4064
            state.tokenToSourceNode(this.tokens.name),
4065
            ' ',
4066
            this.tokens.equals ? state.tokenToSourceNode(this.tokens.equals) : '=',
3!
4067
            ' ',
4068
            ...this.value.transpile(state)
4069
        );
4070
        return result;
3✔
4071
    }
4072

4073
    walk(visitor: WalkVisitor, options: WalkOptions) {
4074
        if (this.value && options.walkMode & InternalWalkMode.walkExpressions) {
1,077✔
4075
            walk(this, 'value', visitor, options);
934✔
4076
        }
4077
    }
4078

4079
    getType(options: GetTypeOptions) {
4080
        return this.value.getType(options);
155✔
4081
    }
4082

4083
    public clone() {
4084
        return this.finalizeClone(
3✔
4085
            new ConstStatement({
4086
                const: util.cloneToken(this.tokens.const),
4087
                name: util.cloneToken(this.tokens.name),
4088
                equals: util.cloneToken(this.tokens.equals),
4089
                value: this.value?.clone()
9✔
4090
            }),
4091
            ['value']
4092
        );
4093
    }
4094
}
4095

4096
export class ContinueStatement extends Statement {
1✔
4097
    constructor(options: {
4098
        continue?: Token;
4099
        loopType: Token;
4100
    }
4101
    ) {
4102
        super();
13✔
4103
        this.tokens = {
13✔
4104
            continue: options.continue,
4105
            loopType: options.loopType
4106
        };
4107
        this.location = util.createBoundingLocation(
13✔
4108
            this.tokens.continue,
4109
            this.tokens.loopType
4110
        );
4111
    }
4112

4113
    public readonly tokens: {
4114
        readonly continue?: Token;
4115
        readonly loopType: Token;
4116
    };
4117

4118
    public readonly kind = AstNodeKind.ContinueStatement;
13✔
4119

4120
    public readonly location: Location | undefined;
4121

4122
    transpile(state: BrsTranspileState) {
4123
        return [
3✔
4124
            state.sourceNode(this.tokens.continue, this.tokens.continue?.text ?? 'continue'),
18!
4125
            this.tokens.loopType?.leadingWhitespace ?? ' ',
18✔
4126
            state.sourceNode(this.tokens.continue, this.tokens.loopType?.text)
9✔
4127
        ];
4128
    }
4129

4130
    walk(visitor: WalkVisitor, options: WalkOptions) {
4131
        //nothing to walk
4132
    }
4133

4134
    public get leadingTrivia(): Token[] {
4135
        return this.tokens.continue?.leadingTrivia ?? [];
86!
4136
    }
4137

4138
    public clone() {
4139
        return this.finalizeClone(
1✔
4140
            new ContinueStatement({
4141
                continue: util.cloneToken(this.tokens.continue),
4142
                loopType: util.cloneToken(this.tokens.loopType)
4143
            })
4144
        );
4145
    }
4146
}
4147

4148
export class TypecastStatement extends Statement {
1✔
4149
    constructor(options: {
4150
        typecast?: Token;
4151
        typecastExpression: TypecastExpression;
4152
    }
4153
    ) {
4154
        super();
27✔
4155
        this.tokens = {
27✔
4156
            typecast: options.typecast
4157
        };
4158
        this.typecastExpression = options.typecastExpression;
27✔
4159
        this.location = util.createBoundingLocation(
27✔
4160
            this.tokens.typecast,
4161
            this.typecastExpression
4162
        );
4163
    }
4164

4165
    public readonly tokens: {
4166
        readonly typecast?: Token;
4167
    };
4168

4169
    public readonly typecastExpression: TypecastExpression;
4170

4171
    public readonly kind = AstNodeKind.TypecastStatement;
27✔
4172

4173
    public readonly location: Location;
4174

4175
    transpile(state: BrsTranspileState) {
4176
        //the typecast statement is a comment just for debugging purposes
4177
        return [
2✔
4178
            state.transpileToken(this.tokens.typecast, 'typecast', true),
4179
            ' ',
4180
            this.typecastExpression.obj.transpile(state),
4181
            ' ',
4182
            state.transpileToken(this.typecastExpression.tokens.as, 'as'),
4183
            ' ',
4184
            this.typecastExpression.typeExpression.getName(ParseMode.BrighterScript)
4185
        ];
4186
    }
4187

4188
    walk(visitor: WalkVisitor, options: WalkOptions) {
4189
        if (options.walkMode & InternalWalkMode.walkExpressions) {
160✔
4190
            walk(this, 'typecastExpression', visitor, options);
147✔
4191
        }
4192
    }
4193

4194
    get leadingTrivia(): Token[] {
4195
        return this.tokens.typecast?.leadingTrivia ?? [];
119!
4196
    }
4197

4198
    getType(options: GetTypeOptions): BscType {
4199
        return this.typecastExpression.getType(options);
22✔
4200
    }
4201

4202
    public clone() {
4203
        return this.finalizeClone(
1✔
4204
            new TypecastStatement({
4205
                typecast: util.cloneToken(this.tokens.typecast),
4206
                typecastExpression: this.typecastExpression?.clone()
3!
4207
            }),
4208
            ['typecastExpression']
4209
        );
4210
    }
4211
}
4212

4213
export class ConditionalCompileErrorStatement extends Statement {
1✔
4214
    constructor(options: {
4215
        hashError?: Token;
4216
        message: Token;
4217
    }) {
4218
        super();
11✔
4219
        this.tokens = {
11✔
4220
            hashError: options.hashError,
4221
            message: options.message
4222
        };
4223
        this.location = util.createBoundingLocation(util.createBoundingLocationFromTokens(this.tokens));
11✔
4224
    }
4225

4226
    public readonly tokens: {
4227
        readonly hashError?: Token;
4228
        readonly message: Token;
4229
    };
4230

4231

4232
    public readonly kind = AstNodeKind.ConditionalCompileErrorStatement;
11✔
4233

4234
    public readonly location: Location | undefined;
4235

4236
    transpile(state: BrsTranspileState) {
4237
        return [
3✔
4238
            state.transpileToken(this.tokens.hashError, '#error'),
4239
            ' ',
4240
            state.transpileToken(this.tokens.message)
4241

4242
        ];
4243
    }
4244

4245
    walk(visitor: WalkVisitor, options: WalkOptions) {
4246
        // nothing to walk
4247
    }
4248

4249
    get leadingTrivia(): Token[] {
4250
        return this.tokens.hashError.leadingTrivia ?? [];
20!
4251
    }
4252

4253
    public clone() {
4254
        return this.finalizeClone(
1✔
4255
            new ConditionalCompileErrorStatement({
4256
                hashError: util.cloneToken(this.tokens.hashError),
4257
                message: util.cloneToken(this.tokens.message)
4258
            })
4259
        );
4260
    }
4261
}
4262

4263
export class AliasStatement extends Statement {
1✔
4264
    constructor(options: {
4265
        alias?: Token;
4266
        name: Token;
4267
        equals?: Token;
4268
        value: VariableExpression | DottedGetExpression;
4269
    }
4270
    ) {
4271
        super();
34✔
4272
        this.tokens = {
34✔
4273
            alias: options.alias,
4274
            name: options.name,
4275
            equals: options.equals
4276
        };
4277
        this.value = options.value;
34✔
4278
        this.location = util.createBoundingLocation(
34✔
4279
            this.tokens.alias,
4280
            this.tokens.name,
4281
            this.tokens.equals,
4282
            this.value
4283
        );
4284
    }
4285

4286
    public readonly tokens: {
4287
        readonly alias?: Token;
4288
        readonly name: Token;
4289
        readonly equals?: Token;
4290
    };
4291

4292
    public readonly value: Expression;
4293

4294
    public readonly kind = AstNodeKind.AliasStatement;
34✔
4295

4296
    public readonly location: Location;
4297

4298
    transpile(state: BrsTranspileState) {
4299
        //the alias statement is a comment just for debugging purposes
4300
        return [
12✔
4301
            state.transpileToken(this.tokens.alias, 'alias', true),
4302
            ' ',
4303
            state.transpileToken(this.tokens.name),
4304
            ' ',
4305
            state.transpileToken(this.tokens.equals, '='),
4306
            ' ',
4307
            this.value.transpile(state)
4308
        ];
4309
    }
4310

4311
    walk(visitor: WalkVisitor, options: WalkOptions) {
4312
        if (options.walkMode & InternalWalkMode.walkExpressions) {
235✔
4313
            walk(this, 'value', visitor, options);
206✔
4314
        }
4315
    }
4316

4317
    get leadingTrivia(): Token[] {
4318
        return this.tokens.alias?.leadingTrivia ?? [];
121!
4319
    }
4320

4321
    getType(options: GetTypeOptions): BscType {
4322
        return this.value.getType(options);
1✔
4323
    }
4324

4325
    public clone() {
4326
        return this.finalizeClone(
1✔
4327
            new AliasStatement({
4328
                alias: util.cloneToken(this.tokens.alias),
4329
                name: util.cloneToken(this.tokens.name),
4330
                equals: util.cloneToken(this.tokens.equals),
4331
                value: this.value?.clone()
3!
4332
            }),
4333
            ['value']
4334
        );
4335
    }
4336
}
4337

4338
export class ConditionalCompileStatement extends Statement {
1✔
4339
    constructor(options: {
4340
        hashIf?: Token;
4341
        not?: Token;
4342
        condition: Token;
4343
        hashElse?: Token;
4344
        hashEndIf?: Token;
4345
        thenBranch: Block;
4346
        elseBranch?: ConditionalCompileStatement | Block;
4347
    }) {
4348
        super();
58✔
4349
        this.thenBranch = options.thenBranch;
58✔
4350
        this.elseBranch = options.elseBranch;
58✔
4351

4352
        this.tokens = {
58✔
4353
            hashIf: options.hashIf,
4354
            not: options.not,
4355
            condition: options.condition,
4356
            hashElse: options.hashElse,
4357
            hashEndIf: options.hashEndIf
4358
        };
4359

4360
        this.location = util.createBoundingLocation(
58✔
4361
            util.createBoundingLocationFromTokens(this.tokens),
4362
            this.thenBranch,
4363
            this.elseBranch
4364
        );
4365
    }
4366

4367
    readonly tokens: {
4368
        readonly hashIf?: Token;
4369
        readonly not?: Token;
4370
        readonly condition: Token;
4371
        readonly hashElse?: Token;
4372
        readonly hashEndIf?: Token;
4373
    };
4374
    public readonly thenBranch: Block;
4375
    public readonly elseBranch?: ConditionalCompileStatement | Block;
4376

4377
    public readonly kind = AstNodeKind.ConditionalCompileStatement;
58✔
4378

4379
    public readonly location: Location | undefined;
4380

4381
    transpile(state: BrsTranspileState) {
4382
        let results = [] as TranspileResult;
6✔
4383
        //if   (already indented by block)
4384
        if (!state.conditionalCompileStatement) {
6✔
4385
            // only transpile the #if in the case when we're not in a conditionalCompileStatement already
4386
            results.push(state.transpileToken(this.tokens.hashIf ?? createToken(TokenKind.HashIf)));
3!
4387
        }
4388

4389
        results.push(' ');
6✔
4390
        //conditions
4391
        if (this.tokens.not) {
6✔
4392
            results.push('not');
2✔
4393
            results.push(' ');
2✔
4394
        }
4395
        results.push(state.transpileToken(this.tokens.condition));
6✔
4396
        state.lineage.unshift(this);
6✔
4397

4398
        //if statement body
4399
        let thenNodes = this.thenBranch.transpile(state);
6✔
4400
        state.lineage.shift();
6✔
4401
        if (thenNodes.length > 0) {
6!
4402
            results.push(thenNodes);
6✔
4403
        }
4404
        //else branch
4405
        if (this.elseBranch) {
6!
4406
            const elseIsCC = isConditionalCompileStatement(this.elseBranch);
6✔
4407
            const endBlockToken = elseIsCC ? (this.elseBranch as ConditionalCompileStatement).tokens.hashIf ?? createToken(TokenKind.HashElseIf) : this.tokens.hashElse;
6!
4408
            //else
4409

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

4412
            if (elseIsCC) {
6✔
4413
                //chained else if
4414
                state.lineage.unshift(this.elseBranch);
3✔
4415

4416
                // transpile following #if with knowledge of current
4417
                const existingCCStmt = state.conditionalCompileStatement;
3✔
4418
                state.conditionalCompileStatement = this;
3✔
4419
                let body = this.elseBranch.transpile(state);
3✔
4420
                state.conditionalCompileStatement = existingCCStmt;
3✔
4421

4422
                state.lineage.shift();
3✔
4423

4424
                if (body.length > 0) {
3!
4425
                    //zero or more spaces between the `else` and the `if`
4426
                    results.push(...body);
3✔
4427

4428
                    // stop here because chained if will transpile the rest
4429
                    return results;
3✔
4430
                } else {
UNCOV
4431
                    results.push('\n');
×
4432
                }
4433

4434
            } else {
4435
                //else body
4436
                state.lineage.unshift(this.tokens.hashElse!);
3✔
4437
                let body = this.elseBranch.transpile(state);
3✔
4438
                state.lineage.shift();
3✔
4439

4440
                if (body.length > 0) {
3!
4441
                    results.push(...body);
3✔
4442
                }
4443
            }
4444
        }
4445

4446
        //end if
4447
        results.push(...state.transpileEndBlockToken(this.elseBranch ?? this.thenBranch, this.tokens.hashEndIf, '#end if'));
3!
4448

4449
        return results;
3✔
4450
    }
4451

4452
    walk(visitor: WalkVisitor, options: WalkOptions) {
4453
        if (options.walkMode & InternalWalkMode.walkStatements) {
210!
4454
            const bsConsts = options.bsConsts ?? this.getBsConsts();
210✔
4455
            let conditionTrue = bsConsts?.get(this.tokens.condition.text.toLowerCase());
210✔
4456
            if (this.tokens.not) {
210✔
4457
                // flips the boolean value
4458
                conditionTrue = !conditionTrue;
25✔
4459
            }
4460
            const walkFalseBlocks = options.walkMode & InternalWalkMode.visitFalseConditionalCompilationBlocks;
210✔
4461
            if (conditionTrue || walkFalseBlocks) {
210✔
4462
                walk(this, 'thenBranch', visitor, options);
176✔
4463
            }
4464
            if (this.elseBranch && (!conditionTrue || walkFalseBlocks)) {
210✔
4465
                walk(this, 'elseBranch', visitor, options);
68✔
4466
            }
4467
        }
4468
    }
4469

4470
    get leadingTrivia(): Token[] {
4471
        return this.tokens.hashIf?.leadingTrivia ?? [];
174!
4472
    }
4473

4474
    public clone() {
4475
        return this.finalizeClone(
1✔
4476
            new ConditionalCompileStatement({
4477
                hashIf: util.cloneToken(this.tokens.hashIf),
4478
                not: util.cloneToken(this.tokens.not),
4479
                condition: util.cloneToken(this.tokens.condition),
4480
                hashElse: util.cloneToken(this.tokens.hashElse),
4481
                hashEndIf: util.cloneToken(this.tokens.hashEndIf),
4482
                thenBranch: this.thenBranch?.clone(),
3!
4483
                elseBranch: this.elseBranch?.clone()
3!
4484
            }),
4485
            ['thenBranch', 'elseBranch']
4486
        );
4487
    }
4488

4489
    public getBranchStatementIndex(stmt: Statement) {
4490
        if (this.thenBranch === stmt) {
1!
NEW
4491
            return 0;
×
4492
        } else if (this.elseBranch === stmt) {
1!
4493
            return 1;
1✔
4494
        }
NEW
4495
        return -1;
×
4496
    }
4497
}
4498

4499

4500
export class ConditionalCompileConstStatement extends Statement {
1✔
4501
    constructor(options: {
4502
        hashConst?: Token;
4503
        assignment: AssignmentStatement;
4504
    }) {
4505
        super();
19✔
4506
        this.tokens = {
19✔
4507
            hashConst: options.hashConst
4508
        };
4509
        this.assignment = options.assignment;
19✔
4510
        this.location = util.createBoundingLocation(util.createBoundingLocationFromTokens(this.tokens), this.assignment);
19✔
4511
    }
4512

4513
    public readonly tokens: {
4514
        readonly hashConst?: Token;
4515
    };
4516

4517
    public readonly assignment: AssignmentStatement;
4518

4519
    public readonly kind = AstNodeKind.ConditionalCompileConstStatement;
19✔
4520

4521
    public readonly location: Location | undefined;
4522

4523
    transpile(state: BrsTranspileState) {
4524
        return [
3✔
4525
            state.transpileToken(this.tokens.hashConst, '#const'),
4526
            ' ',
4527
            state.transpileToken(this.assignment.tokens.name),
4528
            ' ',
4529
            state.transpileToken(this.assignment.tokens.equals, '='),
4530
            ' ',
4531
            ...this.assignment.value.transpile(state)
4532
        ];
4533

4534
    }
4535

4536
    walk(visitor: WalkVisitor, options: WalkOptions) {
4537
        // nothing to walk
4538
    }
4539

4540

4541
    get leadingTrivia(): Token[] {
4542
        return this.tokens.hashConst.leadingTrivia ?? [];
78!
4543
    }
4544

4545
    public clone() {
4546
        return this.finalizeClone(
1✔
4547
            new ConditionalCompileConstStatement({
4548
                hashConst: util.cloneToken(this.tokens.hashConst),
4549
                assignment: this.assignment?.clone()
3!
4550
            }),
4551
            ['assignment']
4552
        );
4553
    }
4554
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc