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

rokucommunity / brighterscript / #13117

01 Oct 2024 08:24AM UTC coverage: 86.842% (-1.4%) from 88.193%
#13117

push

web-flow
Merge abd960cd5 into 3a2dc7282

11537 of 14048 branches covered (82.13%)

Branch coverage included in aggregate %.

6991 of 7582 new or added lines in 100 files covered. (92.21%)

83 existing lines in 18 files now uncovered.

12692 of 13852 relevant lines covered (91.63%)

29478.96 hits per line

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

88.16
/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
import brsDocParser from './BrightScriptDocParser';
1✔
30

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

178
    public readonly value: Expression;
179

180
    public readonly typeExpression?: TypeExpression;
181

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

184
    public readonly location: Location | undefined;
185

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

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

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

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

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

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

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

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

253
    public readonly item: Expression;
254

255
    public readonly value: Expression;
256

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

259
    public readonly location: Location | undefined;
260

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

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

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

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

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

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

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

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

313
    public readonly statements: Statement[];
314

315
    public readonly kind = AstNodeKind.Block;
6,567✔
316

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

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

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

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

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

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

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

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

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

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

491
    public readonly location: Location | undefined;
492

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

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

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

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

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

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

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

543
    public readonly location?: Location;
544

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

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

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

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

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

585
        this.location = this.func?.location;
3,936✔
586
    }
587

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

593
    public readonly kind = AstNodeKind.FunctionStatement as AstNodeKind;
3,936✔
594

595
    public readonly location: Location | undefined;
596

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

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

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

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

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

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

651
    walk(visitor: WalkVisitor, options: WalkOptions) {
652
        if (options.walkMode & InternalWalkMode.walkExpressions) {
15,410✔
653
            walk(this, 'func', visitor, options);
13,698✔
654
        }
655
    }
656

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

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

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

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

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

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

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

715
    public readonly kind = AstNodeKind.IfStatement;
1,984✔
716

717
    public readonly location: Location | undefined;
718

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

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

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

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

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

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

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

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

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

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

790
        return results;
1,019✔
791
    }
792

793
    walk(visitor: WalkVisitor, options: WalkOptions) {
794
        if (options.walkMode & InternalWalkMode.walkExpressions) {
6,255✔
795
            walk(this, 'condition', visitor, options);
6,239✔
796
        }
797
        if (options.walkMode & InternalWalkMode.walkStatements) {
6,255✔
798
            walk(this, 'thenBranch', visitor, options);
6,253✔
799
        }
800
        if (this.elseBranch && options.walkMode & InternalWalkMode.walkStatements) {
6,255✔
801
            walk(this, 'elseBranch', visitor, options);
4,942✔
802
        }
803
    }
804

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

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

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

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

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

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

852
    public readonly location: Location | undefined;
853

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

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

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

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

882
/** Used to indent the current `print` position to the next 16-character-width output zone. */
883
export interface PrintSeparatorTab extends Token {
884
    kind: TokenKind.Comma;
885
}
886

887
/** Used to insert a single whitespace character at the current `print` position. */
888
export interface PrintSeparatorSpace extends Token {
889
    kind: TokenKind.Semicolon;
890
}
891

892
/**
893
 * Represents a `print` statement within BrightScript.
894
 */
895
export class PrintStatement extends Statement {
1✔
896
    /**
897
     * Creates a new internal representation of a BrightScript `print` statement.
898
     * @param options the options for this statement
899
     * @param options.print a print token
900
     * @param options.expressions an array of expressions or `PrintSeparator`s to be evaluated and printed.
901
     */
902
    constructor(options: {
903
        print: Token;
904
        expressions: Array<Expression | PrintSeparatorTab | PrintSeparatorSpace>;
905
    }) {
906
        super();
1,198✔
907
        this.tokens = {
1,198✔
908
            print: options.print
909
        };
910
        this.expressions = options.expressions;
1,198✔
911
        this.location = util.createBoundingLocation(
1,198✔
912
            this.tokens.print,
913
            ...(this.expressions ?? [])
3,594✔
914
        );
915
    }
916
    public readonly tokens: {
917
        readonly print: Token;
918
    };
919
    public readonly expressions: Array<Expression | PrintSeparatorTab | PrintSeparatorSpace>;
920
    public readonly kind = AstNodeKind.PrintStatement;
1,198✔
921

922
    public readonly location: Location | undefined;
923

924
    transpile(state: BrsTranspileState) {
925
        let result = [
229✔
926
            state.transpileToken(this.tokens.print),
927
            ' '
928
        ] as TranspileResult;
929
        for (let i = 0; i < this.expressions.length; i++) {
229✔
930
            const expressionOrSeparator: any = this.expressions[i];
293✔
931
            if (expressionOrSeparator.transpile) {
293✔
932
                result.push(...(expressionOrSeparator as ExpressionStatement).transpile(state));
267✔
933
            } else {
934
                result.push(
26✔
935
                    state.tokenToSourceNode(expressionOrSeparator)
936
                );
937
            }
938
            //if there's an expression after us, add a space
939
            if ((this.expressions[i + 1] as any)?.transpile) {
293✔
940
                result.push(' ');
38✔
941
            }
942
        }
943
        return result;
229✔
944
    }
945

946
    walk(visitor: WalkVisitor, options: WalkOptions) {
947
        if (options.walkMode & InternalWalkMode.walkExpressions) {
5,213✔
948
            //sometimes we have semicolon Tokens in the expressions list (should probably fix that...), so only walk the actual expressions
949
            walkArray(this.expressions, visitor, options, this, (item) => isExpression(item as any));
5,680✔
950
        }
951
    }
952

953
    get leadingTrivia(): Token[] {
954
        return this.tokens.print?.leadingTrivia ?? [];
6,941!
955
    }
956

957
    public clone() {
958
        return this.finalizeClone(
49✔
959
            new PrintStatement({
960
                print: util.cloneToken(this.tokens.print),
961
                expressions: this.expressions?.map(e => {
147✔
962
                    if (isExpression(e as any)) {
49✔
963
                        return (e as Expression).clone();
48✔
964
                    } else {
965
                        return util.cloneToken(e as Token);
1✔
966
                    }
967
                })
968
            }),
969
            ['expressions' as any]
970
        );
971
    }
972
}
973

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

999
    public readonly tokens: {
1000
        readonly dim?: Token;
1001
        readonly name: Identifier;
1002
        readonly openingSquare?: Token;
1003
        readonly closingSquare?: Token;
1004
    };
1005
    public readonly dimensions: Expression[];
1006

1007
    public readonly kind = AstNodeKind.DimStatement;
46✔
1008

1009
    public readonly location: Location | undefined;
1010

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

1030
    public walk(visitor: WalkVisitor, options: WalkOptions) {
1031
        if (this.dimensions?.length !== undefined && this.dimensions?.length > 0 && options.walkMode & InternalWalkMode.walkExpressions) {
134!
1032
            walkArray(this.dimensions, visitor, options, this);
119✔
1033

1034
        }
1035
    }
1036

1037
    public getType(options: GetTypeOptions): BscType {
1038
        const numDimensions = this.dimensions?.length ?? 1;
18!
1039
        let type = new ArrayType();
18✔
1040
        for (let i = 0; i < numDimensions - 1; i++) {
18✔
1041
            type = new ArrayType(type);
17✔
1042
        }
1043
        return type;
18✔
1044
    }
1045

1046
    get leadingTrivia(): Token[] {
1047
        return this.tokens.dim?.leadingTrivia ?? [];
137!
1048
    }
1049

1050
    public clone() {
1051
        return this.finalizeClone(
3✔
1052
            new DimStatement({
1053
                dim: util.cloneToken(this.tokens.dim),
1054
                name: util.cloneToken(this.tokens.name),
1055
                openingSquare: util.cloneToken(this.tokens.openingSquare),
1056
                dimensions: this.dimensions?.map(e => e?.clone()),
5✔
1057
                closingSquare: util.cloneToken(this.tokens.closingSquare)
1058
            }),
1059
            ['dimensions']
1060
        );
1061
    }
1062
}
1063

1064
export class GotoStatement extends Statement {
1✔
1065
    constructor(options: {
1066
        goto?: Token;
1067
        label: Token;
1068
    }) {
1069
        super();
13✔
1070
        this.tokens = {
13✔
1071
            goto: options.goto,
1072
            label: options.label
1073
        };
1074
        this.location = util.createBoundingLocation(
13✔
1075
            this.tokens.goto,
1076
            this.tokens.label
1077
        );
1078
    }
1079

1080
    public readonly tokens: {
1081
        readonly goto?: Token;
1082
        readonly label: Token;
1083
    };
1084

1085
    public readonly kind = AstNodeKind.GotoStatement;
13✔
1086

1087
    public readonly location: Location | undefined;
1088

1089
    transpile(state: BrsTranspileState) {
1090
        return [
2✔
1091
            state.transpileToken(this.tokens.goto, 'goto'),
1092
            ' ',
1093
            state.transpileToken(this.tokens.label)
1094
        ];
1095
    }
1096

1097
    walk(visitor: WalkVisitor, options: WalkOptions) {
1098
        //nothing to walk
1099
    }
1100

1101
    get leadingTrivia(): Token[] {
1102
        return this.tokens.goto?.leadingTrivia ?? [];
19!
1103
    }
1104

1105
    public clone() {
1106
        return this.finalizeClone(
1✔
1107
            new GotoStatement({
1108
                goto: util.cloneToken(this.tokens.goto),
1109
                label: util.cloneToken(this.tokens.label)
1110
            })
1111
        );
1112
    }
1113
}
1114

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

1136
    public readonly location: Location | undefined;
1137

1138
    public get leadingTrivia(): Token[] {
1139
        return this.tokens.name.leadingTrivia;
29✔
1140
    }
1141

1142
    transpile(state: BrsTranspileState) {
1143
        return [
2✔
1144
            state.transpileToken(this.tokens.name),
1145
            state.transpileToken(this.tokens.colon, ':')
1146

1147
        ];
1148
    }
1149

1150
    walk(visitor: WalkVisitor, options: WalkOptions) {
1151
        //nothing to walk
1152
    }
1153

1154
    public clone() {
1155
        return this.finalizeClone(
1✔
1156
            new LabelStatement({
1157
                name: util.cloneToken(this.tokens.name),
1158
                colon: util.cloneToken(this.tokens.colon)
1159
            })
1160
        );
1161
    }
1162
}
1163

1164
export class ReturnStatement extends Statement {
1✔
1165
    constructor(options?: {
1166
        return?: Token;
1167
        value?: Expression;
1168
    }) {
1169
        super();
3,044✔
1170
        this.tokens = {
3,044✔
1171
            return: options?.return
9,132!
1172
        };
1173
        this.value = options?.value;
3,044!
1174
        this.location = util.createBoundingLocation(
3,044✔
1175
            this.tokens.return,
1176
            this.value
1177
        );
1178
    }
1179

1180
    public readonly tokens: {
1181
        readonly return?: Token;
1182
    };
1183
    public readonly value?: Expression;
1184
    public readonly kind = AstNodeKind.ReturnStatement;
3,044✔
1185

1186
    public readonly location: Location | undefined;
1187

1188
    transpile(state: BrsTranspileState) {
1189
        let result = [] as TranspileResult;
2,990✔
1190
        result.push(
2,990✔
1191
            state.transpileToken(this.tokens.return, 'return')
1192
        );
1193
        if (this.value) {
2,990✔
1194
            result.push(' ');
2,989✔
1195
            result.push(...this.value.transpile(state));
2,989✔
1196
        }
1197
        return result;
2,990✔
1198
    }
1199

1200
    walk(visitor: WalkVisitor, options: WalkOptions) {
1201
        if (options.walkMode & InternalWalkMode.walkExpressions) {
10,213✔
1202
            walk(this, 'value', visitor, options);
10,195✔
1203
        }
1204
    }
1205

1206
    get leadingTrivia(): Token[] {
1207
        return this.tokens.return?.leadingTrivia ?? [];
8,579!
1208
    }
1209

1210
    public clone() {
1211
        return this.finalizeClone(
3✔
1212
            new ReturnStatement({
1213
                return: util.cloneToken(this.tokens.return),
1214
                value: this.value?.clone()
9✔
1215
            }),
1216
            ['value']
1217
        );
1218
    }
1219
}
1220

1221
export class EndStatement extends Statement {
1✔
1222
    constructor(options?: {
1223
        end?: Token;
1224
    }) {
1225
        super();
11✔
1226
        this.tokens = {
11✔
1227
            end: options?.end
33!
1228
        };
1229
        this.location = this.tokens.end?.location;
11!
1230
    }
1231
    public readonly tokens: {
1232
        readonly end?: Token;
1233
    };
1234
    public readonly kind = AstNodeKind.EndStatement;
11✔
1235

1236
    public readonly location: Location;
1237

1238
    transpile(state: BrsTranspileState) {
1239
        return [
2✔
1240
            state.transpileToken(this.tokens.end, 'end')
1241
        ];
1242
    }
1243

1244
    walk(visitor: WalkVisitor, options: WalkOptions) {
1245
        //nothing to walk
1246
    }
1247

1248
    get leadingTrivia(): Token[] {
1249
        return this.tokens.end?.leadingTrivia ?? [];
19!
1250
    }
1251

1252
    public clone() {
1253
        return this.finalizeClone(
1✔
1254
            new EndStatement({
1255
                end: util.cloneToken(this.tokens.end)
1256
            })
1257
        );
1258
    }
1259
}
1260

1261
export class StopStatement extends Statement {
1✔
1262
    constructor(options?: {
1263
        stop?: Token;
1264
    }) {
1265
        super();
19✔
1266
        this.tokens = { stop: options?.stop };
19!
1267
        this.location = this.tokens?.stop?.location;
19!
1268
    }
1269
    public readonly tokens: {
1270
        readonly stop?: Token;
1271
    };
1272

1273
    public readonly kind = AstNodeKind.StopStatement;
19✔
1274

1275
    public readonly location: Location;
1276

1277
    transpile(state: BrsTranspileState) {
1278
        return [
2✔
1279
            state.transpileToken(this.tokens.stop, 'stop')
1280
        ];
1281
    }
1282

1283
    walk(visitor: WalkVisitor, options: WalkOptions) {
1284
        //nothing to walk
1285
    }
1286

1287
    get leadingTrivia(): Token[] {
1288
        return this.tokens.stop?.leadingTrivia ?? [];
29!
1289
    }
1290

1291
    public clone() {
1292
        return this.finalizeClone(
1✔
1293
            new StopStatement({
1294
                stop: util.cloneToken(this.tokens.stop)
1295
            })
1296
        );
1297
    }
1298
}
1299

1300
export class ForStatement extends Statement {
1✔
1301
    constructor(options: {
1302
        for?: Token;
1303
        counterDeclaration: AssignmentStatement;
1304
        to?: Token;
1305
        finalValue: Expression;
1306
        body: Block;
1307
        endFor?: Token;
1308
        step?: Token;
1309
        increment?: Expression;
1310
    }) {
1311
        super();
45✔
1312
        this.tokens = {
45✔
1313
            for: options.for,
1314
            to: options.to,
1315
            endFor: options.endFor,
1316
            step: options.step
1317
        };
1318
        this.counterDeclaration = options.counterDeclaration;
45✔
1319
        this.finalValue = options.finalValue;
45✔
1320
        this.body = options.body;
45✔
1321
        this.increment = options.increment;
45✔
1322

1323
        this.location = util.createBoundingLocation(
45✔
1324
            this.tokens.for,
1325
            this.counterDeclaration,
1326
            this.tokens.to,
1327
            this.finalValue,
1328
            this.tokens.step,
1329
            this.increment,
1330
            this.body,
1331
            this.tokens.endFor
1332
        );
1333
    }
1334

1335
    public readonly tokens: {
1336
        readonly for?: Token;
1337
        readonly to?: Token;
1338
        readonly endFor?: Token;
1339
        readonly step?: Token;
1340
    };
1341

1342
    public readonly counterDeclaration: AssignmentStatement;
1343
    public readonly finalValue: Expression;
1344
    public readonly body: Block;
1345
    public readonly increment?: Expression;
1346

1347
    public readonly kind = AstNodeKind.ForStatement;
45✔
1348

1349
    public readonly location: Location | undefined;
1350

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

1384
        //end for
1385
        result.push(...state.transpileEndBlockToken(this.body, this.tokens.endFor, 'end for'));
11✔
1386

1387
        return result;
11✔
1388
    }
1389

1390
    walk(visitor: WalkVisitor, options: WalkOptions) {
1391
        if (options.walkMode & InternalWalkMode.walkStatements) {
141✔
1392
            walk(this, 'counterDeclaration', visitor, options);
140✔
1393
        }
1394
        if (options.walkMode & InternalWalkMode.walkExpressions) {
141✔
1395
            walk(this, 'finalValue', visitor, options);
137✔
1396
            walk(this, 'increment', visitor, options);
137✔
1397
        }
1398
        if (options.walkMode & InternalWalkMode.walkStatements) {
141✔
1399
            walk(this, 'body', visitor, options);
140✔
1400
        }
1401
    }
1402

1403
    get leadingTrivia(): Token[] {
1404
        return this.tokens.for?.leadingTrivia ?? [];
152!
1405
    }
1406

1407
    public get endTrivia(): Token[] {
NEW
1408
        return this.tokens.endFor?.leadingTrivia ?? [];
×
1409
    }
1410

1411
    public clone() {
1412
        return this.finalizeClone(
5✔
1413
            new ForStatement({
1414
                for: util.cloneToken(this.tokens.for),
1415
                counterDeclaration: this.counterDeclaration?.clone(),
15✔
1416
                to: util.cloneToken(this.tokens.to),
1417
                finalValue: this.finalValue?.clone(),
15✔
1418
                body: this.body?.clone(),
15✔
1419
                endFor: util.cloneToken(this.tokens.endFor),
1420
                step: util.cloneToken(this.tokens.step),
1421
                increment: this.increment?.clone()
15✔
1422
            }),
1423
            ['counterDeclaration', 'finalValue', 'body', 'increment']
1424
        );
1425
    }
1426
}
1427

1428
export class ForEachStatement extends Statement {
1✔
1429
    constructor(options: {
1430
        forEach?: Token;
1431
        item: Token;
1432
        in?: Token;
1433
        target: Expression;
1434
        body: Block;
1435
        endFor?: Token;
1436
    }) {
1437
        super();
40✔
1438
        this.tokens = {
40✔
1439
            forEach: options.forEach,
1440
            item: options.item,
1441
            in: options.in,
1442
            endFor: options.endFor
1443
        };
1444
        this.body = options.body;
40✔
1445
        this.target = options.target;
40✔
1446

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

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

1466
    public readonly kind = AstNodeKind.ForEachStatement;
40✔
1467

1468
    public readonly location: Location | undefined;
1469

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

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

1497
        return result;
5✔
1498
    }
1499

1500
    walk(visitor: WalkVisitor, options: WalkOptions) {
1501
        if (options.walkMode & InternalWalkMode.walkExpressions) {
175✔
1502
            walk(this, 'target', visitor, options);
168✔
1503
        }
1504
        if (options.walkMode & InternalWalkMode.walkStatements) {
175✔
1505
            walk(this, 'body', visitor, options);
174✔
1506
        }
1507
    }
1508

1509
    getType(options: GetTypeOptions): BscType {
1510
        return this.getSymbolTable().getSymbolType(this.tokens.item.text, options);
29✔
1511
    }
1512

1513
    get leadingTrivia(): Token[] {
1514
        return this.tokens.forEach?.leadingTrivia ?? [];
187!
1515
    }
1516

1517
    public get endTrivia(): Token[] {
1518
        return this.tokens.endFor?.leadingTrivia ?? [];
1!
1519
    }
1520

1521
    public clone() {
1522
        return this.finalizeClone(
2✔
1523
            new ForEachStatement({
1524
                forEach: util.cloneToken(this.tokens.forEach),
1525
                in: util.cloneToken(this.tokens.in),
1526
                endFor: util.cloneToken(this.tokens.endFor),
1527
                item: util.cloneToken(this.tokens.item),
1528
                target: this.target?.clone(),
6✔
1529
                body: this.body?.clone()
6✔
1530
            }),
1531
            ['target', 'body']
1532
        );
1533
    }
1534
}
1535

1536
export class WhileStatement extends Statement {
1✔
1537
    constructor(options: {
1538
        while?: Token;
1539
        endWhile?: Token;
1540
        condition: Expression;
1541
        body: Block;
1542
    }) {
1543
        super();
37✔
1544
        this.tokens = {
37✔
1545
            while: options.while,
1546
            endWhile: options.endWhile
1547
        };
1548
        this.body = options.body;
37✔
1549
        this.condition = options.condition;
37✔
1550
        this.location = util.createBoundingLocation(
37✔
1551
            this.tokens.while,
1552
            this.condition,
1553
            this.body,
1554
            this.tokens.endWhile
1555
        );
1556
    }
1557

1558
    public readonly tokens: {
1559
        readonly while?: Token;
1560
        readonly endWhile?: Token;
1561
    };
1562
    public readonly condition: Expression;
1563
    public readonly body: Block;
1564

1565
    public readonly kind = AstNodeKind.WhileStatement;
37✔
1566

1567
    public readonly location: Location | undefined;
1568

1569
    transpile(state: BrsTranspileState) {
1570
        let result = [] as TranspileResult;
8✔
1571
        //while
1572
        result.push(
8✔
1573
            state.transpileToken(this.tokens.while, 'while'),
1574
            ' '
1575
        );
1576
        //condition
1577
        result.push(
8✔
1578
            ...this.condition.transpile(state)
1579
        );
1580
        state.lineage.unshift(this);
8✔
1581
        //body
1582
        result.push(...this.body.transpile(state));
8✔
1583
        state.lineage.shift();
8✔
1584

1585
        //end while
1586
        result.push(...state.transpileEndBlockToken(this.body, this.tokens.endWhile, 'end while'));
8✔
1587

1588
        return result;
8✔
1589
    }
1590

1591
    walk(visitor: WalkVisitor, options: WalkOptions) {
1592
        if (options.walkMode & InternalWalkMode.walkExpressions) {
114✔
1593
            walk(this, 'condition', visitor, options);
111✔
1594
        }
1595
        if (options.walkMode & InternalWalkMode.walkStatements) {
114✔
1596
            walk(this, 'body', visitor, options);
113✔
1597
        }
1598
    }
1599

1600
    get leadingTrivia(): Token[] {
1601
        return this.tokens.while?.leadingTrivia ?? [];
107!
1602
    }
1603

1604
    public get endTrivia(): Token[] {
1605
        return this.tokens.endWhile?.leadingTrivia ?? [];
1!
1606
    }
1607

1608
    public clone() {
1609
        return this.finalizeClone(
4✔
1610
            new WhileStatement({
1611
                while: util.cloneToken(this.tokens.while),
1612
                endWhile: util.cloneToken(this.tokens.endWhile),
1613
                condition: this.condition?.clone(),
12✔
1614
                body: this.body?.clone()
12✔
1615
            }),
1616
            ['condition', 'body']
1617
        );
1618
    }
1619
}
1620

1621
export class DottedSetStatement extends Statement {
1✔
1622
    constructor(options: {
1623
        obj: Expression;
1624
        name: Identifier;
1625
        value: Expression;
1626
        dot?: Token;
1627
        equals?: Token;
1628
    }) {
1629
        super();
299✔
1630
        this.tokens = {
299✔
1631
            name: options.name,
1632
            dot: options.dot,
1633
            equals: options.equals
1634
        };
1635
        this.obj = options.obj;
299✔
1636
        this.value = options.value;
299✔
1637
        this.location = util.createBoundingLocation(
299✔
1638
            this.obj,
1639
            this.tokens.dot,
1640
            this.tokens.name,
1641
            this.value
1642
        );
1643
    }
1644
    public readonly tokens: {
1645
        readonly name: Identifier;
1646
        readonly equals?: Token;
1647
        readonly dot?: Token;
1648
    };
1649

1650
    public readonly obj: Expression;
1651
    public readonly value: Expression;
1652

1653
    public readonly kind = AstNodeKind.DottedSetStatement;
299✔
1654

1655
    public readonly location: Location | undefined;
1656

1657
    transpile(state: BrsTranspileState) {
1658
        //if the value is a compound assignment, don't add the obj, dot, name, or operator...the expression will handle that
1659
        return [
9✔
1660
            //object
1661
            ...this.obj.transpile(state),
1662
            this.tokens.dot ? state.tokenToSourceNode(this.tokens.dot) : '.',
9!
1663
            //name
1664
            state.transpileToken(this.tokens.name),
1665
            ' ',
1666
            state.transpileToken(this.tokens.equals, '='),
1667
            ' ',
1668
            //right-hand-side of assignment
1669
            ...this.value.transpile(state)
1670
        ];
1671

1672
    }
1673

1674
    walk(visitor: WalkVisitor, options: WalkOptions) {
1675
        if (options.walkMode & InternalWalkMode.walkExpressions) {
753✔
1676
            walk(this, 'obj', visitor, options);
751✔
1677
            walk(this, 'value', visitor, options);
751✔
1678
        }
1679
    }
1680

1681
    getType(options: GetTypeOptions) {
1682
        const objType = this.obj?.getType(options);
86!
1683
        const result = objType?.getMemberType(this.tokens.name?.text, options);
86!
1684
        options.typeChain?.push(new TypeChainEntry({
86✔
1685
            name: this.tokens.name?.text,
255!
1686
            type: result, data: options.data,
1687
            location: this.tokens.name?.location,
255!
1688
            astNode: this
1689
        }));
1690
        return result;
86✔
1691
    }
1692

1693
    get leadingTrivia(): Token[] {
1694
        return this.obj.leadingTrivia;
759✔
1695
    }
1696

1697
    public clone() {
1698
        return this.finalizeClone(
2✔
1699
            new DottedSetStatement({
1700
                obj: this.obj?.clone(),
6✔
1701
                dot: util.cloneToken(this.tokens.dot),
1702
                name: util.cloneToken(this.tokens.name),
1703
                equals: util.cloneToken(this.tokens.equals),
1704
                value: this.value?.clone()
6✔
1705
            }),
1706
            ['obj', 'value']
1707
        );
1708
    }
1709
}
1710

1711
export class IndexedSetStatement extends Statement {
1✔
1712
    constructor(options: {
1713
        obj: Expression;
1714
        indexes: Expression[];
1715
        value: Expression;
1716
        openingSquare?: Token;
1717
        closingSquare?: Token;
1718
        equals?: Token;
1719
    }) {
1720
        super();
37✔
1721
        this.tokens = {
37✔
1722
            openingSquare: options.openingSquare,
1723
            closingSquare: options.closingSquare,
1724
            equals: options.equals
1725
        };
1726
        this.obj = options.obj;
37✔
1727
        this.indexes = options.indexes;
37✔
1728
        this.value = options.value;
37✔
1729
        this.location = util.createBoundingLocation(
37✔
1730
            this.obj,
1731
            this.tokens.openingSquare,
1732
            ...this.indexes ?? [],
111✔
1733
            this.tokens.closingSquare,
1734
            this.value
1735
        );
1736
    }
1737

1738
    public readonly tokens: {
1739
        readonly openingSquare?: Token;
1740
        readonly closingSquare?: Token;
1741
        readonly equals?: Token;
1742
    };
1743
    public readonly obj: Expression;
1744
    public readonly indexes: Expression[];
1745
    public readonly value: Expression;
1746

1747
    public readonly kind = AstNodeKind.IndexedSetStatement;
37✔
1748

1749
    public readonly location: Location | undefined;
1750

1751
    transpile(state: BrsTranspileState) {
1752
        const result = [];
10✔
1753
        result.push(
10✔
1754
            //obj
1755
            ...this.obj.transpile(state),
1756
            //   [
1757
            state.transpileToken(this.tokens.openingSquare, '[')
1758
        );
1759
        for (let i = 0; i < this.indexes.length; i++) {
10✔
1760
            //add comma between indexes
1761
            if (i > 0) {
11✔
1762
                result.push(', ');
1✔
1763
            }
1764
            let index = this.indexes[i];
11✔
1765
            result.push(
11✔
1766
                ...(index?.transpile(state) ?? [])
66!
1767
            );
1768
        }
1769
        result.push(
10✔
1770
            state.transpileToken(this.tokens.closingSquare, ']'),
1771
            ' ',
1772
            state.transpileToken(this.tokens.equals, '='),
1773
            ' ',
1774
            ...this.value.transpile(state)
1775
        );
1776
        return result;
10✔
1777

1778
    }
1779

1780
    walk(visitor: WalkVisitor, options: WalkOptions) {
1781
        if (options.walkMode & InternalWalkMode.walkExpressions) {
117✔
1782
            walk(this, 'obj', visitor, options);
116✔
1783
            walkArray(this.indexes, visitor, options, this);
116✔
1784
            walk(this, 'value', visitor, options);
116✔
1785
        }
1786
    }
1787

1788
    get leadingTrivia(): Token[] {
1789
        return this.obj.leadingTrivia;
126✔
1790
    }
1791

1792
    public clone() {
1793
        return this.finalizeClone(
6✔
1794
            new IndexedSetStatement({
1795
                obj: this.obj?.clone(),
18✔
1796
                openingSquare: util.cloneToken(this.tokens.openingSquare),
1797
                indexes: this.indexes?.map(x => x?.clone()),
7✔
1798
                equals: util.cloneToken(this.tokens.equals),
1799
                value: this.value?.clone(),
18✔
1800
                closingSquare: util.cloneToken(this.tokens.closingSquare)
1801
            }),
1802
            ['obj', 'indexes', 'value']
1803
        );
1804
    }
1805
}
1806

1807
export class LibraryStatement extends Statement implements TypedefProvider {
1✔
1808
    constructor(options: {
1809
        library: Token;
1810
        filePath?: Token;
1811
    }) {
1812
        super();
16✔
1813
        this.tokens = {
16✔
1814
            library: options?.library,
48!
1815
            filePath: options?.filePath
48!
1816
        };
1817
        this.location = util.createBoundingLocation(
16✔
1818
            this.tokens.library,
1819
            this.tokens.filePath
1820
        );
1821
    }
1822
    public readonly tokens: {
1823
        readonly library: Token;
1824
        readonly filePath?: Token;
1825
    };
1826

1827
    public readonly kind = AstNodeKind.LibraryStatement;
16✔
1828

1829
    public readonly location: Location | undefined;
1830

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

1846
    getTypedef(state: BrsTranspileState) {
1847
        return this.transpile(state);
×
1848
    }
1849

1850
    walk(visitor: WalkVisitor, options: WalkOptions) {
1851
        //nothing to walk
1852
    }
1853

1854
    get leadingTrivia(): Token[] {
1855
        return this.tokens.library?.leadingTrivia ?? [];
26!
1856
    }
1857

1858
    public clone() {
1859
        return this.finalizeClone(
1✔
1860
            new LibraryStatement({
1861
                library: util.cloneToken(this.tokens?.library),
3!
1862
                filePath: util.cloneToken(this.tokens?.filePath)
3!
1863
            })
1864
        );
1865
    }
1866
}
1867

1868
export class NamespaceStatement extends Statement implements TypedefProvider {
1✔
1869
    constructor(options: {
1870
        namespace?: Token;
1871
        nameExpression: VariableExpression | DottedGetExpression;
1872
        body: Body;
1873
        endNamespace?: Token;
1874
    }) {
1875
        super();
613✔
1876
        this.tokens = {
613✔
1877
            namespace: options.namespace,
1878
            endNamespace: options.endNamespace
1879
        };
1880
        this.nameExpression = options.nameExpression;
613✔
1881
        this.body = options.body;
613✔
1882
        this.symbolTable = new SymbolTable(`NamespaceStatement: '${this.name}'`, () => this.parent?.getSymbolTable());
4,933!
1883
    }
1884

1885
    public readonly tokens: {
1886
        readonly namespace?: Token;
1887
        readonly endNamespace?: Token;
1888
    };
1889

1890
    public readonly nameExpression: VariableExpression | DottedGetExpression;
1891
    public readonly body: Body;
1892

1893
    public readonly kind = AstNodeKind.NamespaceStatement;
613✔
1894

1895
    /**
1896
     * The string name for this namespace
1897
     */
1898
    public get name(): string {
1899
        return this.getName(ParseMode.BrighterScript);
2,295✔
1900
    }
1901

1902
    public get location() {
1903
        return this.cacheLocation();
352✔
1904
    }
1905
    private _location: Location | undefined;
1906

1907
    public cacheLocation() {
1908
        if (!this._location) {
963✔
1909
            this._location = util.createBoundingLocation(
613✔
1910
                this.tokens.namespace,
1911
                this.nameExpression,
1912
                this.body,
1913
                this.tokens.endNamespace
1914
            );
1915
        }
1916
        return this._location;
963✔
1917
    }
1918

1919
    public getName(parseMode: ParseMode) {
1920
        const sep = parseMode === ParseMode.BrighterScript ? '.' : '_';
8,279✔
1921
        let name = util.getAllDottedGetPartsAsString(this.nameExpression, parseMode);
8,279✔
1922
        if ((this.parent as Body)?.parent?.kind === AstNodeKind.NamespaceStatement) {
8,279✔
1923
            name = (this.parent.parent as NamespaceStatement).getName(parseMode) + sep + name;
319✔
1924
        }
1925
        return name;
8,279✔
1926
    }
1927

1928
    public get leadingTrivia(): Token[] {
1929
        return this.tokens.namespace?.leadingTrivia;
1,781!
1930
    }
1931

1932
    public get endTrivia(): Token[] {
NEW
1933
        return this.tokens.endNamespace?.leadingTrivia;
×
1934
    }
1935

1936
    public getNameParts() {
1937
        let parts = util.getAllDottedGetParts(this.nameExpression);
1,012✔
1938

1939
        if ((this.parent as Body)?.parent?.kind === AstNodeKind.NamespaceStatement) {
1,012!
1940
            parts = (this.parent.parent as NamespaceStatement).getNameParts().concat(parts);
56✔
1941
        }
1942
        return parts;
1,012✔
1943
    }
1944

1945
    transpile(state: BrsTranspileState) {
1946
        //namespaces don't actually have any real content, so just transpile their bodies
1947
        return [
55✔
1948
            state.transpileAnnotations(this),
1949
            state.transpileLeadingComments(this.tokens.namespace),
1950
            this.body.transpile(state),
1951
            state.transpileLeadingComments(this.tokens.endNamespace)
1952
        ];
1953
    }
1954

1955
    getTypedef(state: BrsTranspileState) {
1956
        let result: TranspileResult = [];
7✔
1957
        for (let comment of util.getLeadingComments(this) ?? []) {
7!
NEW
1958
            result.push(
×
1959
                comment.text,
1960
                state.newline,
1961
                state.indent()
1962
            );
1963
        }
1964

1965
        result.push('namespace ',
7✔
1966
            ...this.getName(ParseMode.BrighterScript),
1967
            state.newline
1968
        );
1969
        state.blockDepth++;
7✔
1970
        result.push(
7✔
1971
            ...this.body.getTypedef(state)
1972
        );
1973
        state.blockDepth--;
7✔
1974

1975
        result.push(
7✔
1976
            state.indent(),
1977
            'end namespace'
1978
        );
1979
        return result;
7✔
1980
    }
1981

1982
    walk(visitor: WalkVisitor, options: WalkOptions) {
1983
        if (options.walkMode & InternalWalkMode.walkExpressions) {
3,054✔
1984
            walk(this, 'nameExpression', visitor, options);
2,492✔
1985
        }
1986

1987
        if (this.body.statements.length > 0 && options.walkMode & InternalWalkMode.walkStatements) {
3,054✔
1988
            walk(this, 'body', visitor, options);
2,854✔
1989
        }
1990
    }
1991

1992
    getType(options: GetTypeOptions) {
1993
        const resultType = new NamespaceType(this.name);
1,067✔
1994
        return resultType;
1,067✔
1995
    }
1996

1997
    public clone() {
1998
        const clone = this.finalizeClone(
3✔
1999
            new NamespaceStatement({
2000
                namespace: util.cloneToken(this.tokens.namespace),
2001
                nameExpression: this.nameExpression?.clone(),
9✔
2002
                body: this.body?.clone(),
9✔
2003
                endNamespace: util.cloneToken(this.tokens.endNamespace)
2004
            }),
2005
            ['nameExpression', 'body']
2006
        );
2007
        clone.cacheLocation();
3✔
2008
        return clone;
3✔
2009
    }
2010
}
2011

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

2042
    public readonly tokens: {
2043
        readonly import?: Token;
2044
        readonly path: Token;
2045
    };
2046

2047
    public readonly kind = AstNodeKind.ImportStatement;
207✔
2048

2049
    public readonly location: Location;
2050

2051
    public readonly filePath: string;
2052

2053
    transpile(state: BrsTranspileState) {
2054
        //The xml files are responsible for adding the additional script imports, but
2055
        //add the import statement as a comment just for debugging purposes
2056
        return [
13✔
2057
            state.transpileToken(this.tokens.import, 'import', true),
2058
            ' ',
2059
            state.transpileToken(this.tokens.path)
2060
        ];
2061
    }
2062

2063
    /**
2064
     * Get the typedef for this statement
2065
     */
2066
    public getTypedef(state: BrsTranspileState) {
2067
        return [
3✔
2068
            this.tokens.import?.text ?? 'import',
18!
2069
            ' ',
2070
            //replace any `.bs` extension with `.brs`
2071
            this.tokens.path.text.replace(/\.bs"?$/i, '.brs"')
2072
        ];
2073
    }
2074

2075
    walk(visitor: WalkVisitor, options: WalkOptions) {
2076
        //nothing to walk
2077
    }
2078

2079
    get leadingTrivia(): Token[] {
2080
        return this.tokens.import?.leadingTrivia ?? [];
576!
2081
    }
2082

2083
    public clone() {
2084
        return this.finalizeClone(
1✔
2085
            new ImportStatement({
2086
                import: util.cloneToken(this.tokens.import),
2087
                path: util.cloneToken(this.tokens.path)
2088
            })
2089
        );
2090
    }
2091
}
2092

2093
export class InterfaceStatement extends Statement implements TypedefProvider {
1✔
2094
    constructor(options: {
2095
        interface: Token;
2096
        name: Identifier;
2097
        extends?: Token;
2098
        parentInterfaceName?: TypeExpression;
2099
        body: Statement[];
2100
        endInterface?: Token;
2101
    }) {
2102
        super();
163✔
2103
        this.tokens = {
163✔
2104
            interface: options.interface,
2105
            name: options.name,
2106
            extends: options.extends,
2107
            endInterface: options.endInterface
2108
        };
2109
        this.parentInterfaceName = options.parentInterfaceName;
163✔
2110
        this.body = options.body;
163✔
2111
        this.location = util.createBoundingLocation(
163✔
2112
            this.tokens.interface,
2113
            this.tokens.name,
2114
            this.tokens.extends,
2115
            this.parentInterfaceName,
2116
            ...this.body ?? [],
489✔
2117
            this.tokens.endInterface
2118
        );
2119
    }
2120
    public readonly parentInterfaceName?: TypeExpression;
2121
    public readonly body: Statement[];
2122

2123
    public readonly kind = AstNodeKind.InterfaceStatement;
163✔
2124

2125
    public readonly tokens = {} as {
163✔
2126
        readonly interface?: Token;
2127
        readonly name: Identifier;
2128
        readonly extends?: Token;
2129
        readonly endInterface?: Token;
2130
    };
2131

2132
    public readonly location: Location | undefined;
2133

2134
    public get fields(): InterfaceFieldStatement[] {
2135
        return this.body.filter(x => isInterfaceFieldStatement(x)) as InterfaceFieldStatement[];
208✔
2136
    }
2137

2138
    public get methods(): InterfaceMethodStatement[] {
2139
        return this.body.filter(x => isInterfaceMethodStatement(x)) as InterfaceMethodStatement[];
203✔
2140
    }
2141

2142

2143
    public hasParentInterface() {
NEW
2144
        return !!this.parentInterfaceName;
×
2145
    }
2146

2147
    public get leadingTrivia(): Token[] {
2148
        return this.tokens.interface?.leadingTrivia;
407!
2149
    }
2150

2151
    public get endTrivia(): Token[] {
NEW
2152
        return this.tokens.endInterface?.leadingTrivia;
×
2153
    }
2154

2155

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

2175
    /**
2176
     * The name of the interface (without the namespace prefix)
2177
     */
2178
    public get name() {
2179
        return this.tokens.name?.text;
140!
2180
    }
2181

2182
    /**
2183
     * Get the name of this expression based on the parse mode
2184
     */
2185
    public getName(parseMode: ParseMode) {
2186
        const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
140✔
2187
        if (namespace) {
140✔
2188
            let delimiter = parseMode === ParseMode.BrighterScript ? '.' : '_';
22!
2189
            let namespaceName = namespace.getName(parseMode);
22✔
2190
            return namespaceName + delimiter + this.name;
22✔
2191
        } else {
2192
            return this.name;
118✔
2193
        }
2194
    }
2195

2196
    public transpile(state: BrsTranspileState): TranspileResult {
2197
        //interfaces should completely disappear at runtime
2198
        return [
13✔
2199
            state.transpileLeadingComments(this.tokens.interface)
2200
        ];
2201
    }
2202

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

2262
    walk(visitor: WalkVisitor, options: WalkOptions) {
2263
        //visitor-less walk function to do parent linking
2264
        walk(this, 'parentInterfaceName', null, options);
693✔
2265

2266
        if (options.walkMode & InternalWalkMode.walkStatements) {
693!
2267
            walkArray(this.body, visitor, options, this);
693✔
2268
        }
2269
    }
2270

2271
    getType(options: GetTypeOptions) {
2272
        const superIface = this.parentInterfaceName?.getType(options) as InterfaceType;
133✔
2273

2274
        const resultType = new InterfaceType(this.getName(ParseMode.BrighterScript), superIface);
133✔
2275
        for (const statement of this.methods) {
133✔
2276
            const memberType = statement?.getType({ ...options, typeChain: undefined }); // no typechain info needed
28!
2277
            const flag = statement.isOptional ? SymbolTypeFlag.runtime | SymbolTypeFlag.optional : SymbolTypeFlag.runtime;
28✔
2278
            resultType.addMember(statement?.tokens.name?.text, { definingNode: statement }, memberType, flag);
28!
2279
        }
2280
        for (const statement of this.fields) {
133✔
2281
            const memberType = statement?.getType({ ...options, typeChain: undefined }); // no typechain info needed
170!
2282
            const flag = statement.isOptional ? SymbolTypeFlag.runtime | SymbolTypeFlag.optional : SymbolTypeFlag.runtime;
170✔
2283
            resultType.addMember(statement?.tokens.name?.text, { definingNode: statement, isInstance: true }, memberType, flag);
170!
2284
        }
2285
        options.typeChain?.push(new TypeChainEntry({
133✔
2286
            name: this.getName(ParseMode.BrighterScript),
2287
            type: resultType,
2288
            data: options.data,
2289
            astNode: this
2290
        }));
2291
        return resultType;
133✔
2292
    }
2293

2294
    public clone() {
2295
        return this.finalizeClone(
8✔
2296
            new InterfaceStatement({
2297
                interface: util.cloneToken(this.tokens.interface),
2298
                name: util.cloneToken(this.tokens.name),
2299
                extends: util.cloneToken(this.tokens.extends),
2300
                parentInterfaceName: this.parentInterfaceName?.clone(),
24✔
2301
                body: this.body?.map(x => x?.clone()),
9✔
2302
                endInterface: util.cloneToken(this.tokens.endInterface)
2303
            }),
2304
            ['parentInterfaceName', 'body']
2305
        );
2306
    }
2307
}
2308

2309
export class InterfaceFieldStatement extends Statement implements TypedefProvider {
1✔
2310
    public transpile(state: BrsTranspileState): TranspileResult {
2311
        throw new Error('Method not implemented.');
×
2312
    }
2313
    constructor(options: {
2314
        name: Identifier;
2315
        as?: Token;
2316
        typeExpression?: TypeExpression;
2317
        optional?: Token;
2318
    }) {
2319
        super();
189✔
2320
        this.tokens = {
189✔
2321
            optional: options.optional,
2322
            name: options.name,
2323
            as: options.as
2324
        };
2325
        this.typeExpression = options.typeExpression;
189✔
2326
        this.location = util.createBoundingLocation(
189✔
2327
            this.tokens.optional,
2328
            this.tokens.name,
2329
            this.tokens.as,
2330
            this.typeExpression
2331
        );
2332
    }
2333

2334
    public readonly kind = AstNodeKind.InterfaceFieldStatement;
189✔
2335

2336
    public readonly typeExpression?: TypeExpression;
2337

2338
    public readonly location: Location | undefined;
2339

2340
    public readonly tokens: {
2341
        readonly name: Identifier;
2342
        readonly as: Token;
2343
        readonly optional?: Token;
2344
    };
2345

2346
    public get leadingTrivia(): Token[] {
2347
        return this.tokens.optional?.leadingTrivia ?? this.tokens.name.leadingTrivia;
502✔
2348
    }
2349

2350
    public get name() {
2351
        return this.tokens.name.text;
×
2352
    }
2353

2354
    public get isOptional() {
2355
        return !!this.tokens.optional;
190✔
2356
    }
2357

2358
    walk(visitor: WalkVisitor, options: WalkOptions) {
2359
        if (options.walkMode & InternalWalkMode.walkExpressions) {
1,175✔
2360
            walk(this, 'typeExpression', visitor, options);
1,018✔
2361
        }
2362
    }
2363

2364
    getTypedef(state: BrsTranspileState): TranspileResult {
2365
        const result = [] as TranspileResult;
12✔
2366
        for (let comment of util.getLeadingComments(this) ?? []) {
12!
NEW
2367
            result.push(
×
2368
                comment.text,
2369
                state.newline,
2370
                state.indent()
2371
            );
2372
        }
2373
        for (let annotation of this.annotations ?? []) {
12✔
2374
            result.push(
1✔
2375
                ...annotation.getTypedef(state),
2376
                state.newline,
2377
                state.indent()
2378
            );
2379
        }
2380
        if (this.isOptional) {
12✔
2381
            result.push(
1✔
2382
                this.tokens.optional!.text,
2383
                ' '
2384
            );
2385
        }
2386
        result.push(
12✔
2387
            this.tokens.name.text
2388
        );
2389

2390
        if (this.typeExpression) {
12!
2391
            result.push(
12✔
2392
                ' as ',
2393
                ...this.typeExpression.getTypedef(state)
2394
            );
2395
        }
2396
        return result;
12✔
2397
    }
2398

2399
    public getType(options: GetTypeOptions): BscType {
2400
        return this.typeExpression?.getType(options) ?? DynamicType.instance;
174✔
2401
    }
2402

2403
    public clone() {
2404
        return this.finalizeClone(
4✔
2405
            new InterfaceFieldStatement({
2406
                name: util.cloneToken(this.tokens.name),
2407
                as: util.cloneToken(this.tokens.as),
2408
                typeExpression: this.typeExpression?.clone(),
12✔
2409
                optional: util.cloneToken(this.tokens.optional)
2410
            })
2411
        );
2412
    }
2413

2414
}
2415

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

2445
    public readonly kind = AstNodeKind.InterfaceMethodStatement;
46✔
2446

2447
    public get location() {
2448
        return util.createBoundingLocation(
72✔
2449
            this.tokens.optional,
2450
            this.tokens.functionType,
2451
            this.tokens.name,
2452
            this.tokens.leftParen,
2453
            ...(this.params ?? []),
216!
2454
            this.tokens.rightParen,
2455
            this.tokens.as,
2456
            this.returnTypeExpression
2457
        );
2458
    }
2459
    /**
2460
     * Get the name of this method.
2461
     */
2462
    public getName(parseMode: ParseMode) {
2463
        return this.tokens.name.text;
28✔
2464
    }
2465

2466
    public readonly tokens: {
2467
        readonly optional?: Token;
2468
        readonly functionType: Token;
2469
        readonly name: Identifier;
2470
        readonly leftParen?: Token;
2471
        readonly rightParen?: Token;
2472
        readonly as?: Token;
2473
    };
2474

2475
    public readonly params: FunctionParameterExpression[];
2476
    public readonly returnTypeExpression?: TypeExpression;
2477

2478
    public get isOptional() {
2479
        return !!this.tokens.optional;
41✔
2480
    }
2481

2482
    public get leadingTrivia(): Token[] {
2483
        return this.tokens.optional?.leadingTrivia ?? this.tokens.functionType.leadingTrivia;
88✔
2484
    }
2485

2486
    walk(visitor: WalkVisitor, options: WalkOptions) {
2487
        if (options.walkMode & InternalWalkMode.walkExpressions) {
204✔
2488
            walk(this, 'returnTypeExpression', visitor, options);
179✔
2489
        }
2490
    }
2491

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

2546
    public getType(options: GetTypeOptions): TypedFunctionType {
2547
        //if there's a defined return type, use that
2548
        let returnType = this.returnTypeExpression?.getType(options);
28✔
2549
        const isSub = this.tokens.functionType?.kind === TokenKind.Sub || !returnType;
28!
2550
        //if we don't have a return type and this is a sub, set the return type to `void`. else use `dynamic`
2551
        if (!returnType) {
28✔
2552
            returnType = isSub ? VoidType.instance : DynamicType.instance;
10!
2553
        }
2554

2555
        const resultType = new TypedFunctionType(returnType);
28✔
2556
        resultType.isSub = isSub;
28✔
2557
        for (let param of this.params) {
28✔
2558
            resultType.addParameter(param.tokens.name.text, param.getType(options), !!param.defaultValue);
7✔
2559
        }
2560
        if (options.typeChain) {
28!
2561
            // need Interface type for type chain
NEW
2562
            this.parent?.getType(options);
×
2563
        }
2564
        let funcName = this.getName(ParseMode.BrighterScript);
28✔
2565
        resultType.setName(funcName);
28✔
2566
        options.typeChain?.push(new TypeChainEntry({ name: resultType.name, type: resultType, data: options.data, astNode: this }));
28!
2567
        return resultType;
28✔
2568
    }
2569

2570
    public clone() {
2571
        return this.finalizeClone(
4✔
2572
            new InterfaceMethodStatement({
2573
                optional: util.cloneToken(this.tokens.optional),
2574
                functionType: util.cloneToken(this.tokens.functionType),
2575
                name: util.cloneToken(this.tokens.name),
2576
                leftParen: util.cloneToken(this.tokens.leftParen),
2577
                params: this.params?.map(p => p?.clone()),
3✔
2578
                rightParen: util.cloneToken(this.tokens.rightParen),
2579
                as: util.cloneToken(this.tokens.as),
2580
                returnTypeExpression: this.returnTypeExpression?.clone()
12✔
2581
            }),
2582
            ['params']
2583
        );
2584
    }
2585
}
2586

2587
export class ClassStatement extends Statement implements TypedefProvider {
1✔
2588
    constructor(options: {
2589
        class?: Token;
2590
        /**
2591
         * The name of the class (without namespace prefix)
2592
         */
2593
        name: Identifier;
2594
        body: Statement[];
2595
        endClass?: Token;
2596
        extends?: Token;
2597
        parentClassName?: TypeExpression;
2598
    }) {
2599
        super();
688✔
2600
        this.body = options.body ?? [];
688✔
2601
        this.tokens = {
688✔
2602
            name: options.name,
2603
            class: options.class,
2604
            endClass: options.endClass,
2605
            extends: options.extends
2606
        };
2607
        this.parentClassName = options.parentClassName;
688✔
2608
        this.symbolTable = new SymbolTable(`ClassStatement: '${this.tokens.name?.text}'`, () => this.parent?.getSymbolTable());
1,293!
2609

2610
        for (let statement of this.body) {
688✔
2611
            if (isMethodStatement(statement)) {
689✔
2612
                this.methods.push(statement);
361✔
2613
                this.memberMap[statement?.tokens.name?.text.toLowerCase()] = statement;
361!
2614
            } else if (isFieldStatement(statement)) {
328✔
2615
                this.fields.push(statement);
327✔
2616
                this.memberMap[statement?.tokens.name?.text.toLowerCase()] = statement;
327!
2617
            }
2618
        }
2619

2620
        this.location = util.createBoundingLocation(
688✔
2621
            this.parentClassName,
2622
            ...(this.body ?? []),
2,064!
2623
            util.createBoundingLocationFromTokens(this.tokens)
2624
        );
2625
    }
2626

2627
    public readonly kind = AstNodeKind.ClassStatement;
688✔
2628

2629

2630
    public readonly tokens: {
2631
        readonly class?: Token;
2632
        /**
2633
         * The name of the class (without namespace prefix)
2634
         */
2635
        readonly name: Identifier;
2636
        readonly endClass?: Token;
2637
        readonly extends?: Token;
2638
    };
2639
    public readonly body: Statement[];
2640
    public readonly parentClassName: TypeExpression;
2641

2642

2643
    public getName(parseMode: ParseMode) {
2644
        const name = this.tokens.name?.text;
2,300✔
2645
        if (name) {
2,300✔
2646
            const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
2,298✔
2647
            if (namespace) {
2,298✔
2648
                let namespaceName = namespace.getName(parseMode);
864✔
2649
                let separator = parseMode === ParseMode.BrighterScript ? '.' : '_';
864✔
2650
                return namespaceName + separator + name;
864✔
2651
            } else {
2652
                return name;
1,434✔
2653
            }
2654
        } else {
2655
            //return undefined which will allow outside callers to know that this class doesn't have a name
2656
            return undefined;
2✔
2657
        }
2658
    }
2659

2660
    public get leadingTrivia(): Token[] {
2661
        return this.tokens.class?.leadingTrivia;
1,301!
2662
    }
2663

2664
    public get endTrivia(): Token[] {
NEW
2665
        return this.tokens.endClass?.leadingTrivia ?? [];
×
2666
    }
2667

2668
    public readonly memberMap = {} as Record<string, MemberStatement>;
688✔
2669
    public readonly methods = [] as MethodStatement[];
688✔
2670
    public readonly fields = [] as FieldStatement[];
688✔
2671

2672
    public readonly location: Location | undefined;
2673

2674
    transpile(state: BrsTranspileState) {
2675
        let result = [] as TranspileResult;
50✔
2676
        //make the builder
2677
        result.push(...this.getTranspiledBuilder(state));
50✔
2678
        result.push(
50✔
2679
            '\n',
2680
            state.indent()
2681
        );
2682
        //make the class assembler (i.e. the public-facing class creator method)
2683
        result.push(...this.getTranspiledClassFunction(state));
50✔
2684
        return result;
50✔
2685
    }
2686

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

2720
        let body = this.body;
15✔
2721
        //inject an empty "new" method if missing
2722
        if (!this.getConstructorFunction()) {
15✔
2723
            const constructor = createMethodStatement('new', TokenKind.Sub);
11✔
2724
            constructor.parent = this;
11✔
2725
            //walk the constructor to set up parent links
2726
            constructor.link();
11✔
2727
            body = [
11✔
2728
                constructor,
2729
                ...this.body
2730
            ];
2731
        }
2732

2733
        for (const member of body) {
15✔
2734
            if (isTypedefProvider(member)) {
33!
2735
                result.push(
33✔
2736
                    state.indent(),
2737
                    ...member.getTypedef(state),
2738
                    state.newline
2739
                );
2740
            }
2741
        }
2742
        state.blockDepth--;
15✔
2743
        result.push(
15✔
2744
            state.indent(),
2745
            'end class'
2746
        );
2747
        return result;
15✔
2748
    }
2749

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

2779
    public hasParentClass() {
2780
        return !!this.parentClassName;
282✔
2781
    }
2782

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

2805
    private getBuilderName(name: string) {
2806
        if (name.includes('.')) {
118✔
2807
            name = name.replace(/\./gi, '_');
3✔
2808
        }
2809
        return `__${name}_builder`;
118✔
2810
    }
2811

2812
    public getConstructorType() {
2813
        const constructorType = this.getConstructorFunction()?.getType({ flags: SymbolTypeFlag.runtime }) ?? new TypedFunctionType(null);
128✔
2814
        constructorType.returnType = this.getType({ flags: SymbolTypeFlag.runtime });
128✔
2815
        return constructorType;
128✔
2816
    }
2817

2818
    /**
2819
     * Get the constructor function for this class (if exists), or undefined if not exist
2820
     */
2821
    private getConstructorFunction() {
2822
        return this.body.find((stmt) => {
243✔
2823
            return (stmt as MethodStatement)?.tokens.name?.text?.toLowerCase() === 'new';
212!
2824
        }) as MethodStatement;
2825
    }
2826

2827
    /**
2828
     * Determine if the specified field was declared in one of the ancestor classes
2829
     */
2830
    public isFieldDeclaredByAncestor(fieldName: string, ancestors: ClassStatement[]) {
2831
        let lowerFieldName = fieldName.toLowerCase();
×
2832
        for (let ancestor of ancestors) {
×
2833
            if (ancestor.memberMap[lowerFieldName]) {
×
2834
                return true;
×
2835
            }
2836
        }
2837
        return false;
×
2838
    }
2839

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

2852
        /**
2853
         * The lineage of this class. index 0 is a direct parent, index 1 is index 0's parent, etc...
2854
         */
2855
        let ancestors = this.getAncestors(state);
50✔
2856

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

2877
        let body = this.body;
50✔
2878
        //inject an empty "new" method if missing
2879
        if (!this.getConstructorFunction()) {
50✔
2880
            body = [
29✔
2881
                createMethodStatement('new', TokenKind.Sub),
2882
                ...this.body
2883
            ];
2884
        }
2885

2886
        for (let statement of body) {
50✔
2887
            //is field statement
2888
            if (isFieldStatement(statement)) {
79✔
2889
                //do nothing with class fields in this situation, they are handled elsewhere
2890
                continue;
14✔
2891

2892
                //methods
2893
            } else if (isMethodStatement(statement)) {
65!
2894

2895
                //store overridden parent methods as super{parentIndex}_{methodName}
2896
                if (
65✔
2897
                    //is override method
2898
                    statement.tokens.override ||
176✔
2899
                    //is constructor function in child class
2900
                    (statement.tokens.name.text.toLowerCase() === 'new' && ancestors[0])
2901
                ) {
2902
                    result.push(
22✔
2903
                        `instance.super${parentClassIndex}_${statement.tokens.name.text} = instance.${statement.tokens.name.text}`,
2904
                        state.newline,
2905
                        state.indent()
2906
                    );
2907
                }
2908

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

2945
    /**
2946
     * The class function is the function with the same name as the class. This is the function that
2947
     * consumers should call to create a new instance of that class.
2948
     * This invokes the builder, gets an instance of the class, then invokes the "new" function on that class.
2949
     */
2950
    private getTranspiledClassFunction(state: BrsTranspileState) {
2951
        let result: TranspileResult = state.transpileAnnotations(this);
50✔
2952
        const constructorFunction = this.getConstructorFunction();
50✔
2953
        const constructorParams = constructorFunction ? constructorFunction.func.parameters : [];
50✔
2954

2955
        result.push(
50✔
2956
            state.transpileLeadingComments(this.tokens.class),
2957
            state.sourceNode(this.tokens.class, 'function'),
2958
            state.sourceNode(this.tokens.class, ' '),
2959
            state.sourceNode(this.tokens.name, this.getName(ParseMode.BrightScript)),
2960
            `(`
2961
        );
2962
        let i = 0;
50✔
2963
        for (let param of constructorParams) {
50✔
2964
            if (i > 0) {
8✔
2965
                result.push(', ');
2✔
2966
            }
2967
            result.push(
8✔
2968
                param.transpile(state)
2969
            );
2970
            i++;
8✔
2971
        }
2972
        result.push(
50✔
2973
            ')',
2974
            '\n'
2975
        );
2976

2977
        state.blockDepth++;
50✔
2978
        result.push(state.indent());
50✔
2979
        result.push(`instance = ${this.getBuilderName(this.getName(ParseMode.BrightScript)!)}()\n`);
50✔
2980

2981
        result.push(state.indent());
50✔
2982
        result.push(`instance.new(`);
50✔
2983

2984
        //append constructor arguments
2985
        i = 0;
50✔
2986
        for (let param of constructorParams) {
50✔
2987
            if (i > 0) {
8✔
2988
                result.push(', ');
2✔
2989
            }
2990
            result.push(
8✔
2991
                state.transpileToken(param.tokens.name)
2992
            );
2993
            i++;
8✔
2994
        }
2995
        result.push(
50✔
2996
            ')',
2997
            '\n'
2998
        );
2999

3000
        result.push(state.indent());
50✔
3001
        result.push(`return instance\n`);
50✔
3002

3003
        state.blockDepth--;
50✔
3004
        result.push(state.indent());
50✔
3005
        result.push(`end function`);
50✔
3006
        return result;
50✔
3007
    }
3008

3009
    walk(visitor: WalkVisitor, options: WalkOptions) {
3010
        //visitor-less walk function to do parent linking
3011
        walk(this, 'parentClassName', null, options);
2,635✔
3012

3013
        if (options.walkMode & InternalWalkMode.walkStatements) {
2,635!
3014
            walkArray(this.body, visitor, options, this);
2,635✔
3015
        }
3016
    }
3017

3018
    getType(options: GetTypeOptions) {
3019
        const superClass = this.parentClassName?.getType(options) as ClassType;
542✔
3020

3021
        const resultType = new ClassType(this.getName(ParseMode.BrighterScript), superClass);
542✔
3022

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

3052
    public clone() {
3053
        return this.finalizeClone(
11✔
3054
            new ClassStatement({
3055
                class: util.cloneToken(this.tokens.class),
3056
                name: util.cloneToken(this.tokens.name),
3057
                body: this.body?.map(x => x?.clone()),
10✔
3058
                endClass: util.cloneToken(this.tokens.endClass),
3059
                extends: util.cloneToken(this.tokens.extends),
3060
                parentClassName: this.parentClassName?.clone()
33✔
3061
            }),
3062
            ['body', 'parentClassName']
3063
        );
3064
    }
3065
}
3066

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

3100
    public readonly kind = AstNodeKind.MethodStatement as AstNodeKind;
401✔
3101

3102
    public readonly modifiers: Token[] = [];
401✔
3103

3104
    public readonly tokens: {
3105
        readonly name: Identifier;
3106
        readonly override?: Token;
3107
    };
3108

3109
    public get accessModifier() {
3110
        return this.modifiers.find(x => accessModifiers.includes(x.kind));
678✔
3111
    }
3112

3113
    public readonly location: Location | undefined;
3114

3115
    /**
3116
     * Get the name of this method.
3117
     */
3118
    public getName(parseMode: ParseMode) {
3119
        return this.tokens.name.text;
358✔
3120
    }
3121

3122
    public get leadingTrivia(): Token[] {
3123
        return this.func.leadingTrivia;
800✔
3124
    }
3125

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

3158
    getTypedef(state: BrsTranspileState) {
3159
        const result: TranspileResult = [];
23✔
3160
        for (let comment of util.getLeadingComments(this) ?? []) {
23!
NEW
3161
            result.push(
×
3162
                comment.text,
3163
                state.newline,
3164
                state.indent()
3165
            );
3166
        }
3167
        for (let annotation of this.annotations ?? []) {
23✔
3168
            result.push(
2✔
3169
                ...annotation.getTypedef(state),
3170
                state.newline,
3171
                state.indent()
3172
            );
3173
        }
3174
        if (this.accessModifier) {
23✔
3175
            result.push(
8✔
3176
                this.accessModifier.text,
3177
                ' '
3178
            );
3179
        }
3180
        if (this.tokens.override) {
23✔
3181
            result.push('override ');
1✔
3182
        }
3183
        result.push(
23✔
3184
            ...this.func.getTypedef(state)
3185
        );
3186
        return result;
23✔
3187
    }
3188

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

3199
        //check whether any calls to super exist
3200
        let containsSuperCall =
3201
            this.func.body.statements.findIndex((x) => {
18✔
3202
                //is a call statement
3203
                return isExpressionStatement(x) && isCallExpression(x.expression) &&
8✔
3204
                    //is a call to super
3205
                    util.findBeginningVariableExpression(x.expression.callee as any).tokens.name?.text.toLowerCase() === 'super';
21!
3206
            }) !== -1;
3207

3208
        //if a call to super exists, quit here
3209
        if (containsSuperCall) {
18✔
3210
            return;
7✔
3211
        }
3212

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

3248
    /**
3249
     * Inject field initializers at the top of the `new` function (after any present `super()` call)
3250
     */
3251
    private injectFieldInitializersForConstructor(state: BrsTranspileState) {
3252
        let startingIndex = state.classStatement!.hasParentClass() ? 1 : 0;
50✔
3253

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

3278
    walk(visitor: WalkVisitor, options: WalkOptions) {
3279
        if (options.walkMode & InternalWalkMode.walkExpressions) {
2,170✔
3280
            walk(this, 'func', visitor, options);
1,729✔
3281
        }
3282
    }
3283

3284
    public clone() {
3285
        return this.finalizeClone(
5✔
3286
            new MethodStatement({
3287
                modifiers: this.modifiers?.map(m => util.cloneToken(m)),
1✔
3288
                name: util.cloneToken(this.tokens.name),
3289
                func: this.func?.clone(),
15✔
3290
                override: util.cloneToken(this.tokens.override)
3291
            }),
3292
            ['func']
3293
        );
3294
    }
3295
}
3296

3297
export class FieldStatement extends Statement implements TypedefProvider {
1✔
3298
    constructor(options: {
3299
        accessModifier?: Token;
3300
        name: Identifier;
3301
        as?: Token;
3302
        typeExpression?: TypeExpression;
3303
        equals?: Token;
3304
        initialValue?: Expression;
3305
        optional?: Token;
3306
    }) {
3307
        super();
327✔
3308
        this.tokens = {
327✔
3309
            accessModifier: options.accessModifier,
3310
            name: options.name,
3311
            as: options.as,
3312
            equals: options.equals,
3313
            optional: options.optional
3314
        };
3315
        this.typeExpression = options.typeExpression;
327✔
3316
        this.initialValue = options.initialValue;
327✔
3317

3318
        this.location = util.createBoundingLocation(
327✔
3319
            util.createBoundingLocationFromTokens(this.tokens),
3320
            this.typeExpression,
3321
            this.initialValue
3322
        );
3323
    }
3324

3325
    public readonly tokens: {
3326
        readonly accessModifier?: Token;
3327
        readonly name: Identifier;
3328
        readonly as?: Token;
3329
        readonly equals?: Token;
3330
        readonly optional?: Token;
3331
    };
3332

3333
    public readonly typeExpression?: TypeExpression;
3334
    public readonly initialValue?: Expression;
3335

3336
    public readonly kind = AstNodeKind.FieldStatement;
327✔
3337

3338
    /**
3339
     * Derive a ValueKind from the type token, or the initial value.
3340
     * Defaults to `DynamicType`
3341
     */
3342
    getType(options: GetTypeOptions) {
3343
        return this.typeExpression?.getType({ ...options, flags: SymbolTypeFlag.typetime }) ??
311✔
3344
            this.initialValue?.getType({ ...options, flags: SymbolTypeFlag.runtime }) ?? DynamicType.instance;
725✔
3345
    }
3346

3347
    public readonly location: Location | undefined;
3348

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

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

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

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

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

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

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

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

3422
export type MemberStatement = FieldStatement | MethodStatement;
3423

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

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

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

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

3456
    public readonly location: Location | undefined;
3457

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

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

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

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

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

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

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

3522
    public readonly exceptionVariableExpression?: Expression;
3523

3524
    public readonly catchBranch?: Block;
3525

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

3528
    public readonly location: Location | undefined;
3529

3530
    public transpile(state: BrsTranspileState): TranspileResult {
3531
        return [
7✔
3532
            state.transpileToken(this.tokens.catch, 'catch'),
3533
            ' ',
3534
            this.exceptionVariableExpression?.transpile(state) ?? [
42✔
3535
                //use the variable named `e` if it doesn't exist in this function body. otherwise use '__bsc_error' just to make sure we're out of the way
3536
                this.getSymbolTable()?.hasSymbol('e', SymbolTypeFlag.runtime)
6!
3537
                    ? '__bsc_error'
2✔
3538
                    : 'e'
3539
            ],
3540
            ...(this.catchBranch?.transpile(state) ?? [])
42!
3541
        ];
3542
    }
3543

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

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

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

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

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

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

3589
    public readonly location: Location | undefined;
3590

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

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

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

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

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

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

3631

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

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

3656
    public readonly kind = AstNodeKind.EnumStatement;
170✔
3657

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

3804
        }
3805
    }
3806

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

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

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

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

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

3854
    public readonly kind = AstNodeKind.EnumMemberStatement;
321✔
3855

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

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

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

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

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

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

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

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

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

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

3950
    public readonly kind = AstNodeKind.ConstStatement;
158✔
3951

3952
    public readonly location: Location | undefined;
3953

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

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

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

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

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

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

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

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

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

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

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

4057
    public readonly location: Location | undefined;
4058

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

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

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

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

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

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

4106
    public readonly typecastExpression: TypecastExpression;
4107

4108
    public readonly kind = AstNodeKind.TypecastStatement;
25✔
4109

4110
    public readonly location: Location;
4111

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

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

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

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

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

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

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

4168

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

4171
    public readonly location: Location | undefined;
4172

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

4179
        ];
4180
    }
4181

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

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

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

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

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

4229
    public readonly value: Expression;
4230

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

4233
    public readonly location: Location;
4234

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

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

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

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

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

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

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

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

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

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

4316
    public readonly location: Location | undefined;
4317

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

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

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

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

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

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

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

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

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

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

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

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

4386
        return results;
3✔
4387
    }
4388

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

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

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

4427

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

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

4445
    public readonly assignment: AssignmentStatement;
4446

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

4449
    public readonly location: Location | undefined;
4450

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

4462
    }
4463

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

4468

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

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