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

rokucommunity / brighterscript / #15134

28 Jan 2026 04:30PM UTC coverage: 87.198% (+0.006%) from 87.192%
#15134

push

web-flow
Merge 3366e9429 into 610607efc

14643 of 17747 branches covered (82.51%)

Branch coverage included in aggregate %.

76 of 78 new or added lines in 11 files covered. (97.44%)

200 existing lines in 8 files now uncovered.

15402 of 16709 relevant lines covered (92.18%)

24806.05 hits per line

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

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

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

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

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

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

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

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

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

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

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

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

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

118
            result.push(...statement.transpile(state));
1,792✔
119
        }
120
        return result;
815✔
121
    }
122

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

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

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

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

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

179
    public readonly value: Expression;
180

181
    public readonly typeExpression?: TypeExpression;
182

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

185
    public readonly location: Location | undefined;
186

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

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

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

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

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

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

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

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

254
    public readonly item: Expression;
255

256
    public readonly value: Expression;
257

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

260
    public readonly location: Location | undefined;
261

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

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

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

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

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

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

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

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

315
    public readonly statements: Statement[];
316

317
    public readonly kind = AstNodeKind.Block;
7,943✔
318

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

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

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

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

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

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

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

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

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

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

493
    public readonly location: Location | undefined;
494

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

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

508
    get leadingTrivia(): Token[] {
509
        return this.expression.leadingTrivia;
4,209✔
510
    }
511

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

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

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

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

545
    public readonly location?: Location;
546

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

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

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

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

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

587
        this.location = this.func?.location;
4,717✔
588
    }
589

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

595
    public readonly kind = AstNodeKind.FunctionStatement as AstNodeKind;
4,717✔
596

597
    public readonly location: Location | undefined;
598

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

613
    public get leadingTrivia(): Token[] {
614
        return this.func.leadingTrivia;
20,667✔
615
    }
616

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

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

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

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

653
    walk(visitor: WalkVisitor, options: WalkOptions) {
654
        if (options.walkMode & InternalWalkMode.walkExpressions) {
21,327✔
655
            walk(this, 'func', visitor, options);
19,075✔
656
        }
657
    }
658

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

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

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

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

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

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

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

717
    public readonly kind = AstNodeKind.IfStatement;
2,384✔
718

719
    public readonly location: Location | undefined;
720

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

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

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

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

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

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

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

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

783
                if (body.length > 0) {
805✔
784
                    results.push(...body);
803✔
785
                }
786
            }
787
        }
788

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

792
        return results;
1,208✔
793
    }
794

795
    walk(visitor: WalkVisitor, options: WalkOptions) {
796
        if (options.walkMode & InternalWalkMode.walkExpressions) {
10,033✔
797
            walk(this, 'condition', visitor, options);
10,016✔
798
        }
799
        if (options.walkMode & InternalWalkMode.walkStatements) {
10,033✔
800
            walk(this, 'thenBranch', visitor, options);
10,031✔
801
        }
802
        if (this.elseBranch && options.walkMode & InternalWalkMode.walkStatements) {
10,033✔
803
            walk(this, 'elseBranch', visitor, options);
7,863✔
804
        }
805
    }
806

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

811
    get endTrivia(): Token[] {
812
        return this.tokens.endIf?.leadingTrivia ?? [];
19✔
813
    }
814

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

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

847
    public readonly value: Expression;
848
    public readonly tokens: {
849
        readonly operator: Token;
850
    };
851

852
    public readonly kind = AstNodeKind.IncrementStatement;
29✔
853

854
    public readonly location: Location | undefined;
855

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

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

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

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

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

909
    public readonly tokens: {
910
        readonly print?: Token;
911
    };
912

913
    public readonly expressions: Array<Expression>;
914

915
    public readonly kind = AstNodeKind.PrintStatement;
1,498✔
916

917
    public readonly location: Location | undefined;
918

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

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

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

940
            result.push(
311✔
941
                ...expression.transpile(state)
942
            );
943
        }
944
        return result;
247✔
945
    }
946

947
    walk(visitor: WalkVisitor, options: WalkOptions) {
948
        if (options.walkMode & InternalWalkMode.walkExpressions) {
7,222✔
949
            walkArray(this.expressions, visitor, options, this);
7,128✔
950
        }
951
    }
952

953
    get leadingTrivia(): Token[] {
954
        return this.tokens.print?.leadingTrivia ?? [];
9,058✔
955
    }
956

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

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

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

1001
    public readonly kind = AstNodeKind.DimStatement;
46✔
1002

1003
    public readonly location: Location | undefined;
1004

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

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

1028
        }
1029
    }
1030

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

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

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

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

1074
    public readonly tokens: {
1075
        readonly goto?: Token;
1076
        readonly label: Token;
1077
    };
1078

1079
    public readonly kind = AstNodeKind.GotoStatement;
13✔
1080

1081
    public readonly location: Location | undefined;
1082

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

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

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

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

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

1130
    public readonly location: Location | undefined;
1131

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

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

1141
        ];
1142
    }
1143

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

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

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

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

1180
    public readonly location: Location | undefined;
1181

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

1194
    walk(visitor: WalkVisitor, options: WalkOptions) {
1195
        if (options.walkMode & InternalWalkMode.walkExpressions) {
16,521✔
1196
            walk(this, 'value', visitor, options);
16,498✔
1197
        }
1198
    }
1199

1200
    get leadingTrivia(): Token[] {
1201
        return this.tokens.return?.leadingTrivia ?? [];
11,702!
1202
    }
1203

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

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

1230
    public readonly location: Location;
1231

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

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

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

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

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

1267
    public readonly kind = AstNodeKind.StopStatement;
19✔
1268

1269
    public readonly location: Location;
1270

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

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

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

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

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

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

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

1336
    public readonly counterDeclaration: AssignmentStatement;
1337
    public readonly finalValue: Expression;
1338
    public readonly body: Block;
1339
    public readonly increment?: Expression;
1340

1341
    public readonly kind = AstNodeKind.ForStatement;
52✔
1342

1343
    public readonly location: Location | undefined;
1344

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

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

1381
        return result;
11✔
1382
    }
1383

1384
    walk(visitor: WalkVisitor, options: WalkOptions) {
1385
        if (options.walkMode & InternalWalkMode.walkStatements) {
183✔
1386
            walk(this, 'counterDeclaration', visitor, options);
182✔
1387
        }
1388
        if (options.walkMode & InternalWalkMode.walkExpressions) {
183✔
1389
            walk(this, 'finalValue', visitor, options);
179✔
1390
            walk(this, 'increment', visitor, options);
179✔
1391
        }
1392
        if (options.walkMode & InternalWalkMode.walkStatements) {
183✔
1393
            walk(this, 'body', visitor, options);
182✔
1394
        }
1395
    }
1396

1397
    get leadingTrivia(): Token[] {
1398
        return this.tokens.for?.leadingTrivia ?? [];
198!
1399
    }
1400

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

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

1422
export class ForEachStatement extends Statement {
1✔
1423
    constructor(options: {
1424
        forEach?: Token;
1425
        item: Token;
1426
        as?: Token;
1427
        typeExpression?: TypeExpression;
1428
        in?: Token;
1429
        target: Expression;
1430
        body: Block;
1431
        endFor?: Token;
1432
    }) {
1433
        super();
67✔
1434
        this.tokens = {
67✔
1435
            forEach: options.forEach,
1436
            item: options.item,
1437
            as: options.as,
1438
            in: options.in,
1439
            endFor: options.endFor
1440
        };
1441
        this.body = options.body;
67✔
1442
        this.target = options.target;
67✔
1443
        this.typeExpression = options.typeExpression;
67✔
1444

1445
        this.location = util.createBoundingLocation(
67✔
1446
            this.tokens.forEach,
1447
            this.tokens.item,
1448
            this.tokens.as,
1449
            this.typeExpression,
1450
            this.tokens.in,
1451
            this.target,
1452
            this.body,
1453
            this.tokens.endFor
1454
        );
1455
    }
1456

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

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

1470
    public readonly location: Location | undefined;
1471

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

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

1499
        return result;
5✔
1500
    }
1501

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

1511
    public getType(options: GetTypeOptions): BscType {
1512
        // Used for hovers on the statement 'item' token
1513
        return this.getLoopVariableType(options);
56✔
1514
    }
1515

1516
    getLoopVariableType(options: GetTypeOptions): BscType {
1517
        if (this.typeExpression) {
144✔
1518
            return this.typeExpression.getType(options);
16✔
1519
        }
1520
        //register the for loop variable
1521
        const loopTargetType = this.target.getType({ flags: SymbolTypeFlag.runtime });
128✔
1522
        const loopVarType = new ArrayDefaultTypeReferenceType(loopTargetType);
128✔
1523

1524
        return loopVarType;
128✔
1525
    }
1526

1527
    get leadingTrivia(): Token[] {
1528
        return this.tokens.forEach?.leadingTrivia ?? [];
390!
1529
    }
1530

1531
    public get endTrivia(): Token[] {
1532
        return this.tokens.endFor?.leadingTrivia ?? [];
1!
1533
    }
1534

1535
    public clone() {
1536
        return this.finalizeClone(
2✔
1537
            new ForEachStatement({
1538
                forEach: util.cloneToken(this.tokens.forEach),
1539
                in: util.cloneToken(this.tokens.in),
1540
                as: util.cloneToken(this.tokens.as),
1541
                typeExpression: this.typeExpression?.clone(),
6!
1542
                endFor: util.cloneToken(this.tokens.endFor),
1543
                item: util.cloneToken(this.tokens.item),
1544
                target: this.target?.clone(),
6✔
1545
                body: this.body?.clone()
6✔
1546
            }),
1547
            ['target', 'body']
1548
        );
1549
    }
1550
}
1551

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

1574
    public readonly tokens: {
1575
        readonly while?: Token;
1576
        readonly endWhile?: Token;
1577
    };
1578
    public readonly condition: Expression;
1579
    public readonly body: Block;
1580

1581
    public readonly kind = AstNodeKind.WhileStatement;
38✔
1582

1583
    public readonly location: Location | undefined;
1584

1585
    transpile(state: BrsTranspileState) {
1586
        let result = [] as TranspileResult;
8✔
1587
        //while
1588
        result.push(
8✔
1589
            state.transpileToken(this.tokens.while, 'while'),
1590
            ' '
1591
        );
1592
        //condition
1593
        result.push(
8✔
1594
            ...this.condition.transpile(state)
1595
        );
1596
        state.lineage.unshift(this);
8✔
1597
        //body
1598
        result.push(...this.body.transpile(state));
8✔
1599
        state.lineage.shift();
8✔
1600

1601
        //end while
1602
        result.push(...state.transpileEndBlockToken(this.body, this.tokens.endWhile, 'end while'));
8✔
1603

1604
        return result;
8✔
1605
    }
1606

1607
    walk(visitor: WalkVisitor, options: WalkOptions) {
1608
        if (options.walkMode & InternalWalkMode.walkExpressions) {
128✔
1609
            walk(this, 'condition', visitor, options);
125✔
1610
        }
1611
        if (options.walkMode & InternalWalkMode.walkStatements) {
128✔
1612
            walk(this, 'body', visitor, options);
127✔
1613
        }
1614
    }
1615

1616
    get leadingTrivia(): Token[] {
1617
        return this.tokens.while?.leadingTrivia ?? [];
113!
1618
    }
1619

1620
    public get endTrivia(): Token[] {
1621
        return this.tokens.endWhile?.leadingTrivia ?? [];
1!
1622
    }
1623

1624
    public clone() {
1625
        return this.finalizeClone(
4✔
1626
            new WhileStatement({
1627
                while: util.cloneToken(this.tokens.while),
1628
                endWhile: util.cloneToken(this.tokens.endWhile),
1629
                condition: this.condition?.clone(),
12✔
1630
                body: this.body?.clone()
12✔
1631
            }),
1632
            ['condition', 'body']
1633
        );
1634
    }
1635
}
1636

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

1667
    public readonly obj: Expression;
1668
    public readonly value: Expression;
1669

1670
    public readonly kind = AstNodeKind.DottedSetStatement;
320✔
1671

1672
    public readonly location: Location | undefined;
1673

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

1689
    }
1690

1691
    walk(visitor: WalkVisitor, options: WalkOptions) {
1692
        if (options.walkMode & InternalWalkMode.walkExpressions) {
875✔
1693
            walk(this, 'obj', visitor, options);
873✔
1694
            walk(this, 'value', visitor, options);
873✔
1695
        }
1696
    }
1697

1698
    getType(options: GetTypeOptions) {
1699
        const objType = this.obj?.getType(options);
105!
1700
        const result = objType?.getMemberType(this.tokens.name?.text, options);
105!
1701
        options.typeChain?.push(new TypeChainEntry({
105✔
1702
            name: this.tokens.name?.text,
312!
1703
            type: result, data: options.data,
1704
            location: this.tokens.name?.location,
312!
1705
            astNode: this
1706
        }));
1707
        return result;
105✔
1708
    }
1709

1710
    get leadingTrivia(): Token[] {
1711
        return this.obj.leadingTrivia;
912✔
1712
    }
1713

1714
    public clone() {
1715
        return this.finalizeClone(
2✔
1716
            new DottedSetStatement({
1717
                obj: this.obj?.clone(),
6✔
1718
                dot: util.cloneToken(this.tokens.dot),
1719
                name: util.cloneToken(this.tokens.name),
1720
                equals: util.cloneToken(this.tokens.equals),
1721
                value: this.value?.clone()
6✔
1722
            }),
1723
            ['obj', 'value']
1724
        );
1725
    }
1726
}
1727

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

1755
    public readonly tokens: {
1756
        readonly openingSquare?: Token;
1757
        readonly closingSquare?: Token;
1758
        readonly equals?: Token;
1759
    };
1760
    public readonly obj: Expression;
1761
    public readonly indexes: Expression[];
1762
    public readonly value: Expression;
1763

1764
    public readonly kind = AstNodeKind.IndexedSetStatement;
47✔
1765

1766
    public readonly location: Location | undefined;
1767

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

1795
    }
1796

1797
    walk(visitor: WalkVisitor, options: WalkOptions) {
1798
        if (options.walkMode & InternalWalkMode.walkExpressions) {
166✔
1799
            walk(this, 'obj', visitor, options);
165✔
1800
            walkArray(this.indexes, visitor, options, this);
165✔
1801
            walk(this, 'value', visitor, options);
165✔
1802
        }
1803
    }
1804

1805
    get leadingTrivia(): Token[] {
1806
        return this.obj.leadingTrivia;
175✔
1807
    }
1808

1809
    public clone() {
1810
        return this.finalizeClone(
6✔
1811
            new IndexedSetStatement({
1812
                obj: this.obj?.clone(),
18✔
1813
                openingSquare: util.cloneToken(this.tokens.openingSquare),
1814
                indexes: this.indexes?.map(x => x?.clone()),
7✔
1815
                closingSquare: util.cloneToken(this.tokens.closingSquare),
1816
                equals: util.cloneToken(this.tokens.equals),
1817
                value: this.value?.clone()
18✔
1818
            }),
1819
            ['obj', 'indexes', 'value']
1820
        );
1821
    }
1822
}
1823

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

1844
    public readonly kind = AstNodeKind.LibraryStatement;
16✔
1845

1846
    public readonly location: Location | undefined;
1847

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

1863
    getTypedef(state: BrsTranspileState) {
UNCOV
1864
        return this.transpile(state);
×
1865
    }
1866

1867
    walk(visitor: WalkVisitor, options: WalkOptions) {
1868
        //nothing to walk
1869
    }
1870

1871
    get leadingTrivia(): Token[] {
1872
        return this.tokens.library?.leadingTrivia ?? [];
26!
1873
    }
1874

1875
    public clone() {
1876
        return this.finalizeClone(
1✔
1877
            new LibraryStatement({
1878
                library: util.cloneToken(this.tokens?.library),
3!
1879
                filePath: util.cloneToken(this.tokens?.filePath)
3!
1880
            })
1881
        );
1882
    }
1883
}
1884

1885
export class NamespaceStatement extends Statement implements TypedefProvider {
1✔
1886
    constructor(options: {
1887
        namespace?: Token;
1888
        nameExpression: VariableExpression | DottedGetExpression;
1889
        body: Body;
1890
        endNamespace?: Token;
1891
    }) {
1892
        super();
676✔
1893
        this.tokens = {
676✔
1894
            namespace: options.namespace,
1895
            endNamespace: options.endNamespace
1896
        };
1897
        this.nameExpression = options.nameExpression;
676✔
1898
        this.body = options.body;
676✔
1899
        this.symbolTable = new SymbolTable(`NamespaceStatement: '${this.name}'`, () => this.getRoot()?.getSymbolTable());
8,093!
1900
    }
1901

1902
    public readonly tokens: {
1903
        readonly namespace?: Token;
1904
        readonly endNamespace?: Token;
1905
    };
1906

1907
    public readonly nameExpression: VariableExpression | DottedGetExpression;
1908
    public readonly body: Body;
1909

1910
    public readonly kind = AstNodeKind.NamespaceStatement;
676✔
1911

1912
    /**
1913
     * The string name for this namespace
1914
     */
1915
    public get name(): string {
1916
        return this.getName(ParseMode.BrighterScript);
2,543✔
1917
    }
1918

1919
    public get location() {
1920
        return this.cacheLocation();
553✔
1921
    }
1922
    private _location: Location | undefined;
1923

1924
    public cacheLocation() {
1925
        if (!this._location) {
1,227✔
1926
            this._location = util.createBoundingLocation(
676✔
1927
                this.tokens.namespace,
1928
                this.nameExpression,
1929
                this.body,
1930
                this.tokens.endNamespace
1931
            );
1932
        }
1933
        return this._location;
1,227✔
1934
    }
1935

1936
    public getName(parseMode: ParseMode) {
1937
        const sep = parseMode === ParseMode.BrighterScript ? '.' : '_';
9,114✔
1938
        let name = util.getAllDottedGetPartsAsString(this.nameExpression, parseMode);
9,114✔
1939
        if ((this.parent as Body)?.parent?.kind === AstNodeKind.NamespaceStatement) {
9,114✔
1940
            name = (this.parent.parent as NamespaceStatement).getName(parseMode) + sep + name;
358✔
1941
        }
1942
        return name;
9,114✔
1943
    }
1944

1945
    public get leadingTrivia(): Token[] {
1946
        return this.tokens.namespace?.leadingTrivia;
1,976!
1947
    }
1948

1949
    public get endTrivia(): Token[] {
UNCOV
1950
        return this.tokens.endNamespace?.leadingTrivia;
×
1951
    }
1952

1953
    public getNameParts() {
1954
        let parts = util.getAllDottedGetParts(this.nameExpression);
1,509✔
1955

1956
        if ((this.parent as Body)?.parent?.kind === AstNodeKind.NamespaceStatement) {
1,509!
1957
            parts = (this.parent.parent as NamespaceStatement).getNameParts().concat(parts);
67✔
1958
        }
1959
        return parts;
1,509✔
1960
    }
1961

1962
    transpile(state: BrsTranspileState) {
1963
        //namespaces don't actually have any real content, so just transpile their bodies
1964
        return [
73✔
1965
            state.transpileAnnotations(this),
1966
            state.transpileLeadingComments(this.tokens.namespace),
1967
            this.body.transpile(state),
1968
            state.transpileLeadingComments(this.tokens.endNamespace)
1969
        ];
1970
    }
1971

1972
    getTypedef(state: BrsTranspileState) {
1973
        let result: TranspileResult = [];
8✔
1974
        for (let comment of util.getLeadingComments(this) ?? []) {
8!
UNCOV
1975
            result.push(
×
1976
                comment.text,
1977
                state.newline,
1978
                state.indent()
1979
            );
1980
        }
1981

1982
        result.push('namespace ',
8✔
1983
            ...this.getName(ParseMode.BrighterScript),
1984
            state.newline
1985
        );
1986
        state.blockDepth++;
8✔
1987
        result.push(
8✔
1988
            ...this.body.getTypedef(state)
1989
        );
1990
        state.blockDepth--;
8✔
1991

1992
        result.push(
8✔
1993
            state.indent(),
1994
            'end namespace'
1995
        );
1996
        return result;
8✔
1997
    }
1998

1999
    walk(visitor: WalkVisitor, options: WalkOptions) {
2000
        if (options.walkMode & InternalWalkMode.walkExpressions) {
3,448✔
2001
            walk(this, 'nameExpression', visitor, options);
2,824✔
2002
        }
2003

2004
        if (this.body.statements.length > 0 && options.walkMode & InternalWalkMode.walkStatements) {
3,448✔
2005
            walk(this, 'body', visitor, options);
3,245✔
2006
        }
2007
    }
2008

2009
    getType(options: GetTypeOptions) {
2010
        const resultType = new NamespaceType(this.name);
1,189✔
2011
        return resultType;
1,189✔
2012
    }
2013

2014
    public clone() {
2015
        const clone = this.finalizeClone(
3✔
2016
            new NamespaceStatement({
2017
                namespace: util.cloneToken(this.tokens.namespace),
2018
                nameExpression: this.nameExpression?.clone(),
9✔
2019
                body: this.body?.clone(),
9✔
2020
                endNamespace: util.cloneToken(this.tokens.endNamespace)
2021
            }),
2022
            ['nameExpression', 'body']
2023
        );
2024
        clone.cacheLocation();
3✔
2025
        return clone;
3✔
2026
    }
2027
}
2028

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

2059
    public readonly tokens: {
2060
        readonly import?: Token;
2061
        readonly path: Token;
2062
    };
2063

2064
    public readonly kind = AstNodeKind.ImportStatement;
219✔
2065

2066
    public readonly location: Location;
2067

2068
    public readonly filePath: string;
2069

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

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

2092
    walk(visitor: WalkVisitor, options: WalkOptions) {
2093
        //nothing to walk
2094
    }
2095

2096
    get leadingTrivia(): Token[] {
2097
        return this.tokens.import?.leadingTrivia ?? [];
612!
2098
    }
2099

2100
    public clone() {
2101
        return this.finalizeClone(
1✔
2102
            new ImportStatement({
2103
                import: util.cloneToken(this.tokens.import),
2104
                path: util.cloneToken(this.tokens.path)
2105
            })
2106
        );
2107
    }
2108
}
2109

2110
export class InterfaceStatement extends Statement implements TypedefProvider {
1✔
2111
    constructor(options: {
2112
        interface: Token;
2113
        name: Identifier;
2114
        extends?: Token;
2115
        parentInterfaceName?: TypeExpression;
2116
        body: Statement[];
2117
        endInterface?: Token;
2118
    }) {
2119
        super();
235✔
2120
        this.tokens = {
235✔
2121
            interface: options.interface,
2122
            name: options.name,
2123
            extends: options.extends,
2124
            endInterface: options.endInterface
2125
        };
2126
        this.parentInterfaceName = options.parentInterfaceName;
235✔
2127
        this.body = options.body;
235✔
2128
        this.location = util.createBoundingLocation(
235✔
2129
            this.tokens.interface,
2130
            this.tokens.name,
2131
            this.tokens.extends,
2132
            this.parentInterfaceName,
2133
            ...this.body ?? [],
705✔
2134
            this.tokens.endInterface
2135
        );
2136
    }
2137
    public readonly parentInterfaceName?: TypeExpression;
2138
    public readonly body: Statement[];
2139

2140
    public readonly kind = AstNodeKind.InterfaceStatement;
235✔
2141

2142
    public readonly tokens = {} as {
235✔
2143
        readonly interface?: Token;
2144
        readonly name: Identifier;
2145
        readonly extends?: Token;
2146
        readonly endInterface?: Token;
2147
    };
2148

2149
    public readonly location: Location | undefined;
2150

2151
    public get fields(): InterfaceFieldStatement[] {
2152
        return this.body.filter(x => isInterfaceFieldStatement(x)) as InterfaceFieldStatement[];
288✔
2153
    }
2154

2155
    public get methods(): InterfaceMethodStatement[] {
2156
        return this.body.filter(x => isInterfaceMethodStatement(x)) as InterfaceMethodStatement[];
283✔
2157
    }
2158

2159

2160
    public hasParentInterface() {
UNCOV
2161
        return !!this.parentInterfaceName;
×
2162
    }
2163

2164
    public get leadingTrivia(): Token[] {
2165
        return this.tokens.interface?.leadingTrivia;
614!
2166
    }
2167

2168
    public get endTrivia(): Token[] {
UNCOV
2169
        return this.tokens.endInterface?.leadingTrivia;
×
2170
    }
2171

2172

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

2192
    /**
2193
     * The name of the interface (without the namespace prefix)
2194
     */
2195
    public get name() {
2196
        return this.tokens.name?.text;
210!
2197
    }
2198

2199
    /**
2200
     * Get the name of this expression based on the parse mode
2201
     */
2202
    public getName(parseMode: ParseMode) {
2203
        const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
210✔
2204
        if (namespace) {
210✔
2205
            let delimiter = parseMode === ParseMode.BrighterScript ? '.' : '_';
25!
2206
            let namespaceName = namespace.getName(parseMode);
25✔
2207
            return namespaceName + delimiter + this.name;
25✔
2208
        } else {
2209
            return this.name;
185✔
2210
        }
2211
    }
2212

2213
    public transpile(state: BrsTranspileState): TranspileResult {
2214
        //interfaces should completely disappear at runtime
2215
        return [
14✔
2216
            state.transpileLeadingComments(this.tokens.interface)
2217
        ];
2218
    }
2219

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

2279
    walk(visitor: WalkVisitor, options: WalkOptions) {
2280
        //visitor-less walk function to do parent linking
2281
        walk(this, 'parentInterfaceName', null, options);
1,061✔
2282

2283
        if (options.walkMode & InternalWalkMode.walkStatements) {
1,061!
2284
            walkArray(this.body, visitor, options, this);
1,061✔
2285
        }
2286
    }
2287

2288
    getType(options: GetTypeOptions) {
2289
        const superIface = this.parentInterfaceName?.getType(options) as InterfaceType;
201✔
2290

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

2311
    public clone() {
2312
        return this.finalizeClone(
8✔
2313
            new InterfaceStatement({
2314
                interface: util.cloneToken(this.tokens.interface),
2315
                name: util.cloneToken(this.tokens.name),
2316
                extends: util.cloneToken(this.tokens.extends),
2317
                parentInterfaceName: this.parentInterfaceName?.clone(),
24✔
2318
                body: this.body?.map(x => x?.clone()),
9✔
2319
                endInterface: util.cloneToken(this.tokens.endInterface)
2320
            }),
2321
            ['parentInterfaceName', 'body']
2322
        );
2323
    }
2324
}
2325

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

2351
    public readonly kind = AstNodeKind.InterfaceFieldStatement;
262✔
2352

2353
    public readonly typeExpression?: TypeExpression;
2354

2355
    public readonly location: Location | undefined;
2356

2357
    public readonly tokens: {
2358
        readonly name: Identifier;
2359
        readonly as: Token;
2360
        readonly optional?: Token;
2361
    };
2362

2363
    public get leadingTrivia(): Token[] {
2364
        return this.tokens.optional?.leadingTrivia ?? this.tokens.name.leadingTrivia;
713✔
2365
    }
2366

2367
    public get name() {
UNCOV
2368
        return this.tokens.name.text;
×
2369
    }
2370

2371
    public get isOptional() {
2372
        return !!this.tokens.optional;
260✔
2373
    }
2374

2375
    walk(visitor: WalkVisitor, options: WalkOptions) {
2376
        if (options.walkMode & InternalWalkMode.walkExpressions) {
1,715✔
2377
            walk(this, 'typeExpression', visitor, options);
1,488✔
2378
        }
2379
    }
2380

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

2407
        if (this.typeExpression) {
12!
2408
            result.push(
12✔
2409
                ' as ',
2410
                ...this.typeExpression.getTypedef(state)
2411
            );
2412
        }
2413
        return result;
12✔
2414
    }
2415

2416
    public getType(options: GetTypeOptions): BscType {
2417
        return this.typeExpression?.getType(options) ?? DynamicType.instance;
251✔
2418
    }
2419

2420
    public clone() {
2421
        return this.finalizeClone(
4✔
2422
            new InterfaceFieldStatement({
2423
                name: util.cloneToken(this.tokens.name),
2424
                as: util.cloneToken(this.tokens.as),
2425
                typeExpression: this.typeExpression?.clone(),
12✔
2426
                optional: util.cloneToken(this.tokens.optional)
2427
            })
2428
        );
2429
    }
2430

2431
}
2432

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

2462
    public readonly kind = AstNodeKind.InterfaceMethodStatement;
57✔
2463

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

2483
    public readonly tokens: {
2484
        readonly optional?: Token;
2485
        readonly functionType: Token;
2486
        readonly name: Identifier;
2487
        readonly leftParen?: Token;
2488
        readonly rightParen?: Token;
2489
        readonly as?: Token;
2490
    };
2491

2492
    public readonly params: FunctionParameterExpression[];
2493
    public readonly returnTypeExpression?: TypeExpression;
2494

2495
    public get isOptional() {
2496
        return !!this.tokens.optional;
51✔
2497
    }
2498

2499
    public get leadingTrivia(): Token[] {
2500
        return this.tokens.optional?.leadingTrivia ?? this.tokens.functionType.leadingTrivia;
118✔
2501
    }
2502

2503
    walk(visitor: WalkVisitor, options: WalkOptions) {
2504
        if (options.walkMode & InternalWalkMode.walkExpressions) {
286✔
2505
            walk(this, 'returnTypeExpression', visitor, options);
251✔
2506
        }
2507
    }
2508

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

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

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

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

2626
        for (let statement of this.body) {
729✔
2627
            if (isMethodStatement(statement)) {
732✔
2628
                this.methods.push(statement);
376✔
2629
                this.memberMap[statement?.tokens.name?.text.toLowerCase()] = statement;
376!
2630
            } else if (isFieldStatement(statement)) {
356✔
2631
                this.fields.push(statement);
355✔
2632
                this.memberMap[statement?.tokens.name?.text.toLowerCase()] = statement;
355!
2633
            }
2634
        }
2635

2636
        this.location = util.createBoundingLocation(
729✔
2637
            this.parentClassName,
2638
            ...(this.body ?? []),
2,187!
2639
            util.createBoundingLocationFromTokens(this.tokens)
2640
        );
2641
    }
2642

2643
    public readonly kind = AstNodeKind.ClassStatement;
729✔
2644

2645

2646
    public readonly tokens: {
2647
        readonly class?: Token;
2648
        /**
2649
         * The name of the class (without namespace prefix)
2650
         */
2651
        readonly name: Identifier;
2652
        readonly endClass?: Token;
2653
        readonly extends?: Token;
2654
    };
2655
    public readonly body: Statement[];
2656
    public readonly parentClassName: TypeExpression;
2657

2658

2659
    public getName(parseMode: ParseMode) {
2660
        const name = this.tokens.name?.text;
2,432✔
2661
        if (name) {
2,432✔
2662
            const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
2,431✔
2663
            if (namespace) {
2,431✔
2664
                let namespaceName = namespace.getName(parseMode);
923✔
2665
                let separator = parseMode === ParseMode.BrighterScript ? '.' : '_';
923✔
2666
                return namespaceName + separator + name;
923✔
2667
            } else {
2668
                return name;
1,508✔
2669
            }
2670
        } else {
2671
            //return undefined which will allow outside callers to know that this class doesn't have a name
2672
            return undefined;
1✔
2673
        }
2674
    }
2675

2676
    public get leadingTrivia(): Token[] {
2677
        return this.tokens.class?.leadingTrivia;
1,405!
2678
    }
2679

2680
    public get endTrivia(): Token[] {
UNCOV
2681
        return this.tokens.endClass?.leadingTrivia ?? [];
×
2682
    }
2683

2684
    public readonly memberMap = {} as Record<string, MemberStatement>;
729✔
2685
    public readonly methods = [] as MethodStatement[];
729✔
2686
    public readonly fields = [] as FieldStatement[];
729✔
2687

2688
    public readonly location: Location | undefined;
2689

2690
    transpile(state: BrsTranspileState) {
2691
        let result = [] as TranspileResult;
58✔
2692

2693
        const className = this.getName(ParseMode.BrightScript).replace(/\./g, '_');
58✔
2694
        const ancestors = this.getAncestors(state);
58✔
2695
        const body = this.getTranspiledClassBody(ancestors);
58✔
2696

2697
        //make the methods
2698
        result.push(...this.getTranspiledMethods(state, className, body));
58✔
2699
        //make the builder
2700
        result.push(...this.getTranspiledBuilder(state, className, ancestors, body));
58✔
2701
        result.push('\n', state.indent());
58✔
2702
        //make the class assembler (i.e. the public-facing class creator method)
2703
        result.push(...this.getTranspiledClassFunction(state, className));
58✔
2704

2705
        return result;
58✔
2706
    }
2707

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

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

2754
        for (const member of body) {
15✔
2755
            if (isTypedefProvider(member)) {
35!
2756
                result.push(
35✔
2757
                    state.indent(),
2758
                    ...member.getTypedef(state),
2759
                    state.newline
2760
                );
2761
            }
2762
        }
2763
        state.blockDepth--;
15✔
2764
        result.push(
15✔
2765
            state.indent(),
2766
            'end class'
2767
        );
2768
        return result;
15✔
2769
    }
2770

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

2800
    public hasParentClass() {
2801
        return !!this.parentClassName;
301✔
2802
    }
2803

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

2826
    private getBuilderName(transpiledClassName: string) {
2827
        return `__${transpiledClassName}_builder`;
140✔
2828
    }
2829

2830
    private getMethodIdentifier(transpiledClassName: string, statement: MethodStatement) {
2831
        return { ...statement.tokens.name, text: `__${transpiledClassName}_method_${statement.tokens.name.text}` };
146✔
2832
    }
2833

2834
    public getConstructorType() {
2835
        const constructorType = this.getConstructorFunction()?.getType({ flags: SymbolTypeFlag.runtime }) ?? new TypedFunctionType(null);
137✔
2836
        constructorType.returnType = this.getType({ flags: SymbolTypeFlag.runtime });
137✔
2837
        return constructorType;
137✔
2838
    }
2839

2840
    /**
2841
     * Get the constructor function for this class (if exists), or undefined if not exist
2842
     */
2843
    private getConstructorFunction() {
2844
        return this.body.find((stmt) => {
308✔
2845
            return (stmt as MethodStatement)?.tokens.name?.text?.toLowerCase() === 'new';
258!
2846
        }) as MethodStatement;
2847
    }
2848

2849
    /**
2850
     * Return the parameters for the first constructor function for this class
2851
     * @param ancestors The list of ancestors for this class
2852
     * @returns The parameters for the first constructor function for this class
2853
     */
2854
    private getConstructorParams(ancestors: ClassStatement[]) {
2855
        for (let ancestor of ancestors) {
49✔
2856
            const ctor = ancestor?.getConstructorFunction();
40!
2857
            if (ctor) {
40✔
2858
                return ctor.func.parameters;
16✔
2859
            }
2860
        }
2861
        return [];
33✔
2862
    }
2863

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

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

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

2907
        for (let statement of body) {
58✔
2908
            //is field statement
2909
            if (isFieldStatement(statement)) {
88✔
2910
                //do nothing with class fields in this situation, they are handled elsewhere
2911
                continue;
15✔
2912

2913
                //methods
2914
            } else if (isMethodStatement(statement)) {
73!
2915

2916
                //store overridden parent methods as super{parentIndex}_{methodName}
2917
                if (
73✔
2918
                    //is override method
2919
                    statement.tokens.override ||
200✔
2920
                    //is constructor function in child class
2921
                    (statement.tokens.name.text.toLowerCase() === 'new' && ancestors[0])
2922
                ) {
2923
                    result.push(
28✔
2924
                        `instance.super${parentClassIndex}_${statement.tokens.name.text} = instance.${statement.tokens.name.text}`,
2925
                        state.newline,
2926
                        state.indent()
2927
                    );
2928
                }
2929

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

2966
    /**
2967
     * Returns a copy of the class' body, with the constructor function added if it doesn't exist.
2968
     */
2969
    private getTranspiledClassBody(ancestors: ClassStatement[]): Statement[] {
2970
        const body = [];
58✔
2971
        body.push(...this.body);
58✔
2972

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

3009
        return body;
58✔
3010
    }
3011

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

3032
    /**
3033
     * The class function is the function with the same name as the class. This is the function that
3034
     * consumers should call to create a new instance of that class.
3035
     * This invokes the builder, gets an instance of the class, then invokes the "new" function on that class.
3036
     */
3037
    private getTranspiledClassFunction(state: BrsTranspileState, transpiledClassName: string) {
3038
        let result: TranspileResult = state.transpileAnnotations(this);
58✔
3039

3040
        const constructorFunction = this.getConstructorFunction();
58✔
3041
        let constructorParams = [];
58✔
3042
        if (constructorFunction) {
58✔
3043
            constructorParams = constructorFunction.func.parameters;
24✔
3044
        } else {
3045
            constructorParams = this.getConstructorParams(this.getAncestors(state));
34✔
3046
        }
3047

3048
        result.push(
58✔
3049
            state.transpileLeadingComments(this.tokens.class),
3050
            state.sourceNode(this.tokens.class, 'function'),
3051
            state.sourceNode(this.tokens.class, ' '),
3052
            state.sourceNode(this.tokens.name, this.getName(ParseMode.BrightScript)),
3053
            `(`
3054
        );
3055
        let i = 0;
58✔
3056
        for (let param of constructorParams) {
58✔
3057
            if (i > 0) {
17✔
3058
                result.push(', ');
4✔
3059
            }
3060
            result.push(
17✔
3061
                param.transpile(state)
3062
            );
3063
            i++;
17✔
3064
        }
3065
        result.push(
58✔
3066
            ')',
3067
            '\n'
3068
        );
3069

3070
        state.blockDepth++;
58✔
3071
        result.push(state.indent());
58✔
3072
        result.push(`instance = ${this.getBuilderName(transpiledClassName)}()\n`);
58✔
3073

3074
        result.push(state.indent());
58✔
3075
        result.push(`instance.new(`);
58✔
3076

3077
        //append constructor arguments
3078
        i = 0;
58✔
3079
        for (let param of constructorParams) {
58✔
3080
            if (i > 0) {
17✔
3081
                result.push(', ');
4✔
3082
            }
3083
            result.push(
17✔
3084
                state.transpileToken(param.tokens.name)
3085
            );
3086
            i++;
17✔
3087
        }
3088
        result.push(
58✔
3089
            ')',
3090
            '\n'
3091
        );
3092

3093
        result.push(state.indent());
58✔
3094
        result.push(`return instance\n`);
58✔
3095

3096
        state.blockDepth--;
58✔
3097
        result.push(state.indent());
58✔
3098
        result.push(`end function`);
58✔
3099
        return result;
58✔
3100
    }
3101

3102
    walk(visitor: WalkVisitor, options: WalkOptions) {
3103
        //visitor-less walk function to do parent linking
3104
        walk(this, 'parentClassName', null, options);
2,861✔
3105

3106
        if (options.walkMode & InternalWalkMode.walkStatements) {
2,861!
3107
            walkArray(this.body, visitor, options, this);
2,861✔
3108
        }
3109
    }
3110

3111
    getType(options: GetTypeOptions) {
3112
        const superClass = this.parentClassName?.getType(options) as ClassType;
582✔
3113

3114
        const resultType = new ClassType(this.getName(ParseMode.BrighterScript), superClass);
582✔
3115

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

3145
    public clone() {
3146
        return this.finalizeClone(
11✔
3147
            new ClassStatement({
3148
                class: util.cloneToken(this.tokens.class),
3149
                name: util.cloneToken(this.tokens.name),
3150
                body: this.body?.map(x => x?.clone()),
10✔
3151
                endClass: util.cloneToken(this.tokens.endClass),
3152
                extends: util.cloneToken(this.tokens.extends),
3153
                parentClassName: this.parentClassName?.clone()
33✔
3154
            }),
3155
            ['body', 'parentClassName']
3156
        );
3157
    }
3158
}
3159

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

3193
    public readonly kind = AstNodeKind.MethodStatement as AstNodeKind;
421✔
3194

3195
    public readonly modifiers: Token[] = [];
421✔
3196

3197
    public readonly tokens: {
3198
        readonly name: Identifier;
3199
        readonly override?: Token;
3200
    };
3201

3202
    public get accessModifier() {
3203
        return this.modifiers.find(x => accessModifiers.includes(x.kind));
708✔
3204
    }
3205

3206
    public readonly location: Location | undefined;
3207

3208
    /**
3209
     * Get the name of this method.
3210
     */
3211
    public getName(parseMode: ParseMode) {
3212
        return this.tokens.name.text;
380✔
3213
    }
3214

3215
    public get leadingTrivia(): Token[] {
3216
        return this.func.leadingTrivia;
841✔
3217
    }
3218

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

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

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

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

3301
        //if a call to super exists, quit here
3302
        if (containsSuperCall) {
24✔
3303
            return;
23✔
3304
        }
3305

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

3341
    /**
3342
     * Inject field initializers at the top of the `new` function (after any present `super()` call)
3343
     */
3344
    private injectFieldInitializersForConstructor(state: BrsTranspileState) {
3345
        let startingIndex = state.classStatement!.hasParentClass() ? 1 : 0;
58✔
3346

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

3371
    walk(visitor: WalkVisitor, options: WalkOptions) {
3372
        if (options.walkMode & InternalWalkMode.walkExpressions) {
2,280✔
3373
            walk(this, 'func', visitor, options);
1,828✔
3374
        }
3375
    }
3376

3377
    public clone() {
3378
        return this.finalizeClone(
5✔
3379
            new MethodStatement({
3380
                modifiers: this.modifiers?.map(m => util.cloneToken(m)),
1✔
3381
                name: util.cloneToken(this.tokens.name),
3382
                func: this.func?.clone(),
15✔
3383
                override: util.cloneToken(this.tokens.override)
3384
            }),
3385
            ['func']
3386
        );
3387
    }
3388
}
3389

3390
export class FieldStatement extends Statement implements TypedefProvider {
1✔
3391
    constructor(options: {
3392
        accessModifier?: Token;
3393
        name: Identifier;
3394
        as?: Token;
3395
        typeExpression?: TypeExpression;
3396
        equals?: Token;
3397
        initialValue?: Expression;
3398
        optional?: Token;
3399
    }) {
3400
        super();
355✔
3401
        this.tokens = {
355✔
3402
            accessModifier: options.accessModifier,
3403
            name: options.name,
3404
            as: options.as,
3405
            equals: options.equals,
3406
            optional: options.optional
3407
        };
3408
        this.typeExpression = options.typeExpression;
355✔
3409
        this.initialValue = options.initialValue;
355✔
3410

3411
        this.location = util.createBoundingLocation(
355✔
3412
            util.createBoundingLocationFromTokens(this.tokens),
3413
            this.typeExpression,
3414
            this.initialValue
3415
        );
3416
    }
3417

3418
    public readonly tokens: {
3419
        readonly accessModifier?: Token;
3420
        readonly name: Identifier;
3421
        readonly as?: Token;
3422
        readonly equals?: Token;
3423
        readonly optional?: Token;
3424
    };
3425

3426
    public readonly typeExpression?: TypeExpression;
3427
    public readonly initialValue?: Expression;
3428

3429
    public readonly kind = AstNodeKind.FieldStatement;
355✔
3430

3431
    /**
3432
     * Derive a ValueKind from the type token, or the initial value.
3433
     * Defaults to `DynamicType`
3434
     */
3435
    getType(options: GetTypeOptions) {
3436
        let initialValueType = this.initialValue?.getType({ ...options, flags: SymbolTypeFlag.runtime });
349✔
3437

3438
        if (isInvalidType(initialValueType) || isVoidType(initialValueType) || isUninitializedType(initialValueType)) {
349✔
3439
            initialValueType = undefined;
4✔
3440
        }
3441

3442
        return this.typeExpression?.getType({ ...options, flags: SymbolTypeFlag.typetime }) ??
349✔
3443
            util.getDefaultTypeFromValueType(initialValueType) ??
349✔
3444
            DynamicType.instance;
3445
    }
3446

3447
    public readonly location: Location | undefined;
3448

3449
    public get leadingTrivia(): Token[] {
3450
        return this.tokens.accessModifier?.leadingTrivia ?? this.tokens.optional?.leadingTrivia ?? this.tokens.name.leadingTrivia;
713✔
3451
    }
3452

3453
    public get isOptional() {
3454
        return !!this.tokens.optional;
318✔
3455
    }
3456

3457
    transpile(state: BrsTranspileState): TranspileResult {
UNCOV
3458
        throw new Error('transpile not implemented for ' + Object.getPrototypeOf(this).constructor.name);
×
3459
    }
3460

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

3479
            let type = this.getType({ flags: SymbolTypeFlag.typetime });
12✔
3480
            if (isInvalidType(type) || isVoidType(type) || isUninitializedType(type)) {
12!
3481
                type = DynamicType.instance;
×
3482
            }
3483

3484
            result.push(
12✔
3485
                this.tokens.accessModifier?.text ?? 'public',
72✔
3486
                ' '
3487
            );
3488
            if (this.isOptional) {
12!
UNCOV
3489
                result.push(this.tokens.optional.text, ' ');
×
3490
            }
3491
            result.push(this.tokens.name?.text,
12!
3492
                ' as ',
3493
                type.toTypeString()
3494
            );
3495
        }
3496
        return result;
12✔
3497
    }
3498

3499
    walk(visitor: WalkVisitor, options: WalkOptions) {
3500
        if (options.walkMode & InternalWalkMode.walkExpressions) {
1,782✔
3501
            walk(this, 'typeExpression', visitor, options);
1,554✔
3502
            walk(this, 'initialValue', visitor, options);
1,554✔
3503
        }
3504
    }
3505

3506
    public clone() {
3507
        return this.finalizeClone(
4✔
3508
            new FieldStatement({
3509
                accessModifier: util.cloneToken(this.tokens.accessModifier),
3510
                name: util.cloneToken(this.tokens.name),
3511
                as: util.cloneToken(this.tokens.as),
3512
                typeExpression: this.typeExpression?.clone(),
12✔
3513
                equals: util.cloneToken(this.tokens.equals),
3514
                initialValue: this.initialValue?.clone(),
12✔
3515
                optional: util.cloneToken(this.tokens.optional)
3516
            }),
3517
            ['initialValue']
3518
        );
3519
    }
3520
}
3521

3522
export type MemberStatement = FieldStatement | MethodStatement;
3523

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

3546
    public readonly tokens: {
3547
        readonly try?: Token;
3548
        readonly endTry?: Token;
3549
    };
3550

3551
    public readonly tryBranch: Block;
3552
    public readonly catchStatement: CatchStatement;
3553

3554
    public readonly kind = AstNodeKind.TryCatchStatement;
46✔
3555

3556
    public readonly location: Location | undefined;
3557

3558
    public transpile(state: BrsTranspileState): TranspileResult {
3559
        return [
7✔
3560
            state.transpileToken(this.tokens.try, 'try'),
3561
            ...this.tryBranch.transpile(state),
3562
            state.newline,
3563
            state.indent(),
3564
            ...(this.catchStatement?.transpile(state) ?? ['catch']),
42!
3565
            state.newline,
3566
            state.indent(),
3567
            state.transpileToken(this.tokens.endTry!, 'end try')
3568
        ];
3569
    }
3570

3571
    public walk(visitor: WalkVisitor, options: WalkOptions) {
3572
        if (this.tryBranch && options.walkMode & InternalWalkMode.walkStatements) {
134!
3573
            walk(this, 'tryBranch', visitor, options);
134✔
3574
            walk(this, 'catchStatement', visitor, options);
134✔
3575
        }
3576
    }
3577

3578
    public get leadingTrivia(): Token[] {
3579
        return this.tokens.try?.leadingTrivia ?? [];
131!
3580
    }
3581

3582
    public get endTrivia(): Token[] {
3583
        return this.tokens.endTry?.leadingTrivia ?? [];
1!
3584
    }
3585

3586
    public clone() {
3587
        return this.finalizeClone(
3✔
3588
            new TryCatchStatement({
3589
                try: util.cloneToken(this.tokens.try),
3590
                endTry: util.cloneToken(this.tokens.endTry),
3591
                tryBranch: this.tryBranch?.clone(),
9✔
3592
                catchStatement: this.catchStatement?.clone()
9✔
3593
            }),
3594
            ['tryBranch', 'catchStatement']
3595
        );
3596
    }
3597
}
3598

3599
export class CatchStatement extends Statement {
1✔
3600
    constructor(options?: {
3601
        catch?: Token;
3602
        exceptionVariableExpression?: Expression;
3603
        catchBranch?: Block;
3604
    }) {
3605
        super();
43✔
3606
        this.tokens = {
43✔
3607
            catch: options?.catch
129!
3608
        };
3609
        this.exceptionVariableExpression = options?.exceptionVariableExpression;
43!
3610
        this.catchBranch = options?.catchBranch;
43!
3611
        this.location = util.createBoundingLocation(
43✔
3612
            this.tokens.catch,
3613
            this.exceptionVariableExpression,
3614
            this.catchBranch
3615
        );
3616
    }
3617

3618
    public readonly tokens: {
3619
        readonly catch?: Token;
3620
    };
3621

3622
    public readonly exceptionVariableExpression?: Expression;
3623

3624
    public readonly catchBranch?: Block;
3625

3626
    public readonly kind = AstNodeKind.CatchStatement;
43✔
3627

3628
    public readonly location: Location | undefined;
3629

3630
    public transpile(state: BrsTranspileState): TranspileResult {
3631
        return [
7✔
3632
            state.transpileToken(this.tokens.catch, 'catch'),
3633
            ' ',
3634
            this.exceptionVariableExpression?.transpile(state) ?? [
42✔
3635
                //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
3636
                this.getSymbolTable()?.hasSymbol('e', SymbolTypeFlag.runtime)
6!
3637
                    ? '__bsc_error'
2✔
3638
                    : 'e'
3639
            ],
3640
            ...(this.catchBranch?.transpile(state) ?? [])
42!
3641
        ];
3642
    }
3643

3644
    public walk(visitor: WalkVisitor, options: WalkOptions) {
3645
        if (this.catchBranch && options.walkMode & InternalWalkMode.walkStatements) {
131!
3646
            walk(this, 'catchBranch', visitor, options);
131✔
3647
        }
3648
    }
3649

3650
    public get leadingTrivia(): Token[] {
3651
        return this.tokens.catch?.leadingTrivia ?? [];
83!
3652
    }
3653

3654
    public clone() {
3655
        return this.finalizeClone(
2✔
3656
            new CatchStatement({
3657
                catch: util.cloneToken(this.tokens.catch),
3658
                exceptionVariableExpression: this.exceptionVariableExpression?.clone(),
6!
3659
                catchBranch: this.catchBranch?.clone()
6✔
3660
            }),
3661
            ['catchBranch']
3662
        );
3663
    }
3664
}
3665

3666
export class ThrowStatement extends Statement {
1✔
3667
    constructor(options?: {
3668
        throw?: Token;
3669
        expression?: Expression;
3670
    }) {
3671
        super();
14✔
3672
        this.tokens = {
14✔
3673
            throw: options.throw
3674
        };
3675
        this.expression = options.expression;
14✔
3676
        this.location = util.createBoundingLocation(
14✔
3677
            this.tokens.throw,
3678
            this.expression
3679
        );
3680
    }
3681

3682
    public readonly tokens: {
3683
        readonly throw?: Token;
3684
    };
3685
    public readonly expression?: Expression;
3686

3687
    public readonly kind = AstNodeKind.ThrowStatement;
14✔
3688

3689
    public readonly location: Location | undefined;
3690

3691
    public transpile(state: BrsTranspileState) {
3692
        const result = [
5✔
3693
            state.transpileToken(this.tokens.throw, 'throw'),
3694
            ' '
3695
        ] as TranspileResult;
3696

3697
        //if we have an expression, transpile it
3698
        if (this.expression) {
5✔
3699
            result.push(
4✔
3700
                ...this.expression.transpile(state)
3701
            );
3702

3703
            //no expression found. Rather than emit syntax errors, provide a generic error message
3704
        } else {
3705
            result.push('"User-specified exception"');
1✔
3706
        }
3707
        return result;
5✔
3708
    }
3709

3710
    public walk(visitor: WalkVisitor, options: WalkOptions) {
3711
        if (this.expression && options.walkMode & InternalWalkMode.walkExpressions) {
38✔
3712
            walk(this, 'expression', visitor, options);
30✔
3713
        }
3714
    }
3715

3716
    public get leadingTrivia(): Token[] {
3717
        return this.tokens.throw?.leadingTrivia ?? [];
54!
3718
    }
3719

3720
    public clone() {
3721
        return this.finalizeClone(
2✔
3722
            new ThrowStatement({
3723
                throw: util.cloneToken(this.tokens.throw),
3724
                expression: this.expression?.clone()
6✔
3725
            }),
3726
            ['expression']
3727
        );
3728
    }
3729
}
3730

3731

3732
export class EnumStatement extends Statement implements TypedefProvider {
1✔
3733
    constructor(options: {
3734
        enum?: Token;
3735
        name: Identifier;
3736
        endEnum?: Token;
3737
        body: Array<EnumMemberStatement>;
3738
    }) {
3739
        super();
192✔
3740
        this.tokens = {
192✔
3741
            enum: options.enum,
3742
            name: options.name,
3743
            endEnum: options.endEnum
3744
        };
3745
        this.symbolTable = new SymbolTable('Enum');
192✔
3746
        this.body = options.body ?? [];
192✔
3747
    }
3748

3749
    public readonly tokens: {
3750
        readonly enum?: Token;
3751
        readonly name: Identifier;
3752
        readonly endEnum?: Token;
3753
    };
3754
    public readonly body: Array<EnumMemberStatement>;
3755

3756
    public readonly kind = AstNodeKind.EnumStatement;
192✔
3757

3758
    public get location(): Location | undefined {
3759
        return util.createBoundingLocation(
185✔
3760
            this.tokens.enum,
3761
            this.tokens.name,
3762
            ...this.body,
3763
            this.tokens.endEnum
3764
        );
3765
    }
3766

3767
    public getMembers() {
3768
        const result = [] as EnumMemberStatement[];
382✔
3769
        for (const statement of this.body) {
382✔
3770
            if (isEnumMemberStatement(statement)) {
797!
3771
                result.push(statement);
797✔
3772
            }
3773
        }
3774
        return result;
382✔
3775
    }
3776

3777
    public get leadingTrivia(): Token[] {
3778
        return this.tokens.enum?.leadingTrivia;
542!
3779
    }
3780

3781
    public get endTrivia(): Token[] {
UNCOV
3782
        return this.tokens.endEnum?.leadingTrivia ?? [];
×
3783
    }
3784

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

3799
                //if explicit integer value, use it and increment the int counter
3800
            } else if (isLiteralExpression(member.value) && member.value.tokens.value.kind === TokenKind.IntegerLiteral) {
129✔
3801
                //try parsing as integer literal, then as hex integer literal.
3802
                let tokenIntValue = util.parseInt(member.value.tokens.value.text) ?? util.parseInt(member.value.tokens.value.text.replace(/&h/i, '0x'));
29✔
3803
                if (tokenIntValue !== undefined) {
29!
3804
                    currentIntValue = tokenIntValue;
29✔
3805
                    currentIntValue++;
29✔
3806
                }
3807
                result.set(member.name?.toLowerCase(), member.value.tokens.value.text);
29!
3808

3809
                //simple unary expressions (like `-1`)
3810
            } else if (isUnaryExpression(member.value) && isLiteralExpression(member.value.right)) {
100✔
3811
                result.set(member.name?.toLowerCase(), member.value.tokens.operator.text + member.value.right.tokens.value.text);
1!
3812

3813
                //all other values
3814
            } else {
3815
                result.set(member.name?.toLowerCase(), (member.value as LiteralExpression)?.tokens?.value?.text ?? 'invalid');
99!
3816
            }
3817
        }
3818
        return result;
66✔
3819
    }
3820

3821
    public getMemberValue(name: string) {
3822
        return this.getMemberValueMap().get(name.toLowerCase());
63✔
3823
    }
3824

3825
    /**
3826
     * The name of the enum (without the namespace prefix)
3827
     */
3828
    public get name() {
3829
        return this.tokens.name?.text;
1!
3830
    }
3831

3832
    /**
3833
     * The name of the enum WITH its leading namespace (if applicable)
3834
     */
3835
    public get fullName() {
3836
        const name = this.tokens.name?.text;
767!
3837
        if (name) {
767!
3838
            const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
767✔
3839

3840
            if (namespace) {
767✔
3841
                let namespaceName = namespace.getName(ParseMode.BrighterScript);
252✔
3842
                return `${namespaceName}.${name}`;
252✔
3843
            } else {
3844
                return name;
515✔
3845
            }
3846
        } else {
3847
            //return undefined which will allow outside callers to know that this doesn't have a name
UNCOV
3848
            return undefined;
×
3849
        }
3850
    }
3851

3852
    transpile(state: BrsTranspileState) {
3853
        //enum declarations don't exist at runtime, so just transpile comments and trivia
3854
        return [
29✔
3855
            state.transpileAnnotations(this),
3856
            state.transpileLeadingComments(this.tokens.enum)
3857
        ];
3858
    }
3859

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

3900
    walk(visitor: WalkVisitor, options: WalkOptions) {
3901
        if (options.walkMode & InternalWalkMode.walkStatements) {
1,201!
3902
            walkArray(this.body, visitor, options, this);
1,201✔
3903

3904
        }
3905
    }
3906

3907
    getType(options: GetTypeOptions) {
3908
        const members = this.getMembers();
158✔
3909

3910
        const resultType = new EnumType(
158✔
3911
            this.fullName,
3912
            members[0]?.getType(options).underlyingType
474✔
3913
        );
3914
        resultType.pushMemberProvider(() => this.getSymbolTable());
659✔
3915
        for (const statement of members) {
158✔
3916
            const memberType = statement.getType({ ...options, typeChain: undefined });
316✔
3917
            memberType.parentEnumType = resultType;
316✔
3918
            resultType.addMember(statement?.tokens?.name?.text, { definingNode: statement }, memberType, SymbolTypeFlag.runtime);
316!
3919
        }
3920
        return resultType;
158✔
3921
    }
3922

3923
    public clone() {
3924
        return this.finalizeClone(
6✔
3925
            new EnumStatement({
3926
                enum: util.cloneToken(this.tokens.enum),
3927
                name: util.cloneToken(this.tokens.name),
3928
                endEnum: util.cloneToken(this.tokens.endEnum),
3929
                body: this.body?.map(x => x?.clone())
4✔
3930
            }),
3931
            ['body']
3932
        );
3933
    }
3934
}
3935

3936
export class EnumMemberStatement extends Statement implements TypedefProvider {
1✔
3937
    public constructor(options: {
3938
        name: Identifier;
3939
        equals?: Token;
3940
        value?: Expression;
3941
    }) {
3942
        super();
370✔
3943
        this.tokens = {
370✔
3944
            name: options.name,
3945
            equals: options.equals
3946
        };
3947
        this.value = options.value;
370✔
3948
    }
3949

3950
    public readonly tokens: {
3951
        readonly name: Identifier;
3952
        readonly equals?: Token;
3953
    };
3954
    public readonly value?: Expression;
3955

3956
    public readonly kind = AstNodeKind.EnumMemberStatement;
370✔
3957

3958
    public get location() {
3959
        return util.createBoundingLocation(
612✔
3960
            this.tokens.name,
3961
            this.tokens.equals,
3962
            this.value
3963
        );
3964
    }
3965

3966
    /**
3967
     * The name of the member
3968
     */
3969
    public get name() {
3970
        return this.tokens.name.text;
471✔
3971
    }
3972

3973
    public get leadingTrivia(): Token[] {
3974
        return this.tokens.name.leadingTrivia;
1,483✔
3975
    }
3976

3977
    public transpile(state: BrsTranspileState): TranspileResult {
UNCOV
3978
        return [];
×
3979
    }
3980

3981
    getTypedef(state: BrsTranspileState): TranspileResult {
3982
        const result: TranspileResult = [];
1✔
3983
        for (let comment of util.getLeadingComments(this) ?? []) {
1!
UNCOV
3984
            result.push(
×
3985
                comment.text,
3986
                state.newline,
3987
                state.indent()
3988
            );
3989
        }
3990
        result.push(this.tokens.name.text);
1✔
3991
        if (this.tokens.equals) {
1!
UNCOV
3992
            result.push(' ', this.tokens.equals.text, ' ');
×
UNCOV
3993
            if (this.value) {
×
UNCOV
3994
                result.push(
×
3995
                    ...this.value.transpile(state)
3996
                );
3997
            }
3998
        }
3999
        return result;
1✔
4000
    }
4001

4002
    walk(visitor: WalkVisitor, options: WalkOptions) {
4003
        if (this.value && options.walkMode & InternalWalkMode.walkExpressions) {
2,910✔
4004
            walk(this, 'value', visitor, options);
1,434✔
4005
        }
4006
    }
4007

4008
    getType(options: GetTypeOptions) {
4009
        return new EnumMemberType(
471✔
4010
            (this.parent as EnumStatement)?.fullName,
1,413!
4011
            this.tokens?.name?.text,
2,826!
4012
            this.value?.getType(options)
1,413✔
4013
        );
4014
    }
4015

4016
    public clone() {
4017
        return this.finalizeClone(
3✔
4018
            new EnumMemberStatement({
4019
                name: util.cloneToken(this.tokens.name),
4020
                equals: util.cloneToken(this.tokens.equals),
4021
                value: this.value?.clone()
9✔
4022
            }),
4023
            ['value']
4024
        );
4025
    }
4026
}
4027

4028
export class ConstStatement extends Statement implements TypedefProvider {
1✔
4029
    public constructor(options: {
4030
        const?: Token;
4031
        name: Identifier;
4032
        equals?: Token;
4033
        value: Expression;
4034
    }) {
4035
        super();
195✔
4036
        this.tokens = {
195✔
4037
            const: options.const,
4038
            name: options.name,
4039
            equals: options.equals
4040
        };
4041
        this.value = options.value;
195✔
4042
        this.location = util.createBoundingLocation(this.tokens.const, this.tokens.name, this.tokens.equals, this.value);
195✔
4043
    }
4044

4045
    public readonly tokens: {
4046
        readonly const: Token;
4047
        readonly name: Identifier;
4048
        readonly equals: Token;
4049
    };
4050
    public readonly value: Expression;
4051

4052
    public readonly kind = AstNodeKind.ConstStatement;
195✔
4053

4054
    public readonly location: Location | undefined;
4055

4056
    public get name() {
UNCOV
4057
        return this.tokens.name.text;
×
4058
    }
4059

4060
    public get leadingTrivia(): Token[] {
4061
        return this.tokens.const?.leadingTrivia;
609!
4062
    }
4063

4064
    /**
4065
     * The name of the statement WITH its leading namespace (if applicable)
4066
     */
4067
    public get fullName() {
4068
        const name = this.tokens.name?.text;
120!
4069
        if (name) {
120!
4070
            const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
120✔
4071
            if (namespace) {
120✔
4072
                let namespaceName = namespace.getName(ParseMode.BrighterScript);
59✔
4073
                return `${namespaceName}.${name}`;
59✔
4074
            } else {
4075
                return name;
61✔
4076
            }
4077
        } else {
4078
            //return undefined which will allow outside callers to know that this doesn't have a name
UNCOV
4079
            return undefined;
×
4080
        }
4081
    }
4082

4083
    public transpile(state: BrsTranspileState): TranspileResult {
4084
        //const declarations don't exist at runtime, so just transpile comments and trivia
4085
        return [
49✔
4086
            state.transpileAnnotations(this),
4087
            state.transpileLeadingComments(this.tokens.const)
4088
        ];
4089
    }
4090

4091
    getTypedef(state: BrsTranspileState): TranspileResult {
4092
        const result: TranspileResult = [];
3✔
4093
        for (let comment of util.getLeadingComments(this) ?? []) {
3!
UNCOV
4094
            result.push(
×
4095
                comment.text,
4096
                state.newline,
4097
                state.indent()
4098
            );
4099
        }
4100
        result.push(
3✔
4101
            this.tokens.const ? state.tokenToSourceNode(this.tokens.const) : 'const',
3!
4102
            ' ',
4103
            state.tokenToSourceNode(this.tokens.name),
4104
            ' ',
4105
            this.tokens.equals ? state.tokenToSourceNode(this.tokens.equals) : '=',
3!
4106
            ' ',
4107
            ...this.value.transpile(state)
4108
        );
4109
        return result;
3✔
4110
    }
4111

4112
    walk(visitor: WalkVisitor, options: WalkOptions) {
4113
        if (this.value && options.walkMode & InternalWalkMode.walkExpressions) {
1,323✔
4114
            walk(this, 'value', visitor, options);
1,148✔
4115
        }
4116
    }
4117

4118
    getType(options: GetTypeOptions) {
4119
        return this.value.getType(options);
187✔
4120
    }
4121

4122
    public clone() {
4123
        return this.finalizeClone(
3✔
4124
            new ConstStatement({
4125
                const: util.cloneToken(this.tokens.const),
4126
                name: util.cloneToken(this.tokens.name),
4127
                equals: util.cloneToken(this.tokens.equals),
4128
                value: this.value?.clone()
9✔
4129
            }),
4130
            ['value']
4131
        );
4132
    }
4133
}
4134

4135
export class ContinueStatement extends Statement {
1✔
4136
    constructor(options: {
4137
        continue?: Token;
4138
        loopType: Token;
4139
    }
4140
    ) {
4141
        super();
13✔
4142
        this.tokens = {
13✔
4143
            continue: options.continue,
4144
            loopType: options.loopType
4145
        };
4146
        this.location = util.createBoundingLocation(
13✔
4147
            this.tokens.continue,
4148
            this.tokens.loopType
4149
        );
4150
    }
4151

4152
    public readonly tokens: {
4153
        readonly continue?: Token;
4154
        readonly loopType: Token;
4155
    };
4156

4157
    public readonly kind = AstNodeKind.ContinueStatement;
13✔
4158

4159
    public readonly location: Location | undefined;
4160

4161
    transpile(state: BrsTranspileState) {
4162
        return [
3✔
4163
            state.sourceNode(this.tokens.continue, this.tokens.continue?.text ?? 'continue'),
18!
4164
            this.tokens.loopType?.leadingWhitespace ?? ' ',
18✔
4165
            state.sourceNode(this.tokens.continue, this.tokens.loopType?.text)
9✔
4166
        ];
4167
    }
4168

4169
    walk(visitor: WalkVisitor, options: WalkOptions) {
4170
        //nothing to walk
4171
    }
4172

4173
    public get leadingTrivia(): Token[] {
4174
        return this.tokens.continue?.leadingTrivia ?? [];
86!
4175
    }
4176

4177
    public clone() {
4178
        return this.finalizeClone(
1✔
4179
            new ContinueStatement({
4180
                continue: util.cloneToken(this.tokens.continue),
4181
                loopType: util.cloneToken(this.tokens.loopType)
4182
            })
4183
        );
4184
    }
4185
}
4186

4187
export class TypecastStatement extends Statement {
1✔
4188
    constructor(options: {
4189
        typecast?: Token;
4190
        typecastExpression: TypecastExpression;
4191
    }
4192
    ) {
4193
        super();
27✔
4194
        this.tokens = {
27✔
4195
            typecast: options.typecast
4196
        };
4197
        this.typecastExpression = options.typecastExpression;
27✔
4198
        this.location = util.createBoundingLocation(
27✔
4199
            this.tokens.typecast,
4200
            this.typecastExpression
4201
        );
4202
    }
4203

4204
    public readonly tokens: {
4205
        readonly typecast?: Token;
4206
    };
4207

4208
    public readonly typecastExpression: TypecastExpression;
4209

4210
    public readonly kind = AstNodeKind.TypecastStatement;
27✔
4211

4212
    public readonly location: Location;
4213

4214
    transpile(state: BrsTranspileState) {
4215
        //the typecast statement is a comment just for debugging purposes
4216
        return [
2✔
4217
            state.transpileToken(this.tokens.typecast, 'typecast', true),
4218
            ' ',
4219
            this.typecastExpression.obj.transpile(state),
4220
            ' ',
4221
            state.transpileToken(this.typecastExpression.tokens.as, 'as'),
4222
            ' ',
4223
            this.typecastExpression.typeExpression.getName(ParseMode.BrighterScript)
4224
        ];
4225
    }
4226

4227
    walk(visitor: WalkVisitor, options: WalkOptions) {
4228
        if (options.walkMode & InternalWalkMode.walkExpressions) {
160✔
4229
            walk(this, 'typecastExpression', visitor, options);
147✔
4230
        }
4231
    }
4232

4233
    get leadingTrivia(): Token[] {
4234
        return this.tokens.typecast?.leadingTrivia ?? [];
119!
4235
    }
4236

4237
    getType(options: GetTypeOptions): BscType {
4238
        return this.typecastExpression.getType(options);
22✔
4239
    }
4240

4241
    public clone() {
4242
        return this.finalizeClone(
1✔
4243
            new TypecastStatement({
4244
                typecast: util.cloneToken(this.tokens.typecast),
4245
                typecastExpression: this.typecastExpression?.clone()
3!
4246
            }),
4247
            ['typecastExpression']
4248
        );
4249
    }
4250
}
4251

4252
export class ConditionalCompileErrorStatement extends Statement {
1✔
4253
    constructor(options: {
4254
        hashError?: Token;
4255
        message: Token;
4256
    }) {
4257
        super();
11✔
4258
        this.tokens = {
11✔
4259
            hashError: options.hashError,
4260
            message: options.message
4261
        };
4262
        this.location = util.createBoundingLocation(util.createBoundingLocationFromTokens(this.tokens));
11✔
4263
    }
4264

4265
    public readonly tokens: {
4266
        readonly hashError?: Token;
4267
        readonly message: Token;
4268
    };
4269

4270

4271
    public readonly kind = AstNodeKind.ConditionalCompileErrorStatement;
11✔
4272

4273
    public readonly location: Location | undefined;
4274

4275
    transpile(state: BrsTranspileState) {
4276
        return [
3✔
4277
            state.transpileToken(this.tokens.hashError, '#error'),
4278
            ' ',
4279
            state.transpileToken(this.tokens.message)
4280

4281
        ];
4282
    }
4283

4284
    walk(visitor: WalkVisitor, options: WalkOptions) {
4285
        // nothing to walk
4286
    }
4287

4288
    get leadingTrivia(): Token[] {
4289
        return this.tokens.hashError.leadingTrivia ?? [];
12!
4290
    }
4291

4292
    public clone() {
4293
        return this.finalizeClone(
1✔
4294
            new ConditionalCompileErrorStatement({
4295
                hashError: util.cloneToken(this.tokens.hashError),
4296
                message: util.cloneToken(this.tokens.message)
4297
            })
4298
        );
4299
    }
4300
}
4301

4302
export class AliasStatement extends Statement {
1✔
4303
    constructor(options: {
4304
        alias?: Token;
4305
        name: Token;
4306
        equals?: Token;
4307
        value: VariableExpression | DottedGetExpression;
4308
    }
4309
    ) {
4310
        super();
34✔
4311
        this.tokens = {
34✔
4312
            alias: options.alias,
4313
            name: options.name,
4314
            equals: options.equals
4315
        };
4316
        this.value = options.value;
34✔
4317
        this.location = util.createBoundingLocation(
34✔
4318
            this.tokens.alias,
4319
            this.tokens.name,
4320
            this.tokens.equals,
4321
            this.value
4322
        );
4323
    }
4324

4325
    public readonly tokens: {
4326
        readonly alias?: Token;
4327
        readonly name: Token;
4328
        readonly equals?: Token;
4329
    };
4330

4331
    public readonly value: Expression;
4332

4333
    public readonly kind = AstNodeKind.AliasStatement;
34✔
4334

4335
    public readonly location: Location;
4336

4337
    transpile(state: BrsTranspileState) {
4338
        //transpile to a comment just for debugging purposes
4339
        return [
12✔
4340
            state.transpileToken(this.tokens.alias, 'alias', true),
4341
            ' ',
4342
            state.transpileToken(this.tokens.name),
4343
            ' ',
4344
            state.transpileToken(this.tokens.equals, '='),
4345
            ' ',
4346
            this.value.transpile(state)
4347
        ];
4348
    }
4349

4350
    walk(visitor: WalkVisitor, options: WalkOptions) {
4351
        if (options.walkMode & InternalWalkMode.walkExpressions) {
235✔
4352
            walk(this, 'value', visitor, options);
206✔
4353
        }
4354
    }
4355

4356
    get leadingTrivia(): Token[] {
4357
        return this.tokens.alias?.leadingTrivia ?? [];
121!
4358
    }
4359

4360
    getType(options: GetTypeOptions): BscType {
4361
        return this.value.getType(options);
1✔
4362
    }
4363

4364
    public clone() {
4365
        return this.finalizeClone(
1✔
4366
            new AliasStatement({
4367
                alias: util.cloneToken(this.tokens.alias),
4368
                name: util.cloneToken(this.tokens.name),
4369
                equals: util.cloneToken(this.tokens.equals),
4370
                value: this.value?.clone()
3!
4371
            }),
4372
            ['value']
4373
        );
4374
    }
4375
}
4376

4377
export class ConditionalCompileStatement extends Statement {
1✔
4378
    constructor(options: {
4379
        hashIf?: Token;
4380
        not?: Token;
4381
        condition: Token;
4382
        hashElse?: Token;
4383
        hashEndIf?: Token;
4384
        thenBranch: Block;
4385
        elseBranch?: ConditionalCompileStatement | Block;
4386
    }) {
4387
        super();
58✔
4388
        this.thenBranch = options.thenBranch;
58✔
4389
        this.elseBranch = options.elseBranch;
58✔
4390

4391
        this.tokens = {
58✔
4392
            hashIf: options.hashIf,
4393
            not: options.not,
4394
            condition: options.condition,
4395
            hashElse: options.hashElse,
4396
            hashEndIf: options.hashEndIf
4397
        };
4398

4399
        this.location = util.createBoundingLocation(
58✔
4400
            util.createBoundingLocationFromTokens(this.tokens),
4401
            this.thenBranch,
4402
            this.elseBranch
4403
        );
4404
    }
4405

4406
    readonly tokens: {
4407
        readonly hashIf?: Token;
4408
        readonly not?: Token;
4409
        readonly condition: Token;
4410
        readonly hashElse?: Token;
4411
        readonly hashEndIf?: Token;
4412
    };
4413
    public readonly thenBranch: Block;
4414
    public readonly elseBranch?: ConditionalCompileStatement | Block;
4415

4416
    public readonly kind = AstNodeKind.ConditionalCompileStatement;
58✔
4417

4418
    public readonly location: Location | undefined;
4419

4420
    transpile(state: BrsTranspileState) {
4421
        let results = [] as TranspileResult;
6✔
4422
        //if   (already indented by block)
4423
        if (!state.conditionalCompileStatement) {
6✔
4424
            // only transpile the #if in the case when we're not in a conditionalCompileStatement already
4425
            results.push(state.transpileToken(this.tokens.hashIf ?? createToken(TokenKind.HashIf)));
3!
4426
        }
4427

4428
        results.push(' ');
6✔
4429
        //conditions
4430
        if (this.tokens.not) {
6✔
4431
            results.push('not');
2✔
4432
            results.push(' ');
2✔
4433
        }
4434
        results.push(state.transpileToken(this.tokens.condition));
6✔
4435
        state.lineage.unshift(this);
6✔
4436

4437
        //if statement body
4438
        let thenNodes = this.thenBranch.transpile(state);
6✔
4439
        state.lineage.shift();
6✔
4440
        if (thenNodes.length > 0) {
6!
4441
            results.push(thenNodes);
6✔
4442
        }
4443
        //else branch
4444
        if (this.elseBranch) {
6!
4445
            const elseIsCC = isConditionalCompileStatement(this.elseBranch);
6✔
4446
            const endBlockToken = elseIsCC ? (this.elseBranch as ConditionalCompileStatement).tokens.hashIf ?? createToken(TokenKind.HashElseIf) : this.tokens.hashElse;
6!
4447
            //else
4448

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

4451
            if (elseIsCC) {
6✔
4452
                //chained else if
4453
                state.lineage.unshift(this.elseBranch);
3✔
4454

4455
                // transpile following #if with knowledge of current
4456
                const existingCCStmt = state.conditionalCompileStatement;
3✔
4457
                state.conditionalCompileStatement = this;
3✔
4458
                let body = this.elseBranch.transpile(state);
3✔
4459
                state.conditionalCompileStatement = existingCCStmt;
3✔
4460

4461
                state.lineage.shift();
3✔
4462

4463
                if (body.length > 0) {
3!
4464
                    //zero or more spaces between the `else` and the `if`
4465
                    results.push(...body);
3✔
4466

4467
                    // stop here because chained if will transpile the rest
4468
                    return results;
3✔
4469
                } else {
UNCOV
4470
                    results.push('\n');
×
4471
                }
4472

4473
            } else {
4474
                //else body
4475
                state.lineage.unshift(this.tokens.hashElse!);
3✔
4476
                let body = this.elseBranch.transpile(state);
3✔
4477
                state.lineage.shift();
3✔
4478

4479
                if (body.length > 0) {
3!
4480
                    results.push(...body);
3✔
4481
                }
4482
            }
4483
        }
4484

4485
        //end if
4486
        results.push(...state.transpileEndBlockToken(this.elseBranch ?? this.thenBranch, this.tokens.hashEndIf, '#end if'));
3!
4487

4488
        return results;
3✔
4489
    }
4490

4491
    walk(visitor: WalkVisitor, options: WalkOptions) {
4492
        if (options.walkMode & InternalWalkMode.walkStatements) {
210!
4493
            const bsConsts = options.bsConsts ?? this.getBsConsts();
210✔
4494
            let conditionTrue = bsConsts?.get(this.tokens.condition.text.toLowerCase());
210✔
4495
            if (this.tokens.not) {
210✔
4496
                // flips the boolean value
4497
                conditionTrue = !conditionTrue;
25✔
4498
            }
4499
            const walkFalseBlocks = options.walkMode & InternalWalkMode.visitFalseConditionalCompilationBlocks;
210✔
4500
            if (conditionTrue || walkFalseBlocks) {
210✔
4501
                walk(this, 'thenBranch', visitor, options);
169✔
4502
            }
4503
            if (this.elseBranch && (!conditionTrue || walkFalseBlocks)) {
210✔
4504
                walk(this, 'elseBranch', visitor, options);
71✔
4505
            }
4506
        }
4507
    }
4508

4509
    get leadingTrivia(): Token[] {
4510
        return this.tokens.hashIf?.leadingTrivia ?? [];
175!
4511
    }
4512

4513
    public clone() {
4514
        return this.finalizeClone(
1✔
4515
            new ConditionalCompileStatement({
4516
                hashIf: util.cloneToken(this.tokens.hashIf),
4517
                not: util.cloneToken(this.tokens.not),
4518
                condition: util.cloneToken(this.tokens.condition),
4519
                hashElse: util.cloneToken(this.tokens.hashElse),
4520
                hashEndIf: util.cloneToken(this.tokens.hashEndIf),
4521
                thenBranch: this.thenBranch?.clone(),
3!
4522
                elseBranch: this.elseBranch?.clone()
3!
4523
            }),
4524
            ['thenBranch', 'elseBranch']
4525
        );
4526
    }
4527

4528
    public getBranchStatementIndex(stmt: Statement) {
UNCOV
4529
        if (this.thenBranch === stmt) {
×
UNCOV
4530
            return 0;
×
UNCOV
4531
        } else if (this.elseBranch === stmt) {
×
UNCOV
4532
            return 1;
×
4533
        }
UNCOV
4534
        return -1;
×
4535
    }
4536
}
4537

4538

4539
export class ConditionalCompileConstStatement extends Statement {
1✔
4540
    constructor(options: {
4541
        hashConst?: Token;
4542
        assignment: AssignmentStatement;
4543
    }) {
4544
        super();
19✔
4545
        this.tokens = {
19✔
4546
            hashConst: options.hashConst
4547
        };
4548
        this.assignment = options.assignment;
19✔
4549
        this.location = util.createBoundingLocation(util.createBoundingLocationFromTokens(this.tokens), this.assignment);
19✔
4550
    }
4551

4552
    public readonly tokens: {
4553
        readonly hashConst?: Token;
4554
    };
4555

4556
    public readonly assignment: AssignmentStatement;
4557

4558
    public readonly kind = AstNodeKind.ConditionalCompileConstStatement;
19✔
4559

4560
    public readonly location: Location | undefined;
4561

4562
    transpile(state: BrsTranspileState) {
4563
        return [
3✔
4564
            state.transpileToken(this.tokens.hashConst, '#const'),
4565
            ' ',
4566
            state.transpileToken(this.assignment.tokens.name),
4567
            ' ',
4568
            state.transpileToken(this.assignment.tokens.equals, '='),
4569
            ' ',
4570
            ...this.assignment.value.transpile(state)
4571
        ];
4572

4573
    }
4574

4575
    walk(visitor: WalkVisitor, options: WalkOptions) {
4576
        // nothing to walk
4577
    }
4578

4579

4580
    get leadingTrivia(): Token[] {
4581
        return this.tokens.hashConst.leadingTrivia ?? [];
78!
4582
    }
4583

4584
    public clone() {
4585
        return this.finalizeClone(
1✔
4586
            new ConditionalCompileConstStatement({
4587
                hashConst: util.cloneToken(this.tokens.hashConst),
4588
                assignment: this.assignment?.clone()
3!
4589
            }),
4590
            ['assignment']
4591
        );
4592
    }
4593
}
4594

4595

4596
export class TypeStatement extends Statement {
1✔
4597
    constructor(options: {
4598
        type?: Token;
4599
        name: Token;
4600
        equals?: Token;
4601
        value: TypeExpression;
4602
    }
4603
    ) {
4604
        super();
26✔
4605
        this.tokens = {
26✔
4606
            type: options.type,
4607
            name: options.name,
4608
            equals: options.equals
4609
        };
4610
        this.value = options.value;
26✔
4611
        this.location = util.createBoundingLocation(
26✔
4612
            this.tokens.type,
4613
            this.tokens.name,
4614
            this.tokens.equals,
4615
            this.value
4616
        );
4617
    }
4618

4619
    public readonly tokens: {
4620
        readonly type?: Token;
4621
        readonly name: Token;
4622
        readonly equals?: Token;
4623
    };
4624

4625
    public readonly value: TypeExpression;
4626

4627
    public readonly kind = AstNodeKind.TypeStatement;
26✔
4628

4629
    public readonly location: Location;
4630

4631
    transpile(state: BrsTranspileState) {
4632
        //transpile to a comment just for debugging purposes
UNCOV
4633
        return [
×
4634
            state.transpileToken(this.tokens.type, 'type', true),
4635
            ' ',
4636
            state.transpileToken(this.tokens.name),
4637
            ' ',
4638
            state.transpileToken(this.tokens.equals, '='),
4639
            ' ',
4640
            this.value.transpile(state)
4641
        ];
4642
    }
4643

4644
    walk(visitor: WalkVisitor, options: WalkOptions) {
4645
        if (options.walkMode & InternalWalkMode.walkExpressions) {
160✔
4646
            walk(this, 'value', visitor, options);
139✔
4647
        }
4648
    }
4649

4650
    get leadingTrivia(): Token[] {
4651
        return this.tokens.type?.leadingTrivia ?? [];
73!
4652
    }
4653

4654
    getType(options: GetTypeOptions): BscType {
4655
        return this.value.getType(options);
22✔
4656
    }
4657

4658
    public clone() {
UNCOV
4659
        return this.finalizeClone(
×
4660
            new TypeStatement({
4661
                type: util.cloneToken(this.tokens.type),
4662
                name: util.cloneToken(this.tokens.name),
4663
                equals: util.cloneToken(this.tokens.equals),
4664
                value: this.value?.clone()
×
4665
            }),
4666
            ['value']
4667
        );
4668
    }
4669
}
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