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

rokucommunity / brighterscript / #14385

09 May 2025 11:44AM UTC coverage: 87.032% (-2.0%) from 89.017%
#14385

push

web-flow
Merge a194c3925 into 489231ac7

13732 of 16677 branches covered (82.34%)

Branch coverage included in aggregate %.

8175 of 8874 new or added lines in 103 files covered. (92.12%)

84 existing lines in 22 files now uncovered.

14604 of 15881 relevant lines covered (91.96%)

20324.31 hits per line

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

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

82

83
    public getType(options: GetTypeOptions): BscType {
84
        const operatorKind = this.tokens.operator.kind;
426✔
85
        if (options.flags & SymbolTypeFlag.typetime) {
426✔
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) {
312!
93
            return util.binaryOperatorResultType(
312✔
94
                this.left.getType(options),
95
                this.tokens.operator,
96
                this.right.getType(options)) ?? DynamicType.instance;
312✔
97
        }
98
        return DynamicType.instance;
1✔
99
    }
100

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

195
    getType(options: GetTypeOptions) {
196
        const calleeType = this.callee.getType(options);
1,062✔
197
        if (options.ignoreCall) {
1,062!
NEW
198
            return calleeType;
×
199
        }
200
        if (isNewExpression(this.parent)) {
1,062✔
201
            return calleeType;
350✔
202
        }
203
        const specialCaseReturnType = util.getSpecialCaseCallExpressionReturnType(this, options);
712✔
204
        if (specialCaseReturnType) {
712✔
205
            return specialCaseReturnType;
124✔
206
        }
207
        if (isCallableType(calleeType) && (!isReferenceType(calleeType.returnType) || calleeType.returnType?.isResolvable())) {
588!
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)) {
54✔
219
            return util.getReturnTypeOfUnionOfFunctions(calleeType);
4✔
220
        }
221
        if (!isReferenceType(calleeType) && (calleeType as BaseFunctionType)?.returnType?.isResolvable()) {
50!
NEW
222
            return (calleeType as BaseFunctionType).returnType;
×
223
        }
224
        return new TypePropertyReferenceType(calleeType, 'returnType');
50✔
225
    }
226

227
    get leadingTrivia(): Token[] {
228
        return this.callee.leadingTrivia;
9,439✔
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,173✔
256
        this.tokens = {
4,173✔
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,173✔
264
        this.body = options.body;
4,173✔
265
        this.returnTypeExpression = options.returnTypeExpression;
4,173✔
266

267
        //if there's a body, and it doesn't have a SymbolTable, assign one
268
        if (this.body) {
4,173✔
269
            if (!this.body.symbolTable) {
4,172!
270
                this.body.symbolTable = new SymbolTable(`Block`, () => this.getSymbolTable());
32,451✔
271
            } else {
NEW
272
                this.body.symbolTable.pushParentProvider(() => this.getSymbolTable());
×
273
            }
274
            this.body.parent = this;
4,172✔
275
        }
276
        this.symbolTable = new SymbolTable('FunctionExpression', () => this.parent?.getSymbolTable());
41,049!
277
    }
278

279
    public readonly kind = AstNodeKind.FunctionExpression;
4,173✔
280

281
    readonly parameters: FunctionParameterExpression[];
282
    public readonly body: Block;
283
    public readonly returnTypeExpression?: TypeExpression;
284

285
    readonly tokens: {
286
        readonly functionType?: Token;
287
        readonly endFunctionType?: Token;
288
        readonly leftParen?: Token;
289
        readonly rightParen?: Token;
290
        readonly as?: Token;
291
    };
292

293
    public get leadingTrivia(): Token[] {
294
        return this.tokens.functionType?.leadingTrivia;
30,717✔
295
    }
296

297
    public get endTrivia(): Token[] {
298
        return this.tokens.endFunctionType?.leadingTrivia;
2!
299
    }
300

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

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

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

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

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

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

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

426
        const docs = brsDocParser.parseNode(this.findAncestor(isFunctionStatement));
4,663✔
427

428
        returnType = util.chooseTypeFromCodeOrDocComment(
4,663✔
429
            this.returnTypeExpression?.getType({ ...options, typeChain: undefined }),
13,989✔
NEW
430
            docs.getReturnBscType({ ...options, tableProvider: () => this.getSymbolTable() }),
×
431
            options
432
        );
433

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

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

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

480

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

489

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

496
        return false;
15✔
497
    }
498

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

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

534
    public readonly kind = AstNodeKind.FunctionParameterExpression;
3,236✔
535

536
    readonly tokens: {
537
        readonly name: Identifier;
538
        readonly equals?: Token;
539
        readonly as?: Token;
540
    };
541

542
    public readonly defaultValue?: Expression;
543
    public readonly typeExpression?: TypeExpression;
544

545
    public getType(options: GetTypeOptions) {
546
        const docs = brsDocParser.parseNode(this.findAncestor(isFunctionStatement));
5,916✔
547
        const paramName = this.tokens.name.text;
5,916✔
548

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

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

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

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

591
        return result;
2,345✔
592
    }
593

594
    public getTypedef(state: BrsTranspileState): TranspileResult {
595
        const results = [this.tokens.name.text] as TranspileResult;
73✔
596

597
        if (this.defaultValue) {
73!
598
            results.push(' = ', ...this.defaultValue.transpile(state));
×
599
        }
600

601
        if (this.tokens.as) {
73✔
602
            results.push(' as ');
6✔
603

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

611
        return results;
73✔
612
    }
613

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

622
    get leadingTrivia(): Token[] {
623
        return this.tokens.name.leadingTrivia;
4,975✔
624
    }
625

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

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

656
        this.location = util.createBoundingLocation(this.obj, this.tokens.dot, this.tokens.name);
2,984✔
657
    }
658

659
    readonly tokens: {
660
        readonly name: Identifier;
661
        readonly dot?: Token;
662
    };
663
    readonly obj: Expression;
664

665
    public readonly kind = AstNodeKind.DottedGetExpression;
2,984✔
666

667
    public readonly location: Location | undefined;
668

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

685
    walk(visitor: WalkVisitor, options: WalkOptions) {
686
        if (options.walkMode & InternalWalkMode.walkExpressions) {
12,490!
687
            walk(this, 'obj', visitor, options);
12,490✔
688
        }
689
    }
690

691
    getType(options: GetTypeOptions) {
692
        const objType = this.obj?.getType(options);
6,795!
693
        let result = objType?.getMemberType(this.tokens.name?.text, options);
6,795!
694

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

717
    getName(parseMode: ParseMode) {
718
        return util.getAllDottedGetPartsAsString(this, parseMode);
35✔
719
    }
720

721
    get leadingTrivia(): Token[] {
722
        return this.obj.leadingTrivia;
15,817✔
723
    }
724

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

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

752
    public readonly kind = AstNodeKind.XmlAttributeGetExpression;
14✔
753

754
    public readonly tokens: {
755
        name: Identifier;
756
        at?: Token;
757
    };
758

759
    public readonly obj: Expression;
760

761
    public readonly location: Location | undefined;
762

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

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

777
    get leadingTrivia(): Token[] {
778
        return this.obj.leadingTrivia;
21✔
779
    }
780

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

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

822
    public readonly kind = AstNodeKind.IndexedGetExpression;
166✔
823

824
    public readonly obj: Expression;
825
    public readonly indexes: Expression[];
826

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

836
    public readonly location: Location | undefined;
837

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

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

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

877
    get leadingTrivia(): Token[] {
878
        return this.obj.leadingTrivia;
1,066✔
879
    }
880

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

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

910
    public readonly tokens: {
911
        readonly leftParen?: Token;
912
        readonly rightParen?: Token;
913
    };
914
    public readonly expression: Expression;
915

916
    public readonly kind = AstNodeKind.GroupingExpression;
60✔
917

918
    public readonly location: Location | undefined;
919

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

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

937
    getType(options: GetTypeOptions) {
938
        return this.expression.getType(options);
83✔
939
    }
940

941
    get leadingTrivia(): Token[] {
942
        return this.tokens.leftParen?.leadingTrivia;
355!
943
    }
944

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

957
export class LiteralExpression extends Expression {
1✔
958
    constructor(options: {
959
        value: Token;
960
    }) {
961
        super();
8,199✔
962
        this.tokens = {
8,199✔
963
            value: options.value
964
        };
965
    }
966

967
    public readonly tokens: {
968
        readonly value: Token;
969
    };
970

971
    public readonly kind = AstNodeKind.LiteralExpression;
8,199✔
972

973
    public get location() {
974
        return this.tokens.value.location;
23,309✔
975
    }
976

977
    public getType(options?: GetTypeOptions) {
978
        return util.tokenToBscType(this.tokens.value);
3,640✔
979
    }
980

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

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

997
        return [
4,926✔
998
            state.transpileToken({ ...this.tokens.value, text: text })
999
        ];
1000
    }
1001

1002
    walk(visitor: WalkVisitor, options: WalkOptions) {
1003
        //nothing to walk
1004
    }
1005

1006
    get leadingTrivia(): Token[] {
1007
        return this.tokens.value.leadingTrivia;
13,003✔
1008
    }
1009

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

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

1034
    public readonly tokens: {
1035
        readonly separator: PrintSeparatorToken;
1036
    };
1037

1038
    public readonly kind = AstNodeKind.PrintSeparatorExpression;
42✔
1039

1040
    public location: Location;
1041

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

1049
    walk(visitor: WalkVisitor, options: WalkOptions) {
1050
        //nothing to walk
1051
    }
1052

1053
    get leadingTrivia(): Token[] {
1054
        return this.tokens.separator.leadingTrivia;
166✔
1055
    }
1056

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

1064

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

1078
    public readonly kind = AstNodeKind.EscapedCharCodeLiteralExpression;
37✔
1079

1080
    public readonly tokens: {
1081
        readonly value: Token & { charCode: number };
1082
    };
1083

1084
    public readonly location: Location;
1085

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

1092
    walk(visitor: WalkVisitor, options: WalkOptions) {
1093
        //nothing to walk
1094
    }
1095

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

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

1120
    public readonly elements: Array<Expression>;
1121

1122
    public readonly tokens: {
1123
        readonly open?: Token;
1124
        readonly close?: Token;
1125
    };
1126

1127
    public readonly kind = AstNodeKind.ArrayLiteralExpression;
171✔
1128

1129
    public readonly location: Location | undefined;
1130

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

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

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

1160
        return result;
62✔
1161
    }
1162

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

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

1177
    get endTrivia(): Token[] {
1178
        return this.tokens.close?.leadingTrivia;
2!
1179
    }
1180

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

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

1211
    public readonly kind = AstNodeKind.AAMemberExpression;
305✔
1212

1213
    public readonly location: Location | undefined;
1214

1215
    public readonly tokens: {
1216
        readonly key: Token;
1217
        readonly colon?: Token;
1218
        readonly comma?: Token;
1219
    };
1220

1221
    /** The expression evaluated to determine the member's initial value. */
1222
    public readonly value: Expression;
1223

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

1229
    walk(visitor: WalkVisitor, options: WalkOptions) {
1230
        walk(this, 'value', visitor, options);
1,142✔
1231
    }
1232

1233
    getType(options: GetTypeOptions): BscType {
1234
        return this.value.getType(options);
230✔
1235
    }
1236

1237
    get leadingTrivia(): Token[] {
1238
        return this.tokens.key.leadingTrivia;
823✔
1239
    }
1240

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

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

1268
    public readonly elements: Array<AAMemberExpression>;
1269
    public readonly tokens: {
1270
        readonly open?: Token;
1271
        readonly close?: Token;
1272
    };
1273

1274
    public readonly kind = AstNodeKind.AALiteralExpression;
300✔
1275

1276
    public readonly location: Location | undefined;
1277

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

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

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

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

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

1327
        return result;
67✔
1328
    }
1329

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

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

1353
    public get leadingTrivia(): Token[] {
1354
        return this.tokens.open?.leadingTrivia;
833!
1355
    }
1356

1357
    public get endTrivia(): Token[] {
1358
        return this.tokens.close?.leadingTrivia;
1!
1359
    }
1360

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

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

1386
    public readonly kind = AstNodeKind.UnaryExpression;
66✔
1387

1388
    public readonly location: Location | undefined;
1389

1390
    public readonly tokens: {
1391
        readonly operator: Token;
1392
    };
1393
    public readonly right: Expression;
1394

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

1405
        return [
12✔
1406
            state.transpileToken(this.tokens.operator),
1407
            separatingWhitespace,
1408
            ...this.right.transpile(state)
1409
        ];
1410
    }
1411

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

1418
    getType(options: GetTypeOptions): BscType {
1419
        return util.unaryOperatorResultType(this.tokens.operator, this.right.getType(options));
48✔
1420
    }
1421

1422
    public get leadingTrivia(): Token[] {
1423
        return this.tokens.operator.leadingTrivia;
187✔
1424
    }
1425

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

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

1448
    public readonly tokens: {
1449
        readonly name: Identifier;
1450
    };
1451

1452
    public readonly kind = AstNodeKind.VariableExpression;
11,865✔
1453

1454
    public readonly location: Location;
1455

1456
    public getName(parseMode?: ParseMode) {
1457
        return this.tokens.name.text;
26,082✔
1458
    }
1459

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

1483
    walk(visitor: WalkVisitor, options: WalkOptions) {
1484
        //nothing to walk
1485
    }
1486

1487

1488
    getType(options: GetTypeOptions) {
1489
        let resultType: BscType = util.tokenToBscType(this.tokens.name);
25,935✔
1490
        const nameKey = this.getName();
25,935✔
1491
        if (!resultType) {
25,935✔
1492
            const symbolTable = this.getSymbolTable();
19,898✔
1493
            resultType = symbolTable?.getSymbolType(nameKey, { ...options, fullName: nameKey, tableProvider: () => this.getSymbolTable() });
43,513!
1494

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

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

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

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

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

1528
    public readonly location: Location;
1529

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1676
    public readonly location: Location | undefined;
1677

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

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

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

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

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

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

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

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

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

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

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

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

1777
    public readonly location: Location | undefined;
1778

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

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

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

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

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

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

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

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

1858
    readonly location: Location | undefined;
1859

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

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

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

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

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

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

1925
    public readonly location: Location | undefined;
1926

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

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

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

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

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

1982
        return result;
18✔
1983
    }
1984

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

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

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

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

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

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

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

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

2049
    public readonly location: Location | undefined;
2050

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

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

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

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

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

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

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

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

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

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

2151
    public readonly name: string;
2152

2153
    public call: CallExpression;
2154

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

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

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

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

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

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

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

2224
    public readonly location: Location | undefined;
2225

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

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

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

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

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

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

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

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

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

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

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

2347
    public readonly location: Location | undefined;
2348

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2565
    public readonly kind = AstNodeKind.TypeExpression;
1,691✔
2566

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

2572
    public readonly location: Location;
2573

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

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

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

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

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

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

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

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

2637
    public readonly obj: Expression;
2638

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

2643
    public typeExpression?: TypeExpression;
2644

2645
    public readonly location: Location;
2646

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

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

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

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

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

2705
    public readonly innerType: Expression;
2706

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

2709
    public readonly location: Location;
2710

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

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

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

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

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