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

rokucommunity / brighterscript / #14044

20 Mar 2025 07:09PM UTC coverage: 87.163% (-2.0%) from 89.117%
#14044

push

web-flow
Merge e33b1f944 into 0eceb0830

13257 of 16072 branches covered (82.49%)

Branch coverage included in aggregate %.

1163 of 1279 new or added lines in 24 files covered. (90.93%)

802 existing lines in 52 files now uncovered.

14323 of 15570 relevant lines covered (91.99%)

21312.85 hits per line

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

178
    public readonly value: Expression;
179

180
    public readonly typeExpression?: TypeExpression;
181

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

184
    public readonly location: Location | undefined;
185

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

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

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

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

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

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

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

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

253
    public readonly item: Expression;
254

255
    public readonly value: Expression;
256

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

259
    public readonly location: Location | undefined;
260

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

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

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

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

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

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

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

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

313
    public readonly statements: Statement[];
314

315
    public readonly kind = AstNodeKind.Block;
7,222✔
316

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

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

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

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

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

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

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

464
    walk(visitor: WalkVisitor, options: WalkOptions) {
465
        if (options.walkMode & InternalWalkMode.walkStatements) {
32,307✔
466
            walkArray(this.statements, visitor, options, this);
32,301✔
467
        }
468
    }
469

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

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

491
    public readonly location: Location | undefined;
492

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

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

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

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

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

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

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

543
    public readonly location?: Location;
544

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

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

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

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

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

585
        this.location = this.func?.location;
4,325✔
586
    }
587

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

593
    public readonly kind = AstNodeKind.FunctionStatement as AstNodeKind;
4,325✔
594

595
    public readonly location: Location | undefined;
596

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

611
    public get leadingTrivia(): Token[] {
612
        return this.func.leadingTrivia;
17,942✔
613
    }
614

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

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

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

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

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

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

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

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

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

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

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

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

715
    public readonly kind = AstNodeKind.IfStatement;
2,174✔
716

717
    public readonly location: Location | undefined;
718

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

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

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

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

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

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

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

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

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

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

790
        return results;
1,118✔
791
    }
792

793
    walk(visitor: WalkVisitor, options: WalkOptions) {
794
        if (options.walkMode & InternalWalkMode.walkExpressions) {
9,110✔
795
            walk(this, 'condition', visitor, options);
9,094✔
796
        }
797
        if (options.walkMode & InternalWalkMode.walkStatements) {
9,110✔
798
            walk(this, 'thenBranch', visitor, options);
9,108✔
799
        }
800
        if (this.elseBranch && options.walkMode & InternalWalkMode.walkStatements) {
9,110✔
801
            walk(this, 'elseBranch', visitor, options);
7,193✔
802
        }
803
    }
804

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

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

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

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

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

850
    public readonly kind = AstNodeKind.IncrementStatement;
29✔
851

852
    public readonly location: Location | undefined;
853

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

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

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

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

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

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

911
    public readonly expressions: Array<Expression>;
912

913
    public readonly kind = AstNodeKind.PrintStatement;
1,307✔
914

915
    public readonly location: Location | undefined;
916

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

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

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

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

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

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

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

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

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

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

1001
    public readonly location: Location | undefined;
1002

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

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

1026
        }
1027
    }
1028

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

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

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

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

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

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

1079
    public readonly location: Location | undefined;
1080

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

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

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

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

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

1128
    public readonly location: Location | undefined;
1129

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

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

1139
        ];
1140
    }
1141

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

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

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

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

1178
    public readonly location: Location | undefined;
1179

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

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

1198
    get leadingTrivia(): Token[] {
1199
        return this.tokens.return?.leadingTrivia ?? [];
10,164!
1200
    }
1201

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

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

1228
    public readonly location: Location;
1229

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

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

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

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

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

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

1267
    public readonly location: Location;
1268

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

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

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

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

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

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

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

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

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

1341
    public readonly location: Location | undefined;
1342

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

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

1379
        return result;
11✔
1380
    }
1381

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

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

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

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

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

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

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

1458
    public readonly kind = AstNodeKind.ForEachStatement;
43✔
1459

1460
    public readonly location: Location | undefined;
1461

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

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

1489
        return result;
5✔
1490
    }
1491

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

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

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

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

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

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

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

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

1559
    public readonly location: Location | undefined;
1560

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

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

1580
        return result;
8✔
1581
    }
1582

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

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

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

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

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

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

1646
    public readonly kind = AstNodeKind.DottedSetStatement;
318✔
1647

1648
    public readonly location: Location | undefined;
1649

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

1665
    }
1666

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

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

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

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

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

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

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

1742
    public readonly location: Location | undefined;
1743

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

1771
    }
1772

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

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

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

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

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

1822
    public readonly location: Location | undefined;
1823

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

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

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

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

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

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

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

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

1886
    public readonly kind = AstNodeKind.NamespaceStatement;
647✔
1887

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2040
    public readonly kind = AstNodeKind.ImportStatement;
215✔
2041

2042
    public readonly location: Location;
2043

2044
    public readonly filePath: string;
2045

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

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

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

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

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

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

2116
    public readonly kind = AstNodeKind.InterfaceStatement;
181✔
2117

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

2125
    public readonly location: Location | undefined;
2126

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

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

2135

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

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

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

2148

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

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

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

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

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

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

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

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

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

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

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

2327
    public readonly kind = AstNodeKind.InterfaceFieldStatement;
212✔
2328

2329
    public readonly typeExpression?: TypeExpression;
2330

2331
    public readonly location: Location | undefined;
2332

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

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

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

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

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

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

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

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

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

2407
}
2408

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

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

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

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

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

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

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

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

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

2539
    public getType(options: GetTypeOptions): TypedFunctionType {
2540
        //if there's a defined return type, use that
2541
        let returnType = this.returnTypeExpression?.getType(options);
28✔
2542
        const isSub = this.tokens.functionType?.kind === TokenKind.Sub || !returnType;
28!
2543
        //if we don't have a return type and this is a sub, set the return type to `void`. else use `dynamic`
2544
        if (!returnType) {
28✔
2545
            returnType = isSub ? VoidType.instance : DynamicType.instance;
10!
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();
708✔
2592
        this.body = options.body ?? [];
708✔
2593
        this.tokens = {
708✔
2594
            name: options.name,
2595
            class: options.class,
2596
            endClass: options.endClass,
2597
            extends: options.extends
2598
        };
2599
        this.parentClassName = options.parentClassName;
708✔
2600
        this.symbolTable = new SymbolTable(`ClassStatement: '${this.tokens.name?.text}'`, () => this.parent?.getSymbolTable());
1,687!
2601

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

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

2619
    public readonly kind = AstNodeKind.ClassStatement;
708✔
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,394✔
2637
        if (name) {
2,394✔
2638
            const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
2,392✔
2639
            if (namespace) {
2,392✔
2640
                let namespaceName = namespace.getName(parseMode);
885✔
2641
                let separator = parseMode === ParseMode.BrighterScript ? '.' : '_';
885✔
2642
                return namespaceName + separator + name;
885✔
2643
            } else {
2644
                return name;
1,507✔
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,355!
2654
    }
2655

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

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

2664
    public readonly location: Location | undefined;
2665

2666
    transpile(state: BrsTranspileState) {
2667
        let result = [] as TranspileResult;
54✔
2668
        //make the builder
2669
        result.push(...this.getTranspiledBuilder(state));
54✔
2670
        result.push(
54✔
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));
54✔
2676
        return result;
54✔
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;
124✔
2749
        let stmt = this as ClassStatement;
124✔
2750
        while (stmt) {
124✔
2751
            if (stmt.parentClassName) {
185✔
2752
                const namespace = stmt.findAncestor<NamespaceStatement>(isNamespaceStatement);
62✔
2753
                //find the parent class
2754
                stmt = state.file.getClassFileLink(
62✔
2755
                    stmt.parentClassName.getName(),
2756
                    namespace?.getName(ParseMode.BrighterScript)
186✔
2757
                )?.item;
62✔
2758
                myIndex++;
62✔
2759
            } else {
2760
                break;
123✔
2761
            }
2762
        }
2763
        const result = myIndex - 1;
124✔
2764
        if (result >= 0) {
124✔
2765
            return result;
49✔
2766
        } else {
2767
            return null;
75✔
2768
        }
2769
    }
2770

2771
    public hasParentClass() {
2772
        return !!this.parentClassName;
296✔
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[];
139✔
2781
        let stmt = this as ClassStatement;
139✔
2782
        while (stmt) {
139✔
2783
            if (stmt.parentClassName) {
209✔
2784
                const namespace = stmt.findAncestor<NamespaceStatement>(isNamespaceStatement);
70✔
2785
                stmt = state.file.getClassFileLink(
70✔
2786
                    stmt.parentClassName.getName(),
2787
                    namespace?.getName(ParseMode.BrighterScript)
210✔
2788
                )?.item;
70!
2789
                ancestors.push(stmt);
70✔
2790
            } else {
2791
                break;
139✔
2792
            }
2793
        }
2794
        return ancestors;
139✔
2795
    }
2796

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

2804
    public getConstructorType() {
2805
        const constructorType = this.getConstructorFunction()?.getType({ flags: SymbolTypeFlag.runtime }) ?? new TypedFunctionType(null);
131✔
2806
        constructorType.returnType = this.getType({ flags: SymbolTypeFlag.runtime });
131✔
2807
        return constructorType;
131✔
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) => {
282✔
2815
            return (stmt as MethodStatement)?.tokens.name?.text?.toLowerCase() === 'new';
247!
2816
        }) as MethodStatement;
2817
    }
2818

2819
    /**
2820
     * Return the parameters for the first constructor function for this class
2821
     * @param ancestors The list of ancestors for this class
2822
     * @returns The parameters for the first constructor function for this class
2823
     */
2824
    private getConstructorParams(ancestors: ClassStatement[]) {
2825
        for (let ancestor of ancestors) {
43✔
2826
            const ctor = ancestor?.getConstructorFunction();
28!
2827
            if (ctor) {
28✔
2828
                return ctor.func.parameters;
10✔
2829
            }
2830
        }
2831
        return [];
33✔
2832
    }
2833

2834
    /**
2835
     * Determine if the specified field was declared in one of the ancestor classes
2836
     */
2837
    public isFieldDeclaredByAncestor(fieldName: string, ancestors: ClassStatement[]) {
UNCOV
2838
        let lowerFieldName = fieldName.toLowerCase();
×
UNCOV
2839
        for (let ancestor of ancestors) {
×
UNCOV
2840
            if (ancestor.memberMap[lowerFieldName]) {
×
UNCOV
2841
                return true;
×
2842
            }
2843
        }
UNCOV
2844
        return false;
×
2845
    }
2846

2847
    /**
2848
     * The builder is a function that assigns all of the methods and property names to a class instance.
2849
     * This needs to be a separate function so that child classes can call the builder from their parent
2850
     * without instantiating the parent constructor at that point in time.
2851
     */
2852
    private getTranspiledBuilder(state: BrsTranspileState) {
2853
        let result = [] as TranspileResult;
54✔
2854
        result.push(`function ${this.getBuilderName(this.getName(ParseMode.BrightScript)!)}()\n`);
54✔
2855
        state.blockDepth++;
54✔
2856
        //indent
2857
        result.push(state.indent());
54✔
2858

2859
        /**
2860
         * The lineage of this class. index 0 is a direct parent, index 1 is index 0's parent, etc...
2861
         */
2862
        let ancestors = this.getAncestors(state);
54✔
2863

2864
        //construct parent class or empty object
2865
        if (ancestors[0]) {
54✔
2866
            const ancestorNamespace = ancestors[0].findAncestor<NamespaceStatement>(isNamespaceStatement);
21✔
2867
            let fullyQualifiedClassName = util.getFullyQualifiedClassName(
21✔
2868
                ancestors[0].getName(ParseMode.BrighterScript)!,
2869
                ancestorNamespace?.getName(ParseMode.BrighterScript)
63✔
2870
            );
2871
            result.push(
21✔
2872
                'instance = ',
2873
                this.getBuilderName(fullyQualifiedClassName), '()');
2874
        } else {
2875
            //use an empty object.
2876
            result.push('instance = {}');
33✔
2877
        }
2878
        result.push(
54✔
2879
            state.newline,
2880
            state.indent()
2881
        );
2882
        let parentClassIndex = this.getParentClassIndex(state);
54✔
2883

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

2925
        for (let statement of body) {
54✔
2926
            //is field statement
2927
            if (isFieldStatement(statement)) {
84✔
2928
                //do nothing with class fields in this situation, they are handled elsewhere
2929
                continue;
15✔
2930

2931
                //methods
2932
            } else if (isMethodStatement(statement)) {
69!
2933

2934
                //store overridden parent methods as super{parentIndex}_{methodName}
2935
                if (
69✔
2936
                    //is override method
2937
                    statement.tokens.override ||
188✔
2938
                    //is constructor function in child class
2939
                    (statement.tokens.name.text.toLowerCase() === 'new' && ancestors[0])
2940
                ) {
2941
                    result.push(
25✔
2942
                        `instance.super${parentClassIndex}_${statement.tokens.name.text} = instance.${statement.tokens.name.text}`,
2943
                        state.newline,
2944
                        state.indent()
2945
                    );
2946
                }
2947

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

2984
    /**
2985
     * The class function is the function with the same name as the class. This is the function that
2986
     * consumers should call to create a new instance of that class.
2987
     * This invokes the builder, gets an instance of the class, then invokes the "new" function on that class.
2988
     */
2989
    private getTranspiledClassFunction(state: BrsTranspileState) {
2990
        let result: TranspileResult = state.transpileAnnotations(this);
54✔
2991
        const constructorFunction = this.getConstructorFunction();
54✔
2992
        let constructorParams = [];
54✔
2993
        if (constructorFunction) {
54✔
2994
            constructorParams = constructorFunction.func.parameters;
23✔
2995
        } else {
2996
            constructorParams = this.getConstructorParams(this.getAncestors(state));
31✔
2997
        }
2998

2999
        result.push(
54✔
3000
            state.transpileLeadingComments(this.tokens.class),
3001
            state.sourceNode(this.tokens.class, 'function'),
3002
            state.sourceNode(this.tokens.class, ' '),
3003
            state.sourceNode(this.tokens.name, this.getName(ParseMode.BrightScript)),
3004
            `(`
3005
        );
3006
        let i = 0;
54✔
3007
        for (let param of constructorParams) {
54✔
3008
            if (i > 0) {
17✔
3009
                result.push(', ');
4✔
3010
            }
3011
            result.push(
17✔
3012
                param.transpile(state)
3013
            );
3014
            i++;
17✔
3015
        }
3016
        result.push(
54✔
3017
            ')',
3018
            '\n'
3019
        );
3020

3021
        state.blockDepth++;
54✔
3022
        result.push(state.indent());
54✔
3023
        result.push(`instance = ${this.getBuilderName(this.getName(ParseMode.BrightScript)!)}()\n`);
54✔
3024

3025
        result.push(state.indent());
54✔
3026
        result.push(`instance.new(`);
54✔
3027

3028
        //append constructor arguments
3029
        i = 0;
54✔
3030
        for (let param of constructorParams) {
54✔
3031
            if (i > 0) {
17✔
3032
                result.push(', ');
4✔
3033
            }
3034
            result.push(
17✔
3035
                state.transpileToken(param.tokens.name)
3036
            );
3037
            i++;
17✔
3038
        }
3039
        result.push(
54✔
3040
            ')',
3041
            '\n'
3042
        );
3043

3044
        result.push(state.indent());
54✔
3045
        result.push(`return instance\n`);
54✔
3046

3047
        state.blockDepth--;
54✔
3048
        result.push(state.indent());
54✔
3049
        result.push(`end function`);
54✔
3050
        return result;
54✔
3051
    }
3052

3053
    walk(visitor: WalkVisitor, options: WalkOptions) {
3054
        //visitor-less walk function to do parent linking
3055
        walk(this, 'parentClassName', null, options);
2,772✔
3056

3057
        if (options.walkMode & InternalWalkMode.walkStatements) {
2,772!
3058
            walkArray(this.body, visitor, options, this);
2,772✔
3059
        }
3060
    }
3061

3062
    getType(options: GetTypeOptions) {
3063
        const superClass = this.parentClassName?.getType(options) as ClassType;
561✔
3064

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

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

3096
    public clone() {
3097
        return this.finalizeClone(
11✔
3098
            new ClassStatement({
3099
                class: util.cloneToken(this.tokens.class),
3100
                name: util.cloneToken(this.tokens.name),
3101
                body: this.body?.map(x => x?.clone()),
11✔
3102
                endClass: util.cloneToken(this.tokens.endClass),
3103
                extends: util.cloneToken(this.tokens.extends),
3104
                parentClassName: this.parentClassName?.clone()
33✔
3105
            }),
3106
            ['body', 'parentClassName']
3107
        );
3108
    }
3109
}
3110

3111
const accessModifiers = [
1✔
3112
    TokenKind.Public,
3113
    TokenKind.Protected,
3114
    TokenKind.Private
3115
];
3116
export class MethodStatement extends FunctionStatement {
1✔
3117
    constructor(
3118
        options: {
3119
            modifiers?: Token | Token[];
3120
            name: Identifier;
3121
            func: FunctionExpression;
3122
            override?: Token;
3123
        }
3124
    ) {
3125
        super(options);
415✔
3126
        if (options.modifiers) {
415✔
3127
            if (Array.isArray(options.modifiers)) {
40✔
3128
                this.modifiers.push(...options.modifiers);
4✔
3129
            } else {
3130
                this.modifiers.push(options.modifiers);
36✔
3131
            }
3132
        }
3133
        this.tokens = {
415✔
3134
            ...this.tokens,
3135
            override: options.override
3136
        };
3137
        this.location = util.createBoundingLocation(
415✔
3138
            ...(this.modifiers),
3139
            util.createBoundingLocationFromTokens(this.tokens),
3140
            this.func
3141
        );
3142
    }
3143

3144
    public readonly kind = AstNodeKind.MethodStatement as AstNodeKind;
415✔
3145

3146
    public readonly modifiers: Token[] = [];
415✔
3147

3148
    public readonly tokens: {
3149
        readonly name: Identifier;
3150
        readonly override?: Token;
3151
    };
3152

3153
    public get accessModifier() {
3154
        return this.modifiers.find(x => accessModifiers.includes(x.kind));
704✔
3155
    }
3156

3157
    public readonly location: Location | undefined;
3158

3159
    /**
3160
     * Get the name of this method.
3161
     */
3162
    public getName(parseMode: ParseMode) {
3163
        return this.tokens.name.text;
376✔
3164
    }
3165

3166
    public get leadingTrivia(): Token[] {
3167
        return this.func.leadingTrivia;
834✔
3168
    }
3169

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

3202
    getTypedef(state: BrsTranspileState) {
3203
        const result: TranspileResult = [];
23✔
3204
        for (let comment of util.getLeadingComments(this) ?? []) {
23!
UNCOV
3205
            result.push(
×
3206
                comment.text,
3207
                state.newline,
3208
                state.indent()
3209
            );
3210
        }
3211
        for (let annotation of this.annotations ?? []) {
23✔
3212
            result.push(
2✔
3213
                ...annotation.getTypedef(state),
3214
                state.newline,
3215
                state.indent()
3216
            );
3217
        }
3218
        if (this.accessModifier) {
23✔
3219
            result.push(
8✔
3220
                this.accessModifier.text,
3221
                ' '
3222
            );
3223
        }
3224
        if (this.tokens.override) {
23✔
3225
            result.push('override ');
1✔
3226
        }
3227
        result.push(
23✔
3228
            ...this.func.getTypedef(state)
3229
        );
3230
        return result;
23✔
3231
    }
3232

3233
    /**
3234
     * All child classes must call the parent constructor. The type checker will warn users when they don't call it in their own class,
3235
     * but we still need to call it even if they have omitted it. This injects the super call if it's missing
3236
     */
3237
    private ensureSuperConstructorCall(state: BrsTranspileState) {
3238
        //if this class doesn't extend another class, quit here
3239
        if (state.classStatement!.getAncestors(state).length === 0) {
54✔
3240
            return;
33✔
3241
        }
3242

3243
        //check whether any calls to super exist
3244
        let containsSuperCall =
3245
            this.func.body.statements.findIndex((x) => {
21✔
3246
                //is a call statement
3247
                return isExpressionStatement(x) && isCallExpression(x.expression) &&
21✔
3248
                    //is a call to super
3249
                    util.findBeginningVariableExpression(x.expression.callee as any).tokens.name?.text.toLowerCase() === 'super';
60!
3250
            }) !== -1;
3251

3252
        //if a call to super exists, quit here
3253
        if (containsSuperCall) {
21✔
3254
            return;
20✔
3255
        }
3256

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

3292
    /**
3293
     * Inject field initializers at the top of the `new` function (after any present `super()` call)
3294
     */
3295
    private injectFieldInitializersForConstructor(state: BrsTranspileState) {
3296
        let startingIndex = state.classStatement!.hasParentClass() ? 1 : 0;
54✔
3297

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

3322
    walk(visitor: WalkVisitor, options: WalkOptions) {
3323
        if (options.walkMode & InternalWalkMode.walkExpressions) {
2,270✔
3324
            walk(this, 'func', visitor, options);
1,819✔
3325
        }
3326
    }
3327

3328
    public clone() {
3329
        return this.finalizeClone(
5✔
3330
            new MethodStatement({
3331
                modifiers: this.modifiers?.map(m => util.cloneToken(m)),
1✔
3332
                name: util.cloneToken(this.tokens.name),
3333
                func: this.func?.clone(),
15✔
3334
                override: util.cloneToken(this.tokens.override)
3335
            }),
3336
            ['func']
3337
        );
3338
    }
3339
}
3340

3341
export class FieldStatement extends Statement implements TypedefProvider {
1✔
3342
    constructor(options: {
3343
        accessModifier?: Token;
3344
        name: Identifier;
3345
        as?: Token;
3346
        typeExpression?: TypeExpression;
3347
        equals?: Token;
3348
        initialValue?: Expression;
3349
        optional?: Token;
3350
    }) {
3351
        super();
344✔
3352
        this.tokens = {
344✔
3353
            accessModifier: options.accessModifier,
3354
            name: options.name,
3355
            as: options.as,
3356
            equals: options.equals,
3357
            optional: options.optional
3358
        };
3359
        this.typeExpression = options.typeExpression;
344✔
3360
        this.initialValue = options.initialValue;
344✔
3361

3362
        this.location = util.createBoundingLocation(
344✔
3363
            util.createBoundingLocationFromTokens(this.tokens),
3364
            this.typeExpression,
3365
            this.initialValue
3366
        );
3367
    }
3368

3369
    public readonly tokens: {
3370
        readonly accessModifier?: Token;
3371
        readonly name: Identifier;
3372
        readonly as?: Token;
3373
        readonly equals?: Token;
3374
        readonly optional?: Token;
3375
    };
3376

3377
    public readonly typeExpression?: TypeExpression;
3378
    public readonly initialValue?: Expression;
3379

3380
    public readonly kind = AstNodeKind.FieldStatement;
344✔
3381

3382
    /**
3383
     * Derive a ValueKind from the type token, or the initial value.
3384
     * Defaults to `DynamicType`
3385
     */
3386
    getType(options: GetTypeOptions) {
3387
        let initialValueType = this.initialValue?.getType({ ...options, flags: SymbolTypeFlag.runtime });
333✔
3388

3389
        if (isInvalidType(initialValueType) || isVoidType(initialValueType) || isUninitializedType(initialValueType)) {
333✔
3390
            initialValueType = undefined;
4✔
3391
        }
3392

3393
        return this.typeExpression?.getType({ ...options, flags: SymbolTypeFlag.typetime }) ??
333✔
3394
            util.getDefaultTypeFromValueType(initialValueType) ??
333✔
3395
            DynamicType.instance;
3396
    }
3397

3398
    public readonly location: Location | undefined;
3399

3400
    public get leadingTrivia(): Token[] {
3401
        return this.tokens.accessModifier?.leadingTrivia ?? this.tokens.optional?.leadingTrivia ?? this.tokens.name.leadingTrivia;
671✔
3402
    }
3403

3404
    public get isOptional() {
3405
        return !!this.tokens.optional;
302✔
3406
    }
3407

3408
    transpile(state: BrsTranspileState): TranspileResult {
UNCOV
3409
        throw new Error('transpile not implemented for ' + Object.getPrototypeOf(this).constructor.name);
×
3410
    }
3411

3412
    getTypedef(state: BrsTranspileState) {
3413
        const result = [];
12✔
3414
        if (this.tokens.name) {
12!
3415
            for (let comment of util.getLeadingComments(this) ?? []) {
12!
UNCOV
3416
                result.push(
×
3417
                    comment.text,
3418
                    state.newline,
3419
                    state.indent()
3420
                );
3421
            }
3422
            for (let annotation of this.annotations ?? []) {
12✔
3423
                result.push(
2✔
3424
                    ...annotation.getTypedef(state),
3425
                    state.newline,
3426
                    state.indent()
3427
                );
3428
            }
3429

3430
            let type = this.getType({ flags: SymbolTypeFlag.typetime });
12✔
3431
            if (isInvalidType(type) || isVoidType(type) || isUninitializedType(type)) {
12!
UNCOV
3432
                type = DynamicType.instance;
×
3433
            }
3434

3435
            result.push(
12✔
3436
                this.tokens.accessModifier?.text ?? 'public',
72✔
3437
                ' '
3438
            );
3439
            if (this.isOptional) {
12!
UNCOV
3440
                result.push(this.tokens.optional.text, ' ');
×
3441
            }
3442
            result.push(this.tokens.name?.text,
12!
3443
                ' as ',
3444
                type.toTypeString()
3445
            );
3446
        }
3447
        return result;
12✔
3448
    }
3449

3450
    walk(visitor: WalkVisitor, options: WalkOptions) {
3451
        if (options.walkMode & InternalWalkMode.walkExpressions) {
1,687✔
3452
            walk(this, 'typeExpression', visitor, options);
1,473✔
3453
            walk(this, 'initialValue', visitor, options);
1,473✔
3454
        }
3455
    }
3456

3457
    public clone() {
3458
        return this.finalizeClone(
5✔
3459
            new FieldStatement({
3460
                accessModifier: util.cloneToken(this.tokens.accessModifier),
3461
                name: util.cloneToken(this.tokens.name),
3462
                as: util.cloneToken(this.tokens.as),
3463
                typeExpression: this.typeExpression?.clone(),
15✔
3464
                equals: util.cloneToken(this.tokens.equals),
3465
                initialValue: this.initialValue?.clone(),
15✔
3466
                optional: util.cloneToken(this.tokens.optional)
3467
            }),
3468
            ['initialValue']
3469
        );
3470
    }
3471
}
3472

3473
export type MemberStatement = FieldStatement | MethodStatement;
3474

3475
export class TryCatchStatement extends Statement {
1✔
3476
    constructor(options?: {
3477
        try?: Token;
3478
        endTry?: Token;
3479
        tryBranch?: Block;
3480
        catchStatement?: CatchStatement;
3481
    }) {
3482
        super();
40✔
3483
        this.tokens = {
40✔
3484
            try: options.try,
3485
            endTry: options.endTry
3486
        };
3487
        this.tryBranch = options.tryBranch;
40✔
3488
        this.catchStatement = options.catchStatement;
40✔
3489
        this.location = util.createBoundingLocation(
40✔
3490
            this.tokens.try,
3491
            this.tryBranch,
3492
            this.catchStatement,
3493
            this.tokens.endTry
3494
        );
3495
    }
3496

3497
    public readonly tokens: {
3498
        readonly try?: Token;
3499
        readonly endTry?: Token;
3500
    };
3501

3502
    public readonly tryBranch: Block;
3503
    public readonly catchStatement: CatchStatement;
3504

3505
    public readonly kind = AstNodeKind.TryCatchStatement;
40✔
3506

3507
    public readonly location: Location | undefined;
3508

3509
    public transpile(state: BrsTranspileState): TranspileResult {
3510
        return [
7✔
3511
            state.transpileToken(this.tokens.try, 'try'),
3512
            ...this.tryBranch.transpile(state),
3513
            state.newline,
3514
            state.indent(),
3515
            ...(this.catchStatement?.transpile(state) ?? ['catch']),
42!
3516
            state.newline,
3517
            state.indent(),
3518
            state.transpileToken(this.tokens.endTry!, 'end try')
3519
        ];
3520
    }
3521

3522
    public walk(visitor: WalkVisitor, options: WalkOptions) {
3523
        if (this.tryBranch && options.walkMode & InternalWalkMode.walkStatements) {
93!
3524
            walk(this, 'tryBranch', visitor, options);
93✔
3525
            walk(this, 'catchStatement', visitor, options);
93✔
3526
        }
3527
    }
3528

3529
    public get leadingTrivia(): Token[] {
3530
        return this.tokens.try?.leadingTrivia ?? [];
88!
3531
    }
3532

3533
    public get endTrivia(): Token[] {
3534
        return this.tokens.endTry?.leadingTrivia ?? [];
1!
3535
    }
3536

3537
    public clone() {
3538
        return this.finalizeClone(
3✔
3539
            new TryCatchStatement({
3540
                try: util.cloneToken(this.tokens.try),
3541
                endTry: util.cloneToken(this.tokens.endTry),
3542
                tryBranch: this.tryBranch?.clone(),
9✔
3543
                catchStatement: this.catchStatement?.clone()
9✔
3544
            }),
3545
            ['tryBranch', 'catchStatement']
3546
        );
3547
    }
3548
}
3549

3550
export class CatchStatement extends Statement {
1✔
3551
    constructor(options?: {
3552
        catch?: Token;
3553
        exceptionVariableExpression?: Expression;
3554
        catchBranch?: Block;
3555
    }) {
3556
        super();
37✔
3557
        this.tokens = {
37✔
3558
            catch: options?.catch
111!
3559
        };
3560
        this.exceptionVariableExpression = options?.exceptionVariableExpression;
37!
3561
        this.catchBranch = options?.catchBranch;
37!
3562
        this.location = util.createBoundingLocation(
37✔
3563
            this.tokens.catch,
3564
            this.exceptionVariableExpression,
3565
            this.catchBranch
3566
        );
3567
    }
3568

3569
    public readonly tokens: {
3570
        readonly catch?: Token;
3571
    };
3572

3573
    public readonly exceptionVariableExpression?: Expression;
3574

3575
    public readonly catchBranch?: Block;
3576

3577
    public readonly kind = AstNodeKind.CatchStatement;
37✔
3578

3579
    public readonly location: Location | undefined;
3580

3581
    public transpile(state: BrsTranspileState): TranspileResult {
3582
        return [
7✔
3583
            state.transpileToken(this.tokens.catch, 'catch'),
3584
            ' ',
3585
            this.exceptionVariableExpression?.transpile(state) ?? [
42✔
3586
                //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
3587
                this.getSymbolTable()?.hasSymbol('e', SymbolTypeFlag.runtime)
6!
3588
                    ? '__bsc_error'
2✔
3589
                    : 'e'
3590
            ],
3591
            ...(this.catchBranch?.transpile(state) ?? [])
42!
3592
        ];
3593
    }
3594

3595
    public walk(visitor: WalkVisitor, options: WalkOptions) {
3596
        if (this.catchBranch && options.walkMode & InternalWalkMode.walkStatements) {
90!
3597
            walk(this, 'catchBranch', visitor, options);
90✔
3598
        }
3599
    }
3600

3601
    public get leadingTrivia(): Token[] {
3602
        return this.tokens.catch?.leadingTrivia ?? [];
50!
3603
    }
3604

3605
    public clone() {
3606
        return this.finalizeClone(
2✔
3607
            new CatchStatement({
3608
                catch: util.cloneToken(this.tokens.catch),
3609
                exceptionVariableExpression: this.exceptionVariableExpression?.clone(),
6!
3610
                catchBranch: this.catchBranch?.clone()
6✔
3611
            }),
3612
            ['catchBranch']
3613
        );
3614
    }
3615
}
3616

3617
export class ThrowStatement extends Statement {
1✔
3618
    constructor(options?: {
3619
        throw?: Token;
3620
        expression?: Expression;
3621
    }) {
3622
        super();
14✔
3623
        this.tokens = {
14✔
3624
            throw: options.throw
3625
        };
3626
        this.expression = options.expression;
14✔
3627
        this.location = util.createBoundingLocation(
14✔
3628
            this.tokens.throw,
3629
            this.expression
3630
        );
3631
    }
3632

3633
    public readonly tokens: {
3634
        readonly throw?: Token;
3635
    };
3636
    public readonly expression?: Expression;
3637

3638
    public readonly kind = AstNodeKind.ThrowStatement;
14✔
3639

3640
    public readonly location: Location | undefined;
3641

3642
    public transpile(state: BrsTranspileState) {
3643
        const result = [
5✔
3644
            state.transpileToken(this.tokens.throw, 'throw'),
3645
            ' '
3646
        ] as TranspileResult;
3647

3648
        //if we have an expression, transpile it
3649
        if (this.expression) {
5✔
3650
            result.push(
4✔
3651
                ...this.expression.transpile(state)
3652
            );
3653

3654
            //no expression found. Rather than emit syntax errors, provide a generic error message
3655
        } else {
3656
            result.push('"User-specified exception"');
1✔
3657
        }
3658
        return result;
5✔
3659
    }
3660

3661
    public walk(visitor: WalkVisitor, options: WalkOptions) {
3662
        if (this.expression && options.walkMode & InternalWalkMode.walkExpressions) {
38✔
3663
            walk(this, 'expression', visitor, options);
30✔
3664
        }
3665
    }
3666

3667
    public get leadingTrivia(): Token[] {
3668
        return this.tokens.throw?.leadingTrivia ?? [];
54!
3669
    }
3670

3671
    public clone() {
3672
        return this.finalizeClone(
2✔
3673
            new ThrowStatement({
3674
                throw: util.cloneToken(this.tokens.throw),
3675
                expression: this.expression?.clone()
6✔
3676
            }),
3677
            ['expression']
3678
        );
3679
    }
3680
}
3681

3682

3683
export class EnumStatement extends Statement implements TypedefProvider {
1✔
3684
    constructor(options: {
3685
        enum?: Token;
3686
        name: Identifier;
3687
        endEnum?: Token;
3688
        body: Array<EnumMemberStatement>;
3689
    }) {
3690
        super();
184✔
3691
        this.tokens = {
184✔
3692
            enum: options.enum,
3693
            name: options.name,
3694
            endEnum: options.endEnum
3695
        };
3696
        this.symbolTable = new SymbolTable('Enum');
184✔
3697
        this.body = options.body ?? [];
184✔
3698
    }
3699

3700
    public readonly tokens: {
3701
        readonly enum?: Token;
3702
        readonly name: Identifier;
3703
        readonly endEnum?: Token;
3704
    };
3705
    public readonly body: Array<EnumMemberStatement>;
3706

3707
    public readonly kind = AstNodeKind.EnumStatement;
184✔
3708

3709
    public get location(): Location | undefined {
3710
        return util.createBoundingLocation(
138✔
3711
            this.tokens.enum,
3712
            this.tokens.name,
3713
            ...this.body,
3714
            this.tokens.endEnum
3715
        );
3716
    }
3717

3718
    public getMembers() {
3719
        const result = [] as EnumMemberStatement[];
359✔
3720
        for (const statement of this.body) {
359✔
3721
            if (isEnumMemberStatement(statement)) {
763!
3722
                result.push(statement);
763✔
3723
            }
3724
        }
3725
        return result;
359✔
3726
    }
3727

3728
    public get leadingTrivia(): Token[] {
3729
        return this.tokens.enum?.leadingTrivia;
512!
3730
    }
3731

3732
    public get endTrivia(): Token[] {
UNCOV
3733
        return this.tokens.endEnum?.leadingTrivia ?? [];
×
3734
    }
3735

3736
    /**
3737
     * Get a map of member names and their values.
3738
     * All values are stored as their AST LiteralExpression representation (i.e. string enum values include the wrapping quotes)
3739
     */
3740
    public getMemberValueMap() {
3741
        const result = new Map<string, string>();
59✔
3742
        const members = this.getMembers();
59✔
3743
        let currentIntValue = 0;
59✔
3744
        for (const member of members) {
59✔
3745
            //if there is no value, assume an integer and increment the int counter
3746
            if (!member.value) {
148✔
3747
                result.set(member.name?.toLowerCase(), currentIntValue.toString());
33!
3748
                currentIntValue++;
33✔
3749

3750
                //if explicit integer value, use it and increment the int counter
3751
            } else if (isLiteralExpression(member.value) && member.value.tokens.value.kind === TokenKind.IntegerLiteral) {
115✔
3752
                //try parsing as integer literal, then as hex integer literal.
3753
                let tokenIntValue = util.parseInt(member.value.tokens.value.text) ?? util.parseInt(member.value.tokens.value.text.replace(/&h/i, '0x'));
29✔
3754
                if (tokenIntValue !== undefined) {
29!
3755
                    currentIntValue = tokenIntValue;
29✔
3756
                    currentIntValue++;
29✔
3757
                }
3758
                result.set(member.name?.toLowerCase(), member.value.tokens.value.text);
29!
3759

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

3764
                //all other values
3765
            } else {
3766
                result.set(member.name?.toLowerCase(), (member.value as LiteralExpression)?.tokens?.value?.text ?? 'invalid');
85!
3767
            }
3768
        }
3769
        return result;
59✔
3770
    }
3771

3772
    public getMemberValue(name: string) {
3773
        return this.getMemberValueMap().get(name.toLowerCase());
56✔
3774
    }
3775

3776
    /**
3777
     * The name of the enum (without the namespace prefix)
3778
     */
3779
    public get name() {
3780
        return this.tokens.name?.text;
1!
3781
    }
3782

3783
    /**
3784
     * The name of the enum WITH its leading namespace (if applicable)
3785
     */
3786
    public get fullName() {
3787
        const name = this.tokens.name?.text;
734!
3788
        if (name) {
734!
3789
            const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
734✔
3790

3791
            if (namespace) {
734✔
3792
                let namespaceName = namespace.getName(ParseMode.BrighterScript);
220✔
3793
                return `${namespaceName}.${name}`;
220✔
3794
            } else {
3795
                return name;
514✔
3796
            }
3797
        } else {
3798
            //return undefined which will allow outside callers to know that this doesn't have a name
UNCOV
3799
            return undefined;
×
3800
        }
3801
    }
3802

3803
    transpile(state: BrsTranspileState) {
3804
        //enum declarations don't exist at runtime, so just transpile comments and trivia
3805
        return [
25✔
3806
            state.transpileAnnotations(this),
3807
            state.transpileLeadingComments(this.tokens.enum)
3808
        ];
3809
    }
3810

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

3851
    walk(visitor: WalkVisitor, options: WalkOptions) {
3852
        if (options.walkMode & InternalWalkMode.walkStatements) {
1,138!
3853
            walkArray(this.body, visitor, options, this);
1,138✔
3854

3855
        }
3856
    }
3857

3858
    getType(options: GetTypeOptions) {
3859
        const members = this.getMembers();
150✔
3860

3861
        const resultType = new EnumType(
150✔
3862
            this.fullName,
3863
            members[0]?.getType(options).underlyingType
450✔
3864
        );
3865
        resultType.pushMemberProvider(() => this.getSymbolTable());
637✔
3866
        for (const statement of members) {
150✔
3867
            const memberType = statement.getType({ ...options, typeChain: undefined });
306✔
3868
            memberType.parentEnumType = resultType;
306✔
3869
            resultType.addMember(statement?.tokens?.name?.text, { definingNode: statement }, memberType, SymbolTypeFlag.runtime);
306!
3870
        }
3871
        return resultType;
150✔
3872
    }
3873

3874
    public clone() {
3875
        return this.finalizeClone(
6✔
3876
            new EnumStatement({
3877
                enum: util.cloneToken(this.tokens.enum),
3878
                name: util.cloneToken(this.tokens.name),
3879
                endEnum: util.cloneToken(this.tokens.endEnum),
3880
                body: this.body?.map(x => x?.clone())
4✔
3881
            }),
3882
            ['body']
3883
        );
3884
    }
3885
}
3886

3887
export class EnumMemberStatement extends Statement implements TypedefProvider {
1✔
3888
    public constructor(options: {
3889
        name: Identifier;
3890
        equals?: Token;
3891
        value?: Expression;
3892
    }) {
3893
        super();
360✔
3894
        this.tokens = {
360✔
3895
            name: options.name,
3896
            equals: options.equals
3897
        };
3898
        this.value = options.value;
360✔
3899
    }
3900

3901
    public readonly tokens: {
3902
        readonly name: Identifier;
3903
        readonly equals?: Token;
3904
    };
3905
    public readonly value?: Expression;
3906

3907
    public readonly kind = AstNodeKind.EnumMemberStatement;
360✔
3908

3909
    public get location() {
3910
        return util.createBoundingLocation(
460✔
3911
            this.tokens.name,
3912
            this.tokens.equals,
3913
            this.value
3914
        );
3915
    }
3916

3917
    /**
3918
     * The name of the member
3919
     */
3920
    public get name() {
3921
        return this.tokens.name.text;
447✔
3922
    }
3923

3924
    public get leadingTrivia(): Token[] {
3925
        return this.tokens.name.leadingTrivia;
1,429✔
3926
    }
3927

3928
    public transpile(state: BrsTranspileState): TranspileResult {
UNCOV
3929
        return [];
×
3930
    }
3931

3932
    getTypedef(state: BrsTranspileState): TranspileResult {
3933
        const result: TranspileResult = [];
1✔
3934
        for (let comment of util.getLeadingComments(this) ?? []) {
1!
UNCOV
3935
            result.push(
×
3936
                comment.text,
3937
                state.newline,
3938
                state.indent()
3939
            );
3940
        }
3941
        result.push(this.tokens.name.text);
1✔
3942
        if (this.tokens.equals) {
1!
UNCOV
3943
            result.push(' ', this.tokens.equals.text, ' ');
×
UNCOV
3944
            if (this.value) {
×
UNCOV
3945
                result.push(
×
3946
                    ...this.value.transpile(state)
3947
                );
3948
            }
3949
        }
3950
        return result;
1✔
3951
    }
3952

3953
    walk(visitor: WalkVisitor, options: WalkOptions) {
3954
        if (this.value && options.walkMode & InternalWalkMode.walkExpressions) {
2,808✔
3955
            walk(this, 'value', visitor, options);
1,352✔
3956
        }
3957
    }
3958

3959
    getType(options: GetTypeOptions) {
3960
        return new EnumMemberType(
456✔
3961
            (this.parent as EnumStatement)?.fullName,
1,368!
3962
            this.tokens?.name?.text,
2,736!
3963
            this.value?.getType(options)
1,368✔
3964
        );
3965
    }
3966

3967
    public clone() {
3968
        return this.finalizeClone(
3✔
3969
            new EnumMemberStatement({
3970
                name: util.cloneToken(this.tokens.name),
3971
                equals: util.cloneToken(this.tokens.equals),
3972
                value: this.value?.clone()
9✔
3973
            }),
3974
            ['value']
3975
        );
3976
    }
3977
}
3978

3979
export class ConstStatement extends Statement implements TypedefProvider {
1✔
3980
    public constructor(options: {
3981
        const?: Token;
3982
        name: Identifier;
3983
        equals?: Token;
3984
        value: Expression;
3985
    }) {
3986
        super();
163✔
3987
        this.tokens = {
163✔
3988
            const: options.const,
3989
            name: options.name,
3990
            equals: options.equals
3991
        };
3992
        this.value = options.value;
163✔
3993
        this.location = util.createBoundingLocation(this.tokens.const, this.tokens.name, this.tokens.equals, this.value);
163✔
3994
    }
3995

3996
    public readonly tokens: {
3997
        readonly const: Token;
3998
        readonly name: Identifier;
3999
        readonly equals: Token;
4000
    };
4001
    public readonly value: Expression;
4002

4003
    public readonly kind = AstNodeKind.ConstStatement;
163✔
4004

4005
    public readonly location: Location | undefined;
4006

4007
    public get name() {
UNCOV
4008
        return this.tokens.name.text;
×
4009
    }
4010

4011
    public get leadingTrivia(): Token[] {
4012
        return this.tokens.const?.leadingTrivia;
487!
4013
    }
4014

4015
    /**
4016
     * The name of the statement WITH its leading namespace (if applicable)
4017
     */
4018
    public get fullName() {
4019
        const name = this.tokens.name?.text;
64!
4020
        if (name) {
64!
4021
            const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
64✔
4022
            if (namespace) {
64✔
4023
                let namespaceName = namespace.getName(ParseMode.BrighterScript);
31✔
4024
                return `${namespaceName}.${name}`;
31✔
4025
            } else {
4026
                return name;
33✔
4027
            }
4028
        } else {
4029
            //return undefined which will allow outside callers to know that this doesn't have a name
UNCOV
4030
            return undefined;
×
4031
        }
4032
    }
4033

4034
    public transpile(state: BrsTranspileState): TranspileResult {
4035
        //const declarations don't exist at runtime, so just transpile comments and trivia
4036
        return [
27✔
4037
            state.transpileAnnotations(this),
4038
            state.transpileLeadingComments(this.tokens.const)
4039
        ];
4040
    }
4041

4042
    getTypedef(state: BrsTranspileState): TranspileResult {
4043
        const result: TranspileResult = [];
3✔
4044
        for (let comment of util.getLeadingComments(this) ?? []) {
3!
UNCOV
4045
            result.push(
×
4046
                comment.text,
4047
                state.newline,
4048
                state.indent()
4049
            );
4050
        }
4051
        result.push(
3✔
4052
            this.tokens.const ? state.tokenToSourceNode(this.tokens.const) : 'const',
3!
4053
            ' ',
4054
            state.tokenToSourceNode(this.tokens.name),
4055
            ' ',
4056
            this.tokens.equals ? state.tokenToSourceNode(this.tokens.equals) : '=',
3!
4057
            ' ',
4058
            ...this.value.transpile(state)
4059
        );
4060
        return result;
3✔
4061
    }
4062

4063
    walk(visitor: WalkVisitor, options: WalkOptions) {
4064
        if (this.value && options.walkMode & InternalWalkMode.walkExpressions) {
1,077✔
4065
            walk(this, 'value', visitor, options);
934✔
4066
        }
4067
    }
4068

4069
    getType(options: GetTypeOptions) {
4070
        return this.value.getType(options);
155✔
4071
    }
4072

4073
    public clone() {
4074
        return this.finalizeClone(
3✔
4075
            new ConstStatement({
4076
                const: util.cloneToken(this.tokens.const),
4077
                name: util.cloneToken(this.tokens.name),
4078
                equals: util.cloneToken(this.tokens.equals),
4079
                value: this.value?.clone()
9✔
4080
            }),
4081
            ['value']
4082
        );
4083
    }
4084
}
4085

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

4103
    public readonly tokens: {
4104
        readonly continue?: Token;
4105
        readonly loopType: Token;
4106
    };
4107

4108
    public readonly kind = AstNodeKind.ContinueStatement;
13✔
4109

4110
    public readonly location: Location | undefined;
4111

4112
    transpile(state: BrsTranspileState) {
4113
        return [
3✔
4114
            state.sourceNode(this.tokens.continue, this.tokens.continue?.text ?? 'continue'),
18!
4115
            this.tokens.loopType?.leadingWhitespace ?? ' ',
18✔
4116
            state.sourceNode(this.tokens.continue, this.tokens.loopType?.text)
9✔
4117
        ];
4118
    }
4119

4120
    walk(visitor: WalkVisitor, options: WalkOptions) {
4121
        //nothing to walk
4122
    }
4123

4124
    public get leadingTrivia(): Token[] {
4125
        return this.tokens.continue?.leadingTrivia ?? [];
86!
4126
    }
4127

4128
    public clone() {
4129
        return this.finalizeClone(
1✔
4130
            new ContinueStatement({
4131
                continue: util.cloneToken(this.tokens.continue),
4132
                loopType: util.cloneToken(this.tokens.loopType)
4133
            })
4134
        );
4135
    }
4136
}
4137

4138
export class TypecastStatement extends Statement {
1✔
4139
    constructor(options: {
4140
        typecast?: Token;
4141
        typecastExpression: TypecastExpression;
4142
    }
4143
    ) {
4144
        super();
26✔
4145
        this.tokens = {
26✔
4146
            typecast: options.typecast
4147
        };
4148
        this.typecastExpression = options.typecastExpression;
26✔
4149
        this.location = util.createBoundingLocation(
26✔
4150
            this.tokens.typecast,
4151
            this.typecastExpression
4152
        );
4153
    }
4154

4155
    public readonly tokens: {
4156
        readonly typecast?: Token;
4157
    };
4158

4159
    public readonly typecastExpression: TypecastExpression;
4160

4161
    public readonly kind = AstNodeKind.TypecastStatement;
26✔
4162

4163
    public readonly location: Location;
4164

4165
    transpile(state: BrsTranspileState) {
4166
        //the typecast statement is a comment just for debugging purposes
4167
        return [
1✔
4168
            state.transpileToken(this.tokens.typecast, 'typecast', true),
4169
            ' ',
4170
            this.typecastExpression.obj.transpile(state),
4171
            ' ',
4172
            state.transpileToken(this.typecastExpression.tokens.as, 'as'),
4173
            ' ',
4174
            this.typecastExpression.typeExpression.transpile(state)
4175
        ];
4176
    }
4177

4178
    walk(visitor: WalkVisitor, options: WalkOptions) {
4179
        if (options.walkMode & InternalWalkMode.walkExpressions) {
152✔
4180
            walk(this, 'typecastExpression', visitor, options);
140✔
4181
        }
4182
    }
4183

4184
    get leadingTrivia(): Token[] {
4185
        return this.tokens.typecast?.leadingTrivia ?? [];
116!
4186
    }
4187

4188
    getType(options: GetTypeOptions): BscType {
4189
        return this.typecastExpression.getType(options);
21✔
4190
    }
4191

4192
    public clone() {
4193
        return this.finalizeClone(
1✔
4194
            new TypecastStatement({
4195
                typecast: util.cloneToken(this.tokens.typecast),
4196
                typecastExpression: this.typecastExpression?.clone()
3!
4197
            }),
4198
            ['typecastExpression']
4199
        );
4200
    }
4201
}
4202

4203
export class ConditionalCompileErrorStatement extends Statement {
1✔
4204
    constructor(options: {
4205
        hashError?: Token;
4206
        message: Token;
4207
    }) {
4208
        super();
11✔
4209
        this.tokens = {
11✔
4210
            hashError: options.hashError,
4211
            message: options.message
4212
        };
4213
        this.location = util.createBoundingLocation(util.createBoundingLocationFromTokens(this.tokens));
11✔
4214
    }
4215

4216
    public readonly tokens: {
4217
        readonly hashError?: Token;
4218
        readonly message: Token;
4219
    };
4220

4221

4222
    public readonly kind = AstNodeKind.ConditionalCompileErrorStatement;
11✔
4223

4224
    public readonly location: Location | undefined;
4225

4226
    transpile(state: BrsTranspileState) {
4227
        return [
3✔
4228
            state.transpileToken(this.tokens.hashError, '#error'),
4229
            ' ',
4230
            state.transpileToken(this.tokens.message)
4231

4232
        ];
4233
    }
4234

4235
    walk(visitor: WalkVisitor, options: WalkOptions) {
4236
        // nothing to walk
4237
    }
4238

4239
    get leadingTrivia(): Token[] {
4240
        return this.tokens.hashError.leadingTrivia ?? [];
12!
4241
    }
4242

4243
    public clone() {
4244
        return this.finalizeClone(
1✔
4245
            new ConditionalCompileErrorStatement({
4246
                hashError: util.cloneToken(this.tokens.hashError),
4247
                message: util.cloneToken(this.tokens.message)
4248
            })
4249
        );
4250
    }
4251
}
4252

4253
export class AliasStatement extends Statement {
1✔
4254
    constructor(options: {
4255
        alias?: Token;
4256
        name: Token;
4257
        equals?: Token;
4258
        value: VariableExpression | DottedGetExpression;
4259
    }
4260
    ) {
4261
        super();
34✔
4262
        this.tokens = {
34✔
4263
            alias: options.alias,
4264
            name: options.name,
4265
            equals: options.equals
4266
        };
4267
        this.value = options.value;
34✔
4268
        this.location = util.createBoundingLocation(
34✔
4269
            this.tokens.alias,
4270
            this.tokens.name,
4271
            this.tokens.equals,
4272
            this.value
4273
        );
4274
    }
4275

4276
    public readonly tokens: {
4277
        readonly alias?: Token;
4278
        readonly name: Token;
4279
        readonly equals?: Token;
4280
    };
4281

4282
    public readonly value: Expression;
4283

4284
    public readonly kind = AstNodeKind.AliasStatement;
34✔
4285

4286
    public readonly location: Location;
4287

4288
    transpile(state: BrsTranspileState) {
4289
        //the alias statement is a comment just for debugging purposes
4290
        return [
12✔
4291
            state.transpileToken(this.tokens.alias, 'alias', true),
4292
            ' ',
4293
            state.transpileToken(this.tokens.name),
4294
            ' ',
4295
            state.transpileToken(this.tokens.equals, '='),
4296
            ' ',
4297
            this.value.transpile(state)
4298
        ];
4299
    }
4300

4301
    walk(visitor: WalkVisitor, options: WalkOptions) {
4302
        if (options.walkMode & InternalWalkMode.walkExpressions) {
234✔
4303
            walk(this, 'value', visitor, options);
205✔
4304
        }
4305
    }
4306

4307
    get leadingTrivia(): Token[] {
4308
        return this.tokens.alias?.leadingTrivia ?? [];
121!
4309
    }
4310

4311
    getType(options: GetTypeOptions): BscType {
4312
        return this.value.getType(options);
1✔
4313
    }
4314

4315
    public clone() {
4316
        return this.finalizeClone(
1✔
4317
            new AliasStatement({
4318
                alias: util.cloneToken(this.tokens.alias),
4319
                name: util.cloneToken(this.tokens.name),
4320
                equals: util.cloneToken(this.tokens.equals),
4321
                value: this.value?.clone()
3!
4322
            }),
4323
            ['value']
4324
        );
4325
    }
4326
}
4327

4328
export class ConditionalCompileStatement extends Statement {
1✔
4329
    constructor(options: {
4330
        hashIf?: Token;
4331
        not?: Token;
4332
        condition: Token;
4333
        hashElse?: Token;
4334
        hashEndIf?: Token;
4335
        thenBranch: Block;
4336
        elseBranch?: ConditionalCompileStatement | Block;
4337
    }) {
4338
        super();
56✔
4339
        this.thenBranch = options.thenBranch;
56✔
4340
        this.elseBranch = options.elseBranch;
56✔
4341

4342
        this.tokens = {
56✔
4343
            hashIf: options.hashIf,
4344
            not: options.not,
4345
            condition: options.condition,
4346
            hashElse: options.hashElse,
4347
            hashEndIf: options.hashEndIf
4348
        };
4349

4350
        this.location = util.createBoundingLocation(
56✔
4351
            util.createBoundingLocationFromTokens(this.tokens),
4352
            this.thenBranch,
4353
            this.elseBranch
4354
        );
4355
    }
4356

4357
    readonly tokens: {
4358
        readonly hashIf?: Token;
4359
        readonly not?: Token;
4360
        readonly condition: Token;
4361
        readonly hashElse?: Token;
4362
        readonly hashEndIf?: Token;
4363
    };
4364
    public readonly thenBranch: Block;
4365
    public readonly elseBranch?: ConditionalCompileStatement | Block;
4366

4367
    public readonly kind = AstNodeKind.ConditionalCompileStatement;
56✔
4368

4369
    public readonly location: Location | undefined;
4370

4371
    transpile(state: BrsTranspileState) {
4372
        let results = [] as TranspileResult;
6✔
4373
        //if   (already indented by block)
4374
        if (!state.conditionalCompileStatement) {
6✔
4375
            // only transpile the #if in the case when we're not in a conditionalCompileStatement already
4376
            results.push(state.transpileToken(this.tokens.hashIf ?? createToken(TokenKind.HashIf)));
3!
4377
        }
4378

4379
        results.push(' ');
6✔
4380
        //conditions
4381
        if (this.tokens.not) {
6✔
4382
            results.push('not');
2✔
4383
            results.push(' ');
2✔
4384
        }
4385
        results.push(state.transpileToken(this.tokens.condition));
6✔
4386
        state.lineage.unshift(this);
6✔
4387

4388
        //if statement body
4389
        let thenNodes = this.thenBranch.transpile(state);
6✔
4390
        state.lineage.shift();
6✔
4391
        if (thenNodes.length > 0) {
6!
4392
            results.push(thenNodes);
6✔
4393
        }
4394
        //else branch
4395
        if (this.elseBranch) {
6!
4396
            const elseIsCC = isConditionalCompileStatement(this.elseBranch);
6✔
4397
            const endBlockToken = elseIsCC ? (this.elseBranch as ConditionalCompileStatement).tokens.hashIf ?? createToken(TokenKind.HashElseIf) : this.tokens.hashElse;
6!
4398
            //else
4399

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

4402
            if (elseIsCC) {
6✔
4403
                //chained else if
4404
                state.lineage.unshift(this.elseBranch);
3✔
4405

4406
                // transpile following #if with knowledge of current
4407
                const existingCCStmt = state.conditionalCompileStatement;
3✔
4408
                state.conditionalCompileStatement = this;
3✔
4409
                let body = this.elseBranch.transpile(state);
3✔
4410
                state.conditionalCompileStatement = existingCCStmt;
3✔
4411

4412
                state.lineage.shift();
3✔
4413

4414
                if (body.length > 0) {
3!
4415
                    //zero or more spaces between the `else` and the `if`
4416
                    results.push(...body);
3✔
4417

4418
                    // stop here because chained if will transpile the rest
4419
                    return results;
3✔
4420
                } else {
UNCOV
4421
                    results.push('\n');
×
4422
                }
4423

4424
            } else {
4425
                //else body
4426
                state.lineage.unshift(this.tokens.hashElse!);
3✔
4427
                let body = this.elseBranch.transpile(state);
3✔
4428
                state.lineage.shift();
3✔
4429

4430
                if (body.length > 0) {
3!
4431
                    results.push(...body);
3✔
4432
                }
4433
            }
4434
        }
4435

4436
        //end if
4437
        results.push(...state.transpileEndBlockToken(this.elseBranch ?? this.thenBranch, this.tokens.hashEndIf, '#end if'));
3!
4438

4439
        return results;
3✔
4440
    }
4441

4442
    walk(visitor: WalkVisitor, options: WalkOptions) {
4443
        if (options.walkMode & InternalWalkMode.walkStatements) {
197!
4444
            const bsConsts = options.bsConsts ?? this.getBsConsts();
197✔
4445
            let conditionTrue = bsConsts?.get(this.tokens.condition.text.toLowerCase());
197✔
4446
            if (this.tokens.not) {
197✔
4447
                // flips the boolean value
4448
                conditionTrue = !conditionTrue;
25✔
4449
            }
4450
            const walkFalseBlocks = options.walkMode & InternalWalkMode.visitFalseConditionalCompilationBlocks;
197✔
4451
            if (conditionTrue || walkFalseBlocks) {
197✔
4452
                walk(this, 'thenBranch', visitor, options);
156✔
4453
            }
4454
            if (this.elseBranch && (!conditionTrue || walkFalseBlocks)) {
197✔
4455
                walk(this, 'elseBranch', visitor, options);
68✔
4456
            }
4457
        }
4458
    }
4459

4460
    get leadingTrivia(): Token[] {
4461
        return this.tokens.hashIf?.leadingTrivia ?? [];
164!
4462
    }
4463

4464
    public clone() {
4465
        return this.finalizeClone(
1✔
4466
            new ConditionalCompileStatement({
4467
                hashIf: util.cloneToken(this.tokens.hashIf),
4468
                not: util.cloneToken(this.tokens.not),
4469
                condition: util.cloneToken(this.tokens.condition),
4470
                hashElse: util.cloneToken(this.tokens.hashElse),
4471
                hashEndIf: util.cloneToken(this.tokens.hashEndIf),
4472
                thenBranch: this.thenBranch?.clone(),
3!
4473
                elseBranch: this.elseBranch?.clone()
3!
4474
            }),
4475
            ['thenBranch', 'elseBranch']
4476
        );
4477
    }
4478
}
4479

4480

4481
export class ConditionalCompileConstStatement extends Statement {
1✔
4482
    constructor(options: {
4483
        hashConst?: Token;
4484
        assignment: AssignmentStatement;
4485
    }) {
4486
        super();
19✔
4487
        this.tokens = {
19✔
4488
            hashConst: options.hashConst
4489
        };
4490
        this.assignment = options.assignment;
19✔
4491
        this.location = util.createBoundingLocation(util.createBoundingLocationFromTokens(this.tokens), this.assignment);
19✔
4492
    }
4493

4494
    public readonly tokens: {
4495
        readonly hashConst?: Token;
4496
    };
4497

4498
    public readonly assignment: AssignmentStatement;
4499

4500
    public readonly kind = AstNodeKind.ConditionalCompileConstStatement;
19✔
4501

4502
    public readonly location: Location | undefined;
4503

4504
    transpile(state: BrsTranspileState) {
4505
        return [
3✔
4506
            state.transpileToken(this.tokens.hashConst, '#const'),
4507
            ' ',
4508
            state.transpileToken(this.assignment.tokens.name),
4509
            ' ',
4510
            state.transpileToken(this.assignment.tokens.equals, '='),
4511
            ' ',
4512
            ...this.assignment.value.transpile(state)
4513
        ];
4514

4515
    }
4516

4517
    walk(visitor: WalkVisitor, options: WalkOptions) {
4518
        // nothing to walk
4519
    }
4520

4521

4522
    get leadingTrivia(): Token[] {
4523
        return this.tokens.hashConst.leadingTrivia ?? [];
78!
4524
    }
4525

4526
    public clone() {
4527
        return this.finalizeClone(
1✔
4528
            new ConditionalCompileConstStatement({
4529
                hashConst: util.cloneToken(this.tokens.hashConst),
4530
                assignment: this.assignment?.clone()
3!
4531
            }),
4532
            ['assignment']
4533
        );
4534
    }
4535
}
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