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

rokucommunity / brighterscript / #14403

14 May 2025 05:45PM UTC coverage: 87.003% (-2.0%) from 89.017%
#14403

push

web-flow
Merge 343773173 into a194c3925

13887 of 16869 branches covered (82.32%)

Branch coverage included in aggregate %.

174 of 191 new or added lines in 11 files covered. (91.1%)

839 existing lines in 51 files now uncovered.

14750 of 16046 relevant lines covered (91.92%)

21898.44 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,475✔
47
        this.tokens = {
3,475✔
48
            operator: options.operator
49
        };
50
        this.left = options.left;
3,475✔
51
        this.right = options.right;
3,475✔
52
        this.location = util.createBoundingLocation(this.left, this.tokens.operator, this.right);
3,475✔
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,475✔
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,905!
77
            walk(this, 'left', visitor, options);
14,905✔
78
            walk(this, 'right', visitor, options);
14,905✔
79
        }
80
    }
81

82

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

101
    get leadingTrivia(): Token[] {
102
        return this.left.leadingTrivia;
1,983✔
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,670✔
134
        this.tokens = {
2,670✔
135
            openingParen: options.openingParen,
136
            closingParen: options.closingParen
137
        };
138
        this.callee = options.callee;
2,670✔
139
        this.args = options.args ?? [];
2,670✔
140
        this.location = util.createBoundingLocation(this.callee, this.tokens.openingParen, ...this.args ?? [], this.tokens.closingParen);
2,670!
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,670✔
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,910!
190
            walk(this, 'callee', visitor, options);
12,910✔
191
            walkArray(this.args, visitor, options, this);
12,910✔
192
        }
193
    }
194

195
    getType(options: GetTypeOptions) {
196
        const calleeType = this.callee.getType(options);
1,059✔
197
        if (options.ignoreCall) {
1,059!
UNCOV
198
            return calleeType;
×
199
        }
200
        if (isNewExpression(this.parent)) {
1,059✔
201
            return calleeType;
348✔
202
        }
203
        const specialCaseReturnType = util.getSpecialCaseCallExpressionReturnType(this, options);
711✔
204
        if (specialCaseReturnType) {
711✔
205
            return specialCaseReturnType;
124✔
206
        }
207
        if (isCallableType(calleeType) && (!isReferenceType(calleeType.returnType) || calleeType.returnType?.isResolvable())) {
587!
208
            if (isVoidType(calleeType.returnType)) {
534✔
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;
524✔
217
        }
218
        if (util.isUnionOfFunctions(calleeType, true)) {
53✔
219
            return util.getReturnTypeOfUnionOfFunctions(calleeType);
5✔
220
        }
221
        if (!isReferenceType(calleeType) && (calleeType as BaseFunctionType)?.returnType?.isResolvable()) {
48!
UNCOV
222
            return (calleeType as BaseFunctionType).returnType;
×
223
        }
224
        return new TypePropertyReferenceType(calleeType, 'returnType');
48✔
225
    }
226

227
    get leadingTrivia(): Token[] {
228
        return this.callee.leadingTrivia;
9,452✔
229
    }
230

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

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

267
        if (this.body) {
4,182✔
268
            this.body.parent = this;
4,181✔
269
        }
270
        this.symbolTable = new SymbolTable('FunctionExpression', () => this.parent?.getSymbolTable());
41,465!
271
    }
272

273
    public readonly kind = AstNodeKind.FunctionExpression;
4,182✔
274

275
    readonly parameters: FunctionParameterExpression[];
276
    public readonly body: Block;
277
    public readonly returnTypeExpression?: TypeExpression;
278

279
    readonly tokens: {
280
        readonly functionType?: Token;
281
        readonly endFunctionType?: Token;
282
        readonly leftParen?: Token;
283
        readonly rightParen?: Token;
284
        readonly as?: Token;
285
    };
286

287
    public get leadingTrivia(): Token[] {
288
        return this.tokens.functionType?.leadingTrivia;
30,834✔
289
    }
290

291
    public get endTrivia(): Token[] {
292
        return this.tokens.endFunctionType?.leadingTrivia;
2!
293
    }
294

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

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

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

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

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

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

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

420
        const docs = brsDocParser.parseNode(this.findAncestor(isFunctionStatement));
4,682✔
421

422
        returnType = util.chooseTypeFromCodeOrDocComment(
4,682✔
423
            this.returnTypeExpression?.getType({ ...options, typeChain: undefined }),
14,046✔
UNCOV
424
            docs.getReturnBscType({ ...options, tableProvider: () => this.getSymbolTable() }),
×
425
            options
426
        );
427

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

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

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

474

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

483

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

490
        return false;
15✔
491
    }
492

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

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

528
    public readonly kind = AstNodeKind.FunctionParameterExpression;
3,241✔
529

530
    readonly tokens: {
531
        readonly name: Identifier;
532
        readonly equals?: Token;
533
        readonly as?: Token;
534
    };
535

536
    public readonly defaultValue?: Expression;
537
    public readonly typeExpression?: TypeExpression;
538

539
    public getType(options: GetTypeOptions) {
540
        const docs = brsDocParser.parseNode(this.findAncestor(isFunctionStatement));
5,942✔
541
        const paramName = this.tokens.name.text;
5,942✔
542

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

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

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

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

585
        return result;
2,345✔
586
    }
587

588
    public getTypedef(state: BrsTranspileState): TranspileResult {
589
        const results = [this.tokens.name.text] as TranspileResult;
73✔
590

591
        if (this.defaultValue) {
73!
UNCOV
592
            results.push(' = ', ...this.defaultValue.transpile(state));
×
593
        }
594

595
        if (this.tokens.as) {
73✔
596
            results.push(' as ');
6✔
597

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

605
        return results;
73✔
606
    }
607

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

616
    get leadingTrivia(): Token[] {
617
        return this.tokens.name.leadingTrivia;
4,995✔
618
    }
619

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

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

650
        this.location = util.createBoundingLocation(this.obj, this.tokens.dot, this.tokens.name);
2,986✔
651
    }
652

653
    readonly tokens: {
654
        readonly name: Identifier;
655
        readonly dot?: Token;
656
    };
657
    readonly obj: Expression;
658

659
    public readonly kind = AstNodeKind.DottedGetExpression;
2,986✔
660

661
    public readonly location: Location | undefined;
662

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

679
    walk(visitor: WalkVisitor, options: WalkOptions) {
680
        if (options.walkMode & InternalWalkMode.walkExpressions) {
12,511!
681
            walk(this, 'obj', visitor, options);
12,511✔
682
        }
683
    }
684

685
    getType(options: GetTypeOptions) {
686
        const objType = this.obj?.getType(options);
6,802!
687
        let result = objType?.getMemberType(this.tokens.name?.text, options);
6,802!
688

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

711
    getName(parseMode: ParseMode) {
712
        return util.getAllDottedGetPartsAsString(this, parseMode);
35✔
713
    }
714

715
    get leadingTrivia(): Token[] {
716
        return this.obj.leadingTrivia;
15,832✔
717
    }
718

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

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

746
    public readonly kind = AstNodeKind.XmlAttributeGetExpression;
14✔
747

748
    public readonly tokens: {
749
        name: Identifier;
750
        at?: Token;
751
    };
752

753
    public readonly obj: Expression;
754

755
    public readonly location: Location | undefined;
756

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

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

771
    get leadingTrivia(): Token[] {
772
        return this.obj.leadingTrivia;
21✔
773
    }
774

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

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

816
    public readonly kind = AstNodeKind.IndexedGetExpression;
166✔
817

818
    public readonly obj: Expression;
819
    public readonly indexes: Expression[];
820

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

830
    public readonly location: Location | undefined;
831

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

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

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

871
    get leadingTrivia(): Token[] {
872
        return this.obj.leadingTrivia;
1,066✔
873
    }
874

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

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

904
    public readonly tokens: {
905
        readonly leftParen?: Token;
906
        readonly rightParen?: Token;
907
    };
908
    public readonly expression: Expression;
909

910
    public readonly kind = AstNodeKind.GroupingExpression;
61✔
911

912
    public readonly location: Location | undefined;
913

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

925
    walk(visitor: WalkVisitor, options: WalkOptions) {
926
        if (options.walkMode & InternalWalkMode.walkExpressions) {
239!
927
            walk(this, 'expression', visitor, options);
239✔
928
        }
929
    }
930

931
    getType(options: GetTypeOptions) {
932
        return this.expression.getType(options);
83✔
933
    }
934

935
    get leadingTrivia(): Token[] {
936
        return this.tokens.leftParen?.leadingTrivia;
359!
937
    }
938

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

951
export class LiteralExpression extends Expression {
1✔
952
    constructor(options: {
953
        value: Token;
954
    }) {
955
        super();
8,233✔
956
        this.tokens = {
8,233✔
957
            value: options.value
958
        };
959
    }
960

961
    public readonly tokens: {
962
        readonly value: Token;
963
    };
964

965
    public readonly kind = AstNodeKind.LiteralExpression;
8,233✔
966

967
    public get location() {
968
        return this.tokens.value.location;
23,529✔
969
    }
970

971
    public getType(options?: GetTypeOptions) {
972
        return util.tokenToBscType(this.tokens.value);
3,685✔
973
    }
974

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

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

991
        return [
4,926✔
992
            state.transpileToken({ ...this.tokens.value, text: text })
993
        ];
994
    }
995

996
    walk(visitor: WalkVisitor, options: WalkOptions) {
997
        //nothing to walk
998
    }
999

1000
    get leadingTrivia(): Token[] {
1001
        return this.tokens.value.leadingTrivia;
13,172✔
1002
    }
1003

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

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

1028
    public readonly tokens: {
1029
        readonly separator: PrintSeparatorToken;
1030
    };
1031

1032
    public readonly kind = AstNodeKind.PrintSeparatorExpression;
49✔
1033

1034
    public location: Location;
1035

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

1043
    walk(visitor: WalkVisitor, options: WalkOptions) {
1044
        //nothing to walk
1045
    }
1046

1047
    get leadingTrivia(): Token[] {
1048
        return this.tokens.separator.leadingTrivia;
194✔
1049
    }
1050

1051
    public clone() {
UNCOV
1052
        return new PrintSeparatorExpression({
×
1053
            separator: util.cloneToken(this.tokens?.separator)
×
1054
        });
1055
    }
1056
}
1057

1058

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

1072
    public readonly kind = AstNodeKind.EscapedCharCodeLiteralExpression;
37✔
1073

1074
    public readonly tokens: {
1075
        readonly value: Token & { charCode: number };
1076
    };
1077

1078
    public readonly location: Location;
1079

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

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

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

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

1114
    public readonly elements: Array<Expression>;
1115

1116
    public readonly tokens: {
1117
        readonly open?: Token;
1118
        readonly close?: Token;
1119
    };
1120

1121
    public readonly kind = AstNodeKind.ArrayLiteralExpression;
171✔
1122

1123
    public readonly location: Location | undefined;
1124

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

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

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

1154
        return result;
62✔
1155
    }
1156

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

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

1171
    get endTrivia(): Token[] {
1172
        return this.tokens.close?.leadingTrivia;
2!
1173
    }
1174

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

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

1205
    public readonly kind = AstNodeKind.AAMemberExpression;
305✔
1206

1207
    public readonly location: Location | undefined;
1208

1209
    public readonly tokens: {
1210
        readonly key: Token;
1211
        readonly colon?: Token;
1212
        readonly comma?: Token;
1213
    };
1214

1215
    /** The expression evaluated to determine the member's initial value. */
1216
    public readonly value: Expression;
1217

1218
    transpile(state: BrsTranspileState) {
1219
        //TODO move the logic from AALiteralExpression loop into this function
UNCOV
1220
        return [];
×
1221
    }
1222

1223
    walk(visitor: WalkVisitor, options: WalkOptions) {
1224
        walk(this, 'value', visitor, options);
1,142✔
1225
    }
1226

1227
    getType(options: GetTypeOptions): BscType {
1228
        return this.value.getType(options);
230✔
1229
    }
1230

1231
    get leadingTrivia(): Token[] {
1232
        return this.tokens.key.leadingTrivia;
823✔
1233
    }
1234

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

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

1262
    public readonly elements: Array<AAMemberExpression>;
1263
    public readonly tokens: {
1264
        readonly open?: Token;
1265
        readonly close?: Token;
1266
    };
1267

1268
    public readonly kind = AstNodeKind.AALiteralExpression;
300✔
1269

1270
    public readonly location: Location | undefined;
1271

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

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

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

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

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

1321
        return result;
67✔
1322
    }
1323

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

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

1347
    public get leadingTrivia(): Token[] {
1348
        return this.tokens.open?.leadingTrivia;
833!
1349
    }
1350

1351
    public get endTrivia(): Token[] {
1352
        return this.tokens.close?.leadingTrivia;
1!
1353
    }
1354

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

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

1380
    public readonly kind = AstNodeKind.UnaryExpression;
66✔
1381

1382
    public readonly location: Location | undefined;
1383

1384
    public readonly tokens: {
1385
        readonly operator: Token;
1386
    };
1387
    public readonly right: Expression;
1388

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

1399
        return [
12✔
1400
            state.transpileToken(this.tokens.operator),
1401
            separatingWhitespace,
1402
            ...this.right.transpile(state)
1403
        ];
1404
    }
1405

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

1412
    getType(options: GetTypeOptions): BscType {
1413
        return util.unaryOperatorResultType(this.tokens.operator, this.right.getType(options));
48✔
1414
    }
1415

1416
    public get leadingTrivia(): Token[] {
1417
        return this.tokens.operator.leadingTrivia;
187✔
1418
    }
1419

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

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

1442
    public readonly tokens: {
1443
        readonly name: Identifier;
1444
    };
1445

1446
    public readonly kind = AstNodeKind.VariableExpression;
11,913✔
1447

1448
    public readonly location: Location;
1449

1450
    public getName(parseMode?: ParseMode) {
1451
        return this.tokens.name.text;
26,224✔
1452
    }
1453

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

1477
    walk(visitor: WalkVisitor, options: WalkOptions) {
1478
        //nothing to walk
1479
    }
1480

1481

1482
    getType(options: GetTypeOptions) {
1483
        let resultType: BscType = util.tokenToBscType(this.tokens.name);
26,077✔
1484
        const nameKey = this.getName();
26,077✔
1485
        if (!resultType) {
26,077✔
1486
            const symbolTable = this.getSymbolTable();
19,996✔
1487
            resultType = symbolTable?.getSymbolType(nameKey, {
19,996!
1488
                ...options,
1489
                statementIndex: this.statementIndex,
1490
                fullName: nameKey,
1491
                tableProvider: () => this.getSymbolTable()
43,596✔
1492
            });
1493

1494
            if (util.isClassUsedAsFunction(resultType, this, options)) {
19,996✔
1495
                resultType = FunctionType.instance;
20✔
1496
            }
1497

1498
        }
1499
        options?.typeChain?.push(new TypeChainEntry({ name: nameKey, type: resultType, data: options.data, astNode: this }));
26,077!
1500
        return resultType;
26,077✔
1501
    }
1502

1503
    get leadingTrivia(): Token[] {
1504
        return this.tokens.name.leadingTrivia;
41,926✔
1505
    }
1506

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

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

1527
    public readonly location: Location;
1528

1529
    public readonly kind = AstNodeKind.SourceLiteralExpression;
37✔
1530

1531
    public readonly tokens: {
1532
        readonly value: Token;
1533
    };
1534

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

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

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

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

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

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

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

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

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

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

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

1673
    public readonly kind = AstNodeKind.NewExpression;
140✔
1674

1675
    public readonly location: Location | undefined;
1676

1677
    public readonly tokens: {
1678
        readonly new?: Token;
1679
    };
1680
    public readonly call: CallExpression;
1681

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

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

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

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

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

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

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

1754
        this.location = util.createBoundingLocation(
76✔
1755
            this.callee,
1756
            this.tokens.operator,
1757
            this.tokens.methodName,
1758
            this.tokens.openingParen,
1759
            ...this.args ?? [],
228!
1760
            this.tokens.closingParen
1761
        );
1762
    }
1763

1764
    public readonly callee: Expression;
1765
    public readonly args: Expression[];
1766

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

1774
    public readonly kind = AstNodeKind.CallfuncExpression;
76✔
1775

1776
    public readonly location: Location | undefined;
1777

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

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

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

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

1820
    get leadingTrivia(): Token[] {
1821
        return this.callee.leadingTrivia;
660✔
1822
    }
1823

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

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

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

1857
    readonly location: Location | undefined;
1858

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

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

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

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

1915
    public readonly kind = AstNodeKind.TemplateStringExpression;
53✔
1916

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

1924
    public readonly location: Location | undefined;
1925

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

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

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

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

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

1981
        return result;
18✔
1982
    }
1983

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

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

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

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

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

2037
    public readonly kind = AstNodeKind.TaggedTemplateStringExpression;
12✔
2038

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

2045
    public readonly quasis: TemplateStringQuasiExpression[];
2046
    public readonly expressions: Expression[];
2047

2048
    public readonly location: Location | undefined;
2049

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

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

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

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

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

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

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

2135
    public readonly kind = AstNodeKind.AnnotationExpression;
83✔
2136

2137
    public readonly tokens: {
2138
        readonly at: Token;
2139
        readonly name: Token;
2140
    };
2141

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

2150
    public readonly name: string;
2151

2152
    public call: CallExpression;
2153

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

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

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

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

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

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

2221
    public readonly kind = AstNodeKind.TernaryExpression;
100✔
2222

2223
    public readonly location: Location | undefined;
2224

2225
    public readonly tokens: {
2226
        readonly questionMark?: Token;
2227
        readonly colon?: Token;
2228
    };
2229

2230
    public readonly test: Expression;
2231
    public readonly consequent?: Expression;
2232
    public readonly alternate?: Expression;
2233

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

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

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

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

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

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

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

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

2344
    public readonly kind = AstNodeKind.NullCoalescingExpression;
37✔
2345

2346
    public readonly location: Location | undefined;
2347

2348
    public readonly tokens: {
2349
        readonly questionQuestion?: Token;
2350
    };
2351

2352
    public readonly consequent: Expression;
2353
    public readonly alternate: Expression;
2354

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2564
    public readonly kind = AstNodeKind.TypeExpression;
1,698✔
2565

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

2571
    public readonly location: Location;
2572

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

2586
    public getType(options: GetTypeOptions): BscType {
2587
        return this.expression.getType({ ...options, flags: SymbolTypeFlag.typetime });
7,806✔
2588
    }
2589

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

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

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

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

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

2634
    public readonly kind = AstNodeKind.TypecastExpression;
81✔
2635

2636
    public readonly obj: Expression;
2637

2638
    public readonly tokens: {
2639
        readonly as?: Token;
2640
    };
2641

2642
    public typeExpression?: TypeExpression;
2643

2644
    public readonly location: Location;
2645

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

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

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

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

2699
    public readonly tokens: {
2700
        readonly leftBracket?: Token;
2701
        readonly rightBracket?: Token;
2702
    };
2703

2704
    public readonly innerType: Expression;
2705

2706
    public readonly kind = AstNodeKind.TypedArrayExpression;
29✔
2707

2708
    public readonly location: Location;
2709

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

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

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

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

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