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

rokucommunity / brighterscript / #12898

31 Jul 2024 11:29AM UTC coverage: 86.175% (-1.8%) from 87.933%
#12898

push

web-flow
Merge 1b2ddf4b9 into ccce446ec

10590 of 13082 branches covered (80.95%)

Branch coverage included in aggregate %.

6646 of 7254 new or added lines in 99 files covered. (91.62%)

85 existing lines in 18 files now uncovered.

12292 of 13471 relevant lines covered (91.25%)

26722.8 hits per line

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

85.14
/src/parser/Statement.ts
1
/* eslint-disable no-bitwise */
2
import type { Token, Identifier } from '../lexer/Token';
3
import { TokenKind } from '../lexer/TokenKind';
1✔
4
import type { DottedGetExpression, FunctionExpression, FunctionParameterExpression, LiteralExpression, TypeExpression, TypecastExpression } from './Expression';
5
import { CallExpression, VariableExpression } from './Expression';
1✔
6
import { util } from '../util';
1✔
7
import type { Location } from 'vscode-languageserver';
8
import type { BrsTranspileState } from './BrsTranspileState';
9
import { ParseMode } from './Parser';
1✔
10
import type { WalkVisitor, WalkOptions } from '../astUtils/visitors';
11
import { InternalWalkMode, walk, createVisitor, WalkMode, walkArray } from '../astUtils/visitors';
1✔
12
import { isCallExpression, isCatchStatement, isConditionalCompileStatement, isEnumMemberStatement, isExpression, isExpressionStatement, isFieldStatement, isForEachStatement, isForStatement, isFunctionExpression, isFunctionStatement, isIfStatement, isInterfaceFieldStatement, isInterfaceMethodStatement, isInvalidType, isLiteralExpression, isMethodStatement, isNamespaceStatement, isTryCatchStatement, isTypedefProvider, isUnaryExpression, isVoidType, isWhileStatement } from '../astUtils/reflection';
1✔
13
import { TypeChainEntry, type GetTypeOptions, type TranspileResult, type TypedefProvider } from '../interfaces';
1✔
14
import { createInvalidLiteral, createMethodStatement, createToken } from '../astUtils/creators';
1✔
15
import { DynamicType } from '../types/DynamicType';
1✔
16
import type { BscType } from '../types/BscType';
17
import { SymbolTable } from '../SymbolTable';
1✔
18
import type { Expression } from './AstNode';
19
import { AstNodeKind } from './AstNode';
1✔
20
import { Statement } from './AstNode';
1✔
21
import { ClassType } from '../types/ClassType';
1✔
22
import { EnumMemberType, EnumType } from '../types/EnumType';
1✔
23
import { NamespaceType } from '../types/NamespaceType';
1✔
24
import { InterfaceType } from '../types/InterfaceType';
1✔
25
import { VoidType } from '../types/VoidType';
1✔
26
import { TypedFunctionType } from '../types/TypedFunctionType';
1✔
27
import { ArrayType } from '../types/ArrayType';
1✔
28
import { SymbolTypeFlag } from '../SymbolTypeFlag';
1✔
29

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

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

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

51
/**
52
 * This is a top-level statement. Consider this the root of the AST
53
 */
54
export class Body extends Statement implements TypedefProvider {
1✔
55
    constructor(options?: {
56
        statements?: Statement[];
57
    }) {
58
        super();
6,561✔
59
        this.statements = options?.statements ?? [];
6,561!
60
    }
61

62
    public readonly statements: Statement[] = [];
6,561✔
63
    public readonly kind = AstNodeKind.Body;
6,561✔
64

65
    public readonly symbolTable = new SymbolTable('Body', () => this.parent?.getSymbolTable());
7,032✔
66

67
    public get location() {
68
        //this needs to be a getter because the body has its statements pushed to it after being constructed
69
        return util.createBoundingLocation(
684✔
70
            ...(this.statements ?? [])
2,052!
71
        );
72
    }
73

74
    transpile(state: BrsTranspileState) {
75
        let result = [] as TranspileResult;
625✔
76
        for (let i = 0; i < this.statements.length; i++) {
625✔
77
            let statement = this.statements[i];
1,380✔
78
            let previousStatement = this.statements[i - 1];
1,380✔
79
            let nextStatement = this.statements[i + 1];
1,380✔
80

81
            if (!previousStatement) {
1,380✔
82
                //this is the first statement. do nothing related to spacing and newlines
83

84
                //if comment is on same line as prior sibling
85
            } else if (util.hasLeadingComments(statement) && previousStatement && util.getLeadingComments(statement)?.[0]?.location?.range?.start.line === previousStatement.location?.range?.end.line) {
759!
86
                result.push(
8✔
87
                    ' '
88
                );
89
                //add double newline if this is a comment, and next is a function
90
            } else if (util.hasLeadingComments(statement) && nextStatement && isFunctionStatement(nextStatement)) {
751✔
91
                result.push(state.newline, state.newline);
305✔
92

93
                //add double newline if is function not preceeded by a comment
94
            } else if (isFunctionStatement(statement) && previousStatement && !util.hasLeadingComments(statement)) {
446✔
95
                result.push(state.newline, state.newline);
69✔
96
            } else {
97
                //separate statements by a single newline
98
                result.push(state.newline);
377✔
99
            }
100

101
            result.push(...statement.transpile(state));
1,380✔
102
        }
103
        return result;
625✔
104
    }
105

106
    getTypedef(state: BrsTranspileState): TranspileResult {
107
        let result = [] as TranspileResult;
41✔
108
        for (const statement of this.statements) {
41✔
109
            //if the current statement supports generating typedef, call it
110
            if (isTypedefProvider(statement)) {
75!
111
                result.push(
75✔
112
                    state.indent(),
113
                    ...statement.getTypedef(state),
114
                    state.newline
115
                );
116
            }
117
        }
118
        return result;
41✔
119
    }
120

121
    walk(visitor: WalkVisitor, options: WalkOptions) {
122
        if (options.walkMode & InternalWalkMode.walkStatements) {
13,439!
123
            walkArray(this.statements, visitor, options, this);
13,439✔
124
        }
125
    }
126
}
127

128
export class AssignmentStatement extends Statement {
1✔
129
    constructor(options: {
130
        name: Identifier;
131
        equals?: Token;
132
        value: Expression;
133
        as?: Token;
134
        typeExpression?: TypeExpression;
135
    }) {
136
        super();
1,381✔
137
        this.value = options.value;
1,381✔
138
        this.tokens = {
1,381✔
139
            equals: options.equals,
140
            name: options.name,
141
            as: options.as
142
        };
143
        this.typeExpression = options.typeExpression;
1,381✔
144
        this.location = util.createBoundingLocation(util.createBoundingLocationFromTokens(this.tokens), this.value);
1,381✔
145
    }
146

147
    public readonly tokens: {
148
        readonly equals?: Token;
149
        readonly name: Identifier;
150
        readonly as?: Token;
151
    };
152

153
    public readonly value: Expression;
154

155
    public readonly typeExpression?: TypeExpression;
156

157
    public readonly kind = AstNodeKind.AssignmentStatement;
1,381✔
158

159
    public readonly location: Location | undefined;
160

161
    transpile(state: BrsTranspileState) {
162
        return [
531✔
163
            state.transpileToken(this.tokens.name),
164
            ' ',
165
            state.transpileToken(this.tokens.equals ?? createToken(TokenKind.Equal)),
1,593!
166
            ' ',
167
            ...this.value.transpile(state)
168
        ];
169
    }
170

171
    walk(visitor: WalkVisitor, options: WalkOptions) {
172
        if (options.walkMode & InternalWalkMode.walkExpressions) {
5,380✔
173
            walk(this, 'typeExpression', visitor, options);
5,254✔
174
            walk(this, 'value', visitor, options);
5,254✔
175
        }
176
    }
177

178
    getType(options: GetTypeOptions) {
179
        const variableType = this.typeExpression?.getType({ ...options, typeChain: undefined }) ?? this.value.getType({ ...options, typeChain: undefined });
1,195✔
180

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

187
    get leadingTrivia(): Token[] {
188
        return this.tokens.name.leadingTrivia;
1,082✔
189
    }
190
}
191

192
export class AugmentedAssignmentStatement extends Statement {
1✔
193
    constructor(options: {
194
        item: Expression;
195
        operator: Token;
196
        value: Expression;
197
    }) {
198
        super();
78✔
199
        this.value = options.value;
78✔
200
        this.tokens = {
78✔
201
            operator: options.operator
202
        };
203
        this.item = options.item;
78✔
204
        this.value = options.value;
78✔
205
        this.location = util.createBoundingLocation(this.item, util.createBoundingLocationFromTokens(this.tokens), this.value);
78✔
206
    }
207

208
    public readonly tokens: {
209
        readonly operator?: Token;
210
    };
211

212
    public readonly item: Expression;
213

214
    public readonly value: Expression;
215

216
    public readonly kind = AstNodeKind.AugmentedAssignmentStatement;
78✔
217

218
    public readonly location: Location | undefined;
219

220
    transpile(state: BrsTranspileState) {
221
        return [
27✔
222
            this.item.transpile(state),
223
            ' ',
224
            state.transpileToken(this.tokens.operator),
225
            ' ',
226
            this.value.transpile(state)
227
        ];
228
    }
229

230
    walk(visitor: WalkVisitor, options: WalkOptions) {
231
        if (options.walkMode & InternalWalkMode.walkExpressions) {
330✔
232
            walk(this, 'item', visitor, options);
322✔
233
            walk(this, 'value', visitor, options);
322✔
234
        }
235
    }
236

237
    getType(options: GetTypeOptions) {
NEW
238
        const variableType = util.binaryOperatorResultType(this.item.getType(options), this.tokens.operator, this.value.getType(options));
×
239

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

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

248
    get leadingTrivia(): Token[] {
249
        return this.item.leadingTrivia;
54✔
250
    }
251
}
252

253
export class Block extends Statement {
1✔
254
    constructor(options: {
255
        statements: Statement[];
256
    }) {
257
        super();
5,920✔
258
        this.statements = options.statements;
5,920✔
259
    }
260

261
    public readonly statements: Statement[];
262

263
    public readonly kind = AstNodeKind.Block;
5,920✔
264

265
    get location(): Location {
266
        if (this.statements.length > 0) {
3,720✔
267
            return util.createBoundingLocation(...this.statements);
3,258✔
268
        }
269
        let lastBitBefore: Location;
270
        let firstBitAfter: Location;
271

272
        if (isFunctionExpression(this.parent)) {
462✔
273
            lastBitBefore = util.createBoundingLocation(
392✔
274
                this.parent.tokens.functionType,
275
                this.parent.tokens.leftParen,
276
                ...(this.parent.parameters ?? []),
1,176!
277
                this.parent.tokens.rightParen,
278
                this.parent.tokens.as,
279
                this.parent.returnTypeExpression
280
            );
281
            firstBitAfter = this.parent.tokens.endFunctionType?.location;
392!
282
        } else if (isIfStatement(this.parent)) {
70!
NEW
283
            if (this.parent.thenBranch === this) {
×
NEW
284
                lastBitBefore = util.createBoundingLocation(
×
285
                    this.parent.tokens.then,
286
                    this.parent.condition
287
                );
NEW
288
                firstBitAfter = util.createBoundingLocation(
×
289
                    this.parent.tokens.else,
290
                    this.parent.elseBranch,
291
                    this.parent.tokens.endIf
292
                );
NEW
293
            } else if (this.parent.elseBranch === this) {
×
NEW
294
                lastBitBefore = this.parent.tokens.else?.location;
×
NEW
295
                firstBitAfter = this.parent.tokens.endIf?.location;
×
296
            }
297
        } else if (isConditionalCompileStatement(this.parent)) {
70!
NEW
298
            if (this.parent.thenBranch === this) {
×
NEW
299
                lastBitBefore = util.createBoundingLocation(
×
300
                    this.parent.tokens.condition,
301
                    this.parent.tokens.not,
302
                    this.parent.tokens.hashIf
303
                );
NEW
304
                firstBitAfter = util.createBoundingLocation(
×
305
                    this.parent.tokens.hashElse,
306
                    this.parent.elseBranch,
307
                    this.parent.tokens.hashEndIf
308
                );
NEW
309
            } else if (this.parent.elseBranch === this) {
×
NEW
310
                lastBitBefore = this.parent.tokens.hashElse?.location;
×
NEW
311
                firstBitAfter = this.parent.tokens.hashEndIf?.location;
×
312
            }
313
        } else if (isForStatement(this.parent)) {
70✔
314
            lastBitBefore = util.createBoundingLocation(
2✔
315
                this.parent.increment,
316
                this.parent.tokens.step,
317
                this.parent.finalValue,
318
                this.parent.tokens.to,
319
                this.parent.counterDeclaration,
320
                this.parent.tokens.for
321
            );
322
            firstBitAfter = this.parent.tokens.endFor?.location;
2!
323
        } else if (isForEachStatement(this.parent)) {
68✔
324
            lastBitBefore = util.createBoundingLocation(
2✔
325
                this.parent.target,
326
                this.parent.tokens.in,
327
                this.parent.tokens.item,
328
                this.parent.tokens.forEach
329
            );
330
            firstBitAfter = this.parent.tokens.endFor?.location;
2!
331
        } else if (isWhileStatement(this.parent)) {
66!
NEW
332
            lastBitBefore = util.createBoundingLocation(
×
333
                this.parent.condition,
334
                this.parent.tokens.while
335
            );
NEW
336
            firstBitAfter = this.parent.tokens.endWhile?.location;
×
337
        } else if (isTryCatchStatement(this.parent)) {
66!
NEW
338
            lastBitBefore = util.createBoundingLocation(
×
339
                this.parent.tokens.try
340
            );
NEW
341
            firstBitAfter = util.createBoundingLocation(
×
342
                this.parent.tokens.endTry,
343
                this.parent.catchStatement
344
            );
345
        } else if (isCatchStatement(this.parent) && isTryCatchStatement(this.parent?.parent)) {
66!
NEW
346
            lastBitBefore = util.createBoundingLocation(
×
347
                this.parent.tokens.catch,
348
                this.parent.tokens.exceptionVariable
349
            );
NEW
350
            firstBitAfter = this.parent.parent.tokens.endTry?.location;
×
351
        }
352
        if (lastBitBefore?.range && firstBitAfter?.range) {
462!
353
            return util.createLocation(
372✔
354
                lastBitBefore.range.end.line,
355
                lastBitBefore.range.end.character,
356
                firstBitAfter.range.start.line,
357
                firstBitAfter.range.start.character,
358
                lastBitBefore.uri ?? firstBitAfter.uri
1,116!
359
            );
360
        }
361
    }
362

363
    transpile(state: BrsTranspileState) {
364
        state.blockDepth++;
3,805✔
365
        let results = [] as TranspileResult;
3,805✔
366
        for (let i = 0; i < this.statements.length; i++) {
3,805✔
367
            let previousStatement = this.statements[i - 1];
4,613✔
368
            let statement = this.statements[i];
4,613✔
369
            //is not a comment
370
            //if comment is on same line as parent
371
            if (util.isLeadingCommentOnSameLine(state.lineage[0]?.location, statement) ||
4,613!
372
                util.isLeadingCommentOnSameLine(previousStatement?.location, statement)
13,791✔
373
            ) {
374
                results.push(' ');
50✔
375

376
                //is not a comment
377
            } else {
378
                //add a newline and indent
379
                results.push(
4,563✔
380
                    state.newline,
381
                    state.indent()
382
                );
383
            }
384

385
            //push block onto parent list
386
            state.lineage.unshift(this);
4,613✔
387
            results.push(
4,613✔
388
                ...statement.transpile(state)
389
            );
390
            state.lineage.shift();
4,613✔
391
        }
392
        state.blockDepth--;
3,805✔
393
        return results;
3,805✔
394
    }
395

396
    public get leadingTrivia(): Token[] {
397
        return this.statements[0]?.leadingTrivia ?? [];
12!
398
    }
399

400
    walk(visitor: WalkVisitor, options: WalkOptions) {
401
        if (options.walkMode & InternalWalkMode.walkStatements) {
23,233✔
402
            walkArray(this.statements, visitor, options, this);
23,227✔
403
        }
404
    }
405

406
}
407

408
export class ExpressionStatement extends Statement {
1✔
409
    constructor(options: {
410
        expression: Expression;
411
    }) {
412
        super();
532✔
413
        this.expression = options.expression;
532✔
414
        this.location = this.expression.location;
532✔
415
    }
416
    public readonly expression: Expression;
417
    public readonly kind = AstNodeKind.ExpressionStatement;
532✔
418

419
    public readonly location: Location | undefined;
420

421
    transpile(state: BrsTranspileState) {
422
        return this.expression.transpile(state);
53✔
423
    }
424

425
    walk(visitor: WalkVisitor, options: WalkOptions) {
426
        if (options.walkMode & InternalWalkMode.walkExpressions) {
2,783✔
427
            walk(this, 'expression', visitor, options);
2,755✔
428
        }
429
    }
430

431
    get leadingTrivia(): Token[] {
432
        return this.expression.leadingTrivia;
110✔
433
    }
434
}
435

436

437
export class ExitForStatement extends Statement {
1✔
438
    constructor(options?: {
439
        exitFor?: Token;
440
    }) {
441
        super();
5✔
442
        this.tokens = {
5✔
443
            exitFor: options?.exitFor
15!
444
        };
445
        this.location = this.tokens.exitFor?.location;
5!
446
    }
447

448
    public readonly tokens: {
449
        readonly exitFor?: Token;
450
    };
451

452
    public readonly kind = AstNodeKind.ExitForStatement;
5✔
453

454
    public readonly location?: Location;
455

456
    transpile(state: BrsTranspileState) {
457
        return this.tokens.exitFor ? state.transpileToken(this.tokens.exitFor) : ['exit for'];
2!
458
    }
459

460
    walk(visitor: WalkVisitor, options: WalkOptions) {
461
        //nothing to walk
462
    }
463

464
    get leadingTrivia(): Token[] {
465
        return this.tokens.exitFor?.leadingTrivia;
8!
466
    }
467

468
}
469

470
export class ExitWhileStatement extends Statement {
1✔
471
    constructor(options?: {
472
        exitWhile?: Token;
473
    }) {
474
        super();
8✔
475
        this.tokens = {
8✔
476
            exitWhile: options?.exitWhile
24!
477
        };
478
        this.location = this.tokens.exitWhile?.location;
8!
479
    }
480

481
    public readonly tokens: {
482
        readonly exitWhile?: Token;
483
    };
484

485
    public readonly kind = AstNodeKind.ExitWhileStatement;
8✔
486

487
    public readonly location?: Location;
488

489
    transpile(state: BrsTranspileState) {
490
        return this.tokens.exitWhile ? state.transpileToken(this.tokens.exitWhile) : ['exit while'];
3!
491
    }
492

493
    walk(visitor: WalkVisitor, options: WalkOptions) {
494
        //nothing to walk
495
    }
496

497
    get leadingTrivia(): Token[] {
498
        return this.tokens.exitWhile?.leadingTrivia;
10!
499
    }
500
}
501

502
export class FunctionStatement extends Statement implements TypedefProvider {
1✔
503
    constructor(options: {
504
        name: Identifier;
505
        func: FunctionExpression;
506
    }) {
507
        super();
3,550✔
508
        this.tokens = {
3,550✔
509
            name: options.name
510
        };
511
        this.func = options.func;
3,550✔
512
        this.func.symbolTable.name += `: '${this.tokens.name?.text}'`;
3,550!
513
        this.func.functionStatement = this;
3,550✔
514

515
        this.location = this.func.location;
3,550✔
516
    }
517

518
    public readonly tokens: {
519
        readonly name: Identifier;
520
    };
521
    public readonly func: FunctionExpression;
522

523
    public readonly kind = AstNodeKind.FunctionStatement as AstNodeKind;
3,550✔
524

525
    public readonly location: Location | undefined;
526

527
    /**
528
     * Get the name of this expression based on the parse mode
529
     */
530
    public getName(parseMode: ParseMode) {
531
        const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
11,170✔
532
        if (namespace) {
11,170✔
533
            let delimiter = parseMode === ParseMode.BrighterScript ? '.' : '_';
2,693✔
534
            let namespaceName = namespace.getName(parseMode);
2,693✔
535
            return namespaceName + delimiter + this.tokens.name?.text;
2,693!
536
        } else {
537
            return this.tokens.name.text;
8,477✔
538
        }
539
    }
540

541
    public get leadingTrivia(): Token[] {
542
        return this.func.leadingTrivia;
2,545✔
543
    }
544

545
    transpile(state: BrsTranspileState) {
546
        //create a fake token using the full transpiled name
547
        let nameToken = {
1,204✔
548
            ...this.tokens.name,
549
            text: this.getName(ParseMode.BrightScript)
550
        };
551

552
        return this.func.transpile(state, nameToken);
1,204✔
553
    }
554

555
    getTypedef(state: BrsTranspileState) {
556
        let result: TranspileResult = [];
39✔
557
        for (let comment of util.getLeadingComments(this) ?? []) {
39!
558
            result.push(
154✔
559
                comment.text,
560
                state.newline,
561
                state.indent()
562
            );
563
        }
564
        for (let annotation of this.annotations ?? []) {
39✔
565
            result.push(
2✔
566
                ...annotation.getTypedef(state),
567
                state.newline,
568
                state.indent()
569
            );
570
        }
571

572
        result.push(
39✔
573
            ...this.func.getTypedef(state)
574
        );
575
        return result;
39✔
576
    }
577

578
    walk(visitor: WalkVisitor, options: WalkOptions) {
579
        if (options.walkMode & InternalWalkMode.walkExpressions) {
14,568✔
580
            walk(this, 'func', visitor, options);
12,913✔
581
        }
582
    }
583

584
    getType(options: GetTypeOptions) {
585
        const funcExprType = this.func.getType(options);
3,059✔
586
        funcExprType.setName(this.getName(ParseMode.BrighterScript));
3,059✔
587
        return funcExprType;
3,059✔
588
    }
589
}
590

591
export class IfStatement extends Statement {
1✔
592
    constructor(options: {
593
        if?: Token;
594
        then?: Token;
595
        else?: Token;
596
        endIf?: Token;
597

598
        condition: Expression;
599
        thenBranch: Block;
600
        elseBranch?: IfStatement | Block;
601
    }) {
602
        super();
1,824✔
603
        this.condition = options.condition;
1,824✔
604
        this.thenBranch = options.thenBranch;
1,824✔
605
        this.elseBranch = options.elseBranch;
1,824✔
606

607
        this.tokens = {
1,824✔
608
            if: options.if,
609
            then: options.then,
610
            else: options.else,
611
            endIf: options.endIf
612
        };
613

614
        this.location = util.createBoundingLocation(
1,824✔
615
            util.createBoundingLocationFromTokens(this.tokens),
616
            this.condition,
617
            this.thenBranch,
618
            this.elseBranch
619
        );
620
    }
621

622
    readonly tokens: {
623
        readonly if?: Token;
624
        readonly then?: Token;
625
        readonly else?: Token;
626
        readonly endIf?: Token;
627
    };
628
    public readonly condition: Expression;
629
    public readonly thenBranch: Block;
630
    public readonly elseBranch?: IfStatement | Block;
631

632
    public readonly kind = AstNodeKind.IfStatement;
1,824✔
633

634
    public readonly location: Location | undefined;
635

636
    get isInline() {
637
        const allLeadingTrivia = [
12✔
638
            ...this.thenBranch.leadingTrivia,
639
            ...this.thenBranch.statements.map(s => s.leadingTrivia).flat(),
16✔
640
            ...(this.tokens.else?.leadingTrivia ?? []),
72✔
641
            ...(this.tokens.endIf?.leadingTrivia ?? [])
72✔
642
        ];
643

644
        const hasNewline = allLeadingTrivia.find(t => t.kind === TokenKind.Newline);
29✔
645
        return !hasNewline;
12✔
646
    }
647

648
    transpile(state: BrsTranspileState) {
649
        let results = [] as TranspileResult;
1,878✔
650
        //if   (already indented by block)
651
        results.push(state.transpileToken(this.tokens.if ?? createToken(TokenKind.If)));
1,878!
652
        results.push(' ');
1,878✔
653
        //conditions
654
        results.push(...this.condition.transpile(state));
1,878✔
655
        //then
656
        if (this.tokens.then) {
1,878✔
657
            results.push(' ');
1,562✔
658
            results.push(
1,562✔
659
                state.transpileToken(this.tokens.then)
660
            );
661
        }
662
        state.lineage.unshift(this);
1,878✔
663

664
        //if statement body
665
        let thenNodes = this.thenBranch.transpile(state);
1,878✔
666
        state.lineage.shift();
1,878✔
667
        if (thenNodes.length > 0) {
1,878✔
668
            results.push(thenNodes);
1,867✔
669
        }
670
        //else branch
671
        if (this.elseBranch) {
1,878✔
672
            //else
673
            results.push(...state.transpileEndBlockToken(this.thenBranch, this.tokens.else, 'else'));
1,554✔
674

675
            if (isIfStatement(this.elseBranch)) {
1,554✔
676
                //chained elseif
677
                state.lineage.unshift(this.elseBranch);
934✔
678
                let body = this.elseBranch.transpile(state);
934✔
679
                state.lineage.shift();
934✔
680

681
                if (body.length > 0) {
934!
682
                    //zero or more spaces between the `else` and the `if`
683
                    results.push(this.elseBranch.tokens.if.leadingWhitespace!);
934✔
684
                    results.push(...body);
934✔
685

686
                    // stop here because chained if will transpile the rest
687
                    return results;
934✔
688
                } else {
689
                    results.push('\n');
×
690
                }
691

692
            } else {
693
                //else body
694
                state.lineage.unshift(this.tokens.else!);
620✔
695
                let body = this.elseBranch.transpile(state);
620✔
696
                state.lineage.shift();
620✔
697

698
                if (body.length > 0) {
620✔
699
                    results.push(...body);
618✔
700
                }
701
            }
702
        }
703

704
        //end if
705
        results.push(...state.transpileEndBlockToken(this.elseBranch ?? this.thenBranch, this.tokens.endIf, 'end if'));
944✔
706

707
        return results;
944✔
708
    }
709

710
    walk(visitor: WalkVisitor, options: WalkOptions) {
711
        if (options.walkMode & InternalWalkMode.walkExpressions) {
5,800✔
712
            walk(this, 'condition', visitor, options);
5,784✔
713
        }
714
        if (options.walkMode & InternalWalkMode.walkStatements) {
5,800✔
715
            walk(this, 'thenBranch', visitor, options);
5,798✔
716
        }
717
        if (this.elseBranch && options.walkMode & InternalWalkMode.walkStatements) {
5,800✔
718
            walk(this, 'elseBranch', visitor, options);
4,565✔
719
        }
720
    }
721

722
    get leadingTrivia(): Token[] {
723
        return this.tokens.if?.leadingTrivia ?? [];
1,893!
724
    }
725

726
    get endTrivia(): Token[] {
727
        return this.tokens.endIf?.leadingTrivia ?? [];
1!
728
    }
729

730
}
731

732
export class IncrementStatement extends Statement {
1✔
733
    constructor(options: {
734
        value: Expression;
735
        operator: Token;
736
    }) {
737
        super();
21✔
738
        this.value = options.value;
21✔
739
        this.tokens = {
21✔
740
            operator: options.operator
741
        };
742
        this.location = util.createBoundingLocation(
21✔
743
            this.value,
744
            this.tokens.operator
745
        );
746
    }
747

748
    public readonly value: Expression;
749
    public readonly tokens: {
750
        readonly operator: Token;
751
    };
752

753
    public readonly kind = AstNodeKind.IncrementStatement;
21✔
754

755
    public readonly location: Location | undefined;
756

757
    transpile(state: BrsTranspileState) {
758
        return [
6✔
759
            ...this.value.transpile(state),
760
            state.transpileToken(this.tokens.operator)
761
        ];
762
    }
763

764
    walk(visitor: WalkVisitor, options: WalkOptions) {
765
        if (options.walkMode & InternalWalkMode.walkExpressions) {
73✔
766
            walk(this, 'value', visitor, options);
72✔
767
        }
768
    }
769

770
    get leadingTrivia(): Token[] {
771
        return this.value?.leadingTrivia ?? [];
15!
772
    }
773
}
774

775
/** Used to indent the current `print` position to the next 16-character-width output zone. */
776
export interface PrintSeparatorTab extends Token {
777
    kind: TokenKind.Comma;
778
}
779

780
/** Used to insert a single whitespace character at the current `print` position. */
781
export interface PrintSeparatorSpace extends Token {
782
    kind: TokenKind.Semicolon;
783
}
784

785
/**
786
 * Represents a `print` statement within BrightScript.
787
 */
788
export class PrintStatement extends Statement {
1✔
789
    /**
790
     * Creates a new internal representation of a BrightScript `print` statement.
791
     * @param options the options for this statement
792
     * @param options.print a print token
793
     * @param options.expressions an array of expressions or `PrintSeparator`s to be evaluated and printed.
794
     */
795
    constructor(options: {
796
        print: Token;
797
        expressions: Array<Expression | PrintSeparatorTab | PrintSeparatorSpace>;
798
    }) {
799
        super();
1,064✔
800
        this.tokens = {
1,064✔
801
            print: options.print
802
        };
803
        this.expressions = options.expressions;
1,064✔
804
        this.location = util.createBoundingLocation(
1,064✔
805
            this.tokens.print,
806
            ...(this.expressions ?? [])
3,192!
807
        );
808
    }
809
    public readonly tokens: {
810
        readonly print: Token;
811
    };
812
    public readonly expressions: Array<Expression | PrintSeparatorTab | PrintSeparatorSpace>;
813
    public readonly kind = AstNodeKind.PrintStatement;
1,064✔
814

815
    public readonly location: Location | undefined;
816

817
    transpile(state: BrsTranspileState) {
818
        let result = [
217✔
819
            state.transpileToken(this.tokens.print),
820
            ' '
821
        ] as TranspileResult;
822
        for (let i = 0; i < this.expressions.length; i++) {
217✔
823
            const expressionOrSeparator: any = this.expressions[i];
281✔
824
            if (expressionOrSeparator.transpile) {
281✔
825
                result.push(...(expressionOrSeparator as ExpressionStatement).transpile(state));
255✔
826
            } else {
827
                result.push(
26✔
828
                    state.tokenToSourceNode(expressionOrSeparator)
829
                );
830
            }
831
            //if there's an expression after us, add a space
832
            if ((this.expressions[i + 1] as any)?.transpile) {
281✔
833
                result.push(' ');
38✔
834
            }
835
        }
836
        return result;
217✔
837
    }
838

839
    walk(visitor: WalkVisitor, options: WalkOptions) {
840
        if (options.walkMode & InternalWalkMode.walkExpressions) {
4,960✔
841
            //sometimes we have semicolon Tokens in the expressions list (should probably fix that...), so only walk the actual expressions
842
            walkArray(this.expressions, visitor, options, this, (item) => isExpression(item as any));
5,427✔
843
        }
844
    }
845

846
    get leadingTrivia(): Token[] {
847
        return this.tokens.print?.leadingTrivia ?? [];
450!
848
    }
849
}
850

851
export class DimStatement extends Statement {
1✔
852
    constructor(options: {
853
        dim?: Token;
854
        name: Identifier;
855
        openingSquare?: Token;
856
        dimensions: Expression[];
857
        closingSquare?: Token;
858
    }) {
859
        super();
40✔
860
        this.tokens = {
40✔
861
            dim: options?.dim,
120!
862
            name: options.name,
863
            openingSquare: options.openingSquare,
864
            closingSquare: options.closingSquare
865
        };
866
        this.dimensions = options.dimensions;
40✔
867
        this.location = util.createBoundingLocation(
40✔
868
            options.dim,
869
            options.name,
870
            options.openingSquare,
871
            ...(this.dimensions ?? []),
120!
872
            options.closingSquare
873
        );
874
    }
875

876
    public readonly tokens: {
877
        readonly dim?: Token;
878
        readonly name: Identifier;
879
        readonly openingSquare?: Token;
880
        readonly closingSquare?: Token;
881
    };
882
    public readonly dimensions: Expression[];
883

884
    public readonly kind = AstNodeKind.DimStatement;
40✔
885

886
    public readonly location: Location | undefined;
887

888
    public transpile(state: BrsTranspileState) {
889
        let result: TranspileResult = [
15✔
890
            state.transpileToken(this.tokens.dim, 'dim'),
891
            ' ',
892
            state.transpileToken(this.tokens.name),
893
            state.transpileToken(this.tokens.openingSquare, '[')
894
        ];
895
        for (let i = 0; i < this.dimensions.length; i++) {
15✔
896
            if (i > 0) {
32✔
897
                result.push(', ');
17✔
898
            }
899
            result.push(
32✔
900
                ...this.dimensions![i].transpile(state)
901
            );
902
        }
903
        result.push(state.transpileToken(this.tokens.closingSquare, ']'));
15✔
904
        return result;
15✔
905
    }
906

907
    public walk(visitor: WalkVisitor, options: WalkOptions) {
908
        if (this.dimensions?.length !== undefined && this.dimensions?.length > 0 && options.walkMode & InternalWalkMode.walkExpressions) {
131!
909
            walkArray(this.dimensions, visitor, options, this);
116✔
910

911
        }
912
    }
913

914
    public getType(options: GetTypeOptions): BscType {
915
        const numDimensions = this.dimensions?.length ?? 1;
18!
916
        let type = new ArrayType();
18✔
917
        for (let i = 0; i < numDimensions - 1; i++) {
18✔
918
            type = new ArrayType(type);
17✔
919
        }
920
        return type;
18✔
921
    }
922

923
    get leadingTrivia(): Token[] {
924
        return this.tokens.dim?.leadingTrivia ?? [];
30!
925
    }
926
}
927

928
export class GotoStatement extends Statement {
1✔
929
    constructor(options: {
930
        goto?: Token;
931
        label: Token;
932
    }) {
933
        super();
11✔
934
        this.tokens = {
11✔
935
            goto: options.goto,
936
            label: options.label
937
        };
938
        this.location = util.createBoundingLocation(
11✔
939
            this.tokens.goto,
940
            this.tokens.label
941
        );
942
    }
943

944
    public readonly tokens: {
945
        readonly goto?: Token;
946
        readonly label: Token;
947
    };
948

949
    public readonly kind = AstNodeKind.GotoStatement;
11✔
950

951
    public readonly location: Location | undefined;
952

953
    transpile(state: BrsTranspileState) {
954
        return [
2✔
955
            state.transpileToken(this.tokens.goto, 'goto'),
956
            ' ',
957
            state.transpileToken(this.tokens.label)
958
        ];
959
    }
960

961
    walk(visitor: WalkVisitor, options: WalkOptions) {
962
        //nothing to walk
963
    }
964

965
    get leadingTrivia(): Token[] {
966
        return this.tokens.goto?.leadingTrivia ?? [];
8!
967
    }
968
}
969

970
export class LabelStatement extends Statement {
1✔
971
    constructor(options: {
972
        name: Token;
973
        colon?: Token;
974
    }) {
975
        super();
11✔
976
        this.tokens = {
11✔
977
            name: options.name,
978
            colon: options.colon
979
        };
980
        this.location = util.createBoundingLocation(
11✔
981
            this.tokens.name,
982
            this.tokens.colon
983
        );
984
    }
985
    public readonly tokens: {
986
        readonly name: Token;
987
        readonly colon: Token;
988
    };
989
    public readonly kind = AstNodeKind.LabelStatement;
11✔
990

991
    public readonly location: Location | undefined;
992

993
    public get leadingTrivia(): Token[] {
994
        return this.tokens.name.leadingTrivia;
8✔
995
    }
996

997
    transpile(state: BrsTranspileState) {
998
        return [
2✔
999
            state.transpileToken(this.tokens.name),
1000
            state.transpileToken(this.tokens.colon, ':')
1001

1002
        ];
1003
    }
1004

1005
    walk(visitor: WalkVisitor, options: WalkOptions) {
1006
        //nothing to walk
1007
    }
1008
}
1009

1010
export class ReturnStatement extends Statement {
1✔
1011
    constructor(options?: {
1012
        return?: Token;
1013
        value?: Expression;
1014
    }) {
1015
        super();
2,792✔
1016
        this.tokens = {
2,792✔
1017
            return: options?.return
8,376!
1018
        };
1019
        this.value = options?.value;
2,792!
1020
        this.location = util.createBoundingLocation(
2,792✔
1021
            this.tokens.return,
1022
            this.value
1023
        );
1024
    }
1025

1026
    public readonly tokens: {
1027
        readonly return?: Token;
1028
    };
1029
    public readonly value?: Expression;
1030
    public readonly kind = AstNodeKind.ReturnStatement;
2,792✔
1031

1032
    public readonly location: Location | undefined;
1033

1034
    transpile(state: BrsTranspileState) {
1035
        let result = [] as TranspileResult;
2,764✔
1036
        result.push(
2,764✔
1037
            state.transpileToken(this.tokens.return, 'return')
1038
        );
1039
        if (this.value) {
2,764✔
1040
            result.push(' ');
2,763✔
1041
            result.push(...this.value.transpile(state));
2,763✔
1042
        }
1043
        return result;
2,764✔
1044
    }
1045

1046
    walk(visitor: WalkVisitor, options: WalkOptions) {
1047
        if (options.walkMode & InternalWalkMode.walkExpressions) {
9,442✔
1048
            walk(this, 'value', visitor, options);
9,424✔
1049
        }
1050
    }
1051

1052
    get leadingTrivia(): Token[] {
1053
        return this.tokens.return?.leadingTrivia ?? [];
5,540!
1054
    }
1055
}
1056

1057
export class EndStatement extends Statement {
1✔
1058
    constructor(options?: {
1059
        end?: Token;
1060
    }) {
1061
        super();
9✔
1062
        this.tokens = {
9✔
1063
            end: options?.end
27!
1064
        };
1065
        this.location = this.tokens.end?.location;
9!
1066
    }
1067
    public readonly tokens: {
1068
        readonly end?: Token;
1069
    };
1070
    public readonly kind = AstNodeKind.EndStatement;
9✔
1071

1072
    public readonly location: Location;
1073

1074
    transpile(state: BrsTranspileState) {
1075
        return [
2✔
1076
            state.transpileToken(this.tokens.end, 'end')
1077
        ];
1078
    }
1079

1080
    walk(visitor: WalkVisitor, options: WalkOptions) {
1081
        //nothing to walk
1082
    }
1083

1084
    get leadingTrivia(): Token[] {
1085
        return this.tokens.end?.leadingTrivia ?? [];
8!
1086
    }
1087
}
1088

1089
export class StopStatement extends Statement {
1✔
1090
    constructor(options?: {
1091
        stop?: Token;
1092
    }) {
1093
        super();
17✔
1094
        this.tokens = { stop: options?.stop };
17!
1095
        this.location = this.tokens?.stop?.location;
17!
1096
    }
1097
    public readonly tokens: {
1098
        readonly stop?: Token;
1099
    };
1100

1101
    public readonly kind = AstNodeKind.StopStatement;
17✔
1102

1103
    public readonly location: Location;
1104

1105
    transpile(state: BrsTranspileState) {
1106
        return [
2✔
1107
            state.transpileToken(this.tokens.stop, 'stop')
1108
        ];
1109
    }
1110

1111
    walk(visitor: WalkVisitor, options: WalkOptions) {
1112
        //nothing to walk
1113
    }
1114

1115
    get leadingTrivia(): Token[] {
1116
        return this.tokens.stop?.leadingTrivia ?? [];
8!
1117
    }
1118
}
1119

1120
export class ForStatement extends Statement {
1✔
1121
    constructor(options: {
1122
        for?: Token;
1123
        counterDeclaration: AssignmentStatement;
1124
        to?: Token;
1125
        finalValue: Expression;
1126
        body: Block;
1127
        endFor?: Token;
1128
        step?: Token;
1129
        increment?: Expression;
1130
    }) {
1131
        super();
31✔
1132
        this.tokens = {
31✔
1133
            for: options.for,
1134
            to: options.to,
1135
            endFor: options.endFor,
1136
            step: options.step
1137
        };
1138
        this.counterDeclaration = options.counterDeclaration;
31✔
1139
        this.finalValue = options.finalValue;
31✔
1140
        this.body = options.body;
31✔
1141
        this.increment = options.increment;
31✔
1142

1143
        this.location = util.createBoundingLocation(
31✔
1144
            this.tokens.for,
1145
            this.counterDeclaration,
1146
            this.tokens.to,
1147
            this.finalValue,
1148
            this.tokens.step,
1149
            this.increment,
1150
            this.body,
1151
            this.tokens.endFor
1152
        );
1153
    }
1154

1155
    public readonly tokens: {
1156
        readonly for?: Token;
1157
        readonly to?: Token;
1158
        readonly endFor?: Token;
1159
        readonly step?: Token;
1160
    };
1161

1162
    public readonly counterDeclaration: AssignmentStatement;
1163
    public readonly finalValue: Expression;
1164
    public readonly body: Block;
1165
    public readonly increment?: Expression;
1166

1167
    public readonly kind = AstNodeKind.ForStatement;
31✔
1168

1169
    public readonly location: Location | undefined;
1170

1171
    transpile(state: BrsTranspileState) {
1172
        let result = [] as TranspileResult;
9✔
1173
        //for
1174
        result.push(
9✔
1175
            state.transpileToken(this.tokens.for, 'for'),
1176
            ' '
1177
        );
1178
        //i=1
1179
        result.push(
9✔
1180
            ...this.counterDeclaration.transpile(state),
1181
            ' '
1182
        );
1183
        //to
1184
        result.push(
9✔
1185
            state.transpileToken(this.tokens.to, 'to'),
1186
            ' '
1187
        );
1188
        //final value
1189
        result.push(this.finalValue.transpile(state));
9✔
1190
        //step
1191
        if (this.increment) {
9✔
1192
            result.push(
4✔
1193
                ' ',
1194
                state.transpileToken(this.tokens.step, 'step'),
1195
                ' ',
1196
                this.increment!.transpile(state)
1197
            );
1198
        }
1199
        //loop body
1200
        state.lineage.unshift(this);
9✔
1201
        result.push(...this.body.transpile(state));
9✔
1202
        state.lineage.shift();
9✔
1203

1204
        //end for
1205
        result.push(...state.transpileEndBlockToken(this.body, this.tokens.endFor, 'end for'));
9✔
1206

1207
        return result;
9✔
1208
    }
1209

1210
    walk(visitor: WalkVisitor, options: WalkOptions) {
1211
        if (options.walkMode & InternalWalkMode.walkStatements) {
122✔
1212
            walk(this, 'counterDeclaration', visitor, options);
121✔
1213
        }
1214
        if (options.walkMode & InternalWalkMode.walkExpressions) {
122✔
1215
            walk(this, 'finalValue', visitor, options);
118✔
1216
            walk(this, 'increment', visitor, options);
118✔
1217
        }
1218
        if (options.walkMode & InternalWalkMode.walkStatements) {
122✔
1219
            walk(this, 'body', visitor, options);
121✔
1220
        }
1221
    }
1222

1223
    get leadingTrivia(): Token[] {
1224
        return this.tokens.for?.leadingTrivia ?? [];
22!
1225
    }
1226

1227
    public get endTrivia(): Token[] {
NEW
1228
        return this.tokens.endFor?.leadingTrivia ?? [];
×
1229
    }
1230
}
1231

1232
export class ForEachStatement extends Statement {
1✔
1233
    constructor(options: {
1234
        forEach?: Token;
1235
        item: Token;
1236
        in?: Token;
1237
        target: Expression;
1238
        body: Block;
1239
        endFor?: Token;
1240
    }) {
1241
        super();
34✔
1242
        this.tokens = {
34✔
1243
            forEach: options.forEach,
1244
            item: options.item,
1245
            in: options.in,
1246
            endFor: options.endFor
1247
        };
1248
        this.body = options.body;
34✔
1249
        this.target = options.target;
34✔
1250

1251
        this.location = util.createBoundingLocation(
34✔
1252
            this.tokens.forEach,
1253
            this.tokens.item,
1254
            this.tokens.in,
1255
            this.target,
1256
            this.body,
1257
            this.tokens.endFor
1258
        );
1259
    }
1260

1261
    public readonly tokens: {
1262
        readonly forEach?: Token;
1263
        readonly item: Token;
1264
        readonly in?: Token;
1265
        readonly endFor?: Token;
1266
    };
1267
    public readonly body: Block;
1268
    public readonly target: Expression;
1269

1270
    public readonly kind = AstNodeKind.ForEachStatement;
34✔
1271

1272
    public readonly location: Location | undefined;
1273

1274
    transpile(state: BrsTranspileState) {
1275
        let result = [] as TranspileResult;
5✔
1276
        //for each
1277
        result.push(
5✔
1278
            state.transpileToken(this.tokens.forEach, 'for each'),
1279
            ' '
1280
        );
1281
        //item
1282
        result.push(
5✔
1283
            state.transpileToken(this.tokens.item),
1284
            ' '
1285
        );
1286
        //in
1287
        result.push(
5✔
1288
            state.transpileToken(this.tokens.in, 'in'),
1289
            ' '
1290
        );
1291
        //target
1292
        result.push(...this.target.transpile(state));
5✔
1293
        //body
1294
        state.lineage.unshift(this);
5✔
1295
        result.push(...this.body.transpile(state));
5✔
1296
        state.lineage.shift();
5✔
1297

1298
        //end for
1299
        result.push(...state.transpileEndBlockToken(this.body, this.tokens.endFor, 'end for'));
5✔
1300

1301
        return result;
5✔
1302
    }
1303

1304
    walk(visitor: WalkVisitor, options: WalkOptions) {
1305
        if (options.walkMode & InternalWalkMode.walkExpressions) {
161✔
1306
            walk(this, 'target', visitor, options);
154✔
1307
        }
1308
        if (options.walkMode & InternalWalkMode.walkStatements) {
161✔
1309
            walk(this, 'body', visitor, options);
160✔
1310
        }
1311
    }
1312

1313
    getType(options: GetTypeOptions): BscType {
1314
        return this.getSymbolTable().getSymbolType(this.tokens.item.text, options);
24✔
1315
    }
1316

1317
    get leadingTrivia(): Token[] {
1318
        return this.tokens.forEach?.leadingTrivia ?? [];
16!
1319
    }
1320

1321
    public get endTrivia(): Token[] {
1322
        return this.tokens.endFor?.leadingTrivia ?? [];
1!
1323
    }
1324
}
1325

1326
export class WhileStatement extends Statement {
1✔
1327
    constructor(options: {
1328
        while?: Token;
1329
        endWhile?: Token;
1330
        condition: Expression;
1331
        body: Block;
1332
    }) {
1333
        super();
23✔
1334
        this.tokens = {
23✔
1335
            while: options.while,
1336
            endWhile: options.endWhile
1337
        };
1338
        this.body = options.body;
23✔
1339
        this.condition = options.condition;
23✔
1340
        this.location = util.createBoundingLocation(
23✔
1341
            this.tokens.while,
1342
            this.condition,
1343
            this.body,
1344
            this.tokens.endWhile
1345
        );
1346
    }
1347

1348
    public readonly tokens: {
1349
        readonly while?: Token;
1350
        readonly endWhile?: Token;
1351
    };
1352
    public readonly condition: Expression;
1353
    public readonly body: Block;
1354

1355
    public readonly kind = AstNodeKind.WhileStatement;
23✔
1356

1357
    public readonly location: Location | undefined;
1358

1359
    transpile(state: BrsTranspileState) {
1360
        let result = [] as TranspileResult;
4✔
1361
        //while
1362
        result.push(
4✔
1363
            state.transpileToken(this.tokens.while, 'while'),
1364
            ' '
1365
        );
1366
        //condition
1367
        result.push(
4✔
1368
            ...this.condition.transpile(state)
1369
        );
1370
        state.lineage.unshift(this);
4✔
1371
        //body
1372
        result.push(...this.body.transpile(state));
4✔
1373
        state.lineage.shift();
4✔
1374

1375
        //end while
1376
        result.push(...state.transpileEndBlockToken(this.body, this.tokens.endWhile, 'end while'));
4✔
1377

1378
        return result;
4✔
1379
    }
1380

1381
    walk(visitor: WalkVisitor, options: WalkOptions) {
1382
        if (options.walkMode & InternalWalkMode.walkExpressions) {
84✔
1383
            walk(this, 'condition', visitor, options);
81✔
1384
        }
1385
        if (options.walkMode & InternalWalkMode.walkStatements) {
84✔
1386
            walk(this, 'body', visitor, options);
83✔
1387
        }
1388
    }
1389

1390
    get leadingTrivia(): Token[] {
1391
        return this.tokens.while?.leadingTrivia ?? [];
12!
1392
    }
1393

1394
    public get endTrivia(): Token[] {
1395
        return this.tokens.endWhile?.leadingTrivia ?? [];
1!
1396
    }
1397
}
1398

1399
export class DottedSetStatement extends Statement {
1✔
1400
    constructor(options: {
1401
        obj: Expression;
1402
        name: Identifier;
1403
        value: Expression;
1404
        dot?: Token;
1405
        equals?: Token;
1406
    }) {
1407
        super();
286✔
1408
        this.tokens = {
286✔
1409
            name: options.name,
1410
            dot: options.dot,
1411
            equals: options.equals
1412
        };
1413
        this.obj = options.obj;
286✔
1414
        this.value = options.value;
286✔
1415
        this.location = util.createBoundingLocation(
286✔
1416
            this.obj,
1417
            this.tokens.dot,
1418
            this.tokens.name,
1419
            this.value
1420
        );
1421
    }
1422
    public readonly tokens: {
1423
        readonly name: Identifier;
1424
        readonly equals?: Token;
1425
        readonly dot?: Token;
1426
    };
1427

1428
    public readonly obj: Expression;
1429
    public readonly value: Expression;
1430

1431
    public readonly kind = AstNodeKind.DottedSetStatement;
286✔
1432

1433
    public readonly location: Location | undefined;
1434

1435
    transpile(state: BrsTranspileState) {
1436
        //if the value is a compound assignment, don't add the obj, dot, name, or operator...the expression will handle that
1437
        return [
5✔
1438
            //object
1439
            ...this.obj.transpile(state),
1440
            this.tokens.dot ? state.tokenToSourceNode(this.tokens.dot) : '.',
5!
1441
            //name
1442
            state.transpileToken(this.tokens.name),
1443
            ' ',
1444
            state.transpileToken(this.tokens.equals, '='),
1445
            ' ',
1446
            //right-hand-side of assignment
1447
            ...this.value.transpile(state)
1448
        ];
1449

1450
    }
1451

1452
    walk(visitor: WalkVisitor, options: WalkOptions) {
1453
        if (options.walkMode & InternalWalkMode.walkExpressions) {
687✔
1454
            walk(this, 'obj', visitor, options);
686✔
1455
            walk(this, 'value', visitor, options);
686✔
1456
        }
1457
    }
1458

1459
    getType(options: GetTypeOptions) {
1460
        const objType = this.obj?.getType(options);
74!
1461
        const result = objType?.getMemberType(this.tokens.name?.text, options);
74!
1462
        options.typeChain?.push(new TypeChainEntry({
74!
1463
            name: this.tokens.name?.text,
222!
1464
            type: result, data: options.data,
1465
            location: this.tokens.name?.location,
222!
1466
            astNode: this
1467
        }));
1468
        return result;
74✔
1469
    }
1470

1471
    get leadingTrivia(): Token[] {
1472
        return this.obj.leadingTrivia;
14✔
1473
    }
1474
}
1475

1476
export class IndexedSetStatement extends Statement {
1✔
1477
    constructor(options: {
1478
        obj: Expression;
1479
        indexes: Expression[];
1480
        value: Expression;
1481
        openingSquare?: Token;
1482
        closingSquare?: Token;
1483
        equals?: Token;
1484
    }) {
1485
        super();
25✔
1486
        this.tokens = {
25✔
1487
            openingSquare: options.openingSquare,
1488
            closingSquare: options.closingSquare,
1489
            equals: options.equals
1490
        };
1491
        this.obj = options.obj;
25✔
1492
        this.indexes = options.indexes;
25✔
1493
        this.value = options.value;
25✔
1494
        this.location = util.createBoundingLocation(
25✔
1495
            this.obj,
1496
            this.tokens.openingSquare,
1497
            ...this.indexes,
1498
            this.tokens.closingSquare,
1499
            this.value
1500
        );
1501
    }
1502

1503
    public readonly tokens: {
1504
        readonly openingSquare?: Token;
1505
        readonly closingSquare?: Token;
1506
        readonly equals?: Token;
1507
    };
1508
    public readonly obj: Expression;
1509
    public readonly indexes: Expression[];
1510
    public readonly value: Expression;
1511

1512
    public readonly kind = AstNodeKind.IndexedSetStatement;
25✔
1513

1514
    public readonly location: Location | undefined;
1515

1516
    transpile(state: BrsTranspileState) {
1517
        const result = [];
10✔
1518
        result.push(
10✔
1519
            //obj
1520
            ...this.obj.transpile(state),
1521
            //   [
1522
            state.transpileToken(this.tokens.openingSquare, '[')
1523
        );
1524
        for (let i = 0; i < this.indexes.length; i++) {
10✔
1525
            //add comma between indexes
1526
            if (i > 0) {
11✔
1527
                result.push(', ');
1✔
1528
            }
1529
            let index = this.indexes[i];
11✔
1530
            result.push(
11✔
1531
                ...(index?.transpile(state) ?? [])
66!
1532
            );
1533
        }
1534
        result.push(
10✔
1535
            state.transpileToken(this.tokens.closingSquare, ']'),
1536
            ' ',
1537
            state.transpileToken(this.tokens.equals, '='),
1538
            ' ',
1539
            ...this.value.transpile(state)
1540
        );
1541
        return result;
10✔
1542

1543
    }
1544

1545
    walk(visitor: WalkVisitor, options: WalkOptions) {
1546
        if (options.walkMode & InternalWalkMode.walkExpressions) {
111✔
1547
            walk(this, 'obj', visitor, options);
110✔
1548
            walkArray(this.indexes, visitor, options, this);
110✔
1549
            walk(this, 'value', visitor, options);
110✔
1550
        }
1551
    }
1552

1553
    get leadingTrivia(): Token[] {
1554
        return this.obj.leadingTrivia;
23✔
1555
    }
1556
}
1557

1558
export class LibraryStatement extends Statement implements TypedefProvider {
1✔
1559
    constructor(options: {
1560
        library: Token;
1561
        filePath?: Token;
1562
    }) {
1563
        super();
14✔
1564
        this.tokens = {
14✔
1565
            library: options.library,
1566
            filePath: options.filePath
1567
        };
1568
        this.location = util.createBoundingLocation(
14✔
1569
            this.tokens.library,
1570
            this.tokens.filePath
1571
        );
1572
    }
1573
    public readonly tokens: {
1574
        readonly library: Token;
1575
        readonly filePath?: Token;
1576
    };
1577

1578
    public readonly kind = AstNodeKind.LibraryStatement;
14✔
1579

1580
    public readonly location: Location | undefined;
1581

1582
    transpile(state: BrsTranspileState) {
1583
        let result = [] as TranspileResult;
2✔
1584
        result.push(
2✔
1585
            state.transpileToken(this.tokens.library)
1586
        );
1587
        //there will be a parse error if file path is missing, but let's prevent a runtime error just in case
1588
        if (this.tokens.filePath) {
2!
1589
            result.push(
2✔
1590
                ' ',
1591
                state.transpileToken(this.tokens.filePath)
1592
            );
1593
        }
1594
        return result;
2✔
1595
    }
1596

1597
    getTypedef(state: BrsTranspileState) {
1598
        return this.transpile(state);
×
1599
    }
1600

1601
    walk(visitor: WalkVisitor, options: WalkOptions) {
1602
        //nothing to walk
1603
    }
1604

1605
    get leadingTrivia(): Token[] {
1606
        return this.tokens.library?.leadingTrivia ?? [];
4!
1607
    }
1608
}
1609

1610
export class NamespaceStatement extends Statement implements TypedefProvider {
1✔
1611
    constructor(options: {
1612
        namespace?: Token;
1613
        nameExpression: VariableExpression | DottedGetExpression;
1614
        body: Body;
1615
        endNamespace?: Token;
1616
    }) {
1617
        super();
582✔
1618
        this.tokens = {
582✔
1619
            namespace: options.namespace,
1620
            endNamespace: options.endNamespace
1621
        };
1622
        this.nameExpression = options.nameExpression;
582✔
1623
        this.body = options.body;
582✔
1624
        this.name = this.getName(ParseMode.BrighterScript);
582✔
1625
        this.symbolTable = new SymbolTable(`NamespaceStatement: '${this.name}'`, () => this.parent?.getSymbolTable());
3,861!
1626
    }
1627

1628
    public readonly tokens: {
1629
        readonly namespace?: Token;
1630
        readonly endNamespace?: Token;
1631
    };
1632

1633
    public readonly nameExpression: VariableExpression | DottedGetExpression;
1634
    public readonly body: Body;
1635

1636
    public readonly kind = AstNodeKind.NamespaceStatement;
582✔
1637

1638
    /**
1639
     * The string name for this namespace
1640
     */
1641
    public name: string;
1642

1643
    public get location() {
1644
        return this.cacheLocation();
322✔
1645
    }
1646
    private _location: Location | undefined;
1647

1648
    public cacheLocation() {
1649
        if (!this._location) {
902✔
1650
            this._location = util.createBoundingLocation(
582✔
1651
                this.tokens.namespace,
1652
                this.nameExpression,
1653
                this.body,
1654
                this.tokens.endNamespace
1655
            );
1656
        }
1657
        return this._location;
902✔
1658
    }
1659

1660
    public getName(parseMode: ParseMode) {
1661
        const sep = parseMode === ParseMode.BrighterScript ? '.' : '_';
5,974✔
1662
        let name = util.getAllDottedGetPartsAsString(this.nameExpression, parseMode);
5,974✔
1663
        if ((this.parent as Body)?.parent?.kind === AstNodeKind.NamespaceStatement) {
5,974✔
1664
            name = (this.parent.parent as NamespaceStatement).getName(parseMode) + sep + name;
234✔
1665
        }
1666
        return name;
5,974✔
1667
    }
1668

1669
    public get leadingTrivia(): Token[] {
1670
        return this.tokens.namespace?.leadingTrivia;
61!
1671
    }
1672

1673
    public get endTrivia(): Token[] {
NEW
1674
        return this.tokens.endNamespace?.leadingTrivia;
×
1675
    }
1676

1677
    public getNameParts() {
1678
        let parts = util.getAllDottedGetParts(this.nameExpression);
981✔
1679

1680
        if ((this.parent as Body)?.parent?.kind === AstNodeKind.NamespaceStatement) {
981!
1681
            parts = (this.parent.parent as NamespaceStatement).getNameParts().concat(parts);
55✔
1682
        }
1683
        return parts;
981✔
1684
    }
1685

1686
    transpile(state: BrsTranspileState) {
1687
        //namespaces don't actually have any real content, so just transpile their bodies
1688
        return [
40✔
1689
            state.transpileLeadingComments(this.tokens.namespace),
1690
            this.body.transpile(state),
1691
            state.transpileLeadingComments(this.tokens.endNamespace)
1692
        ];
1693
    }
1694

1695
    getTypedef(state: BrsTranspileState) {
1696
        let result: TranspileResult = [];
7✔
1697
        for (let comment of util.getLeadingComments(this) ?? []) {
7!
NEW
1698
            result.push(
×
1699
                comment.text,
1700
                state.newline,
1701
                state.indent()
1702
            );
1703
        }
1704

1705
        result.push('namespace ',
7✔
1706
            ...this.getName(ParseMode.BrighterScript),
1707
            state.newline
1708
        );
1709
        state.blockDepth++;
7✔
1710
        result.push(
7✔
1711
            ...this.body.getTypedef(state)
1712
        );
1713
        state.blockDepth--;
7✔
1714

1715
        result.push(
7✔
1716
            state.indent(),
1717
            'end namespace'
1718
        );
1719
        return result;
7✔
1720
    }
1721

1722
    walk(visitor: WalkVisitor, options: WalkOptions) {
1723
        if (options.walkMode & InternalWalkMode.walkExpressions) {
2,929✔
1724
            walk(this, 'nameExpression', visitor, options);
2,391✔
1725
        }
1726

1727
        if (this.body.statements.length > 0 && options.walkMode & InternalWalkMode.walkStatements) {
2,929✔
1728
            walk(this, 'body', visitor, options);
2,737✔
1729
        }
1730
    }
1731

1732
    getType(options: GetTypeOptions) {
1733
        const resultType = new NamespaceType(this.name);
1,020✔
1734
        return resultType;
1,020✔
1735
    }
1736

1737
}
1738

1739
export class ImportStatement extends Statement implements TypedefProvider {
1✔
1740
    constructor(options: {
1741
        import?: Token;
1742
        path?: Token;
1743
    }) {
1744
        super();
205✔
1745
        this.tokens = {
205✔
1746
            import: options.import,
1747
            path: options.path
1748
        };
1749
        this.location = util.createBoundingLocation(
205✔
1750
            this.tokens.import,
1751
            this.tokens.path
1752
        );
1753
        if (this.tokens.path) {
205✔
1754
            //remove quotes
1755
            this.filePath = this.tokens.path.text.replace(/"/g, '');
203✔
1756
            if (this.tokens.path?.location?.range) {
203!
1757
                //adjust the range to exclude the quotes
1758
                this.tokens.path.location = util.createLocation(
199✔
1759
                    this.tokens.path.location.range.start.line,
1760
                    this.tokens.path.location.range.start.character + 1,
1761
                    this.tokens.path.location.range.end.line,
1762
                    this.tokens.path.location.range.end.character - 1,
1763
                    this.tokens.path.location.uri
1764
                );
1765
            }
1766
        }
1767
    }
1768

1769
    public readonly tokens: {
1770
        readonly import?: Token;
1771
        readonly path: Token;
1772
    };
1773

1774
    public readonly kind = AstNodeKind.ImportStatement;
205✔
1775

1776
    public readonly location: Location;
1777

1778
    public readonly filePath: string;
1779

1780
    transpile(state: BrsTranspileState) {
1781
        //The xml files are responsible for adding the additional script imports, but
1782
        //add the import statement as a comment just for debugging purposes
1783
        return [
13✔
1784
            state.transpileToken(this.tokens.import, 'import', true),
1785
            ' ',
1786
            state.transpileToken(this.tokens.path)
1787
        ];
1788
    }
1789

1790
    /**
1791
     * Get the typedef for this statement
1792
     */
1793
    public getTypedef(state: BrsTranspileState) {
1794
        return [
3✔
1795
            this.tokens.import?.text ?? 'import',
18!
1796
            ' ',
1797
            //replace any `.bs` extension with `.brs`
1798
            this.tokens.path.text.replace(/\.bs"?$/i, '.brs"')
1799
        ];
1800
    }
1801

1802
    walk(visitor: WalkVisitor, options: WalkOptions) {
1803
        //nothing to walk
1804
    }
1805

1806
    get leadingTrivia(): Token[] {
1807
        return this.tokens.import?.leadingTrivia ?? [];
9!
1808
    }
1809
}
1810

1811
export class InterfaceStatement extends Statement implements TypedefProvider {
1✔
1812
    constructor(options: {
1813
        interface: Token;
1814
        name: Identifier;
1815
        extends?: Token;
1816
        parentInterfaceName?: TypeExpression;
1817
        body: Statement[];
1818
        endInterface?: Token;
1819
    }) {
1820
        super();
146✔
1821
        this.tokens = {
146✔
1822
            interface: options.interface,
1823
            name: options.name,
1824
            extends: options.extends,
1825
            endInterface: options.endInterface
1826
        };
1827
        this.parentInterfaceName = options.parentInterfaceName;
146✔
1828
        this.body = options.body;
146✔
1829
        this.location = util.createBoundingLocation(
146✔
1830
            this.tokens.interface,
1831
            this.tokens.name,
1832
            this.tokens.extends,
1833
            this.parentInterfaceName,
1834
            ...this.body,
1835
            this.tokens.endInterface
1836
        );
1837
    }
1838
    public readonly parentInterfaceName?: TypeExpression;
1839
    public readonly body: Statement[];
1840

1841
    public readonly kind = AstNodeKind.InterfaceStatement;
146✔
1842

1843
    public readonly tokens = {} as {
146✔
1844
        readonly interface?: Token;
1845
        readonly name: Identifier;
1846
        readonly extends?: Token;
1847
        readonly endInterface?: Token;
1848
    };
1849

1850
    public readonly location: Location | undefined;
1851

1852
    public get fields(): InterfaceFieldStatement[] {
1853
        return this.body.filter(x => isInterfaceFieldStatement(x)) as InterfaceFieldStatement[];
206✔
1854
    }
1855

1856
    public get methods(): InterfaceMethodStatement[] {
1857
        return this.body.filter(x => isInterfaceMethodStatement(x)) as InterfaceMethodStatement[];
201✔
1858
    }
1859

1860

1861
    public hasParentInterface() {
NEW
1862
        return !!this.parentInterfaceName;
×
1863
    }
1864

1865
    public get leadingTrivia(): Token[] {
1866
        return this.tokens.interface?.leadingTrivia;
21!
1867
    }
1868

1869
    public get endTrivia(): Token[] {
NEW
1870
        return this.tokens.endInterface?.leadingTrivia;
×
1871
    }
1872

1873

1874
    /**
1875
     * The name of the interface WITH its leading namespace (if applicable)
1876
     */
1877
    public get fullName() {
1878
        const name = this.tokens.name?.text;
2!
1879
        if (name) {
2!
1880
            const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
2✔
1881
            if (namespace) {
2✔
1882
                let namespaceName = namespace.getName(ParseMode.BrighterScript);
1✔
1883
                return `${namespaceName}.${name}`;
1✔
1884
            } else {
1885
                return name;
1✔
1886
            }
1887
        } else {
1888
            //return undefined which will allow outside callers to know that this interface doesn't have a name
1889
            return undefined;
×
1890
        }
1891
    }
1892

1893
    /**
1894
     * The name of the interface (without the namespace prefix)
1895
     */
1896
    public get name() {
1897
        return this.tokens.name?.text;
137!
1898
    }
1899

1900
    /**
1901
     * Get the name of this expression based on the parse mode
1902
     */
1903
    public getName(parseMode: ParseMode) {
1904
        const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
137✔
1905
        if (namespace) {
137✔
1906
            let delimiter = parseMode === ParseMode.BrighterScript ? '.' : '_';
22!
1907
            let namespaceName = namespace.getName(parseMode);
22✔
1908
            return namespaceName + delimiter + this.name;
22✔
1909
        } else {
1910
            return this.name;
115✔
1911
        }
1912
    }
1913

1914
    public transpile(state: BrsTranspileState): TranspileResult {
1915
        //interfaces should completely disappear at runtime
1916
        return [
13✔
1917
            state.transpileLeadingComments(this.tokens.interface)
1918
        ];
1919
    }
1920

1921
    getTypedef(state: BrsTranspileState) {
1922
        const result = [] as TranspileResult;
7✔
1923
        for (let comment of util.getLeadingComments(this) ?? []) {
7!
NEW
1924
            result.push(
×
1925
                comment.text,
1926
                state.newline,
1927
                state.indent()
1928
            );
1929
        }
1930
        for (let annotation of this.annotations ?? []) {
7✔
1931
            result.push(
1✔
1932
                ...annotation.getTypedef(state),
1933
                state.newline,
1934
                state.indent()
1935
            );
1936
        }
1937
        result.push(
7✔
1938
            this.tokens.interface.text,
1939
            ' ',
1940
            this.tokens.name.text
1941
        );
1942
        const parentInterfaceName = this.parentInterfaceName?.getName();
7!
1943
        if (parentInterfaceName) {
7!
1944
            result.push(
×
1945
                ' extends ',
1946
                parentInterfaceName
1947
            );
1948
        }
1949
        const body = this.body ?? [];
7!
1950
        if (body.length > 0) {
7!
1951
            state.blockDepth++;
7✔
1952
        }
1953
        for (const statement of body) {
7✔
1954
            if (isInterfaceMethodStatement(statement) || isInterfaceFieldStatement(statement)) {
22!
1955
                result.push(
22✔
1956
                    state.newline,
1957
                    state.indent(),
1958
                    ...statement.getTypedef(state)
1959
                );
1960
            } else {
UNCOV
1961
                result.push(
×
1962
                    state.newline,
1963
                    state.indent(),
1964
                    ...statement.transpile(state)
1965
                );
1966
            }
1967
        }
1968
        if (body.length > 0) {
7!
1969
            state.blockDepth--;
7✔
1970
        }
1971
        result.push(
7✔
1972
            state.newline,
1973
            state.indent(),
1974
            'end interface',
1975
            state.newline
1976
        );
1977
        return result;
7✔
1978
    }
1979

1980
    walk(visitor: WalkVisitor, options: WalkOptions) {
1981
        //visitor-less walk function to do parent linking
1982
        walk(this, 'parentInterfaceName', null, options);
675✔
1983

1984
        if (options.walkMode & InternalWalkMode.walkStatements) {
675!
1985
            walkArray(this.body, visitor, options, this);
675✔
1986
        }
1987
    }
1988

1989
    getType(options: GetTypeOptions) {
1990
        const superIface = this.parentInterfaceName?.getType(options) as InterfaceType;
131✔
1991

1992
        const resultType = new InterfaceType(this.getName(ParseMode.BrighterScript), superIface);
131✔
1993
        for (const statement of this.methods) {
131✔
1994
            const memberType = statement?.getType({ ...options, typeChain: undefined }); // no typechain info needed
28!
1995
            const flag = statement.isOptional ? SymbolTypeFlag.runtime | SymbolTypeFlag.optional : SymbolTypeFlag.runtime;
28✔
1996
            resultType.addMember(statement?.tokens.name?.text, { definingNode: statement }, memberType, flag);
28!
1997
        }
1998
        for (const statement of this.fields) {
131✔
1999
            const memberType = statement?.getType({ ...options, typeChain: undefined }); // no typechain info needed
168!
2000
            const flag = statement.isOptional ? SymbolTypeFlag.runtime | SymbolTypeFlag.optional : SymbolTypeFlag.runtime;
168✔
2001
            resultType.addMember(statement?.tokens.name?.text, { definingNode: statement, isInstance: true }, memberType, flag);
168!
2002
        }
2003
        options.typeChain?.push(new TypeChainEntry({
131✔
2004
            name: this.getName(ParseMode.BrighterScript),
2005
            type: resultType,
2006
            data: options.data,
2007
            astNode: this
2008
        }));
2009
        return resultType;
131✔
2010
    }
2011
}
2012

2013
export class InterfaceFieldStatement extends Statement implements TypedefProvider {
1✔
2014
    public transpile(state: BrsTranspileState): TranspileResult {
2015
        throw new Error('Method not implemented.');
×
2016
    }
2017
    constructor(options: {
2018
        name: Identifier;
2019
        as?: Token;
2020
        typeExpression?: TypeExpression;
2021
        optional?: Token;
2022
    }) {
2023
        super();
180✔
2024
        this.tokens = {
180✔
2025
            optional: options.optional,
2026
            name: options.name,
2027
            as: options.as
2028
        };
2029
        this.typeExpression = options.typeExpression;
180✔
2030
        this.location = util.createBoundingLocation(
180✔
2031
            this.tokens.optional,
2032
            this.tokens.name,
2033
            this.tokens.as,
2034
            this.typeExpression
2035
        );
2036
    }
2037

2038
    public readonly kind = AstNodeKind.InterfaceFieldStatement;
180✔
2039

2040
    public readonly typeExpression?: TypeExpression;
2041

2042
    public readonly location: Location | undefined;
2043

2044
    public readonly tokens: {
2045
        readonly name: Identifier;
2046
        readonly as: Token;
2047
        readonly optional?: Token;
2048
    };
2049

2050
    public get leadingTrivia(): Token[] {
2051
        return this.tokens.optional?.leadingTrivia ?? this.tokens.name.leadingTrivia;
21✔
2052
    }
2053

2054
    public get name() {
2055
        return this.tokens.name.text;
×
2056
    }
2057

2058
    public get isOptional() {
2059
        return !!this.tokens.optional;
188✔
2060
    }
2061

2062
    walk(visitor: WalkVisitor, options: WalkOptions) {
2063
        if (options.walkMode & InternalWalkMode.walkExpressions) {
1,163✔
2064
            walk(this, 'typeExpression', visitor, options);
1,007✔
2065
        }
2066
    }
2067

2068
    getTypedef(state: BrsTranspileState): TranspileResult {
2069
        const result = [] as TranspileResult;
12✔
2070
        for (let comment of util.getLeadingComments(this) ?? []) {
12!
NEW
2071
            result.push(
×
2072
                comment.text,
2073
                state.newline,
2074
                state.indent()
2075
            );
2076
        }
2077
        for (let annotation of this.annotations ?? []) {
12✔
2078
            result.push(
1✔
2079
                ...annotation.getTypedef(state),
2080
                state.newline,
2081
                state.indent()
2082
            );
2083
        }
2084
        if (this.isOptional) {
12✔
2085
            result.push(
1✔
2086
                this.tokens.optional!.text,
2087
                ' '
2088
            );
2089
        }
2090
        result.push(
12✔
2091
            this.tokens.name.text
2092
        );
2093

2094
        if (this.typeExpression) {
12!
2095
            result.push(
12✔
2096
                ' as ',
2097
                ...this.typeExpression.getTypedef(state)
2098
            );
2099
        }
2100
        return result;
12✔
2101
    }
2102

2103
    public getType(options: GetTypeOptions): BscType {
2104
        return this.typeExpression?.getType(options) ?? DynamicType.instance;
172✔
2105
    }
2106

2107
}
2108

2109
//TODO: there is much that is similar with this and FunctionExpression.
2110
//It would be nice to refactor this so there is less duplicated code
2111
export class InterfaceMethodStatement extends Statement implements TypedefProvider {
1✔
2112
    public transpile(state: BrsTranspileState): TranspileResult {
2113
        throw new Error('Method not implemented.');
×
2114
    }
2115
    constructor(options: {
2116
        functionType?: Token;
2117
        name: Identifier;
2118
        leftParen?: Token;
2119
        params?: FunctionParameterExpression[];
2120
        rightParen?: Token;
2121
        as?: Token;
2122
        returnTypeExpression?: TypeExpression;
2123
        optional?: Token;
2124
    }) {
2125
        super();
38✔
2126
        this.tokens = {
38✔
2127
            optional: options.optional,
2128
            functionType: options.functionType,
2129
            name: options.name,
2130
            leftParen: options.leftParen,
2131
            rightParen: options.rightParen,
2132
            as: options.as
2133
        };
2134
        this.params = options.params ?? [];
38!
2135
        this.returnTypeExpression = options.returnTypeExpression;
38✔
2136
    }
2137

2138
    public readonly kind = AstNodeKind.InterfaceMethodStatement;
38✔
2139

2140
    public get location() {
2141
        return util.createBoundingLocation(
52✔
2142
            this.tokens.optional,
2143
            this.tokens.functionType,
2144
            this.tokens.name,
2145
            this.tokens.leftParen,
2146
            ...(this.params ?? []),
156!
2147
            this.tokens.rightParen,
2148
            this.tokens.as,
2149
            this.returnTypeExpression
2150
        );
2151
    }
2152
    /**
2153
     * Get the name of this method.
2154
     */
2155
    public getName(parseMode: ParseMode) {
2156
        return this.tokens.name.text;
28✔
2157
    }
2158

2159
    public readonly tokens: {
2160
        readonly optional?: Token;
2161
        readonly functionType: Token;
2162
        readonly name: Identifier;
2163
        readonly leftParen?: Token;
2164
        readonly rightParen?: Token;
2165
        readonly as?: Token;
2166
    };
2167

2168
    public readonly params: FunctionParameterExpression[];
2169
    public readonly returnTypeExpression?: TypeExpression;
2170

2171
    public get isOptional() {
2172
        return !!this.tokens.optional;
41✔
2173
    }
2174

2175
    public get leadingTrivia(): Token[] {
2176
        return this.tokens.optional?.leadingTrivia ?? this.tokens.functionType.leadingTrivia;
13✔
2177
    }
2178

2179
    walk(visitor: WalkVisitor, options: WalkOptions) {
2180
        if (options.walkMode & InternalWalkMode.walkExpressions) {
200✔
2181
            walk(this, 'returnTypeExpression', visitor, options);
175✔
2182
        }
2183
    }
2184

2185
    getTypedef(state: BrsTranspileState) {
2186
        const result = [] as TranspileResult;
10✔
2187
        for (let comment of util.getLeadingComments(this) ?? []) {
10!
2188
            result.push(
1✔
2189
                comment.text,
2190
                state.newline,
2191
                state.indent()
2192
            );
2193
        }
2194
        for (let annotation of this.annotations ?? []) {
10✔
2195
            result.push(
1✔
2196
                ...annotation.getTypedef(state),
2197
                state.newline,
2198
                state.indent()
2199
            );
2200
        }
2201
        if (this.isOptional) {
10!
UNCOV
2202
            result.push(
×
2203
                this.tokens.optional!.text,
2204
                ' '
2205
            );
2206
        }
2207
        result.push(
10✔
2208
            this.tokens.functionType?.text ?? 'function',
60!
2209
            ' ',
2210
            this.tokens.name.text,
2211
            '('
2212
        );
2213
        const params = this.params ?? [];
10!
2214
        for (let i = 0; i < params.length; i++) {
10✔
2215
            if (i > 0) {
2✔
2216
                result.push(', ');
1✔
2217
            }
2218
            const param = params[i];
2✔
2219
            result.push(param.tokens.name.text);
2✔
2220
            if (param.typeExpression) {
2!
2221
                result.push(
2✔
2222
                    ' as ',
2223
                    ...param.typeExpression.getTypedef(state)
2224
                );
2225
            }
2226
        }
2227
        result.push(
10✔
2228
            ')'
2229
        );
2230
        if (this.returnTypeExpression) {
10!
2231
            result.push(
10✔
2232
                ' as ',
2233
                ...this.returnTypeExpression.getTypedef(state)
2234
            );
2235
        }
2236
        return result;
10✔
2237
    }
2238

2239
    public getType(options: GetTypeOptions): TypedFunctionType {
2240
        //if there's a defined return type, use that
2241
        let returnType = this.returnTypeExpression?.getType(options);
28✔
2242
        const isSub = this.tokens.functionType?.kind === TokenKind.Sub || !returnType;
28!
2243
        //if we don't have a return type and this is a sub, set the return type to `void`. else use `dynamic`
2244
        if (!returnType) {
28✔
2245
            returnType = isSub ? VoidType.instance : DynamicType.instance;
10!
2246
        }
2247

2248
        const resultType = new TypedFunctionType(returnType);
28✔
2249
        resultType.isSub = isSub;
28✔
2250
        for (let param of this.params) {
28✔
2251
            resultType.addParameter(param.tokens.name.text, param.getType(options), !!param.defaultValue);
7✔
2252
        }
2253
        if (options.typeChain) {
28!
2254
            // need Interface type for type chain
NEW
2255
            this.parent?.getType(options);
×
2256
        }
2257
        let funcName = this.getName(ParseMode.BrighterScript);
28✔
2258
        resultType.setName(funcName);
28✔
2259
        options.typeChain?.push(new TypeChainEntry({ name: resultType.name, type: resultType, data: options.data, astNode: this }));
28!
2260
        return resultType;
28✔
2261
    }
2262
}
2263

2264
export class ClassStatement extends Statement implements TypedefProvider {
1✔
2265
    constructor(options: {
2266
        class?: Token;
2267
        /**
2268
         * The name of the class (without namespace prefix)
2269
         */
2270
        name: Identifier;
2271
        body: Statement[];
2272
        endClass?: Token;
2273
        extends?: Token;
2274
        parentClassName?: TypeExpression;
2275
    }) {
2276
        super();
658✔
2277
        this.body = options.body ?? [];
658!
2278
        this.tokens = {
658✔
2279
            name: options.name,
2280
            class: options.class,
2281
            endClass: options.endClass,
2282
            extends: options.extends
2283
        };
2284
        this.parentClassName = options.parentClassName;
658✔
2285
        this.symbolTable = new SymbolTable(`ClassStatement: '${this.tokens.name?.text}'`, () => this.parent?.getSymbolTable());
1,173!
2286

2287
        for (let statement of this.body) {
658✔
2288
            if (isMethodStatement(statement)) {
658✔
2289
                this.methods.push(statement);
348✔
2290
                this.memberMap[statement?.tokens.name?.text.toLowerCase()] = statement;
348!
2291
            } else if (isFieldStatement(statement)) {
310!
2292
                this.fields.push(statement);
310✔
2293
                this.memberMap[statement?.tokens.name?.text.toLowerCase()] = statement;
310!
2294
            }
2295
        }
2296

2297
        this.location = util.createBoundingLocation(
658✔
2298
            this.parentClassName,
2299
            ...(this.body ?? []),
1,974!
2300
            util.createBoundingLocationFromTokens(this.tokens)
2301
        );
2302
    }
2303

2304
    public readonly kind = AstNodeKind.ClassStatement;
658✔
2305

2306

2307
    public readonly tokens: {
2308
        readonly class?: Token;
2309
        /**
2310
         * The name of the class (without namespace prefix)
2311
         */
2312
        readonly name: Identifier;
2313
        readonly endClass?: Token;
2314
        readonly extends?: Token;
2315
    };
2316
    public readonly body: Statement[];
2317
    public readonly parentClassName: TypeExpression;
2318

2319

2320
    public getName(parseMode: ParseMode) {
2321
        const name = this.tokens.name?.text;
2,004✔
2322
        if (name) {
2,004✔
2323
            const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
2,002✔
2324
            if (namespace) {
2,002✔
2325
                let namespaceName = namespace.getName(parseMode);
587✔
2326
                let separator = parseMode === ParseMode.BrighterScript ? '.' : '_';
587✔
2327
                return namespaceName + separator + name;
587✔
2328
            } else {
2329
                return name;
1,415✔
2330
            }
2331
        } else {
2332
            //return undefined which will allow outside callers to know that this class doesn't have a name
2333
            return undefined;
2✔
2334
        }
2335
    }
2336

2337
    public get leadingTrivia(): Token[] {
2338
        return this.tokens.class?.leadingTrivia;
81!
2339
    }
2340

2341
    public get endTrivia(): Token[] {
NEW
2342
        return this.tokens.endClass?.leadingTrivia ?? [];
×
2343
    }
2344

2345
    public readonly memberMap = {} as Record<string, MemberStatement>;
658✔
2346
    public readonly methods = [] as MethodStatement[];
658✔
2347
    public readonly fields = [] as FieldStatement[];
658✔
2348

2349
    public readonly location: Location | undefined;
2350

2351
    transpile(state: BrsTranspileState) {
2352
        let result = [] as TranspileResult;
48✔
2353
        //make the builder
2354
        result.push(...this.getTranspiledBuilder(state));
48✔
2355
        result.push(
48✔
2356
            '\n',
2357
            state.indent()
2358
        );
2359
        //make the class assembler (i.e. the public-facing class creator method)
2360
        result.push(...this.getTranspiledClassFunction(state));
48✔
2361
        return result;
48✔
2362
    }
2363

2364
    getTypedef(state: BrsTranspileState) {
2365
        const result = [] as TranspileResult;
15✔
2366
        for (let comment of util.getLeadingComments(this) ?? []) {
15!
NEW
2367
            result.push(
×
2368
                comment.text,
2369
                state.newline,
2370
                state.indent()
2371
            );
2372
        }
2373
        for (let annotation of this.annotations ?? []) {
15!
UNCOV
2374
            result.push(
×
2375
                ...annotation.getTypedef(state),
2376
                state.newline,
2377
                state.indent()
2378
            );
2379
        }
2380
        result.push(
15✔
2381
            'class ',
2382
            this.tokens.name.text
2383
        );
2384
        if (this.parentClassName) {
15✔
2385
            const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
4✔
2386
            const fqName = util.getFullyQualifiedClassName(
4✔
2387
                this.parentClassName.getName(),
2388
                namespace?.getName(ParseMode.BrighterScript)
12✔
2389
            );
2390
            result.push(
4✔
2391
                ` extends ${fqName}`
2392
            );
2393
        }
2394
        result.push(state.newline);
15✔
2395
        state.blockDepth++;
15✔
2396

2397
        let body = this.body;
15✔
2398
        //inject an empty "new" method if missing
2399
        if (!this.getConstructorFunction()) {
15✔
2400
            const constructor = createMethodStatement('new', TokenKind.Sub);
11✔
2401
            constructor.parent = this;
11✔
2402
            //walk the constructor to set up parent links
2403
            constructor.link();
11✔
2404
            body = [
11✔
2405
                constructor,
2406
                ...this.body
2407
            ];
2408
        }
2409

2410
        for (const member of body) {
15✔
2411
            if (isTypedefProvider(member)) {
33!
2412
                result.push(
33✔
2413
                    state.indent(),
2414
                    ...member.getTypedef(state),
2415
                    state.newline
2416
                );
2417
            }
2418
        }
2419
        state.blockDepth--;
15✔
2420
        result.push(
15✔
2421
            state.indent(),
2422
            'end class'
2423
        );
2424
        return result;
15✔
2425
    }
2426

2427
    /**
2428
     * Find the parent index for this class's parent.
2429
     * For class inheritance, every class is given an index.
2430
     * The base class is index 0, its child is index 1, and so on.
2431
     */
2432
    public getParentClassIndex(state: BrsTranspileState) {
2433
        let myIndex = 0;
112✔
2434
        let stmt = this as ClassStatement;
112✔
2435
        while (stmt) {
112✔
2436
            if (stmt.parentClassName) {
161✔
2437
                const namespace = stmt.findAncestor<NamespaceStatement>(isNamespaceStatement);
50✔
2438
                //find the parent class
2439
                stmt = state.file.getClassFileLink(
50✔
2440
                    stmt.parentClassName.getName(),
2441
                    namespace?.getName(ParseMode.BrighterScript)
150✔
2442
                )?.item;
50✔
2443
                myIndex++;
50✔
2444
            } else {
2445
                break;
111✔
2446
            }
2447
        }
2448
        const result = myIndex - 1;
112✔
2449
        if (result >= 0) {
112✔
2450
            return result;
43✔
2451
        } else {
2452
            return null;
69✔
2453
        }
2454
    }
2455

2456
    public hasParentClass() {
2457
        return !!this.parentClassName;
276✔
2458
    }
2459

2460
    /**
2461
     * Get all ancestor classes, in closest-to-furthest order (i.e. 0 is parent, 1 is grandparent, etc...).
2462
     * This will return an empty array if no ancestors were found
2463
     */
2464
    public getAncestors(state: BrsTranspileState) {
2465
        let ancestors = [] as ClassStatement[];
96✔
2466
        let stmt = this as ClassStatement;
96✔
2467
        while (stmt) {
96✔
2468
            if (stmt.parentClassName) {
138✔
2469
                const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
42✔
2470
                stmt = state.file.getClassFileLink(
42✔
2471
                    stmt.parentClassName.getName(),
2472
                    namespace?.getName(ParseMode.BrighterScript)
126✔
2473
                )?.item;
42!
2474
                ancestors.push(stmt);
42✔
2475
            } else {
2476
                break;
96✔
2477
            }
2478
        }
2479
        return ancestors;
96✔
2480
    }
2481

2482
    private getBuilderName(name: string) {
2483
        if (name.includes('.')) {
114✔
2484
            name = name.replace(/\./gi, '_');
3✔
2485
        }
2486
        return `__${name}_builder`;
114✔
2487
    }
2488

2489
    public getConstructorType() {
2490
        const constructorType = this.getConstructorFunction()?.getType({ flags: SymbolTypeFlag.runtime }) ?? new TypedFunctionType(null);
2!
2491
        constructorType.returnType = this.getType({ flags: SymbolTypeFlag.runtime });
2✔
2492
        return constructorType;
2✔
2493
    }
2494

2495
    /**
2496
     * Get the constructor function for this class (if exists), or undefined if not exist
2497
     */
2498
    private getConstructorFunction() {
2499
        return this.body.find((stmt) => {
113✔
2500
            return (stmt as MethodStatement)?.tokens.name?.text?.toLowerCase() === 'new';
106!
2501
        }) as MethodStatement;
2502
    }
2503

2504
    /**
2505
     * Determine if the specified field was declared in one of the ancestor classes
2506
     */
2507
    public isFieldDeclaredByAncestor(fieldName: string, ancestors: ClassStatement[]) {
2508
        let lowerFieldName = fieldName.toLowerCase();
×
2509
        for (let ancestor of ancestors) {
×
2510
            if (ancestor.memberMap[lowerFieldName]) {
×
2511
                return true;
×
2512
            }
2513
        }
2514
        return false;
×
2515
    }
2516

2517
    /**
2518
     * The builder is a function that assigns all of the methods and property names to a class instance.
2519
     * This needs to be a separate function so that child classes can call the builder from their parent
2520
     * without instantiating the parent constructor at that point in time.
2521
     */
2522
    private getTranspiledBuilder(state: BrsTranspileState) {
2523
        let result = [] as TranspileResult;
48✔
2524
        result.push(`function ${this.getBuilderName(this.getName(ParseMode.BrightScript)!)}()\n`);
48✔
2525
        state.blockDepth++;
48✔
2526
        //indent
2527
        result.push(state.indent());
48✔
2528

2529
        /**
2530
         * The lineage of this class. index 0 is a direct parent, index 1 is index 0's parent, etc...
2531
         */
2532
        let ancestors = this.getAncestors(state);
48✔
2533

2534
        //construct parent class or empty object
2535
        if (ancestors[0]) {
48✔
2536
            const ancestorNamespace = ancestors[0].findAncestor<NamespaceStatement>(isNamespaceStatement);
18✔
2537
            let fullyQualifiedClassName = util.getFullyQualifiedClassName(
18✔
2538
                ancestors[0].getName(ParseMode.BrighterScript)!,
2539
                ancestorNamespace?.getName(ParseMode.BrighterScript)
54✔
2540
            );
2541
            result.push(
18✔
2542
                'instance = ',
2543
                this.getBuilderName(fullyQualifiedClassName), '()');
2544
        } else {
2545
            //use an empty object.
2546
            result.push('instance = {}');
30✔
2547
        }
2548
        result.push(
48✔
2549
            state.newline,
2550
            state.indent()
2551
        );
2552
        let parentClassIndex = this.getParentClassIndex(state);
48✔
2553

2554
        let body = this.body;
48✔
2555
        //inject an empty "new" method if missing
2556
        if (!this.getConstructorFunction()) {
48✔
2557
            body = [
27✔
2558
                createMethodStatement('new', TokenKind.Sub),
2559
                ...this.body
2560
            ];
2561
        }
2562

2563
        for (let statement of body) {
48✔
2564
            //is field statement
2565
            if (isFieldStatement(statement)) {
77✔
2566
                //do nothing with class fields in this situation, they are handled elsewhere
2567
                continue;
14✔
2568

2569
                //methods
2570
            } else if (isMethodStatement(statement)) {
63!
2571

2572
                //store overridden parent methods as super{parentIndex}_{methodName}
2573
                if (
63✔
2574
                    //is override method
2575
                    statement.tokens.override ||
170✔
2576
                    //is constructor function in child class
2577
                    (statement.tokens.name.text.toLowerCase() === 'new' && ancestors[0])
2578
                ) {
2579
                    result.push(
22✔
2580
                        `instance.super${parentClassIndex}_${statement.tokens.name.text} = instance.${statement.tokens.name.text}`,
2581
                        state.newline,
2582
                        state.indent()
2583
                    );
2584
                }
2585

2586
                state.classStatement = this;
63✔
2587
                state.skipLeadingComments = true;
63✔
2588
                //add leading comments
2589
                if (statement.leadingTrivia.filter(token => token.kind === TokenKind.Comment).length > 0) {
78✔
2590
                    result.push(
2✔
2591
                        ...state.transpileComments(statement.leadingTrivia),
2592
                        state.indent()
2593
                    );
2594
                }
2595
                result.push(
63✔
2596
                    'instance.',
2597
                    state.transpileToken(statement.tokens.name),
2598
                    ' = ',
2599
                    ...statement.transpile(state),
2600
                    state.newline,
2601
                    state.indent()
2602
                );
2603
                state.skipLeadingComments = false;
63✔
2604
                delete state.classStatement;
63✔
2605
            } else {
2606
                //other random statements (probably just comments)
2607
                result.push(
×
2608
                    ...statement.transpile(state),
2609
                    state.newline,
2610
                    state.indent()
2611
                );
2612
            }
2613
        }
2614
        //return the instance
2615
        result.push('return instance\n');
48✔
2616
        state.blockDepth--;
48✔
2617
        result.push(state.indent());
48✔
2618
        result.push(`end function`);
48✔
2619
        return result;
48✔
2620
    }
2621

2622
    /**
2623
     * The class function is the function with the same name as the class. This is the function that
2624
     * consumers should call to create a new instance of that class.
2625
     * This invokes the builder, gets an instance of the class, then invokes the "new" function on that class.
2626
     */
2627
    private getTranspiledClassFunction(state: BrsTranspileState) {
2628
        let result = [] as TranspileResult;
48✔
2629
        const constructorFunction = this.getConstructorFunction();
48✔
2630
        const constructorParams = constructorFunction ? constructorFunction.func.parameters : [];
48✔
2631

2632
        result.push(
48✔
2633
            state.sourceNode(this.tokens.class, 'function'),
2634
            state.sourceNode(this.tokens.class, ' '),
2635
            state.sourceNode(this.tokens.name, this.getName(ParseMode.BrightScript)),
2636
            `(`
2637
        );
2638
        let i = 0;
48✔
2639
        for (let param of constructorParams) {
48✔
2640
            if (i > 0) {
8✔
2641
                result.push(', ');
2✔
2642
            }
2643
            result.push(
8✔
2644
                param.transpile(state)
2645
            );
2646
            i++;
8✔
2647
        }
2648
        result.push(
48✔
2649
            ')',
2650
            '\n'
2651
        );
2652

2653
        state.blockDepth++;
48✔
2654
        result.push(state.indent());
48✔
2655
        result.push(`instance = ${this.getBuilderName(this.getName(ParseMode.BrightScript)!)}()\n`);
48✔
2656

2657
        result.push(state.indent());
48✔
2658
        result.push(`instance.new(`);
48✔
2659

2660
        //append constructor arguments
2661
        i = 0;
48✔
2662
        for (let param of constructorParams) {
48✔
2663
            if (i > 0) {
8✔
2664
                result.push(', ');
2✔
2665
            }
2666
            result.push(
8✔
2667
                state.transpileToken(param.tokens.name)
2668
            );
2669
            i++;
8✔
2670
        }
2671
        result.push(
48✔
2672
            ')',
2673
            '\n'
2674
        );
2675

2676
        result.push(state.indent());
48✔
2677
        result.push(`return instance\n`);
48✔
2678

2679
        state.blockDepth--;
48✔
2680
        result.push(state.indent());
48✔
2681
        result.push(`end function`);
48✔
2682
        return result;
48✔
2683
    }
2684

2685
    walk(visitor: WalkVisitor, options: WalkOptions) {
2686
        //visitor-less walk function to do parent linking
2687
        walk(this, 'parentClassName', null, options);
2,572✔
2688

2689
        if (options.walkMode & InternalWalkMode.walkStatements) {
2,572!
2690
            walkArray(this.body, visitor, options, this);
2,572✔
2691
        }
2692
    }
2693

2694
    getType(options: GetTypeOptions) {
2695
        const superClass = this.parentClassName?.getType(options) as ClassType;
406✔
2696

2697
        const resultType = new ClassType(this.getName(ParseMode.BrighterScript), superClass);
406✔
2698

2699
        for (const statement of this.methods) {
406✔
2700
            const funcType = statement?.func.getType({ ...options, typeChain: undefined }); //no typechain needed
231!
2701
            let flag = SymbolTypeFlag.runtime;
231✔
2702
            if (statement.accessModifier?.kind === TokenKind.Private) {
231✔
2703
                flag |= SymbolTypeFlag.private;
8✔
2704
            }
2705
            if (statement.accessModifier?.kind === TokenKind.Protected) {
231✔
2706
                flag |= SymbolTypeFlag.protected;
7✔
2707
            }
2708
            resultType.addMember(statement?.tokens.name?.text, { definingNode: statement }, funcType, flag);
231!
2709
        }
2710
        for (const statement of this.fields) {
406✔
2711
            const fieldType = statement.getType({ ...options, typeChain: undefined }); //no typechain needed
203✔
2712
            let flag = SymbolTypeFlag.runtime;
203✔
2713
            if (statement.isOptional) {
203✔
2714
                flag |= SymbolTypeFlag.optional;
7✔
2715
            }
2716
            if (statement.tokens.accessModifier?.kind === TokenKind.Private) {
203✔
2717
                flag |= SymbolTypeFlag.private;
14✔
2718
            }
2719
            if (statement.tokens.accessModifier?.kind === TokenKind.Protected) {
203✔
2720
                flag |= SymbolTypeFlag.protected;
9✔
2721
            }
2722
            resultType.addMember(statement?.tokens.name?.text, { definingNode: statement, isInstance: true }, fieldType, flag);
203!
2723
        }
2724
        options.typeChain?.push(new TypeChainEntry({ name: resultType.name, type: resultType, data: options.data, astNode: this }));
406✔
2725
        return resultType;
406✔
2726
    }
2727
}
2728

2729
const accessModifiers = [
1✔
2730
    TokenKind.Public,
2731
    TokenKind.Protected,
2732
    TokenKind.Private
2733
];
2734
export class MethodStatement extends FunctionStatement {
1✔
2735
    constructor(
2736
        options: {
2737
            modifiers?: Token | Token[];
2738
            name: Identifier;
2739
            func: FunctionExpression;
2740
            override?: Token;
2741
        }
2742
    ) {
2743
        super(options);
386✔
2744
        if (options.modifiers) {
386✔
2745
            if (Array.isArray(options.modifiers)) {
35!
NEW
2746
                this.modifiers.push(...options.modifiers);
×
2747
            } else {
2748
                this.modifiers.push(options.modifiers);
35✔
2749
            }
2750
        }
2751
        this.tokens = {
386✔
2752
            ...this.tokens,
2753
            override: options.override
2754
        };
2755
        this.location = util.createBoundingLocation(
386✔
2756
            ...(this.modifiers),
2757
            util.createBoundingLocationFromTokens(this.tokens),
2758
            this.func
2759
        );
2760
    }
2761

2762
    public readonly kind = AstNodeKind.MethodStatement as AstNodeKind;
386✔
2763

2764
    public readonly modifiers: Token[] = [];
386✔
2765

2766
    public readonly tokens: {
2767
        readonly name: Identifier;
2768
        readonly override?: Token;
2769
    };
2770

2771
    public get accessModifier() {
2772
        return this.modifiers.find(x => accessModifiers.includes(x.kind));
570✔
2773
    }
2774

2775
    public readonly location: Location | undefined;
2776

2777
    /**
2778
     * Get the name of this method.
2779
     */
2780
    public getName(parseMode: ParseMode) {
2781
        return this.tokens.name.text;
273✔
2782
    }
2783

2784
    public get leadingTrivia(): Token[] {
2785
        return this.func.leadingTrivia;
102✔
2786
    }
2787

2788
    transpile(state: BrsTranspileState) {
2789
        if (this.tokens.name.text.toLowerCase() === 'new') {
63✔
2790
            this.ensureSuperConstructorCall(state);
48✔
2791
            //TODO we need to undo this at the bottom of this method
2792
            this.injectFieldInitializersForConstructor(state);
48✔
2793
        }
2794
        //TODO - remove type information from these methods because that doesn't work
2795
        //convert the `super` calls into the proper methods
2796
        const parentClassIndex = state.classStatement.getParentClassIndex(state);
63✔
2797
        const visitor = createVisitor({
63✔
2798
            VariableExpression: e => {
2799
                if (e.tokens.name.text.toLocaleLowerCase() === 'super') {
61✔
2800
                    state.editor.setProperty(e.tokens.name, 'text', `m.super${parentClassIndex}_new`);
18✔
2801
                }
2802
            },
2803
            DottedGetExpression: e => {
2804
                const beginningVariable = util.findBeginningVariableExpression(e);
30✔
2805
                const lowerName = beginningVariable?.getName(ParseMode.BrighterScript).toLowerCase();
30!
2806
                if (lowerName === 'super') {
30✔
2807
                    state.editor.setProperty(beginningVariable.tokens.name, 'text', 'm');
7✔
2808
                    state.editor.setProperty(e.tokens.name, 'text', `super${parentClassIndex}_${e.tokens.name.text}`);
7✔
2809
                }
2810
            }
2811
        });
2812
        const walkOptions: WalkOptions = { walkMode: WalkMode.visitExpressions };
63✔
2813
        for (const statement of this.func.body.statements) {
63✔
2814
            visitor(statement, undefined);
62✔
2815
            statement.walk(visitor, walkOptions);
62✔
2816
        }
2817
        return this.func.transpile(state);
63✔
2818
    }
2819

2820
    getTypedef(state: BrsTranspileState) {
2821
        const result: TranspileResult = [];
23✔
2822
        for (let comment of util.getLeadingComments(this) ?? []) {
23!
NEW
2823
            result.push(
×
2824
                comment.text,
2825
                state.newline,
2826
                state.indent()
2827
            );
2828
        }
2829
        for (let annotation of this.annotations ?? []) {
23✔
2830
            result.push(
2✔
2831
                ...annotation.getTypedef(state),
2832
                state.newline,
2833
                state.indent()
2834
            );
2835
        }
2836
        if (this.accessModifier) {
23✔
2837
            result.push(
8✔
2838
                this.accessModifier.text,
2839
                ' '
2840
            );
2841
        }
2842
        if (this.tokens.override) {
23✔
2843
            result.push('override ');
1✔
2844
        }
2845
        result.push(
23✔
2846
            ...this.func.getTypedef(state)
2847
        );
2848
        return result;
23✔
2849
    }
2850

2851
    /**
2852
     * All child classes must call the parent constructor. The type checker will warn users when they don't call it in their own class,
2853
     * but we still need to call it even if they have omitted it. This injects the super call if it's missing
2854
     */
2855
    private ensureSuperConstructorCall(state: BrsTranspileState) {
2856
        //if this class doesn't extend another class, quit here
2857
        if (state.classStatement!.getAncestors(state).length === 0) {
48✔
2858
            return;
30✔
2859
        }
2860

2861
        //check whether any calls to super exist
2862
        let containsSuperCall =
2863
            this.func.body.statements.findIndex((x) => {
18✔
2864
                //is a call statement
2865
                return isExpressionStatement(x) && isCallExpression(x.expression) &&
8✔
2866
                    //is a call to super
2867
                    util.findBeginningVariableExpression(x.expression.callee as any).tokens.name?.text.toLowerCase() === 'super';
21!
2868
            }) !== -1;
2869

2870
        //if a call to super exists, quit here
2871
        if (containsSuperCall) {
18✔
2872
            return;
7✔
2873
        }
2874

2875
        //this is a child class, and the constructor doesn't contain a call to super. Inject one
2876
        const superCall = new ExpressionStatement({
11✔
2877
            expression: new CallExpression({
2878
                callee: new VariableExpression({
2879
                    name: {
2880
                        kind: TokenKind.Identifier,
2881
                        text: 'super',
2882
                        isReserved: false,
2883
                        location: state.classStatement.tokens.name.location,
2884
                        leadingWhitespace: '',
2885
                        leadingTrivia: []
2886
                    }
2887
                }),
2888
                openingParen: {
2889
                    kind: TokenKind.LeftParen,
2890
                    text: '(',
2891
                    isReserved: false,
2892
                    location: state.classStatement.tokens.name.location,
2893
                    leadingWhitespace: '',
2894
                    leadingTrivia: []
2895
                },
2896
                closingParen: {
2897
                    kind: TokenKind.RightParen,
2898
                    text: ')',
2899
                    isReserved: false,
2900
                    location: state.classStatement.tokens.name.location,
2901
                    leadingWhitespace: '',
2902
                    leadingTrivia: []
2903
                },
2904
                args: []
2905
            })
2906
        });
2907
        state.editor.arrayUnshift(this.func.body.statements, superCall);
11✔
2908
    }
2909

2910
    /**
2911
     * Inject field initializers at the top of the `new` function (after any present `super()` call)
2912
     */
2913
    private injectFieldInitializersForConstructor(state: BrsTranspileState) {
2914
        let startingIndex = state.classStatement!.hasParentClass() ? 1 : 0;
48✔
2915

2916
        let newStatements = [] as Statement[];
48✔
2917
        //insert the field initializers in order
2918
        for (let field of state.classStatement!.fields) {
48✔
2919
            let thisQualifiedName = { ...field.tokens.name };
14✔
2920
            thisQualifiedName.text = 'm.' + field.tokens.name?.text;
14!
2921
            const fieldAssignment = field.initialValue
14✔
2922
                ? new AssignmentStatement({
14✔
2923
                    equals: field.tokens.equals,
2924
                    name: thisQualifiedName,
2925
                    value: field.initialValue
2926
                })
2927
                : new AssignmentStatement({
2928
                    equals: createToken(TokenKind.Equal, '=', field.tokens.name.location),
2929
                    name: thisQualifiedName,
2930
                    //if there is no initial value, set the initial value to `invalid`
2931
                    value: createInvalidLiteral('invalid', field.tokens.name.location)
2932
                });
2933
            // Add parent so namespace lookups work
2934
            fieldAssignment.parent = state.classStatement;
14✔
2935
            newStatements.push(fieldAssignment);
14✔
2936
        }
2937
        state.editor.arraySplice(this.func.body.statements, startingIndex, 0, ...newStatements);
48✔
2938
    }
2939

2940
    walk(visitor: WalkVisitor, options: WalkOptions) {
2941
        if (options.walkMode & InternalWalkMode.walkExpressions) {
2,136✔
2942
            walk(this, 'func', visitor, options);
1,699✔
2943
        }
2944
    }
2945
}
2946

2947
export class FieldStatement extends Statement implements TypedefProvider {
1✔
2948
    constructor(options: {
2949
        accessModifier?: Token;
2950
        name: Identifier;
2951
        as?: Token;
2952
        typeExpression?: TypeExpression;
2953
        equals?: Token;
2954
        initialValue?: Expression;
2955
        optional?: Token;
2956
    }) {
2957
        super();
310✔
2958
        this.tokens = {
310✔
2959
            accessModifier: options.accessModifier,
2960
            name: options.name,
2961
            as: options.as,
2962
            equals: options.equals,
2963
            optional: options.optional
2964
        };
2965
        this.typeExpression = options.typeExpression;
310✔
2966
        this.initialValue = options.initialValue;
310✔
2967

2968
        this.location = util.createBoundingLocation(
310✔
2969
            util.createBoundingLocationFromTokens(this.tokens),
2970
            this.typeExpression,
2971
            this.initialValue
2972
        );
2973
    }
2974

2975
    public readonly tokens: {
2976
        readonly accessModifier?: Token;
2977
        readonly name: Identifier;
2978
        readonly as?: Token;
2979
        readonly equals?: Token;
2980
        readonly optional?: Token;
2981
    };
2982

2983
    public readonly typeExpression?: TypeExpression;
2984
    public readonly initialValue?: Expression;
2985

2986
    public readonly kind = AstNodeKind.FieldStatement;
310✔
2987

2988
    /**
2989
     * Derive a ValueKind from the type token, or the initial value.
2990
     * Defaults to `DynamicType`
2991
     */
2992
    getType(options: GetTypeOptions) {
2993
        return this.typeExpression?.getType({ ...options, flags: SymbolTypeFlag.typetime }) ??
244✔
2994
            this.initialValue?.getType({ ...options, flags: SymbolTypeFlag.runtime }) ?? DynamicType.instance;
580✔
2995
    }
2996

2997
    public readonly location: Location | undefined;
2998

2999
    public get leadingTrivia(): Token[] {
3000
        return this.tokens.accessModifier?.leadingTrivia ?? this.tokens.optional?.leadingTrivia ?? this.tokens.name.leadingTrivia;
30!
3001
    }
3002

3003
    public get isOptional() {
3004
        return !!this.tokens.optional;
220✔
3005
    }
3006

3007
    transpile(state: BrsTranspileState): TranspileResult {
3008
        throw new Error('transpile not implemented for ' + Object.getPrototypeOf(this).constructor.name);
×
3009
    }
3010

3011
    getTypedef(state: BrsTranspileState) {
3012
        const result = [];
10✔
3013
        if (this.tokens.name) {
10!
3014
            for (let comment of util.getLeadingComments(this) ?? []) {
10!
NEW
3015
                result.push(
×
3016
                    comment.text,
3017
                    state.newline,
3018
                    state.indent()
3019
                );
3020
            }
3021
            for (let annotation of this.annotations ?? []) {
10✔
3022
                result.push(
2✔
3023
                    ...annotation.getTypedef(state),
3024
                    state.newline,
3025
                    state.indent()
3026
                );
3027
            }
3028

3029
            let type = this.getType({ flags: SymbolTypeFlag.typetime });
10✔
3030
            if (isInvalidType(type) || isVoidType(type)) {
10!
UNCOV
3031
                type = new DynamicType();
×
3032
            }
3033

3034
            result.push(
10✔
3035
                this.tokens.accessModifier?.text ?? 'public',
60!
3036
                ' '
3037
            );
3038
            if (this.isOptional) {
10!
NEW
3039
                result.push(this.tokens.optional.text, ' ');
×
3040
            }
3041
            result.push(this.tokens.name?.text,
10!
3042
                ' as ',
3043
                type.toTypeString()
3044
            );
3045
        }
3046
        return result;
10✔
3047
    }
3048

3049
    walk(visitor: WalkVisitor, options: WalkOptions) {
3050
        if (options.walkMode & InternalWalkMode.walkExpressions) {
1,535✔
3051
            walk(this, 'typeExpression', visitor, options);
1,339✔
3052
            walk(this, 'initialValue', visitor, options);
1,339✔
3053
        }
3054
    }
3055
}
3056

3057
export type MemberStatement = FieldStatement | MethodStatement;
3058

3059
export class TryCatchStatement extends Statement {
1✔
3060
    constructor(options?: {
3061
        try?: Token;
3062
        endTry?: Token;
3063
        tryBranch?: Block;
3064
        catchStatement?: CatchStatement;
3065
    }) {
3066
        super();
28✔
3067
        this.tokens = {
28✔
3068
            try: options.try,
3069
            endTry: options.endTry
3070
        };
3071
        this.tryBranch = options.tryBranch;
28✔
3072
        this.catchStatement = options.catchStatement;
28✔
3073
        this.location = util.createBoundingLocation(
28✔
3074
            this.tokens.try,
3075
            this.tryBranch,
3076
            this.catchStatement,
3077
            this.tokens.endTry
3078
        );
3079
    }
3080

3081
    public readonly tokens: {
3082
        readonly try?: Token;
3083
        readonly endTry?: Token;
3084
    };
3085

3086
    public readonly tryBranch: Block;
3087
    public readonly catchStatement: CatchStatement;
3088

3089
    public readonly kind = AstNodeKind.TryCatchStatement;
28✔
3090

3091
    public readonly location: Location | undefined;
3092

3093
    public transpile(state: BrsTranspileState): TranspileResult {
3094
        return [
4✔
3095
            state.transpileToken(this.tokens.try, 'try'),
3096
            ...this.tryBranch.transpile(state),
3097
            state.newline,
3098
            state.indent(),
3099
            ...(this.catchStatement?.transpile(state) ?? ['catch']),
24!
3100
            state.newline,
3101
            state.indent(),
3102
            state.transpileToken(this.tokens.endTry!, 'end try')
3103
        ];
3104
    }
3105

3106
    public walk(visitor: WalkVisitor, options: WalkOptions) {
3107
        if (this.tryBranch && options.walkMode & InternalWalkMode.walkStatements) {
59!
3108
            walk(this, 'tryBranch', visitor, options);
59✔
3109
            walk(this, 'catchStatement', visitor, options);
59✔
3110
        }
3111
    }
3112

3113
    public get leadingTrivia(): Token[] {
3114
        return this.tokens.try?.leadingTrivia ?? [];
12!
3115
    }
3116

3117
    public get endTrivia(): Token[] {
3118
        return this.tokens.endTry?.leadingTrivia ?? [];
1!
3119
    }
3120
}
3121

3122
export class CatchStatement extends Statement {
1✔
3123
    constructor(options?: {
3124
        catch?: Token;
3125
        exceptionVariable?: Identifier;
3126
        catchBranch?: Block;
3127
    }) {
3128
        super();
26✔
3129
        this.tokens = {
26✔
3130
            catch: options?.catch,
78!
3131
            exceptionVariable: options?.exceptionVariable
78!
3132
        };
3133
        this.catchBranch = options?.catchBranch;
26!
3134
        this.location = util.createBoundingLocation(
26✔
3135
            this.tokens.catch,
3136
            this.tokens.exceptionVariable,
3137
            this.catchBranch
3138
        );
3139
    }
3140

3141
    public readonly tokens: {
3142
        readonly catch?: Token;
3143
        readonly exceptionVariable?: Identifier;
3144
    };
3145

3146
    public readonly catchBranch?: Block;
3147

3148
    public readonly kind = AstNodeKind.CatchStatement;
26✔
3149

3150
    public readonly location: Location | undefined;
3151

3152
    public transpile(state: BrsTranspileState): TranspileResult {
3153
        return [
4✔
3154
            state.transpileToken(this.tokens.catch, 'catch'),
3155
            ' ',
3156
            this.tokens.exceptionVariable?.text ?? 'e',
24!
3157
            ...(this.catchBranch?.transpile(state) ?? [])
24!
3158
        ];
3159
    }
3160

3161
    public walk(visitor: WalkVisitor, options: WalkOptions) {
3162
        if (this.catchBranch && options.walkMode & InternalWalkMode.walkStatements) {
57!
3163
            walk(this, 'catchBranch', visitor, options);
57✔
3164
        }
3165
    }
3166

3167
    public get leadingTrivia(): Token[] {
3168
        return this.tokens.catch?.leadingTrivia ?? [];
4!
3169
    }
3170
}
3171

3172
export class ThrowStatement extends Statement {
1✔
3173
    constructor(options?: {
3174
        throw?: Token;
3175
        expression?: Expression;
3176
    }) {
3177
        super();
10✔
3178
        this.tokens = {
10✔
3179
            throw: options.throw
3180
        };
3181
        this.expression = options.expression;
10✔
3182
        this.location = util.createBoundingLocation(
10✔
3183
            this.tokens.throw,
3184
            this.expression
3185
        );
3186
    }
3187

3188
    public readonly tokens: {
3189
        readonly throw?: Token;
3190
    };
3191
    public readonly expression?: Expression;
3192

3193
    public readonly kind = AstNodeKind.ThrowStatement;
10✔
3194

3195
    public readonly location: Location | undefined;
3196

3197
    public transpile(state: BrsTranspileState) {
3198
        const result = [
5✔
3199
            state.transpileToken(this.tokens.throw, 'throw'),
3200
            ' '
3201
        ] as TranspileResult;
3202

3203
        //if we have an expression, transpile it
3204
        if (this.expression) {
5✔
3205
            result.push(
4✔
3206
                ...this.expression.transpile(state)
3207
            );
3208

3209
            //no expression found. Rather than emit syntax errors, provide a generic error message
3210
        } else {
3211
            result.push('"User-specified exception"');
1✔
3212
        }
3213
        return result;
5✔
3214
    }
3215

3216
    public walk(visitor: WalkVisitor, options: WalkOptions) {
3217
        if (this.expression && options.walkMode & InternalWalkMode.walkExpressions) {
33✔
3218
            walk(this, 'expression', visitor, options);
26✔
3219
        }
3220
    }
3221

3222
    public get leadingTrivia(): Token[] {
3223
        return this.tokens.throw?.leadingTrivia ?? [];
14!
3224
    }
3225
}
3226

3227

3228
export class EnumStatement extends Statement implements TypedefProvider {
1✔
3229
    constructor(options: {
3230
        enum?: Token;
3231
        name: Identifier;
3232
        endEnum?: Token;
3233
        body: Array<EnumMemberStatement>;
3234
    }) {
3235
        super();
156✔
3236
        this.tokens = {
156✔
3237
            enum: options.enum,
3238
            name: options.name,
3239
            endEnum: options.endEnum
3240
        };
3241
        this.symbolTable = new SymbolTable('Enum');
156✔
3242
        this.body = options.body ?? [];
156!
3243
    }
3244

3245
    public readonly tokens: {
3246
        readonly enum?: Token;
3247
        readonly name: Identifier;
3248
        readonly endEnum?: Token;
3249
    };
3250
    public readonly body: Array<EnumMemberStatement>;
3251

3252
    public readonly kind = AstNodeKind.EnumStatement;
156✔
3253

3254
    public get location(): Location | undefined {
3255
        return util.createBoundingLocation(
136✔
3256
            this.tokens.enum,
3257
            this.tokens.name,
3258
            ...this.body,
3259
            this.tokens.endEnum
3260
        );
3261
    }
3262

3263
    public getMembers() {
3264
        const result = [] as EnumMemberStatement[];
327✔
3265
        for (const statement of this.body) {
327✔
3266
            if (isEnumMemberStatement(statement)) {
679!
3267
                result.push(statement);
679✔
3268
            }
3269
        }
3270
        return result;
327✔
3271
    }
3272

3273
    public get leadingTrivia(): Token[] {
3274
        return this.tokens.enum?.leadingTrivia;
57!
3275
    }
3276

3277
    public get endTrivia(): Token[] {
NEW
3278
        return this.tokens.endEnum?.leadingTrivia ?? [];
×
3279
    }
3280

3281
    /**
3282
     * Get a map of member names and their values.
3283
     * All values are stored as their AST LiteralExpression representation (i.e. string enum values include the wrapping quotes)
3284
     */
3285
    public getMemberValueMap() {
3286
        const result = new Map<string, string>();
59✔
3287
        const members = this.getMembers();
59✔
3288
        let currentIntValue = 0;
59✔
3289
        for (const member of members) {
59✔
3290
            //if there is no value, assume an integer and increment the int counter
3291
            if (!member.value) {
148✔
3292
                result.set(member.name?.toLowerCase(), currentIntValue.toString());
33!
3293
                currentIntValue++;
33✔
3294

3295
                //if explicit integer value, use it and increment the int counter
3296
            } else if (isLiteralExpression(member.value) && member.value.tokens.value.kind === TokenKind.IntegerLiteral) {
115✔
3297
                //try parsing as integer literal, then as hex integer literal.
3298
                let tokenIntValue = util.parseInt(member.value.tokens.value.text) ?? util.parseInt(member.value.tokens.value.text.replace(/&h/i, '0x'));
29✔
3299
                if (tokenIntValue !== undefined) {
29!
3300
                    currentIntValue = tokenIntValue;
29✔
3301
                    currentIntValue++;
29✔
3302
                }
3303
                result.set(member.name?.toLowerCase(), member.value.tokens.value.text);
29!
3304

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

3309
                //all other values
3310
            } else {
3311
                result.set(member.name?.toLowerCase(), (member.value as LiteralExpression)?.tokens?.value?.text ?? 'invalid');
85!
3312
            }
3313
        }
3314
        return result;
59✔
3315
    }
3316

3317
    public getMemberValue(name: string) {
3318
        return this.getMemberValueMap().get(name.toLowerCase());
56✔
3319
    }
3320

3321
    /**
3322
     * The name of the enum (without the namespace prefix)
3323
     */
3324
    public get name() {
3325
        return this.tokens.name?.text;
1!
3326
    }
3327

3328
    /**
3329
     * The name of the enum WITH its leading namespace (if applicable)
3330
     */
3331
    public get fullName() {
3332
        const name = this.tokens.name?.text;
674!
3333
        if (name) {
674!
3334
            const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
674✔
3335

3336
            if (namespace) {
674✔
3337
                let namespaceName = namespace.getName(ParseMode.BrighterScript);
251✔
3338
                return `${namespaceName}.${name}`;
251✔
3339
            } else {
3340
                return name;
423✔
3341
            }
3342
        } else {
3343
            //return undefined which will allow outside callers to know that this doesn't have a name
3344
            return undefined;
×
3345
        }
3346
    }
3347

3348
    transpile(state: BrsTranspileState) {
3349
        //enum declarations do not exist at runtime, so don't transpile anything...
3350
        return [
23✔
3351
            state.transpileLeadingComments(this.tokens.enum)
3352
        ];
3353
    }
3354

3355
    getTypedef(state: BrsTranspileState) {
3356
        const result = [] as TranspileResult;
1✔
3357
        for (let comment of util.getLeadingComments(this) ?? []) {
1!
NEW
3358
            result.push(
×
3359
                comment.text,
3360
                state.newline,
3361
                state.indent()
3362
            );
3363
        }
3364
        for (let annotation of this.annotations ?? []) {
1!
3365
            result.push(
×
3366
                ...annotation.getTypedef(state),
3367
                state.newline,
3368
                state.indent()
3369
            );
3370
        }
3371
        result.push(
1✔
3372
            this.tokens.enum?.text ?? 'enum',
6!
3373
            ' ',
3374
            this.tokens.name.text
3375
        );
3376
        result.push(state.newline);
1✔
3377
        state.blockDepth++;
1✔
3378
        for (const member of this.body) {
1✔
3379
            if (isTypedefProvider(member)) {
1!
3380
                result.push(
1✔
3381
                    state.indent(),
3382
                    ...member.getTypedef(state),
3383
                    state.newline
3384
                );
3385
            }
3386
        }
3387
        state.blockDepth--;
1✔
3388
        result.push(
1✔
3389
            state.indent(),
3390
            this.tokens.endEnum?.text ?? 'end enum'
6!
3391
        );
3392
        return result;
1✔
3393
    }
3394

3395
    walk(visitor: WalkVisitor, options: WalkOptions) {
3396
        if (options.walkMode & InternalWalkMode.walkStatements) {
996!
3397
            walkArray(this.body, visitor, options, this);
996✔
3398

3399
        }
3400
    }
3401

3402
    getType(options: GetTypeOptions) {
3403
        const members = this.getMembers();
134✔
3404

3405
        const resultType = new EnumType(
134✔
3406
            this.fullName,
3407
            members[0]?.getType(options).underlyingType
402✔
3408
        );
3409
        resultType.pushMemberProvider(() => this.getSymbolTable());
134✔
3410
        for (const statement of members) {
134✔
3411
            resultType.addMember(statement?.tokens?.name?.text, { definingNode: statement }, statement.getType(options), SymbolTypeFlag.runtime);
264!
3412
        }
3413
        return resultType;
134✔
3414
    }
3415
}
3416

3417
export class EnumMemberStatement extends Statement implements TypedefProvider {
1✔
3418
    public constructor(options: {
3419
        name: Identifier;
3420
        equals?: Token;
3421
        value?: Expression;
3422
    }) {
3423
        super();
311✔
3424
        this.tokens = {
311✔
3425
            name: options.name,
3426
            equals: options.equals
3427
        };
3428
        this.value = options.value;
311✔
3429
    }
3430

3431
    public readonly tokens: {
3432
        readonly name: Identifier;
3433
        readonly equals?: Token;
3434
    };
3435
    public readonly value?: Expression;
3436

3437
    public readonly kind = AstNodeKind.EnumMemberStatement;
311✔
3438

3439
    public get location() {
3440
        return util.createBoundingLocation(
443✔
3441
            this.tokens.name,
3442
            this.tokens.equals,
3443
            this.value
3444
        );
3445
    }
3446

3447
    /**
3448
     * The name of the member
3449
     */
3450
    public get name() {
3451
        return this.tokens.name.text;
405✔
3452
    }
3453

3454
    public get leadingTrivia(): Token[] {
3455
        return this.tokens.name.leadingTrivia;
20✔
3456
    }
3457

3458
    public transpile(state: BrsTranspileState): TranspileResult {
3459
        return [];
×
3460
    }
3461

3462
    getTypedef(state: BrsTranspileState): TranspileResult {
3463
        const result: TranspileResult = [];
1✔
3464
        for (let comment of util.getLeadingComments(this) ?? []) {
1!
NEW
3465
            result.push(
×
3466
                comment.text,
3467
                state.newline,
3468
                state.indent()
3469
            );
3470
        }
3471
        result.push(this.tokens.name.text);
1✔
3472
        if (this.tokens.equals) {
1!
NEW
3473
            result.push(' ', this.tokens.equals.text, ' ');
×
3474
            if (this.value) {
×
3475
                result.push(
×
3476
                    ...this.value.transpile(state)
3477
                );
3478
            }
3479
        }
3480
        return result;
1✔
3481
    }
3482

3483
    walk(visitor: WalkVisitor, options: WalkOptions) {
3484
        if (this.value && options.walkMode & InternalWalkMode.walkExpressions) {
2,384✔
3485
            walk(this, 'value', visitor, options);
1,131✔
3486
        }
3487
    }
3488

3489
    getType(options: GetTypeOptions) {
3490
        return new EnumMemberType(
398✔
3491
            (this.parent as EnumStatement)?.fullName,
1,194!
3492
            this.tokens?.name?.text,
2,388!
3493
            this.value?.getType(options)
1,194✔
3494
        );
3495
    }
3496
}
3497

3498
export class ConstStatement extends Statement implements TypedefProvider {
1✔
3499
    public constructor(options: {
3500
        const?: Token;
3501
        name: Identifier;
3502
        equals?: Token;
3503
        value: Expression;
3504
    }) {
3505
        super();
146✔
3506
        this.tokens = {
146✔
3507
            const: options.const,
3508
            name: options.name,
3509
            equals: options.equals
3510
        };
3511
        this.value = options.value;
146✔
3512
        this.location = util.createBoundingLocation(this.tokens.const, this.tokens.name, this.tokens.equals, this.value);
146✔
3513
    }
3514

3515
    public readonly tokens: {
3516
        readonly const: Token;
3517
        readonly name: Identifier;
3518
        readonly equals: Token;
3519
    };
3520
    public readonly value: Expression;
3521

3522
    public readonly kind = AstNodeKind.ConstStatement;
146✔
3523

3524
    public readonly location: Location | undefined;
3525

3526
    public get name() {
UNCOV
3527
        return this.tokens.name.text;
×
3528
    }
3529

3530
    public get leadingTrivia(): Token[] {
3531
        return this.tokens.const?.leadingTrivia;
37!
3532
    }
3533

3534
    /**
3535
     * The name of the statement WITH its leading namespace (if applicable)
3536
     */
3537
    public get fullName() {
3538
        const name = this.tokens.name?.text;
218!
3539
        if (name) {
218!
3540
            const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
218✔
3541
            if (namespace) {
218✔
3542
                let namespaceName = namespace.getName(ParseMode.BrighterScript);
195✔
3543
                return `${namespaceName}.${name}`;
195✔
3544
            } else {
3545
                return name;
23✔
3546
            }
3547
        } else {
3548
            //return undefined which will allow outside callers to know that this doesn't have a name
3549
            return undefined;
×
3550
        }
3551
    }
3552

3553
    public transpile(state: BrsTranspileState): TranspileResult {
3554
        //const declarations don't exist at runtime, so just transpile empty
3555
        return [state.transpileLeadingComments(this.tokens.const)];
21✔
3556
    }
3557

3558
    getTypedef(state: BrsTranspileState): TranspileResult {
3559
        const result: TranspileResult = [];
3✔
3560
        for (let comment of util.getLeadingComments(this) ?? []) {
3!
NEW
3561
            result.push(
×
3562
                comment.text,
3563
                state.newline,
3564
                state.indent()
3565
            );
3566
        }
3567
        result.push(
3✔
3568
            this.tokens.const ? state.tokenToSourceNode(this.tokens.const) : 'const',
3!
3569
            ' ',
3570
            state.tokenToSourceNode(this.tokens.name),
3571
            ' ',
3572
            this.tokens.equals ? state.tokenToSourceNode(this.tokens.equals) : '=',
3!
3573
            ' ',
3574
            ...this.value.transpile(state)
3575
        );
3576
        return result;
3✔
3577
    }
3578

3579
    walk(visitor: WalkVisitor, options: WalkOptions) {
3580
        if (this.value && options.walkMode & InternalWalkMode.walkExpressions) {
970✔
3581
            walk(this, 'value', visitor, options);
838✔
3582
        }
3583
    }
3584

3585
    getType(options: GetTypeOptions) {
3586
        return this.value.getType(options);
144✔
3587
    }
3588
}
3589

3590
export class ContinueStatement extends Statement {
1✔
3591
    constructor(options: {
3592
        continue?: Token;
3593
        loopType: Token;
3594
    }
3595
    ) {
3596
        super();
11✔
3597
        this.tokens = {
11✔
3598
            continue: options.continue,
3599
            loopType: options.loopType
3600
        };
3601
        this.location = util.createBoundingLocation(
11✔
3602
            this.tokens.continue,
3603
            this.tokens.loopType
3604
        );
3605
    }
3606

3607
    public readonly tokens: {
3608
        readonly continue?: Token;
3609
        readonly loopType: Token;
3610
    };
3611

3612
    public readonly kind = AstNodeKind.ContinueStatement;
11✔
3613

3614
    public readonly location: Location | undefined;
3615

3616
    transpile(state: BrsTranspileState) {
3617
        return [
3✔
3618
            state.sourceNode(this.tokens.continue, this.tokens.continue?.text ?? 'continue'),
18!
3619
            this.tokens.loopType?.leadingWhitespace ?? ' ',
18✔
3620
            state.sourceNode(this.tokens.continue, this.tokens.loopType?.text)
9✔
3621
        ];
3622
    }
3623
    walk(visitor: WalkVisitor, options: WalkOptions) {
3624
        //nothing to walk
3625
    }
3626

3627
    public get leadingTrivia(): Token[] {
3628
        return this.tokens.continue?.leadingTrivia ?? [];
6!
3629
    }
3630
}
3631

3632

3633
export class TypecastStatement extends Statement {
1✔
3634
    constructor(options: {
3635
        typecast?: Token;
3636
        typecastExpression: TypecastExpression;
3637
    }
3638
    ) {
3639
        super();
23✔
3640
        this.tokens = {
23✔
3641
            typecast: options.typecast
3642
        };
3643
        this.typecastExpression = options.typecastExpression;
23✔
3644
        this.location = util.createBoundingLocation(
23✔
3645
            this.tokens.typecast,
3646
            this.typecastExpression
3647
        );
3648
    }
3649

3650
    public readonly tokens: {
3651
        readonly typecast?: Token;
3652
    };
3653

3654
    public readonly typecastExpression: TypecastExpression;
3655

3656
    public readonly kind = AstNodeKind.TypecastStatement;
23✔
3657

3658
    public readonly location: Location;
3659

3660
    transpile(state: BrsTranspileState) {
3661
        //the typecast statement is a comment just for debugging purposes
3662
        return [
1✔
3663
            state.transpileToken(this.tokens.typecast, 'typecast', true),
3664
            ' ',
3665
            this.typecastExpression.obj.transpile(state),
3666
            ' ',
3667
            state.transpileToken(this.typecastExpression.tokens.as, 'as'),
3668
            ' ',
3669
            this.typecastExpression.typeExpression.transpile(state)
3670
        ];
3671
    }
3672

3673
    walk(visitor: WalkVisitor, options: WalkOptions) {
3674
        if (options.walkMode & InternalWalkMode.walkExpressions) {
135✔
3675
            walk(this, 'typecastExpression', visitor, options);
125✔
3676
        }
3677
    }
3678

3679
    get leadingTrivia(): Token[] {
NEW
3680
        return this.tokens.typecast?.leadingTrivia ?? [];
×
3681
    }
3682

3683
    getType(options: GetTypeOptions): BscType {
3684
        return this.typecastExpression.getType(options);
19✔
3685
    }
3686
}
3687

3688
export class ConditionalCompileErrorStatement extends Statement {
1✔
3689
    constructor(options: {
3690
        hashError?: Token;
3691
        message: Token;
3692
    }) {
3693
        super();
9✔
3694
        this.tokens = {
9✔
3695
            hashError: options.hashError,
3696
            message: options.message
3697
        };
3698
        this.location = util.createBoundingLocation(util.createBoundingLocationFromTokens(this.tokens));
9✔
3699
    }
3700

3701
    public readonly tokens: {
3702
        readonly hashError?: Token;
3703
        readonly message: Token;
3704
    };
3705

3706

3707
    public readonly kind = AstNodeKind.ConditionalCompileErrorStatement;
9✔
3708

3709
    public readonly location: Location | undefined;
3710

3711
    transpile(state: BrsTranspileState) {
3712
        return [
3✔
3713
            state.transpileToken(this.tokens.hashError, '#error'),
3714
            ' ',
3715
            state.transpileToken(this.tokens.message)
3716

3717
        ];
3718
    }
3719

3720
    walk(visitor: WalkVisitor, options: WalkOptions) {
3721
        // nothing to walk
3722
    }
3723

3724
    get leadingTrivia(): Token[] {
3725
        return this.tokens.hashError.leadingTrivia ?? [];
6!
3726
    }
3727
}
3728

3729
export class AliasStatement extends Statement {
1✔
3730
    constructor(options: {
3731
        alias?: Token;
3732
        name: Token;
3733
        equals?: Token;
3734
        value: VariableExpression | DottedGetExpression;
3735
    }
3736
    ) {
3737
        super();
32✔
3738
        this.tokens = {
32✔
3739
            alias: options.alias,
3740
            name: options.name,
3741
            equals: options.equals
3742
        };
3743
        this.value = options.value;
32✔
3744
        this.location = util.createBoundingLocation(
32✔
3745
            this.tokens.alias,
3746
            this.tokens.name,
3747
            this.tokens.equals,
3748
            this.value
3749
        );
3750
    }
3751

3752
    public readonly tokens: {
3753
        readonly alias?: Token;
3754
        readonly name: Token;
3755
        readonly equals?: Token;
3756
    };
3757

3758
    public readonly value: Expression;
3759

3760
    public readonly kind = AstNodeKind.AliasStatement;
32✔
3761

3762
    public readonly location: Location;
3763

3764
    transpile(state: BrsTranspileState) {
3765
        //the alias statement is a comment just for debugging purposes
3766
        return [
12✔
3767
            state.transpileToken(this.tokens.alias, 'alias', true),
3768
            ' ',
3769
            state.transpileToken(this.tokens.name),
3770
            ' ',
3771
            state.transpileToken(this.tokens.equals, '='),
3772
            ' ',
3773
            this.value.transpile(state)
3774
        ];
3775
    }
3776

3777
    walk(visitor: WalkVisitor, options: WalkOptions) {
3778
        if (options.walkMode & InternalWalkMode.walkExpressions) {
221✔
3779
            walk(this, 'value', visitor, options);
192✔
3780
        }
3781
    }
3782

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

3787
    getType(options: GetTypeOptions): BscType {
3788
        return this.value.getType(options);
1✔
3789
    }
3790
}
3791

3792
export class ConditionalCompileStatement extends Statement {
1✔
3793
    constructor(options: {
3794
        hashIf?: Token;
3795
        not?: Token;
3796
        condition: Token;
3797
        hashElse?: Token;
3798
        hashEndIf?: Token;
3799
        thenBranch: Block;
3800
        elseBranch?: ConditionalCompileStatement | Block;
3801
    }) {
3802
        super();
54✔
3803
        this.thenBranch = options.thenBranch;
54✔
3804
        this.elseBranch = options.elseBranch;
54✔
3805

3806
        this.tokens = {
54✔
3807
            hashIf: options.hashIf,
3808
            not: options.not,
3809
            condition: options.condition,
3810
            hashElse: options.hashElse,
3811
            hashEndIf: options.hashEndIf
3812
        };
3813

3814
        this.location = util.createBoundingLocation(
54✔
3815
            util.createBoundingLocationFromTokens(this.tokens),
3816
            this.thenBranch,
3817
            this.elseBranch
3818
        );
3819
    }
3820

3821
    readonly tokens: {
3822
        readonly hashIf?: Token;
3823
        readonly not?: Token;
3824
        readonly condition: Token;
3825
        readonly hashElse?: Token;
3826
        readonly hashEndIf?: Token;
3827
    };
3828
    public readonly thenBranch: Block;
3829
    public readonly elseBranch?: ConditionalCompileStatement | Block;
3830

3831
    public readonly kind = AstNodeKind.ConditionalCompileStatement;
54✔
3832

3833
    public readonly location: Location | undefined;
3834

3835
    transpile(state: BrsTranspileState) {
3836
        let results = [] as TranspileResult;
6✔
3837
        //if   (already indented by block)
3838
        if (!state.conditionalCompileStatement) {
6✔
3839
            // only transpile the #if in the case when we're not in a conditionalCompileStatement already
3840
            results.push(state.transpileToken(this.tokens.hashIf ?? createToken(TokenKind.HashIf)));
3!
3841
        }
3842

3843
        results.push(' ');
6✔
3844
        //conditions
3845
        if (this.tokens.not) {
6✔
3846
            results.push('not');
2✔
3847
            results.push(' ');
2✔
3848
        }
3849
        results.push(state.transpileToken(this.tokens.condition));
6✔
3850
        state.lineage.unshift(this);
6✔
3851

3852
        //if statement body
3853
        let thenNodes = this.thenBranch.transpile(state);
6✔
3854
        state.lineage.shift();
6✔
3855
        if (thenNodes.length > 0) {
6!
3856
            results.push(thenNodes);
6✔
3857
        }
3858
        //else branch
3859
        if (this.elseBranch) {
6!
3860
            const elseIsCC = isConditionalCompileStatement(this.elseBranch);
6✔
3861
            const endBlockToken = elseIsCC ? (this.elseBranch as ConditionalCompileStatement).tokens.hashIf ?? createToken(TokenKind.HashElseIf) : this.tokens.hashElse;
6!
3862
            //else
3863

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

3866
            if (elseIsCC) {
6✔
3867
                //chained else if
3868
                state.lineage.unshift(this.elseBranch);
3✔
3869

3870
                // transpile following #if with knowledge of current
3871
                const existingCCStmt = state.conditionalCompileStatement;
3✔
3872
                state.conditionalCompileStatement = this;
3✔
3873
                let body = this.elseBranch.transpile(state);
3✔
3874
                state.conditionalCompileStatement = existingCCStmt;
3✔
3875

3876
                state.lineage.shift();
3✔
3877

3878
                if (body.length > 0) {
3!
3879
                    //zero or more spaces between the `else` and the `if`
3880
                    results.push(...body);
3✔
3881

3882
                    // stop here because chained if will transpile the rest
3883
                    return results;
3✔
3884
                } else {
NEW
3885
                    results.push('\n');
×
3886
                }
3887

3888
            } else {
3889
                //else body
3890
                state.lineage.unshift(this.tokens.hashElse!);
3✔
3891
                let body = this.elseBranch.transpile(state);
3✔
3892
                state.lineage.shift();
3✔
3893

3894
                if (body.length > 0) {
3!
3895
                    results.push(...body);
3✔
3896
                }
3897
            }
3898
        }
3899

3900
        //end if
3901
        results.push(...state.transpileEndBlockToken(this.elseBranch ?? this.thenBranch, this.tokens.hashEndIf, '#end if'));
3!
3902

3903
        return results;
3✔
3904
    }
3905

3906
    walk(visitor: WalkVisitor, options: WalkOptions) {
3907
        if (options.walkMode & InternalWalkMode.walkStatements) {
185!
3908
            const bsConsts = options.bsConsts ?? this.getBsConsts();
185!
3909
            let conditionTrue = bsConsts?.get(this.tokens.condition.text.toLowerCase());
185!
3910
            if (this.tokens.not) {
185✔
3911
                // flips the boolean value
3912
                conditionTrue = !conditionTrue;
23✔
3913
            }
3914
            const walkFalseBlocks = options.walkMode & InternalWalkMode.visitFalseConditionalCompilationBlocks;
185✔
3915
            if (conditionTrue || walkFalseBlocks) {
185✔
3916
                walk(this, 'thenBranch', visitor, options);
123✔
3917
            }
3918
            if (this.elseBranch && (!conditionTrue || walkFalseBlocks)) {
185✔
3919
                walk(this, 'elseBranch', visitor, options);
52✔
3920
            }
3921
        }
3922
    }
3923

3924
    get leadingTrivia(): Token[] {
3925
        return this.tokens.hashIf?.leadingTrivia ?? [];
6!
3926
    }
3927
}
3928

3929

3930
export class ConditionalCompileConstStatement extends Statement {
1✔
3931
    constructor(options: {
3932
        hashConst?: Token;
3933
        assignment: AssignmentStatement;
3934
    }) {
3935
        super();
17✔
3936
        this.tokens = {
17✔
3937
            hashConst: options.hashConst
3938
        };
3939
        this.assignment = options.assignment;
17✔
3940
        this.location = util.createBoundingLocation(util.createBoundingLocationFromTokens(this.tokens), this.assignment);
17✔
3941
    }
3942

3943
    public readonly tokens: {
3944
        readonly hashConst?: Token;
3945
    };
3946

3947
    public readonly assignment: AssignmentStatement;
3948

3949
    public readonly kind = AstNodeKind.ConditionalCompileConstStatement;
17✔
3950

3951
    public readonly location: Location | undefined;
3952

3953
    transpile(state: BrsTranspileState) {
3954
        return [
3✔
3955
            state.transpileToken(this.tokens.hashConst, '#const'),
3956
            ' ',
3957
            state.transpileToken(this.assignment.tokens.name),
3958
            ' ',
3959
            state.transpileToken(this.assignment.tokens.equals, '='),
3960
            ' ',
3961
            ...this.assignment.value.transpile(state)
3962
        ];
3963

3964
    }
3965

3966
    walk(visitor: WalkVisitor, options: WalkOptions) {
3967
        // nothing to walk
3968
    }
3969

3970

3971
    get leadingTrivia(): Token[] {
3972
        return this.tokens.hashConst.leadingTrivia ?? [];
6!
3973
    }
3974
}
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