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

rokucommunity / brighterscript / #14375

08 May 2025 08:02PM UTC coverage: 87.105% (-1.9%) from 89.017%
#14375

push

web-flow
Merge bb74432dc into 489231ac7

13490 of 16372 branches covered (82.4%)

Branch coverage included in aggregate %.

8031 of 8709 new or added lines in 103 files covered. (92.21%)

84 existing lines in 22 files now uncovered.

14463 of 15719 relevant lines covered (92.01%)

19993.16 hits per line

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

91.52
/src/parser/Expression.ts
1
/* eslint-disable no-bitwise */
2
import type { Token, Identifier } from '../lexer/Token';
3
import type { PrintSeparatorToken } from '../lexer/TokenKind';
4
import { TokenKind } from '../lexer/TokenKind';
1✔
5
import type { Block, NamespaceStatement } from './Statement';
6
import type { Location } from 'vscode-languageserver';
7
import util from '../util';
1✔
8
import type { BrsTranspileState } from './BrsTranspileState';
9
import { ParseMode } from './Parser';
1✔
10
import * as fileUrl from 'file-url';
1✔
11
import type { WalkOptions, WalkVisitor } from '../astUtils/visitors';
12
import { WalkMode } from '../astUtils/visitors';
1✔
13
import { walk, InternalWalkMode, walkArray } from '../astUtils/visitors';
1✔
14
import { isAALiteralExpression, isAAMemberExpression, isArrayLiteralExpression, isArrayType, isCallableType, isCallExpression, isCallfuncExpression, isDottedGetExpression, isEscapedCharCodeLiteralExpression, isFunctionExpression, isFunctionStatement, isIntegerType, isInterfaceMethodStatement, isInvalidType, isLiteralBoolean, isLiteralExpression, isLiteralNumber, isLiteralString, isLongIntegerType, isMethodStatement, isNamespaceStatement, isNativeType, isNewExpression, isPrimitiveType, isReferenceType, isStringType, isTemplateStringExpression, isTypecastExpression, isUnaryExpression, isVariableExpression, isVoidType } from '../astUtils/reflection';
1✔
15
import type { GetTypeOptions, TranspileResult, TypedefProvider } from '../interfaces';
16
import { TypeChainEntry } from '../interfaces';
1✔
17
import { VoidType } from '../types/VoidType';
1✔
18
import { DynamicType } from '../types/DynamicType';
1✔
19
import type { BscType } from '../types/BscType';
20
import type { AstNode } from './AstNode';
21
import { AstNodeKind, Expression } from './AstNode';
1✔
22
import { SymbolTable } from '../SymbolTable';
1✔
23
import { SourceNode } from 'source-map';
1✔
24
import type { TranspileState } from './TranspileState';
25
import { StringType } from '../types/StringType';
1✔
26
import { TypePropertyReferenceType } from '../types/ReferenceType';
1✔
27
import { UnionType } from '../types/UnionType';
1✔
28
import { ArrayType } from '../types/ArrayType';
1✔
29
import { AssociativeArrayType } from '../types/AssociativeArrayType';
1✔
30
import { TypedFunctionType } from '../types/TypedFunctionType';
1✔
31
import { InvalidType } from '../types/InvalidType';
1✔
32
import { UninitializedType } from '../types/UninitializedType';
1✔
33
import { SymbolTypeFlag } from '../SymbolTypeFlag';
1✔
34
import { FunctionType } from '../types/FunctionType';
1✔
35
import type { BaseFunctionType } from '../types/BaseFunctionType';
36
import { brsDocParser } from './BrightScriptDocParser';
1✔
37

38
export type ExpressionVisitor = (expression: Expression, parent: Expression) => void;
39

40
export class BinaryExpression extends Expression {
1✔
41
    constructor(options: {
42
        left: Expression;
43
        operator: Token;
44
        right: Expression;
45
    }) {
46
        super();
3,455✔
47
        this.tokens = {
3,455✔
48
            operator: options.operator
49
        };
50
        this.left = options.left;
3,455✔
51
        this.right = options.right;
3,455✔
52
        this.location = util.createBoundingLocation(this.left, this.tokens.operator, this.right);
3,455✔
53
    }
54
    readonly tokens: {
55
        readonly operator: Token;
56
    };
57

58
    public readonly left: Expression;
59
    public readonly right: Expression;
60

61
    public readonly kind = AstNodeKind.BinaryExpression;
3,455✔
62

63
    public readonly location: Location | undefined;
64

65
    transpile(state: BrsTranspileState): TranspileResult {
66
        return [
3,421✔
67
            state.sourceNode(this.left, this.left.transpile(state)),
68
            ' ',
69
            state.transpileToken(this.tokens.operator),
70
            ' ',
71
            state.sourceNode(this.right, this.right.transpile(state))
72
        ];
73
    }
74

75
    walk(visitor: WalkVisitor, options: WalkOptions) {
76
        if (options.walkMode & InternalWalkMode.walkExpressions) {
14,785!
77
            walk(this, 'left', visitor, options);
14,785✔
78
            walk(this, 'right', visitor, options);
14,785✔
79
        }
80
    }
81

82

83
    public getType(options: GetTypeOptions): BscType {
84
        const operatorKind = this.tokens.operator.kind;
391✔
85
        if (options.flags & SymbolTypeFlag.typetime) {
391✔
86
            // eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check
87
            switch (operatorKind) {
79✔
88
                case TokenKind.Or:
78✔
89
                    return new UnionType([this.left.getType(options), this.right.getType(options)]);
78✔
90
                //TODO: Intersection Types?, eg. case TokenKind.And:
91
            }
92
        } else if (options.flags & SymbolTypeFlag.runtime) {
312!
93
            return util.binaryOperatorResultType(
312✔
94
                this.left.getType(options),
95
                this.tokens.operator,
96
                this.right.getType(options)) ?? DynamicType.instance;
312✔
97
        }
98
        return DynamicType.instance;
1✔
99
    }
100

101
    get leadingTrivia(): Token[] {
102
        return this.left.leadingTrivia;
1,884✔
103
    }
104

105
    public clone() {
106
        return this.finalizeClone(
3✔
107
            new BinaryExpression({
108
                left: this.left?.clone(),
9✔
109
                operator: util.cloneToken(this.tokens.operator),
110
                right: this.right?.clone()
9✔
111
            }),
112
            ['left', 'right']
113
        );
114
    }
115
}
116

117

118
export class CallExpression extends Expression {
1✔
119
    /**
120
     * Number of parameters that can be defined on a function
121
     *
122
     * Prior to Roku OS 11.5, this was 32
123
     * As of Roku OS 11.5, this is 63
124
     */
125
    static MaximumArguments = 63;
1✔
126

127
    constructor(options: {
128
        callee: Expression;
129
        openingParen?: Token;
130
        args?: Expression[];
131
        closingParen?: Token;
132
    }) {
133
        super();
2,635✔
134
        this.tokens = {
2,635✔
135
            openingParen: options.openingParen,
136
            closingParen: options.closingParen
137
        };
138
        this.callee = options.callee;
2,635✔
139
        this.args = options.args ?? [];
2,635✔
140
        this.location = util.createBoundingLocation(this.callee, this.tokens.openingParen, ...this.args ?? [], this.tokens.closingParen);
2,635!
141
    }
142

143
    readonly callee: Expression;
144
    readonly args: Expression[];
145
    readonly tokens: {
146
        /**
147
         * Can either be `(`, or `?(` for optional chaining - defaults to '('
148
         */
149
        readonly openingParen?: Token;
150
        readonly closingParen?: Token;
151
    };
152

153
    public readonly kind = AstNodeKind.CallExpression;
2,635✔
154

155
    public readonly location: Location | undefined;
156

157
    transpile(state: BrsTranspileState, nameOverride?: string) {
158
        let result: TranspileResult = [];
1,728✔
159

160
        //transpile the name
161
        if (nameOverride) {
1,728✔
162
            result.push(
12✔
163
                //transpile leading comments since we're bypassing callee.transpile (which would normally do this)
164
                ...state.transpileLeadingCommentsForAstNode(this),
165
                state.sourceNode(this.callee, nameOverride)
166
            );
167
        } else {
168
            result.push(...this.callee.transpile(state));
1,716✔
169
        }
170

171
        result.push(
1,728✔
172
            state.transpileToken(this.tokens.openingParen, '(')
173
        );
174
        for (let i = 0; i < this.args.length; i++) {
1,728✔
175
            //add comma between args
176
            if (i > 0) {
1,254✔
177
                result.push(', ');
382✔
178
            }
179
            let arg = this.args[i];
1,254✔
180
            result.push(...arg.transpile(state));
1,254✔
181
        }
182
        result.push(
1,728✔
183
            state.transpileToken(this.tokens.closingParen, ')')
184
        );
185
        return result;
1,728✔
186
    }
187

188
    walk(visitor: WalkVisitor, options: WalkOptions) {
189
        if (options.walkMode & InternalWalkMode.walkExpressions) {
12,783!
190
            walk(this, 'callee', visitor, options);
12,783✔
191
            walkArray(this.args, visitor, options, this);
12,783✔
192
        }
193
    }
194

195
    getType(options: GetTypeOptions) {
196
        const calleeType = this.callee.getType(options);
1,050✔
197
        if (options.ignoreCall) {
1,050!
NEW
198
            return calleeType;
×
199
        }
200
        if (isNewExpression(this.parent)) {
1,050✔
201
            return calleeType;
350✔
202
        }
203
        const specialCaseReturnType = util.getSpecialCaseCallExpressionReturnType(this, options);
700✔
204
        if (specialCaseReturnType) {
700✔
205
            return specialCaseReturnType;
124✔
206
        }
207
        if (isCallableType(calleeType) && (!isReferenceType(calleeType.returnType) || calleeType.returnType?.isResolvable())) {
576!
208
            if (isVoidType(calleeType.returnType)) {
270✔
209
                if (options.data?.isBuiltIn) {
10✔
210
                    // built in functions that return `as void` will not initialize the result
211
                    return UninitializedType.instance;
3✔
212
                }
213
                // non-built in functions with return type`as void` actually return `invalid`
214
                return InvalidType.instance;
7✔
215
            }
216
            return calleeType.returnType;
260✔
217
        }
218
        if (!isReferenceType(calleeType) && (calleeType as BaseFunctionType)?.returnType?.isResolvable()) {
306✔
219
            return (calleeType as BaseFunctionType).returnType;
258✔
220
        }
221
        return new TypePropertyReferenceType(calleeType, 'returnType');
48✔
222
    }
223

224
    get leadingTrivia(): Token[] {
225
        return this.callee.leadingTrivia;
9,324✔
226
    }
227

228
    public clone() {
229
        return this.finalizeClone(
13✔
230
            new CallExpression({
231
                callee: this.callee?.clone(),
39✔
232
                openingParen: util.cloneToken(this.tokens.openingParen),
233
                closingParen: util.cloneToken(this.tokens.closingParen),
234
                args: this.args?.map(e => e?.clone())
12✔
235
            }),
236
            ['callee', 'args']
237
        );
238
    }
239
}
240

241
export class FunctionExpression extends Expression implements TypedefProvider {
1✔
242
    constructor(options: {
243
        functionType?: Token;
244
        leftParen?: Token;
245
        parameters?: FunctionParameterExpression[];
246
        rightParen?: Token;
247
        as?: Token;
248
        returnTypeExpression?: TypeExpression;
249
        body: Block;
250
        endFunctionType?: Token;
251
    }) {
252
        super();
4,144✔
253
        this.tokens = {
4,144✔
254
            functionType: options.functionType,
255
            leftParen: options.leftParen,
256
            rightParen: options.rightParen,
257
            as: options.as,
258
            endFunctionType: options.endFunctionType
259
        };
260
        this.parameters = options.parameters ?? [];
4,144✔
261
        this.body = options.body;
4,144✔
262
        this.returnTypeExpression = options.returnTypeExpression;
4,144✔
263

264
        //if there's a body, and it doesn't have a SymbolTable, assign one
265
        if (this.body) {
4,144✔
266
            if (!this.body.symbolTable) {
4,143!
267
                this.body.symbolTable = new SymbolTable(`Block`, () => this.getSymbolTable());
31,223✔
268
            } else {
NEW
269
                this.body.symbolTable.pushParentProvider(() => this.getSymbolTable());
×
270
            }
271
            this.body.parent = this;
4,143✔
272
        }
273
        this.symbolTable = new SymbolTable('FunctionExpression', () => this.parent?.getSymbolTable());
39,450!
274
    }
275

276
    public readonly kind = AstNodeKind.FunctionExpression;
4,144✔
277

278
    readonly parameters: FunctionParameterExpression[];
279
    public readonly body: Block;
280
    public readonly returnTypeExpression?: TypeExpression;
281

282
    readonly tokens: {
283
        readonly functionType?: Token;
284
        readonly endFunctionType?: Token;
285
        readonly leftParen?: Token;
286
        readonly rightParen?: Token;
287
        readonly as?: Token;
288
    };
289

290
    public get leadingTrivia(): Token[] {
291
        return this.tokens.functionType?.leadingTrivia;
30,289✔
292
    }
293

294
    public get endTrivia(): Token[] {
295
        return this.tokens.endFunctionType?.leadingTrivia;
2!
296
    }
297

298
    /**
299
     * The range of the function, starting at the 'f' in function or 's' in sub (or the open paren if the keyword is missing),
300
     * and ending with the last n' in 'end function' or 'b' in 'end sub'
301
     */
302
    public get location(): Location {
303
        return util.createBoundingLocation(
10,878✔
304
            this.tokens.functionType,
305
            this.tokens.leftParen,
306
            ...this.parameters ?? [],
32,634!
307
            this.tokens.rightParen,
308
            this.tokens.as,
309
            this.returnTypeExpression,
310
            this.tokens.endFunctionType
311
        );
312
    }
313

314
    transpile(state: BrsTranspileState, name?: Identifier, includeBody = true) {
1,537✔
315
        let results = [] as TranspileResult;
1,537✔
316
        //'function'|'sub'
317
        results.push(
1,537✔
318
            state.transpileToken(this.tokens.functionType, 'function', false, state.skipLeadingComments)
319
        );
320
        //functionName?
321
        if (name) {
1,537✔
322
            results.push(
1,462✔
323
                ' ',
324
                state.transpileToken(name)
325
            );
326
        }
327
        //leftParen
328
        results.push(
1,537✔
329
            state.transpileToken(this.tokens.leftParen, '(')
330
        );
331
        //parameters
332
        for (let i = 0; i < this.parameters.length; i++) {
1,537✔
333
            let param = this.parameters[i];
2,328✔
334
            //add commas
335
            if (i > 0) {
2,328✔
336
                results.push(', ');
1,142✔
337
            }
338
            //add parameter
339
            results.push(param.transpile(state));
2,328✔
340
        }
341
        //right paren
342
        results.push(
1,537✔
343
            state.transpileToken(this.tokens.rightParen, ')')
344
        );
345
        //as [Type]
346

347
        if (this.tokens.as && this.returnTypeExpression && (this.requiresReturnType || !state.options.removeParameterTypes)) {
1,537✔
348
            results.push(
43✔
349
                ' ',
350
                //as
351
                state.transpileToken(this.tokens.as, 'as'),
352
                ' ',
353
                //return type
354
                ...this.returnTypeExpression.transpile(state)
355
            );
356
        }
357
        let hasBody = false;
1,537✔
358
        if (includeBody) {
1,537!
359
            state.lineage.unshift(this);
1,537✔
360
            let body = this.body.transpile(state);
1,537✔
361
            hasBody = body.length > 0;
1,537✔
362
            state.lineage.shift();
1,537✔
363
            results.push(...body);
1,537✔
364
        }
365

366
        const lastLocatable = hasBody ? this.body : this.returnTypeExpression ?? this.tokens.leftParen ?? this.tokens.functionType;
1,537!
367
        results.push(
1,537✔
368
            ...state.transpileEndBlockToken(lastLocatable, this.tokens.endFunctionType, `end ${this.tokens.functionType ?? 'function'}`)
4,611✔
369
        );
370
        return results;
1,537✔
371
    }
372

373
    getTypedef(state: BrsTranspileState) {
374
        let results = [
62✔
375
            new SourceNode(1, 0, null, [
376
                //'function'|'sub'
377
                this.tokens.functionType?.text ?? 'function',
372!
378
                //functionName?
379
                ...(isFunctionStatement(this.parent) || isMethodStatement(this.parent) ? [' ', this.parent.tokens.name?.text ?? ''] : []),
519!
380
                //leftParen
381
                '(',
382
                //parameters
383
                ...(
384
                    this.parameters?.map((param, i) => ([
73!
385
                        //separating comma
386
                        i > 0 ? ', ' : '',
73✔
387
                        ...param.getTypedef(state)
388
                    ])) ?? []
62!
389
                ) as any,
390
                //right paren
391
                ')',
392
                //as <ReturnType>
393
                ...(this.returnTypeExpression ? [
62✔
394
                    ' ',
395
                    this.tokens.as?.text ?? 'as',
18!
396
                    ' ',
397
                    ...this.returnTypeExpression.getTypedef(state)
398
                ] : []),
399
                '\n',
400
                state.indent(),
401
                //'end sub'|'end function'
402
                this.tokens.endFunctionType?.text ?? `end ${this.tokens.functionType ?? 'function'}`
372!
403
            ])
404
        ];
405
        return results;
62✔
406
    }
407

408
    walk(visitor: WalkVisitor, options: WalkOptions) {
409
        if (options.walkMode & InternalWalkMode.walkExpressions) {
19,604!
410
            walkArray(this.parameters, visitor, options, this);
19,604✔
411
            walk(this, 'returnTypeExpression', visitor, options);
19,603✔
412
            //This is the core of full-program walking...it allows us to step into sub functions
413
            if (options.walkMode & InternalWalkMode.recurseChildFunctions) {
19,603✔
414
                walk(this, 'body', visitor, options);
19,599✔
415
            }
416
        }
417
    }
418

419
    public getType(options: GetTypeOptions): TypedFunctionType {
420
        //if there's a defined return type, use that
421
        let returnType: BscType;
422

423
        const docs = brsDocParser.parseNode(this.findAncestor(isFunctionStatement));
4,597✔
424

425
        returnType = util.chooseTypeFromCodeOrDocComment(
4,597✔
426
            this.returnTypeExpression?.getType({ ...options, typeChain: undefined }),
13,791✔
NEW
427
            docs.getReturnBscType({ ...options, tableProvider: () => this.getSymbolTable() }),
×
428
            options
429
        );
430

431
        const isSub = this.tokens.functionType?.kind === TokenKind.Sub;
4,597✔
432
        //if we don't have a return type and this is a sub, set the return type to `void`. else use `dynamic`
433
        if (!returnType) {
4,597✔
434
            returnType = isSub ? VoidType.instance : DynamicType.instance;
3,745✔
435
        }
436

437
        const resultType = new TypedFunctionType(returnType);
4,597✔
438
        resultType.isSub = isSub;
4,597✔
439
        for (let param of this.parameters) {
4,597✔
440
            resultType.addParameter(param.tokens.name.text, param.getType({ ...options, typeChain: undefined }), !!param.defaultValue);
2,288✔
441
        }
442
        // Figure out this function's name if we can
443
        let funcName = '';
4,597✔
444
        if (isMethodStatement(this.parent) || isInterfaceMethodStatement(this.parent)) {
4,597✔
445
            funcName = this.parent.getName(ParseMode.BrighterScript);
358✔
446
            if (options.typeChain) {
358✔
447
                // Get the typechain info from the parent class
448
                this.parent.parent?.getType(options);
1!
449
            }
450
        } else if (isFunctionStatement(this.parent)) {
4,239✔
451
            funcName = this.parent.getName(ParseMode.BrighterScript);
4,148✔
452
        }
453
        if (funcName) {
4,597✔
454
            resultType.setName(funcName);
4,506✔
455
        }
456
        options.typeChain?.push(new TypeChainEntry({ name: funcName, type: resultType, data: options.data, astNode: this }));
4,597✔
457
        return resultType;
4,597✔
458
    }
459

460
    private get requiresReturnType() {
461
        /**
462
         * RokuOS methods can be written several different ways:
463
         * 1. Function() : return withValue
464
         * 2. Function() as type : return withValue
465
         * 3. Function() as void : return
466
         *
467
         * 4. Sub() : return
468
         * 5. Sub () as void : return
469
         * 6. Sub() as type : return withValue
470
         *
471
         * Formats (1), (2), and (6) throw a compile error if there IS NOT a return value in the function body.
472
         * Formats (3), (4), and (5) throw a compile error if there IS a return value in the function body.
473
         *
474
         * 7. Additionally, as a special case, the OS requires that `onKeyEvent()` be defined with `as boolean`
475
         */
476

477

478
        if ((isFunctionStatement(this.parent) || isMethodStatement(this.parent)) && this.parent?.tokens?.name?.text.toLowerCase() === 'onkeyevent') {
45!
479
            // onKeyEvent() requires 'as Boolean' otherwise RokuOS throws errors
480
            return true;
1✔
481
        }
482
        const isSub = this.tokens.functionType?.text.toLowerCase() === 'sub';
44!
483
        const returnType = this.returnTypeExpression?.getType({ flags: SymbolTypeFlag.typetime });
44!
484
        const isVoidReturnType = isVoidType(returnType);
44✔
485

486

487
        if (isSub && !isVoidReturnType) { // format (6)
44✔
488
            return true;
25✔
489
        } else if (isVoidReturnType) { // format (3)
19✔
490
            return true;
4✔
491
        }
492

493
        return false;
15✔
494
    }
495

496
    public clone() {
497
        return this.finalizeClone(
110✔
498
            new FunctionExpression({
499
                parameters: this.parameters?.map(e => e?.clone()),
7✔
500
                body: this.body?.clone(),
330✔
501
                functionType: util.cloneToken(this.tokens.functionType),
502
                endFunctionType: util.cloneToken(this.tokens.endFunctionType),
503
                leftParen: util.cloneToken(this.tokens.leftParen),
504
                rightParen: util.cloneToken(this.tokens.rightParen),
505
                as: util.cloneToken(this.tokens.as),
506
                returnTypeExpression: this.returnTypeExpression?.clone()
330✔
507
            }),
508
            ['body', 'returnTypeExpression']
509
        );
510
    }
511
}
512

513
export class FunctionParameterExpression extends Expression {
1✔
514
    constructor(options: {
515
        name: Identifier;
516
        equals?: Token;
517
        defaultValue?: Expression;
518
        as?: Token;
519
        typeExpression?: TypeExpression;
520
    }) {
521
        super();
3,210✔
522
        this.tokens = {
3,210✔
523
            name: options.name,
524
            equals: options.equals,
525
            as: options.as
526
        };
527
        this.defaultValue = options.defaultValue;
3,210✔
528
        this.typeExpression = options.typeExpression;
3,210✔
529
    }
530

531
    public readonly kind = AstNodeKind.FunctionParameterExpression;
3,210✔
532

533
    readonly tokens: {
534
        readonly name: Identifier;
535
        readonly equals?: Token;
536
        readonly as?: Token;
537
    };
538

539
    public readonly defaultValue?: Expression;
540
    public readonly typeExpression?: TypeExpression;
541

542
    public getType(options: GetTypeOptions) {
543
        const docs = brsDocParser.parseNode(this.findAncestor(isFunctionStatement));
5,786✔
544
        const paramName = this.tokens.name.text;
5,786✔
545

546
        let paramTypeFromCode = this.typeExpression?.getType({ ...options, flags: SymbolTypeFlag.typetime, typeChain: undefined }) ??
5,786✔
547
            util.getDefaultTypeFromValueType(this.defaultValue?.getType({ ...options, flags: SymbolTypeFlag.runtime, typeChain: undefined }));
8,772✔
548
        if (isInvalidType(paramTypeFromCode) || isVoidType(paramTypeFromCode)) {
5,786✔
549
            paramTypeFromCode = undefined;
20✔
550
        }
551
        const paramTypeFromDoc = docs.getParamBscType(paramName, { ...options, fullName: paramName, typeChain: undefined, tableProvider: () => this.getSymbolTable() });
5,786✔
552

553
        let paramType = util.chooseTypeFromCodeOrDocComment(paramTypeFromCode, paramTypeFromDoc, options) ?? DynamicType.instance;
5,786✔
554
        options.typeChain?.push(new TypeChainEntry({ name: paramName, type: paramType, data: options.data, astNode: this }));
5,786✔
555
        return paramType;
5,786✔
556
    }
557

558
    public get location(): Location | undefined {
559
        return util.createBoundingLocation(
9,538✔
560
            this.tokens.name,
561
            this.tokens.as,
562
            this.typeExpression,
563
            this.tokens.equals,
564
            this.defaultValue
565
        );
566
    }
567

568
    public transpile(state: BrsTranspileState) {
569
        let result: TranspileResult = [
2,345✔
570
            //name
571
            state.transpileToken(this.tokens.name)
572
        ];
573
        //default value
574
        if (this.defaultValue) {
2,345✔
575
            result.push(' = ');
9✔
576
            result.push(this.defaultValue.transpile(state));
9✔
577
        }
578
        //type declaration
579
        if (this.typeExpression && !state.options.removeParameterTypes) {
2,345✔
580
            result.push(' ');
72✔
581
            result.push(state.transpileToken(this.tokens.as, 'as'));
72✔
582
            result.push(' ');
72✔
583
            result.push(
72✔
584
                ...(this.typeExpression?.transpile(state) ?? [])
432!
585
            );
586
        }
587

588
        return result;
2,345✔
589
    }
590

591
    public getTypedef(state: BrsTranspileState): TranspileResult {
592
        const results = [this.tokens.name.text] as TranspileResult;
73✔
593

594
        if (this.defaultValue) {
73!
595
            results.push(' = ', ...this.defaultValue.transpile(state));
×
596
        }
597

598
        if (this.tokens.as) {
73✔
599
            results.push(' as ');
6✔
600

601
            // TODO: Is this conditional needed? Will typeToken always exist
602
            // so long as `asToken` exists?
603
            if (this.typeExpression) {
6!
604
                results.push(...(this.typeExpression?.getTypedef(state) ?? ['']));
6!
605
            }
606
        }
607

608
        return results;
73✔
609
    }
610

611
    walk(visitor: WalkVisitor, options: WalkOptions) {
612
        // eslint-disable-next-line no-bitwise
613
        if (options.walkMode & InternalWalkMode.walkExpressions) {
15,601!
614
            walk(this, 'defaultValue', visitor, options);
15,601✔
615
            walk(this, 'typeExpression', visitor, options);
15,601✔
616
        }
617
    }
618

619
    get leadingTrivia(): Token[] {
620
        return this.tokens.name.leadingTrivia;
4,865✔
621
    }
622

623
    public clone() {
624
        return this.finalizeClone(
14✔
625
            new FunctionParameterExpression({
626
                name: util.cloneToken(this.tokens.name),
627
                as: util.cloneToken(this.tokens.as),
628
                typeExpression: this.typeExpression?.clone(),
42✔
629
                equals: util.cloneToken(this.tokens.equals),
630
                defaultValue: this.defaultValue?.clone()
42✔
631
            }),
632
            ['typeExpression', 'defaultValue']
633
        );
634
    }
635
}
636

637
export class DottedGetExpression extends Expression {
1✔
638
    constructor(options: {
639
        obj: Expression;
640
        name: Identifier;
641
        /**
642
         * Can either be `.`, or `?.` for optional chaining - defaults in transpile to '.'
643
         */
644
        dot?: Token;
645
    }) {
646
        super();
2,976✔
647
        this.tokens = {
2,976✔
648
            name: options.name,
649
            dot: options.dot
650
        };
651
        this.obj = options.obj;
2,976✔
652

653
        this.location = util.createBoundingLocation(this.obj, this.tokens.dot, this.tokens.name);
2,976✔
654
    }
655

656
    readonly tokens: {
657
        readonly name: Identifier;
658
        readonly dot?: Token;
659
    };
660
    readonly obj: Expression;
661

662
    public readonly kind = AstNodeKind.DottedGetExpression;
2,976✔
663

664
    public readonly location: Location | undefined;
665

666
    transpile(state: BrsTranspileState) {
667
        //if the callee starts with a namespace name, transpile the name
668
        if (state.file.calleeStartsWithNamespace(this)) {
968✔
669
            return [
9✔
670
                ...state.transpileLeadingCommentsForAstNode(this),
671
                state.sourceNode(this, this.getName(ParseMode.BrightScript))
672
            ];
673
        } else {
674
            return [
959✔
675
                ...this.obj.transpile(state),
676
                state.transpileToken(this.tokens.dot, '.'),
677
                state.transpileToken(this.tokens.name)
678
            ];
679
        }
680
    }
681

682
    walk(visitor: WalkVisitor, options: WalkOptions) {
683
        if (options.walkMode & InternalWalkMode.walkExpressions) {
12,450!
684
            walk(this, 'obj', visitor, options);
12,450✔
685
        }
686
    }
687

688
    getType(options: GetTypeOptions) {
689
        const objType = this.obj?.getType(options);
6,763!
690
        let result = objType?.getMemberType(this.tokens.name?.text, options);
6,763!
691

692
        if (util.isClassUsedAsFunction(result, this, options)) {
6,763✔
693
            // treat this class constructor as a function
694
            result = FunctionType.instance;
11✔
695
        }
696
        options.typeChain?.push(new TypeChainEntry({
6,763✔
697
            name: this.tokens.name?.text,
8,619!
698
            type: result,
699
            data: options.data,
700
            location: this.tokens.name?.location ?? this.location,
17,238!
701
            astNode: this
702
        }));
703
        if (result ||
6,763✔
704
            options.flags & SymbolTypeFlag.typetime ||
705
            (isPrimitiveType(objType) || isCallableType(objType))) {
706
            // All types should be known at typeTime, or the obj is well known
707
            return result;
6,717✔
708
        }
709
        // It is possible at runtime that a value has been added dynamically to an object, or something
710
        // TODO: maybe have a strict flag on this?
711
        return DynamicType.instance;
46✔
712
    }
713

714
    getName(parseMode: ParseMode) {
715
        return util.getAllDottedGetPartsAsString(this, parseMode);
35✔
716
    }
717

718
    get leadingTrivia(): Token[] {
719
        return this.obj.leadingTrivia;
15,737✔
720
    }
721

722
    public clone() {
723
        return this.finalizeClone(
7✔
724
            new DottedGetExpression({
725
                obj: this.obj?.clone(),
21✔
726
                dot: util.cloneToken(this.tokens.dot),
727
                name: util.cloneToken(this.tokens.name)
728
            }),
729
            ['obj']
730
        );
731
    }
732
}
733

734
export class XmlAttributeGetExpression extends Expression {
1✔
735
    constructor(options: {
736
        obj: Expression;
737
        /**
738
         * Can either be `@`, or `?@` for optional chaining - defaults to '@'
739
         */
740
        at?: Token;
741
        name: Identifier;
742
    }) {
743
        super();
14✔
744
        this.obj = options.obj;
14✔
745
        this.tokens = { at: options.at, name: options.name };
14✔
746
        this.location = util.createBoundingLocation(this.obj, this.tokens.at, this.tokens.name);
14✔
747
    }
748

749
    public readonly kind = AstNodeKind.XmlAttributeGetExpression;
14✔
750

751
    public readonly tokens: {
752
        name: Identifier;
753
        at?: Token;
754
    };
755

756
    public readonly obj: Expression;
757

758
    public readonly location: Location | undefined;
759

760
    transpile(state: BrsTranspileState) {
761
        return [
3✔
762
            ...this.obj.transpile(state),
763
            state.transpileToken(this.tokens.at, '@'),
764
            state.transpileToken(this.tokens.name)
765
        ];
766
    }
767

768
    walk(visitor: WalkVisitor, options: WalkOptions) {
769
        if (options.walkMode & InternalWalkMode.walkExpressions) {
32!
770
            walk(this, 'obj', visitor, options);
32✔
771
        }
772
    }
773

774
    get leadingTrivia(): Token[] {
775
        return this.obj.leadingTrivia;
21✔
776
    }
777

778
    public clone() {
779
        return this.finalizeClone(
2✔
780
            new XmlAttributeGetExpression({
781
                obj: this.obj?.clone(),
6✔
782
                at: util.cloneToken(this.tokens.at),
783
                name: util.cloneToken(this.tokens.name)
784
            }),
785
            ['obj']
786
        );
787
    }
788
}
789

790
export class IndexedGetExpression extends Expression {
1✔
791
    constructor(options: {
792
        obj: Expression;
793
        indexes: Expression[];
794
        /**
795
         * Can either be `[` or `?[`. If `?.[` is used, this will be `[` and `optionalChainingToken` will be `?.` - defaults to '[' in transpile
796
         */
797
        openingSquare?: Token;
798
        closingSquare?: Token;
799
        questionDot?: Token;//  ? or ?.
800
    }) {
801
        super();
166✔
802
        this.tokens = {
166✔
803
            openingSquare: options.openingSquare,
804
            closingSquare: options.closingSquare,
805
            questionDot: options.questionDot
806
        };
807
        this.obj = options.obj;
166✔
808
        this.indexes = options.indexes;
166✔
809
        this.location = util.createBoundingLocation(
166✔
810
            this.obj,
811
            this.tokens.openingSquare,
812
            this.tokens.questionDot,
813
            this.tokens.openingSquare,
814
            ...this.indexes ?? [],
498✔
815
            this.tokens.closingSquare
816
        );
817
    }
818

819
    public readonly kind = AstNodeKind.IndexedGetExpression;
166✔
820

821
    public readonly obj: Expression;
822
    public readonly indexes: Expression[];
823

824
    readonly tokens: {
825
        /**
826
         * Can either be `[` or `?[`. If `?.[` is used, this will be `[` and `optionalChainingToken` will be `?.` - defaults to '[' in transpile
827
         */
828
        readonly openingSquare?: Token;
829
        readonly closingSquare?: Token;
830
        readonly questionDot?: Token; //  ? or ?.
831
    };
832

833
    public readonly location: Location | undefined;
834

835
    transpile(state: BrsTranspileState) {
836
        const result = [];
67✔
837
        result.push(
67✔
838
            ...this.obj.transpile(state),
839
            this.tokens.questionDot ? state.transpileToken(this.tokens.questionDot) : '',
67✔
840
            state.transpileToken(this.tokens.openingSquare, '[')
841
        );
842
        for (let i = 0; i < this.indexes.length; i++) {
67✔
843
            //add comma between indexes
844
            if (i > 0) {
75✔
845
                result.push(', ');
8✔
846
            }
847
            let index = this.indexes[i];
75✔
848
            result.push(
75✔
849
                ...(index?.transpile(state) ?? [])
450!
850
            );
851
        }
852
        result.push(
67✔
853
            state.transpileToken(this.tokens.closingSquare, ']')
854
        );
855
        return result;
67✔
856
    }
857

858
    walk(visitor: WalkVisitor, options: WalkOptions) {
859
        if (options.walkMode & InternalWalkMode.walkExpressions) {
583!
860
            walk(this, 'obj', visitor, options);
583✔
861
            walkArray(this.indexes, visitor, options, this);
583✔
862
        }
863
    }
864

865
    getType(options: GetTypeOptions): BscType {
866
        const objType = this.obj.getType(options);
202✔
867
        if (isArrayType(objType)) {
202✔
868
            // This is used on an array. What is the default type of that array?
869
            return objType.defaultType;
10✔
870
        }
871
        return super.getType(options);
192✔
872
    }
873

874
    get leadingTrivia(): Token[] {
875
        return this.obj.leadingTrivia;
1,066✔
876
    }
877

878
    public clone() {
879
        return this.finalizeClone(
6✔
880
            new IndexedGetExpression({
881
                obj: this.obj?.clone(),
18✔
882
                questionDot: util.cloneToken(this.tokens.questionDot),
883
                openingSquare: util.cloneToken(this.tokens.openingSquare),
884
                indexes: this.indexes?.map(x => x?.clone()),
7✔
885
                closingSquare: util.cloneToken(this.tokens.closingSquare)
886
            }),
887
            ['obj', 'indexes']
888
        );
889
    }
890
}
891

892
export class GroupingExpression extends Expression {
1✔
893
    constructor(options: {
894
        leftParen?: Token;
895
        rightParen?: Token;
896
        expression: Expression;
897
    }) {
898
        super();
60✔
899
        this.tokens = {
60✔
900
            rightParen: options.rightParen,
901
            leftParen: options.leftParen
902
        };
903
        this.expression = options.expression;
60✔
904
        this.location = util.createBoundingLocation(this.tokens.leftParen, this.expression, this.tokens.rightParen);
60✔
905
    }
906

907
    public readonly tokens: {
908
        readonly leftParen?: Token;
909
        readonly rightParen?: Token;
910
    };
911
    public readonly expression: Expression;
912

913
    public readonly kind = AstNodeKind.GroupingExpression;
60✔
914

915
    public readonly location: Location | undefined;
916

917
    transpile(state: BrsTranspileState) {
918
        if (isTypecastExpression(this.expression)) {
13✔
919
            return this.expression.transpile(state);
7✔
920
        }
921
        return [
6✔
922
            state.transpileToken(this.tokens.leftParen, '('),
923
            ...this.expression.transpile(state),
924
            state.transpileToken(this.tokens.rightParen, ')')
925
        ];
926
    }
927

928
    walk(visitor: WalkVisitor, options: WalkOptions) {
929
        if (options.walkMode & InternalWalkMode.walkExpressions) {
232!
930
            walk(this, 'expression', visitor, options);
232✔
931
        }
932
    }
933

934
    getType(options: GetTypeOptions) {
935
        return this.expression.getType(options);
83✔
936
    }
937

938
    get leadingTrivia(): Token[] {
939
        return this.tokens.leftParen?.leadingTrivia;
355!
940
    }
941

942
    public clone() {
943
        return this.finalizeClone(
2✔
944
            new GroupingExpression({
945
                leftParen: util.cloneToken(this.tokens.leftParen),
946
                expression: this.expression?.clone(),
6✔
947
                rightParen: util.cloneToken(this.tokens.rightParen)
948
            }),
949
            ['expression']
950
        );
951
    }
952
}
953

954
export class LiteralExpression extends Expression {
1✔
955
    constructor(options: {
956
        value: Token;
957
    }) {
958
        super();
8,178✔
959
        this.tokens = {
8,178✔
960
            value: options.value
961
        };
962
    }
963

964
    public readonly tokens: {
965
        readonly value: Token;
966
    };
967

968
    public readonly kind = AstNodeKind.LiteralExpression;
8,178✔
969

970
    public get location() {
971
        return this.tokens.value.location;
23,282✔
972
    }
973

974
    public getType(options?: GetTypeOptions) {
975
        return util.tokenToBscType(this.tokens.value);
3,611✔
976
    }
977

978
    transpile(state: BrsTranspileState) {
979
        let text: string;
980
        if (this.tokens.value.kind === TokenKind.TemplateStringQuasi) {
4,926✔
981
            //wrap quasis with quotes (and escape inner quotemarks)
982
            text = `"${this.tokens.value.text.replace(/"/g, '""')}"`;
35✔
983

984
        } else if (this.tokens.value.kind === TokenKind.StringLiteral) {
4,891✔
985
            text = this.tokens.value.text;
3,268✔
986
            //add trailing quotemark if it's missing. We will have already generated a diagnostic for this.
987
            if (text.endsWith('"') === false) {
3,268✔
988
                text += '"';
1✔
989
            }
990
        } else {
991
            text = this.tokens.value.text;
1,623✔
992
        }
993

994
        return [
4,926✔
995
            state.transpileToken({ ...this.tokens.value, text: text })
996
        ];
997
    }
998

999
    walk(visitor: WalkVisitor, options: WalkOptions) {
1000
        //nothing to walk
1001
    }
1002

1003
    get leadingTrivia(): Token[] {
1004
        return this.tokens.value.leadingTrivia;
12,880✔
1005
    }
1006

1007
    public clone() {
1008
        return this.finalizeClone(
109✔
1009
            new LiteralExpression({
1010
                value: util.cloneToken(this.tokens.value)
1011
            })
1012
        );
1013
    }
1014
}
1015

1016
/**
1017
 * The print statement can have a mix of expressions and separators. These separators represent actual output to the screen,
1018
 * so this AstNode represents those separators (comma, semicolon, and whitespace)
1019
 */
1020
export class PrintSeparatorExpression extends Expression {
1✔
1021
    constructor(options: {
1022
        separator: PrintSeparatorToken;
1023
    }) {
1024
        super();
42✔
1025
        this.tokens = {
42✔
1026
            separator: options.separator
1027
        };
1028
        this.location = this.tokens.separator.location;
42✔
1029
    }
1030

1031
    public readonly tokens: {
1032
        readonly separator: PrintSeparatorToken;
1033
    };
1034

1035
    public readonly kind = AstNodeKind.PrintSeparatorExpression;
42✔
1036

1037
    public location: Location;
1038

1039
    transpile(state: BrsTranspileState) {
1040
        return [
26✔
1041
            ...this.tokens.separator.leadingWhitespace ?? [],
78!
1042
            ...state.transpileToken(this.tokens.separator)
1043
        ];
1044
    }
1045

1046
    walk(visitor: WalkVisitor, options: WalkOptions) {
1047
        //nothing to walk
1048
    }
1049

1050
    get leadingTrivia(): Token[] {
1051
        return this.tokens.separator.leadingTrivia;
166✔
1052
    }
1053

1054
    public clone() {
NEW
1055
        return new PrintSeparatorExpression({
×
1056
            separator: util.cloneToken(this.tokens?.separator)
×
1057
        });
1058
    }
1059
}
1060

1061

1062
/**
1063
 * This is a special expression only used within template strings. It exists so we can prevent producing lots of empty strings
1064
 * during template string transpile by identifying these expressions explicitly and skipping the bslib_toString around them
1065
 */
1066
export class EscapedCharCodeLiteralExpression extends Expression {
1✔
1067
    constructor(options: {
1068
        value: Token & { charCode: number };
1069
    }) {
1070
        super();
37✔
1071
        this.tokens = { value: options.value };
37✔
1072
        this.location = util.cloneLocation(this.tokens.value.location);
37✔
1073
    }
1074

1075
    public readonly kind = AstNodeKind.EscapedCharCodeLiteralExpression;
37✔
1076

1077
    public readonly tokens: {
1078
        readonly value: Token & { charCode: number };
1079
    };
1080

1081
    public readonly location: Location;
1082

1083
    transpile(state: BrsTranspileState) {
1084
        return [
15✔
1085
            state.sourceNode(this, `chr(${this.tokens.value.charCode})`)
1086
        ];
1087
    }
1088

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

1093
    public clone() {
1094
        return this.finalizeClone(
3✔
1095
            new EscapedCharCodeLiteralExpression({
1096
                value: util.cloneToken(this.tokens.value)
1097
            })
1098
        );
1099
    }
1100
}
1101

1102
export class ArrayLiteralExpression extends Expression {
1✔
1103
    constructor(options: {
1104
        elements: Array<Expression>;
1105
        open?: Token;
1106
        close?: Token;
1107
    }) {
1108
        super();
171✔
1109
        this.tokens = {
171✔
1110
            open: options.open,
1111
            close: options.close
1112
        };
1113
        this.elements = options.elements;
171✔
1114
        this.location = util.createBoundingLocation(this.tokens.open, ...this.elements ?? [], this.tokens.close);
171✔
1115
    }
1116

1117
    public readonly elements: Array<Expression>;
1118

1119
    public readonly tokens: {
1120
        readonly open?: Token;
1121
        readonly close?: Token;
1122
    };
1123

1124
    public readonly kind = AstNodeKind.ArrayLiteralExpression;
171✔
1125

1126
    public readonly location: Location | undefined;
1127

1128
    transpile(state: BrsTranspileState) {
1129
        let result: TranspileResult = [];
62✔
1130
        result.push(
62✔
1131
            state.transpileToken(this.tokens.open, '[')
1132
        );
1133
        let hasChildren = this.elements.length > 0;
62✔
1134
        state.blockDepth++;
62✔
1135

1136
        for (let i = 0; i < this.elements.length; i++) {
62✔
1137
            let previousElement = this.elements[i - 1];
85✔
1138
            let element = this.elements[i];
85✔
1139

1140
            if (util.isLeadingCommentOnSameLine(previousElement ?? this.tokens.open, element)) {
85✔
1141
                result.push(' ');
3✔
1142
            } else {
1143
                result.push(
82✔
1144
                    '\n',
1145
                    state.indent()
1146
                );
1147
            }
1148
            result.push(
85✔
1149
                ...element.transpile(state)
1150
            );
1151
        }
1152
        state.blockDepth--;
62✔
1153
        //add a newline between open and close if there are elements
1154
        const lastLocatable = this.elements[this.elements.length - 1] ?? this.tokens.open;
62✔
1155
        result.push(...state.transpileEndBlockToken(lastLocatable, this.tokens.close, ']', hasChildren));
62✔
1156

1157
        return result;
62✔
1158
    }
1159

1160
    walk(visitor: WalkVisitor, options: WalkOptions) {
1161
        if (options.walkMode & InternalWalkMode.walkExpressions) {
895!
1162
            walkArray(this.elements, visitor, options, this);
895✔
1163
        }
1164
    }
1165

1166
    getType(options: GetTypeOptions): BscType {
1167
        const innerTypes = this.elements.map(expr => expr.getType(options));
271✔
1168
        return new ArrayType(...innerTypes);
193✔
1169
    }
1170
    get leadingTrivia(): Token[] {
1171
        return this.tokens.open?.leadingTrivia;
587!
1172
    }
1173

1174
    get endTrivia(): Token[] {
1175
        return this.tokens.close?.leadingTrivia;
2!
1176
    }
1177

1178
    public clone() {
1179
        return this.finalizeClone(
4✔
1180
            new ArrayLiteralExpression({
1181
                elements: this.elements?.map(e => e?.clone()),
6✔
1182
                open: util.cloneToken(this.tokens.open),
1183
                close: util.cloneToken(this.tokens.close)
1184
            }),
1185
            ['elements']
1186
        );
1187
    }
1188
}
1189

1190
export class AAMemberExpression extends Expression {
1✔
1191
    constructor(options: {
1192
        key: Token;
1193
        colon?: Token;
1194
        /** The expression evaluated to determine the member's initial value. */
1195
        value: Expression;
1196
        comma?: Token;
1197
    }) {
1198
        super();
305✔
1199
        this.tokens = {
305✔
1200
            key: options.key,
1201
            colon: options.colon,
1202
            comma: options.comma
1203
        };
1204
        this.value = options.value;
305✔
1205
        this.location = util.createBoundingLocation(this.tokens.key, this.tokens.colon, this.value);
305✔
1206
    }
1207

1208
    public readonly kind = AstNodeKind.AAMemberExpression;
305✔
1209

1210
    public readonly location: Location | undefined;
1211

1212
    public readonly tokens: {
1213
        readonly key: Token;
1214
        readonly colon?: Token;
1215
        readonly comma?: Token;
1216
    };
1217

1218
    /** The expression evaluated to determine the member's initial value. */
1219
    public readonly value: Expression;
1220

1221
    transpile(state: BrsTranspileState) {
1222
        //TODO move the logic from AALiteralExpression loop into this function
1223
        return [];
×
1224
    }
1225

1226
    walk(visitor: WalkVisitor, options: WalkOptions) {
1227
        walk(this, 'value', visitor, options);
1,142✔
1228
    }
1229

1230
    getType(options: GetTypeOptions): BscType {
1231
        return this.value.getType(options);
230✔
1232
    }
1233

1234
    get leadingTrivia(): Token[] {
1235
        return this.tokens.key.leadingTrivia;
823✔
1236
    }
1237

1238
    public clone() {
1239
        return this.finalizeClone(
4✔
1240
            new AAMemberExpression({
1241
                key: util.cloneToken(this.tokens.key),
1242
                colon: util.cloneToken(this.tokens.colon),
1243
                value: this.value?.clone()
12✔
1244
            }),
1245
            ['value']
1246
        );
1247
    }
1248
}
1249

1250
export class AALiteralExpression extends Expression {
1✔
1251
    constructor(options: {
1252
        elements: Array<AAMemberExpression>;
1253
        open?: Token;
1254
        close?: Token;
1255
    }) {
1256
        super();
300✔
1257
        this.tokens = {
300✔
1258
            open: options.open,
1259
            close: options.close
1260
        };
1261
        this.elements = options.elements;
300✔
1262
        this.location = util.createBoundingLocation(this.tokens.open, ...this.elements ?? [], this.tokens.close);
300✔
1263
    }
1264

1265
    public readonly elements: Array<AAMemberExpression>;
1266
    public readonly tokens: {
1267
        readonly open?: Token;
1268
        readonly close?: Token;
1269
    };
1270

1271
    public readonly kind = AstNodeKind.AALiteralExpression;
300✔
1272

1273
    public readonly location: Location | undefined;
1274

1275
    transpile(state: BrsTranspileState) {
1276
        let result: TranspileResult = [];
67✔
1277
        //open curly
1278
        result.push(
67✔
1279
            state.transpileToken(this.tokens.open, '{')
1280
        );
1281
        let hasChildren = this.elements.length > 0;
67✔
1282
        //add newline if the object has children and the first child isn't a comment starting on the same line as opening curly
1283
        if (hasChildren && !util.isLeadingCommentOnSameLine(this.tokens.open, this.elements[0])) {
67✔
1284
            result.push('\n');
24✔
1285
        }
1286
        state.blockDepth++;
67✔
1287
        for (let i = 0; i < this.elements.length; i++) {
67✔
1288
            let element = this.elements[i];
36✔
1289
            let previousElement = this.elements[i - 1];
36✔
1290
            let nextElement = this.elements[i + 1];
36✔
1291

1292
            //don't indent if comment is same-line
1293
            if (util.isLeadingCommentOnSameLine(this.tokens.open, element) ||
36✔
1294
                util.isLeadingCommentOnSameLine(previousElement, element)) {
1295
                result.push(' ');
7✔
1296
            } else {
1297
                //indent line
1298
                result.push(state.indent());
29✔
1299
            }
1300

1301
            //key
1302
            result.push(
36✔
1303
                state.transpileToken(element.tokens.key)
1304
            );
1305
            //colon
1306
            result.push(
36✔
1307
                state.transpileToken(element.tokens.colon, ':'),
1308
                ' '
1309
            );
1310
            //value
1311
            result.push(...element.value.transpile(state));
36✔
1312

1313
            //if next element is a same-line comment, skip the newline
1314
            if (nextElement && !util.isLeadingCommentOnSameLine(element, nextElement)) {
36✔
1315
                //add a newline between statements
1316
                result.push('\n');
5✔
1317
            }
1318
        }
1319
        state.blockDepth--;
67✔
1320

1321
        const lastElement = this.elements[this.elements.length - 1] ?? this.tokens.open;
67✔
1322
        result.push(...state.transpileEndBlockToken(lastElement, this.tokens.close, '}', hasChildren));
67✔
1323

1324
        return result;
67✔
1325
    }
1326

1327
    walk(visitor: WalkVisitor, options: WalkOptions) {
1328
        if (options.walkMode & InternalWalkMode.walkExpressions) {
1,227!
1329
            walkArray(this.elements, visitor, options, this);
1,227✔
1330
        }
1331
    }
1332

1333
    getType(options: GetTypeOptions): BscType {
1334
        const resultType = new AssociativeArrayType();
233✔
1335
        resultType.addBuiltInInterfaces();
233✔
1336
        for (const element of this.elements) {
233✔
1337
            if (isAAMemberExpression(element)) {
230!
1338
                let memberName = element.tokens?.key?.text ?? '';
230!
1339
                if (element.tokens.key.kind === TokenKind.StringLiteral) {
230✔
1340
                    memberName = memberName.replace(/"/g, ''); // remove quotes if it was a stringLiteral
6✔
1341
                }
1342
                if (memberName) {
230!
1343
                    resultType.addMember(memberName, { definingNode: element }, element.getType(options), SymbolTypeFlag.runtime);
230✔
1344
                }
1345
            }
1346
        }
1347
        return resultType;
233✔
1348
    }
1349

1350
    public get leadingTrivia(): Token[] {
1351
        return this.tokens.open?.leadingTrivia;
833!
1352
    }
1353

1354
    public get endTrivia(): Token[] {
1355
        return this.tokens.close?.leadingTrivia;
1!
1356
    }
1357

1358
    public clone() {
1359
        return this.finalizeClone(
6✔
1360
            new AALiteralExpression({
1361
                elements: this.elements?.map(e => e?.clone()),
5✔
1362
                open: util.cloneToken(this.tokens.open),
1363
                close: util.cloneToken(this.tokens.close)
1364
            }),
1365
            ['elements']
1366
        );
1367
    }
1368
}
1369

1370
export class UnaryExpression extends Expression {
1✔
1371
    constructor(options: {
1372
        operator: Token;
1373
        right: Expression;
1374
    }) {
1375
        super();
66✔
1376
        this.tokens = {
66✔
1377
            operator: options.operator
1378
        };
1379
        this.right = options.right;
66✔
1380
        this.location = util.createBoundingLocation(this.tokens.operator, this.right);
66✔
1381
    }
1382

1383
    public readonly kind = AstNodeKind.UnaryExpression;
66✔
1384

1385
    public readonly location: Location | undefined;
1386

1387
    public readonly tokens: {
1388
        readonly operator: Token;
1389
    };
1390
    public readonly right: Expression;
1391

1392
    transpile(state: BrsTranspileState) {
1393
        let separatingWhitespace: string | undefined;
1394
        if (isVariableExpression(this.right)) {
12✔
1395
            separatingWhitespace = this.right.tokens.name.leadingWhitespace;
6✔
1396
        } else if (isLiteralExpression(this.right)) {
6✔
1397
            separatingWhitespace = this.right.tokens.value.leadingWhitespace;
2✔
1398
        } else {
1399
            separatingWhitespace = ' ';
4✔
1400
        }
1401

1402
        return [
12✔
1403
            state.transpileToken(this.tokens.operator),
1404
            separatingWhitespace,
1405
            ...this.right.transpile(state)
1406
        ];
1407
    }
1408

1409
    walk(visitor: WalkVisitor, options: WalkOptions) {
1410
        if (options.walkMode & InternalWalkMode.walkExpressions) {
247!
1411
            walk(this, 'right', visitor, options);
247✔
1412
        }
1413
    }
1414

1415
    getType(options: GetTypeOptions): BscType {
1416
        return util.unaryOperatorResultType(this.tokens.operator, this.right.getType(options));
48✔
1417
    }
1418

1419
    public get leadingTrivia(): Token[] {
1420
        return this.tokens.operator.leadingTrivia;
187✔
1421
    }
1422

1423
    public clone() {
1424
        return this.finalizeClone(
2✔
1425
            new UnaryExpression({
1426
                operator: util.cloneToken(this.tokens.operator),
1427
                right: this.right?.clone()
6✔
1428
            }),
1429
            ['right']
1430
        );
1431
    }
1432
}
1433

1434
export class VariableExpression extends Expression {
1✔
1435
    constructor(options: {
1436
        name: Identifier;
1437
    }) {
1438
        super();
11,785✔
1439
        this.tokens = {
11,785✔
1440
            name: options.name
1441
        };
1442
        this.location = util.cloneLocation(this.tokens.name?.location);
11,785!
1443
    }
1444

1445
    public readonly tokens: {
1446
        readonly name: Identifier;
1447
    };
1448

1449
    public readonly kind = AstNodeKind.VariableExpression;
11,785✔
1450

1451
    public readonly location: Location;
1452

1453
    public getName(parseMode?: ParseMode) {
1454
        return this.tokens.name.text;
25,678✔
1455
    }
1456

1457
    transpile(state: BrsTranspileState) {
1458
        let result: TranspileResult = [];
6,816✔
1459
        const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
6,816✔
1460
        //if the callee is the name of a known namespace function
1461
        if (namespace && util.isCalleeMemberOfNamespace(this.tokens.name.text, this, namespace)) {
6,816✔
1462
            result.push(
17✔
1463
                //transpile leading comments since the token isn't being transpiled directly
1464
                ...state.transpileLeadingCommentsForAstNode(this),
1465
                state.sourceNode(this, [
1466
                    namespace.getName(ParseMode.BrightScript),
1467
                    '_',
1468
                    this.getName(ParseMode.BrightScript)
1469
                ])
1470
            );
1471
            //transpile  normally
1472
        } else {
1473
            result.push(
6,799✔
1474
                state.transpileToken(this.tokens.name)
1475
            );
1476
        }
1477
        return result;
6,816✔
1478
    }
1479

1480
    walk(visitor: WalkVisitor, options: WalkOptions) {
1481
        //nothing to walk
1482
    }
1483

1484

1485
    getType(options: GetTypeOptions) {
1486
        let resultType: BscType = util.tokenToBscType(this.tokens.name);
25,531✔
1487
        const nameKey = this.getName();
25,531✔
1488
        if (!resultType) {
25,531✔
1489
            const symbolTable = this.getSymbolTable();
19,662✔
1490
            resultType = symbolTable?.getSymbolType(nameKey, { ...options, fullName: nameKey, tableProvider: () => this.getSymbolTable() });
41,142!
1491

1492
            if (util.isClassUsedAsFunction(resultType, this, options)) {
19,662✔
1493
                resultType = FunctionType.instance;
20✔
1494
            }
1495

1496
        }
1497
        options?.typeChain?.push(new TypeChainEntry({ name: nameKey, type: resultType, data: options.data, astNode: this }));
25,531!
1498
        return resultType;
25,531✔
1499
    }
1500

1501
    get leadingTrivia(): Token[] {
1502
        return this.tokens.name.leadingTrivia;
40,883✔
1503
    }
1504

1505
    public clone() {
1506
        return this.finalizeClone(
70✔
1507
            new VariableExpression({
1508
                name: util.cloneToken(this.tokens.name)
1509
            })
1510
        );
1511
    }
1512
}
1513

1514
export class SourceLiteralExpression extends Expression {
1✔
1515
    constructor(options: {
1516
        value: Token;
1517
    }) {
1518
        super();
37✔
1519
        this.tokens = {
37✔
1520
            value: options.value
1521
        };
1522
        this.location = util.cloneLocation(this.tokens.value?.location);
37!
1523
    }
1524

1525
    public readonly location: Location;
1526

1527
    public readonly kind = AstNodeKind.SourceLiteralExpression;
37✔
1528

1529
    public readonly tokens: {
1530
        readonly value: Token;
1531
    };
1532

1533
    /**
1534
     * Find the index of the function in its parent
1535
     */
1536
    private findFunctionIndex(parentFunction: FunctionExpression, func: FunctionExpression) {
1537
        let index = -1;
4✔
1538
        parentFunction.findChild((node) => {
4✔
1539
            if (isFunctionExpression(node)) {
12✔
1540
                index++;
4✔
1541
                if (node === func) {
4!
1542
                    return true;
4✔
1543
                }
1544
            }
1545
        }, {
1546
            walkMode: WalkMode.visitAllRecursive
1547
        });
1548
        return index;
4✔
1549
    }
1550

1551
    private getFunctionName(state: BrsTranspileState, parseMode: ParseMode) {
1552
        let func = this.findAncestor<FunctionExpression>(isFunctionExpression);
8✔
1553
        let nameParts = [] as TranspileResult;
8✔
1554
        let parentFunction: FunctionExpression;
1555
        while ((parentFunction = func.findAncestor<FunctionExpression>(isFunctionExpression))) {
8✔
1556
            let index = this.findFunctionIndex(parentFunction, func);
4✔
1557
            nameParts.unshift(`anon${index}`);
4✔
1558
            func = parentFunction;
4✔
1559
        }
1560
        //get the index of this function in its parent
1561
        if (isFunctionStatement(func.parent)) {
8!
1562
            nameParts.unshift(
8✔
1563
                func.parent.getName(parseMode)
1564
            );
1565
        }
1566
        return nameParts.join('$');
8✔
1567
    }
1568

1569
    /**
1570
     * Get the line number from our token or from the closest ancestor that has a range
1571
     */
1572
    private getClosestLineNumber() {
1573
        let node: AstNode = this;
7✔
1574
        while (node) {
7✔
1575
            if (node.location?.range) {
17✔
1576
                return node.location.range.start.line + 1;
5✔
1577
            }
1578
            node = node.parent;
12✔
1579
        }
1580
        return -1;
2✔
1581
    }
1582

1583
    transpile(state: BrsTranspileState) {
1584
        let text: string;
1585
        switch (this.tokens.value.kind) {
31✔
1586
            case TokenKind.SourceFilePathLiteral:
40!
1587
                const pathUrl = fileUrl(state.srcPath);
3✔
1588
                text = `"${pathUrl.substring(0, 4)}" + "${pathUrl.substring(4)}"`;
3✔
1589
                break;
3✔
1590
            case TokenKind.SourceLineNumLiteral:
1591
                //TODO find first parent that has range, or default to -1
1592
                text = `${this.getClosestLineNumber()}`;
4✔
1593
                break;
4✔
1594
            case TokenKind.FunctionNameLiteral:
1595
                text = `"${this.getFunctionName(state, ParseMode.BrightScript)}"`;
4✔
1596
                break;
4✔
1597
            case TokenKind.SourceFunctionNameLiteral:
1598
                text = `"${this.getFunctionName(state, ParseMode.BrighterScript)}"`;
4✔
1599
                break;
4✔
1600
            case TokenKind.SourceNamespaceNameLiteral:
1601
                let namespaceParts = this.getFunctionName(state, ParseMode.BrighterScript).split('.');
×
1602
                namespaceParts.pop(); // remove the function name
×
1603

1604
                text = `"${namespaceParts.join('.')}"`;
×
1605
                break;
×
1606
            case TokenKind.SourceNamespaceRootNameLiteral:
1607
                let namespaceRootParts = this.getFunctionName(state, ParseMode.BrighterScript).split('.');
×
1608
                namespaceRootParts.pop(); // remove the function name
×
1609

1610
                let rootNamespace = namespaceRootParts.shift() ?? '';
×
1611
                text = `"${rootNamespace}"`;
×
1612
                break;
×
1613
            case TokenKind.SourceLocationLiteral:
1614
                const locationUrl = fileUrl(state.srcPath);
3✔
1615
                //TODO find first parent that has range, or default to -1
1616
                text = `"${locationUrl.substring(0, 4)}" + "${locationUrl.substring(4)}:${this.getClosestLineNumber()}"`;
3✔
1617
                break;
3✔
1618
            case TokenKind.PkgPathLiteral:
1619
                text = `"${util.sanitizePkgPath(state.file.pkgPath)}"`;
2✔
1620
                break;
2✔
1621
            case TokenKind.PkgLocationLiteral:
1622
                text = `"${util.sanitizePkgPath(state.file.pkgPath)}:" + str(LINE_NUM)`;
2✔
1623
                break;
2✔
1624
            case TokenKind.LineNumLiteral:
1625
            default:
1626
                //use the original text (because it looks like a variable)
1627
                text = this.tokens.value.text;
9✔
1628
                break;
9✔
1629

1630
        }
1631
        return [
31✔
1632
            state.sourceNode(this, text)
1633
        ];
1634
    }
1635

1636
    walk(visitor: WalkVisitor, options: WalkOptions) {
1637
        //nothing to walk
1638
    }
1639

1640
    get leadingTrivia(): Token[] {
1641
        return this.tokens.value.leadingTrivia;
200✔
1642
    }
1643

1644
    public clone() {
1645
        return this.finalizeClone(
1✔
1646
            new SourceLiteralExpression({
1647
                value: util.cloneToken(this.tokens.value)
1648
            })
1649
        );
1650
    }
1651
}
1652

1653
/**
1654
 * This expression transpiles and acts exactly like a CallExpression,
1655
 * except we need to uniquely identify these statements so we can
1656
 * do more type checking.
1657
 */
1658
export class NewExpression extends Expression {
1✔
1659
    constructor(options: {
1660
        new?: Token;
1661
        call: CallExpression;
1662
    }) {
1663
        super();
140✔
1664
        this.tokens = {
140✔
1665
            new: options.new
1666
        };
1667
        this.call = options.call;
140✔
1668
        this.location = util.createBoundingLocation(this.tokens.new, this.call);
140✔
1669
    }
1670

1671
    public readonly kind = AstNodeKind.NewExpression;
140✔
1672

1673
    public readonly location: Location | undefined;
1674

1675
    public readonly tokens: {
1676
        readonly new?: Token;
1677
    };
1678
    public readonly call: CallExpression;
1679

1680
    /**
1681
     * The name of the class to initialize (with optional namespace prefixed)
1682
     */
1683
    public get className() {
1684
        //the parser guarantees the callee of a new statement's call object will be
1685
        //either a VariableExpression or a DottedGet
1686
        return this.call.callee as (VariableExpression | DottedGetExpression);
92✔
1687
    }
1688

1689
    public transpile(state: BrsTranspileState) {
1690
        const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
15✔
1691
        const cls = state.file.getClassFileLink(
15✔
1692
            this.className.getName(ParseMode.BrighterScript),
1693
            namespace?.getName(ParseMode.BrighterScript)
45✔
1694
        )?.item;
15✔
1695
        //new statements within a namespace block can omit the leading namespace if the class resides in that same namespace.
1696
        //So we need to figure out if this is a namespace-omitted class, or if this class exists without a namespace.
1697
        return this.call.transpile(state, cls?.getName(ParseMode.BrightScript));
15✔
1698
    }
1699

1700
    walk(visitor: WalkVisitor, options: WalkOptions) {
1701
        if (options.walkMode & InternalWalkMode.walkExpressions) {
876!
1702
            walk(this, 'call', visitor, options);
876✔
1703
        }
1704
    }
1705

1706
    getType(options: GetTypeOptions) {
1707
        const result = this.call.getType(options);
350✔
1708
        if (options.typeChain) {
350✔
1709
            // modify last typechain entry to show it is a new ...()
1710
            const lastEntry = options.typeChain[options.typeChain.length - 1];
3✔
1711
            if (lastEntry) {
3!
1712
                lastEntry.astNode = this;
3✔
1713
            }
1714
        }
1715
        return result;
350✔
1716
    }
1717

1718
    get leadingTrivia(): Token[] {
1719
        return this.tokens.new.leadingTrivia;
611✔
1720
    }
1721

1722
    public clone() {
1723
        return this.finalizeClone(
2✔
1724
            new NewExpression({
1725
                new: util.cloneToken(this.tokens.new),
1726
                call: this.call?.clone()
6✔
1727
            }),
1728
            ['call']
1729
        );
1730
    }
1731
}
1732

1733
export class CallfuncExpression extends Expression {
1✔
1734
    constructor(options: {
1735
        callee: Expression;
1736
        operator?: Token;
1737
        methodName: Identifier;
1738
        openingParen?: Token;
1739
        args?: Expression[];
1740
        closingParen?: Token;
1741
    }) {
1742
        super();
61✔
1743
        this.tokens = {
61✔
1744
            operator: options.operator,
1745
            methodName: options.methodName,
1746
            openingParen: options.openingParen,
1747
            closingParen: options.closingParen
1748
        };
1749
        this.callee = options.callee;
61✔
1750
        this.args = options.args ?? [];
61✔
1751

1752
        this.location = util.createBoundingLocation(
61✔
1753
            this.callee,
1754
            this.tokens.operator,
1755
            this.tokens.methodName,
1756
            this.tokens.openingParen,
1757
            ...this.args ?? [],
183!
1758
            this.tokens.closingParen
1759
        );
1760
    }
1761

1762
    public readonly callee: Expression;
1763
    public readonly args: Expression[];
1764

1765
    public readonly tokens: {
1766
        readonly operator: Token;
1767
        readonly methodName: Identifier;
1768
        readonly openingParen?: Token;
1769
        readonly closingParen?: Token;
1770
    };
1771

1772
    public readonly kind = AstNodeKind.CallfuncExpression;
61✔
1773

1774
    public readonly location: Location | undefined;
1775

1776
    public transpile(state: BrsTranspileState) {
1777
        let result = [] as TranspileResult;
9✔
1778
        result.push(
9✔
1779
            ...this.callee.transpile(state),
1780
            state.sourceNode(this.tokens.operator, '.callfunc'),
1781
            state.transpileToken(this.tokens.openingParen, '('),
1782
            //the name of the function
1783
            state.sourceNode(this.tokens.methodName, ['"', this.tokens.methodName.text, '"'])
1784
        );
1785
        if (this.args?.length > 0) {
9!
1786
            result.push(', ');
4✔
1787
            //transpile args
1788
            for (let i = 0; i < this.args.length; i++) {
4✔
1789
                //add comma between args
1790
                if (i > 0) {
7✔
1791
                    result.push(', ');
3✔
1792
                }
1793
                let arg = this.args[i];
7✔
1794
                result.push(...arg.transpile(state));
7✔
1795
            }
1796
        } else if (state.options.legacyCallfuncHandling) {
5✔
1797
            result.push(', ', 'invalid');
2✔
1798
        }
1799

1800
        result.push(
9✔
1801
            state.transpileToken(this.tokens.closingParen, ')')
1802
        );
1803
        return result;
9✔
1804
    }
1805

1806
    walk(visitor: WalkVisitor, options: WalkOptions) {
1807
        if (options.walkMode & InternalWalkMode.walkExpressions) {
322!
1808
            walk(this, 'callee', visitor, options);
322✔
1809
            walkArray(this.args, visitor, options, this);
322✔
1810
        }
1811
    }
1812

1813
    getType(options: GetTypeOptions) {
1814
        const result = util.getCallFuncType(this, this.tokens.methodName, options) ?? DynamicType.instance;
23✔
1815
        return result;
23✔
1816
    }
1817

1818
    get leadingTrivia(): Token[] {
1819
        return this.callee.leadingTrivia;
476✔
1820
    }
1821

1822
    public clone() {
1823
        return this.finalizeClone(
3✔
1824
            new CallfuncExpression({
1825
                callee: this.callee?.clone(),
9✔
1826
                operator: util.cloneToken(this.tokens.operator),
1827
                methodName: util.cloneToken(this.tokens.methodName),
1828
                openingParen: util.cloneToken(this.tokens.openingParen),
1829
                args: this.args?.map(e => e?.clone()),
2✔
1830
                closingParen: util.cloneToken(this.tokens.closingParen)
1831
            }),
1832
            ['callee', 'args']
1833
        );
1834
    }
1835
}
1836

1837
/**
1838
 * Since template strings can contain newlines, we need to concatenate multiple strings together with chr() calls.
1839
 * This is a single expression that represents the string contatenation of all parts of a single quasi.
1840
 */
1841
export class TemplateStringQuasiExpression extends Expression {
1✔
1842
    constructor(options: {
1843
        expressions: Array<LiteralExpression | EscapedCharCodeLiteralExpression>;
1844
    }) {
1845
        super();
114✔
1846
        this.expressions = options.expressions;
114✔
1847
        this.location = util.createBoundingLocation(
114✔
1848
            ...this.expressions ?? []
342✔
1849
        );
1850
    }
1851

1852
    public readonly expressions: Array<LiteralExpression | EscapedCharCodeLiteralExpression>;
1853
    public readonly kind = AstNodeKind.TemplateStringQuasiExpression;
114✔
1854

1855
    readonly location: Location | undefined;
1856

1857
    transpile(state: BrsTranspileState, skipEmptyStrings = true) {
41✔
1858
        let result = [] as TranspileResult;
49✔
1859
        let plus = '';
49✔
1860
        for (let expression of this.expressions) {
49✔
1861
            //skip empty strings
1862
            //TODO what does an empty string literal expression look like?
1863
            if (expression.tokens.value.text === '' && skipEmptyStrings === true) {
78✔
1864
                continue;
28✔
1865
            }
1866
            result.push(
50✔
1867
                plus,
1868
                ...expression.transpile(state)
1869
            );
1870
            plus = ' + ';
50✔
1871
        }
1872
        return result;
49✔
1873
    }
1874

1875
    walk(visitor: WalkVisitor, options: WalkOptions) {
1876
        if (options.walkMode & InternalWalkMode.walkExpressions) {
412!
1877
            walkArray(this.expressions, visitor, options, this);
412✔
1878
        }
1879
    }
1880

1881
    public clone() {
1882
        return this.finalizeClone(
15✔
1883
            new TemplateStringQuasiExpression({
1884
                expressions: this.expressions?.map(e => e?.clone())
20✔
1885
            }),
1886
            ['expressions']
1887
        );
1888
    }
1889
}
1890

1891
export class TemplateStringExpression extends Expression {
1✔
1892
    constructor(options: {
1893
        openingBacktick?: Token;
1894
        quasis: TemplateStringQuasiExpression[];
1895
        expressions: Expression[];
1896
        closingBacktick?: Token;
1897
    }) {
1898
        super();
53✔
1899
        this.tokens = {
53✔
1900
            openingBacktick: options.openingBacktick,
1901
            closingBacktick: options.closingBacktick
1902
        };
1903
        this.quasis = options.quasis;
53✔
1904
        this.expressions = options.expressions;
53✔
1905
        this.location = util.createBoundingLocation(
53✔
1906
            this.tokens.openingBacktick,
1907
            this.quasis?.[0],
159✔
1908
            this.quasis?.[this.quasis?.length - 1],
315!
1909
            this.tokens.closingBacktick
1910
        );
1911
    }
1912

1913
    public readonly kind = AstNodeKind.TemplateStringExpression;
53✔
1914

1915
    public readonly tokens: {
1916
        readonly openingBacktick?: Token;
1917
        readonly closingBacktick?: Token;
1918
    };
1919
    public readonly quasis: TemplateStringQuasiExpression[];
1920
    public readonly expressions: Expression[];
1921

1922
    public readonly location: Location | undefined;
1923

1924
    public getType(options: GetTypeOptions) {
1925
        return StringType.instance;
34✔
1926
    }
1927

1928
    transpile(state: BrsTranspileState) {
1929
        //if this is essentially just a normal brightscript string but with backticks, transpile it as a normal string without parens
1930
        if (this.expressions.length === 0 && this.quasis.length === 1 && this.quasis[0].expressions.length === 1) {
24✔
1931
            return this.quasis[0].transpile(state);
6✔
1932
        }
1933
        let result = ['('];
18✔
1934
        let plus = '';
18✔
1935
        //helper function to figure out when to include the plus
1936
        function add(...items) {
1937
            if (items.length > 0) {
52✔
1938
                result.push(
40✔
1939
                    plus,
1940
                    ...items
1941
                );
1942
            }
1943
            //set the plus after the first occurance of a nonzero length set of items
1944
            if (plus === '' && items.length > 0) {
52✔
1945
                plus = ' + ';
18✔
1946
            }
1947
        }
1948

1949
        for (let i = 0; i < this.quasis.length; i++) {
18✔
1950
            let quasi = this.quasis[i];
35✔
1951
            let expression = this.expressions[i];
35✔
1952

1953
            add(
35✔
1954
                ...quasi.transpile(state)
1955
            );
1956
            if (expression) {
35✔
1957
                //skip the toString wrapper around certain expressions
1958
                if (
17✔
1959
                    isEscapedCharCodeLiteralExpression(expression) ||
39✔
1960
                    (isLiteralExpression(expression) && isStringType(expression.getType()))
1961
                ) {
1962
                    add(
3✔
1963
                        ...expression.transpile(state)
1964
                    );
1965

1966
                    //wrap all other expressions with a bslib_toString call to prevent runtime type mismatch errors
1967
                } else {
1968
                    add(
14✔
1969
                        state.bslibPrefix + '_toString(',
1970
                        ...expression.transpile(state),
1971
                        ')'
1972
                    );
1973
                }
1974
            }
1975
        }
1976
        //the expression should be wrapped in parens so it can be used line a single expression at runtime
1977
        result.push(')');
18✔
1978

1979
        return result;
18✔
1980
    }
1981

1982
    walk(visitor: WalkVisitor, options: WalkOptions) {
1983
        if (options.walkMode & InternalWalkMode.walkExpressions) {
208!
1984
            //walk the quasis and expressions in left-to-right order
1985
            for (let i = 0; i < this.quasis?.length; i++) {
208!
1986
                walk(this.quasis, i, visitor, options, this);
348✔
1987

1988
                //this skips the final loop iteration since we'll always have one more quasi than expression
1989
                if (this.expressions[i]) {
348✔
1990
                    walk(this.expressions, i, visitor, options, this);
140✔
1991
                }
1992
            }
1993
        }
1994
    }
1995

1996
    public clone() {
1997
        return this.finalizeClone(
7✔
1998
            new TemplateStringExpression({
1999
                openingBacktick: util.cloneToken(this.tokens.openingBacktick),
2000
                quasis: this.quasis?.map(e => e?.clone()),
12✔
2001
                expressions: this.expressions?.map(e => e?.clone()),
6✔
2002
                closingBacktick: util.cloneToken(this.tokens.closingBacktick)
2003
            }),
2004
            ['quasis', 'expressions']
2005
        );
2006
    }
2007
}
2008

2009
export class TaggedTemplateStringExpression extends Expression {
1✔
2010
    constructor(options: {
2011
        tagName: Identifier;
2012
        openingBacktick?: Token;
2013
        quasis: TemplateStringQuasiExpression[];
2014
        expressions: Expression[];
2015
        closingBacktick?: Token;
2016
    }) {
2017
        super();
12✔
2018
        this.tokens = {
12✔
2019
            tagName: options.tagName,
2020
            openingBacktick: options.openingBacktick,
2021
            closingBacktick: options.closingBacktick
2022
        };
2023
        this.quasis = options.quasis;
12✔
2024
        this.expressions = options.expressions;
12✔
2025

2026
        this.location = util.createBoundingLocation(
12✔
2027
            this.tokens.tagName,
2028
            this.tokens.openingBacktick,
2029
            this.quasis?.[0],
36✔
2030
            this.quasis?.[this.quasis?.length - 1],
69!
2031
            this.tokens.closingBacktick
2032
        );
2033
    }
2034

2035
    public readonly kind = AstNodeKind.TaggedTemplateStringExpression;
12✔
2036

2037
    public readonly tokens: {
2038
        readonly tagName: Identifier;
2039
        readonly openingBacktick?: Token;
2040
        readonly closingBacktick?: Token;
2041
    };
2042

2043
    public readonly quasis: TemplateStringQuasiExpression[];
2044
    public readonly expressions: Expression[];
2045

2046
    public readonly location: Location | undefined;
2047

2048
    transpile(state: BrsTranspileState) {
2049
        let result = [] as TranspileResult;
3✔
2050
        result.push(
3✔
2051
            state.transpileToken(this.tokens.tagName),
2052
            '(['
2053
        );
2054

2055
        //add quasis as the first array
2056
        for (let i = 0; i < this.quasis.length; i++) {
3✔
2057
            let quasi = this.quasis[i];
8✔
2058
            //separate items with a comma
2059
            if (i > 0) {
8✔
2060
                result.push(
5✔
2061
                    ', '
2062
                );
2063
            }
2064
            result.push(
8✔
2065
                ...quasi.transpile(state, false)
2066
            );
2067
        }
2068
        result.push(
3✔
2069
            '], ['
2070
        );
2071

2072
        //add expressions as the second array
2073
        for (let i = 0; i < this.expressions.length; i++) {
3✔
2074
            let expression = this.expressions[i];
5✔
2075
            if (i > 0) {
5✔
2076
                result.push(
2✔
2077
                    ', '
2078
                );
2079
            }
2080
            result.push(
5✔
2081
                ...expression.transpile(state)
2082
            );
2083
        }
2084
        result.push(
3✔
2085
            state.sourceNode(this.tokens.closingBacktick, '])')
2086
        );
2087
        return result;
3✔
2088
    }
2089

2090
    walk(visitor: WalkVisitor, options: WalkOptions) {
2091
        if (options.walkMode & InternalWalkMode.walkExpressions) {
28!
2092
            //walk the quasis and expressions in left-to-right order
2093
            for (let i = 0; i < this.quasis?.length; i++) {
28!
2094
                walk(this.quasis, i, visitor, options, this);
68✔
2095

2096
                //this skips the final loop iteration since we'll always have one more quasi than expression
2097
                if (this.expressions[i]) {
68✔
2098
                    walk(this.expressions, i, visitor, options, this);
40✔
2099
                }
2100
            }
2101
        }
2102
    }
2103

2104
    public clone() {
2105
        return this.finalizeClone(
3✔
2106
            new TaggedTemplateStringExpression({
2107
                tagName: util.cloneToken(this.tokens.tagName),
2108
                openingBacktick: util.cloneToken(this.tokens.openingBacktick),
2109
                quasis: this.quasis?.map(e => e?.clone()),
5✔
2110
                expressions: this.expressions?.map(e => e?.clone()),
3✔
2111
                closingBacktick: util.cloneToken(this.tokens.closingBacktick)
2112
            }),
2113
            ['quasis', 'expressions']
2114
        );
2115
    }
2116
}
2117

2118
export class AnnotationExpression extends Expression {
1✔
2119
    constructor(options: {
2120
        at?: Token;
2121
        name: Token;
2122
        call?: CallExpression;
2123
    }) {
2124
        super();
83✔
2125
        this.tokens = {
83✔
2126
            at: options.at,
2127
            name: options.name
2128
        };
2129
        this.call = options.call;
83✔
2130
        this.name = this.tokens.name.text;
83✔
2131
    }
2132

2133
    public readonly kind = AstNodeKind.AnnotationExpression;
83✔
2134

2135
    public readonly tokens: {
2136
        readonly at: Token;
2137
        readonly name: Token;
2138
    };
2139

2140
    public get location(): Location | undefined {
2141
        return util.createBoundingLocation(
75✔
2142
            this.tokens.at,
2143
            this.tokens.name,
2144
            this.call
2145
        );
2146
    }
2147

2148
    public readonly name: string;
2149

2150
    public call: CallExpression;
2151

2152
    /**
2153
     * Convert annotation arguments to JavaScript types
2154
     * @param strict If false, keep Expression objects not corresponding to JS types
2155
     */
2156
    getArguments(strict = true): ExpressionValue[] {
10✔
2157
        if (!this.call) {
11✔
2158
            return [];
1✔
2159
        }
2160
        return this.call.args.map(e => expressionToValue(e, strict));
20✔
2161
    }
2162

2163
    public get leadingTrivia(): Token[] {
2164
        return this.tokens.at?.leadingTrivia;
50!
2165
    }
2166

2167
    transpile(state: BrsTranspileState) {
2168
        //transpile only our leading comments
2169
        return state.transpileComments(this.leadingTrivia);
16✔
2170
    }
2171

2172
    walk(visitor: WalkVisitor, options: WalkOptions) {
2173
        //nothing to walk
2174
    }
2175
    getTypedef(state: BrsTranspileState) {
2176
        return [
9✔
2177
            '@',
2178
            this.name,
2179
            ...(this.call?.transpile(state) ?? [])
54✔
2180
        ];
2181
    }
2182

2183
    public clone() {
2184
        const clone = this.finalizeClone(
7✔
2185
            new AnnotationExpression({
2186
                at: util.cloneToken(this.tokens.at),
2187
                name: util.cloneToken(this.tokens.name)
2188
            })
2189
        );
2190
        return clone;
7✔
2191
    }
2192
}
2193

2194
export class TernaryExpression extends Expression {
1✔
2195
    constructor(options: {
2196
        test: Expression;
2197
        questionMark?: Token;
2198
        consequent?: Expression;
2199
        colon?: Token;
2200
        alternate?: Expression;
2201
    }) {
2202
        super();
100✔
2203
        this.tokens = {
100✔
2204
            questionMark: options.questionMark,
2205
            colon: options.colon
2206
        };
2207
        this.test = options.test;
100✔
2208
        this.consequent = options.consequent;
100✔
2209
        this.alternate = options.alternate;
100✔
2210
        this.location = util.createBoundingLocation(
100✔
2211
            this.test,
2212
            this.tokens.questionMark,
2213
            this.consequent,
2214
            this.tokens.colon,
2215
            this.alternate
2216
        );
2217
    }
2218

2219
    public readonly kind = AstNodeKind.TernaryExpression;
100✔
2220

2221
    public readonly location: Location | undefined;
2222

2223
    public readonly tokens: {
2224
        readonly questionMark?: Token;
2225
        readonly colon?: Token;
2226
    };
2227

2228
    public readonly test: Expression;
2229
    public readonly consequent?: Expression;
2230
    public readonly alternate?: Expression;
2231

2232
    transpile(state: BrsTranspileState) {
2233
        let result = [] as TranspileResult;
22✔
2234
        const file = state.file;
22✔
2235
        let consequentInfo = util.getExpressionInfo(this.consequent!, file);
22✔
2236
        let alternateInfo = util.getExpressionInfo(this.alternate!, file);
22✔
2237

2238
        //get all unique variable names used in the consequent and alternate, and sort them alphabetically so the output is consistent
2239
        let allUniqueVarNames = [...new Set([...consequentInfo.uniqueVarNames, ...alternateInfo.uniqueVarNames])].sort();
22✔
2240
        //discard names of global functions that cannot be passed by reference
2241
        allUniqueVarNames = allUniqueVarNames.filter(name => {
22✔
2242
            return !nonReferenceableFunctions.includes(name.toLowerCase());
23✔
2243
        });
2244

2245
        let mutatingExpressions = [
22✔
2246
            ...consequentInfo.expressions,
2247
            ...alternateInfo.expressions
2248
        ].filter(e => e instanceof CallExpression || e instanceof CallfuncExpression || e instanceof DottedGetExpression);
136✔
2249

2250
        if (mutatingExpressions.length > 0) {
22✔
2251
            result.push(
10✔
2252
                state.sourceNode(
2253
                    this.tokens.questionMark,
2254
                    //write all the scope variables as parameters.
2255
                    //TODO handle when there are more than 31 parameters
2256
                    `(function(${['__bsCondition', ...allUniqueVarNames].join(', ')})`
2257
                ),
2258
                state.newline,
2259
                //double indent so our `end function` line is still indented one at the end
2260
                state.indent(2),
2261
                state.sourceNode(this.test, `if __bsCondition then`),
2262
                state.newline,
2263
                state.indent(1),
2264
                state.sourceNode(this.consequent ?? this.tokens.questionMark, 'return '),
30!
2265
                ...this.consequent?.transpile(state) ?? [state.sourceNode(this.tokens.questionMark, 'invalid')],
60!
2266
                state.newline,
2267
                state.indent(-1),
2268
                state.sourceNode(this.consequent ?? this.tokens.questionMark, 'else'),
30!
2269
                state.newline,
2270
                state.indent(1),
2271
                state.sourceNode(this.consequent ?? this.tokens.questionMark, 'return '),
30!
2272
                ...this.alternate?.transpile(state) ?? [state.sourceNode(this.consequent ?? this.tokens.questionMark, 'invalid')],
60!
2273
                state.newline,
2274
                state.indent(-1),
2275
                state.sourceNode(this.tokens.questionMark, 'end if'),
2276
                state.newline,
2277
                state.indent(-1),
2278
                state.sourceNode(this.tokens.questionMark, 'end function)('),
2279
                ...this.test.transpile(state),
2280
                state.sourceNode(this.tokens.questionMark, `${['', ...allUniqueVarNames].join(', ')})`)
2281
            );
2282
            state.blockDepth--;
10✔
2283
        } else {
2284
            result.push(
12✔
2285
                state.sourceNode(this.test, state.bslibPrefix + `_ternary(`),
2286
                ...this.test.transpile(state),
2287
                state.sourceNode(this.test, `, `),
2288
                ...this.consequent?.transpile(state) ?? ['invalid'],
72✔
2289
                `, `,
2290
                ...this.alternate?.transpile(state) ?? ['invalid'],
72✔
2291
                `)`
2292
            );
2293
        }
2294
        return result;
22✔
2295
    }
2296

2297
    public walk(visitor: WalkVisitor, options: WalkOptions) {
2298
        if (options.walkMode & InternalWalkMode.walkExpressions) {
394!
2299
            walk(this, 'test', visitor, options);
394✔
2300
            walk(this, 'consequent', visitor, options);
394✔
2301
            walk(this, 'alternate', visitor, options);
394✔
2302
        }
2303
    }
2304

2305
    get leadingTrivia(): Token[] {
2306
        return this.test.leadingTrivia;
258✔
2307
    }
2308

2309
    public clone() {
2310
        return this.finalizeClone(
2✔
2311
            new TernaryExpression({
2312
                test: this.test?.clone(),
6✔
2313
                questionMark: util.cloneToken(this.tokens.questionMark),
2314
                consequent: this.consequent?.clone(),
6✔
2315
                colon: util.cloneToken(this.tokens.colon),
2316
                alternate: this.alternate?.clone()
6✔
2317
            }),
2318
            ['test', 'consequent', 'alternate']
2319
        );
2320
    }
2321
}
2322

2323
export class NullCoalescingExpression extends Expression {
1✔
2324
    constructor(options: {
2325
        consequent: Expression;
2326
        questionQuestion?: Token;
2327
        alternate: Expression;
2328
    }) {
2329
        super();
37✔
2330
        this.tokens = {
37✔
2331
            questionQuestion: options.questionQuestion
2332
        };
2333
        this.consequent = options.consequent;
37✔
2334
        this.alternate = options.alternate;
37✔
2335
        this.location = util.createBoundingLocation(
37✔
2336
            this.consequent,
2337
            this.tokens.questionQuestion,
2338
            this.alternate
2339
        );
2340
    }
2341

2342
    public readonly kind = AstNodeKind.NullCoalescingExpression;
37✔
2343

2344
    public readonly location: Location | undefined;
2345

2346
    public readonly tokens: {
2347
        readonly questionQuestion?: Token;
2348
    };
2349

2350
    public readonly consequent: Expression;
2351
    public readonly alternate: Expression;
2352

2353
    transpile(state: BrsTranspileState) {
2354
        let result = [] as TranspileResult;
11✔
2355
        let consequentInfo = util.getExpressionInfo(this.consequent, state.file);
11✔
2356
        let alternateInfo = util.getExpressionInfo(this.alternate, state.file);
11✔
2357

2358
        //get all unique variable names used in the consequent and alternate, and sort them alphabetically so the output is consistent
2359
        let allUniqueVarNames = [...new Set([...consequentInfo.uniqueVarNames, ...alternateInfo.uniqueVarNames])].sort();
11✔
2360
        //discard names of global functions that cannot be passed by reference
2361
        allUniqueVarNames = allUniqueVarNames.filter(name => {
11✔
2362
            return !nonReferenceableFunctions.includes(name.toLowerCase());
22✔
2363
        });
2364

2365
        let hasMutatingExpression = [
11✔
2366
            ...consequentInfo.expressions,
2367
            ...alternateInfo.expressions
2368
        ].find(e => isCallExpression(e) || isCallfuncExpression(e) || isDottedGetExpression(e));
30✔
2369

2370
        if (hasMutatingExpression) {
11✔
2371
            result.push(
7✔
2372
                `(function(`,
2373
                //write all the scope variables as parameters.
2374
                //TODO handle when there are more than 31 parameters
2375
                allUniqueVarNames.join(', '),
2376
                ')',
2377
                state.newline,
2378
                //double indent so our `end function` line is still indented one at the end
2379
                state.indent(2),
2380
                //evaluate the consequent exactly once, and then use it in the following condition
2381
                `__bsConsequent = `,
2382
                ...this.consequent.transpile(state),
2383
                state.newline,
2384
                state.indent(),
2385
                `if __bsConsequent <> invalid then`,
2386
                state.newline,
2387
                state.indent(1),
2388
                'return __bsConsequent',
2389
                state.newline,
2390
                state.indent(-1),
2391
                'else',
2392
                state.newline,
2393
                state.indent(1),
2394
                'return ',
2395
                ...this.alternate.transpile(state),
2396
                state.newline,
2397
                state.indent(-1),
2398
                'end if',
2399
                state.newline,
2400
                state.indent(-1),
2401
                'end function)(',
2402
                allUniqueVarNames.join(', '),
2403
                ')'
2404
            );
2405
            state.blockDepth--;
7✔
2406
        } else {
2407
            result.push(
4✔
2408
                state.bslibPrefix + `_coalesce(`,
2409
                ...this.consequent.transpile(state),
2410
                ', ',
2411
                ...this.alternate.transpile(state),
2412
                ')'
2413
            );
2414
        }
2415
        return result;
11✔
2416
    }
2417

2418
    public walk(visitor: WalkVisitor, options: WalkOptions) {
2419
        if (options.walkMode & InternalWalkMode.walkExpressions) {
99!
2420
            walk(this, 'consequent', visitor, options);
99✔
2421
            walk(this, 'alternate', visitor, options);
99✔
2422
        }
2423
    }
2424

2425
    get leadingTrivia(): Token[] {
2426
        return this.consequent.leadingTrivia;
58✔
2427
    }
2428

2429
    public clone() {
2430
        return this.finalizeClone(
2✔
2431
            new NullCoalescingExpression({
2432
                consequent: this.consequent?.clone(),
6✔
2433
                questionQuestion: util.cloneToken(this.tokens.questionQuestion),
2434
                alternate: this.alternate?.clone()
6✔
2435
            }),
2436
            ['consequent', 'alternate']
2437
        );
2438
    }
2439
}
2440

2441
export class RegexLiteralExpression extends Expression {
1✔
2442
    constructor(options: {
2443
        regexLiteral: Token;
2444
    }) {
2445
        super();
46✔
2446
        this.tokens = {
46✔
2447
            regexLiteral: options.regexLiteral
2448
        };
2449
    }
2450

2451
    public readonly kind = AstNodeKind.RegexLiteralExpression;
46✔
2452
    public readonly tokens: {
2453
        readonly regexLiteral: Token;
2454
    };
2455

2456
    public get location(): Location {
2457
        return this.tokens?.regexLiteral?.location;
150!
2458
    }
2459

2460
    public transpile(state: BrsTranspileState): TranspileResult {
2461
        let text = this.tokens.regexLiteral?.text ?? '';
42!
2462
        let flags = '';
42✔
2463
        //get any flags from the end
2464
        const flagMatch = /\/([a-z]+)$/i.exec(text);
42✔
2465
        if (flagMatch) {
42✔
2466
            text = text.substring(0, flagMatch.index + 1);
2✔
2467
            flags = flagMatch[1];
2✔
2468
        }
2469
        let pattern = text
42✔
2470
            //remove leading and trailing slashes
2471
            .substring(1, text.length - 1)
2472
            //escape quotemarks
2473
            .split('"').join('" + chr(34) + "');
2474

2475
        return [
42✔
2476
            state.sourceNode(this.tokens.regexLiteral, [
2477
                'CreateObject("roRegex", ',
2478
                `"${pattern}", `,
2479
                `"${flags}"`,
2480
                ')'
2481
            ])
2482
        ];
2483
    }
2484

2485
    walk(visitor: WalkVisitor, options: WalkOptions) {
2486
        //nothing to walk
2487
    }
2488

2489
    public clone() {
2490
        return this.finalizeClone(
1✔
2491
            new RegexLiteralExpression({
2492
                regexLiteral: util.cloneToken(this.tokens.regexLiteral)
2493
            })
2494
        );
2495
    }
2496

2497
    get leadingTrivia(): Token[] {
2498
        return this.tokens.regexLiteral?.leadingTrivia;
1,038!
2499
    }
2500
}
2501

2502
// eslint-disable-next-line @typescript-eslint/consistent-indexed-object-style
2503
type ExpressionValue = string | number | boolean | Expression | ExpressionValue[] | { [key: string]: ExpressionValue } | null;
2504

2505
function expressionToValue(expr: Expression, strict: boolean): ExpressionValue {
2506
    if (!expr) {
30!
2507
        return null;
×
2508
    }
2509
    if (isUnaryExpression(expr) && isLiteralNumber(expr.right)) {
30✔
2510
        return numberExpressionToValue(expr.right, expr.tokens.operator.text);
1✔
2511
    }
2512
    if (isLiteralString(expr)) {
29✔
2513
        //remove leading and trailing quotes
2514
        return expr.tokens.value.text.replace(/^"/, '').replace(/"$/, '');
5✔
2515
    }
2516
    if (isLiteralNumber(expr)) {
24✔
2517
        return numberExpressionToValue(expr);
11✔
2518
    }
2519

2520
    if (isLiteralBoolean(expr)) {
13✔
2521
        return expr.tokens.value.text.toLowerCase() === 'true';
3✔
2522
    }
2523
    if (isArrayLiteralExpression(expr)) {
10✔
2524
        return expr.elements
3✔
2525
            .map(e => expressionToValue(e, strict));
7✔
2526
    }
2527
    if (isAALiteralExpression(expr)) {
7✔
2528
        return expr.elements.reduce((acc, e) => {
3✔
2529
            acc[e.tokens.key.text] = expressionToValue(e.value, strict);
3✔
2530
            return acc;
3✔
2531
        }, {});
2532
    }
2533
    //for annotations, we only support serializing pure string values
2534
    if (isTemplateStringExpression(expr)) {
4✔
2535
        if (expr.quasis?.length === 1 && expr.expressions.length === 0) {
2!
2536
            return expr.quasis[0].expressions.map(x => x.tokens.value.text).join('');
10✔
2537
        }
2538
    }
2539
    return strict ? null : expr;
2✔
2540
}
2541

2542
function numberExpressionToValue(expr: LiteralExpression, operator = '') {
11✔
2543
    if (isIntegerType(expr.getType()) || isLongIntegerType(expr.getType())) {
12!
2544
        return parseInt(operator + expr.tokens.value.text);
12✔
2545
    } else {
NEW
2546
        return parseFloat(operator + expr.tokens.value.text);
×
2547
    }
2548
}
2549

2550
export class TypeExpression extends Expression implements TypedefProvider {
1✔
2551
    constructor(options: {
2552
        /**
2553
         * The standard AST expression that represents the type for this TypeExpression.
2554
         */
2555
        expression: Expression;
2556
    }) {
2557
        super();
1,655✔
2558
        this.expression = options.expression;
1,655✔
2559
        this.location = util.cloneLocation(this.expression?.location);
1,655!
2560
    }
2561

2562
    public readonly kind = AstNodeKind.TypeExpression;
1,655✔
2563

2564
    /**
2565
     * The standard AST expression that represents the type for this TypeExpression.
2566
     */
2567
    public readonly expression: Expression;
2568

2569
    public readonly location: Location;
2570

2571
    public transpile(state: BrsTranspileState): TranspileResult {
2572
        const exprType = this.getType({ flags: SymbolTypeFlag.typetime });
115✔
2573
        if (isNativeType(exprType)) {
115✔
2574
            return this.expression.transpile(state);
107✔
2575
        }
2576
        return [exprType.toTypeString()];
8✔
2577
    }
2578
    public walk(visitor: WalkVisitor, options: WalkOptions) {
2579
        if (options.walkMode & InternalWalkMode.walkExpressions) {
7,847✔
2580
            walk(this, 'expression', visitor, options);
7,719✔
2581
        }
2582
    }
2583

2584
    public getType(options: GetTypeOptions): BscType {
2585
        return this.expression.getType({ ...options, flags: SymbolTypeFlag.typetime });
7,555✔
2586
    }
2587

2588
    getTypedef(state: TranspileState): TranspileResult {
2589
        // TypeDefs should pass through any valid type names
2590
        return this.expression.transpile(state as BrsTranspileState);
33✔
2591
    }
2592

2593
    getName(parseMode = ParseMode.BrighterScript): string {
238✔
2594
        //TODO: this may not support Complex Types, eg. generics or Unions
2595
        return util.getAllDottedGetPartsAsString(this.expression, parseMode);
240✔
2596
    }
2597

2598
    getNameParts(): string[] {
2599
        //TODO: really, this code is only used to get Namespaces. It could be more clear.
2600
        return util.getAllDottedGetParts(this.expression).map(x => x.text);
20✔
2601
    }
2602

2603
    public clone() {
2604
        return this.finalizeClone(
18✔
2605
            new TypeExpression({
2606
                expression: this.expression?.clone()
54!
2607
            }),
2608
            ['expression']
2609
        );
2610
    }
2611
}
2612

2613
export class TypecastExpression extends Expression {
1✔
2614
    constructor(options: {
2615
        obj: Expression;
2616
        as?: Token;
2617
        typeExpression?: TypeExpression;
2618
    }) {
2619
        super();
81✔
2620
        this.tokens = {
81✔
2621
            as: options.as
2622
        };
2623
        this.obj = options.obj;
81✔
2624
        this.typeExpression = options.typeExpression;
81✔
2625
        this.location = util.createBoundingLocation(
81✔
2626
            this.obj,
2627
            this.tokens.as,
2628
            this.typeExpression
2629
        );
2630
    }
2631

2632
    public readonly kind = AstNodeKind.TypecastExpression;
81✔
2633

2634
    public readonly obj: Expression;
2635

2636
    public readonly tokens: {
2637
        readonly as?: Token;
2638
    };
2639

2640
    public typeExpression?: TypeExpression;
2641

2642
    public readonly location: Location;
2643

2644
    public transpile(state: BrsTranspileState): TranspileResult {
2645
        return this.obj.transpile(state);
13✔
2646
    }
2647
    public walk(visitor: WalkVisitor, options: WalkOptions) {
2648
        if (options.walkMode & InternalWalkMode.walkExpressions) {
417!
2649
            walk(this, 'obj', visitor, options);
417✔
2650
            walk(this, 'typeExpression', visitor, options);
417✔
2651
        }
2652
    }
2653

2654
    public getType(options: GetTypeOptions): BscType {
2655
        const result = this.typeExpression.getType(options);
107✔
2656
        if (options.typeChain) {
107✔
2657
            // modify last typechain entry to show it is a typecast
2658
            const lastEntry = options.typeChain[options.typeChain.length - 1];
25✔
2659
            if (lastEntry) {
25!
2660
                lastEntry.astNode = this;
25✔
2661
            }
2662
        }
2663
        return result;
107✔
2664
    }
2665

2666
    public clone() {
2667
        return this.finalizeClone(
3✔
2668
            new TypecastExpression({
2669
                obj: this.obj?.clone(),
9✔
2670
                as: util.cloneToken(this.tokens.as),
2671
                typeExpression: this.typeExpression?.clone()
9!
2672
            }),
2673
            ['obj', 'typeExpression']
2674
        );
2675
    }
2676
}
2677

2678
export class TypedArrayExpression extends Expression {
1✔
2679
    constructor(options: {
2680
        innerType: Expression;
2681
        leftBracket?: Token;
2682
        rightBracket?: Token;
2683
    }) {
2684
        super();
29✔
2685
        this.tokens = {
29✔
2686
            leftBracket: options.leftBracket,
2687
            rightBracket: options.rightBracket
2688
        };
2689
        this.innerType = options.innerType;
29✔
2690
        this.location = util.createBoundingLocation(
29✔
2691
            this.innerType,
2692
            this.tokens.leftBracket,
2693
            this.tokens.rightBracket
2694
        );
2695
    }
2696

2697
    public readonly tokens: {
2698
        readonly leftBracket?: Token;
2699
        readonly rightBracket?: Token;
2700
    };
2701

2702
    public readonly innerType: Expression;
2703

2704
    public readonly kind = AstNodeKind.TypedArrayExpression;
29✔
2705

2706
    public readonly location: Location;
2707

2708
    public transpile(state: BrsTranspileState): TranspileResult {
NEW
2709
        return [this.getType({ flags: SymbolTypeFlag.typetime }).toTypeString()];
×
2710
    }
2711

2712
    public walk(visitor: WalkVisitor, options: WalkOptions) {
2713
        if (options.walkMode & InternalWalkMode.walkExpressions) {
129!
2714
            walk(this, 'innerType', visitor, options);
129✔
2715
        }
2716
    }
2717

2718
    public getType(options: GetTypeOptions): BscType {
2719
        return new ArrayType(this.innerType.getType(options));
145✔
2720
    }
2721

2722
    public clone() {
NEW
2723
        return this.finalizeClone(
×
2724
            new TypedArrayExpression({
2725
                innerType: this.innerType?.clone(),
×
2726
                leftBracket: util.cloneToken(this.tokens.leftBracket),
2727
                rightBracket: util.cloneToken(this.tokens.rightBracket)
2728
            }),
2729
            ['innerType']
2730
        );
2731
    }
2732
}
2733

2734
/**
2735
 * A list of names of functions that are restricted from being stored to a
2736
 * variable, property, or passed as an argument. (i.e. `type` or `createobject`).
2737
 * Names are stored in lower case.
2738
 */
2739
const nonReferenceableFunctions = [
1✔
2740
    'createobject',
2741
    'type',
2742
    'getglobalaa',
2743
    'box',
2744
    'run',
2745
    'eval',
2746
    'getlastruncompileerror',
2747
    'getlastrunruntimeerror',
2748
    'tab',
2749
    'pos'
2750
];
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