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

rokucommunity / brighterscript / #12930

13 Aug 2024 05:02PM UTC coverage: 86.193% (-1.7%) from 87.933%
#12930

push

web-flow
Merge 58ad447a2 into 0e968f1c3

10630 of 13125 branches covered (80.99%)

Branch coverage included in aggregate %.

6675 of 7284 new or added lines in 99 files covered. (91.64%)

84 existing lines in 18 files now uncovered.

12312 of 13492 relevant lines covered (91.25%)

26865.48 hits per line

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

85.18
/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,604✔
59
        this.statements = options?.statements ?? [];
6,604!
60
    }
61

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

65
    public readonly symbolTable = new SymbolTable('Body', () => this.parent?.getSymbolTable());
7,074✔
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(
689✔
70
            ...(this.statements ?? [])
2,067!
71
        );
72
    }
73

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

81
            if (!previousStatement) {
1,419✔
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) {
778!
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)) {
770✔
91
                result.push(state.newline, state.newline);
313✔
92

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

101
            result.push(...statement.transpile(state));
1,419✔
102
        }
103
        return result;
646✔
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,534!
123
            walkArray(this.statements, visitor, options, this);
13,534✔
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,392✔
137
        this.value = options.value;
1,392✔
138
        this.tokens = {
1,392✔
139
            equals: options.equals,
140
            name: options.name,
141
            as: options.as
142
        };
143
        this.typeExpression = options.typeExpression;
1,392✔
144
        this.location = util.createBoundingLocation(util.createBoundingLocationFromTokens(this.tokens), this.value);
1,392✔
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,392✔
158

159
    public readonly location: Location | undefined;
160

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

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

178
    getType(options: GetTypeOptions) {
179
        const variableType = this.typeExpression?.getType({ ...options, typeChain: undefined }) ?? this.value.getType({ ...options, typeChain: undefined });
1,201✔
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,201!
184
        return variableType;
1,201✔
185
    }
186

187
    get leadingTrivia(): Token[] {
188
        return this.tokens.name.leadingTrivia;
1,098✔
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();
6,022✔
258
        this.statements = options.statements;
6,022✔
259
    }
260

261
    public readonly statements: Statement[];
262

263
    public readonly kind = AstNodeKind.Block;
6,022✔
264

265
    get location(): Location {
266
        if (this.statements.length > 0) {
3,788✔
267
            return util.createBoundingLocation(...this.statements);
3,326✔
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,901✔
365
        let results = [] as TranspileResult;
3,901✔
366
        for (let i = 0; i < this.statements.length; i++) {
3,901✔
367
            let previousStatement = this.statements[i - 1];
4,720✔
368
            let statement = this.statements[i];
4,720✔
369
            //is not a comment
370
            //if comment is on same line as parent
371
            if (util.isLeadingCommentOnSameLine(state.lineage[0]?.location, statement) ||
4,720!
372
                util.isLeadingCommentOnSameLine(previousStatement?.location, statement)
14,112✔
373
            ) {
374
                results.push(' ');
50✔
375

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

385
            //push block onto parent list
386
            state.lineage.unshift(this);
4,720✔
387
            results.push(
4,720✔
388
                ...statement.transpile(state)
389
            );
390
            state.lineage.shift();
4,720✔
391
        }
392
        state.blockDepth--;
3,901✔
393
        return results;
3,901✔
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,572✔
402
            walkArray(this.statements, visitor, options, this);
23,566✔
403
        }
404
    }
405

406
}
407

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

419
    public readonly location: Location | undefined;
420

421
    transpile(state: BrsTranspileState) {
422
        return [
56✔
423
            state.transpileAnnotations(this),
424
            this.expression.transpile(state)
425
        ];
426
    }
427

428
    walk(visitor: WalkVisitor, options: WalkOptions) {
429
        if (options.walkMode & InternalWalkMode.walkExpressions) {
2,797✔
430
            walk(this, 'expression', visitor, options);
2,769✔
431
        }
432
    }
433

434
    get leadingTrivia(): Token[] {
435
        return this.expression.leadingTrivia;
116✔
436
    }
437
}
438

439

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

451
    public readonly tokens: {
452
        readonly exitFor?: Token;
453
    };
454

455
    public readonly kind = AstNodeKind.ExitForStatement;
5✔
456

457
    public readonly location?: Location;
458

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

463
    walk(visitor: WalkVisitor, options: WalkOptions) {
464
        //nothing to walk
465
    }
466

467
    get leadingTrivia(): Token[] {
468
        return this.tokens.exitFor?.leadingTrivia;
8!
469
    }
470

471
}
472

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

484
    public readonly tokens: {
485
        readonly exitWhile?: Token;
486
    };
487

488
    public readonly kind = AstNodeKind.ExitWhileStatement;
8✔
489

490
    public readonly location?: Location;
491

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

496
    walk(visitor: WalkVisitor, options: WalkOptions) {
497
        //nothing to walk
498
    }
499

500
    get leadingTrivia(): Token[] {
501
        return this.tokens.exitWhile?.leadingTrivia;
10!
502
    }
503
}
504

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

518
        this.location = this.func.location;
3,587✔
519
    }
520

521
    public readonly tokens: {
522
        readonly name: Identifier;
523
    };
524
    public readonly func: FunctionExpression;
525

526
    public readonly kind = AstNodeKind.FunctionStatement as AstNodeKind;
3,587✔
527

528
    public readonly location: Location | undefined;
529

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

544
    public get leadingTrivia(): Token[] {
545
        return this.func.leadingTrivia;
2,607✔
546
    }
547

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

555
        return [
1,235✔
556
            ...state.transpileAnnotations(this),
557
            ...this.func.transpile(state, nameToken)
558
        ];
559
    }
560

561
    getTypedef(state: BrsTranspileState) {
562
        let result: TranspileResult = [];
39✔
563
        for (let comment of util.getLeadingComments(this) ?? []) {
39!
564
            result.push(
154✔
565
                comment.text,
566
                state.newline,
567
                state.indent()
568
            );
569
        }
570
        for (let annotation of this.annotations ?? []) {
39✔
571
            result.push(
2✔
572
                ...annotation.getTypedef(state),
573
                state.newline,
574
                state.indent()
575
            );
576
        }
577

578
        result.push(
39✔
579
            ...this.func.getTypedef(state)
580
        );
581
        return result;
39✔
582
    }
583

584
    walk(visitor: WalkVisitor, options: WalkOptions) {
585
        if (options.walkMode & InternalWalkMode.walkExpressions) {
14,686✔
586
            walk(this, 'func', visitor, options);
13,025✔
587
        }
588
    }
589

590
    getType(options: GetTypeOptions) {
591
        const funcExprType = this.func.getType(options);
3,071✔
592
        funcExprType.setName(this.getName(ParseMode.BrighterScript));
3,071✔
593
        return funcExprType;
3,071✔
594
    }
595
}
596

597
export class IfStatement extends Statement {
1✔
598
    constructor(options: {
599
        if?: Token;
600
        then?: Token;
601
        else?: Token;
602
        endIf?: Token;
603

604
        condition: Expression;
605
        thenBranch: Block;
606
        elseBranch?: IfStatement | Block;
607
    }) {
608
        super();
1,872✔
609
        this.condition = options.condition;
1,872✔
610
        this.thenBranch = options.thenBranch;
1,872✔
611
        this.elseBranch = options.elseBranch;
1,872✔
612

613
        this.tokens = {
1,872✔
614
            if: options.if,
615
            then: options.then,
616
            else: options.else,
617
            endIf: options.endIf
618
        };
619

620
        this.location = util.createBoundingLocation(
1,872✔
621
            util.createBoundingLocationFromTokens(this.tokens),
622
            this.condition,
623
            this.thenBranch,
624
            this.elseBranch
625
        );
626
    }
627

628
    readonly tokens: {
629
        readonly if?: Token;
630
        readonly then?: Token;
631
        readonly else?: Token;
632
        readonly endIf?: Token;
633
    };
634
    public readonly condition: Expression;
635
    public readonly thenBranch: Block;
636
    public readonly elseBranch?: IfStatement | Block;
637

638
    public readonly kind = AstNodeKind.IfStatement;
1,872✔
639

640
    public readonly location: Location | undefined;
641

642
    get isInline() {
643
        const allLeadingTrivia = [
12✔
644
            ...this.thenBranch.leadingTrivia,
645
            ...this.thenBranch.statements.map(s => s.leadingTrivia).flat(),
16✔
646
            ...(this.tokens.else?.leadingTrivia ?? []),
72✔
647
            ...(this.tokens.endIf?.leadingTrivia ?? [])
72✔
648
        ];
649

650
        const hasNewline = allLeadingTrivia.find(t => t.kind === TokenKind.Newline);
29✔
651
        return !hasNewline;
12✔
652
    }
653

654
    transpile(state: BrsTranspileState) {
655
        let results = [] as TranspileResult;
1,926✔
656
        //if   (already indented by block)
657
        results.push(state.transpileToken(this.tokens.if ?? createToken(TokenKind.If)));
1,926!
658
        results.push(' ');
1,926✔
659
        //conditions
660
        results.push(...this.condition.transpile(state));
1,926✔
661
        //then
662
        if (this.tokens.then) {
1,926✔
663
            results.push(' ');
1,602✔
664
            results.push(
1,602✔
665
                state.transpileToken(this.tokens.then)
666
            );
667
        }
668
        state.lineage.unshift(this);
1,926✔
669

670
        //if statement body
671
        let thenNodes = this.thenBranch.transpile(state);
1,926✔
672
        state.lineage.shift();
1,926✔
673
        if (thenNodes.length > 0) {
1,926✔
674
            results.push(thenNodes);
1,915✔
675
        }
676
        //else branch
677
        if (this.elseBranch) {
1,926✔
678
            //else
679
            results.push(...state.transpileEndBlockToken(this.thenBranch, this.tokens.else, 'else'));
1,594✔
680

681
            if (isIfStatement(this.elseBranch)) {
1,594✔
682
                //chained elseif
683
                state.lineage.unshift(this.elseBranch);
958✔
684
                let body = this.elseBranch.transpile(state);
958✔
685
                state.lineage.shift();
958✔
686

687
                if (body.length > 0) {
958!
688
                    //zero or more spaces between the `else` and the `if`
689
                    results.push(this.elseBranch.tokens.if.leadingWhitespace!);
958✔
690
                    results.push(...body);
958✔
691

692
                    // stop here because chained if will transpile the rest
693
                    return results;
958✔
694
                } else {
695
                    results.push('\n');
×
696
                }
697

698
            } else {
699
                //else body
700
                state.lineage.unshift(this.tokens.else!);
636✔
701
                let body = this.elseBranch.transpile(state);
636✔
702
                state.lineage.shift();
636✔
703

704
                if (body.length > 0) {
636✔
705
                    results.push(...body);
634✔
706
                }
707
            }
708
        }
709

710
        //end if
711
        results.push(...state.transpileEndBlockToken(this.elseBranch ?? this.thenBranch, this.tokens.endIf, 'end if'));
968✔
712

713
        return results;
968✔
714
    }
715

716
    walk(visitor: WalkVisitor, options: WalkOptions) {
717
        if (options.walkMode & InternalWalkMode.walkExpressions) {
5,944✔
718
            walk(this, 'condition', visitor, options);
5,928✔
719
        }
720
        if (options.walkMode & InternalWalkMode.walkStatements) {
5,944✔
721
            walk(this, 'thenBranch', visitor, options);
5,942✔
722
        }
723
        if (this.elseBranch && options.walkMode & InternalWalkMode.walkStatements) {
5,944✔
724
            walk(this, 'elseBranch', visitor, options);
4,685✔
725
        }
726
    }
727

728
    get leadingTrivia(): Token[] {
729
        return this.tokens.if?.leadingTrivia ?? [];
1,941!
730
    }
731

732
    get endTrivia(): Token[] {
733
        return this.tokens.endIf?.leadingTrivia ?? [];
1!
734
    }
735

736
}
737

738
export class IncrementStatement extends Statement {
1✔
739
    constructor(options: {
740
        value: Expression;
741
        operator: Token;
742
    }) {
743
        super();
21✔
744
        this.value = options.value;
21✔
745
        this.tokens = {
21✔
746
            operator: options.operator
747
        };
748
        this.location = util.createBoundingLocation(
21✔
749
            this.value,
750
            this.tokens.operator
751
        );
752
    }
753

754
    public readonly value: Expression;
755
    public readonly tokens: {
756
        readonly operator: Token;
757
    };
758

759
    public readonly kind = AstNodeKind.IncrementStatement;
21✔
760

761
    public readonly location: Location | undefined;
762

763
    transpile(state: BrsTranspileState) {
764
        return [
6✔
765
            ...this.value.transpile(state),
766
            state.transpileToken(this.tokens.operator)
767
        ];
768
    }
769

770
    walk(visitor: WalkVisitor, options: WalkOptions) {
771
        if (options.walkMode & InternalWalkMode.walkExpressions) {
73✔
772
            walk(this, 'value', visitor, options);
72✔
773
        }
774
    }
775

776
    get leadingTrivia(): Token[] {
777
        return this.value?.leadingTrivia ?? [];
15!
778
    }
779
}
780

781
/** Used to indent the current `print` position to the next 16-character-width output zone. */
782
export interface PrintSeparatorTab extends Token {
783
    kind: TokenKind.Comma;
784
}
785

786
/** Used to insert a single whitespace character at the current `print` position. */
787
export interface PrintSeparatorSpace extends Token {
788
    kind: TokenKind.Semicolon;
789
}
790

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

821
    public readonly location: Location | undefined;
822

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

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

852
    get leadingTrivia(): Token[] {
853
        return this.tokens.print?.leadingTrivia ?? [];
450!
854
    }
855
}
856

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

882
    public readonly tokens: {
883
        readonly dim?: Token;
884
        readonly name: Identifier;
885
        readonly openingSquare?: Token;
886
        readonly closingSquare?: Token;
887
    };
888
    public readonly dimensions: Expression[];
889

890
    public readonly kind = AstNodeKind.DimStatement;
40✔
891

892
    public readonly location: Location | undefined;
893

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

913
    public walk(visitor: WalkVisitor, options: WalkOptions) {
914
        if (this.dimensions?.length !== undefined && this.dimensions?.length > 0 && options.walkMode & InternalWalkMode.walkExpressions) {
131!
915
            walkArray(this.dimensions, visitor, options, this);
116✔
916

917
        }
918
    }
919

920
    public getType(options: GetTypeOptions): BscType {
921
        const numDimensions = this.dimensions?.length ?? 1;
18!
922
        let type = new ArrayType();
18✔
923
        for (let i = 0; i < numDimensions - 1; i++) {
18✔
924
            type = new ArrayType(type);
17✔
925
        }
926
        return type;
18✔
927
    }
928

929
    get leadingTrivia(): Token[] {
930
        return this.tokens.dim?.leadingTrivia ?? [];
30!
931
    }
932
}
933

934
export class GotoStatement extends Statement {
1✔
935
    constructor(options: {
936
        goto?: Token;
937
        label: Token;
938
    }) {
939
        super();
11✔
940
        this.tokens = {
11✔
941
            goto: options.goto,
942
            label: options.label
943
        };
944
        this.location = util.createBoundingLocation(
11✔
945
            this.tokens.goto,
946
            this.tokens.label
947
        );
948
    }
949

950
    public readonly tokens: {
951
        readonly goto?: Token;
952
        readonly label: Token;
953
    };
954

955
    public readonly kind = AstNodeKind.GotoStatement;
11✔
956

957
    public readonly location: Location | undefined;
958

959
    transpile(state: BrsTranspileState) {
960
        return [
2✔
961
            state.transpileToken(this.tokens.goto, 'goto'),
962
            ' ',
963
            state.transpileToken(this.tokens.label)
964
        ];
965
    }
966

967
    walk(visitor: WalkVisitor, options: WalkOptions) {
968
        //nothing to walk
969
    }
970

971
    get leadingTrivia(): Token[] {
972
        return this.tokens.goto?.leadingTrivia ?? [];
8!
973
    }
974
}
975

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

997
    public readonly location: Location | undefined;
998

999
    public get leadingTrivia(): Token[] {
1000
        return this.tokens.name.leadingTrivia;
8✔
1001
    }
1002

1003
    transpile(state: BrsTranspileState) {
1004
        return [
2✔
1005
            state.transpileToken(this.tokens.name),
1006
            state.transpileToken(this.tokens.colon, ':')
1007

1008
        ];
1009
    }
1010

1011
    walk(visitor: WalkVisitor, options: WalkOptions) {
1012
        //nothing to walk
1013
    }
1014
}
1015

1016
export class ReturnStatement extends Statement {
1✔
1017
    constructor(options?: {
1018
        return?: Token;
1019
        value?: Expression;
1020
    }) {
1021
        super();
2,866✔
1022
        this.tokens = {
2,866✔
1023
            return: options?.return
8,598!
1024
        };
1025
        this.value = options?.value;
2,866!
1026
        this.location = util.createBoundingLocation(
2,866✔
1027
            this.tokens.return,
1028
            this.value
1029
        );
1030
    }
1031

1032
    public readonly tokens: {
1033
        readonly return?: Token;
1034
    };
1035
    public readonly value?: Expression;
1036
    public readonly kind = AstNodeKind.ReturnStatement;
2,866✔
1037

1038
    public readonly location: Location | undefined;
1039

1040
    transpile(state: BrsTranspileState) {
1041
        let result = [] as TranspileResult;
2,836✔
1042
        result.push(
2,836✔
1043
            state.transpileToken(this.tokens.return, 'return')
1044
        );
1045
        if (this.value) {
2,836✔
1046
            result.push(' ');
2,835✔
1047
            result.push(...this.value.transpile(state));
2,835✔
1048
        }
1049
        return result;
2,836✔
1050
    }
1051

1052
    walk(visitor: WalkVisitor, options: WalkOptions) {
1053
        if (options.walkMode & InternalWalkMode.walkExpressions) {
9,670✔
1054
            walk(this, 'value', visitor, options);
9,652✔
1055
        }
1056
    }
1057

1058
    get leadingTrivia(): Token[] {
1059
        return this.tokens.return?.leadingTrivia ?? [];
5,684!
1060
    }
1061
}
1062

1063
export class EndStatement extends Statement {
1✔
1064
    constructor(options?: {
1065
        end?: Token;
1066
    }) {
1067
        super();
9✔
1068
        this.tokens = {
9✔
1069
            end: options?.end
27!
1070
        };
1071
        this.location = this.tokens.end?.location;
9!
1072
    }
1073
    public readonly tokens: {
1074
        readonly end?: Token;
1075
    };
1076
    public readonly kind = AstNodeKind.EndStatement;
9✔
1077

1078
    public readonly location: Location;
1079

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

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

1090
    get leadingTrivia(): Token[] {
1091
        return this.tokens.end?.leadingTrivia ?? [];
8!
1092
    }
1093
}
1094

1095
export class StopStatement extends Statement {
1✔
1096
    constructor(options?: {
1097
        stop?: Token;
1098
    }) {
1099
        super();
17✔
1100
        this.tokens = { stop: options?.stop };
17!
1101
        this.location = this.tokens?.stop?.location;
17!
1102
    }
1103
    public readonly tokens: {
1104
        readonly stop?: Token;
1105
    };
1106

1107
    public readonly kind = AstNodeKind.StopStatement;
17✔
1108

1109
    public readonly location: Location;
1110

1111
    transpile(state: BrsTranspileState) {
1112
        return [
2✔
1113
            state.transpileToken(this.tokens.stop, 'stop')
1114
        ];
1115
    }
1116

1117
    walk(visitor: WalkVisitor, options: WalkOptions) {
1118
        //nothing to walk
1119
    }
1120

1121
    get leadingTrivia(): Token[] {
1122
        return this.tokens.stop?.leadingTrivia ?? [];
8!
1123
    }
1124
}
1125

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

1149
        this.location = util.createBoundingLocation(
31✔
1150
            this.tokens.for,
1151
            this.counterDeclaration,
1152
            this.tokens.to,
1153
            this.finalValue,
1154
            this.tokens.step,
1155
            this.increment,
1156
            this.body,
1157
            this.tokens.endFor
1158
        );
1159
    }
1160

1161
    public readonly tokens: {
1162
        readonly for?: Token;
1163
        readonly to?: Token;
1164
        readonly endFor?: Token;
1165
        readonly step?: Token;
1166
    };
1167

1168
    public readonly counterDeclaration: AssignmentStatement;
1169
    public readonly finalValue: Expression;
1170
    public readonly body: Block;
1171
    public readonly increment?: Expression;
1172

1173
    public readonly kind = AstNodeKind.ForStatement;
31✔
1174

1175
    public readonly location: Location | undefined;
1176

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

1210
        //end for
1211
        result.push(...state.transpileEndBlockToken(this.body, this.tokens.endFor, 'end for'));
9✔
1212

1213
        return result;
9✔
1214
    }
1215

1216
    walk(visitor: WalkVisitor, options: WalkOptions) {
1217
        if (options.walkMode & InternalWalkMode.walkStatements) {
122✔
1218
            walk(this, 'counterDeclaration', visitor, options);
121✔
1219
        }
1220
        if (options.walkMode & InternalWalkMode.walkExpressions) {
122✔
1221
            walk(this, 'finalValue', visitor, options);
118✔
1222
            walk(this, 'increment', visitor, options);
118✔
1223
        }
1224
        if (options.walkMode & InternalWalkMode.walkStatements) {
122✔
1225
            walk(this, 'body', visitor, options);
121✔
1226
        }
1227
    }
1228

1229
    get leadingTrivia(): Token[] {
1230
        return this.tokens.for?.leadingTrivia ?? [];
22!
1231
    }
1232

1233
    public get endTrivia(): Token[] {
NEW
1234
        return this.tokens.endFor?.leadingTrivia ?? [];
×
1235
    }
1236
}
1237

1238
export class ForEachStatement extends Statement {
1✔
1239
    constructor(options: {
1240
        forEach?: Token;
1241
        item: Token;
1242
        in?: Token;
1243
        target: Expression;
1244
        body: Block;
1245
        endFor?: Token;
1246
    }) {
1247
        super();
34✔
1248
        this.tokens = {
34✔
1249
            forEach: options.forEach,
1250
            item: options.item,
1251
            in: options.in,
1252
            endFor: options.endFor
1253
        };
1254
        this.body = options.body;
34✔
1255
        this.target = options.target;
34✔
1256

1257
        this.location = util.createBoundingLocation(
34✔
1258
            this.tokens.forEach,
1259
            this.tokens.item,
1260
            this.tokens.in,
1261
            this.target,
1262
            this.body,
1263
            this.tokens.endFor
1264
        );
1265
    }
1266

1267
    public readonly tokens: {
1268
        readonly forEach?: Token;
1269
        readonly item: Token;
1270
        readonly in?: Token;
1271
        readonly endFor?: Token;
1272
    };
1273
    public readonly body: Block;
1274
    public readonly target: Expression;
1275

1276
    public readonly kind = AstNodeKind.ForEachStatement;
34✔
1277

1278
    public readonly location: Location | undefined;
1279

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

1304
        //end for
1305
        result.push(...state.transpileEndBlockToken(this.body, this.tokens.endFor, 'end for'));
5✔
1306

1307
        return result;
5✔
1308
    }
1309

1310
    walk(visitor: WalkVisitor, options: WalkOptions) {
1311
        if (options.walkMode & InternalWalkMode.walkExpressions) {
161✔
1312
            walk(this, 'target', visitor, options);
154✔
1313
        }
1314
        if (options.walkMode & InternalWalkMode.walkStatements) {
161✔
1315
            walk(this, 'body', visitor, options);
160✔
1316
        }
1317
    }
1318

1319
    getType(options: GetTypeOptions): BscType {
1320
        return this.getSymbolTable().getSymbolType(this.tokens.item.text, options);
24✔
1321
    }
1322

1323
    get leadingTrivia(): Token[] {
1324
        return this.tokens.forEach?.leadingTrivia ?? [];
16!
1325
    }
1326

1327
    public get endTrivia(): Token[] {
1328
        return this.tokens.endFor?.leadingTrivia ?? [];
1!
1329
    }
1330
}
1331

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

1354
    public readonly tokens: {
1355
        readonly while?: Token;
1356
        readonly endWhile?: Token;
1357
    };
1358
    public readonly condition: Expression;
1359
    public readonly body: Block;
1360

1361
    public readonly kind = AstNodeKind.WhileStatement;
23✔
1362

1363
    public readonly location: Location | undefined;
1364

1365
    transpile(state: BrsTranspileState) {
1366
        let result = [] as TranspileResult;
4✔
1367
        //while
1368
        result.push(
4✔
1369
            state.transpileToken(this.tokens.while, 'while'),
1370
            ' '
1371
        );
1372
        //condition
1373
        result.push(
4✔
1374
            ...this.condition.transpile(state)
1375
        );
1376
        state.lineage.unshift(this);
4✔
1377
        //body
1378
        result.push(...this.body.transpile(state));
4✔
1379
        state.lineage.shift();
4✔
1380

1381
        //end while
1382
        result.push(...state.transpileEndBlockToken(this.body, this.tokens.endWhile, 'end while'));
4✔
1383

1384
        return result;
4✔
1385
    }
1386

1387
    walk(visitor: WalkVisitor, options: WalkOptions) {
1388
        if (options.walkMode & InternalWalkMode.walkExpressions) {
84✔
1389
            walk(this, 'condition', visitor, options);
81✔
1390
        }
1391
        if (options.walkMode & InternalWalkMode.walkStatements) {
84✔
1392
            walk(this, 'body', visitor, options);
83✔
1393
        }
1394
    }
1395

1396
    get leadingTrivia(): Token[] {
1397
        return this.tokens.while?.leadingTrivia ?? [];
12!
1398
    }
1399

1400
    public get endTrivia(): Token[] {
1401
        return this.tokens.endWhile?.leadingTrivia ?? [];
1!
1402
    }
1403
}
1404

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

1434
    public readonly obj: Expression;
1435
    public readonly value: Expression;
1436

1437
    public readonly kind = AstNodeKind.DottedSetStatement;
289✔
1438

1439
    public readonly location: Location | undefined;
1440

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

1456
    }
1457

1458
    walk(visitor: WalkVisitor, options: WalkOptions) {
1459
        if (options.walkMode & InternalWalkMode.walkExpressions) {
708✔
1460
            walk(this, 'obj', visitor, options);
706✔
1461
            walk(this, 'value', visitor, options);
706✔
1462
        }
1463
    }
1464

1465
    getType(options: GetTypeOptions) {
1466
        const objType = this.obj?.getType(options);
79!
1467
        const result = objType?.getMemberType(this.tokens.name?.text, options);
79!
1468
        options.typeChain?.push(new TypeChainEntry({
79✔
1469
            name: this.tokens.name?.text,
234!
1470
            type: result, data: options.data,
1471
            location: this.tokens.name?.location,
234!
1472
            astNode: this
1473
        }));
1474
        return result;
79✔
1475
    }
1476

1477
    get leadingTrivia(): Token[] {
1478
        return this.obj.leadingTrivia;
14✔
1479
    }
1480
}
1481

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

1509
    public readonly tokens: {
1510
        readonly openingSquare?: Token;
1511
        readonly closingSquare?: Token;
1512
        readonly equals?: Token;
1513
    };
1514
    public readonly obj: Expression;
1515
    public readonly indexes: Expression[];
1516
    public readonly value: Expression;
1517

1518
    public readonly kind = AstNodeKind.IndexedSetStatement;
25✔
1519

1520
    public readonly location: Location | undefined;
1521

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

1549
    }
1550

1551
    walk(visitor: WalkVisitor, options: WalkOptions) {
1552
        if (options.walkMode & InternalWalkMode.walkExpressions) {
111✔
1553
            walk(this, 'obj', visitor, options);
110✔
1554
            walkArray(this.indexes, visitor, options, this);
110✔
1555
            walk(this, 'value', visitor, options);
110✔
1556
        }
1557
    }
1558

1559
    get leadingTrivia(): Token[] {
1560
        return this.obj.leadingTrivia;
23✔
1561
    }
1562
}
1563

1564
export class LibraryStatement extends Statement implements TypedefProvider {
1✔
1565
    constructor(options: {
1566
        library: Token;
1567
        filePath?: Token;
1568
    }) {
1569
        super();
14✔
1570
        this.tokens = {
14✔
1571
            library: options.library,
1572
            filePath: options.filePath
1573
        };
1574
        this.location = util.createBoundingLocation(
14✔
1575
            this.tokens.library,
1576
            this.tokens.filePath
1577
        );
1578
    }
1579
    public readonly tokens: {
1580
        readonly library: Token;
1581
        readonly filePath?: Token;
1582
    };
1583

1584
    public readonly kind = AstNodeKind.LibraryStatement;
14✔
1585

1586
    public readonly location: Location | undefined;
1587

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

1603
    getTypedef(state: BrsTranspileState) {
1604
        return this.transpile(state);
×
1605
    }
1606

1607
    walk(visitor: WalkVisitor, options: WalkOptions) {
1608
        //nothing to walk
1609
    }
1610

1611
    get leadingTrivia(): Token[] {
1612
        return this.tokens.library?.leadingTrivia ?? [];
4!
1613
    }
1614
}
1615

1616
export class NamespaceStatement extends Statement implements TypedefProvider {
1✔
1617
    constructor(options: {
1618
        namespace?: Token;
1619
        nameExpression: VariableExpression | DottedGetExpression;
1620
        body: Body;
1621
        endNamespace?: Token;
1622
    }) {
1623
        super();
587✔
1624
        this.tokens = {
587✔
1625
            namespace: options.namespace,
1626
            endNamespace: options.endNamespace
1627
        };
1628
        this.nameExpression = options.nameExpression;
587✔
1629
        this.body = options.body;
587✔
1630
        this.name = this.getName(ParseMode.BrighterScript);
587✔
1631
        this.symbolTable = new SymbolTable(`NamespaceStatement: '${this.name}'`, () => this.parent?.getSymbolTable());
3,864!
1632
    }
1633

1634
    public readonly tokens: {
1635
        readonly namespace?: Token;
1636
        readonly endNamespace?: Token;
1637
    };
1638

1639
    public readonly nameExpression: VariableExpression | DottedGetExpression;
1640
    public readonly body: Body;
1641

1642
    public readonly kind = AstNodeKind.NamespaceStatement;
587✔
1643

1644
    /**
1645
     * The string name for this namespace
1646
     */
1647
    public name: string;
1648

1649
    public get location() {
1650
        return this.cacheLocation();
327✔
1651
    }
1652
    private _location: Location | undefined;
1653

1654
    public cacheLocation() {
1655
        if (!this._location) {
912✔
1656
            this._location = util.createBoundingLocation(
587✔
1657
                this.tokens.namespace,
1658
                this.nameExpression,
1659
                this.body,
1660
                this.tokens.endNamespace
1661
            );
1662
        }
1663
        return this._location;
912✔
1664
    }
1665

1666
    public getName(parseMode: ParseMode) {
1667
        const sep = parseMode === ParseMode.BrighterScript ? '.' : '_';
6,013✔
1668
        let name = util.getAllDottedGetPartsAsString(this.nameExpression, parseMode);
6,013✔
1669
        if ((this.parent as Body)?.parent?.kind === AstNodeKind.NamespaceStatement) {
6,013✔
1670
            name = (this.parent.parent as NamespaceStatement).getName(parseMode) + sep + name;
235✔
1671
        }
1672
        return name;
6,013✔
1673
    }
1674

1675
    public get leadingTrivia(): Token[] {
1676
        return this.tokens.namespace?.leadingTrivia;
63!
1677
    }
1678

1679
    public get endTrivia(): Token[] {
NEW
1680
        return this.tokens.endNamespace?.leadingTrivia;
×
1681
    }
1682

1683
    public getNameParts() {
1684
        let parts = util.getAllDottedGetParts(this.nameExpression);
990✔
1685

1686
        if ((this.parent as Body)?.parent?.kind === AstNodeKind.NamespaceStatement) {
990!
1687
            parts = (this.parent.parent as NamespaceStatement).getNameParts().concat(parts);
56✔
1688
        }
1689
        return parts;
990✔
1690
    }
1691

1692
    transpile(state: BrsTranspileState) {
1693
        //namespaces don't actually have any real content, so just transpile their bodies
1694
        return [
45✔
1695
            state.transpileAnnotations(this),
1696
            state.transpileLeadingComments(this.tokens.namespace),
1697
            this.body.transpile(state),
1698
            state.transpileLeadingComments(this.tokens.endNamespace)
1699
        ];
1700
    }
1701

1702
    getTypedef(state: BrsTranspileState) {
1703
        let result: TranspileResult = [];
7✔
1704
        for (let comment of util.getLeadingComments(this) ?? []) {
7!
NEW
1705
            result.push(
×
1706
                comment.text,
1707
                state.newline,
1708
                state.indent()
1709
            );
1710
        }
1711

1712
        result.push('namespace ',
7✔
1713
            ...this.getName(ParseMode.BrighterScript),
1714
            state.newline
1715
        );
1716
        state.blockDepth++;
7✔
1717
        result.push(
7✔
1718
            ...this.body.getTypedef(state)
1719
        );
1720
        state.blockDepth--;
7✔
1721

1722
        result.push(
7✔
1723
            state.indent(),
1724
            'end namespace'
1725
        );
1726
        return result;
7✔
1727
    }
1728

1729
    walk(visitor: WalkVisitor, options: WalkOptions) {
1730
        if (options.walkMode & InternalWalkMode.walkExpressions) {
2,951✔
1731
            walk(this, 'nameExpression', visitor, options);
2,409✔
1732
        }
1733

1734
        if (this.body.statements.length > 0 && options.walkMode & InternalWalkMode.walkStatements) {
2,951✔
1735
            walk(this, 'body', visitor, options);
2,754✔
1736
        }
1737
    }
1738

1739
    getType(options: GetTypeOptions) {
1740
        const resultType = new NamespaceType(this.name);
1,029✔
1741
        return resultType;
1,029✔
1742
    }
1743

1744
}
1745

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

1776
    public readonly tokens: {
1777
        readonly import?: Token;
1778
        readonly path: Token;
1779
    };
1780

1781
    public readonly kind = AstNodeKind.ImportStatement;
205✔
1782

1783
    public readonly location: Location;
1784

1785
    public readonly filePath: string;
1786

1787
    transpile(state: BrsTranspileState) {
1788
        //The xml files are responsible for adding the additional script imports, but
1789
        //add the import statement as a comment just for debugging purposes
1790
        return [
13✔
1791
            state.transpileToken(this.tokens.import, 'import', true),
1792
            ' ',
1793
            state.transpileToken(this.tokens.path)
1794
        ];
1795
    }
1796

1797
    /**
1798
     * Get the typedef for this statement
1799
     */
1800
    public getTypedef(state: BrsTranspileState) {
1801
        return [
3✔
1802
            this.tokens.import?.text ?? 'import',
18!
1803
            ' ',
1804
            //replace any `.bs` extension with `.brs`
1805
            this.tokens.path.text.replace(/\.bs"?$/i, '.brs"')
1806
        ];
1807
    }
1808

1809
    walk(visitor: WalkVisitor, options: WalkOptions) {
1810
        //nothing to walk
1811
    }
1812

1813
    get leadingTrivia(): Token[] {
1814
        return this.tokens.import?.leadingTrivia ?? [];
9!
1815
    }
1816
}
1817

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

1848
    public readonly kind = AstNodeKind.InterfaceStatement;
146✔
1849

1850
    public readonly tokens = {} as {
146✔
1851
        readonly interface?: Token;
1852
        readonly name: Identifier;
1853
        readonly extends?: Token;
1854
        readonly endInterface?: Token;
1855
    };
1856

1857
    public readonly location: Location | undefined;
1858

1859
    public get fields(): InterfaceFieldStatement[] {
1860
        return this.body.filter(x => isInterfaceFieldStatement(x)) as InterfaceFieldStatement[];
206✔
1861
    }
1862

1863
    public get methods(): InterfaceMethodStatement[] {
1864
        return this.body.filter(x => isInterfaceMethodStatement(x)) as InterfaceMethodStatement[];
201✔
1865
    }
1866

1867

1868
    public hasParentInterface() {
NEW
1869
        return !!this.parentInterfaceName;
×
1870
    }
1871

1872
    public get leadingTrivia(): Token[] {
1873
        return this.tokens.interface?.leadingTrivia;
21!
1874
    }
1875

1876
    public get endTrivia(): Token[] {
NEW
1877
        return this.tokens.endInterface?.leadingTrivia;
×
1878
    }
1879

1880

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

1900
    /**
1901
     * The name of the interface (without the namespace prefix)
1902
     */
1903
    public get name() {
1904
        return this.tokens.name?.text;
137!
1905
    }
1906

1907
    /**
1908
     * Get the name of this expression based on the parse mode
1909
     */
1910
    public getName(parseMode: ParseMode) {
1911
        const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
137✔
1912
        if (namespace) {
137✔
1913
            let delimiter = parseMode === ParseMode.BrighterScript ? '.' : '_';
22!
1914
            let namespaceName = namespace.getName(parseMode);
22✔
1915
            return namespaceName + delimiter + this.name;
22✔
1916
        } else {
1917
            return this.name;
115✔
1918
        }
1919
    }
1920

1921
    public transpile(state: BrsTranspileState): TranspileResult {
1922
        //interfaces should completely disappear at runtime
1923
        return [
13✔
1924
            state.transpileLeadingComments(this.tokens.interface)
1925
        ];
1926
    }
1927

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

1987
    walk(visitor: WalkVisitor, options: WalkOptions) {
1988
        //visitor-less walk function to do parent linking
1989
        walk(this, 'parentInterfaceName', null, options);
675✔
1990

1991
        if (options.walkMode & InternalWalkMode.walkStatements) {
675!
1992
            walkArray(this.body, visitor, options, this);
675✔
1993
        }
1994
    }
1995

1996
    getType(options: GetTypeOptions) {
1997
        const superIface = this.parentInterfaceName?.getType(options) as InterfaceType;
131✔
1998

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

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

2045
    public readonly kind = AstNodeKind.InterfaceFieldStatement;
180✔
2046

2047
    public readonly typeExpression?: TypeExpression;
2048

2049
    public readonly location: Location | undefined;
2050

2051
    public readonly tokens: {
2052
        readonly name: Identifier;
2053
        readonly as: Token;
2054
        readonly optional?: Token;
2055
    };
2056

2057
    public get leadingTrivia(): Token[] {
2058
        return this.tokens.optional?.leadingTrivia ?? this.tokens.name.leadingTrivia;
21✔
2059
    }
2060

2061
    public get name() {
2062
        return this.tokens.name.text;
×
2063
    }
2064

2065
    public get isOptional() {
2066
        return !!this.tokens.optional;
188✔
2067
    }
2068

2069
    walk(visitor: WalkVisitor, options: WalkOptions) {
2070
        if (options.walkMode & InternalWalkMode.walkExpressions) {
1,163✔
2071
            walk(this, 'typeExpression', visitor, options);
1,007✔
2072
        }
2073
    }
2074

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

2101
        if (this.typeExpression) {
12!
2102
            result.push(
12✔
2103
                ' as ',
2104
                ...this.typeExpression.getTypedef(state)
2105
            );
2106
        }
2107
        return result;
12✔
2108
    }
2109

2110
    public getType(options: GetTypeOptions): BscType {
2111
        return this.typeExpression?.getType(options) ?? DynamicType.instance;
172✔
2112
    }
2113

2114
}
2115

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

2145
    public readonly kind = AstNodeKind.InterfaceMethodStatement;
38✔
2146

2147
    public get location() {
2148
        return util.createBoundingLocation(
52✔
2149
            this.tokens.optional,
2150
            this.tokens.functionType,
2151
            this.tokens.name,
2152
            this.tokens.leftParen,
2153
            ...(this.params ?? []),
156!
2154
            this.tokens.rightParen,
2155
            this.tokens.as,
2156
            this.returnTypeExpression
2157
        );
2158
    }
2159
    /**
2160
     * Get the name of this method.
2161
     */
2162
    public getName(parseMode: ParseMode) {
2163
        return this.tokens.name.text;
28✔
2164
    }
2165

2166
    public readonly tokens: {
2167
        readonly optional?: Token;
2168
        readonly functionType: Token;
2169
        readonly name: Identifier;
2170
        readonly leftParen?: Token;
2171
        readonly rightParen?: Token;
2172
        readonly as?: Token;
2173
    };
2174

2175
    public readonly params: FunctionParameterExpression[];
2176
    public readonly returnTypeExpression?: TypeExpression;
2177

2178
    public get isOptional() {
2179
        return !!this.tokens.optional;
41✔
2180
    }
2181

2182
    public get leadingTrivia(): Token[] {
2183
        return this.tokens.optional?.leadingTrivia ?? this.tokens.functionType.leadingTrivia;
13✔
2184
    }
2185

2186
    walk(visitor: WalkVisitor, options: WalkOptions) {
2187
        if (options.walkMode & InternalWalkMode.walkExpressions) {
200✔
2188
            walk(this, 'returnTypeExpression', visitor, options);
175✔
2189
        }
2190
    }
2191

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

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

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

2271
export class ClassStatement extends Statement implements TypedefProvider {
1✔
2272
    constructor(options: {
2273
        class?: Token;
2274
        /**
2275
         * The name of the class (without namespace prefix)
2276
         */
2277
        name: Identifier;
2278
        body: Statement[];
2279
        endClass?: Token;
2280
        extends?: Token;
2281
        parentClassName?: TypeExpression;
2282
    }) {
2283
        super();
661✔
2284
        this.body = options.body ?? [];
661!
2285
        this.tokens = {
661✔
2286
            name: options.name,
2287
            class: options.class,
2288
            endClass: options.endClass,
2289
            extends: options.extends
2290
        };
2291
        this.parentClassName = options.parentClassName;
661✔
2292
        this.symbolTable = new SymbolTable(`ClassStatement: '${this.tokens.name?.text}'`, () => this.parent?.getSymbolTable());
1,177!
2293

2294
        for (let statement of this.body) {
661✔
2295
            if (isMethodStatement(statement)) {
660✔
2296
                this.methods.push(statement);
350✔
2297
                this.memberMap[statement?.tokens.name?.text.toLowerCase()] = statement;
350!
2298
            } else if (isFieldStatement(statement)) {
310!
2299
                this.fields.push(statement);
310✔
2300
                this.memberMap[statement?.tokens.name?.text.toLowerCase()] = statement;
310!
2301
            }
2302
        }
2303

2304
        this.location = util.createBoundingLocation(
661✔
2305
            this.parentClassName,
2306
            ...(this.body ?? []),
1,983!
2307
            util.createBoundingLocationFromTokens(this.tokens)
2308
        );
2309
    }
2310

2311
    public readonly kind = AstNodeKind.ClassStatement;
661✔
2312

2313

2314
    public readonly tokens: {
2315
        readonly class?: Token;
2316
        /**
2317
         * The name of the class (without namespace prefix)
2318
         */
2319
        readonly name: Identifier;
2320
        readonly endClass?: Token;
2321
        readonly extends?: Token;
2322
    };
2323
    public readonly body: Statement[];
2324
    public readonly parentClassName: TypeExpression;
2325

2326

2327
    public getName(parseMode: ParseMode) {
2328
        const name = this.tokens.name?.text;
2,018✔
2329
        if (name) {
2,018✔
2330
            const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
2,016✔
2331
            if (namespace) {
2,016✔
2332
                let namespaceName = namespace.getName(parseMode);
587✔
2333
                let separator = parseMode === ParseMode.BrighterScript ? '.' : '_';
587✔
2334
                return namespaceName + separator + name;
587✔
2335
            } else {
2336
                return name;
1,429✔
2337
            }
2338
        } else {
2339
            //return undefined which will allow outside callers to know that this class doesn't have a name
2340
            return undefined;
2✔
2341
        }
2342
    }
2343

2344
    public get leadingTrivia(): Token[] {
2345
        return this.tokens.class?.leadingTrivia;
81!
2346
    }
2347

2348
    public get endTrivia(): Token[] {
NEW
2349
        return this.tokens.endClass?.leadingTrivia ?? [];
×
2350
    }
2351

2352
    public readonly memberMap = {} as Record<string, MemberStatement>;
661✔
2353
    public readonly methods = [] as MethodStatement[];
661✔
2354
    public readonly fields = [] as FieldStatement[];
661✔
2355

2356
    public readonly location: Location | undefined;
2357

2358
    transpile(state: BrsTranspileState) {
2359
        let result = [] as TranspileResult;
49✔
2360
        //make the builder
2361
        result.push(...this.getTranspiledBuilder(state));
49✔
2362
        result.push(
49✔
2363
            '\n',
2364
            state.indent()
2365
        );
2366
        //make the class assembler (i.e. the public-facing class creator method)
2367
        result.push(...this.getTranspiledClassFunction(state));
49✔
2368
        return result;
49✔
2369
    }
2370

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

2404
        let body = this.body;
15✔
2405
        //inject an empty "new" method if missing
2406
        if (!this.getConstructorFunction()) {
15✔
2407
            const constructor = createMethodStatement('new', TokenKind.Sub);
11✔
2408
            constructor.parent = this;
11✔
2409
            //walk the constructor to set up parent links
2410
            constructor.link();
11✔
2411
            body = [
11✔
2412
                constructor,
2413
                ...this.body
2414
            ];
2415
        }
2416

2417
        for (const member of body) {
15✔
2418
            if (isTypedefProvider(member)) {
33!
2419
                result.push(
33✔
2420
                    state.indent(),
2421
                    ...member.getTypedef(state),
2422
                    state.newline
2423
                );
2424
            }
2425
        }
2426
        state.blockDepth--;
15✔
2427
        result.push(
15✔
2428
            state.indent(),
2429
            'end class'
2430
        );
2431
        return result;
15✔
2432
    }
2433

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

2463
    public hasParentClass() {
2464
        return !!this.parentClassName;
279✔
2465
    }
2466

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

2489
    private getBuilderName(name: string) {
2490
        if (name.includes('.')) {
116✔
2491
            name = name.replace(/\./gi, '_');
3✔
2492
        }
2493
        return `__${name}_builder`;
116✔
2494
    }
2495

2496
    public getConstructorType() {
2497
        const constructorType = this.getConstructorFunction()?.getType({ flags: SymbolTypeFlag.runtime }) ?? new TypedFunctionType(null);
2!
2498
        constructorType.returnType = this.getType({ flags: SymbolTypeFlag.runtime });
2✔
2499
        return constructorType;
2✔
2500
    }
2501

2502
    /**
2503
     * Get the constructor function for this class (if exists), or undefined if not exist
2504
     */
2505
    private getConstructorFunction() {
2506
        return this.body.find((stmt) => {
115✔
2507
            return (stmt as MethodStatement)?.tokens.name?.text?.toLowerCase() === 'new';
106!
2508
        }) as MethodStatement;
2509
    }
2510

2511
    /**
2512
     * Determine if the specified field was declared in one of the ancestor classes
2513
     */
2514
    public isFieldDeclaredByAncestor(fieldName: string, ancestors: ClassStatement[]) {
2515
        let lowerFieldName = fieldName.toLowerCase();
×
2516
        for (let ancestor of ancestors) {
×
2517
            if (ancestor.memberMap[lowerFieldName]) {
×
2518
                return true;
×
2519
            }
2520
        }
2521
        return false;
×
2522
    }
2523

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

2536
        /**
2537
         * The lineage of this class. index 0 is a direct parent, index 1 is index 0's parent, etc...
2538
         */
2539
        let ancestors = this.getAncestors(state);
49✔
2540

2541
        //construct parent class or empty object
2542
        if (ancestors[0]) {
49✔
2543
            const ancestorNamespace = ancestors[0].findAncestor<NamespaceStatement>(isNamespaceStatement);
18✔
2544
            let fullyQualifiedClassName = util.getFullyQualifiedClassName(
18✔
2545
                ancestors[0].getName(ParseMode.BrighterScript)!,
2546
                ancestorNamespace?.getName(ParseMode.BrighterScript)
54✔
2547
            );
2548
            result.push(
18✔
2549
                'instance = ',
2550
                this.getBuilderName(fullyQualifiedClassName), '()');
2551
        } else {
2552
            //use an empty object.
2553
            result.push('instance = {}');
31✔
2554
        }
2555
        result.push(
49✔
2556
            state.newline,
2557
            state.indent()
2558
        );
2559
        let parentClassIndex = this.getParentClassIndex(state);
49✔
2560

2561
        let body = this.body;
49✔
2562
        //inject an empty "new" method if missing
2563
        if (!this.getConstructorFunction()) {
49✔
2564
            body = [
28✔
2565
                createMethodStatement('new', TokenKind.Sub),
2566
                ...this.body
2567
            ];
2568
        }
2569

2570
        for (let statement of body) {
49✔
2571
            //is field statement
2572
            if (isFieldStatement(statement)) {
78✔
2573
                //do nothing with class fields in this situation, they are handled elsewhere
2574
                continue;
14✔
2575

2576
                //methods
2577
            } else if (isMethodStatement(statement)) {
64!
2578

2579
                //store overridden parent methods as super{parentIndex}_{methodName}
2580
                if (
64✔
2581
                    //is override method
2582
                    statement.tokens.override ||
173✔
2583
                    //is constructor function in child class
2584
                    (statement.tokens.name.text.toLowerCase() === 'new' && ancestors[0])
2585
                ) {
2586
                    result.push(
22✔
2587
                        `instance.super${parentClassIndex}_${statement.tokens.name.text} = instance.${statement.tokens.name.text}`,
2588
                        state.newline,
2589
                        state.indent()
2590
                    );
2591
                }
2592

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

2629
    /**
2630
     * The class function is the function with the same name as the class. This is the function that
2631
     * consumers should call to create a new instance of that class.
2632
     * This invokes the builder, gets an instance of the class, then invokes the "new" function on that class.
2633
     */
2634
    private getTranspiledClassFunction(state: BrsTranspileState) {
2635
        let result: TranspileResult = state.transpileAnnotations(this);
49✔
2636
        const constructorFunction = this.getConstructorFunction();
49✔
2637
        const constructorParams = constructorFunction ? constructorFunction.func.parameters : [];
49✔
2638

2639
        result.push(
49✔
2640
            state.transpileLeadingComments(this.tokens.class),
2641
            state.sourceNode(this.tokens.class, 'function'),
2642
            state.sourceNode(this.tokens.class, ' '),
2643
            state.sourceNode(this.tokens.name, this.getName(ParseMode.BrightScript)),
2644
            `(`
2645
        );
2646
        let i = 0;
49✔
2647
        for (let param of constructorParams) {
49✔
2648
            if (i > 0) {
8✔
2649
                result.push(', ');
2✔
2650
            }
2651
            result.push(
8✔
2652
                param.transpile(state)
2653
            );
2654
            i++;
8✔
2655
        }
2656
        result.push(
49✔
2657
            ')',
2658
            '\n'
2659
        );
2660

2661
        state.blockDepth++;
49✔
2662
        result.push(state.indent());
49✔
2663
        result.push(`instance = ${this.getBuilderName(this.getName(ParseMode.BrightScript)!)}()\n`);
49✔
2664

2665
        result.push(state.indent());
49✔
2666
        result.push(`instance.new(`);
49✔
2667

2668
        //append constructor arguments
2669
        i = 0;
49✔
2670
        for (let param of constructorParams) {
49✔
2671
            if (i > 0) {
8✔
2672
                result.push(', ');
2✔
2673
            }
2674
            result.push(
8✔
2675
                state.transpileToken(param.tokens.name)
2676
            );
2677
            i++;
8✔
2678
        }
2679
        result.push(
49✔
2680
            ')',
2681
            '\n'
2682
        );
2683

2684
        result.push(state.indent());
49✔
2685
        result.push(`return instance\n`);
49✔
2686

2687
        state.blockDepth--;
49✔
2688
        result.push(state.indent());
49✔
2689
        result.push(`end function`);
49✔
2690
        return result;
49✔
2691
    }
2692

2693
    walk(visitor: WalkVisitor, options: WalkOptions) {
2694
        //visitor-less walk function to do parent linking
2695
        walk(this, 'parentClassName', null, options);
2,589✔
2696

2697
        if (options.walkMode & InternalWalkMode.walkStatements) {
2,589!
2698
            walkArray(this.body, visitor, options, this);
2,589✔
2699
        }
2700
    }
2701

2702
    getType(options: GetTypeOptions) {
2703
        const superClass = this.parentClassName?.getType(options) as ClassType;
409✔
2704

2705
        const resultType = new ClassType(this.getName(ParseMode.BrighterScript), superClass);
409✔
2706

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

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

2770
    public readonly kind = AstNodeKind.MethodStatement as AstNodeKind;
389✔
2771

2772
    public readonly modifiers: Token[] = [];
389✔
2773

2774
    public readonly tokens: {
2775
        readonly name: Identifier;
2776
        readonly override?: Token;
2777
    };
2778

2779
    public get accessModifier() {
2780
        return this.modifiers.find(x => accessModifiers.includes(x.kind));
574✔
2781
    }
2782

2783
    public readonly location: Location | undefined;
2784

2785
    /**
2786
     * Get the name of this method.
2787
     */
2788
    public getName(parseMode: ParseMode) {
2789
        return this.tokens.name.text;
276✔
2790
    }
2791

2792
    public get leadingTrivia(): Token[] {
2793
        return this.func.leadingTrivia;
103✔
2794
    }
2795

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

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

2859
    /**
2860
     * All child classes must call the parent constructor. The type checker will warn users when they don't call it in their own class,
2861
     * but we still need to call it even if they have omitted it. This injects the super call if it's missing
2862
     */
2863
    private ensureSuperConstructorCall(state: BrsTranspileState) {
2864
        //if this class doesn't extend another class, quit here
2865
        if (state.classStatement!.getAncestors(state).length === 0) {
49✔
2866
            return;
31✔
2867
        }
2868

2869
        //check whether any calls to super exist
2870
        let containsSuperCall =
2871
            this.func.body.statements.findIndex((x) => {
18✔
2872
                //is a call statement
2873
                return isExpressionStatement(x) && isCallExpression(x.expression) &&
8✔
2874
                    //is a call to super
2875
                    util.findBeginningVariableExpression(x.expression.callee as any).tokens.name?.text.toLowerCase() === 'super';
21!
2876
            }) !== -1;
2877

2878
        //if a call to super exists, quit here
2879
        if (containsSuperCall) {
18✔
2880
            return;
7✔
2881
        }
2882

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

2918
    /**
2919
     * Inject field initializers at the top of the `new` function (after any present `super()` call)
2920
     */
2921
    private injectFieldInitializersForConstructor(state: BrsTranspileState) {
2922
        let startingIndex = state.classStatement!.hasParentClass() ? 1 : 0;
49✔
2923

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

2948
    walk(visitor: WalkVisitor, options: WalkOptions) {
2949
        if (options.walkMode & InternalWalkMode.walkExpressions) {
2,152✔
2950
            walk(this, 'func', visitor, options);
1,713✔
2951
        }
2952
    }
2953
}
2954

2955
export class FieldStatement extends Statement implements TypedefProvider {
1✔
2956
    constructor(options: {
2957
        accessModifier?: Token;
2958
        name: Identifier;
2959
        as?: Token;
2960
        typeExpression?: TypeExpression;
2961
        equals?: Token;
2962
        initialValue?: Expression;
2963
        optional?: Token;
2964
    }) {
2965
        super();
310✔
2966
        this.tokens = {
310✔
2967
            accessModifier: options.accessModifier,
2968
            name: options.name,
2969
            as: options.as,
2970
            equals: options.equals,
2971
            optional: options.optional
2972
        };
2973
        this.typeExpression = options.typeExpression;
310✔
2974
        this.initialValue = options.initialValue;
310✔
2975

2976
        this.location = util.createBoundingLocation(
310✔
2977
            util.createBoundingLocationFromTokens(this.tokens),
2978
            this.typeExpression,
2979
            this.initialValue
2980
        );
2981
    }
2982

2983
    public readonly tokens: {
2984
        readonly accessModifier?: Token;
2985
        readonly name: Identifier;
2986
        readonly as?: Token;
2987
        readonly equals?: Token;
2988
        readonly optional?: Token;
2989
    };
2990

2991
    public readonly typeExpression?: TypeExpression;
2992
    public readonly initialValue?: Expression;
2993

2994
    public readonly kind = AstNodeKind.FieldStatement;
310✔
2995

2996
    /**
2997
     * Derive a ValueKind from the type token, or the initial value.
2998
     * Defaults to `DynamicType`
2999
     */
3000
    getType(options: GetTypeOptions) {
3001
        return this.typeExpression?.getType({ ...options, flags: SymbolTypeFlag.typetime }) ??
244✔
3002
            this.initialValue?.getType({ ...options, flags: SymbolTypeFlag.runtime }) ?? DynamicType.instance;
580✔
3003
    }
3004

3005
    public readonly location: Location | undefined;
3006

3007
    public get leadingTrivia(): Token[] {
3008
        return this.tokens.accessModifier?.leadingTrivia ?? this.tokens.optional?.leadingTrivia ?? this.tokens.name.leadingTrivia;
30!
3009
    }
3010

3011
    public get isOptional() {
3012
        return !!this.tokens.optional;
220✔
3013
    }
3014

3015
    transpile(state: BrsTranspileState): TranspileResult {
3016
        throw new Error('transpile not implemented for ' + Object.getPrototypeOf(this).constructor.name);
×
3017
    }
3018

3019
    getTypedef(state: BrsTranspileState) {
3020
        const result = [];
10✔
3021
        if (this.tokens.name) {
10!
3022
            for (let comment of util.getLeadingComments(this) ?? []) {
10!
NEW
3023
                result.push(
×
3024
                    comment.text,
3025
                    state.newline,
3026
                    state.indent()
3027
                );
3028
            }
3029
            for (let annotation of this.annotations ?? []) {
10✔
3030
                result.push(
2✔
3031
                    ...annotation.getTypedef(state),
3032
                    state.newline,
3033
                    state.indent()
3034
                );
3035
            }
3036

3037
            let type = this.getType({ flags: SymbolTypeFlag.typetime });
10✔
3038
            if (isInvalidType(type) || isVoidType(type)) {
10!
UNCOV
3039
                type = new DynamicType();
×
3040
            }
3041

3042
            result.push(
10✔
3043
                this.tokens.accessModifier?.text ?? 'public',
60!
3044
                ' '
3045
            );
3046
            if (this.isOptional) {
10!
NEW
3047
                result.push(this.tokens.optional.text, ' ');
×
3048
            }
3049
            result.push(this.tokens.name?.text,
10!
3050
                ' as ',
3051
                type.toTypeString()
3052
            );
3053
        }
3054
        return result;
10✔
3055
    }
3056

3057
    walk(visitor: WalkVisitor, options: WalkOptions) {
3058
        if (options.walkMode & InternalWalkMode.walkExpressions) {
1,535✔
3059
            walk(this, 'typeExpression', visitor, options);
1,339✔
3060
            walk(this, 'initialValue', visitor, options);
1,339✔
3061
        }
3062
    }
3063
}
3064

3065
export type MemberStatement = FieldStatement | MethodStatement;
3066

3067
export class TryCatchStatement extends Statement {
1✔
3068
    constructor(options?: {
3069
        try?: Token;
3070
        endTry?: Token;
3071
        tryBranch?: Block;
3072
        catchStatement?: CatchStatement;
3073
    }) {
3074
        super();
28✔
3075
        this.tokens = {
28✔
3076
            try: options.try,
3077
            endTry: options.endTry
3078
        };
3079
        this.tryBranch = options.tryBranch;
28✔
3080
        this.catchStatement = options.catchStatement;
28✔
3081
        this.location = util.createBoundingLocation(
28✔
3082
            this.tokens.try,
3083
            this.tryBranch,
3084
            this.catchStatement,
3085
            this.tokens.endTry
3086
        );
3087
    }
3088

3089
    public readonly tokens: {
3090
        readonly try?: Token;
3091
        readonly endTry?: Token;
3092
    };
3093

3094
    public readonly tryBranch: Block;
3095
    public readonly catchStatement: CatchStatement;
3096

3097
    public readonly kind = AstNodeKind.TryCatchStatement;
28✔
3098

3099
    public readonly location: Location | undefined;
3100

3101
    public transpile(state: BrsTranspileState): TranspileResult {
3102
        return [
4✔
3103
            state.transpileToken(this.tokens.try, 'try'),
3104
            ...this.tryBranch.transpile(state),
3105
            state.newline,
3106
            state.indent(),
3107
            ...(this.catchStatement?.transpile(state) ?? ['catch']),
24!
3108
            state.newline,
3109
            state.indent(),
3110
            state.transpileToken(this.tokens.endTry!, 'end try')
3111
        ];
3112
    }
3113

3114
    public walk(visitor: WalkVisitor, options: WalkOptions) {
3115
        if (this.tryBranch && options.walkMode & InternalWalkMode.walkStatements) {
59!
3116
            walk(this, 'tryBranch', visitor, options);
59✔
3117
            walk(this, 'catchStatement', visitor, options);
59✔
3118
        }
3119
    }
3120

3121
    public get leadingTrivia(): Token[] {
3122
        return this.tokens.try?.leadingTrivia ?? [];
12!
3123
    }
3124

3125
    public get endTrivia(): Token[] {
3126
        return this.tokens.endTry?.leadingTrivia ?? [];
1!
3127
    }
3128
}
3129

3130
export class CatchStatement extends Statement {
1✔
3131
    constructor(options?: {
3132
        catch?: Token;
3133
        exceptionVariable?: Identifier;
3134
        catchBranch?: Block;
3135
    }) {
3136
        super();
26✔
3137
        this.tokens = {
26✔
3138
            catch: options?.catch,
78!
3139
            exceptionVariable: options?.exceptionVariable
78!
3140
        };
3141
        this.catchBranch = options?.catchBranch;
26!
3142
        this.location = util.createBoundingLocation(
26✔
3143
            this.tokens.catch,
3144
            this.tokens.exceptionVariable,
3145
            this.catchBranch
3146
        );
3147
    }
3148

3149
    public readonly tokens: {
3150
        readonly catch?: Token;
3151
        readonly exceptionVariable?: Identifier;
3152
    };
3153

3154
    public readonly catchBranch?: Block;
3155

3156
    public readonly kind = AstNodeKind.CatchStatement;
26✔
3157

3158
    public readonly location: Location | undefined;
3159

3160
    public transpile(state: BrsTranspileState): TranspileResult {
3161
        return [
4✔
3162
            state.transpileToken(this.tokens.catch, 'catch'),
3163
            ' ',
3164
            this.tokens.exceptionVariable?.text ?? 'e',
24!
3165
            ...(this.catchBranch?.transpile(state) ?? [])
24!
3166
        ];
3167
    }
3168

3169
    public walk(visitor: WalkVisitor, options: WalkOptions) {
3170
        if (this.catchBranch && options.walkMode & InternalWalkMode.walkStatements) {
57!
3171
            walk(this, 'catchBranch', visitor, options);
57✔
3172
        }
3173
    }
3174

3175
    public get leadingTrivia(): Token[] {
3176
        return this.tokens.catch?.leadingTrivia ?? [];
4!
3177
    }
3178
}
3179

3180
export class ThrowStatement extends Statement {
1✔
3181
    constructor(options?: {
3182
        throw?: Token;
3183
        expression?: Expression;
3184
    }) {
3185
        super();
10✔
3186
        this.tokens = {
10✔
3187
            throw: options.throw
3188
        };
3189
        this.expression = options.expression;
10✔
3190
        this.location = util.createBoundingLocation(
10✔
3191
            this.tokens.throw,
3192
            this.expression
3193
        );
3194
    }
3195

3196
    public readonly tokens: {
3197
        readonly throw?: Token;
3198
    };
3199
    public readonly expression?: Expression;
3200

3201
    public readonly kind = AstNodeKind.ThrowStatement;
10✔
3202

3203
    public readonly location: Location | undefined;
3204

3205
    public transpile(state: BrsTranspileState) {
3206
        const result = [
5✔
3207
            state.transpileToken(this.tokens.throw, 'throw'),
3208
            ' '
3209
        ] as TranspileResult;
3210

3211
        //if we have an expression, transpile it
3212
        if (this.expression) {
5✔
3213
            result.push(
4✔
3214
                ...this.expression.transpile(state)
3215
            );
3216

3217
            //no expression found. Rather than emit syntax errors, provide a generic error message
3218
        } else {
3219
            result.push('"User-specified exception"');
1✔
3220
        }
3221
        return result;
5✔
3222
    }
3223

3224
    public walk(visitor: WalkVisitor, options: WalkOptions) {
3225
        if (this.expression && options.walkMode & InternalWalkMode.walkExpressions) {
33✔
3226
            walk(this, 'expression', visitor, options);
26✔
3227
        }
3228
    }
3229

3230
    public get leadingTrivia(): Token[] {
3231
        return this.tokens.throw?.leadingTrivia ?? [];
14!
3232
    }
3233
}
3234

3235

3236
export class EnumStatement extends Statement implements TypedefProvider {
1✔
3237
    constructor(options: {
3238
        enum?: Token;
3239
        name: Identifier;
3240
        endEnum?: Token;
3241
        body: Array<EnumMemberStatement>;
3242
    }) {
3243
        super();
157✔
3244
        this.tokens = {
157✔
3245
            enum: options.enum,
3246
            name: options.name,
3247
            endEnum: options.endEnum
3248
        };
3249
        this.symbolTable = new SymbolTable('Enum');
157✔
3250
        this.body = options.body ?? [];
157!
3251
    }
3252

3253
    public readonly tokens: {
3254
        readonly enum?: Token;
3255
        readonly name: Identifier;
3256
        readonly endEnum?: Token;
3257
    };
3258
    public readonly body: Array<EnumMemberStatement>;
3259

3260
    public readonly kind = AstNodeKind.EnumStatement;
157✔
3261

3262
    public get location(): Location | undefined {
3263
        return util.createBoundingLocation(
136✔
3264
            this.tokens.enum,
3265
            this.tokens.name,
3266
            ...this.body,
3267
            this.tokens.endEnum
3268
        );
3269
    }
3270

3271
    public getMembers() {
3272
        const result = [] as EnumMemberStatement[];
329✔
3273
        for (const statement of this.body) {
329✔
3274
            if (isEnumMemberStatement(statement)) {
681!
3275
                result.push(statement);
681✔
3276
            }
3277
        }
3278
        return result;
329✔
3279
    }
3280

3281
    public get leadingTrivia(): Token[] {
3282
        return this.tokens.enum?.leadingTrivia;
57!
3283
    }
3284

3285
    public get endTrivia(): Token[] {
NEW
3286
        return this.tokens.endEnum?.leadingTrivia ?? [];
×
3287
    }
3288

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

3303
                //if explicit integer value, use it and increment the int counter
3304
            } else if (isLiteralExpression(member.value) && member.value.tokens.value.kind === TokenKind.IntegerLiteral) {
115✔
3305
                //try parsing as integer literal, then as hex integer literal.
3306
                let tokenIntValue = util.parseInt(member.value.tokens.value.text) ?? util.parseInt(member.value.tokens.value.text.replace(/&h/i, '0x'));
29✔
3307
                if (tokenIntValue !== undefined) {
29!
3308
                    currentIntValue = tokenIntValue;
29✔
3309
                    currentIntValue++;
29✔
3310
                }
3311
                result.set(member.name?.toLowerCase(), member.value.tokens.value.text);
29!
3312

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

3317
                //all other values
3318
            } else {
3319
                result.set(member.name?.toLowerCase(), (member.value as LiteralExpression)?.tokens?.value?.text ?? 'invalid');
85!
3320
            }
3321
        }
3322
        return result;
59✔
3323
    }
3324

3325
    public getMemberValue(name: string) {
3326
        return this.getMemberValueMap().get(name.toLowerCase());
56✔
3327
    }
3328

3329
    /**
3330
     * The name of the enum (without the namespace prefix)
3331
     */
3332
    public get name() {
3333
        return this.tokens.name?.text;
1!
3334
    }
3335

3336
    /**
3337
     * The name of the enum WITH its leading namespace (if applicable)
3338
     */
3339
    public get fullName() {
3340
        const name = this.tokens.name?.text;
677!
3341
        if (name) {
677!
3342
            const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
677✔
3343

3344
            if (namespace) {
677✔
3345
                let namespaceName = namespace.getName(ParseMode.BrighterScript);
251✔
3346
                return `${namespaceName}.${name}`;
251✔
3347
            } else {
3348
                return name;
426✔
3349
            }
3350
        } else {
3351
            //return undefined which will allow outside callers to know that this doesn't have a name
3352
            return undefined;
×
3353
        }
3354
    }
3355

3356
    transpile(state: BrsTranspileState) {
3357
        //enum declarations don't exist at runtime, so just transpile comments and trivia
3358
        return [
24✔
3359
            state.transpileAnnotations(this),
3360
            state.transpileLeadingComments(this.tokens.enum)
3361
        ];
3362
    }
3363

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

3404
    walk(visitor: WalkVisitor, options: WalkOptions) {
3405
        if (options.walkMode & InternalWalkMode.walkStatements) {
1,003!
3406
            walkArray(this.body, visitor, options, this);
1,003✔
3407

3408
        }
3409
    }
3410

3411
    getType(options: GetTypeOptions) {
3412
        const members = this.getMembers();
135✔
3413

3414
        const resultType = new EnumType(
135✔
3415
            this.fullName,
3416
            members[0]?.getType(options).underlyingType
405✔
3417
        );
3418
        resultType.pushMemberProvider(() => this.getSymbolTable());
135✔
3419
        for (const statement of members) {
135✔
3420
            resultType.addMember(statement?.tokens?.name?.text, { definingNode: statement }, statement.getType(options), SymbolTypeFlag.runtime);
265!
3421
        }
3422
        return resultType;
135✔
3423
    }
3424
}
3425

3426
export class EnumMemberStatement extends Statement implements TypedefProvider {
1✔
3427
    public constructor(options: {
3428
        name: Identifier;
3429
        equals?: Token;
3430
        value?: Expression;
3431
    }) {
3432
        super();
312✔
3433
        this.tokens = {
312✔
3434
            name: options.name,
3435
            equals: options.equals
3436
        };
3437
        this.value = options.value;
312✔
3438
    }
3439

3440
    public readonly tokens: {
3441
        readonly name: Identifier;
3442
        readonly equals?: Token;
3443
    };
3444
    public readonly value?: Expression;
3445

3446
    public readonly kind = AstNodeKind.EnumMemberStatement;
312✔
3447

3448
    public get location() {
3449
        return util.createBoundingLocation(
443✔
3450
            this.tokens.name,
3451
            this.tokens.equals,
3452
            this.value
3453
        );
3454
    }
3455

3456
    /**
3457
     * The name of the member
3458
     */
3459
    public get name() {
3460
        return this.tokens.name.text;
406✔
3461
    }
3462

3463
    public get leadingTrivia(): Token[] {
3464
        return this.tokens.name.leadingTrivia;
20✔
3465
    }
3466

3467
    public transpile(state: BrsTranspileState): TranspileResult {
3468
        return [];
×
3469
    }
3470

3471
    getTypedef(state: BrsTranspileState): TranspileResult {
3472
        const result: TranspileResult = [];
1✔
3473
        for (let comment of util.getLeadingComments(this) ?? []) {
1!
NEW
3474
            result.push(
×
3475
                comment.text,
3476
                state.newline,
3477
                state.indent()
3478
            );
3479
        }
3480
        result.push(this.tokens.name.text);
1✔
3481
        if (this.tokens.equals) {
1!
NEW
3482
            result.push(' ', this.tokens.equals.text, ' ');
×
3483
            if (this.value) {
×
3484
                result.push(
×
3485
                    ...this.value.transpile(state)
3486
                );
3487
            }
3488
        }
3489
        return result;
1✔
3490
    }
3491

3492
    walk(visitor: WalkVisitor, options: WalkOptions) {
3493
        if (this.value && options.walkMode & InternalWalkMode.walkExpressions) {
2,393✔
3494
            walk(this, 'value', visitor, options);
1,139✔
3495
        }
3496
    }
3497

3498
    getType(options: GetTypeOptions) {
3499
        return new EnumMemberType(
400✔
3500
            (this.parent as EnumStatement)?.fullName,
1,200!
3501
            this.tokens?.name?.text,
2,400!
3502
            this.value?.getType(options)
1,200✔
3503
        );
3504
    }
3505
}
3506

3507
export class ConstStatement extends Statement implements TypedefProvider {
1✔
3508
    public constructor(options: {
3509
        const?: Token;
3510
        name: Identifier;
3511
        equals?: Token;
3512
        value: Expression;
3513
    }) {
3514
        super();
147✔
3515
        this.tokens = {
147✔
3516
            const: options.const,
3517
            name: options.name,
3518
            equals: options.equals
3519
        };
3520
        this.value = options.value;
147✔
3521
        this.location = util.createBoundingLocation(this.tokens.const, this.tokens.name, this.tokens.equals, this.value);
147✔
3522
    }
3523

3524
    public readonly tokens: {
3525
        readonly const: Token;
3526
        readonly name: Identifier;
3527
        readonly equals: Token;
3528
    };
3529
    public readonly value: Expression;
3530

3531
    public readonly kind = AstNodeKind.ConstStatement;
147✔
3532

3533
    public readonly location: Location | undefined;
3534

3535
    public get name() {
UNCOV
3536
        return this.tokens.name.text;
×
3537
    }
3538

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

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

3562
    public transpile(state: BrsTranspileState): TranspileResult {
3563
        //const declarations don't exist at runtime, so just transpile comments and trivia
3564
        return [
22✔
3565
            state.transpileAnnotations(this),
3566
            state.transpileLeadingComments(this.tokens.const)
3567
        ];
3568
    }
3569

3570
    getTypedef(state: BrsTranspileState): TranspileResult {
3571
        const result: TranspileResult = [];
3✔
3572
        for (let comment of util.getLeadingComments(this) ?? []) {
3!
NEW
3573
            result.push(
×
3574
                comment.text,
3575
                state.newline,
3576
                state.indent()
3577
            );
3578
        }
3579
        result.push(
3✔
3580
            this.tokens.const ? state.tokenToSourceNode(this.tokens.const) : 'const',
3!
3581
            ' ',
3582
            state.tokenToSourceNode(this.tokens.name),
3583
            ' ',
3584
            this.tokens.equals ? state.tokenToSourceNode(this.tokens.equals) : '=',
3!
3585
            ' ',
3586
            ...this.value.transpile(state)
3587
        );
3588
        return result;
3✔
3589
    }
3590

3591
    walk(visitor: WalkVisitor, options: WalkOptions) {
3592
        if (this.value && options.walkMode & InternalWalkMode.walkExpressions) {
977✔
3593
            walk(this, 'value', visitor, options);
844✔
3594
        }
3595
    }
3596

3597
    getType(options: GetTypeOptions) {
3598
        return this.value.getType(options);
145✔
3599
    }
3600
}
3601

3602
export class ContinueStatement extends Statement {
1✔
3603
    constructor(options: {
3604
        continue?: Token;
3605
        loopType: Token;
3606
    }
3607
    ) {
3608
        super();
11✔
3609
        this.tokens = {
11✔
3610
            continue: options.continue,
3611
            loopType: options.loopType
3612
        };
3613
        this.location = util.createBoundingLocation(
11✔
3614
            this.tokens.continue,
3615
            this.tokens.loopType
3616
        );
3617
    }
3618

3619
    public readonly tokens: {
3620
        readonly continue?: Token;
3621
        readonly loopType: Token;
3622
    };
3623

3624
    public readonly kind = AstNodeKind.ContinueStatement;
11✔
3625

3626
    public readonly location: Location | undefined;
3627

3628
    transpile(state: BrsTranspileState) {
3629
        return [
3✔
3630
            state.sourceNode(this.tokens.continue, this.tokens.continue?.text ?? 'continue'),
18!
3631
            this.tokens.loopType?.leadingWhitespace ?? ' ',
18✔
3632
            state.sourceNode(this.tokens.continue, this.tokens.loopType?.text)
9✔
3633
        ];
3634
    }
3635
    walk(visitor: WalkVisitor, options: WalkOptions) {
3636
        //nothing to walk
3637
    }
3638

3639
    public get leadingTrivia(): Token[] {
3640
        return this.tokens.continue?.leadingTrivia ?? [];
6!
3641
    }
3642
}
3643

3644

3645
export class TypecastStatement extends Statement {
1✔
3646
    constructor(options: {
3647
        typecast?: Token;
3648
        typecastExpression: TypecastExpression;
3649
    }
3650
    ) {
3651
        super();
23✔
3652
        this.tokens = {
23✔
3653
            typecast: options.typecast
3654
        };
3655
        this.typecastExpression = options.typecastExpression;
23✔
3656
        this.location = util.createBoundingLocation(
23✔
3657
            this.tokens.typecast,
3658
            this.typecastExpression
3659
        );
3660
    }
3661

3662
    public readonly tokens: {
3663
        readonly typecast?: Token;
3664
    };
3665

3666
    public readonly typecastExpression: TypecastExpression;
3667

3668
    public readonly kind = AstNodeKind.TypecastStatement;
23✔
3669

3670
    public readonly location: Location;
3671

3672
    transpile(state: BrsTranspileState) {
3673
        //the typecast statement is a comment just for debugging purposes
3674
        return [
1✔
3675
            state.transpileToken(this.tokens.typecast, 'typecast', true),
3676
            ' ',
3677
            this.typecastExpression.obj.transpile(state),
3678
            ' ',
3679
            state.transpileToken(this.typecastExpression.tokens.as, 'as'),
3680
            ' ',
3681
            this.typecastExpression.typeExpression.transpile(state)
3682
        ];
3683
    }
3684

3685
    walk(visitor: WalkVisitor, options: WalkOptions) {
3686
        if (options.walkMode & InternalWalkMode.walkExpressions) {
135✔
3687
            walk(this, 'typecastExpression', visitor, options);
125✔
3688
        }
3689
    }
3690

3691
    get leadingTrivia(): Token[] {
NEW
3692
        return this.tokens.typecast?.leadingTrivia ?? [];
×
3693
    }
3694

3695
    getType(options: GetTypeOptions): BscType {
3696
        return this.typecastExpression.getType(options);
19✔
3697
    }
3698
}
3699

3700
export class ConditionalCompileErrorStatement extends Statement {
1✔
3701
    constructor(options: {
3702
        hashError?: Token;
3703
        message: Token;
3704
    }) {
3705
        super();
9✔
3706
        this.tokens = {
9✔
3707
            hashError: options.hashError,
3708
            message: options.message
3709
        };
3710
        this.location = util.createBoundingLocation(util.createBoundingLocationFromTokens(this.tokens));
9✔
3711
    }
3712

3713
    public readonly tokens: {
3714
        readonly hashError?: Token;
3715
        readonly message: Token;
3716
    };
3717

3718

3719
    public readonly kind = AstNodeKind.ConditionalCompileErrorStatement;
9✔
3720

3721
    public readonly location: Location | undefined;
3722

3723
    transpile(state: BrsTranspileState) {
3724
        return [
3✔
3725
            state.transpileToken(this.tokens.hashError, '#error'),
3726
            ' ',
3727
            state.transpileToken(this.tokens.message)
3728

3729
        ];
3730
    }
3731

3732
    walk(visitor: WalkVisitor, options: WalkOptions) {
3733
        // nothing to walk
3734
    }
3735

3736
    get leadingTrivia(): Token[] {
3737
        return this.tokens.hashError.leadingTrivia ?? [];
6!
3738
    }
3739
}
3740

3741
export class AliasStatement extends Statement {
1✔
3742
    constructor(options: {
3743
        alias?: Token;
3744
        name: Token;
3745
        equals?: Token;
3746
        value: VariableExpression | DottedGetExpression;
3747
    }
3748
    ) {
3749
        super();
32✔
3750
        this.tokens = {
32✔
3751
            alias: options.alias,
3752
            name: options.name,
3753
            equals: options.equals
3754
        };
3755
        this.value = options.value;
32✔
3756
        this.location = util.createBoundingLocation(
32✔
3757
            this.tokens.alias,
3758
            this.tokens.name,
3759
            this.tokens.equals,
3760
            this.value
3761
        );
3762
    }
3763

3764
    public readonly tokens: {
3765
        readonly alias?: Token;
3766
        readonly name: Token;
3767
        readonly equals?: Token;
3768
    };
3769

3770
    public readonly value: Expression;
3771

3772
    public readonly kind = AstNodeKind.AliasStatement;
32✔
3773

3774
    public readonly location: Location;
3775

3776
    transpile(state: BrsTranspileState) {
3777
        //the alias statement is a comment just for debugging purposes
3778
        return [
12✔
3779
            state.transpileToken(this.tokens.alias, 'alias', true),
3780
            ' ',
3781
            state.transpileToken(this.tokens.name),
3782
            ' ',
3783
            state.transpileToken(this.tokens.equals, '='),
3784
            ' ',
3785
            this.value.transpile(state)
3786
        ];
3787
    }
3788

3789
    walk(visitor: WalkVisitor, options: WalkOptions) {
3790
        if (options.walkMode & InternalWalkMode.walkExpressions) {
221✔
3791
            walk(this, 'value', visitor, options);
192✔
3792
        }
3793
    }
3794

3795
    get leadingTrivia(): Token[] {
3796
        return this.tokens.alias?.leadingTrivia ?? [];
24!
3797
    }
3798

3799
    getType(options: GetTypeOptions): BscType {
3800
        return this.value.getType(options);
1✔
3801
    }
3802
}
3803

3804
export class ConditionalCompileStatement extends Statement {
1✔
3805
    constructor(options: {
3806
        hashIf?: Token;
3807
        not?: Token;
3808
        condition: Token;
3809
        hashElse?: Token;
3810
        hashEndIf?: Token;
3811
        thenBranch: Block;
3812
        elseBranch?: ConditionalCompileStatement | Block;
3813
    }) {
3814
        super();
54✔
3815
        this.thenBranch = options.thenBranch;
54✔
3816
        this.elseBranch = options.elseBranch;
54✔
3817

3818
        this.tokens = {
54✔
3819
            hashIf: options.hashIf,
3820
            not: options.not,
3821
            condition: options.condition,
3822
            hashElse: options.hashElse,
3823
            hashEndIf: options.hashEndIf
3824
        };
3825

3826
        this.location = util.createBoundingLocation(
54✔
3827
            util.createBoundingLocationFromTokens(this.tokens),
3828
            this.thenBranch,
3829
            this.elseBranch
3830
        );
3831
    }
3832

3833
    readonly tokens: {
3834
        readonly hashIf?: Token;
3835
        readonly not?: Token;
3836
        readonly condition: Token;
3837
        readonly hashElse?: Token;
3838
        readonly hashEndIf?: Token;
3839
    };
3840
    public readonly thenBranch: Block;
3841
    public readonly elseBranch?: ConditionalCompileStatement | Block;
3842

3843
    public readonly kind = AstNodeKind.ConditionalCompileStatement;
54✔
3844

3845
    public readonly location: Location | undefined;
3846

3847
    transpile(state: BrsTranspileState) {
3848
        let results = [] as TranspileResult;
6✔
3849
        //if   (already indented by block)
3850
        if (!state.conditionalCompileStatement) {
6✔
3851
            // only transpile the #if in the case when we're not in a conditionalCompileStatement already
3852
            results.push(state.transpileToken(this.tokens.hashIf ?? createToken(TokenKind.HashIf)));
3!
3853
        }
3854

3855
        results.push(' ');
6✔
3856
        //conditions
3857
        if (this.tokens.not) {
6✔
3858
            results.push('not');
2✔
3859
            results.push(' ');
2✔
3860
        }
3861
        results.push(state.transpileToken(this.tokens.condition));
6✔
3862
        state.lineage.unshift(this);
6✔
3863

3864
        //if statement body
3865
        let thenNodes = this.thenBranch.transpile(state);
6✔
3866
        state.lineage.shift();
6✔
3867
        if (thenNodes.length > 0) {
6!
3868
            results.push(thenNodes);
6✔
3869
        }
3870
        //else branch
3871
        if (this.elseBranch) {
6!
3872
            const elseIsCC = isConditionalCompileStatement(this.elseBranch);
6✔
3873
            const endBlockToken = elseIsCC ? (this.elseBranch as ConditionalCompileStatement).tokens.hashIf ?? createToken(TokenKind.HashElseIf) : this.tokens.hashElse;
6!
3874
            //else
3875

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

3878
            if (elseIsCC) {
6✔
3879
                //chained else if
3880
                state.lineage.unshift(this.elseBranch);
3✔
3881

3882
                // transpile following #if with knowledge of current
3883
                const existingCCStmt = state.conditionalCompileStatement;
3✔
3884
                state.conditionalCompileStatement = this;
3✔
3885
                let body = this.elseBranch.transpile(state);
3✔
3886
                state.conditionalCompileStatement = existingCCStmt;
3✔
3887

3888
                state.lineage.shift();
3✔
3889

3890
                if (body.length > 0) {
3!
3891
                    //zero or more spaces between the `else` and the `if`
3892
                    results.push(...body);
3✔
3893

3894
                    // stop here because chained if will transpile the rest
3895
                    return results;
3✔
3896
                } else {
NEW
3897
                    results.push('\n');
×
3898
                }
3899

3900
            } else {
3901
                //else body
3902
                state.lineage.unshift(this.tokens.hashElse!);
3✔
3903
                let body = this.elseBranch.transpile(state);
3✔
3904
                state.lineage.shift();
3✔
3905

3906
                if (body.length > 0) {
3!
3907
                    results.push(...body);
3✔
3908
                }
3909
            }
3910
        }
3911

3912
        //end if
3913
        results.push(...state.transpileEndBlockToken(this.elseBranch ?? this.thenBranch, this.tokens.hashEndIf, '#end if'));
3!
3914

3915
        return results;
3✔
3916
    }
3917

3918
    walk(visitor: WalkVisitor, options: WalkOptions) {
3919
        if (options.walkMode & InternalWalkMode.walkStatements) {
185!
3920
            const bsConsts = options.bsConsts ?? this.getBsConsts();
185!
3921
            let conditionTrue = bsConsts?.get(this.tokens.condition.text.toLowerCase());
185!
3922
            if (this.tokens.not) {
185✔
3923
                // flips the boolean value
3924
                conditionTrue = !conditionTrue;
23✔
3925
            }
3926
            const walkFalseBlocks = options.walkMode & InternalWalkMode.visitFalseConditionalCompilationBlocks;
185✔
3927
            if (conditionTrue || walkFalseBlocks) {
185✔
3928
                walk(this, 'thenBranch', visitor, options);
123✔
3929
            }
3930
            if (this.elseBranch && (!conditionTrue || walkFalseBlocks)) {
185✔
3931
                walk(this, 'elseBranch', visitor, options);
52✔
3932
            }
3933
        }
3934
    }
3935

3936
    get leadingTrivia(): Token[] {
3937
        return this.tokens.hashIf?.leadingTrivia ?? [];
6!
3938
    }
3939
}
3940

3941

3942
export class ConditionalCompileConstStatement extends Statement {
1✔
3943
    constructor(options: {
3944
        hashConst?: Token;
3945
        assignment: AssignmentStatement;
3946
    }) {
3947
        super();
17✔
3948
        this.tokens = {
17✔
3949
            hashConst: options.hashConst
3950
        };
3951
        this.assignment = options.assignment;
17✔
3952
        this.location = util.createBoundingLocation(util.createBoundingLocationFromTokens(this.tokens), this.assignment);
17✔
3953
    }
3954

3955
    public readonly tokens: {
3956
        readonly hashConst?: Token;
3957
    };
3958

3959
    public readonly assignment: AssignmentStatement;
3960

3961
    public readonly kind = AstNodeKind.ConditionalCompileConstStatement;
17✔
3962

3963
    public readonly location: Location | undefined;
3964

3965
    transpile(state: BrsTranspileState) {
3966
        return [
3✔
3967
            state.transpileToken(this.tokens.hashConst, '#const'),
3968
            ' ',
3969
            state.transpileToken(this.assignment.tokens.name),
3970
            ' ',
3971
            state.transpileToken(this.assignment.tokens.equals, '='),
3972
            ' ',
3973
            ...this.assignment.value.transpile(state)
3974
        ];
3975

3976
    }
3977

3978
    walk(visitor: WalkVisitor, options: WalkOptions) {
3979
        // nothing to walk
3980
    }
3981

3982

3983
    get leadingTrivia(): Token[] {
3984
        return this.tokens.hashConst.leadingTrivia ?? [];
6!
3985
    }
3986
}
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