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

rokucommunity / brighterscript / #14044

20 Mar 2025 07:09PM UTC coverage: 87.163% (-2.0%) from 89.117%
#14044

push

web-flow
Merge e33b1f944 into 0eceb0830

13257 of 16072 branches covered (82.49%)

Branch coverage included in aggregate %.

1163 of 1279 new or added lines in 24 files covered. (90.93%)

802 existing lines in 52 files now uncovered.

14323 of 15570 relevant lines covered (91.99%)

21312.85 hits per line

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

91.57
/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, isCallExpression, isCallableType, 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,337✔
47
        this.tokens = {
3,337✔
48
            operator: options.operator
49
        };
50
        this.left = options.left;
3,337✔
51
        this.right = options.right;
3,337✔
52
        this.location = util.createBoundingLocation(this.left, this.tokens.operator, this.right);
3,337✔
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,337✔
62

63
    public readonly location: Location | undefined;
64

65
    transpile(state: BrsTranspileState): TranspileResult {
66
        return [
3,303✔
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,309!
77
            walk(this, 'left', visitor, options);
14,309✔
78
            walk(this, 'right', visitor, options);
14,309✔
79
        }
80
    }
81

82

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

101
    get leadingTrivia(): Token[] {
102
        return this.left.leadingTrivia;
1,876✔
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,552✔
134
        this.tokens = {
2,552✔
135
            openingParen: options.openingParen,
136
            closingParen: options.closingParen
137
        };
138
        this.callee = options.callee;
2,552✔
139
        this.args = options.args ?? [];
2,552✔
140
        this.location = util.createBoundingLocation(this.callee, this.tokens.openingParen, ...this.args ?? [], this.tokens.closingParen);
2,552!
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,552✔
154

155
    public readonly location: Location | undefined;
156

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

160
        //transpile the name
161
        if (nameOverride) {
1,650✔
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,638✔
169
        }
170

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

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

195
    getType(options: GetTypeOptions) {
196
        const calleeType = this.callee.getType(options);
1,047✔
197
        if (options.ignoreCall) {
1,047!
UNCOV
198
            return calleeType;
×
199
        }
200
        if (isNewExpression(this.parent)) {
1,047✔
201
            return calleeType;
350✔
202
        }
203
        const specialCaseReturnType = util.getSpecialCaseCallExpressionReturnType(this, options);
697✔
204
        if (specialCaseReturnType) {
697✔
205
            return specialCaseReturnType;
124✔
206
        }
207
        if (isCallableType(calleeType) && (!isReferenceType(calleeType.returnType) || calleeType.returnType?.isResolvable())) {
573!
208
            if (isVoidType(calleeType.returnType)) {
268✔
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;
258✔
217
        }
218
        if (!isReferenceType(calleeType) && (calleeType as BaseFunctionType)?.returnType?.isResolvable()) {
305✔
219
            return (calleeType as BaseFunctionType).returnType;
257✔
220
        }
221
        return new TypePropertyReferenceType(calleeType, 'returnType');
48✔
222
    }
223

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

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

241
export class FunctionExpression extends Expression implements TypedefProvider {
1✔
242
    constructor(options: {
243
        functionType?: Token;
244
        leftParen?: Token;
245
        parameters?: FunctionParameterExpression[];
246
        rightParen?: Token;
247
        as?: Token;
248
        returnTypeExpression?: TypeExpression;
249
        body: Block;
250
        endFunctionType?: Token;
251
    }) {
252
        super();
4,043✔
253
        this.tokens = {
4,043✔
254
            functionType: options.functionType,
255
            leftParen: options.leftParen,
256
            rightParen: options.rightParen,
257
            as: options.as,
258
            endFunctionType: options.endFunctionType
259
        };
260
        this.parameters = options.parameters ?? [];
4,043✔
261
        this.body = options.body;
4,043✔
262
        this.returnTypeExpression = options.returnTypeExpression;
4,043✔
263
        //if there's a body, and it doesn't have a SymbolTable, assign one
264
        if (this.body) {
4,043✔
265
            if (!this.body.symbolTable) {
4,042!
266
                this.body.symbolTable = new SymbolTable(`Block`, () => this.getSymbolTable());
30,410✔
267
            } else {
UNCOV
268
                this.body.symbolTable.pushParentProvider(() => this.getSymbolTable());
×
269
            }
270
            this.body.parent = this;
4,042✔
271
        }
272
        this.symbolTable = new SymbolTable('FunctionExpression', () => this.parent?.getSymbolTable());
38,585!
273
    }
274

275
    public readonly kind = AstNodeKind.FunctionExpression;
4,043✔
276

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

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

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

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

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

313
    transpile(state: BrsTranspileState, name?: Identifier, includeBody = true) {
1,482✔
314
        let results = [] as TranspileResult;
1,482✔
315
        //'function'|'sub'
316
        results.push(
1,482✔
317
            state.transpileToken(this.tokens.functionType, 'function', false, state.skipLeadingComments)
318
        );
319
        //functionName?
320
        if (name) {
1,482✔
321
            results.push(
1,407✔
322
                ' ',
323
                state.transpileToken(name)
324
            );
325
        }
326
        //leftParen
327
        results.push(
1,482✔
328
            state.transpileToken(this.tokens.leftParen, '(')
329
        );
330
        //parameters
331
        for (let i = 0; i < this.parameters.length; i++) {
1,482✔
332
            let param = this.parameters[i];
2,246✔
333
            //add commas
334
            if (i > 0) {
2,246✔
335
                results.push(', ');
1,103✔
336
            }
337
            //add parameter
338
            results.push(param.transpile(state));
2,246✔
339
        }
340
        //right paren
341
        results.push(
1,482✔
342
            state.transpileToken(this.tokens.rightParen, ')')
343
        );
344
        //as [Type]
345
        if (!state.options.removeParameterTypes && this.returnTypeExpression) {
1,482✔
346
            results.push(
40✔
347
                ' ',
348
                //as
349
                state.transpileToken(this.tokens.as, 'as'),
350
                ' ',
351
                //return type
352
                ...this.returnTypeExpression.transpile(state)
353
            );
354
        }
355
        let hasBody = false;
1,482✔
356
        if (includeBody) {
1,482!
357
            state.lineage.unshift(this);
1,482✔
358
            let body = this.body.transpile(state);
1,482✔
359
            hasBody = body.length > 0;
1,482✔
360
            state.lineage.shift();
1,482✔
361
            results.push(...body);
1,482✔
362
        }
363

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

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

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

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

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

423
        returnType = util.chooseTypeFromCodeOrDocComment(
4,455✔
424
            this.returnTypeExpression?.getType({ ...options, typeChain: undefined }),
13,365✔
425
            docs.getReturnBscType({ ...options, tableProvider: () => this.getSymbolTable() }),
×
426
            options
427
        );
428

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

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

458
    public clone() {
459
        return this.finalizeClone(
110✔
460
            new FunctionExpression({
461
                parameters: this.parameters?.map(e => e?.clone()),
7✔
462
                body: this.body?.clone(),
330✔
463
                functionType: util.cloneToken(this.tokens.functionType),
464
                endFunctionType: util.cloneToken(this.tokens.endFunctionType),
465
                leftParen: util.cloneToken(this.tokens.leftParen),
466
                rightParen: util.cloneToken(this.tokens.rightParen),
467
                as: util.cloneToken(this.tokens.as),
468
                returnTypeExpression: this.returnTypeExpression?.clone()
330✔
469
            }),
470
            ['body', 'returnTypeExpression']
471
        );
472
    }
473
}
474

475
export class FunctionParameterExpression extends Expression {
1✔
476
    constructor(options: {
477
        name: Identifier;
478
        equals?: Token;
479
        defaultValue?: Expression;
480
        as?: Token;
481
        typeExpression?: TypeExpression;
482
    }) {
483
        super();
3,117✔
484
        this.tokens = {
3,117✔
485
            name: options.name,
486
            equals: options.equals,
487
            as: options.as
488
        };
489
        this.defaultValue = options.defaultValue;
3,117✔
490
        this.typeExpression = options.typeExpression;
3,117✔
491
    }
492

493
    public readonly kind = AstNodeKind.FunctionParameterExpression;
3,117✔
494

495
    readonly tokens: {
496
        readonly name: Identifier;
497
        readonly equals?: Token;
498
        readonly as?: Token;
499
    };
500

501
    public readonly defaultValue?: Expression;
502
    public readonly typeExpression?: TypeExpression;
503

504
    public getType(options: GetTypeOptions) {
505
        const docs = brsDocParser.parseNode(this.findAncestor(isFunctionStatement));
5,716✔
506
        const paramName = this.tokens.name.text;
5,716✔
507

508
        let paramTypeFromCode = this.typeExpression?.getType({ ...options, flags: SymbolTypeFlag.typetime, typeChain: undefined }) ??
5,716✔
509
            util.getDefaultTypeFromValueType(this.defaultValue?.getType({ ...options, flags: SymbolTypeFlag.runtime, typeChain: undefined }));
8,649✔
510
        if (isInvalidType(paramTypeFromCode) || isVoidType(paramTypeFromCode)) {
5,716✔
511
            paramTypeFromCode = undefined;
20✔
512
        }
513
        const paramTypeFromDoc = docs.getParamBscType(paramName, { ...options, fullName: paramName, typeChain: undefined, tableProvider: () => this.getSymbolTable() });
5,716✔
514

515
        let paramType = util.chooseTypeFromCodeOrDocComment(paramTypeFromCode, paramTypeFromDoc, options) ?? DynamicType.instance;
5,716✔
516
        options.typeChain?.push(new TypeChainEntry({ name: paramName, type: paramType, data: options.data, astNode: this }));
5,716✔
517
        return paramType;
5,716✔
518
    }
519

520
    public get location(): Location | undefined {
521
        return util.createBoundingLocation(
9,051✔
522
            this.tokens.name,
523
            this.tokens.as,
524
            this.typeExpression,
525
            this.tokens.equals,
526
            this.defaultValue
527
        );
528
    }
529

530
    public transpile(state: BrsTranspileState) {
531
        let result: TranspileResult = [
2,263✔
532
            //name
533
            state.transpileToken(this.tokens.name)
534
        ];
535
        //default value
536
        if (this.defaultValue) {
2,263✔
537
            result.push(' = ');
9✔
538
            result.push(this.defaultValue.transpile(state));
9✔
539
        }
540
        //type declaration
541
        if (this.typeExpression && !state.options.removeParameterTypes) {
2,263✔
542
            result.push(' ');
71✔
543
            result.push(state.transpileToken(this.tokens.as, 'as'));
71✔
544
            result.push(' ');
71✔
545
            result.push(
71✔
546
                ...(this.typeExpression?.transpile(state) ?? [])
426!
547
            );
548
        }
549

550
        return result;
2,263✔
551
    }
552

553
    public getTypedef(state: BrsTranspileState): TranspileResult {
554
        const results = [this.tokens.name.text] as TranspileResult;
73✔
555

556
        if (this.defaultValue) {
73!
UNCOV
557
            results.push(' = ', ...this.defaultValue.transpile(state));
×
558
        }
559

560
        if (this.tokens.as) {
73✔
561
            results.push(' as ');
6✔
562

563
            // TODO: Is this conditional needed? Will typeToken always exist
564
            // so long as `asToken` exists?
565
            if (this.typeExpression) {
6!
566
                results.push(...(this.typeExpression?.getTypedef(state) ?? ['']));
6!
567
            }
568
        }
569

570
        return results;
73✔
571
    }
572

573
    walk(visitor: WalkVisitor, options: WalkOptions) {
574
        // eslint-disable-next-line no-bitwise
575
        if (options.walkMode & InternalWalkMode.walkExpressions) {
15,195!
576
            walk(this, 'defaultValue', visitor, options);
15,195✔
577
            walk(this, 'typeExpression', visitor, options);
15,195✔
578
        }
579
    }
580

581
    get leadingTrivia(): Token[] {
582
        return this.tokens.name.leadingTrivia;
4,814✔
583
    }
584

585
    public clone() {
586
        return this.finalizeClone(
14✔
587
            new FunctionParameterExpression({
588
                name: util.cloneToken(this.tokens.name),
589
                as: util.cloneToken(this.tokens.as),
590
                typeExpression: this.typeExpression?.clone(),
42✔
591
                equals: util.cloneToken(this.tokens.equals),
592
                defaultValue: this.defaultValue?.clone()
42✔
593
            }),
594
            ['typeExpression', 'defaultValue']
595
        );
596
    }
597
}
598

599
export class DottedGetExpression extends Expression {
1✔
600
    constructor(options: {
601
        obj: Expression;
602
        name: Identifier;
603
        /**
604
         * Can either be `.`, or `?.` for optional chaining - defaults in transpile to '.'
605
         */
606
        dot?: Token;
607
    }) {
608
        super();
2,936✔
609
        this.tokens = {
2,936✔
610
            name: options.name,
611
            dot: options.dot
612
        };
613
        this.obj = options.obj;
2,936✔
614

615
        this.location = util.createBoundingLocation(this.obj, this.tokens.dot, this.tokens.name);
2,936✔
616
    }
617

618
    readonly tokens: {
619
        readonly name: Identifier;
620
        readonly dot?: Token;
621
    };
622
    readonly obj: Expression;
623

624
    public readonly kind = AstNodeKind.DottedGetExpression;
2,936✔
625

626
    public readonly location: Location | undefined;
627

628
    transpile(state: BrsTranspileState) {
629
        //if the callee starts with a namespace name, transpile the name
630
        if (state.file.calleeStartsWithNamespace(this)) {
934✔
631
            return [
9✔
632
                ...state.transpileLeadingCommentsForAstNode(this),
633
                state.sourceNode(this, this.getName(ParseMode.BrightScript))
634
            ];
635
        } else {
636
            return [
925✔
637
                ...this.obj.transpile(state),
638
                state.transpileToken(this.tokens.dot, '.'),
639
                state.transpileToken(this.tokens.name)
640
            ];
641
        }
642
    }
643

644
    walk(visitor: WalkVisitor, options: WalkOptions) {
645
        if (options.walkMode & InternalWalkMode.walkExpressions) {
12,259!
646
            walk(this, 'obj', visitor, options);
12,259✔
647
        }
648
    }
649

650
    getType(options: GetTypeOptions) {
651
        const objType = this.obj?.getType(options);
6,717!
652
        let result = objType?.getMemberType(this.tokens.name?.text, options);
6,717!
653

654
        if (util.isClassUsedAsFunction(result, this, options)) {
6,717✔
655
            // treat this class constructor as a function
656
            result = FunctionType.instance;
11✔
657
        }
658
        options.typeChain?.push(new TypeChainEntry({
6,717✔
659
            name: this.tokens.name?.text,
8,529!
660
            type: result,
661
            data: options.data,
662
            location: this.tokens.name?.location ?? this.location,
17,058!
663
            astNode: this
664
        }));
665
        if (result ||
6,717✔
666
            options.flags & SymbolTypeFlag.typetime ||
667
            (isPrimitiveType(objType) || isCallableType(objType))) {
668
            // All types should be known at typeTime, or the obj is well known
669
            return result;
6,671✔
670
        }
671
        // It is possible at runtime that a value has been added dynamically to an object, or something
672
        // TODO: maybe have a strict flag on this?
673
        return DynamicType.instance;
46✔
674
    }
675

676
    getName(parseMode: ParseMode) {
677
        return util.getAllDottedGetPartsAsString(this, parseMode);
35✔
678
    }
679

680
    get leadingTrivia(): Token[] {
681
        return this.obj.leadingTrivia;
15,538✔
682
    }
683

684
    public clone() {
685
        return this.finalizeClone(
7✔
686
            new DottedGetExpression({
687
                obj: this.obj?.clone(),
21✔
688
                dot: util.cloneToken(this.tokens.dot),
689
                name: util.cloneToken(this.tokens.name)
690
            }),
691
            ['obj']
692
        );
693
    }
694
}
695

696
export class XmlAttributeGetExpression extends Expression {
1✔
697
    constructor(options: {
698
        obj: Expression;
699
        /**
700
         * Can either be `@`, or `?@` for optional chaining - defaults to '@'
701
         */
702
        at?: Token;
703
        name: Identifier;
704
    }) {
705
        super();
14✔
706
        this.obj = options.obj;
14✔
707
        this.tokens = { at: options.at, name: options.name };
14✔
708
        this.location = util.createBoundingLocation(this.obj, this.tokens.at, this.tokens.name);
14✔
709
    }
710

711
    public readonly kind = AstNodeKind.XmlAttributeGetExpression;
14✔
712

713
    public readonly tokens: {
714
        name: Identifier;
715
        at?: Token;
716
    };
717

718
    public readonly obj: Expression;
719

720
    public readonly location: Location | undefined;
721

722
    transpile(state: BrsTranspileState) {
723
        return [
3✔
724
            ...this.obj.transpile(state),
725
            state.transpileToken(this.tokens.at, '@'),
726
            state.transpileToken(this.tokens.name)
727
        ];
728
    }
729

730
    walk(visitor: WalkVisitor, options: WalkOptions) {
731
        if (options.walkMode & InternalWalkMode.walkExpressions) {
32!
732
            walk(this, 'obj', visitor, options);
32✔
733
        }
734
    }
735

736
    get leadingTrivia(): Token[] {
737
        return this.obj.leadingTrivia;
21✔
738
    }
739

740
    public clone() {
741
        return this.finalizeClone(
2✔
742
            new XmlAttributeGetExpression({
743
                obj: this.obj?.clone(),
6✔
744
                at: util.cloneToken(this.tokens.at),
745
                name: util.cloneToken(this.tokens.name)
746
            }),
747
            ['obj']
748
        );
749
    }
750
}
751

752
export class IndexedGetExpression extends Expression {
1✔
753
    constructor(options: {
754
        obj: Expression;
755
        indexes: Expression[];
756
        /**
757
         * Can either be `[` or `?[`. If `?.[` is used, this will be `[` and `optionalChainingToken` will be `?.` - defaults to '[' in transpile
758
         */
759
        openingSquare?: Token;
760
        closingSquare?: Token;
761
        questionDot?: Token;//  ? or ?.
762
    }) {
763
        super();
165✔
764
        this.tokens = {
165✔
765
            openingSquare: options.openingSquare,
766
            closingSquare: options.closingSquare,
767
            questionDot: options.questionDot
768
        };
769
        this.obj = options.obj;
165✔
770
        this.indexes = options.indexes;
165✔
771
        this.location = util.createBoundingLocation(
165✔
772
            this.obj,
773
            this.tokens.openingSquare,
774
            this.tokens.questionDot,
775
            this.tokens.openingSquare,
776
            ...this.indexes ?? [],
495✔
777
            this.tokens.closingSquare
778
        );
779
    }
780

781
    public readonly kind = AstNodeKind.IndexedGetExpression;
165✔
782

783
    public readonly obj: Expression;
784
    public readonly indexes: Expression[];
785

786
    readonly tokens: {
787
        /**
788
         * Can either be `[` or `?[`. If `?.[` is used, this will be `[` and `optionalChainingToken` will be `?.` - defaults to '[' in transpile
789
         */
790
        readonly openingSquare?: Token;
791
        readonly closingSquare?: Token;
792
        readonly questionDot?: Token; //  ? or ?.
793
    };
794

795
    public readonly location: Location | undefined;
796

797
    transpile(state: BrsTranspileState) {
798
        const result = [];
66✔
799
        result.push(
66✔
800
            ...this.obj.transpile(state),
801
            this.tokens.questionDot ? state.transpileToken(this.tokens.questionDot) : '',
66✔
802
            state.transpileToken(this.tokens.openingSquare, '[')
803
        );
804
        for (let i = 0; i < this.indexes.length; i++) {
66✔
805
            //add comma between indexes
806
            if (i > 0) {
74✔
807
                result.push(', ');
8✔
808
            }
809
            let index = this.indexes[i];
74✔
810
            result.push(
74✔
811
                ...(index?.transpile(state) ?? [])
444!
812
            );
813
        }
814
        result.push(
66✔
815
            state.transpileToken(this.tokens.closingSquare, ']')
816
        );
817
        return result;
66✔
818
    }
819

820
    walk(visitor: WalkVisitor, options: WalkOptions) {
821
        if (options.walkMode & InternalWalkMode.walkExpressions) {
576!
822
            walk(this, 'obj', visitor, options);
576✔
823
            walkArray(this.indexes, visitor, options, this);
576✔
824
        }
825
    }
826

827
    getType(options: GetTypeOptions): BscType {
828
        const objType = this.obj.getType(options);
202✔
829
        if (isArrayType(objType)) {
202✔
830
            // This is used on an array. What is the default type of that array?
831
            return objType.defaultType;
10✔
832
        }
833
        return super.getType(options);
192✔
834
    }
835

836
    get leadingTrivia(): Token[] {
837
        return this.obj.leadingTrivia;
1,059✔
838
    }
839

840
    public clone() {
841
        return this.finalizeClone(
6✔
842
            new IndexedGetExpression({
843
                obj: this.obj?.clone(),
18✔
844
                questionDot: util.cloneToken(this.tokens.questionDot),
845
                openingSquare: util.cloneToken(this.tokens.openingSquare),
846
                indexes: this.indexes?.map(x => x?.clone()),
7✔
847
                closingSquare: util.cloneToken(this.tokens.closingSquare)
848
            }),
849
            ['obj', 'indexes']
850
        );
851
    }
852
}
853

854
export class GroupingExpression extends Expression {
1✔
855
    constructor(options: {
856
        leftParen?: Token;
857
        rightParen?: Token;
858
        expression: Expression;
859
    }) {
860
        super();
59✔
861
        this.tokens = {
59✔
862
            rightParen: options.rightParen,
863
            leftParen: options.leftParen
864
        };
865
        this.expression = options.expression;
59✔
866
        this.location = util.createBoundingLocation(this.tokens.leftParen, this.expression, this.tokens.rightParen);
59✔
867
    }
868

869
    public readonly tokens: {
870
        readonly leftParen?: Token;
871
        readonly rightParen?: Token;
872
    };
873
    public readonly expression: Expression;
874

875
    public readonly kind = AstNodeKind.GroupingExpression;
59✔
876

877
    public readonly location: Location | undefined;
878

879
    transpile(state: BrsTranspileState) {
880
        if (isTypecastExpression(this.expression)) {
13✔
881
            return this.expression.transpile(state);
7✔
882
        }
883
        return [
6✔
884
            state.transpileToken(this.tokens.leftParen, '('),
885
            ...this.expression.transpile(state),
886
            state.transpileToken(this.tokens.rightParen, ')')
887
        ];
888
    }
889

890
    walk(visitor: WalkVisitor, options: WalkOptions) {
891
        if (options.walkMode & InternalWalkMode.walkExpressions) {
227!
892
            walk(this, 'expression', visitor, options);
227✔
893
        }
894
    }
895

896
    getType(options: GetTypeOptions) {
897
        return this.expression.getType(options);
79✔
898
    }
899

900
    get leadingTrivia(): Token[] {
901
        return this.tokens.leftParen?.leadingTrivia;
345!
902
    }
903

904
    public clone() {
905
        return this.finalizeClone(
2✔
906
            new GroupingExpression({
907
                leftParen: util.cloneToken(this.tokens.leftParen),
908
                expression: this.expression?.clone(),
6✔
909
                rightParen: util.cloneToken(this.tokens.rightParen)
910
            }),
911
            ['expression']
912
        );
913
    }
914
}
915

916
export class LiteralExpression extends Expression {
1✔
917
    constructor(options: {
918
        value: Token;
919
    }) {
920
        super();
7,976✔
921
        this.tokens = {
7,976✔
922
            value: options.value
923
        };
924
    }
925

926
    public readonly tokens: {
927
        readonly value: Token;
928
    };
929

930
    public readonly kind = AstNodeKind.LiteralExpression;
7,976✔
931

932
    public get location() {
933
        return this.tokens.value.location;
22,355✔
934
    }
935

936
    public getType(options?: GetTypeOptions) {
937
        return util.tokenToBscType(this.tokens.value);
3,572✔
938
    }
939

940
    transpile(state: BrsTranspileState) {
941
        let text: string;
942
        if (this.tokens.value.kind === TokenKind.TemplateStringQuasi) {
4,750✔
943
            //wrap quasis with quotes (and escape inner quotemarks)
944
            text = `"${this.tokens.value.text.replace(/"/g, '""')}"`;
28✔
945

946
        } else if (this.tokens.value.kind === TokenKind.StringLiteral) {
4,722✔
947
            text = this.tokens.value.text;
3,155✔
948
            //add trailing quotemark if it's missing. We will have already generated a diagnostic for this.
949
            if (text.endsWith('"') === false) {
3,155✔
950
                text += '"';
1✔
951
            }
952
        } else {
953
            text = this.tokens.value.text;
1,567✔
954
        }
955

956
        return [
4,750✔
957
            state.transpileToken({ ...this.tokens.value, text: text })
958
        ];
959
    }
960

961
    walk(visitor: WalkVisitor, options: WalkOptions) {
962
        //nothing to walk
963
    }
964

965
    get leadingTrivia(): Token[] {
966
        return this.tokens.value.leadingTrivia;
12,645✔
967
    }
968

969
    public clone() {
970
        return this.finalizeClone(
109✔
971
            new LiteralExpression({
972
                value: util.cloneToken(this.tokens.value)
973
            })
974
        );
975
    }
976
}
977

978
/**
979
 * The print statement can have a mix of expressions and separators. These separators represent actual output to the screen,
980
 * so this AstNode represents those separators (comma, semicolon, and whitespace)
981
 */
982
export class PrintSeparatorExpression extends Expression {
1✔
983
    constructor(options: {
984
        separator: PrintSeparatorToken;
985
    }) {
986
        super();
42✔
987
        this.tokens = {
42✔
988
            separator: options.separator
989
        };
990
        this.location = this.tokens.separator.location;
42✔
991
    }
992

993
    public readonly tokens: {
994
        readonly separator: PrintSeparatorToken;
995
    };
996

997
    public readonly kind = AstNodeKind.PrintSeparatorExpression;
42✔
998

999
    public location: Location;
1000

1001
    transpile(state: BrsTranspileState) {
1002
        return [
26✔
1003
            ...this.tokens.separator.leadingWhitespace ?? [],
78!
1004
            ...state.transpileToken(this.tokens.separator)
1005
        ];
1006
    }
1007

1008
    walk(visitor: WalkVisitor, options: WalkOptions) {
1009
        //nothing to walk
1010
    }
1011

1012
    get leadingTrivia(): Token[] {
1013
        return this.tokens.separator.leadingTrivia;
166✔
1014
    }
1015

1016
    public clone() {
UNCOV
1017
        return new PrintSeparatorExpression({
×
1018
            separator: util.cloneToken(this.tokens?.separator)
×
1019
        });
1020
    }
1021
}
1022

1023

1024
/**
1025
 * This is a special expression only used within template strings. It exists so we can prevent producing lots of empty strings
1026
 * during template string transpile by identifying these expressions explicitly and skipping the bslib_toString around them
1027
 */
1028
export class EscapedCharCodeLiteralExpression extends Expression {
1✔
1029
    constructor(options: {
1030
        value: Token & { charCode: number };
1031
    }) {
1032
        super();
35✔
1033
        this.tokens = { value: options.value };
35✔
1034
        this.location = util.cloneLocation(this.tokens.value.location);
35✔
1035
    }
1036

1037
    public readonly kind = AstNodeKind.EscapedCharCodeLiteralExpression;
35✔
1038

1039
    public readonly tokens: {
1040
        readonly value: Token & { charCode: number };
1041
    };
1042

1043
    public readonly location: Location;
1044

1045
    transpile(state: BrsTranspileState) {
1046
        return [
13✔
1047
            state.sourceNode(this, `chr(${this.tokens.value.charCode})`)
1048
        ];
1049
    }
1050

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

1055
    public clone() {
1056
        return this.finalizeClone(
3✔
1057
            new EscapedCharCodeLiteralExpression({
1058
                value: util.cloneToken(this.tokens.value)
1059
            })
1060
        );
1061
    }
1062
}
1063

1064
export class ArrayLiteralExpression extends Expression {
1✔
1065
    constructor(options: {
1066
        elements: Array<Expression>;
1067
        open?: Token;
1068
        close?: Token;
1069
    }) {
1070
        super();
169✔
1071
        this.tokens = {
169✔
1072
            open: options.open,
1073
            close: options.close
1074
        };
1075
        this.elements = options.elements;
169✔
1076
        this.location = util.createBoundingLocation(this.tokens.open, ...this.elements ?? [], this.tokens.close);
169✔
1077
    }
1078

1079
    public readonly elements: Array<Expression>;
1080

1081
    public readonly tokens: {
1082
        readonly open?: Token;
1083
        readonly close?: Token;
1084
    };
1085

1086
    public readonly kind = AstNodeKind.ArrayLiteralExpression;
169✔
1087

1088
    public readonly location: Location | undefined;
1089

1090
    transpile(state: BrsTranspileState) {
1091
        let result: TranspileResult = [];
60✔
1092
        result.push(
60✔
1093
            state.transpileToken(this.tokens.open, '[')
1094
        );
1095
        let hasChildren = this.elements.length > 0;
60✔
1096
        state.blockDepth++;
60✔
1097

1098
        for (let i = 0; i < this.elements.length; i++) {
60✔
1099
            let previousElement = this.elements[i - 1];
65✔
1100
            let element = this.elements[i];
65✔
1101

1102
            if (util.isLeadingCommentOnSameLine(previousElement ?? this.tokens.open, element)) {
65✔
1103
                result.push(' ');
3✔
1104
            } else {
1105
                result.push(
62✔
1106
                    '\n',
1107
                    state.indent()
1108
                );
1109
            }
1110
            result.push(
65✔
1111
                ...element.transpile(state)
1112
            );
1113
        }
1114
        state.blockDepth--;
60✔
1115
        //add a newline between open and close if there are elements
1116
        const lastLocatable = this.elements[this.elements.length - 1] ?? this.tokens.open;
60✔
1117
        result.push(...state.transpileEndBlockToken(lastLocatable, this.tokens.close, ']', hasChildren));
60✔
1118

1119
        return result;
60✔
1120
    }
1121

1122
    walk(visitor: WalkVisitor, options: WalkOptions) {
1123
        if (options.walkMode & InternalWalkMode.walkExpressions) {
879!
1124
            walkArray(this.elements, visitor, options, this);
879✔
1125
        }
1126
    }
1127

1128
    getType(options: GetTypeOptions): BscType {
1129
        const innerTypes = this.elements.map(expr => expr.getType(options));
271✔
1130
        return new ArrayType(...innerTypes);
193✔
1131
    }
1132
    get leadingTrivia(): Token[] {
1133
        return this.tokens.open?.leadingTrivia;
577!
1134
    }
1135

1136
    get endTrivia(): Token[] {
1137
        return this.tokens.close?.leadingTrivia;
2!
1138
    }
1139

1140
    public clone() {
1141
        return this.finalizeClone(
4✔
1142
            new ArrayLiteralExpression({
1143
                elements: this.elements?.map(e => e?.clone()),
6✔
1144
                open: util.cloneToken(this.tokens.open),
1145
                close: util.cloneToken(this.tokens.close)
1146
            }),
1147
            ['elements']
1148
        );
1149
    }
1150
}
1151

1152
export class AAMemberExpression extends Expression {
1✔
1153
    constructor(options: {
1154
        key: Token;
1155
        colon?: Token;
1156
        /** The expression evaluated to determine the member's initial value. */
1157
        value: Expression;
1158
        comma?: Token;
1159
    }) {
1160
        super();
305✔
1161
        this.tokens = {
305✔
1162
            key: options.key,
1163
            colon: options.colon,
1164
            comma: options.comma
1165
        };
1166
        this.value = options.value;
305✔
1167
        this.location = util.createBoundingLocation(this.tokens.key, this.tokens.colon, this.value);
305✔
1168
    }
1169

1170
    public readonly kind = AstNodeKind.AAMemberExpression;
305✔
1171

1172
    public readonly location: Location | undefined;
1173

1174
    public readonly tokens: {
1175
        readonly key: Token;
1176
        readonly colon?: Token;
1177
        readonly comma?: Token;
1178
    };
1179

1180
    /** The expression evaluated to determine the member's initial value. */
1181
    public readonly value: Expression;
1182

1183
    transpile(state: BrsTranspileState) {
1184
        //TODO move the logic from AALiteralExpression loop into this function
UNCOV
1185
        return [];
×
1186
    }
1187

1188
    walk(visitor: WalkVisitor, options: WalkOptions) {
1189
        walk(this, 'value', visitor, options);
1,142✔
1190
    }
1191

1192
    getType(options: GetTypeOptions): BscType {
1193
        return this.value.getType(options);
230✔
1194
    }
1195

1196
    get leadingTrivia(): Token[] {
1197
        return this.tokens.key.leadingTrivia;
823✔
1198
    }
1199

1200
    public clone() {
1201
        return this.finalizeClone(
4✔
1202
            new AAMemberExpression({
1203
                key: util.cloneToken(this.tokens.key),
1204
                colon: util.cloneToken(this.tokens.colon),
1205
                value: this.value?.clone()
12✔
1206
            }),
1207
            ['value']
1208
        );
1209
    }
1210
}
1211

1212
export class AALiteralExpression extends Expression {
1✔
1213
    constructor(options: {
1214
        elements: Array<AAMemberExpression>;
1215
        open?: Token;
1216
        close?: Token;
1217
    }) {
1218
        super();
294✔
1219
        this.tokens = {
294✔
1220
            open: options.open,
1221
            close: options.close
1222
        };
1223
        this.elements = options.elements;
294✔
1224
        this.location = util.createBoundingLocation(this.tokens.open, ...this.elements ?? [], this.tokens.close);
294✔
1225
    }
1226

1227
    public readonly elements: Array<AAMemberExpression>;
1228
    public readonly tokens: {
1229
        readonly open?: Token;
1230
        readonly close?: Token;
1231
    };
1232

1233
    public readonly kind = AstNodeKind.AALiteralExpression;
294✔
1234

1235
    public readonly location: Location | undefined;
1236

1237
    transpile(state: BrsTranspileState) {
1238
        let result: TranspileResult = [];
61✔
1239
        //open curly
1240
        result.push(
61✔
1241
            state.transpileToken(this.tokens.open, '{')
1242
        );
1243
        let hasChildren = this.elements.length > 0;
61✔
1244
        //add newline if the object has children and the first child isn't a comment starting on the same line as opening curly
1245
        if (hasChildren && !util.isLeadingCommentOnSameLine(this.tokens.open, this.elements[0])) {
61✔
1246
            result.push('\n');
24✔
1247
        }
1248
        state.blockDepth++;
61✔
1249
        for (let i = 0; i < this.elements.length; i++) {
61✔
1250
            let element = this.elements[i];
36✔
1251
            let previousElement = this.elements[i - 1];
36✔
1252
            let nextElement = this.elements[i + 1];
36✔
1253

1254
            //don't indent if comment is same-line
1255
            if (util.isLeadingCommentOnSameLine(this.tokens.open, element) ||
36✔
1256
                util.isLeadingCommentOnSameLine(previousElement, element)) {
1257
                result.push(' ');
7✔
1258
            } else {
1259
                //indent line
1260
                result.push(state.indent());
29✔
1261
            }
1262

1263
            //key
1264
            result.push(
36✔
1265
                state.transpileToken(element.tokens.key)
1266
            );
1267
            //colon
1268
            result.push(
36✔
1269
                state.transpileToken(element.tokens.colon, ':'),
1270
                ' '
1271
            );
1272
            //value
1273
            result.push(...element.value.transpile(state));
36✔
1274

1275
            //if next element is a same-line comment, skip the newline
1276
            if (nextElement && !util.isLeadingCommentOnSameLine(element, nextElement)) {
36✔
1277
                //add a newline between statements
1278
                result.push('\n');
5✔
1279
            }
1280
        }
1281
        state.blockDepth--;
61✔
1282

1283
        const lastElement = this.elements[this.elements.length - 1] ?? this.tokens.open;
61✔
1284
        result.push(...state.transpileEndBlockToken(lastElement, this.tokens.close, '}', hasChildren));
61✔
1285

1286
        return result;
61✔
1287
    }
1288

1289
    walk(visitor: WalkVisitor, options: WalkOptions) {
1290
        if (options.walkMode & InternalWalkMode.walkExpressions) {
1,185!
1291
            walkArray(this.elements, visitor, options, this);
1,185✔
1292
        }
1293
    }
1294

1295
    getType(options: GetTypeOptions): BscType {
1296
        const resultType = new AssociativeArrayType();
225✔
1297
        resultType.addBuiltInInterfaces();
225✔
1298
        for (const element of this.elements) {
225✔
1299
            if (isAAMemberExpression(element)) {
230!
1300
                let memberName = element.tokens?.key?.text ?? '';
230!
1301
                if (element.tokens.key.kind === TokenKind.StringLiteral) {
230✔
1302
                    memberName = memberName.replace(/"/g, ''); // remove quotes if it was a stringLiteral
6✔
1303
                }
1304
                if (memberName) {
230!
1305
                    resultType.addMember(memberName, { definingNode: element }, element.getType(options), SymbolTypeFlag.runtime);
230✔
1306
                }
1307
            }
1308
        }
1309
        return resultType;
225✔
1310
    }
1311

1312
    public get leadingTrivia(): Token[] {
1313
        return this.tokens.open?.leadingTrivia;
806!
1314
    }
1315

1316
    public get endTrivia(): Token[] {
1317
        return this.tokens.close?.leadingTrivia;
1!
1318
    }
1319

1320
    public clone() {
1321
        return this.finalizeClone(
6✔
1322
            new AALiteralExpression({
1323
                elements: this.elements?.map(e => e?.clone()),
5✔
1324
                open: util.cloneToken(this.tokens.open),
1325
                close: util.cloneToken(this.tokens.close)
1326
            }),
1327
            ['elements']
1328
        );
1329
    }
1330
}
1331

1332
export class UnaryExpression extends Expression {
1✔
1333
    constructor(options: {
1334
        operator: Token;
1335
        right: Expression;
1336
    }) {
1337
        super();
66✔
1338
        this.tokens = {
66✔
1339
            operator: options.operator
1340
        };
1341
        this.right = options.right;
66✔
1342
        this.location = util.createBoundingLocation(this.tokens.operator, this.right);
66✔
1343
    }
1344

1345
    public readonly kind = AstNodeKind.UnaryExpression;
66✔
1346

1347
    public readonly location: Location | undefined;
1348

1349
    public readonly tokens: {
1350
        readonly operator: Token;
1351
    };
1352
    public readonly right: Expression;
1353

1354
    transpile(state: BrsTranspileState) {
1355
        let separatingWhitespace: string | undefined;
1356
        if (isVariableExpression(this.right)) {
12✔
1357
            separatingWhitespace = this.right.tokens.name.leadingWhitespace;
6✔
1358
        } else if (isLiteralExpression(this.right)) {
6✔
1359
            separatingWhitespace = this.right.tokens.value.leadingWhitespace;
2✔
1360
        } else {
1361
            separatingWhitespace = ' ';
4✔
1362
        }
1363

1364
        return [
12✔
1365
            state.transpileToken(this.tokens.operator),
1366
            separatingWhitespace,
1367
            ...this.right.transpile(state)
1368
        ];
1369
    }
1370

1371
    walk(visitor: WalkVisitor, options: WalkOptions) {
1372
        if (options.walkMode & InternalWalkMode.walkExpressions) {
247!
1373
            walk(this, 'right', visitor, options);
247✔
1374
        }
1375
    }
1376

1377
    getType(options: GetTypeOptions): BscType {
1378
        return util.unaryOperatorResultType(this.tokens.operator, this.right.getType(options));
48✔
1379
    }
1380

1381
    public get leadingTrivia(): Token[] {
1382
        return this.tokens.operator.leadingTrivia;
187✔
1383
    }
1384

1385
    public clone() {
1386
        return this.finalizeClone(
2✔
1387
            new UnaryExpression({
1388
                operator: util.cloneToken(this.tokens.operator),
1389
                right: this.right?.clone()
6✔
1390
            }),
1391
            ['right']
1392
        );
1393
    }
1394
}
1395

1396
export class VariableExpression extends Expression {
1✔
1397
    constructor(options: {
1398
        name: Identifier;
1399
    }) {
1400
        super();
11,482✔
1401
        this.tokens = {
11,482✔
1402
            name: options.name
1403
        };
1404
        this.location = util.cloneLocation(this.tokens.name?.location);
11,482!
1405
    }
1406

1407
    public readonly tokens: {
1408
        readonly name: Identifier;
1409
    };
1410

1411
    public readonly kind = AstNodeKind.VariableExpression;
11,482✔
1412

1413
    public readonly location: Location;
1414

1415
    public getName(parseMode?: ParseMode) {
1416
        return this.tokens.name.text;
25,001✔
1417
    }
1418

1419
    transpile(state: BrsTranspileState) {
1420
        let result: TranspileResult = [];
6,561✔
1421
        const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
6,561✔
1422
        //if the callee is the name of a known namespace function
1423
        if (namespace && util.isCalleeMemberOfNamespace(this.tokens.name.text, this, namespace)) {
6,561✔
1424
            result.push(
17✔
1425
                //transpile leading comments since the token isn't being transpiled directly
1426
                ...state.transpileLeadingCommentsForAstNode(this),
1427
                state.sourceNode(this, [
1428
                    namespace.getName(ParseMode.BrightScript),
1429
                    '_',
1430
                    this.getName(ParseMode.BrightScript)
1431
                ])
1432
            );
1433
            //transpile  normally
1434
        } else {
1435
            result.push(
6,544✔
1436
                state.transpileToken(this.tokens.name)
1437
            );
1438
        }
1439
        return result;
6,561✔
1440
    }
1441

1442
    walk(visitor: WalkVisitor, options: WalkOptions) {
1443
        //nothing to walk
1444
    }
1445

1446

1447
    getType(options: GetTypeOptions) {
1448
        let resultType: BscType = util.tokenToBscType(this.tokens.name);
24,854✔
1449
        const nameKey = this.getName();
24,854✔
1450
        if (!resultType) {
24,854✔
1451
            const symbolTable = this.getSymbolTable();
19,233✔
1452
            resultType = symbolTable?.getSymbolType(nameKey, { ...options, fullName: nameKey, tableProvider: () => this.getSymbolTable() });
40,631!
1453

1454
            if (util.isClassUsedAsFunction(resultType, this, options)) {
19,233✔
1455
                resultType = FunctionType.instance;
20✔
1456
            }
1457

1458
        }
1459
        options?.typeChain?.push(new TypeChainEntry({ name: nameKey, type: resultType, data: options.data, astNode: this }));
24,854!
1460
        return resultType;
24,854✔
1461
    }
1462

1463
    get leadingTrivia(): Token[] {
1464
        return this.tokens.name.leadingTrivia;
40,206✔
1465
    }
1466

1467
    public clone() {
1468
        return this.finalizeClone(
70✔
1469
            new VariableExpression({
1470
                name: util.cloneToken(this.tokens.name)
1471
            })
1472
        );
1473
    }
1474
}
1475

1476
export class SourceLiteralExpression extends Expression {
1✔
1477
    constructor(options: {
1478
        value: Token;
1479
    }) {
1480
        super();
37✔
1481
        this.tokens = {
37✔
1482
            value: options.value
1483
        };
1484
        this.location = util.cloneLocation(this.tokens.value?.location);
37!
1485
    }
1486

1487
    public readonly location: Location;
1488

1489
    public readonly kind = AstNodeKind.SourceLiteralExpression;
37✔
1490

1491
    public readonly tokens: {
1492
        readonly value: Token;
1493
    };
1494

1495
    /**
1496
     * Find the index of the function in its parent
1497
     */
1498
    private findFunctionIndex(parentFunction: FunctionExpression, func: FunctionExpression) {
1499
        let index = -1;
4✔
1500
        parentFunction.findChild((node) => {
4✔
1501
            if (isFunctionExpression(node)) {
12✔
1502
                index++;
4✔
1503
                if (node === func) {
4!
1504
                    return true;
4✔
1505
                }
1506
            }
1507
        }, {
1508
            walkMode: WalkMode.visitAllRecursive
1509
        });
1510
        return index;
4✔
1511
    }
1512

1513
    private getFunctionName(state: BrsTranspileState, parseMode: ParseMode) {
1514
        let func = this.findAncestor<FunctionExpression>(isFunctionExpression);
8✔
1515
        let nameParts = [] as TranspileResult;
8✔
1516
        let parentFunction: FunctionExpression;
1517
        while ((parentFunction = func.findAncestor<FunctionExpression>(isFunctionExpression))) {
8✔
1518
            let index = this.findFunctionIndex(parentFunction, func);
4✔
1519
            nameParts.unshift(`anon${index}`);
4✔
1520
            func = parentFunction;
4✔
1521
        }
1522
        //get the index of this function in its parent
1523
        if (isFunctionStatement(func.parent)) {
8!
1524
            nameParts.unshift(
8✔
1525
                func.parent.getName(parseMode)
1526
            );
1527
        }
1528
        return nameParts.join('$');
8✔
1529
    }
1530

1531
    /**
1532
     * Get the line number from our token or from the closest ancestor that has a range
1533
     */
1534
    private getClosestLineNumber() {
1535
        let node: AstNode = this;
7✔
1536
        while (node) {
7✔
1537
            if (node.location?.range) {
17✔
1538
                return node.location.range.start.line + 1;
5✔
1539
            }
1540
            node = node.parent;
12✔
1541
        }
1542
        return -1;
2✔
1543
    }
1544

1545
    transpile(state: BrsTranspileState) {
1546
        let text: string;
1547
        switch (this.tokens.value.kind) {
31✔
1548
            case TokenKind.SourceFilePathLiteral:
40!
1549
                const pathUrl = fileUrl(state.srcPath);
3✔
1550
                text = `"${pathUrl.substring(0, 4)}" + "${pathUrl.substring(4)}"`;
3✔
1551
                break;
3✔
1552
            case TokenKind.SourceLineNumLiteral:
1553
                //TODO find first parent that has range, or default to -1
1554
                text = `${this.getClosestLineNumber()}`;
4✔
1555
                break;
4✔
1556
            case TokenKind.FunctionNameLiteral:
1557
                text = `"${this.getFunctionName(state, ParseMode.BrightScript)}"`;
4✔
1558
                break;
4✔
1559
            case TokenKind.SourceFunctionNameLiteral:
1560
                text = `"${this.getFunctionName(state, ParseMode.BrighterScript)}"`;
4✔
1561
                break;
4✔
1562
            case TokenKind.SourceNamespaceNameLiteral:
UNCOV
1563
                let namespaceParts = this.getFunctionName(state, ParseMode.BrighterScript).split('.');
×
UNCOV
1564
                namespaceParts.pop(); // remove the function name
×
1565

UNCOV
1566
                text = `"${namespaceParts.join('.')}"`;
×
UNCOV
1567
                break;
×
1568
            case TokenKind.SourceNamespaceRootNameLiteral:
UNCOV
1569
                let namespaceRootParts = this.getFunctionName(state, ParseMode.BrighterScript).split('.');
×
UNCOV
1570
                namespaceRootParts.pop(); // remove the function name
×
1571

UNCOV
1572
                let rootNamespace = namespaceRootParts.shift() ?? '';
×
UNCOV
1573
                text = `"${rootNamespace}"`;
×
UNCOV
1574
                break;
×
1575
            case TokenKind.SourceLocationLiteral:
1576
                const locationUrl = fileUrl(state.srcPath);
3✔
1577
                //TODO find first parent that has range, or default to -1
1578
                text = `"${locationUrl.substring(0, 4)}" + "${locationUrl.substring(4)}:${this.getClosestLineNumber()}"`;
3✔
1579
                break;
3✔
1580
            case TokenKind.PkgPathLiteral:
1581
                text = `"${util.sanitizePkgPath(state.file.pkgPath)}"`;
2✔
1582
                break;
2✔
1583
            case TokenKind.PkgLocationLiteral:
1584
                text = `"${util.sanitizePkgPath(state.file.pkgPath)}:" + str(LINE_NUM)`;
2✔
1585
                break;
2✔
1586
            case TokenKind.LineNumLiteral:
1587
            default:
1588
                //use the original text (because it looks like a variable)
1589
                text = this.tokens.value.text;
9✔
1590
                break;
9✔
1591

1592
        }
1593
        return [
31✔
1594
            state.sourceNode(this, text)
1595
        ];
1596
    }
1597

1598
    walk(visitor: WalkVisitor, options: WalkOptions) {
1599
        //nothing to walk
1600
    }
1601

1602
    get leadingTrivia(): Token[] {
1603
        return this.tokens.value.leadingTrivia;
200✔
1604
    }
1605

1606
    public clone() {
1607
        return this.finalizeClone(
1✔
1608
            new SourceLiteralExpression({
1609
                value: util.cloneToken(this.tokens.value)
1610
            })
1611
        );
1612
    }
1613
}
1614

1615
/**
1616
 * This expression transpiles and acts exactly like a CallExpression,
1617
 * except we need to uniquely identify these statements so we can
1618
 * do more type checking.
1619
 */
1620
export class NewExpression extends Expression {
1✔
1621
    constructor(options: {
1622
        new?: Token;
1623
        call: CallExpression;
1624
    }) {
1625
        super();
140✔
1626
        this.tokens = {
140✔
1627
            new: options.new
1628
        };
1629
        this.call = options.call;
140✔
1630
        this.location = util.createBoundingLocation(this.tokens.new, this.call);
140✔
1631
    }
1632

1633
    public readonly kind = AstNodeKind.NewExpression;
140✔
1634

1635
    public readonly location: Location | undefined;
1636

1637
    public readonly tokens: {
1638
        readonly new?: Token;
1639
    };
1640
    public readonly call: CallExpression;
1641

1642
    /**
1643
     * The name of the class to initialize (with optional namespace prefixed)
1644
     */
1645
    public get className() {
1646
        //the parser guarantees the callee of a new statement's call object will be
1647
        //either a VariableExpression or a DottedGet
1648
        return this.call.callee as (VariableExpression | DottedGetExpression);
92✔
1649
    }
1650

1651
    public transpile(state: BrsTranspileState) {
1652
        const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
15✔
1653
        const cls = state.file.getClassFileLink(
15✔
1654
            this.className.getName(ParseMode.BrighterScript),
1655
            namespace?.getName(ParseMode.BrighterScript)
45✔
1656
        )?.item;
15✔
1657
        //new statements within a namespace block can omit the leading namespace if the class resides in that same namespace.
1658
        //So we need to figure out if this is a namespace-omitted class, or if this class exists without a namespace.
1659
        return this.call.transpile(state, cls?.getName(ParseMode.BrightScript));
15✔
1660
    }
1661

1662
    walk(visitor: WalkVisitor, options: WalkOptions) {
1663
        if (options.walkMode & InternalWalkMode.walkExpressions) {
876!
1664
            walk(this, 'call', visitor, options);
876✔
1665
        }
1666
    }
1667

1668
    getType(options: GetTypeOptions) {
1669
        const result = this.call.getType(options);
350✔
1670
        if (options.typeChain) {
350✔
1671
            // modify last typechain entry to show it is a new ...()
1672
            const lastEntry = options.typeChain[options.typeChain.length - 1];
3✔
1673
            if (lastEntry) {
3!
1674
                lastEntry.astNode = this;
3✔
1675
            }
1676
        }
1677
        return result;
350✔
1678
    }
1679

1680
    get leadingTrivia(): Token[] {
1681
        return this.tokens.new.leadingTrivia;
611✔
1682
    }
1683

1684
    public clone() {
1685
        return this.finalizeClone(
2✔
1686
            new NewExpression({
1687
                new: util.cloneToken(this.tokens.new),
1688
                call: this.call?.clone()
6✔
1689
            }),
1690
            ['call']
1691
        );
1692
    }
1693
}
1694

1695
export class CallfuncExpression extends Expression {
1✔
1696
    constructor(options: {
1697
        callee: Expression;
1698
        operator?: Token;
1699
        methodName: Identifier;
1700
        openingParen?: Token;
1701
        args?: Expression[];
1702
        closingParen?: Token;
1703
    }) {
1704
        super();
61✔
1705
        this.tokens = {
61✔
1706
            operator: options.operator,
1707
            methodName: options.methodName,
1708
            openingParen: options.openingParen,
1709
            closingParen: options.closingParen
1710
        };
1711
        this.callee = options.callee;
61✔
1712
        this.args = options.args ?? [];
61✔
1713

1714
        this.location = util.createBoundingLocation(
61✔
1715
            this.callee,
1716
            this.tokens.operator,
1717
            this.tokens.methodName,
1718
            this.tokens.openingParen,
1719
            ...this.args ?? [],
183!
1720
            this.tokens.closingParen
1721
        );
1722
    }
1723

1724
    public readonly callee: Expression;
1725
    public readonly args: Expression[];
1726

1727
    public readonly tokens: {
1728
        readonly operator: Token;
1729
        readonly methodName: Identifier;
1730
        readonly openingParen?: Token;
1731
        readonly closingParen?: Token;
1732
    };
1733

1734
    public readonly kind = AstNodeKind.CallfuncExpression;
61✔
1735

1736
    public readonly location: Location | undefined;
1737

1738
    public transpile(state: BrsTranspileState) {
1739
        let result = [] as TranspileResult;
9✔
1740
        result.push(
9✔
1741
            ...this.callee.transpile(state),
1742
            state.sourceNode(this.tokens.operator, '.callfunc'),
1743
            state.transpileToken(this.tokens.openingParen, '('),
1744
            //the name of the function
1745
            state.sourceNode(this.tokens.methodName, ['"', this.tokens.methodName.text, '"'])
1746
        );
1747
        if (this.args?.length > 0) {
9!
1748
            result.push(', ');
4✔
1749
            //transpile args
1750
            for (let i = 0; i < this.args.length; i++) {
4✔
1751
                //add comma between args
1752
                if (i > 0) {
7✔
1753
                    result.push(', ');
3✔
1754
                }
1755
                let arg = this.args[i];
7✔
1756
                result.push(...arg.transpile(state));
7✔
1757
            }
1758
        } else if (state.options.legacyCallfuncHandling) {
5✔
1759
            result.push(', ', 'invalid');
2✔
1760
        }
1761

1762
        result.push(
9✔
1763
            state.transpileToken(this.tokens.closingParen, ')')
1764
        );
1765
        return result;
9✔
1766
    }
1767

1768
    walk(visitor: WalkVisitor, options: WalkOptions) {
1769
        if (options.walkMode & InternalWalkMode.walkExpressions) {
322!
1770
            walk(this, 'callee', visitor, options);
322✔
1771
            walkArray(this.args, visitor, options, this);
322✔
1772
        }
1773
    }
1774

1775
    getType(options: GetTypeOptions) {
1776
        const result = util.getCallFuncType(this, this.tokens.methodName, options) ?? DynamicType.instance;
23✔
1777
        return result;
23✔
1778
    }
1779

1780
    get leadingTrivia(): Token[] {
1781
        return this.callee.leadingTrivia;
476✔
1782
    }
1783

1784
    public clone() {
1785
        return this.finalizeClone(
3✔
1786
            new CallfuncExpression({
1787
                callee: this.callee?.clone(),
9✔
1788
                operator: util.cloneToken(this.tokens.operator),
1789
                methodName: util.cloneToken(this.tokens.methodName),
1790
                openingParen: util.cloneToken(this.tokens.openingParen),
1791
                args: this.args?.map(e => e?.clone()),
2✔
1792
                closingParen: util.cloneToken(this.tokens.closingParen)
1793
            }),
1794
            ['callee', 'args']
1795
        );
1796
    }
1797
}
1798

1799
/**
1800
 * Since template strings can contain newlines, we need to concatenate multiple strings together with chr() calls.
1801
 * This is a single expression that represents the string contatenation of all parts of a single quasi.
1802
 */
1803
export class TemplateStringQuasiExpression extends Expression {
1✔
1804
    constructor(options: {
1805
        expressions: Array<LiteralExpression | EscapedCharCodeLiteralExpression>;
1806
    }) {
1807
        super();
108✔
1808
        this.expressions = options.expressions;
108✔
1809
        this.location = util.createBoundingLocation(
108✔
1810
            ...this.expressions ?? []
324✔
1811
        );
1812
    }
1813

1814
    public readonly expressions: Array<LiteralExpression | EscapedCharCodeLiteralExpression>;
1815
    public readonly kind = AstNodeKind.TemplateStringQuasiExpression;
108✔
1816

1817
    readonly location: Location | undefined;
1818

1819
    transpile(state: BrsTranspileState, skipEmptyStrings = true) {
35✔
1820
        let result = [] as TranspileResult;
43✔
1821
        let plus = '';
43✔
1822
        for (let expression of this.expressions) {
43✔
1823
            //skip empty strings
1824
            //TODO what does an empty string literal expression look like?
1825
            if (expression.tokens.value.text === '' && skipEmptyStrings === true) {
68✔
1826
                continue;
27✔
1827
            }
1828
            result.push(
41✔
1829
                plus,
1830
                ...expression.transpile(state)
1831
            );
1832
            plus = ' + ';
41✔
1833
        }
1834
        return result;
43✔
1835
    }
1836

1837
    walk(visitor: WalkVisitor, options: WalkOptions) {
1838
        if (options.walkMode & InternalWalkMode.walkExpressions) {
370!
1839
            walkArray(this.expressions, visitor, options, this);
370✔
1840
        }
1841
    }
1842

1843
    public clone() {
1844
        return this.finalizeClone(
15✔
1845
            new TemplateStringQuasiExpression({
1846
                expressions: this.expressions?.map(e => e?.clone())
20✔
1847
            }),
1848
            ['expressions']
1849
        );
1850
    }
1851
}
1852

1853
export class TemplateStringExpression extends Expression {
1✔
1854
    constructor(options: {
1855
        openingBacktick?: Token;
1856
        quasis: TemplateStringQuasiExpression[];
1857
        expressions: Expression[];
1858
        closingBacktick?: Token;
1859
    }) {
1860
        super();
49✔
1861
        this.tokens = {
49✔
1862
            openingBacktick: options.openingBacktick,
1863
            closingBacktick: options.closingBacktick
1864
        };
1865
        this.quasis = options.quasis;
49✔
1866
        this.expressions = options.expressions;
49✔
1867
        this.location = util.createBoundingLocation(
49✔
1868
            this.tokens.openingBacktick,
1869
            this.quasis?.[0],
147✔
1870
            this.quasis?.[this.quasis?.length - 1],
291!
1871
            this.tokens.closingBacktick
1872
        );
1873
    }
1874

1875
    public readonly kind = AstNodeKind.TemplateStringExpression;
49✔
1876

1877
    public readonly tokens: {
1878
        readonly openingBacktick?: Token;
1879
        readonly closingBacktick?: Token;
1880
    };
1881
    public readonly quasis: TemplateStringQuasiExpression[];
1882
    public readonly expressions: Expression[];
1883

1884
    public readonly location: Location | undefined;
1885

1886
    public getType(options: GetTypeOptions) {
1887
        return StringType.instance;
34✔
1888
    }
1889

1890
    transpile(state: BrsTranspileState) {
1891
        if (this.quasis.length === 1 && this.expressions.length === 0) {
20✔
1892
            return this.quasis[0].transpile(state);
10✔
1893
        }
1894
        let result = ['('];
10✔
1895
        let plus = '';
10✔
1896
        //helper function to figure out when to include the plus
1897
        function add(...items) {
1898
            if (items.length > 0) {
40✔
1899
                result.push(
29✔
1900
                    plus,
1901
                    ...items
1902
                );
1903
            }
1904
            //set the plus after the first occurance of a nonzero length set of items
1905
            if (plus === '' && items.length > 0) {
40✔
1906
                plus = ' + ';
10✔
1907
            }
1908
        }
1909

1910
        for (let i = 0; i < this.quasis.length; i++) {
10✔
1911
            let quasi = this.quasis[i];
25✔
1912
            let expression = this.expressions[i];
25✔
1913

1914
            add(
25✔
1915
                ...quasi.transpile(state)
1916
            );
1917
            if (expression) {
25✔
1918
                //skip the toString wrapper around certain expressions
1919
                if (
15✔
1920
                    isEscapedCharCodeLiteralExpression(expression) ||
34✔
1921
                    (isLiteralExpression(expression) && isStringType(expression.getType()))
1922
                ) {
1923
                    add(
3✔
1924
                        ...expression.transpile(state)
1925
                    );
1926

1927
                    //wrap all other expressions with a bslib_toString call to prevent runtime type mismatch errors
1928
                } else {
1929
                    add(
12✔
1930
                        state.bslibPrefix + '_toString(',
1931
                        ...expression.transpile(state),
1932
                        ')'
1933
                    );
1934
                }
1935
            }
1936
        }
1937
        //the expression should be wrapped in parens so it can be used line a single expression at runtime
1938
        result.push(')');
10✔
1939

1940
        return result;
10✔
1941
    }
1942

1943
    walk(visitor: WalkVisitor, options: WalkOptions) {
1944
        if (options.walkMode & InternalWalkMode.walkExpressions) {
180!
1945
            //walk the quasis and expressions in left-to-right order
1946
            for (let i = 0; i < this.quasis?.length; i++) {
180!
1947
                walk(this.quasis, i, visitor, options, this);
306✔
1948

1949
                //this skips the final loop iteration since we'll always have one more quasi than expression
1950
                if (this.expressions[i]) {
306✔
1951
                    walk(this.expressions, i, visitor, options, this);
126✔
1952
                }
1953
            }
1954
        }
1955
    }
1956

1957
    public clone() {
1958
        return this.finalizeClone(
7✔
1959
            new TemplateStringExpression({
1960
                openingBacktick: util.cloneToken(this.tokens.openingBacktick),
1961
                quasis: this.quasis?.map(e => e?.clone()),
12✔
1962
                expressions: this.expressions?.map(e => e?.clone()),
6✔
1963
                closingBacktick: util.cloneToken(this.tokens.closingBacktick)
1964
            }),
1965
            ['quasis', 'expressions']
1966
        );
1967
    }
1968
}
1969

1970
export class TaggedTemplateStringExpression extends Expression {
1✔
1971
    constructor(options: {
1972
        tagName: Identifier;
1973
        openingBacktick?: Token;
1974
        quasis: TemplateStringQuasiExpression[];
1975
        expressions: Expression[];
1976
        closingBacktick?: Token;
1977
    }) {
1978
        super();
12✔
1979
        this.tokens = {
12✔
1980
            tagName: options.tagName,
1981
            openingBacktick: options.openingBacktick,
1982
            closingBacktick: options.closingBacktick
1983
        };
1984
        this.quasis = options.quasis;
12✔
1985
        this.expressions = options.expressions;
12✔
1986

1987
        this.location = util.createBoundingLocation(
12✔
1988
            this.tokens.tagName,
1989
            this.tokens.openingBacktick,
1990
            this.quasis?.[0],
36✔
1991
            this.quasis?.[this.quasis?.length - 1],
69!
1992
            this.tokens.closingBacktick
1993
        );
1994
    }
1995

1996
    public readonly kind = AstNodeKind.TaggedTemplateStringExpression;
12✔
1997

1998
    public readonly tokens: {
1999
        readonly tagName: Identifier;
2000
        readonly openingBacktick?: Token;
2001
        readonly closingBacktick?: Token;
2002
    };
2003

2004
    public readonly quasis: TemplateStringQuasiExpression[];
2005
    public readonly expressions: Expression[];
2006

2007
    public readonly location: Location | undefined;
2008

2009
    transpile(state: BrsTranspileState) {
2010
        let result = [] as TranspileResult;
3✔
2011
        result.push(
3✔
2012
            state.transpileToken(this.tokens.tagName),
2013
            '(['
2014
        );
2015

2016
        //add quasis as the first array
2017
        for (let i = 0; i < this.quasis.length; i++) {
3✔
2018
            let quasi = this.quasis[i];
8✔
2019
            //separate items with a comma
2020
            if (i > 0) {
8✔
2021
                result.push(
5✔
2022
                    ', '
2023
                );
2024
            }
2025
            result.push(
8✔
2026
                ...quasi.transpile(state, false)
2027
            );
2028
        }
2029
        result.push(
3✔
2030
            '], ['
2031
        );
2032

2033
        //add expressions as the second array
2034
        for (let i = 0; i < this.expressions.length; i++) {
3✔
2035
            let expression = this.expressions[i];
5✔
2036
            if (i > 0) {
5✔
2037
                result.push(
2✔
2038
                    ', '
2039
                );
2040
            }
2041
            result.push(
5✔
2042
                ...expression.transpile(state)
2043
            );
2044
        }
2045
        result.push(
3✔
2046
            state.sourceNode(this.tokens.closingBacktick, '])')
2047
        );
2048
        return result;
3✔
2049
    }
2050

2051
    walk(visitor: WalkVisitor, options: WalkOptions) {
2052
        if (options.walkMode & InternalWalkMode.walkExpressions) {
28!
2053
            //walk the quasis and expressions in left-to-right order
2054
            for (let i = 0; i < this.quasis?.length; i++) {
28!
2055
                walk(this.quasis, i, visitor, options, this);
68✔
2056

2057
                //this skips the final loop iteration since we'll always have one more quasi than expression
2058
                if (this.expressions[i]) {
68✔
2059
                    walk(this.expressions, i, visitor, options, this);
40✔
2060
                }
2061
            }
2062
        }
2063
    }
2064

2065
    public clone() {
2066
        return this.finalizeClone(
3✔
2067
            new TaggedTemplateStringExpression({
2068
                tagName: util.cloneToken(this.tokens.tagName),
2069
                openingBacktick: util.cloneToken(this.tokens.openingBacktick),
2070
                quasis: this.quasis?.map(e => e?.clone()),
5✔
2071
                expressions: this.expressions?.map(e => e?.clone()),
3✔
2072
                closingBacktick: util.cloneToken(this.tokens.closingBacktick)
2073
            }),
2074
            ['quasis', 'expressions']
2075
        );
2076
    }
2077
}
2078

2079
export class AnnotationExpression extends Expression {
1✔
2080
    constructor(options: {
2081
        at?: Token;
2082
        name: Token;
2083
        call?: CallExpression;
2084
    }) {
2085
        super();
83✔
2086
        this.tokens = {
83✔
2087
            at: options.at,
2088
            name: options.name
2089
        };
2090
        this.call = options.call;
83✔
2091
        this.name = this.tokens.name.text;
83✔
2092
    }
2093

2094
    public readonly kind = AstNodeKind.AnnotationExpression;
83✔
2095

2096
    public readonly tokens: {
2097
        readonly at: Token;
2098
        readonly name: Token;
2099
    };
2100

2101
    public get location(): Location | undefined {
2102
        return util.createBoundingLocation(
75✔
2103
            this.tokens.at,
2104
            this.tokens.name,
2105
            this.call
2106
        );
2107
    }
2108

2109
    public readonly name: string;
2110

2111
    public call: CallExpression;
2112

2113
    /**
2114
     * Convert annotation arguments to JavaScript types
2115
     * @param strict If false, keep Expression objects not corresponding to JS types
2116
     */
2117
    getArguments(strict = true): ExpressionValue[] {
10✔
2118
        if (!this.call) {
11✔
2119
            return [];
1✔
2120
        }
2121
        return this.call.args.map(e => expressionToValue(e, strict));
20✔
2122
    }
2123

2124
    public get leadingTrivia(): Token[] {
2125
        return this.tokens.at?.leadingTrivia;
50!
2126
    }
2127

2128
    transpile(state: BrsTranspileState) {
2129
        //transpile only our leading comments
2130
        return state.transpileComments(this.leadingTrivia);
16✔
2131
    }
2132

2133
    walk(visitor: WalkVisitor, options: WalkOptions) {
2134
        //nothing to walk
2135
    }
2136
    getTypedef(state: BrsTranspileState) {
2137
        return [
9✔
2138
            '@',
2139
            this.name,
2140
            ...(this.call?.transpile(state) ?? [])
54✔
2141
        ];
2142
    }
2143

2144
    public clone() {
2145
        const clone = this.finalizeClone(
7✔
2146
            new AnnotationExpression({
2147
                at: util.cloneToken(this.tokens.at),
2148
                name: util.cloneToken(this.tokens.name)
2149
            })
2150
        );
2151
        return clone;
7✔
2152
    }
2153
}
2154

2155
export class TernaryExpression extends Expression {
1✔
2156
    constructor(options: {
2157
        test: Expression;
2158
        questionMark?: Token;
2159
        consequent?: Expression;
2160
        colon?: Token;
2161
        alternate?: Expression;
2162
    }) {
2163
        super();
99✔
2164
        this.tokens = {
99✔
2165
            questionMark: options.questionMark,
2166
            colon: options.colon
2167
        };
2168
        this.test = options.test;
99✔
2169
        this.consequent = options.consequent;
99✔
2170
        this.alternate = options.alternate;
99✔
2171
        this.location = util.createBoundingLocation(
99✔
2172
            this.test,
2173
            this.tokens.questionMark,
2174
            this.consequent,
2175
            this.tokens.colon,
2176
            this.alternate
2177
        );
2178
    }
2179

2180
    public readonly kind = AstNodeKind.TernaryExpression;
99✔
2181

2182
    public readonly location: Location | undefined;
2183

2184
    public readonly tokens: {
2185
        readonly questionMark?: Token;
2186
        readonly colon?: Token;
2187
    };
2188

2189
    public readonly test: Expression;
2190
    public readonly consequent?: Expression;
2191
    public readonly alternate?: Expression;
2192

2193
    transpile(state: BrsTranspileState) {
2194
        let result = [] as TranspileResult;
21✔
2195
        const file = state.file;
21✔
2196
        let consequentInfo = util.getExpressionInfo(this.consequent!, file);
21✔
2197
        let alternateInfo = util.getExpressionInfo(this.alternate!, file);
21✔
2198

2199
        //get all unique variable names used in the consequent and alternate, and sort them alphabetically so the output is consistent
2200
        let allUniqueVarNames = [...new Set([...consequentInfo.uniqueVarNames, ...alternateInfo.uniqueVarNames])].sort();
21✔
2201
        let mutatingExpressions = [
21✔
2202
            ...consequentInfo.expressions,
2203
            ...alternateInfo.expressions
2204
        ].filter(e => e instanceof CallExpression || e instanceof CallfuncExpression || e instanceof DottedGetExpression);
104✔
2205

2206
        if (mutatingExpressions.length > 0) {
21✔
2207
            result.push(
9✔
2208
                state.sourceNode(
2209
                    this.tokens.questionMark,
2210
                    //write all the scope variables as parameters.
2211
                    //TODO handle when there are more than 31 parameters
2212
                    `(function(${['__bsCondition', ...allUniqueVarNames].join(', ')})`
2213
                ),
2214
                state.newline,
2215
                //double indent so our `end function` line is still indented one at the end
2216
                state.indent(2),
2217
                state.sourceNode(this.test, `if __bsCondition then`),
2218
                state.newline,
2219
                state.indent(1),
2220
                state.sourceNode(this.consequent ?? this.tokens.questionMark, 'return '),
27!
2221
                ...this.consequent?.transpile(state) ?? [state.sourceNode(this.tokens.questionMark, 'invalid')],
54!
2222
                state.newline,
2223
                state.indent(-1),
2224
                state.sourceNode(this.consequent ?? this.tokens.questionMark, 'else'),
27!
2225
                state.newline,
2226
                state.indent(1),
2227
                state.sourceNode(this.consequent ?? this.tokens.questionMark, 'return '),
27!
2228
                ...this.alternate?.transpile(state) ?? [state.sourceNode(this.consequent ?? this.tokens.questionMark, 'invalid')],
54!
2229
                state.newline,
2230
                state.indent(-1),
2231
                state.sourceNode(this.tokens.questionMark, 'end if'),
2232
                state.newline,
2233
                state.indent(-1),
2234
                state.sourceNode(this.tokens.questionMark, 'end function)('),
2235
                ...this.test.transpile(state),
2236
                state.sourceNode(this.tokens.questionMark, `${['', ...allUniqueVarNames].join(', ')})`)
2237
            );
2238
            state.blockDepth--;
9✔
2239
        } else {
2240
            result.push(
12✔
2241
                state.sourceNode(this.test, state.bslibPrefix + `_ternary(`),
2242
                ...this.test.transpile(state),
2243
                state.sourceNode(this.test, `, `),
2244
                ...this.consequent?.transpile(state) ?? ['invalid'],
72✔
2245
                `, `,
2246
                ...this.alternate?.transpile(state) ?? ['invalid'],
72✔
2247
                `)`
2248
            );
2249
        }
2250
        return result;
21✔
2251
    }
2252

2253
    public walk(visitor: WalkVisitor, options: WalkOptions) {
2254
        if (options.walkMode & InternalWalkMode.walkExpressions) {
387!
2255
            walk(this, 'test', visitor, options);
387✔
2256
            walk(this, 'consequent', visitor, options);
387✔
2257
            walk(this, 'alternate', visitor, options);
387✔
2258
        }
2259
    }
2260

2261
    get leadingTrivia(): Token[] {
2262
        return this.test.leadingTrivia;
254✔
2263
    }
2264

2265
    public clone() {
2266
        return this.finalizeClone(
2✔
2267
            new TernaryExpression({
2268
                test: this.test?.clone(),
6✔
2269
                questionMark: util.cloneToken(this.tokens.questionMark),
2270
                consequent: this.consequent?.clone(),
6✔
2271
                colon: util.cloneToken(this.tokens.colon),
2272
                alternate: this.alternate?.clone()
6✔
2273
            }),
2274
            ['test', 'consequent', 'alternate']
2275
        );
2276
    }
2277
}
2278

2279
export class NullCoalescingExpression extends Expression {
1✔
2280
    constructor(options: {
2281
        consequent: Expression;
2282
        questionQuestion?: Token;
2283
        alternate: Expression;
2284
    }) {
2285
        super();
36✔
2286
        this.tokens = {
36✔
2287
            questionQuestion: options.questionQuestion
2288
        };
2289
        this.consequent = options.consequent;
36✔
2290
        this.alternate = options.alternate;
36✔
2291
        this.location = util.createBoundingLocation(
36✔
2292
            this.consequent,
2293
            this.tokens.questionQuestion,
2294
            this.alternate
2295
        );
2296
    }
2297

2298
    public readonly kind = AstNodeKind.NullCoalescingExpression;
36✔
2299

2300
    public readonly location: Location | undefined;
2301

2302
    public readonly tokens: {
2303
        readonly questionQuestion?: Token;
2304
    };
2305

2306
    public readonly consequent: Expression;
2307
    public readonly alternate: Expression;
2308

2309
    transpile(state: BrsTranspileState) {
2310
        let result = [] as TranspileResult;
10✔
2311
        let consequentInfo = util.getExpressionInfo(this.consequent, state.file);
10✔
2312
        let alternateInfo = util.getExpressionInfo(this.alternate, state.file);
10✔
2313

2314
        //get all unique variable names used in the consequent and alternate, and sort them alphabetically so the output is consistent
2315
        let allUniqueVarNames = [...new Set([...consequentInfo.uniqueVarNames, ...alternateInfo.uniqueVarNames])].sort();
10✔
2316
        let hasMutatingExpression = [
10✔
2317
            ...consequentInfo.expressions,
2318
            ...alternateInfo.expressions
2319
        ].find(e => isCallExpression(e) || isCallfuncExpression(e) || isDottedGetExpression(e));
28✔
2320

2321
        if (hasMutatingExpression) {
10✔
2322
            result.push(
6✔
2323
                `(function(`,
2324
                //write all the scope variables as parameters.
2325
                //TODO handle when there are more than 31 parameters
2326
                allUniqueVarNames.join(', '),
2327
                ')',
2328
                state.newline,
2329
                //double indent so our `end function` line is still indented one at the end
2330
                state.indent(2),
2331
                //evaluate the consequent exactly once, and then use it in the following condition
2332
                `__bsConsequent = `,
2333
                ...this.consequent.transpile(state),
2334
                state.newline,
2335
                state.indent(),
2336
                `if __bsConsequent <> invalid then`,
2337
                state.newline,
2338
                state.indent(1),
2339
                'return __bsConsequent',
2340
                state.newline,
2341
                state.indent(-1),
2342
                'else',
2343
                state.newline,
2344
                state.indent(1),
2345
                'return ',
2346
                ...this.alternate.transpile(state),
2347
                state.newline,
2348
                state.indent(-1),
2349
                'end if',
2350
                state.newline,
2351
                state.indent(-1),
2352
                'end function)(',
2353
                allUniqueVarNames.join(', '),
2354
                ')'
2355
            );
2356
            state.blockDepth--;
6✔
2357
        } else {
2358
            result.push(
4✔
2359
                state.bslibPrefix + `_coalesce(`,
2360
                ...this.consequent.transpile(state),
2361
                ', ',
2362
                ...this.alternate.transpile(state),
2363
                ')'
2364
            );
2365
        }
2366
        return result;
10✔
2367
    }
2368

2369
    public walk(visitor: WalkVisitor, options: WalkOptions) {
2370
        if (options.walkMode & InternalWalkMode.walkExpressions) {
92!
2371
            walk(this, 'consequent', visitor, options);
92✔
2372
            walk(this, 'alternate', visitor, options);
92✔
2373
        }
2374
    }
2375

2376
    get leadingTrivia(): Token[] {
2377
        return this.consequent.leadingTrivia;
54✔
2378
    }
2379

2380
    public clone() {
2381
        return this.finalizeClone(
2✔
2382
            new NullCoalescingExpression({
2383
                consequent: this.consequent?.clone(),
6✔
2384
                questionQuestion: util.cloneToken(this.tokens.questionQuestion),
2385
                alternate: this.alternate?.clone()
6✔
2386
            }),
2387
            ['consequent', 'alternate']
2388
        );
2389
    }
2390
}
2391

2392
export class RegexLiteralExpression extends Expression {
1✔
2393
    constructor(options: {
2394
        regexLiteral: Token;
2395
    }) {
2396
        super();
46✔
2397
        this.tokens = {
46✔
2398
            regexLiteral: options.regexLiteral
2399
        };
2400
    }
2401

2402
    public readonly kind = AstNodeKind.RegexLiteralExpression;
46✔
2403
    public readonly tokens: {
2404
        readonly regexLiteral: Token;
2405
    };
2406

2407
    public get location(): Location {
2408
        return this.tokens?.regexLiteral?.location;
150!
2409
    }
2410

2411
    public transpile(state: BrsTranspileState): TranspileResult {
2412
        let text = this.tokens.regexLiteral?.text ?? '';
42!
2413
        let flags = '';
42✔
2414
        //get any flags from the end
2415
        const flagMatch = /\/([a-z]+)$/i.exec(text);
42✔
2416
        if (flagMatch) {
42✔
2417
            text = text.substring(0, flagMatch.index + 1);
2✔
2418
            flags = flagMatch[1];
2✔
2419
        }
2420
        let pattern = text
42✔
2421
            //remove leading and trailing slashes
2422
            .substring(1, text.length - 1)
2423
            //escape quotemarks
2424
            .split('"').join('" + chr(34) + "');
2425

2426
        return [
42✔
2427
            state.sourceNode(this.tokens.regexLiteral, [
2428
                'CreateObject("roRegex", ',
2429
                `"${pattern}", `,
2430
                `"${flags}"`,
2431
                ')'
2432
            ])
2433
        ];
2434
    }
2435

2436
    walk(visitor: WalkVisitor, options: WalkOptions) {
2437
        //nothing to walk
2438
    }
2439

2440
    public clone() {
2441
        return this.finalizeClone(
1✔
2442
            new RegexLiteralExpression({
2443
                regexLiteral: util.cloneToken(this.tokens.regexLiteral)
2444
            })
2445
        );
2446
    }
2447

2448
    get leadingTrivia(): Token[] {
2449
        return this.tokens.regexLiteral?.leadingTrivia;
1,038!
2450
    }
2451
}
2452

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

2456
function expressionToValue(expr: Expression, strict: boolean): ExpressionValue {
2457
    if (!expr) {
30!
UNCOV
2458
        return null;
×
2459
    }
2460
    if (isUnaryExpression(expr) && isLiteralNumber(expr.right)) {
30✔
2461
        return numberExpressionToValue(expr.right, expr.tokens.operator.text);
1✔
2462
    }
2463
    if (isLiteralString(expr)) {
29✔
2464
        //remove leading and trailing quotes
2465
        return expr.tokens.value.text.replace(/^"/, '').replace(/"$/, '');
5✔
2466
    }
2467
    if (isLiteralNumber(expr)) {
24✔
2468
        return numberExpressionToValue(expr);
11✔
2469
    }
2470

2471
    if (isLiteralBoolean(expr)) {
13✔
2472
        return expr.tokens.value.text.toLowerCase() === 'true';
3✔
2473
    }
2474
    if (isArrayLiteralExpression(expr)) {
10✔
2475
        return expr.elements
3✔
2476
            .map(e => expressionToValue(e, strict));
7✔
2477
    }
2478
    if (isAALiteralExpression(expr)) {
7✔
2479
        return expr.elements.reduce((acc, e) => {
3✔
2480
            acc[e.tokens.key.text] = expressionToValue(e.value, strict);
3✔
2481
            return acc;
3✔
2482
        }, {});
2483
    }
2484
    //for annotations, we only support serializing pure string values
2485
    if (isTemplateStringExpression(expr)) {
4✔
2486
        if (expr.quasis?.length === 1 && expr.expressions.length === 0) {
2!
2487
            return expr.quasis[0].expressions.map(x => x.tokens.value.text).join('');
10✔
2488
        }
2489
    }
2490
    return strict ? null : expr;
2✔
2491
}
2492

2493
function numberExpressionToValue(expr: LiteralExpression, operator = '') {
11✔
2494
    if (isIntegerType(expr.getType()) || isLongIntegerType(expr.getType())) {
12!
2495
        return parseInt(operator + expr.tokens.value.text);
12✔
2496
    } else {
UNCOV
2497
        return parseFloat(operator + expr.tokens.value.text);
×
2498
    }
2499
}
2500

2501
export class TypeExpression extends Expression implements TypedefProvider {
1✔
2502
    constructor(options: {
2503
        /**
2504
         * The standard AST expression that represents the type for this TypeExpression.
2505
         */
2506
        expression: Expression;
2507
    }) {
2508
        super();
1,622✔
2509
        this.expression = options.expression;
1,622✔
2510
        this.location = util.cloneLocation(this.expression?.location);
1,622!
2511
    }
2512

2513
    public readonly kind = AstNodeKind.TypeExpression;
1,622✔
2514

2515
    /**
2516
     * The standard AST expression that represents the type for this TypeExpression.
2517
     */
2518
    public readonly expression: Expression;
2519

2520
    public readonly location: Location;
2521

2522
    public transpile(state: BrsTranspileState): TranspileResult {
2523
        const exprType = this.getType({ flags: SymbolTypeFlag.typetime });
112✔
2524
        if (isNativeType(exprType)) {
112✔
2525
            return this.expression.transpile(state);
103✔
2526
        }
2527
        return [exprType.toTypeString()];
9✔
2528
    }
2529
    public walk(visitor: WalkVisitor, options: WalkOptions) {
2530
        if (options.walkMode & InternalWalkMode.walkExpressions) {
7,669✔
2531
            walk(this, 'expression', visitor, options);
7,541✔
2532
        }
2533
    }
2534

2535
    public getType(options: GetTypeOptions): BscType {
2536
        return this.expression.getType({ ...options, flags: SymbolTypeFlag.typetime });
7,321✔
2537
    }
2538

2539
    getTypedef(state: TranspileState): TranspileResult {
2540
        // TypeDefs should pass through any valid type names
2541
        return this.expression.transpile(state as BrsTranspileState);
33✔
2542
    }
2543

2544
    getName(parseMode = ParseMode.BrighterScript): string {
238✔
2545
        //TODO: this may not support Complex Types, eg. generics or Unions
2546
        return util.getAllDottedGetPartsAsString(this.expression, parseMode);
238✔
2547
    }
2548

2549
    getNameParts(): string[] {
2550
        //TODO: really, this code is only used to get Namespaces. It could be more clear.
2551
        return util.getAllDottedGetParts(this.expression).map(x => x.text);
20✔
2552
    }
2553

2554
    public clone() {
2555
        return this.finalizeClone(
18✔
2556
            new TypeExpression({
2557
                expression: this.expression?.clone()
54!
2558
            }),
2559
            ['expression']
2560
        );
2561
    }
2562
}
2563

2564
export class TypecastExpression extends Expression {
1✔
2565
    constructor(options: {
2566
        obj: Expression;
2567
        as?: Token;
2568
        typeExpression?: TypeExpression;
2569
    }) {
2570
        super();
79✔
2571
        this.tokens = {
79✔
2572
            as: options.as
2573
        };
2574
        this.obj = options.obj;
79✔
2575
        this.typeExpression = options.typeExpression;
79✔
2576
        this.location = util.createBoundingLocation(
79✔
2577
            this.obj,
2578
            this.tokens.as,
2579
            this.typeExpression
2580
        );
2581
    }
2582

2583
    public readonly kind = AstNodeKind.TypecastExpression;
79✔
2584

2585
    public readonly obj: Expression;
2586

2587
    public readonly tokens: {
2588
        readonly as?: Token;
2589
    };
2590

2591
    public typeExpression?: TypeExpression;
2592

2593
    public readonly location: Location;
2594

2595
    public transpile(state: BrsTranspileState): TranspileResult {
2596
        return this.obj.transpile(state);
13✔
2597
    }
2598
    public walk(visitor: WalkVisitor, options: WalkOptions) {
2599
        if (options.walkMode & InternalWalkMode.walkExpressions) {
405!
2600
            walk(this, 'obj', visitor, options);
405✔
2601
            walk(this, 'typeExpression', visitor, options);
405✔
2602
        }
2603
    }
2604

2605
    public getType(options: GetTypeOptions): BscType {
2606
        const result = this.typeExpression.getType(options);
102✔
2607
        if (options.typeChain) {
102✔
2608
            // modify last typechain entry to show it is a typecast
2609
            const lastEntry = options.typeChain[options.typeChain.length - 1];
23✔
2610
            if (lastEntry) {
23!
2611
                lastEntry.astNode = this;
23✔
2612
            }
2613
        }
2614
        return result;
102✔
2615
    }
2616

2617
    public clone() {
2618
        return this.finalizeClone(
3✔
2619
            new TypecastExpression({
2620
                obj: this.obj?.clone(),
9✔
2621
                as: util.cloneToken(this.tokens.as),
2622
                typeExpression: this.typeExpression?.clone()
9!
2623
            }),
2624
            ['obj', 'typeExpression']
2625
        );
2626
    }
2627
}
2628

2629
export class TypedArrayExpression extends Expression {
1✔
2630
    constructor(options: {
2631
        innerType: Expression;
2632
        leftBracket?: Token;
2633
        rightBracket?: Token;
2634
    }) {
2635
        super();
29✔
2636
        this.tokens = {
29✔
2637
            leftBracket: options.leftBracket,
2638
            rightBracket: options.rightBracket
2639
        };
2640
        this.innerType = options.innerType;
29✔
2641
        this.location = util.createBoundingLocation(
29✔
2642
            this.innerType,
2643
            this.tokens.leftBracket,
2644
            this.tokens.rightBracket
2645
        );
2646
    }
2647

2648
    public readonly tokens: {
2649
        readonly leftBracket?: Token;
2650
        readonly rightBracket?: Token;
2651
    };
2652

2653
    public readonly innerType: Expression;
2654

2655
    public readonly kind = AstNodeKind.TypedArrayExpression;
29✔
2656

2657
    public readonly location: Location;
2658

2659
    public transpile(state: BrsTranspileState): TranspileResult {
UNCOV
2660
        return [this.getType({ flags: SymbolTypeFlag.typetime }).toTypeString()];
×
2661
    }
2662

2663
    public walk(visitor: WalkVisitor, options: WalkOptions) {
2664
        if (options.walkMode & InternalWalkMode.walkExpressions) {
129!
2665
            walk(this, 'innerType', visitor, options);
129✔
2666
        }
2667
    }
2668

2669
    public getType(options: GetTypeOptions): BscType {
2670
        return new ArrayType(this.innerType.getType(options));
145✔
2671
    }
2672

2673
    public clone() {
UNCOV
2674
        return this.finalizeClone(
×
2675
            new TypedArrayExpression({
2676
                innerType: this.innerType?.clone(),
×
2677
                leftBracket: util.cloneToken(this.tokens.leftBracket),
2678
                rightBracket: util.cloneToken(this.tokens.rightBracket)
2679
            }),
2680
            ['innerType']
2681
        );
2682
    }
2683
}
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