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

rokucommunity / brighterscript / #13590

13 Jan 2025 02:40PM UTC coverage: 86.911% (-1.3%) from 88.185%
#13590

push

web-flow
Merge 38702985d into 9d6ef67ba

12071 of 14663 branches covered (82.32%)

Branch coverage included in aggregate %.

90 of 94 new or added lines in 11 files covered. (95.74%)

796 existing lines in 47 files now uncovered.

13048 of 14239 relevant lines covered (91.64%)

31886.92 hits per line

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

88.4
/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, FunctionExpression, FunctionParameterExpression, LiteralExpression, TypeExpression, TypecastExpression } from './Expression';
5
import { CallExpression, VariableExpression } from './Expression';
1✔
6
import { util } from '../util';
1✔
7
import type { Location } from 'vscode-languageserver';
8
import type { BrsTranspileState } from './BrsTranspileState';
9
import { ParseMode } from './Parser';
1✔
10
import type { WalkVisitor, WalkOptions } from '../astUtils/visitors';
11
import { InternalWalkMode, walk, createVisitor, WalkMode, walkArray } from '../astUtils/visitors';
1✔
12
import { isCallExpression, 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✔
13
import { TypeChainEntry, type GetTypeOptions, type TranspileResult, type TypedefProvider } from '../interfaces';
1✔
14
import { createInvalidLiteral, createMethodStatement, createToken } from '../astUtils/creators';
1✔
15
import { DynamicType } from '../types/DynamicType';
1✔
16
import type { BscType } from '../types/BscType';
17
import { SymbolTable } from '../SymbolTable';
1✔
18
import type { Expression } from './AstNode';
19
import { AstNodeKind, Statement } from './AstNode';
1✔
20
import { ClassType } from '../types/ClassType';
1✔
21
import { EnumMemberType, EnumType } from '../types/EnumType';
1✔
22
import { NamespaceType } from '../types/NamespaceType';
1✔
23
import { InterfaceType } from '../types/InterfaceType';
1✔
24
import { VoidType } from '../types/VoidType';
1✔
25
import { TypedFunctionType } from '../types/TypedFunctionType';
1✔
26
import { ArrayType } from '../types/ArrayType';
1✔
27
import { SymbolTypeFlag } from '../SymbolTypeFlag';
1✔
28
import brsDocParser from './BrightScriptDocParser';
1✔
29

30
export class EmptyStatement extends Statement {
1✔
31
    constructor(options?: { range?: Location }
32
    ) {
33
        super();
6✔
34
        this.location = undefined;
6✔
35
    }
36
    /**
37
     * Create a negative range to indicate this is an interpolated location
38
     */
39
    public readonly location?: Location;
40

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

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

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

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

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

73
    public readonly symbolTable = new SymbolTable('Body', () => this.parent?.getSymbolTable());
30,283✔
74

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

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

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

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

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

116
            result.push(...statement.transpile(state));
1,611✔
117
        }
118
        return result;
740✔
119
    }
120

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

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

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

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

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

177
    public readonly value: Expression;
178

179
    public readonly typeExpression?: TypeExpression;
180

181
    public readonly kind = AstNodeKind.AssignmentStatement;
1,564✔
182

183
    public readonly location: Location | undefined;
184

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

195
    walk(visitor: WalkVisitor, options: WalkOptions) {
196
        if (options.walkMode & InternalWalkMode.walkExpressions) {
6,631✔
197
            walk(this, 'typeExpression', visitor, options);
6,504✔
198
            walk(this, 'value', visitor, options);
6,504✔
199
        }
200
    }
201

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

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

214
    get leadingTrivia(): Token[] {
215
        return this.tokens.name.leadingTrivia;
7,846✔
216
    }
217

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

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

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

252
    public readonly item: Expression;
253

254
    public readonly value: Expression;
255

256
    public readonly kind = AstNodeKind.AugmentedAssignmentStatement;
99✔
257

258
    public readonly location: Location | undefined;
259

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

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

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

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

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

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

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

304
export class Block extends Statement {
1✔
305
    constructor(options: {
306
        statements: Statement[];
307
    }) {
308
        super();
7,023✔
309
        this.statements = options.statements;
7,023✔
310
    }
311

312
    public readonly statements: Statement[];
313

314
    public readonly kind = AstNodeKind.Block;
7,023✔
315

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

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

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

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

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

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

459
    public get leadingTrivia(): Token[] {
460
        return this.statements[0]?.leadingTrivia ?? [];
10,474✔
461
    }
462

463
    walk(visitor: WalkVisitor, options: WalkOptions) {
464
        if (options.walkMode & InternalWalkMode.walkStatements) {
31,173✔
465
            walkArray(this.statements, visitor, options, this);
31,167✔
466
        }
467
    }
468

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

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

490
    public readonly location: Location | undefined;
491

492
    transpile(state: BrsTranspileState) {
493
        return [
60✔
494
            state.transpileAnnotations(this),
495
            this.expression.transpile(state)
496
        ];
497
    }
498

499
    walk(visitor: WalkVisitor, options: WalkOptions) {
500
        if (options.walkMode & InternalWalkMode.walkExpressions) {
3,319✔
501
            walk(this, 'expression', visitor, options);
3,290✔
502
        }
503
    }
504

505
    get leadingTrivia(): Token[] {
506
        return this.expression.leadingTrivia;
3,351✔
507
    }
508

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

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

535
    public readonly tokens: {
536
        readonly exit: Token;
537
        readonly loopType?: Token;
538
    };
539

540
    public readonly kind = AstNodeKind.ExitStatement;
26✔
541

542
    public readonly location?: Location;
543

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

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

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

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

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

584
        this.location = this.func?.location;
4,157✔
585
    }
586

587
    public readonly tokens: {
588
        readonly name: Identifier;
589
    };
590
    public readonly func: FunctionExpression;
591

592
    public readonly kind = AstNodeKind.FunctionStatement as AstNodeKind;
4,157✔
593

594
    public readonly location: Location | undefined;
595

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

610
    public get leadingTrivia(): Token[] {
611
        return this.func.leadingTrivia;
16,848✔
612
    }
613

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

621
        return [
1,404✔
622
            ...state.transpileAnnotations(this),
623
            ...this.func.transpile(state, nameToken)
624
        ];
625
    }
626

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

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

650
    walk(visitor: WalkVisitor, options: WalkOptions) {
651
        if (options.walkMode & InternalWalkMode.walkExpressions) {
17,962✔
652
            walk(this, 'func', visitor, options);
16,121✔
653
        }
654
    }
655

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

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

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

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

689
        this.tokens = {
2,145✔
690
            if: options.if,
691
            then: options.then,
692
            else: options.else,
693
            endIf: options.endIf
694
        };
695

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

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

714
    public readonly kind = AstNodeKind.IfStatement;
2,145✔
715

716
    public readonly location: Location | undefined;
717

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

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

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

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

757
            if (isIfStatement(this.elseBranch)) {
1,821✔
758
                //chained elseif
759
                state.lineage.unshift(this.elseBranch);
1,078✔
760
                let body = this.elseBranch.transpile(state);
1,078✔
761
                state.lineage.shift();
1,078✔
762

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

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

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

780
                if (body.length > 0) {
743✔
781
                    results.push(...body);
741✔
782
                }
783
            }
784
        }
785

786
        //end if
787
        results.push(...state.transpileEndBlockToken(this.elseBranch ?? this.thenBranch, this.tokens.endIf, 'end if'));
1,115✔
788

789
        return results;
1,115✔
790
    }
791

792
    walk(visitor: WalkVisitor, options: WalkOptions) {
793
        if (options.walkMode & InternalWalkMode.walkExpressions) {
8,963✔
794
            walk(this, 'condition', visitor, options);
8,947✔
795
        }
796
        if (options.walkMode & InternalWalkMode.walkStatements) {
8,963✔
797
            walk(this, 'thenBranch', visitor, options);
8,961✔
798
        }
799
        if (this.elseBranch && options.walkMode & InternalWalkMode.walkStatements) {
8,963✔
800
            walk(this, 'elseBranch', visitor, options);
7,131✔
801
        }
802
    }
803

804
    get leadingTrivia(): Token[] {
805
        return this.tokens.if?.leadingTrivia ?? [];
2,891!
806
    }
807

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

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

828
export class IncrementStatement extends Statement {
1✔
829
    constructor(options: {
830
        value: Expression;
831
        operator: Token;
832
    }) {
833
        super();
27✔
834
        this.value = options.value;
27✔
835
        this.tokens = {
27✔
836
            operator: options.operator
837
        };
838
        this.location = util.createBoundingLocation(
27✔
839
            this.value,
840
            this.tokens.operator
841
        );
842
    }
843

844
    public readonly value: Expression;
845
    public readonly tokens: {
846
        readonly operator: Token;
847
    };
848

849
    public readonly kind = AstNodeKind.IncrementStatement;
27✔
850

851
    public readonly location: Location | undefined;
852

853
    transpile(state: BrsTranspileState) {
854
        return [
6✔
855
            ...this.value.transpile(state),
856
            state.transpileToken(this.tokens.operator)
857
        ];
858
    }
859

860
    walk(visitor: WalkVisitor, options: WalkOptions) {
861
        if (options.walkMode & InternalWalkMode.walkExpressions) {
82✔
862
            walk(this, 'value', visitor, options);
81✔
863
        }
864
    }
865

866
    get leadingTrivia(): Token[] {
867
        return this.value?.leadingTrivia ?? [];
84!
868
    }
869

870
    public clone() {
871
        return this.finalizeClone(
2✔
872
            new IncrementStatement({
873
                value: this.value?.clone(),
6✔
874
                operator: util.cloneToken(this.tokens.operator)
875
            }),
876
            ['value']
877
        );
878
    }
879
}
880

881
/**
882
 * Represents a `print` statement within BrightScript.
883
 */
884
export class PrintStatement extends Statement {
1✔
885
    /**
886
     * Creates a new internal representation of a BrightScript `print` statement.
887
     * @param options the options for this statement
888
     * @param options.print a print token
889
     * @param options.expressions an array of expressions to be evaluated and printed. Wrap PrintSeparator tokens (`;` or `,`) in `PrintSeparatorExpression`
890
     */
891
    constructor(options: {
892
        print?: Token;
893
        expressions: Array<Expression>;
894
    }) {
895
        super();
1,247✔
896
        this.tokens = {
1,247✔
897
            print: options.print
898
        };
899
        this.expressions = options.expressions;
1,247✔
900
        this.location = util.createBoundingLocation(
1,247✔
901
            this.tokens.print,
902
            ...(this.expressions ?? [])
3,741✔
903
        );
904
    }
905

906
    public readonly tokens: {
907
        readonly print?: Token;
908
    };
909

910
    public readonly expressions: Array<Expression>;
911

912
    public readonly kind = AstNodeKind.PrintStatement;
1,247✔
913

914
    public readonly location: Location | undefined;
915

916
    transpile(state: BrsTranspileState) {
917
        let result = [
232✔
918
            state.transpileToken(this.tokens.print, 'print')
919
        ] as TranspileResult;
920

921
        //if the first expression has no leading whitespace, add a single space between the `print` and the expression
922
        if (this.expressions.length > 0 && !this.expressions[0].leadingTrivia.find(t => t.kind === TokenKind.Whitespace)) {
232✔
923
            result.push(' ');
6✔
924
        }
925

926
        // eslint-disable-next-line @typescript-eslint/prefer-for-of
927
        for (let i = 0; i < this.expressions.length; i++) {
232✔
928
            const expression = this.expressions[i];
296✔
929
            let leadingWhitespace = expression.leadingTrivia.find(t => t.kind === TokenKind.Whitespace)?.text;
296✔
930
            if (leadingWhitespace) {
296✔
931
                result.push(leadingWhitespace);
250✔
932
                //if the previous expression was NOT a separator, and this one is not also, add a space between them
933
            } else if (i > 0 && !isPrintSeparatorExpression(this.expressions[i - 1]) && !isPrintSeparatorExpression(expression) && !leadingWhitespace) {
46✔
934
                result.push(' ');
6✔
935
            }
936

937
            result.push(
296✔
938
                ...expression.transpile(state)
939
            );
940
        }
941
        return result;
232✔
942
    }
943

944
    walk(visitor: WalkVisitor, options: WalkOptions) {
945
        if (options.walkMode & InternalWalkMode.walkExpressions) {
5,644✔
946
            walkArray(this.expressions, visitor, options, this);
5,582✔
947
        }
948
    }
949

950
    get leadingTrivia(): Token[] {
951
        return this.tokens.print?.leadingTrivia ?? [];
7,217✔
952
    }
953

954
    public clone() {
955
        return this.finalizeClone(
49✔
956
            new PrintStatement({
957
                print: util.cloneToken(this.tokens.print),
958
                expressions: this.expressions?.map(e => e?.clone())
49✔
959
            }),
960
            ['expressions' as any]
961
        );
962
    }
963
}
964

965
export class DimStatement extends Statement {
1✔
966
    constructor(options: {
967
        dim?: Token;
968
        name: Identifier;
969
        openingSquare?: Token;
970
        dimensions: Expression[];
971
        closingSquare?: Token;
972
    }) {
973
        super();
46✔
974
        this.tokens = {
46✔
975
            dim: options?.dim,
138!
976
            name: options.name,
977
            openingSquare: options.openingSquare,
978
            closingSquare: options.closingSquare
979
        };
980
        this.dimensions = options.dimensions;
46✔
981
        this.location = util.createBoundingLocation(
46✔
982
            options.dim,
983
            options.name,
984
            options.openingSquare,
985
            ...(this.dimensions ?? []),
138✔
986
            options.closingSquare
987
        );
988
    }
989

990
    public readonly tokens: {
991
        readonly dim?: Token;
992
        readonly name: Identifier;
993
        readonly openingSquare?: Token;
994
        readonly closingSquare?: Token;
995
    };
996
    public readonly dimensions: Expression[];
997

998
    public readonly kind = AstNodeKind.DimStatement;
46✔
999

1000
    public readonly location: Location | undefined;
1001

1002
    public transpile(state: BrsTranspileState) {
1003
        let result: TranspileResult = [
15✔
1004
            state.transpileToken(this.tokens.dim, 'dim'),
1005
            ' ',
1006
            state.transpileToken(this.tokens.name),
1007
            state.transpileToken(this.tokens.openingSquare, '[')
1008
        ];
1009
        for (let i = 0; i < this.dimensions.length; i++) {
15✔
1010
            if (i > 0) {
32✔
1011
                result.push(', ');
17✔
1012
            }
1013
            result.push(
32✔
1014
                ...this.dimensions![i].transpile(state)
1015
            );
1016
        }
1017
        result.push(state.transpileToken(this.tokens.closingSquare, ']'));
15✔
1018
        return result;
15✔
1019
    }
1020

1021
    public walk(visitor: WalkVisitor, options: WalkOptions) {
1022
        if (this.dimensions?.length !== undefined && this.dimensions?.length > 0 && options.walkMode & InternalWalkMode.walkExpressions) {
148!
1023
            walkArray(this.dimensions, visitor, options, this);
133✔
1024

1025
        }
1026
    }
1027

1028
    public getType(options: GetTypeOptions): BscType {
1029
        const numDimensions = this.dimensions?.length ?? 1;
18!
1030
        let type = new ArrayType();
18✔
1031
        for (let i = 0; i < numDimensions - 1; i++) {
18✔
1032
            type = new ArrayType(type);
17✔
1033
        }
1034
        return type;
18✔
1035
    }
1036

1037
    get leadingTrivia(): Token[] {
1038
        return this.tokens.dim?.leadingTrivia ?? [];
137!
1039
    }
1040

1041
    public clone() {
1042
        return this.finalizeClone(
3✔
1043
            new DimStatement({
1044
                dim: util.cloneToken(this.tokens.dim),
1045
                name: util.cloneToken(this.tokens.name),
1046
                openingSquare: util.cloneToken(this.tokens.openingSquare),
1047
                dimensions: this.dimensions?.map(e => e?.clone()),
5✔
1048
                closingSquare: util.cloneToken(this.tokens.closingSquare)
1049
            }),
1050
            ['dimensions']
1051
        );
1052
    }
1053
}
1054

1055
export class GotoStatement extends Statement {
1✔
1056
    constructor(options: {
1057
        goto?: Token;
1058
        label: Token;
1059
    }) {
1060
        super();
13✔
1061
        this.tokens = {
13✔
1062
            goto: options.goto,
1063
            label: options.label
1064
        };
1065
        this.location = util.createBoundingLocation(
13✔
1066
            this.tokens.goto,
1067
            this.tokens.label
1068
        );
1069
    }
1070

1071
    public readonly tokens: {
1072
        readonly goto?: Token;
1073
        readonly label: Token;
1074
    };
1075

1076
    public readonly kind = AstNodeKind.GotoStatement;
13✔
1077

1078
    public readonly location: Location | undefined;
1079

1080
    transpile(state: BrsTranspileState) {
1081
        return [
2✔
1082
            state.transpileToken(this.tokens.goto, 'goto'),
1083
            ' ',
1084
            state.transpileToken(this.tokens.label)
1085
        ];
1086
    }
1087

1088
    walk(visitor: WalkVisitor, options: WalkOptions) {
1089
        //nothing to walk
1090
    }
1091

1092
    get leadingTrivia(): Token[] {
1093
        return this.tokens.goto?.leadingTrivia ?? [];
19!
1094
    }
1095

1096
    public clone() {
1097
        return this.finalizeClone(
1✔
1098
            new GotoStatement({
1099
                goto: util.cloneToken(this.tokens.goto),
1100
                label: util.cloneToken(this.tokens.label)
1101
            })
1102
        );
1103
    }
1104
}
1105

1106
export class LabelStatement extends Statement {
1✔
1107
    constructor(options: {
1108
        name: Token;
1109
        colon?: Token;
1110
    }) {
1111
        super();
13✔
1112
        this.tokens = {
13✔
1113
            name: options.name,
1114
            colon: options.colon
1115
        };
1116
        this.location = util.createBoundingLocation(
13✔
1117
            this.tokens.name,
1118
            this.tokens.colon
1119
        );
1120
    }
1121
    public readonly tokens: {
1122
        readonly name: Token;
1123
        readonly colon: Token;
1124
    };
1125
    public readonly kind = AstNodeKind.LabelStatement;
13✔
1126

1127
    public readonly location: Location | undefined;
1128

1129
    public get leadingTrivia(): Token[] {
1130
        return this.tokens.name.leadingTrivia;
29✔
1131
    }
1132

1133
    transpile(state: BrsTranspileState) {
1134
        return [
2✔
1135
            state.transpileToken(this.tokens.name),
1136
            state.transpileToken(this.tokens.colon, ':')
1137

1138
        ];
1139
    }
1140

1141
    walk(visitor: WalkVisitor, options: WalkOptions) {
1142
        //nothing to walk
1143
    }
1144

1145
    public clone() {
1146
        return this.finalizeClone(
1✔
1147
            new LabelStatement({
1148
                name: util.cloneToken(this.tokens.name),
1149
                colon: util.cloneToken(this.tokens.colon)
1150
            })
1151
        );
1152
    }
1153
}
1154

1155
export class ReturnStatement extends Statement {
1✔
1156
    constructor(options?: {
1157
        return?: Token;
1158
        value?: Expression;
1159
    }) {
1160
        super();
3,303✔
1161
        this.tokens = {
3,303✔
1162
            return: options?.return
9,909!
1163
        };
1164
        this.value = options?.value;
3,303!
1165
        this.location = util.createBoundingLocation(
3,303✔
1166
            this.tokens.return,
1167
            this.value
1168
        );
1169
    }
1170

1171
    public readonly tokens: {
1172
        readonly return?: Token;
1173
    };
1174
    public readonly value?: Expression;
1175
    public readonly kind = AstNodeKind.ReturnStatement;
3,303✔
1176

1177
    public readonly location: Location | undefined;
1178

1179
    transpile(state: BrsTranspileState) {
1180
        let result = [] as TranspileResult;
3,233✔
1181
        result.push(
3,233✔
1182
            state.transpileToken(this.tokens.return, 'return')
1183
        );
1184
        if (this.value) {
3,233✔
1185
            result.push(' ');
3,229✔
1186
            result.push(...this.value.transpile(state));
3,229✔
1187
        }
1188
        return result;
3,233✔
1189
    }
1190

1191
    walk(visitor: WalkVisitor, options: WalkOptions) {
1192
        if (options.walkMode & InternalWalkMode.walkExpressions) {
14,470✔
1193
            walk(this, 'value', visitor, options);
14,452✔
1194
        }
1195
    }
1196

1197
    get leadingTrivia(): Token[] {
1198
        return this.tokens.return?.leadingTrivia ?? [];
9,728!
1199
    }
1200

1201
    public clone() {
1202
        return this.finalizeClone(
3✔
1203
            new ReturnStatement({
1204
                return: util.cloneToken(this.tokens.return),
1205
                value: this.value?.clone()
9✔
1206
            }),
1207
            ['value']
1208
        );
1209
    }
1210
}
1211

1212
export class EndStatement extends Statement {
1✔
1213
    constructor(options?: {
1214
        end?: Token;
1215
    }) {
1216
        super();
11✔
1217
        this.tokens = {
11✔
1218
            end: options?.end
33!
1219
        };
1220
        this.location = this.tokens.end?.location;
11!
1221
    }
1222
    public readonly tokens: {
1223
        readonly end?: Token;
1224
    };
1225
    public readonly kind = AstNodeKind.EndStatement;
11✔
1226

1227
    public readonly location: Location;
1228

1229
    transpile(state: BrsTranspileState) {
1230
        return [
2✔
1231
            state.transpileToken(this.tokens.end, 'end')
1232
        ];
1233
    }
1234

1235
    walk(visitor: WalkVisitor, options: WalkOptions) {
1236
        //nothing to walk
1237
    }
1238

1239
    get leadingTrivia(): Token[] {
1240
        return this.tokens.end?.leadingTrivia ?? [];
19!
1241
    }
1242

1243
    public clone() {
1244
        return this.finalizeClone(
1✔
1245
            new EndStatement({
1246
                end: util.cloneToken(this.tokens.end)
1247
            })
1248
        );
1249
    }
1250
}
1251

1252
export class StopStatement extends Statement {
1✔
1253
    constructor(options?: {
1254
        stop?: Token;
1255
    }) {
1256
        super();
19✔
1257
        this.tokens = { stop: options?.stop };
19!
1258
        this.location = this.tokens?.stop?.location;
19!
1259
    }
1260
    public readonly tokens: {
1261
        readonly stop?: Token;
1262
    };
1263

1264
    public readonly kind = AstNodeKind.StopStatement;
19✔
1265

1266
    public readonly location: Location;
1267

1268
    transpile(state: BrsTranspileState) {
1269
        return [
2✔
1270
            state.transpileToken(this.tokens.stop, 'stop')
1271
        ];
1272
    }
1273

1274
    walk(visitor: WalkVisitor, options: WalkOptions) {
1275
        //nothing to walk
1276
    }
1277

1278
    get leadingTrivia(): Token[] {
1279
        return this.tokens.stop?.leadingTrivia ?? [];
29!
1280
    }
1281

1282
    public clone() {
1283
        return this.finalizeClone(
1✔
1284
            new StopStatement({
1285
                stop: util.cloneToken(this.tokens.stop)
1286
            })
1287
        );
1288
    }
1289
}
1290

1291
export class ForStatement extends Statement {
1✔
1292
    constructor(options: {
1293
        for?: Token;
1294
        counterDeclaration: AssignmentStatement;
1295
        to?: Token;
1296
        finalValue: Expression;
1297
        body: Block;
1298
        endFor?: Token;
1299
        step?: Token;
1300
        increment?: Expression;
1301
    }) {
1302
        super();
47✔
1303
        this.tokens = {
47✔
1304
            for: options.for,
1305
            to: options.to,
1306
            endFor: options.endFor,
1307
            step: options.step
1308
        };
1309
        this.counterDeclaration = options.counterDeclaration;
47✔
1310
        this.finalValue = options.finalValue;
47✔
1311
        this.body = options.body;
47✔
1312
        this.increment = options.increment;
47✔
1313

1314
        this.location = util.createBoundingLocation(
47✔
1315
            this.tokens.for,
1316
            this.counterDeclaration,
1317
            this.tokens.to,
1318
            this.finalValue,
1319
            this.tokens.step,
1320
            this.increment,
1321
            this.body,
1322
            this.tokens.endFor
1323
        );
1324
    }
1325

1326
    public readonly tokens: {
1327
        readonly for?: Token;
1328
        readonly to?: Token;
1329
        readonly endFor?: Token;
1330
        readonly step?: Token;
1331
    };
1332

1333
    public readonly counterDeclaration: AssignmentStatement;
1334
    public readonly finalValue: Expression;
1335
    public readonly body: Block;
1336
    public readonly increment?: Expression;
1337

1338
    public readonly kind = AstNodeKind.ForStatement;
47✔
1339

1340
    public readonly location: Location | undefined;
1341

1342
    transpile(state: BrsTranspileState) {
1343
        let result = [] as TranspileResult;
11✔
1344
        //for
1345
        result.push(
11✔
1346
            state.transpileToken(this.tokens.for, 'for'),
1347
            ' '
1348
        );
1349
        //i=1
1350
        result.push(
11✔
1351
            ...this.counterDeclaration.transpile(state),
1352
            ' '
1353
        );
1354
        //to
1355
        result.push(
11✔
1356
            state.transpileToken(this.tokens.to, 'to'),
1357
            ' '
1358
        );
1359
        //final value
1360
        result.push(this.finalValue.transpile(state));
11✔
1361
        //step
1362
        if (this.increment) {
11✔
1363
            result.push(
4✔
1364
                ' ',
1365
                state.transpileToken(this.tokens.step, 'step'),
1366
                ' ',
1367
                this.increment!.transpile(state)
1368
            );
1369
        }
1370
        //loop body
1371
        state.lineage.unshift(this);
11✔
1372
        result.push(...this.body.transpile(state));
11✔
1373
        state.lineage.shift();
11✔
1374

1375
        //end for
1376
        result.push(...state.transpileEndBlockToken(this.body, this.tokens.endFor, 'end for'));
11✔
1377

1378
        return result;
11✔
1379
    }
1380

1381
    walk(visitor: WalkVisitor, options: WalkOptions) {
1382
        if (options.walkMode & InternalWalkMode.walkStatements) {
152✔
1383
            walk(this, 'counterDeclaration', visitor, options);
151✔
1384
        }
1385
        if (options.walkMode & InternalWalkMode.walkExpressions) {
152✔
1386
            walk(this, 'finalValue', visitor, options);
148✔
1387
            walk(this, 'increment', visitor, options);
148✔
1388
        }
1389
        if (options.walkMode & InternalWalkMode.walkStatements) {
152✔
1390
            walk(this, 'body', visitor, options);
151✔
1391
        }
1392
    }
1393

1394
    get leadingTrivia(): Token[] {
1395
        return this.tokens.for?.leadingTrivia ?? [];
152!
1396
    }
1397

1398
    public get endTrivia(): Token[] {
UNCOV
1399
        return this.tokens.endFor?.leadingTrivia ?? [];
×
1400
    }
1401

1402
    public clone() {
1403
        return this.finalizeClone(
5✔
1404
            new ForStatement({
1405
                for: util.cloneToken(this.tokens.for),
1406
                counterDeclaration: this.counterDeclaration?.clone(),
15✔
1407
                to: util.cloneToken(this.tokens.to),
1408
                finalValue: this.finalValue?.clone(),
15✔
1409
                body: this.body?.clone(),
15✔
1410
                endFor: util.cloneToken(this.tokens.endFor),
1411
                step: util.cloneToken(this.tokens.step),
1412
                increment: this.increment?.clone()
15✔
1413
            }),
1414
            ['counterDeclaration', 'finalValue', 'body', 'increment']
1415
        );
1416
    }
1417
}
1418

1419
export class ForEachStatement extends Statement {
1✔
1420
    constructor(options: {
1421
        forEach?: Token;
1422
        item: Token;
1423
        in?: Token;
1424
        target: Expression;
1425
        body: Block;
1426
        endFor?: Token;
1427
    }) {
1428
        super();
41✔
1429
        this.tokens = {
41✔
1430
            forEach: options.forEach,
1431
            item: options.item,
1432
            in: options.in,
1433
            endFor: options.endFor
1434
        };
1435
        this.body = options.body;
41✔
1436
        this.target = options.target;
41✔
1437

1438
        this.location = util.createBoundingLocation(
41✔
1439
            this.tokens.forEach,
1440
            this.tokens.item,
1441
            this.tokens.in,
1442
            this.target,
1443
            this.body,
1444
            this.tokens.endFor
1445
        );
1446
    }
1447

1448
    public readonly tokens: {
1449
        readonly forEach?: Token;
1450
        readonly item: Token;
1451
        readonly in?: Token;
1452
        readonly endFor?: Token;
1453
    };
1454
    public readonly body: Block;
1455
    public readonly target: Expression;
1456

1457
    public readonly kind = AstNodeKind.ForEachStatement;
41✔
1458

1459
    public readonly location: Location | undefined;
1460

1461
    transpile(state: BrsTranspileState) {
1462
        let result = [] as TranspileResult;
5✔
1463
        //for each
1464
        result.push(
5✔
1465
            state.transpileToken(this.tokens.forEach, 'for each'),
1466
            ' '
1467
        );
1468
        //item
1469
        result.push(
5✔
1470
            state.transpileToken(this.tokens.item),
1471
            ' '
1472
        );
1473
        //in
1474
        result.push(
5✔
1475
            state.transpileToken(this.tokens.in, 'in'),
1476
            ' '
1477
        );
1478
        //target
1479
        result.push(...this.target.transpile(state));
5✔
1480
        //body
1481
        state.lineage.unshift(this);
5✔
1482
        result.push(...this.body.transpile(state));
5✔
1483
        state.lineage.shift();
5✔
1484

1485
        //end for
1486
        result.push(...state.transpileEndBlockToken(this.body, this.tokens.endFor, 'end for'));
5✔
1487

1488
        return result;
5✔
1489
    }
1490

1491
    walk(visitor: WalkVisitor, options: WalkOptions) {
1492
        if (options.walkMode & InternalWalkMode.walkExpressions) {
185✔
1493
            walk(this, 'target', visitor, options);
178✔
1494
        }
1495
        if (options.walkMode & InternalWalkMode.walkStatements) {
185✔
1496
            walk(this, 'body', visitor, options);
184✔
1497
        }
1498
    }
1499

1500
    getType(options: GetTypeOptions): BscType {
1501
        return this.getSymbolTable().getSymbolType(this.tokens.item.text, options);
29✔
1502
    }
1503

1504
    get leadingTrivia(): Token[] {
1505
        return this.tokens.forEach?.leadingTrivia ?? [];
187!
1506
    }
1507

1508
    public get endTrivia(): Token[] {
1509
        return this.tokens.endFor?.leadingTrivia ?? [];
1!
1510
    }
1511

1512
    public clone() {
1513
        return this.finalizeClone(
2✔
1514
            new ForEachStatement({
1515
                forEach: util.cloneToken(this.tokens.forEach),
1516
                in: util.cloneToken(this.tokens.in),
1517
                endFor: util.cloneToken(this.tokens.endFor),
1518
                item: util.cloneToken(this.tokens.item),
1519
                target: this.target?.clone(),
6✔
1520
                body: this.body?.clone()
6✔
1521
            }),
1522
            ['target', 'body']
1523
        );
1524
    }
1525
}
1526

1527
export class WhileStatement extends Statement {
1✔
1528
    constructor(options: {
1529
        while?: Token;
1530
        endWhile?: Token;
1531
        condition: Expression;
1532
        body: Block;
1533
    }) {
1534
        super();
37✔
1535
        this.tokens = {
37✔
1536
            while: options.while,
1537
            endWhile: options.endWhile
1538
        };
1539
        this.body = options.body;
37✔
1540
        this.condition = options.condition;
37✔
1541
        this.location = util.createBoundingLocation(
37✔
1542
            this.tokens.while,
1543
            this.condition,
1544
            this.body,
1545
            this.tokens.endWhile
1546
        );
1547
    }
1548

1549
    public readonly tokens: {
1550
        readonly while?: Token;
1551
        readonly endWhile?: Token;
1552
    };
1553
    public readonly condition: Expression;
1554
    public readonly body: Block;
1555

1556
    public readonly kind = AstNodeKind.WhileStatement;
37✔
1557

1558
    public readonly location: Location | undefined;
1559

1560
    transpile(state: BrsTranspileState) {
1561
        let result = [] as TranspileResult;
8✔
1562
        //while
1563
        result.push(
8✔
1564
            state.transpileToken(this.tokens.while, 'while'),
1565
            ' '
1566
        );
1567
        //condition
1568
        result.push(
8✔
1569
            ...this.condition.transpile(state)
1570
        );
1571
        state.lineage.unshift(this);
8✔
1572
        //body
1573
        result.push(...this.body.transpile(state));
8✔
1574
        state.lineage.shift();
8✔
1575

1576
        //end while
1577
        result.push(...state.transpileEndBlockToken(this.body, this.tokens.endWhile, 'end while'));
8✔
1578

1579
        return result;
8✔
1580
    }
1581

1582
    walk(visitor: WalkVisitor, options: WalkOptions) {
1583
        if (options.walkMode & InternalWalkMode.walkExpressions) {
121✔
1584
            walk(this, 'condition', visitor, options);
118✔
1585
        }
1586
        if (options.walkMode & InternalWalkMode.walkStatements) {
121✔
1587
            walk(this, 'body', visitor, options);
120✔
1588
        }
1589
    }
1590

1591
    get leadingTrivia(): Token[] {
1592
        return this.tokens.while?.leadingTrivia ?? [];
107!
1593
    }
1594

1595
    public get endTrivia(): Token[] {
1596
        return this.tokens.endWhile?.leadingTrivia ?? [];
1!
1597
    }
1598

1599
    public clone() {
1600
        return this.finalizeClone(
4✔
1601
            new WhileStatement({
1602
                while: util.cloneToken(this.tokens.while),
1603
                endWhile: util.cloneToken(this.tokens.endWhile),
1604
                condition: this.condition?.clone(),
12✔
1605
                body: this.body?.clone()
12✔
1606
            }),
1607
            ['condition', 'body']
1608
        );
1609
    }
1610
}
1611

1612
export class DottedSetStatement extends Statement {
1✔
1613
    constructor(options: {
1614
        obj: Expression;
1615
        name: Identifier;
1616
        value: Expression;
1617
        dot?: Token;
1618
        equals?: Token;
1619
    }) {
1620
        super();
311✔
1621
        this.tokens = {
311✔
1622
            name: options.name,
1623
            dot: options.dot,
1624
            equals: options.equals
1625
        };
1626
        this.obj = options.obj;
311✔
1627
        this.value = options.value;
311✔
1628
        this.location = util.createBoundingLocation(
311✔
1629
            this.obj,
1630
            this.tokens.dot,
1631
            this.tokens.equals,
1632
            this.tokens.name,
1633
            this.value
1634
        );
1635
    }
1636
    public readonly tokens: {
1637
        readonly name: Identifier;
1638
        readonly equals?: Token;
1639
        readonly dot?: Token;
1640
    };
1641

1642
    public readonly obj: Expression;
1643
    public readonly value: Expression;
1644

1645
    public readonly kind = AstNodeKind.DottedSetStatement;
311✔
1646

1647
    public readonly location: Location | undefined;
1648

1649
    transpile(state: BrsTranspileState) {
1650
        //if the value is a compound assignment, don't add the obj, dot, name, or operator...the expression will handle that
1651
        return [
14✔
1652
            //object
1653
            ...this.obj.transpile(state),
1654
            this.tokens.dot ? state.tokenToSourceNode(this.tokens.dot) : '.',
14✔
1655
            //name
1656
            state.transpileToken(this.tokens.name),
1657
            ' ',
1658
            state.transpileToken(this.tokens.equals, '='),
1659
            ' ',
1660
            //right-hand-side of assignment
1661
            ...this.value.transpile(state)
1662
        ];
1663

1664
    }
1665

1666
    walk(visitor: WalkVisitor, options: WalkOptions) {
1667
        if (options.walkMode & InternalWalkMode.walkExpressions) {
813✔
1668
            walk(this, 'obj', visitor, options);
811✔
1669
            walk(this, 'value', visitor, options);
811✔
1670
        }
1671
    }
1672

1673
    getType(options: GetTypeOptions) {
1674
        const objType = this.obj?.getType(options);
92!
1675
        const result = objType?.getMemberType(this.tokens.name?.text, options);
92!
1676
        options.typeChain?.push(new TypeChainEntry({
92✔
1677
            name: this.tokens.name?.text,
273!
1678
            type: result, data: options.data,
1679
            location: this.tokens.name?.location,
273!
1680
            astNode: this
1681
        }));
1682
        return result;
92✔
1683
    }
1684

1685
    get leadingTrivia(): Token[] {
1686
        return this.obj.leadingTrivia;
819✔
1687
    }
1688

1689
    public clone() {
1690
        return this.finalizeClone(
2✔
1691
            new DottedSetStatement({
1692
                obj: this.obj?.clone(),
6✔
1693
                dot: util.cloneToken(this.tokens.dot),
1694
                name: util.cloneToken(this.tokens.name),
1695
                equals: util.cloneToken(this.tokens.equals),
1696
                value: this.value?.clone()
6✔
1697
            }),
1698
            ['obj', 'value']
1699
        );
1700
    }
1701
}
1702

1703
export class IndexedSetStatement extends Statement {
1✔
1704
    constructor(options: {
1705
        obj: Expression;
1706
        indexes: Expression[];
1707
        value: Expression;
1708
        openingSquare?: Token;
1709
        closingSquare?: Token;
1710
        equals?: Token;
1711
    }) {
1712
        super();
47✔
1713
        this.tokens = {
47✔
1714
            openingSquare: options.openingSquare,
1715
            closingSquare: options.closingSquare,
1716
            equals: options.equals
1717
        };
1718
        this.obj = options.obj;
47✔
1719
        this.indexes = options.indexes ?? [];
47✔
1720
        this.value = options.value;
47✔
1721
        this.location = util.createBoundingLocation(
47✔
1722
            this.obj,
1723
            this.tokens.openingSquare,
1724
            ...this.indexes,
1725
            this.tokens.closingSquare,
1726
            this.value
1727
        );
1728
    }
1729

1730
    public readonly tokens: {
1731
        readonly openingSquare?: Token;
1732
        readonly closingSquare?: Token;
1733
        readonly equals?: Token;
1734
    };
1735
    public readonly obj: Expression;
1736
    public readonly indexes: Expression[];
1737
    public readonly value: Expression;
1738

1739
    public readonly kind = AstNodeKind.IndexedSetStatement;
47✔
1740

1741
    public readonly location: Location | undefined;
1742

1743
    transpile(state: BrsTranspileState) {
1744
        const result = [];
17✔
1745
        result.push(
17✔
1746
            //obj
1747
            ...this.obj.transpile(state),
1748
            //   [
1749
            state.transpileToken(this.tokens.openingSquare, '[')
1750
        );
1751
        for (let i = 0; i < this.indexes.length; i++) {
17✔
1752
            //add comma between indexes
1753
            if (i > 0) {
18✔
1754
                result.push(', ');
1✔
1755
            }
1756
            let index = this.indexes[i];
18✔
1757
            result.push(
18✔
1758
                ...(index?.transpile(state) ?? [])
108!
1759
            );
1760
        }
1761
        result.push(
17✔
1762
            state.transpileToken(this.tokens.closingSquare, ']'),
1763
            ' ',
1764
            state.transpileToken(this.tokens.equals, '='),
1765
            ' ',
1766
            ...this.value.transpile(state)
1767
        );
1768
        return result;
17✔
1769

1770
    }
1771

1772
    walk(visitor: WalkVisitor, options: WalkOptions) {
1773
        if (options.walkMode & InternalWalkMode.walkExpressions) {
166✔
1774
            walk(this, 'obj', visitor, options);
165✔
1775
            walkArray(this.indexes, visitor, options, this);
165✔
1776
            walk(this, 'value', visitor, options);
165✔
1777
        }
1778
    }
1779

1780
    get leadingTrivia(): Token[] {
1781
        return this.obj.leadingTrivia;
175✔
1782
    }
1783

1784
    public clone() {
1785
        return this.finalizeClone(
6✔
1786
            new IndexedSetStatement({
1787
                obj: this.obj?.clone(),
18✔
1788
                openingSquare: util.cloneToken(this.tokens.openingSquare),
1789
                indexes: this.indexes?.map(x => x?.clone()),
7✔
1790
                closingSquare: util.cloneToken(this.tokens.closingSquare),
1791
                equals: util.cloneToken(this.tokens.equals),
1792
                value: this.value?.clone()
18✔
1793
            }),
1794
            ['obj', 'indexes', 'value']
1795
        );
1796
    }
1797
}
1798

1799
export class LibraryStatement extends Statement implements TypedefProvider {
1✔
1800
    constructor(options: {
1801
        library: Token;
1802
        filePath?: Token;
1803
    }) {
1804
        super();
16✔
1805
        this.tokens = {
16✔
1806
            library: options?.library,
48!
1807
            filePath: options?.filePath
48!
1808
        };
1809
        this.location = util.createBoundingLocation(
16✔
1810
            this.tokens.library,
1811
            this.tokens.filePath
1812
        );
1813
    }
1814
    public readonly tokens: {
1815
        readonly library: Token;
1816
        readonly filePath?: Token;
1817
    };
1818

1819
    public readonly kind = AstNodeKind.LibraryStatement;
16✔
1820

1821
    public readonly location: Location | undefined;
1822

1823
    transpile(state: BrsTranspileState) {
1824
        let result = [] as TranspileResult;
2✔
1825
        result.push(
2✔
1826
            state.transpileToken(this.tokens.library)
1827
        );
1828
        //there will be a parse error if file path is missing, but let's prevent a runtime error just in case
1829
        if (this.tokens.filePath) {
2!
1830
            result.push(
2✔
1831
                ' ',
1832
                state.transpileToken(this.tokens.filePath)
1833
            );
1834
        }
1835
        return result;
2✔
1836
    }
1837

1838
    getTypedef(state: BrsTranspileState) {
UNCOV
1839
        return this.transpile(state);
×
1840
    }
1841

1842
    walk(visitor: WalkVisitor, options: WalkOptions) {
1843
        //nothing to walk
1844
    }
1845

1846
    get leadingTrivia(): Token[] {
1847
        return this.tokens.library?.leadingTrivia ?? [];
26!
1848
    }
1849

1850
    public clone() {
1851
        return this.finalizeClone(
1✔
1852
            new LibraryStatement({
1853
                library: util.cloneToken(this.tokens?.library),
3!
1854
                filePath: util.cloneToken(this.tokens?.filePath)
3!
1855
            })
1856
        );
1857
    }
1858
}
1859

1860
export class NamespaceStatement extends Statement implements TypedefProvider {
1✔
1861
    constructor(options: {
1862
        namespace?: Token;
1863
        nameExpression: VariableExpression | DottedGetExpression;
1864
        body: Body;
1865
        endNamespace?: Token;
1866
    }) {
1867
        super();
638✔
1868
        this.tokens = {
638✔
1869
            namespace: options.namespace,
1870
            endNamespace: options.endNamespace
1871
        };
1872
        this.nameExpression = options.nameExpression;
638✔
1873
        this.body = options.body;
638✔
1874
        this.symbolTable = new SymbolTable(`NamespaceStatement: '${this.name}'`, () => this.getRoot()?.getSymbolTable());
5,720!
1875
    }
1876

1877
    public readonly tokens: {
1878
        readonly namespace?: Token;
1879
        readonly endNamespace?: Token;
1880
    };
1881

1882
    public readonly nameExpression: VariableExpression | DottedGetExpression;
1883
    public readonly body: Body;
1884

1885
    public readonly kind = AstNodeKind.NamespaceStatement;
638✔
1886

1887
    /**
1888
     * The string name for this namespace
1889
     */
1890
    public get name(): string {
1891
        return this.getName(ParseMode.BrighterScript);
2,395✔
1892
    }
1893

1894
    public get location() {
1895
        return this.cacheLocation();
390✔
1896
    }
1897
    private _location: Location | undefined;
1898

1899
    public cacheLocation() {
1900
        if (!this._location) {
1,026✔
1901
            this._location = util.createBoundingLocation(
638✔
1902
                this.tokens.namespace,
1903
                this.nameExpression,
1904
                this.body,
1905
                this.tokens.endNamespace
1906
            );
1907
        }
1908
        return this._location;
1,026✔
1909
    }
1910

1911
    public getName(parseMode: ParseMode) {
1912
        const sep = parseMode === ParseMode.BrighterScript ? '.' : '_';
8,770✔
1913
        let name = util.getAllDottedGetPartsAsString(this.nameExpression, parseMode);
8,770✔
1914
        if ((this.parent as Body)?.parent?.kind === AstNodeKind.NamespaceStatement) {
8,770✔
1915
            name = (this.parent.parent as NamespaceStatement).getName(parseMode) + sep + name;
370✔
1916
        }
1917
        return name;
8,770✔
1918
    }
1919

1920
    public get leadingTrivia(): Token[] {
1921
        return this.tokens.namespace?.leadingTrivia;
1,858!
1922
    }
1923

1924
    public get endTrivia(): Token[] {
UNCOV
1925
        return this.tokens.endNamespace?.leadingTrivia;
×
1926
    }
1927

1928
    public getNameParts() {
1929
        let parts = util.getAllDottedGetParts(this.nameExpression);
1,425✔
1930

1931
        if ((this.parent as Body)?.parent?.kind === AstNodeKind.NamespaceStatement) {
1,425!
1932
            parts = (this.parent.parent as NamespaceStatement).getNameParts().concat(parts);
64✔
1933
        }
1934
        return parts;
1,425✔
1935
    }
1936

1937
    transpile(state: BrsTranspileState) {
1938
        //namespaces don't actually have any real content, so just transpile their bodies
1939
        return [
60✔
1940
            state.transpileAnnotations(this),
1941
            state.transpileLeadingComments(this.tokens.namespace),
1942
            this.body.transpile(state),
1943
            state.transpileLeadingComments(this.tokens.endNamespace)
1944
        ];
1945
    }
1946

1947
    getTypedef(state: BrsTranspileState) {
1948
        let result: TranspileResult = [];
7✔
1949
        for (let comment of util.getLeadingComments(this) ?? []) {
7!
UNCOV
1950
            result.push(
×
1951
                comment.text,
1952
                state.newline,
1953
                state.indent()
1954
            );
1955
        }
1956

1957
        result.push('namespace ',
7✔
1958
            ...this.getName(ParseMode.BrighterScript),
1959
            state.newline
1960
        );
1961
        state.blockDepth++;
7✔
1962
        result.push(
7✔
1963
            ...this.body.getTypedef(state)
1964
        );
1965
        state.blockDepth--;
7✔
1966

1967
        result.push(
7✔
1968
            state.indent(),
1969
            'end namespace'
1970
        );
1971
        return result;
7✔
1972
    }
1973

1974
    walk(visitor: WalkVisitor, options: WalkOptions) {
1975
        if (options.walkMode & InternalWalkMode.walkExpressions) {
3,243✔
1976
            walk(this, 'nameExpression', visitor, options);
2,656✔
1977
        }
1978

1979
        if (this.body.statements.length > 0 && options.walkMode & InternalWalkMode.walkStatements) {
3,243✔
1980
            walk(this, 'body', visitor, options);
3,040✔
1981
        }
1982
    }
1983

1984
    getType(options: GetTypeOptions) {
1985
        const resultType = new NamespaceType(this.name);
1,117✔
1986
        return resultType;
1,117✔
1987
    }
1988

1989
    public clone() {
1990
        const clone = this.finalizeClone(
3✔
1991
            new NamespaceStatement({
1992
                namespace: util.cloneToken(this.tokens.namespace),
1993
                nameExpression: this.nameExpression?.clone(),
9✔
1994
                body: this.body?.clone(),
9✔
1995
                endNamespace: util.cloneToken(this.tokens.endNamespace)
1996
            }),
1997
            ['nameExpression', 'body']
1998
        );
1999
        clone.cacheLocation();
3✔
2000
        return clone;
3✔
2001
    }
2002
}
2003

2004
export class ImportStatement extends Statement implements TypedefProvider {
1✔
2005
    constructor(options: {
2006
        import?: Token;
2007
        path?: Token;
2008
    }) {
2009
        super();
207✔
2010
        this.tokens = {
207✔
2011
            import: options.import,
2012
            path: options.path
2013
        };
2014
        this.location = util.createBoundingLocation(
207✔
2015
            this.tokens.import,
2016
            this.tokens.path
2017
        );
2018
        if (this.tokens.path) {
207✔
2019
            //remove quotes
2020
            this.filePath = this.tokens.path.text.replace(/"/g, '');
205✔
2021
            if (this.tokens.path?.location?.range) {
205!
2022
                //adjust the range to exclude the quotes
2023
                this.tokens.path.location = util.createLocation(
201✔
2024
                    this.tokens.path.location.range.start.line,
2025
                    this.tokens.path.location.range.start.character + 1,
2026
                    this.tokens.path.location.range.end.line,
2027
                    this.tokens.path.location.range.end.character - 1,
2028
                    this.tokens.path.location.uri
2029
                );
2030
            }
2031
        }
2032
    }
2033

2034
    public readonly tokens: {
2035
        readonly import?: Token;
2036
        readonly path: Token;
2037
    };
2038

2039
    public readonly kind = AstNodeKind.ImportStatement;
207✔
2040

2041
    public readonly location: Location;
2042

2043
    public readonly filePath: string;
2044

2045
    transpile(state: BrsTranspileState) {
2046
        //The xml files are responsible for adding the additional script imports, but
2047
        //add the import statement as a comment just for debugging purposes
2048
        return [
13✔
2049
            state.transpileToken(this.tokens.import, 'import', true),
2050
            ' ',
2051
            state.transpileToken(this.tokens.path)
2052
        ];
2053
    }
2054

2055
    /**
2056
     * Get the typedef for this statement
2057
     */
2058
    public getTypedef(state: BrsTranspileState) {
2059
        return [
3✔
2060
            this.tokens.import?.text ?? 'import',
18!
2061
            ' ',
2062
            //replace any `.bs` extension with `.brs`
2063
            this.tokens.path.text.replace(/\.bs"?$/i, '.brs"')
2064
        ];
2065
    }
2066

2067
    walk(visitor: WalkVisitor, options: WalkOptions) {
2068
        //nothing to walk
2069
    }
2070

2071
    get leadingTrivia(): Token[] {
2072
        return this.tokens.import?.leadingTrivia ?? [];
576!
2073
    }
2074

2075
    public clone() {
2076
        return this.finalizeClone(
1✔
2077
            new ImportStatement({
2078
                import: util.cloneToken(this.tokens.import),
2079
                path: util.cloneToken(this.tokens.path)
2080
            })
2081
        );
2082
    }
2083
}
2084

2085
export class InterfaceStatement extends Statement implements TypedefProvider {
1✔
2086
    constructor(options: {
2087
        interface: Token;
2088
        name: Identifier;
2089
        extends?: Token;
2090
        parentInterfaceName?: TypeExpression;
2091
        body: Statement[];
2092
        endInterface?: Token;
2093
    }) {
2094
        super();
165✔
2095
        this.tokens = {
165✔
2096
            interface: options.interface,
2097
            name: options.name,
2098
            extends: options.extends,
2099
            endInterface: options.endInterface
2100
        };
2101
        this.parentInterfaceName = options.parentInterfaceName;
165✔
2102
        this.body = options.body;
165✔
2103
        this.location = util.createBoundingLocation(
165✔
2104
            this.tokens.interface,
2105
            this.tokens.name,
2106
            this.tokens.extends,
2107
            this.parentInterfaceName,
2108
            ...this.body ?? [],
495✔
2109
            this.tokens.endInterface
2110
        );
2111
    }
2112
    public readonly parentInterfaceName?: TypeExpression;
2113
    public readonly body: Statement[];
2114

2115
    public readonly kind = AstNodeKind.InterfaceStatement;
165✔
2116

2117
    public readonly tokens = {} as {
165✔
2118
        readonly interface?: Token;
2119
        readonly name: Identifier;
2120
        readonly extends?: Token;
2121
        readonly endInterface?: Token;
2122
    };
2123

2124
    public readonly location: Location | undefined;
2125

2126
    public get fields(): InterfaceFieldStatement[] {
2127
        return this.body.filter(x => isInterfaceFieldStatement(x)) as InterfaceFieldStatement[];
210✔
2128
    }
2129

2130
    public get methods(): InterfaceMethodStatement[] {
2131
        return this.body.filter(x => isInterfaceMethodStatement(x)) as InterfaceMethodStatement[];
205✔
2132
    }
2133

2134

2135
    public hasParentInterface() {
UNCOV
2136
        return !!this.parentInterfaceName;
×
2137
    }
2138

2139
    public get leadingTrivia(): Token[] {
2140
        return this.tokens.interface?.leadingTrivia;
414!
2141
    }
2142

2143
    public get endTrivia(): Token[] {
UNCOV
2144
        return this.tokens.endInterface?.leadingTrivia;
×
2145
    }
2146

2147

2148
    /**
2149
     * The name of the interface WITH its leading namespace (if applicable)
2150
     */
2151
    public get fullName() {
2152
        const name = this.tokens.name?.text;
2!
2153
        if (name) {
2!
2154
            const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
2✔
2155
            if (namespace) {
2✔
2156
                let namespaceName = namespace.getName(ParseMode.BrighterScript);
1✔
2157
                return `${namespaceName}.${name}`;
1✔
2158
            } else {
2159
                return name;
1✔
2160
            }
2161
        } else {
2162
            //return undefined which will allow outside callers to know that this interface doesn't have a name
UNCOV
2163
            return undefined;
×
2164
        }
2165
    }
2166

2167
    /**
2168
     * The name of the interface (without the namespace prefix)
2169
     */
2170
    public get name() {
2171
        return this.tokens.name?.text;
142!
2172
    }
2173

2174
    /**
2175
     * Get the name of this expression based on the parse mode
2176
     */
2177
    public getName(parseMode: ParseMode) {
2178
        const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
142✔
2179
        if (namespace) {
142✔
2180
            let delimiter = parseMode === ParseMode.BrighterScript ? '.' : '_';
23!
2181
            let namespaceName = namespace.getName(parseMode);
23✔
2182
            return namespaceName + delimiter + this.name;
23✔
2183
        } else {
2184
            return this.name;
119✔
2185
        }
2186
    }
2187

2188
    public transpile(state: BrsTranspileState): TranspileResult {
2189
        //interfaces should completely disappear at runtime
2190
        return [
13✔
2191
            state.transpileLeadingComments(this.tokens.interface)
2192
        ];
2193
    }
2194

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

2254
    walk(visitor: WalkVisitor, options: WalkOptions) {
2255
        //visitor-less walk function to do parent linking
2256
        walk(this, 'parentInterfaceName', null, options);
716✔
2257

2258
        if (options.walkMode & InternalWalkMode.walkStatements) {
716!
2259
            walkArray(this.body, visitor, options, this);
716✔
2260
        }
2261
    }
2262

2263
    getType(options: GetTypeOptions) {
2264
        const superIface = this.parentInterfaceName?.getType(options) as InterfaceType;
135✔
2265

2266
        const resultType = new InterfaceType(this.getName(ParseMode.BrighterScript), superIface);
135✔
2267
        for (const statement of this.methods) {
135✔
2268
            const memberType = statement?.getType({ ...options, typeChain: undefined }); // no typechain info needed
28!
2269
            const flag = statement.isOptional ? SymbolTypeFlag.runtime | SymbolTypeFlag.optional : SymbolTypeFlag.runtime;
28✔
2270
            resultType.addMember(statement?.tokens.name?.text, { definingNode: statement }, memberType, flag);
28!
2271
        }
2272
        for (const statement of this.fields) {
135✔
2273
            const memberType = statement?.getType({ ...options, typeChain: undefined }); // no typechain info needed
172!
2274
            const flag = statement.isOptional ? SymbolTypeFlag.runtime | SymbolTypeFlag.optional : SymbolTypeFlag.runtime;
172✔
2275
            resultType.addMember(statement?.tokens.name?.text, { definingNode: statement, isInstance: true }, memberType, flag);
172!
2276
        }
2277
        options.typeChain?.push(new TypeChainEntry({
135✔
2278
            name: this.getName(ParseMode.BrighterScript),
2279
            type: resultType,
2280
            data: options.data,
2281
            astNode: this
2282
        }));
2283
        return resultType;
135✔
2284
    }
2285

2286
    public clone() {
2287
        return this.finalizeClone(
8✔
2288
            new InterfaceStatement({
2289
                interface: util.cloneToken(this.tokens.interface),
2290
                name: util.cloneToken(this.tokens.name),
2291
                extends: util.cloneToken(this.tokens.extends),
2292
                parentInterfaceName: this.parentInterfaceName?.clone(),
24✔
2293
                body: this.body?.map(x => x?.clone()),
9✔
2294
                endInterface: util.cloneToken(this.tokens.endInterface)
2295
            }),
2296
            ['parentInterfaceName', 'body']
2297
        );
2298
    }
2299
}
2300

2301
export class InterfaceFieldStatement extends Statement implements TypedefProvider {
1✔
2302
    public transpile(state: BrsTranspileState): TranspileResult {
UNCOV
2303
        throw new Error('Method not implemented.');
×
2304
    }
2305
    constructor(options: {
2306
        name: Identifier;
2307
        as?: Token;
2308
        typeExpression?: TypeExpression;
2309
        optional?: Token;
2310
    }) {
2311
        super();
191✔
2312
        this.tokens = {
191✔
2313
            optional: options.optional,
2314
            name: options.name,
2315
            as: options.as
2316
        };
2317
        this.typeExpression = options.typeExpression;
191✔
2318
        this.location = util.createBoundingLocation(
191✔
2319
            this.tokens.optional,
2320
            this.tokens.name,
2321
            this.tokens.as,
2322
            this.typeExpression
2323
        );
2324
    }
2325

2326
    public readonly kind = AstNodeKind.InterfaceFieldStatement;
191✔
2327

2328
    public readonly typeExpression?: TypeExpression;
2329

2330
    public readonly location: Location | undefined;
2331

2332
    public readonly tokens: {
2333
        readonly name: Identifier;
2334
        readonly as: Token;
2335
        readonly optional?: Token;
2336
    };
2337

2338
    public get leadingTrivia(): Token[] {
2339
        return this.tokens.optional?.leadingTrivia ?? this.tokens.name.leadingTrivia;
508✔
2340
    }
2341

2342
    public get name() {
UNCOV
2343
        return this.tokens.name.text;
×
2344
    }
2345

2346
    public get isOptional() {
2347
        return !!this.tokens.optional;
192✔
2348
    }
2349

2350
    walk(visitor: WalkVisitor, options: WalkOptions) {
2351
        if (options.walkMode & InternalWalkMode.walkExpressions) {
1,207✔
2352
            walk(this, 'typeExpression', visitor, options);
1,048✔
2353
        }
2354
    }
2355

2356
    getTypedef(state: BrsTranspileState): TranspileResult {
2357
        const result = [] as TranspileResult;
12✔
2358
        for (let comment of util.getLeadingComments(this) ?? []) {
12!
UNCOV
2359
            result.push(
×
2360
                comment.text,
2361
                state.newline,
2362
                state.indent()
2363
            );
2364
        }
2365
        for (let annotation of this.annotations ?? []) {
12✔
2366
            result.push(
1✔
2367
                ...annotation.getTypedef(state),
2368
                state.newline,
2369
                state.indent()
2370
            );
2371
        }
2372
        if (this.isOptional) {
12✔
2373
            result.push(
1✔
2374
                this.tokens.optional!.text,
2375
                ' '
2376
            );
2377
        }
2378
        result.push(
12✔
2379
            this.tokens.name.text
2380
        );
2381

2382
        if (this.typeExpression) {
12!
2383
            result.push(
12✔
2384
                ' as ',
2385
                ...this.typeExpression.getTypedef(state)
2386
            );
2387
        }
2388
        return result;
12✔
2389
    }
2390

2391
    public getType(options: GetTypeOptions): BscType {
2392
        return this.typeExpression?.getType(options) ?? DynamicType.instance;
176✔
2393
    }
2394

2395
    public clone() {
2396
        return this.finalizeClone(
4✔
2397
            new InterfaceFieldStatement({
2398
                name: util.cloneToken(this.tokens.name),
2399
                as: util.cloneToken(this.tokens.as),
2400
                typeExpression: this.typeExpression?.clone(),
12✔
2401
                optional: util.cloneToken(this.tokens.optional)
2402
            })
2403
        );
2404
    }
2405

2406
}
2407

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

2437
    public readonly kind = AstNodeKind.InterfaceMethodStatement;
46✔
2438

2439
    public get location() {
2440
        return util.createBoundingLocation(
72✔
2441
            this.tokens.optional,
2442
            this.tokens.functionType,
2443
            this.tokens.name,
2444
            this.tokens.leftParen,
2445
            ...(this.params ?? []),
216!
2446
            this.tokens.rightParen,
2447
            this.tokens.as,
2448
            this.returnTypeExpression
2449
        );
2450
    }
2451
    /**
2452
     * Get the name of this method.
2453
     */
2454
    public getName(parseMode: ParseMode) {
2455
        return this.tokens.name.text;
28✔
2456
    }
2457

2458
    public readonly tokens: {
2459
        readonly optional?: Token;
2460
        readonly functionType: Token;
2461
        readonly name: Identifier;
2462
        readonly leftParen?: Token;
2463
        readonly rightParen?: Token;
2464
        readonly as?: Token;
2465
    };
2466

2467
    public readonly params: FunctionParameterExpression[];
2468
    public readonly returnTypeExpression?: TypeExpression;
2469

2470
    public get isOptional() {
2471
        return !!this.tokens.optional;
41✔
2472
    }
2473

2474
    public get leadingTrivia(): Token[] {
2475
        return this.tokens.optional?.leadingTrivia ?? this.tokens.functionType.leadingTrivia;
88✔
2476
    }
2477

2478
    walk(visitor: WalkVisitor, options: WalkOptions) {
2479
        if (options.walkMode & InternalWalkMode.walkExpressions) {
215✔
2480
            walk(this, 'returnTypeExpression', visitor, options);
190✔
2481
        }
2482
    }
2483

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

2538
    public getType(options: GetTypeOptions): TypedFunctionType {
2539
        //if there's a defined return type, use that
2540
        let returnType = this.returnTypeExpression?.getType(options);
28✔
2541
        const isSub = this.tokens.functionType?.kind === TokenKind.Sub || !returnType;
28!
2542
        //if we don't have a return type and this is a sub, set the return type to `void`. else use `dynamic`
2543
        if (!returnType) {
28✔
2544
            returnType = isSub ? VoidType.instance : DynamicType.instance;
10!
2545
        }
2546

2547
        const resultType = new TypedFunctionType(returnType);
28✔
2548
        resultType.isSub = isSub;
28✔
2549
        for (let param of this.params) {
28✔
2550
            resultType.addParameter(param.tokens.name.text, param.getType(options), !!param.defaultValue);
7✔
2551
        }
2552
        if (options.typeChain) {
28!
2553
            // need Interface type for type chain
UNCOV
2554
            this.parent?.getType(options);
×
2555
        }
2556
        let funcName = this.getName(ParseMode.BrighterScript);
28✔
2557
        resultType.setName(funcName);
28✔
2558
        options.typeChain?.push(new TypeChainEntry({ name: resultType.name, type: resultType, data: options.data, astNode: this }));
28!
2559
        return resultType;
28✔
2560
    }
2561

2562
    public clone() {
2563
        return this.finalizeClone(
4✔
2564
            new InterfaceMethodStatement({
2565
                optional: util.cloneToken(this.tokens.optional),
2566
                functionType: util.cloneToken(this.tokens.functionType),
2567
                name: util.cloneToken(this.tokens.name),
2568
                leftParen: util.cloneToken(this.tokens.leftParen),
2569
                params: this.params?.map(p => p?.clone()),
3✔
2570
                rightParen: util.cloneToken(this.tokens.rightParen),
2571
                as: util.cloneToken(this.tokens.as),
2572
                returnTypeExpression: this.returnTypeExpression?.clone()
12✔
2573
            }),
2574
            ['params']
2575
        );
2576
    }
2577
}
2578

2579
export class ClassStatement extends Statement implements TypedefProvider {
1✔
2580
    constructor(options: {
2581
        class?: Token;
2582
        /**
2583
         * The name of the class (without namespace prefix)
2584
         */
2585
        name: Identifier;
2586
        body: Statement[];
2587
        endClass?: Token;
2588
        extends?: Token;
2589
        parentClassName?: TypeExpression;
2590
    }) {
2591
        super();
699✔
2592
        this.body = options.body ?? [];
699✔
2593
        this.tokens = {
699✔
2594
            name: options.name,
2595
            class: options.class,
2596
            endClass: options.endClass,
2597
            extends: options.extends
2598
        };
2599
        this.parentClassName = options.parentClassName;
699✔
2600
        this.symbolTable = new SymbolTable(`ClassStatement: '${this.tokens.name?.text}'`, () => this.parent?.getSymbolTable());
1,431!
2601

2602
        for (let statement of this.body) {
699✔
2603
            if (isMethodStatement(statement)) {
707✔
2604
                this.methods.push(statement);
363✔
2605
                this.memberMap[statement?.tokens.name?.text.toLowerCase()] = statement;
363!
2606
            } else if (isFieldStatement(statement)) {
344✔
2607
                this.fields.push(statement);
343✔
2608
                this.memberMap[statement?.tokens.name?.text.toLowerCase()] = statement;
343!
2609
            }
2610
        }
2611

2612
        this.location = util.createBoundingLocation(
699✔
2613
            this.parentClassName,
2614
            ...(this.body ?? []),
2,097!
2615
            util.createBoundingLocationFromTokens(this.tokens)
2616
        );
2617
    }
2618

2619
    public readonly kind = AstNodeKind.ClassStatement;
699✔
2620

2621

2622
    public readonly tokens: {
2623
        readonly class?: Token;
2624
        /**
2625
         * The name of the class (without namespace prefix)
2626
         */
2627
        readonly name: Identifier;
2628
        readonly endClass?: Token;
2629
        readonly extends?: Token;
2630
    };
2631
    public readonly body: Statement[];
2632
    public readonly parentClassName: TypeExpression;
2633

2634

2635
    public getName(parseMode: ParseMode) {
2636
        const name = this.tokens.name?.text;
2,343✔
2637
        if (name) {
2,343✔
2638
            const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
2,341✔
2639
            if (namespace) {
2,341✔
2640
                let namespaceName = namespace.getName(parseMode);
882✔
2641
                let separator = parseMode === ParseMode.BrighterScript ? '.' : '_';
882✔
2642
                return namespaceName + separator + name;
882✔
2643
            } else {
2644
                return name;
1,459✔
2645
            }
2646
        } else {
2647
            //return undefined which will allow outside callers to know that this class doesn't have a name
2648
            return undefined;
2✔
2649
        }
2650
    }
2651

2652
    public get leadingTrivia(): Token[] {
2653
        return this.tokens.class?.leadingTrivia;
1,322!
2654
    }
2655

2656
    public get endTrivia(): Token[] {
UNCOV
2657
        return this.tokens.endClass?.leadingTrivia ?? [];
×
2658
    }
2659

2660
    public readonly memberMap = {} as Record<string, MemberStatement>;
699✔
2661
    public readonly methods = [] as MethodStatement[];
699✔
2662
    public readonly fields = [] as FieldStatement[];
699✔
2663

2664
    public readonly location: Location | undefined;
2665

2666
    transpile(state: BrsTranspileState) {
2667
        let result = [] as TranspileResult;
50✔
2668
        //make the builder
2669
        result.push(...this.getTranspiledBuilder(state));
50✔
2670
        result.push(
50✔
2671
            '\n',
2672
            state.indent()
2673
        );
2674
        //make the class assembler (i.e. the public-facing class creator method)
2675
        result.push(...this.getTranspiledClassFunction(state));
50✔
2676
        return result;
50✔
2677
    }
2678

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

2712
        let body = this.body;
15✔
2713
        //inject an empty "new" method if missing
2714
        if (!this.getConstructorFunction()) {
15✔
2715
            const constructor = createMethodStatement('new', TokenKind.Sub);
11✔
2716
            constructor.parent = this;
11✔
2717
            //walk the constructor to set up parent links
2718
            constructor.link();
11✔
2719
            body = [
11✔
2720
                constructor,
2721
                ...this.body
2722
            ];
2723
        }
2724

2725
        for (const member of body) {
15✔
2726
            if (isTypedefProvider(member)) {
35!
2727
                result.push(
35✔
2728
                    state.indent(),
2729
                    ...member.getTypedef(state),
2730
                    state.newline
2731
                );
2732
            }
2733
        }
2734
        state.blockDepth--;
15✔
2735
        result.push(
15✔
2736
            state.indent(),
2737
            'end class'
2738
        );
2739
        return result;
15✔
2740
    }
2741

2742
    /**
2743
     * Find the parent index for this class's parent.
2744
     * For class inheritance, every class is given an index.
2745
     * The base class is index 0, its child is index 1, and so on.
2746
     */
2747
    public getParentClassIndex(state: BrsTranspileState) {
2748
        let myIndex = 0;
116✔
2749
        let stmt = this as ClassStatement;
116✔
2750
        while (stmt) {
116✔
2751
            if (stmt.parentClassName) {
165✔
2752
                const namespace = stmt.findAncestor<NamespaceStatement>(isNamespaceStatement);
50✔
2753
                //find the parent class
2754
                stmt = state.file.getClassFileLink(
50✔
2755
                    stmt.parentClassName.getName(),
2756
                    namespace?.getName(ParseMode.BrighterScript)
150✔
2757
                )?.item;
50✔
2758
                myIndex++;
50✔
2759
            } else {
2760
                break;
115✔
2761
            }
2762
        }
2763
        const result = myIndex - 1;
116✔
2764
        if (result >= 0) {
116✔
2765
            return result;
43✔
2766
        } else {
2767
            return null;
73✔
2768
        }
2769
    }
2770

2771
    public hasParentClass() {
2772
        return !!this.parentClassName;
282✔
2773
    }
2774

2775
    /**
2776
     * Get all ancestor classes, in closest-to-furthest order (i.e. 0 is parent, 1 is grandparent, etc...).
2777
     * This will return an empty array if no ancestors were found
2778
     */
2779
    public getAncestors(state: BrsTranspileState) {
2780
        let ancestors = [] as ClassStatement[];
100✔
2781
        let stmt = this as ClassStatement;
100✔
2782
        while (stmt) {
100✔
2783
            if (stmt.parentClassName) {
142✔
2784
                const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
42✔
2785
                stmt = state.file.getClassFileLink(
42✔
2786
                    stmt.parentClassName.getName(),
2787
                    namespace?.getName(ParseMode.BrighterScript)
126✔
2788
                )?.item;
42!
2789
                ancestors.push(stmt);
42✔
2790
            } else {
2791
                break;
100✔
2792
            }
2793
        }
2794
        return ancestors;
100✔
2795
    }
2796

2797
    private getBuilderName(name: string) {
2798
        if (name.includes('.')) {
118✔
2799
            name = name.replace(/\./gi, '_');
3✔
2800
        }
2801
        return `__${name}_builder`;
118✔
2802
    }
2803

2804
    public getConstructorType() {
2805
        const constructorType = this.getConstructorFunction()?.getType({ flags: SymbolTypeFlag.runtime }) ?? new TypedFunctionType(null);
130✔
2806
        constructorType.returnType = this.getType({ flags: SymbolTypeFlag.runtime });
130✔
2807
        return constructorType;
130✔
2808
    }
2809

2810
    /**
2811
     * Get the constructor function for this class (if exists), or undefined if not exist
2812
     */
2813
    private getConstructorFunction() {
2814
        return this.body.find((stmt) => {
245✔
2815
            return (stmt as MethodStatement)?.tokens.name?.text?.toLowerCase() === 'new';
217!
2816
        }) as MethodStatement;
2817
    }
2818

2819
    /**
2820
     * Determine if the specified field was declared in one of the ancestor classes
2821
     */
2822
    public isFieldDeclaredByAncestor(fieldName: string, ancestors: ClassStatement[]) {
UNCOV
2823
        let lowerFieldName = fieldName.toLowerCase();
×
UNCOV
2824
        for (let ancestor of ancestors) {
×
UNCOV
2825
            if (ancestor.memberMap[lowerFieldName]) {
×
UNCOV
2826
                return true;
×
2827
            }
2828
        }
UNCOV
2829
        return false;
×
2830
    }
2831

2832
    /**
2833
     * The builder is a function that assigns all of the methods and property names to a class instance.
2834
     * This needs to be a separate function so that child classes can call the builder from their parent
2835
     * without instantiating the parent constructor at that point in time.
2836
     */
2837
    private getTranspiledBuilder(state: BrsTranspileState) {
2838
        let result = [] as TranspileResult;
50✔
2839
        result.push(`function ${this.getBuilderName(this.getName(ParseMode.BrightScript)!)}()\n`);
50✔
2840
        state.blockDepth++;
50✔
2841
        //indent
2842
        result.push(state.indent());
50✔
2843

2844
        /**
2845
         * The lineage of this class. index 0 is a direct parent, index 1 is index 0's parent, etc...
2846
         */
2847
        let ancestors = this.getAncestors(state);
50✔
2848

2849
        //construct parent class or empty object
2850
        if (ancestors[0]) {
50✔
2851
            const ancestorNamespace = ancestors[0].findAncestor<NamespaceStatement>(isNamespaceStatement);
18✔
2852
            let fullyQualifiedClassName = util.getFullyQualifiedClassName(
18✔
2853
                ancestors[0].getName(ParseMode.BrighterScript)!,
2854
                ancestorNamespace?.getName(ParseMode.BrighterScript)
54✔
2855
            );
2856
            result.push(
18✔
2857
                'instance = ',
2858
                this.getBuilderName(fullyQualifiedClassName), '()');
2859
        } else {
2860
            //use an empty object.
2861
            result.push('instance = {}');
32✔
2862
        }
2863
        result.push(
50✔
2864
            state.newline,
2865
            state.indent()
2866
        );
2867
        let parentClassIndex = this.getParentClassIndex(state);
50✔
2868

2869
        let body = this.body;
50✔
2870
        //inject an empty "new" method if missing
2871
        if (!this.getConstructorFunction()) {
50✔
2872
            body = [
29✔
2873
                createMethodStatement('new', TokenKind.Sub),
2874
                ...this.body
2875
            ];
2876
        }
2877

2878
        for (let statement of body) {
50✔
2879
            //is field statement
2880
            if (isFieldStatement(statement)) {
79✔
2881
                //do nothing with class fields in this situation, they are handled elsewhere
2882
                continue;
14✔
2883

2884
                //methods
2885
            } else if (isMethodStatement(statement)) {
65!
2886

2887
                //store overridden parent methods as super{parentIndex}_{methodName}
2888
                if (
65✔
2889
                    //is override method
2890
                    statement.tokens.override ||
176✔
2891
                    //is constructor function in child class
2892
                    (statement.tokens.name.text.toLowerCase() === 'new' && ancestors[0])
2893
                ) {
2894
                    result.push(
22✔
2895
                        `instance.super${parentClassIndex}_${statement.tokens.name.text} = instance.${statement.tokens.name.text}`,
2896
                        state.newline,
2897
                        state.indent()
2898
                    );
2899
                }
2900

2901
                state.classStatement = this;
65✔
2902
                state.skipLeadingComments = true;
65✔
2903
                //add leading comments
2904
                if (statement.leadingTrivia.filter(token => token.kind === TokenKind.Comment).length > 0) {
78✔
2905
                    result.push(
2✔
2906
                        ...state.transpileComments(statement.leadingTrivia),
2907
                        state.indent()
2908
                    );
2909
                }
2910
                result.push(
65✔
2911
                    'instance.',
2912
                    state.transpileToken(statement.tokens.name),
2913
                    ' = ',
2914
                    ...statement.transpile(state),
2915
                    state.newline,
2916
                    state.indent()
2917
                );
2918
                state.skipLeadingComments = false;
65✔
2919
                delete state.classStatement;
65✔
2920
            } else {
2921
                //other random statements (probably just comments)
UNCOV
2922
                result.push(
×
2923
                    ...statement.transpile(state),
2924
                    state.newline,
2925
                    state.indent()
2926
                );
2927
            }
2928
        }
2929
        //return the instance
2930
        result.push('return instance\n');
50✔
2931
        state.blockDepth--;
50✔
2932
        result.push(state.indent());
50✔
2933
        result.push(`end function`);
50✔
2934
        return result;
50✔
2935
    }
2936

2937
    /**
2938
     * The class function is the function with the same name as the class. This is the function that
2939
     * consumers should call to create a new instance of that class.
2940
     * This invokes the builder, gets an instance of the class, then invokes the "new" function on that class.
2941
     */
2942
    private getTranspiledClassFunction(state: BrsTranspileState) {
2943
        let result: TranspileResult = state.transpileAnnotations(this);
50✔
2944
        const constructorFunction = this.getConstructorFunction();
50✔
2945
        const constructorParams = constructorFunction ? constructorFunction.func.parameters : [];
50✔
2946

2947
        result.push(
50✔
2948
            state.transpileLeadingComments(this.tokens.class),
2949
            state.sourceNode(this.tokens.class, 'function'),
2950
            state.sourceNode(this.tokens.class, ' '),
2951
            state.sourceNode(this.tokens.name, this.getName(ParseMode.BrightScript)),
2952
            `(`
2953
        );
2954
        let i = 0;
50✔
2955
        for (let param of constructorParams) {
50✔
2956
            if (i > 0) {
8✔
2957
                result.push(', ');
2✔
2958
            }
2959
            result.push(
8✔
2960
                param.transpile(state)
2961
            );
2962
            i++;
8✔
2963
        }
2964
        result.push(
50✔
2965
            ')',
2966
            '\n'
2967
        );
2968

2969
        state.blockDepth++;
50✔
2970
        result.push(state.indent());
50✔
2971
        result.push(`instance = ${this.getBuilderName(this.getName(ParseMode.BrightScript)!)}()\n`);
50✔
2972

2973
        result.push(state.indent());
50✔
2974
        result.push(`instance.new(`);
50✔
2975

2976
        //append constructor arguments
2977
        i = 0;
50✔
2978
        for (let param of constructorParams) {
50✔
2979
            if (i > 0) {
8✔
2980
                result.push(', ');
2✔
2981
            }
2982
            result.push(
8✔
2983
                state.transpileToken(param.tokens.name)
2984
            );
2985
            i++;
8✔
2986
        }
2987
        result.push(
50✔
2988
            ')',
2989
            '\n'
2990
        );
2991

2992
        result.push(state.indent());
50✔
2993
        result.push(`return instance\n`);
50✔
2994

2995
        state.blockDepth--;
50✔
2996
        result.push(state.indent());
50✔
2997
        result.push(`end function`);
50✔
2998
        return result;
50✔
2999
    }
3000

3001
    walk(visitor: WalkVisitor, options: WalkOptions) {
3002
        //visitor-less walk function to do parent linking
3003
        walk(this, 'parentClassName', null, options);
2,723✔
3004

3005
        if (options.walkMode & InternalWalkMode.walkStatements) {
2,723!
3006
            walkArray(this.body, visitor, options, this);
2,723✔
3007
        }
3008
    }
3009

3010
    getType(options: GetTypeOptions) {
3011
        const superClass = this.parentClassName?.getType(options) as ClassType;
551✔
3012

3013
        const resultType = new ClassType(this.getName(ParseMode.BrighterScript), superClass);
551✔
3014

3015
        for (const statement of this.methods) {
551✔
3016
            const funcType = statement?.func.getType({ ...options, typeChain: undefined }); //no typechain needed
285!
3017
            let flag = SymbolTypeFlag.runtime;
285✔
3018
            if (statement.accessModifier?.kind === TokenKind.Private) {
285✔
3019
                flag |= SymbolTypeFlag.private;
9✔
3020
            }
3021
            if (statement.accessModifier?.kind === TokenKind.Protected) {
285✔
3022
                flag |= SymbolTypeFlag.protected;
8✔
3023
            }
3024
            resultType.addMember(statement?.tokens.name?.text, { definingNode: statement }, funcType, flag);
285!
3025
        }
3026
        for (const statement of this.fields) {
551✔
3027
            const fieldType = statement.getType({ ...options, typeChain: undefined }); //no typechain needed
282✔
3028
            let flag = SymbolTypeFlag.runtime;
282✔
3029
            if (statement.isOptional) {
282✔
3030
                flag |= SymbolTypeFlag.optional;
7✔
3031
            }
3032
            if (statement.tokens.accessModifier?.kind === TokenKind.Private) {
282✔
3033
                flag |= SymbolTypeFlag.private;
19✔
3034
            }
3035
            if (statement.tokens.accessModifier?.kind === TokenKind.Protected) {
282✔
3036
                flag |= SymbolTypeFlag.protected;
9✔
3037
            }
3038
            resultType.addMember(statement?.tokens.name?.text, { definingNode: statement, isInstance: true }, fieldType, flag);
282!
3039
        }
3040
        options.typeChain?.push(new TypeChainEntry({ name: resultType.name, type: resultType, data: options.data, astNode: this }));
551✔
3041
        return resultType;
551✔
3042
    }
3043

3044
    public clone() {
3045
        return this.finalizeClone(
11✔
3046
            new ClassStatement({
3047
                class: util.cloneToken(this.tokens.class),
3048
                name: util.cloneToken(this.tokens.name),
3049
                body: this.body?.map(x => x?.clone()),
11✔
3050
                endClass: util.cloneToken(this.tokens.endClass),
3051
                extends: util.cloneToken(this.tokens.extends),
3052
                parentClassName: this.parentClassName?.clone()
33✔
3053
            }),
3054
            ['body', 'parentClassName']
3055
        );
3056
    }
3057
}
3058

3059
const accessModifiers = [
1✔
3060
    TokenKind.Public,
3061
    TokenKind.Protected,
3062
    TokenKind.Private
3063
];
3064
export class MethodStatement extends FunctionStatement {
1✔
3065
    constructor(
3066
        options: {
3067
            modifiers?: Token | Token[];
3068
            name: Identifier;
3069
            func: FunctionExpression;
3070
            override?: Token;
3071
        }
3072
    ) {
3073
        super(options);
403✔
3074
        if (options.modifiers) {
403✔
3075
            if (Array.isArray(options.modifiers)) {
40✔
3076
                this.modifiers.push(...options.modifiers);
4✔
3077
            } else {
3078
                this.modifiers.push(options.modifiers);
36✔
3079
            }
3080
        }
3081
        this.tokens = {
403✔
3082
            ...this.tokens,
3083
            override: options.override
3084
        };
3085
        this.location = util.createBoundingLocation(
403✔
3086
            ...(this.modifiers),
3087
            util.createBoundingLocationFromTokens(this.tokens),
3088
            this.func
3089
        );
3090
    }
3091

3092
    public readonly kind = AstNodeKind.MethodStatement as AstNodeKind;
403✔
3093

3094
    public readonly modifiers: Token[] = [];
403✔
3095

3096
    public readonly tokens: {
3097
        readonly name: Identifier;
3098
        readonly override?: Token;
3099
    };
3100

3101
    public get accessModifier() {
3102
        return this.modifiers.find(x => accessModifiers.includes(x.kind));
678✔
3103
    }
3104

3105
    public readonly location: Location | undefined;
3106

3107
    /**
3108
     * Get the name of this method.
3109
     */
3110
    public getName(parseMode: ParseMode) {
3111
        return this.tokens.name.text;
364✔
3112
    }
3113

3114
    public get leadingTrivia(): Token[] {
3115
        return this.func.leadingTrivia;
800✔
3116
    }
3117

3118
    transpile(state: BrsTranspileState) {
3119
        if (this.tokens.name.text.toLowerCase() === 'new') {
65✔
3120
            this.ensureSuperConstructorCall(state);
50✔
3121
            //TODO we need to undo this at the bottom of this method
3122
            this.injectFieldInitializersForConstructor(state);
50✔
3123
        }
3124
        //TODO - remove type information from these methods because that doesn't work
3125
        //convert the `super` calls into the proper methods
3126
        const parentClassIndex = state.classStatement.getParentClassIndex(state);
65✔
3127
        const visitor = createVisitor({
65✔
3128
            VariableExpression: e => {
3129
                if (e.tokens.name.text.toLocaleLowerCase() === 'super') {
61✔
3130
                    state.editor.setProperty(e.tokens.name, 'text', `m.super${parentClassIndex}_new`);
18✔
3131
                }
3132
            },
3133
            DottedGetExpression: e => {
3134
                const beginningVariable = util.findBeginningVariableExpression(e);
30✔
3135
                const lowerName = beginningVariable?.getName(ParseMode.BrighterScript).toLowerCase();
30!
3136
                if (lowerName === 'super') {
30✔
3137
                    state.editor.setProperty(beginningVariable.tokens.name, 'text', 'm');
7✔
3138
                    state.editor.setProperty(e.tokens.name, 'text', `super${parentClassIndex}_${e.tokens.name.text}`);
7✔
3139
                }
3140
            }
3141
        });
3142
        const walkOptions: WalkOptions = { walkMode: WalkMode.visitExpressions };
65✔
3143
        for (const statement of this.func.body.statements) {
65✔
3144
            visitor(statement, undefined);
62✔
3145
            statement.walk(visitor, walkOptions);
62✔
3146
        }
3147
        return this.func.transpile(state);
65✔
3148
    }
3149

3150
    getTypedef(state: BrsTranspileState) {
3151
        const result: TranspileResult = [];
23✔
3152
        for (let comment of util.getLeadingComments(this) ?? []) {
23!
UNCOV
3153
            result.push(
×
3154
                comment.text,
3155
                state.newline,
3156
                state.indent()
3157
            );
3158
        }
3159
        for (let annotation of this.annotations ?? []) {
23✔
3160
            result.push(
2✔
3161
                ...annotation.getTypedef(state),
3162
                state.newline,
3163
                state.indent()
3164
            );
3165
        }
3166
        if (this.accessModifier) {
23✔
3167
            result.push(
8✔
3168
                this.accessModifier.text,
3169
                ' '
3170
            );
3171
        }
3172
        if (this.tokens.override) {
23✔
3173
            result.push('override ');
1✔
3174
        }
3175
        result.push(
23✔
3176
            ...this.func.getTypedef(state)
3177
        );
3178
        return result;
23✔
3179
    }
3180

3181
    /**
3182
     * All child classes must call the parent constructor. The type checker will warn users when they don't call it in their own class,
3183
     * but we still need to call it even if they have omitted it. This injects the super call if it's missing
3184
     */
3185
    private ensureSuperConstructorCall(state: BrsTranspileState) {
3186
        //if this class doesn't extend another class, quit here
3187
        if (state.classStatement!.getAncestors(state).length === 0) {
50✔
3188
            return;
32✔
3189
        }
3190

3191
        //check whether any calls to super exist
3192
        let containsSuperCall =
3193
            this.func.body.statements.findIndex((x) => {
18✔
3194
                //is a call statement
3195
                return isExpressionStatement(x) && isCallExpression(x.expression) &&
8✔
3196
                    //is a call to super
3197
                    util.findBeginningVariableExpression(x.expression.callee as any).tokens.name?.text.toLowerCase() === 'super';
21!
3198
            }) !== -1;
3199

3200
        //if a call to super exists, quit here
3201
        if (containsSuperCall) {
18✔
3202
            return;
7✔
3203
        }
3204

3205
        //this is a child class, and the constructor doesn't contain a call to super. Inject one
3206
        const superCall = new ExpressionStatement({
11✔
3207
            expression: new CallExpression({
3208
                callee: new VariableExpression({
3209
                    name: {
3210
                        kind: TokenKind.Identifier,
3211
                        text: 'super',
3212
                        isReserved: false,
3213
                        location: state.classStatement.tokens.name.location,
3214
                        leadingWhitespace: '',
3215
                        leadingTrivia: []
3216
                    }
3217
                }),
3218
                openingParen: {
3219
                    kind: TokenKind.LeftParen,
3220
                    text: '(',
3221
                    isReserved: false,
3222
                    location: state.classStatement.tokens.name.location,
3223
                    leadingWhitespace: '',
3224
                    leadingTrivia: []
3225
                },
3226
                closingParen: {
3227
                    kind: TokenKind.RightParen,
3228
                    text: ')',
3229
                    isReserved: false,
3230
                    location: state.classStatement.tokens.name.location,
3231
                    leadingWhitespace: '',
3232
                    leadingTrivia: []
3233
                },
3234
                args: []
3235
            })
3236
        });
3237
        state.editor.arrayUnshift(this.func.body.statements, superCall);
11✔
3238
    }
3239

3240
    /**
3241
     * Inject field initializers at the top of the `new` function (after any present `super()` call)
3242
     */
3243
    private injectFieldInitializersForConstructor(state: BrsTranspileState) {
3244
        let startingIndex = state.classStatement!.hasParentClass() ? 1 : 0;
50✔
3245

3246
        let newStatements = [] as Statement[];
50✔
3247
        //insert the field initializers in order
3248
        for (let field of state.classStatement!.fields) {
50✔
3249
            let thisQualifiedName = { ...field.tokens.name };
14✔
3250
            thisQualifiedName.text = 'm.' + field.tokens.name?.text;
14!
3251
            const fieldAssignment = field.initialValue
14✔
3252
                ? new AssignmentStatement({
14✔
3253
                    equals: field.tokens.equals,
3254
                    name: thisQualifiedName,
3255
                    value: field.initialValue
3256
                })
3257
                : new AssignmentStatement({
3258
                    equals: createToken(TokenKind.Equal, '=', field.tokens.name.location),
3259
                    name: thisQualifiedName,
3260
                    //if there is no initial value, set the initial value to `invalid`
3261
                    value: createInvalidLiteral('invalid', field.tokens.name.location)
3262
                });
3263
            // Add parent so namespace lookups work
3264
            fieldAssignment.parent = state.classStatement;
14✔
3265
            newStatements.push(fieldAssignment);
14✔
3266
        }
3267
        state.editor.arraySplice(this.func.body.statements, startingIndex, 0, ...newStatements);
50✔
3268
    }
3269

3270
    walk(visitor: WalkVisitor, options: WalkOptions) {
3271
        if (options.walkMode & InternalWalkMode.walkExpressions) {
2,206✔
3272
            walk(this, 'func', visitor, options);
1,765✔
3273
        }
3274
    }
3275

3276
    public clone() {
3277
        return this.finalizeClone(
5✔
3278
            new MethodStatement({
3279
                modifiers: this.modifiers?.map(m => util.cloneToken(m)),
1✔
3280
                name: util.cloneToken(this.tokens.name),
3281
                func: this.func?.clone(),
15✔
3282
                override: util.cloneToken(this.tokens.override)
3283
            }),
3284
            ['func']
3285
        );
3286
    }
3287
}
3288

3289
export class FieldStatement extends Statement implements TypedefProvider {
1✔
3290
    constructor(options: {
3291
        accessModifier?: Token;
3292
        name: Identifier;
3293
        as?: Token;
3294
        typeExpression?: TypeExpression;
3295
        equals?: Token;
3296
        initialValue?: Expression;
3297
        optional?: Token;
3298
    }) {
3299
        super();
343✔
3300
        this.tokens = {
343✔
3301
            accessModifier: options.accessModifier,
3302
            name: options.name,
3303
            as: options.as,
3304
            equals: options.equals,
3305
            optional: options.optional
3306
        };
3307
        this.typeExpression = options.typeExpression;
343✔
3308
        this.initialValue = options.initialValue;
343✔
3309

3310
        this.location = util.createBoundingLocation(
343✔
3311
            util.createBoundingLocationFromTokens(this.tokens),
3312
            this.typeExpression,
3313
            this.initialValue
3314
        );
3315
    }
3316

3317
    public readonly tokens: {
3318
        readonly accessModifier?: Token;
3319
        readonly name: Identifier;
3320
        readonly as?: Token;
3321
        readonly equals?: Token;
3322
        readonly optional?: Token;
3323
    };
3324

3325
    public readonly typeExpression?: TypeExpression;
3326
    public readonly initialValue?: Expression;
3327

3328
    public readonly kind = AstNodeKind.FieldStatement;
343✔
3329

3330
    /**
3331
     * Derive a ValueKind from the type token, or the initial value.
3332
     * Defaults to `DynamicType`
3333
     */
3334
    getType(options: GetTypeOptions) {
3335
        let initialValueType = this.initialValue?.getType({ ...options, flags: SymbolTypeFlag.runtime });
328✔
3336

3337
        if (isInvalidType(initialValueType) || isVoidType(initialValueType) || isUninitializedType(initialValueType)) {
328✔
3338
            initialValueType = undefined;
4✔
3339
        }
3340

3341
        return this.typeExpression?.getType({ ...options, flags: SymbolTypeFlag.typetime }) ??
328✔
3342
            util.getDefaultTypeFromValueType(initialValueType) ??
328✔
3343
            DynamicType.instance;
3344
    }
3345

3346
    public readonly location: Location | undefined;
3347

3348
    public get leadingTrivia(): Token[] {
3349
        return this.tokens.accessModifier?.leadingTrivia ?? this.tokens.optional?.leadingTrivia ?? this.tokens.name.leadingTrivia;
668✔
3350
    }
3351

3352
    public get isOptional() {
3353
        return !!this.tokens.optional;
301✔
3354
    }
3355

3356
    transpile(state: BrsTranspileState): TranspileResult {
UNCOV
3357
        throw new Error('transpile not implemented for ' + Object.getPrototypeOf(this).constructor.name);
×
3358
    }
3359

3360
    getTypedef(state: BrsTranspileState) {
3361
        const result = [];
12✔
3362
        if (this.tokens.name) {
12!
3363
            for (let comment of util.getLeadingComments(this) ?? []) {
12!
UNCOV
3364
                result.push(
×
3365
                    comment.text,
3366
                    state.newline,
3367
                    state.indent()
3368
                );
3369
            }
3370
            for (let annotation of this.annotations ?? []) {
12✔
3371
                result.push(
2✔
3372
                    ...annotation.getTypedef(state),
3373
                    state.newline,
3374
                    state.indent()
3375
                );
3376
            }
3377

3378
            let type = this.getType({ flags: SymbolTypeFlag.typetime });
12✔
3379
            if (isInvalidType(type) || isVoidType(type) || isUninitializedType(type)) {
12!
UNCOV
3380
                type = new DynamicType();
×
3381
            }
3382

3383
            result.push(
12✔
3384
                this.tokens.accessModifier?.text ?? 'public',
72✔
3385
                ' '
3386
            );
3387
            if (this.isOptional) {
12!
UNCOV
3388
                result.push(this.tokens.optional.text, ' ');
×
3389
            }
3390
            result.push(this.tokens.name?.text,
12!
3391
                ' as ',
3392
                type.toTypeString()
3393
            );
3394
        }
3395
        return result;
12✔
3396
    }
3397

3398
    walk(visitor: WalkVisitor, options: WalkOptions) {
3399
        if (options.walkMode & InternalWalkMode.walkExpressions) {
1,679✔
3400
            walk(this, 'typeExpression', visitor, options);
1,466✔
3401
            walk(this, 'initialValue', visitor, options);
1,466✔
3402
        }
3403
    }
3404

3405
    public clone() {
3406
        return this.finalizeClone(
5✔
3407
            new FieldStatement({
3408
                accessModifier: util.cloneToken(this.tokens.accessModifier),
3409
                name: util.cloneToken(this.tokens.name),
3410
                as: util.cloneToken(this.tokens.as),
3411
                typeExpression: this.typeExpression?.clone(),
15✔
3412
                equals: util.cloneToken(this.tokens.equals),
3413
                initialValue: this.initialValue?.clone(),
15✔
3414
                optional: util.cloneToken(this.tokens.optional)
3415
            }),
3416
            ['initialValue']
3417
        );
3418
    }
3419
}
3420

3421
export type MemberStatement = FieldStatement | MethodStatement;
3422

3423
export class TryCatchStatement extends Statement {
1✔
3424
    constructor(options?: {
3425
        try?: Token;
3426
        endTry?: Token;
3427
        tryBranch?: Block;
3428
        catchStatement?: CatchStatement;
3429
    }) {
3430
        super();
40✔
3431
        this.tokens = {
40✔
3432
            try: options.try,
3433
            endTry: options.endTry
3434
        };
3435
        this.tryBranch = options.tryBranch;
40✔
3436
        this.catchStatement = options.catchStatement;
40✔
3437
        this.location = util.createBoundingLocation(
40✔
3438
            this.tokens.try,
3439
            this.tryBranch,
3440
            this.catchStatement,
3441
            this.tokens.endTry
3442
        );
3443
    }
3444

3445
    public readonly tokens: {
3446
        readonly try?: Token;
3447
        readonly endTry?: Token;
3448
    };
3449

3450
    public readonly tryBranch: Block;
3451
    public readonly catchStatement: CatchStatement;
3452

3453
    public readonly kind = AstNodeKind.TryCatchStatement;
40✔
3454

3455
    public readonly location: Location | undefined;
3456

3457
    public transpile(state: BrsTranspileState): TranspileResult {
3458
        return [
7✔
3459
            state.transpileToken(this.tokens.try, 'try'),
3460
            ...this.tryBranch.transpile(state),
3461
            state.newline,
3462
            state.indent(),
3463
            ...(this.catchStatement?.transpile(state) ?? ['catch']),
42!
3464
            state.newline,
3465
            state.indent(),
3466
            state.transpileToken(this.tokens.endTry!, 'end try')
3467
        ];
3468
    }
3469

3470
    public walk(visitor: WalkVisitor, options: WalkOptions) {
3471
        if (this.tryBranch && options.walkMode & InternalWalkMode.walkStatements) {
93!
3472
            walk(this, 'tryBranch', visitor, options);
93✔
3473
            walk(this, 'catchStatement', visitor, options);
93✔
3474
        }
3475
    }
3476

3477
    public get leadingTrivia(): Token[] {
3478
        return this.tokens.try?.leadingTrivia ?? [];
88!
3479
    }
3480

3481
    public get endTrivia(): Token[] {
3482
        return this.tokens.endTry?.leadingTrivia ?? [];
1!
3483
    }
3484

3485
    public clone() {
3486
        return this.finalizeClone(
3✔
3487
            new TryCatchStatement({
3488
                try: util.cloneToken(this.tokens.try),
3489
                endTry: util.cloneToken(this.tokens.endTry),
3490
                tryBranch: this.tryBranch?.clone(),
9✔
3491
                catchStatement: this.catchStatement?.clone()
9✔
3492
            }),
3493
            ['tryBranch', 'catchStatement']
3494
        );
3495
    }
3496
}
3497

3498
export class CatchStatement extends Statement {
1✔
3499
    constructor(options?: {
3500
        catch?: Token;
3501
        exceptionVariableExpression?: Expression;
3502
        catchBranch?: Block;
3503
    }) {
3504
        super();
37✔
3505
        this.tokens = {
37✔
3506
            catch: options?.catch
111!
3507
        };
3508
        this.exceptionVariableExpression = options?.exceptionVariableExpression;
37!
3509
        this.catchBranch = options?.catchBranch;
37!
3510
        this.location = util.createBoundingLocation(
37✔
3511
            this.tokens.catch,
3512
            this.exceptionVariableExpression,
3513
            this.catchBranch
3514
        );
3515
    }
3516

3517
    public readonly tokens: {
3518
        readonly catch?: Token;
3519
    };
3520

3521
    public readonly exceptionVariableExpression?: Expression;
3522

3523
    public readonly catchBranch?: Block;
3524

3525
    public readonly kind = AstNodeKind.CatchStatement;
37✔
3526

3527
    public readonly location: Location | undefined;
3528

3529
    public transpile(state: BrsTranspileState): TranspileResult {
3530
        return [
7✔
3531
            state.transpileToken(this.tokens.catch, 'catch'),
3532
            ' ',
3533
            this.exceptionVariableExpression?.transpile(state) ?? [
42✔
3534
                //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
3535
                this.getSymbolTable()?.hasSymbol('e', SymbolTypeFlag.runtime)
6!
3536
                    ? '__bsc_error'
2✔
3537
                    : 'e'
3538
            ],
3539
            ...(this.catchBranch?.transpile(state) ?? [])
42!
3540
        ];
3541
    }
3542

3543
    public walk(visitor: WalkVisitor, options: WalkOptions) {
3544
        if (this.catchBranch && options.walkMode & InternalWalkMode.walkStatements) {
90!
3545
            walk(this, 'catchBranch', visitor, options);
90✔
3546
        }
3547
    }
3548

3549
    public get leadingTrivia(): Token[] {
3550
        return this.tokens.catch?.leadingTrivia ?? [];
50!
3551
    }
3552

3553
    public clone() {
3554
        return this.finalizeClone(
2✔
3555
            new CatchStatement({
3556
                catch: util.cloneToken(this.tokens.catch),
3557
                exceptionVariableExpression: this.exceptionVariableExpression?.clone(),
6!
3558
                catchBranch: this.catchBranch?.clone()
6✔
3559
            }),
3560
            ['catchBranch']
3561
        );
3562
    }
3563
}
3564

3565
export class ThrowStatement extends Statement {
1✔
3566
    constructor(options?: {
3567
        throw?: Token;
3568
        expression?: Expression;
3569
    }) {
3570
        super();
14✔
3571
        this.tokens = {
14✔
3572
            throw: options.throw
3573
        };
3574
        this.expression = options.expression;
14✔
3575
        this.location = util.createBoundingLocation(
14✔
3576
            this.tokens.throw,
3577
            this.expression
3578
        );
3579
    }
3580

3581
    public readonly tokens: {
3582
        readonly throw?: Token;
3583
    };
3584
    public readonly expression?: Expression;
3585

3586
    public readonly kind = AstNodeKind.ThrowStatement;
14✔
3587

3588
    public readonly location: Location | undefined;
3589

3590
    public transpile(state: BrsTranspileState) {
3591
        const result = [
5✔
3592
            state.transpileToken(this.tokens.throw, 'throw'),
3593
            ' '
3594
        ] as TranspileResult;
3595

3596
        //if we have an expression, transpile it
3597
        if (this.expression) {
5✔
3598
            result.push(
4✔
3599
                ...this.expression.transpile(state)
3600
            );
3601

3602
            //no expression found. Rather than emit syntax errors, provide a generic error message
3603
        } else {
3604
            result.push('"User-specified exception"');
1✔
3605
        }
3606
        return result;
5✔
3607
    }
3608

3609
    public walk(visitor: WalkVisitor, options: WalkOptions) {
3610
        if (this.expression && options.walkMode & InternalWalkMode.walkExpressions) {
38✔
3611
            walk(this, 'expression', visitor, options);
30✔
3612
        }
3613
    }
3614

3615
    public get leadingTrivia(): Token[] {
3616
        return this.tokens.throw?.leadingTrivia ?? [];
54!
3617
    }
3618

3619
    public clone() {
3620
        return this.finalizeClone(
2✔
3621
            new ThrowStatement({
3622
                throw: util.cloneToken(this.tokens.throw),
3623
                expression: this.expression?.clone()
6✔
3624
            }),
3625
            ['expression']
3626
        );
3627
    }
3628
}
3629

3630

3631
export class EnumStatement extends Statement implements TypedefProvider {
1✔
3632
    constructor(options: {
3633
        enum?: Token;
3634
        name: Identifier;
3635
        endEnum?: Token;
3636
        body: Array<EnumMemberStatement>;
3637
    }) {
3638
        super();
178✔
3639
        this.tokens = {
178✔
3640
            enum: options.enum,
3641
            name: options.name,
3642
            endEnum: options.endEnum
3643
        };
3644
        this.symbolTable = new SymbolTable('Enum');
178✔
3645
        this.body = options.body ?? [];
178✔
3646
    }
3647

3648
    public readonly tokens: {
3649
        readonly enum?: Token;
3650
        readonly name: Identifier;
3651
        readonly endEnum?: Token;
3652
    };
3653
    public readonly body: Array<EnumMemberStatement>;
3654

3655
    public readonly kind = AstNodeKind.EnumStatement;
178✔
3656

3657
    public get location(): Location | undefined {
3658
        return util.createBoundingLocation(
133✔
3659
            this.tokens.enum,
3660
            this.tokens.name,
3661
            ...this.body,
3662
            this.tokens.endEnum
3663
        );
3664
    }
3665

3666
    public getMembers() {
3667
        const result = [] as EnumMemberStatement[];
347✔
3668
        for (const statement of this.body) {
347✔
3669
            if (isEnumMemberStatement(statement)) {
737!
3670
                result.push(statement);
737✔
3671
            }
3672
        }
3673
        return result;
347✔
3674
    }
3675

3676
    public get leadingTrivia(): Token[] {
3677
        return this.tokens.enum?.leadingTrivia;
494!
3678
    }
3679

3680
    public get endTrivia(): Token[] {
UNCOV
3681
        return this.tokens.endEnum?.leadingTrivia ?? [];
×
3682
    }
3683

3684
    /**
3685
     * Get a map of member names and their values.
3686
     * All values are stored as their AST LiteralExpression representation (i.e. string enum values include the wrapping quotes)
3687
     */
3688
    public getMemberValueMap() {
3689
        const result = new Map<string, string>();
59✔
3690
        const members = this.getMembers();
59✔
3691
        let currentIntValue = 0;
59✔
3692
        for (const member of members) {
59✔
3693
            //if there is no value, assume an integer and increment the int counter
3694
            if (!member.value) {
148✔
3695
                result.set(member.name?.toLowerCase(), currentIntValue.toString());
33!
3696
                currentIntValue++;
33✔
3697

3698
                //if explicit integer value, use it and increment the int counter
3699
            } else if (isLiteralExpression(member.value) && member.value.tokens.value.kind === TokenKind.IntegerLiteral) {
115✔
3700
                //try parsing as integer literal, then as hex integer literal.
3701
                let tokenIntValue = util.parseInt(member.value.tokens.value.text) ?? util.parseInt(member.value.tokens.value.text.replace(/&h/i, '0x'));
29✔
3702
                if (tokenIntValue !== undefined) {
29!
3703
                    currentIntValue = tokenIntValue;
29✔
3704
                    currentIntValue++;
29✔
3705
                }
3706
                result.set(member.name?.toLowerCase(), member.value.tokens.value.text);
29!
3707

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

3712
                //all other values
3713
            } else {
3714
                result.set(member.name?.toLowerCase(), (member.value as LiteralExpression)?.tokens?.value?.text ?? 'invalid');
85!
3715
            }
3716
        }
3717
        return result;
59✔
3718
    }
3719

3720
    public getMemberValue(name: string) {
3721
        return this.getMemberValueMap().get(name.toLowerCase());
56✔
3722
    }
3723

3724
    /**
3725
     * The name of the enum (without the namespace prefix)
3726
     */
3727
    public get name() {
3728
        return this.tokens.name?.text;
1!
3729
    }
3730

3731
    /**
3732
     * The name of the enum WITH its leading namespace (if applicable)
3733
     */
3734
    public get fullName() {
3735
        const name = this.tokens.name?.text;
737!
3736
        if (name) {
737!
3737
            const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
737✔
3738

3739
            if (namespace) {
737✔
3740
                let namespaceName = namespace.getName(ParseMode.BrighterScript);
276✔
3741
                return `${namespaceName}.${name}`;
276✔
3742
            } else {
3743
                return name;
461✔
3744
            }
3745
        } else {
3746
            //return undefined which will allow outside callers to know that this doesn't have a name
UNCOV
3747
            return undefined;
×
3748
        }
3749
    }
3750

3751
    transpile(state: BrsTranspileState) {
3752
        //enum declarations don't exist at runtime, so just transpile comments and trivia
3753
        return [
25✔
3754
            state.transpileAnnotations(this),
3755
            state.transpileLeadingComments(this.tokens.enum)
3756
        ];
3757
    }
3758

3759
    getTypedef(state: BrsTranspileState) {
3760
        const result = [] as TranspileResult;
1✔
3761
        for (let comment of util.getLeadingComments(this) ?? []) {
1!
UNCOV
3762
            result.push(
×
3763
                comment.text,
3764
                state.newline,
3765
                state.indent()
3766
            );
3767
        }
3768
        for (let annotation of this.annotations ?? []) {
1!
UNCOV
3769
            result.push(
×
3770
                ...annotation.getTypedef(state),
3771
                state.newline,
3772
                state.indent()
3773
            );
3774
        }
3775
        result.push(
1✔
3776
            this.tokens.enum?.text ?? 'enum',
6!
3777
            ' ',
3778
            this.tokens.name.text
3779
        );
3780
        result.push(state.newline);
1✔
3781
        state.blockDepth++;
1✔
3782
        for (const member of this.body) {
1✔
3783
            if (isTypedefProvider(member)) {
1!
3784
                result.push(
1✔
3785
                    state.indent(),
3786
                    ...member.getTypedef(state),
3787
                    state.newline
3788
                );
3789
            }
3790
        }
3791
        state.blockDepth--;
1✔
3792
        result.push(
1✔
3793
            state.indent(),
3794
            this.tokens.endEnum?.text ?? 'end enum'
6!
3795
        );
3796
        return result;
1✔
3797
    }
3798

3799
    walk(visitor: WalkVisitor, options: WalkOptions) {
3800
        if (options.walkMode & InternalWalkMode.walkStatements) {
1,098!
3801
            walkArray(this.body, visitor, options, this);
1,098✔
3802

3803
        }
3804
    }
3805

3806
    getType(options: GetTypeOptions) {
3807
        const members = this.getMembers();
144✔
3808

3809
        const resultType = new EnumType(
144✔
3810
            this.fullName,
3811
            members[0]?.getType(options).underlyingType
432✔
3812
        );
3813
        resultType.pushMemberProvider(() => this.getSymbolTable());
144✔
3814
        for (const statement of members) {
144✔
3815
            const memberType = statement.getType({ ...options, typeChain: undefined });
293✔
3816
            memberType.parentEnumType = resultType;
293✔
3817
            resultType.addMember(statement?.tokens?.name?.text, { definingNode: statement }, memberType, SymbolTypeFlag.runtime);
293!
3818
        }
3819
        return resultType;
144✔
3820
    }
3821

3822
    public clone() {
3823
        return this.finalizeClone(
6✔
3824
            new EnumStatement({
3825
                enum: util.cloneToken(this.tokens.enum),
3826
                name: util.cloneToken(this.tokens.name),
3827
                endEnum: util.cloneToken(this.tokens.endEnum),
3828
                body: this.body?.map(x => x?.clone())
4✔
3829
            }),
3830
            ['body']
3831
        );
3832
    }
3833
}
3834

3835
export class EnumMemberStatement extends Statement implements TypedefProvider {
1✔
3836
    public constructor(options: {
3837
        name: Identifier;
3838
        equals?: Token;
3839
        value?: Expression;
3840
    }) {
3841
        super();
347✔
3842
        this.tokens = {
347✔
3843
            name: options.name,
3844
            equals: options.equals
3845
        };
3846
        this.value = options.value;
347✔
3847
    }
3848

3849
    public readonly tokens: {
3850
        readonly name: Identifier;
3851
        readonly equals?: Token;
3852
    };
3853
    public readonly value?: Expression;
3854

3855
    public readonly kind = AstNodeKind.EnumMemberStatement;
347✔
3856

3857
    public get location() {
3858
        return util.createBoundingLocation(
442✔
3859
            this.tokens.name,
3860
            this.tokens.equals,
3861
            this.value
3862
        );
3863
    }
3864

3865
    /**
3866
     * The name of the member
3867
     */
3868
    public get name() {
3869
        return this.tokens.name.text;
434✔
3870
    }
3871

3872
    public get leadingTrivia(): Token[] {
3873
        return this.tokens.name.leadingTrivia;
1,366✔
3874
    }
3875

3876
    public transpile(state: BrsTranspileState): TranspileResult {
UNCOV
3877
        return [];
×
3878
    }
3879

3880
    getTypedef(state: BrsTranspileState): TranspileResult {
3881
        const result: TranspileResult = [];
1✔
3882
        for (let comment of util.getLeadingComments(this) ?? []) {
1!
UNCOV
3883
            result.push(
×
3884
                comment.text,
3885
                state.newline,
3886
                state.indent()
3887
            );
3888
        }
3889
        result.push(this.tokens.name.text);
1✔
3890
        if (this.tokens.equals) {
1!
UNCOV
3891
            result.push(' ', this.tokens.equals.text, ' ');
×
UNCOV
3892
            if (this.value) {
×
UNCOV
3893
                result.push(
×
3894
                    ...this.value.transpile(state)
3895
                );
3896
            }
3897
        }
3898
        return result;
1✔
3899
    }
3900

3901
    walk(visitor: WalkVisitor, options: WalkOptions) {
3902
        if (this.value && options.walkMode & InternalWalkMode.walkExpressions) {
2,700✔
3903
            walk(this, 'value', visitor, options);
1,344✔
3904
        }
3905
    }
3906

3907
    getType(options: GetTypeOptions) {
3908
        return new EnumMemberType(
437✔
3909
            (this.parent as EnumStatement)?.fullName,
1,311!
3910
            this.tokens?.name?.text,
2,622!
3911
            this.value?.getType(options)
1,311✔
3912
        );
3913
    }
3914

3915
    public clone() {
3916
        return this.finalizeClone(
3✔
3917
            new EnumMemberStatement({
3918
                name: util.cloneToken(this.tokens.name),
3919
                equals: util.cloneToken(this.tokens.equals),
3920
                value: this.value?.clone()
9✔
3921
            }),
3922
            ['value']
3923
        );
3924
    }
3925
}
3926

3927
export class ConstStatement extends Statement implements TypedefProvider {
1✔
3928
    public constructor(options: {
3929
        const?: Token;
3930
        name: Identifier;
3931
        equals?: Token;
3932
        value: Expression;
3933
    }) {
3934
        super();
163✔
3935
        this.tokens = {
163✔
3936
            const: options.const,
3937
            name: options.name,
3938
            equals: options.equals
3939
        };
3940
        this.value = options.value;
163✔
3941
        this.location = util.createBoundingLocation(this.tokens.const, this.tokens.name, this.tokens.equals, this.value);
163✔
3942
    }
3943

3944
    public readonly tokens: {
3945
        readonly const: Token;
3946
        readonly name: Identifier;
3947
        readonly equals: Token;
3948
    };
3949
    public readonly value: Expression;
3950

3951
    public readonly kind = AstNodeKind.ConstStatement;
163✔
3952

3953
    public readonly location: Location | undefined;
3954

3955
    public get name() {
UNCOV
3956
        return this.tokens.name.text;
×
3957
    }
3958

3959
    public get leadingTrivia(): Token[] {
3960
        return this.tokens.const?.leadingTrivia;
486!
3961
    }
3962

3963
    /**
3964
     * The name of the statement WITH its leading namespace (if applicable)
3965
     */
3966
    public get fullName() {
3967
        const name = this.tokens.name?.text;
235!
3968
        if (name) {
235!
3969
            const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
235✔
3970
            if (namespace) {
235✔
3971
                let namespaceName = namespace.getName(ParseMode.BrighterScript);
212✔
3972
                return `${namespaceName}.${name}`;
212✔
3973
            } else {
3974
                return name;
23✔
3975
            }
3976
        } else {
3977
            //return undefined which will allow outside callers to know that this doesn't have a name
UNCOV
3978
            return undefined;
×
3979
        }
3980
    }
3981

3982
    public transpile(state: BrsTranspileState): TranspileResult {
3983
        //const declarations don't exist at runtime, so just transpile comments and trivia
3984
        return [
27✔
3985
            state.transpileAnnotations(this),
3986
            state.transpileLeadingComments(this.tokens.const)
3987
        ];
3988
    }
3989

3990
    getTypedef(state: BrsTranspileState): TranspileResult {
3991
        const result: TranspileResult = [];
3✔
3992
        for (let comment of util.getLeadingComments(this) ?? []) {
3!
UNCOV
3993
            result.push(
×
3994
                comment.text,
3995
                state.newline,
3996
                state.indent()
3997
            );
3998
        }
3999
        result.push(
3✔
4000
            this.tokens.const ? state.tokenToSourceNode(this.tokens.const) : 'const',
3!
4001
            ' ',
4002
            state.tokenToSourceNode(this.tokens.name),
4003
            ' ',
4004
            this.tokens.equals ? state.tokenToSourceNode(this.tokens.equals) : '=',
3!
4005
            ' ',
4006
            ...this.value.transpile(state)
4007
        );
4008
        return result;
3✔
4009
    }
4010

4011
    walk(visitor: WalkVisitor, options: WalkOptions) {
4012
        if (this.value && options.walkMode & InternalWalkMode.walkExpressions) {
1,076✔
4013
            walk(this, 'value', visitor, options);
933✔
4014
        }
4015
    }
4016

4017
    getType(options: GetTypeOptions) {
4018
        return this.value.getType(options);
155✔
4019
    }
4020

4021
    public clone() {
4022
        return this.finalizeClone(
3✔
4023
            new ConstStatement({
4024
                const: util.cloneToken(this.tokens.const),
4025
                name: util.cloneToken(this.tokens.name),
4026
                equals: util.cloneToken(this.tokens.equals),
4027
                value: this.value?.clone()
9✔
4028
            }),
4029
            ['value']
4030
        );
4031
    }
4032
}
4033

4034
export class ContinueStatement extends Statement {
1✔
4035
    constructor(options: {
4036
        continue?: Token;
4037
        loopType: Token;
4038
    }
4039
    ) {
4040
        super();
13✔
4041
        this.tokens = {
13✔
4042
            continue: options.continue,
4043
            loopType: options.loopType
4044
        };
4045
        this.location = util.createBoundingLocation(
13✔
4046
            this.tokens.continue,
4047
            this.tokens.loopType
4048
        );
4049
    }
4050

4051
    public readonly tokens: {
4052
        readonly continue?: Token;
4053
        readonly loopType: Token;
4054
    };
4055

4056
    public readonly kind = AstNodeKind.ContinueStatement;
13✔
4057

4058
    public readonly location: Location | undefined;
4059

4060
    transpile(state: BrsTranspileState) {
4061
        return [
3✔
4062
            state.sourceNode(this.tokens.continue, this.tokens.continue?.text ?? 'continue'),
18!
4063
            this.tokens.loopType?.leadingWhitespace ?? ' ',
18✔
4064
            state.sourceNode(this.tokens.continue, this.tokens.loopType?.text)
9✔
4065
        ];
4066
    }
4067

4068
    walk(visitor: WalkVisitor, options: WalkOptions) {
4069
        //nothing to walk
4070
    }
4071

4072
    public get leadingTrivia(): Token[] {
4073
        return this.tokens.continue?.leadingTrivia ?? [];
86!
4074
    }
4075

4076
    public clone() {
4077
        return this.finalizeClone(
1✔
4078
            new ContinueStatement({
4079
                continue: util.cloneToken(this.tokens.continue),
4080
                loopType: util.cloneToken(this.tokens.loopType)
4081
            })
4082
        );
4083
    }
4084
}
4085

4086
export class TypecastStatement extends Statement {
1✔
4087
    constructor(options: {
4088
        typecast?: Token;
4089
        typecastExpression: TypecastExpression;
4090
    }
4091
    ) {
4092
        super();
24✔
4093
        this.tokens = {
24✔
4094
            typecast: options.typecast
4095
        };
4096
        this.typecastExpression = options.typecastExpression;
24✔
4097
        this.location = util.createBoundingLocation(
24✔
4098
            this.tokens.typecast,
4099
            this.typecastExpression
4100
        );
4101
    }
4102

4103
    public readonly tokens: {
4104
        readonly typecast?: Token;
4105
    };
4106

4107
    public readonly typecastExpression: TypecastExpression;
4108

4109
    public readonly kind = AstNodeKind.TypecastStatement;
24✔
4110

4111
    public readonly location: Location;
4112

4113
    transpile(state: BrsTranspileState) {
4114
        //the typecast statement is a comment just for debugging purposes
4115
        return [
1✔
4116
            state.transpileToken(this.tokens.typecast, 'typecast', true),
4117
            ' ',
4118
            this.typecastExpression.obj.transpile(state),
4119
            ' ',
4120
            state.transpileToken(this.typecastExpression.tokens.as, 'as'),
4121
            ' ',
4122
            this.typecastExpression.typeExpression.transpile(state)
4123
        ];
4124
    }
4125

4126
    walk(visitor: WalkVisitor, options: WalkOptions) {
4127
        if (options.walkMode & InternalWalkMode.walkExpressions) {
136✔
4128
            walk(this, 'typecastExpression', visitor, options);
126✔
4129
        }
4130
    }
4131

4132
    get leadingTrivia(): Token[] {
4133
        return this.tokens.typecast?.leadingTrivia ?? [];
110!
4134
    }
4135

4136
    getType(options: GetTypeOptions): BscType {
4137
        return this.typecastExpression.getType(options);
19✔
4138
    }
4139

4140
    public clone() {
4141
        return this.finalizeClone(
1✔
4142
            new TypecastStatement({
4143
                typecast: util.cloneToken(this.tokens.typecast),
4144
                typecastExpression: this.typecastExpression?.clone()
3!
4145
            }),
4146
            ['typecastExpression']
4147
        );
4148
    }
4149
}
4150

4151
export class ConditionalCompileErrorStatement extends Statement {
1✔
4152
    constructor(options: {
4153
        hashError?: Token;
4154
        message: Token;
4155
    }) {
4156
        super();
11✔
4157
        this.tokens = {
11✔
4158
            hashError: options.hashError,
4159
            message: options.message
4160
        };
4161
        this.location = util.createBoundingLocation(util.createBoundingLocationFromTokens(this.tokens));
11✔
4162
    }
4163

4164
    public readonly tokens: {
4165
        readonly hashError?: Token;
4166
        readonly message: Token;
4167
    };
4168

4169

4170
    public readonly kind = AstNodeKind.ConditionalCompileErrorStatement;
11✔
4171

4172
    public readonly location: Location | undefined;
4173

4174
    transpile(state: BrsTranspileState) {
4175
        return [
3✔
4176
            state.transpileToken(this.tokens.hashError, '#error'),
4177
            ' ',
4178
            state.transpileToken(this.tokens.message)
4179

4180
        ];
4181
    }
4182

4183
    walk(visitor: WalkVisitor, options: WalkOptions) {
4184
        // nothing to walk
4185
    }
4186

4187
    get leadingTrivia(): Token[] {
4188
        return this.tokens.hashError.leadingTrivia ?? [];
12!
4189
    }
4190

4191
    public clone() {
4192
        return this.finalizeClone(
1✔
4193
            new ConditionalCompileErrorStatement({
4194
                hashError: util.cloneToken(this.tokens.hashError),
4195
                message: util.cloneToken(this.tokens.message)
4196
            })
4197
        );
4198
    }
4199
}
4200

4201
export class AliasStatement extends Statement {
1✔
4202
    constructor(options: {
4203
        alias?: Token;
4204
        name: Token;
4205
        equals?: Token;
4206
        value: VariableExpression | DottedGetExpression;
4207
    }
4208
    ) {
4209
        super();
34✔
4210
        this.tokens = {
34✔
4211
            alias: options.alias,
4212
            name: options.name,
4213
            equals: options.equals
4214
        };
4215
        this.value = options.value;
34✔
4216
        this.location = util.createBoundingLocation(
34✔
4217
            this.tokens.alias,
4218
            this.tokens.name,
4219
            this.tokens.equals,
4220
            this.value
4221
        );
4222
    }
4223

4224
    public readonly tokens: {
4225
        readonly alias?: Token;
4226
        readonly name: Token;
4227
        readonly equals?: Token;
4228
    };
4229

4230
    public readonly value: Expression;
4231

4232
    public readonly kind = AstNodeKind.AliasStatement;
34✔
4233

4234
    public readonly location: Location;
4235

4236
    transpile(state: BrsTranspileState) {
4237
        //the alias statement is a comment just for debugging purposes
4238
        return [
12✔
4239
            state.transpileToken(this.tokens.alias, 'alias', true),
4240
            ' ',
4241
            state.transpileToken(this.tokens.name),
4242
            ' ',
4243
            state.transpileToken(this.tokens.equals, '='),
4244
            ' ',
4245
            this.value.transpile(state)
4246
        ];
4247
    }
4248

4249
    walk(visitor: WalkVisitor, options: WalkOptions) {
4250
        if (options.walkMode & InternalWalkMode.walkExpressions) {
234✔
4251
            walk(this, 'value', visitor, options);
205✔
4252
        }
4253
    }
4254

4255
    get leadingTrivia(): Token[] {
4256
        return this.tokens.alias?.leadingTrivia ?? [];
121!
4257
    }
4258

4259
    getType(options: GetTypeOptions): BscType {
4260
        return this.value.getType(options);
1✔
4261
    }
4262

4263
    public clone() {
4264
        return this.finalizeClone(
1✔
4265
            new AliasStatement({
4266
                alias: util.cloneToken(this.tokens.alias),
4267
                name: util.cloneToken(this.tokens.name),
4268
                equals: util.cloneToken(this.tokens.equals),
4269
                value: this.value?.clone()
3!
4270
            }),
4271
            ['value']
4272
        );
4273
    }
4274
}
4275

4276
export class ConditionalCompileStatement extends Statement {
1✔
4277
    constructor(options: {
4278
        hashIf?: Token;
4279
        not?: Token;
4280
        condition: Token;
4281
        hashElse?: Token;
4282
        hashEndIf?: Token;
4283
        thenBranch: Block;
4284
        elseBranch?: ConditionalCompileStatement | Block;
4285
    }) {
4286
        super();
56✔
4287
        this.thenBranch = options.thenBranch;
56✔
4288
        this.elseBranch = options.elseBranch;
56✔
4289

4290
        this.tokens = {
56✔
4291
            hashIf: options.hashIf,
4292
            not: options.not,
4293
            condition: options.condition,
4294
            hashElse: options.hashElse,
4295
            hashEndIf: options.hashEndIf
4296
        };
4297

4298
        this.location = util.createBoundingLocation(
56✔
4299
            util.createBoundingLocationFromTokens(this.tokens),
4300
            this.thenBranch,
4301
            this.elseBranch
4302
        );
4303
    }
4304

4305
    readonly tokens: {
4306
        readonly hashIf?: Token;
4307
        readonly not?: Token;
4308
        readonly condition: Token;
4309
        readonly hashElse?: Token;
4310
        readonly hashEndIf?: Token;
4311
    };
4312
    public readonly thenBranch: Block;
4313
    public readonly elseBranch?: ConditionalCompileStatement | Block;
4314

4315
    public readonly kind = AstNodeKind.ConditionalCompileStatement;
56✔
4316

4317
    public readonly location: Location | undefined;
4318

4319
    transpile(state: BrsTranspileState) {
4320
        let results = [] as TranspileResult;
6✔
4321
        //if   (already indented by block)
4322
        if (!state.conditionalCompileStatement) {
6✔
4323
            // only transpile the #if in the case when we're not in a conditionalCompileStatement already
4324
            results.push(state.transpileToken(this.tokens.hashIf ?? createToken(TokenKind.HashIf)));
3!
4325
        }
4326

4327
        results.push(' ');
6✔
4328
        //conditions
4329
        if (this.tokens.not) {
6✔
4330
            results.push('not');
2✔
4331
            results.push(' ');
2✔
4332
        }
4333
        results.push(state.transpileToken(this.tokens.condition));
6✔
4334
        state.lineage.unshift(this);
6✔
4335

4336
        //if statement body
4337
        let thenNodes = this.thenBranch.transpile(state);
6✔
4338
        state.lineage.shift();
6✔
4339
        if (thenNodes.length > 0) {
6!
4340
            results.push(thenNodes);
6✔
4341
        }
4342
        //else branch
4343
        if (this.elseBranch) {
6!
4344
            const elseIsCC = isConditionalCompileStatement(this.elseBranch);
6✔
4345
            const endBlockToken = elseIsCC ? (this.elseBranch as ConditionalCompileStatement).tokens.hashIf ?? createToken(TokenKind.HashElseIf) : this.tokens.hashElse;
6!
4346
            //else
4347

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

4350
            if (elseIsCC) {
6✔
4351
                //chained else if
4352
                state.lineage.unshift(this.elseBranch);
3✔
4353

4354
                // transpile following #if with knowledge of current
4355
                const existingCCStmt = state.conditionalCompileStatement;
3✔
4356
                state.conditionalCompileStatement = this;
3✔
4357
                let body = this.elseBranch.transpile(state);
3✔
4358
                state.conditionalCompileStatement = existingCCStmt;
3✔
4359

4360
                state.lineage.shift();
3✔
4361

4362
                if (body.length > 0) {
3!
4363
                    //zero or more spaces between the `else` and the `if`
4364
                    results.push(...body);
3✔
4365

4366
                    // stop here because chained if will transpile the rest
4367
                    return results;
3✔
4368
                } else {
UNCOV
4369
                    results.push('\n');
×
4370
                }
4371

4372
            } else {
4373
                //else body
4374
                state.lineage.unshift(this.tokens.hashElse!);
3✔
4375
                let body = this.elseBranch.transpile(state);
3✔
4376
                state.lineage.shift();
3✔
4377

4378
                if (body.length > 0) {
3!
4379
                    results.push(...body);
3✔
4380
                }
4381
            }
4382
        }
4383

4384
        //end if
4385
        results.push(...state.transpileEndBlockToken(this.elseBranch ?? this.thenBranch, this.tokens.hashEndIf, '#end if'));
3!
4386

4387
        return results;
3✔
4388
    }
4389

4390
    walk(visitor: WalkVisitor, options: WalkOptions) {
4391
        if (options.walkMode & InternalWalkMode.walkStatements) {
197!
4392
            const bsConsts = options.bsConsts ?? this.getBsConsts();
197✔
4393
            let conditionTrue = bsConsts?.get(this.tokens.condition.text.toLowerCase());
197✔
4394
            if (this.tokens.not) {
197✔
4395
                // flips the boolean value
4396
                conditionTrue = !conditionTrue;
25✔
4397
            }
4398
            const walkFalseBlocks = options.walkMode & InternalWalkMode.visitFalseConditionalCompilationBlocks;
197✔
4399
            if (conditionTrue || walkFalseBlocks) {
197✔
4400
                walk(this, 'thenBranch', visitor, options);
156✔
4401
            }
4402
            if (this.elseBranch && (!conditionTrue || walkFalseBlocks)) {
197✔
4403
                walk(this, 'elseBranch', visitor, options);
68✔
4404
            }
4405
        }
4406
    }
4407

4408
    get leadingTrivia(): Token[] {
4409
        return this.tokens.hashIf?.leadingTrivia ?? [];
164!
4410
    }
4411

4412
    public clone() {
4413
        return this.finalizeClone(
1✔
4414
            new ConditionalCompileStatement({
4415
                hashIf: util.cloneToken(this.tokens.hashIf),
4416
                not: util.cloneToken(this.tokens.not),
4417
                condition: util.cloneToken(this.tokens.condition),
4418
                hashElse: util.cloneToken(this.tokens.hashElse),
4419
                hashEndIf: util.cloneToken(this.tokens.hashEndIf),
4420
                thenBranch: this.thenBranch?.clone(),
3!
4421
                elseBranch: this.elseBranch?.clone()
3!
4422
            }),
4423
            ['thenBranch', 'elseBranch']
4424
        );
4425
    }
4426
}
4427

4428

4429
export class ConditionalCompileConstStatement extends Statement {
1✔
4430
    constructor(options: {
4431
        hashConst?: Token;
4432
        assignment: AssignmentStatement;
4433
    }) {
4434
        super();
19✔
4435
        this.tokens = {
19✔
4436
            hashConst: options.hashConst
4437
        };
4438
        this.assignment = options.assignment;
19✔
4439
        this.location = util.createBoundingLocation(util.createBoundingLocationFromTokens(this.tokens), this.assignment);
19✔
4440
    }
4441

4442
    public readonly tokens: {
4443
        readonly hashConst?: Token;
4444
    };
4445

4446
    public readonly assignment: AssignmentStatement;
4447

4448
    public readonly kind = AstNodeKind.ConditionalCompileConstStatement;
19✔
4449

4450
    public readonly location: Location | undefined;
4451

4452
    transpile(state: BrsTranspileState) {
4453
        return [
3✔
4454
            state.transpileToken(this.tokens.hashConst, '#const'),
4455
            ' ',
4456
            state.transpileToken(this.assignment.tokens.name),
4457
            ' ',
4458
            state.transpileToken(this.assignment.tokens.equals, '='),
4459
            ' ',
4460
            ...this.assignment.value.transpile(state)
4461
        ];
4462

4463
    }
4464

4465
    walk(visitor: WalkVisitor, options: WalkOptions) {
4466
        // nothing to walk
4467
    }
4468

4469

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

4474
    public clone() {
4475
        return this.finalizeClone(
1✔
4476
            new ConditionalCompileConstStatement({
4477
                hashConst: util.cloneToken(this.tokens.hashConst),
4478
                assignment: this.assignment?.clone()
3!
4479
            }),
4480
            ['assignment']
4481
        );
4482
    }
4483
}
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