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

rokucommunity / brighterscript / #13602

13 Jan 2025 03:29PM UTC coverage: 86.902% (-1.3%) from 88.185%
#13602

push

web-flow
Merge 6255e8be5 into 9d6ef67ba

12080 of 14675 branches covered (82.32%)

Branch coverage included in aggregate %.

94 of 100 new or added lines in 13 files covered. (94.0%)

796 existing lines in 47 files now uncovered.

13052 of 14245 relevant lines covered (91.63%)

31874.63 hits per line

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

91.62
/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, isComponentType, isDottedGetExpression, isEscapedCharCodeLiteralExpression, isFunctionExpression, isFunctionStatement, isIntegerType, isInterfaceMethodStatement, isInvalidType, isLiteralBoolean, isLiteralExpression, isLiteralNumber, isLiteralString, isLongIntegerType, isMethodStatement, isNamespaceStatement, 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 type { ComponentType } from '../types/ComponentType';
31
import { createToken } from '../astUtils/creators';
1✔
32
import { InvalidType, TypedFunctionType, UninitializedType } from '../types';
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,303✔
47
        this.tokens = {
3,303✔
48
            operator: options.operator
49
        };
50
        this.left = options.left;
3,303✔
51
        this.right = options.right;
3,303✔
52
        this.location = util.createBoundingLocation(this.left, this.tokens.operator, this.right);
3,303✔
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,303✔
62

63
    public readonly location: Location | undefined;
64

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

82

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

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

155
    public readonly location: Location | undefined;
156

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

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

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

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

195
    getType(options: GetTypeOptions) {
196
        const calleeType = this.callee.getType(options);
1,008✔
197
        if (options.ignoreCall) {
1,008!
UNCOV
198
            return calleeType;
×
199
        }
200
        if (isNewExpression(this.parent)) {
1,008✔
201
            return calleeType;
349✔
202
        }
203
        const specialCaseReturnType = util.getSpecialCaseCallExpressionReturnType(this);
659✔
204
        if (specialCaseReturnType) {
659✔
205
            return specialCaseReturnType;
104✔
206
        }
207
        if (isCallableType(calleeType) && (!isReferenceType(calleeType.returnType) || calleeType.returnType?.isResolvable())) {
555!
208
            if (isVoidType(calleeType.returnType)) {
253✔
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;
243✔
217
        }
218
        if (!isReferenceType(calleeType) && (calleeType as BaseFunctionType)?.returnType?.isResolvable()) {
302✔
219
            return (calleeType as BaseFunctionType).returnType;
257✔
220
        }
221
        return new TypePropertyReferenceType(calleeType, 'returnType');
45✔
222
    }
223

224
    get leadingTrivia(): Token[] {
225
        return this.callee.leadingTrivia;
8,359✔
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();
3,885✔
253
        this.tokens = {
3,885✔
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 ?? [];
3,885✔
261
        this.body = options.body;
3,885✔
262
        this.returnTypeExpression = options.returnTypeExpression;
3,885✔
263
        //if there's a body, and it doesn't have a SymbolTable, assign one
264
        if (this.body) {
3,885✔
265
            if (!this.body.symbolTable) {
3,884!
266
                this.body.symbolTable = new SymbolTable(`Block`, () => this.getSymbolTable());
28,363✔
267
            } else {
UNCOV
268
                this.body.symbolTable.pushParentProvider(() => this.getSymbolTable());
×
269
            }
270
            this.body.parent = this;
3,884✔
271
        }
272
        this.symbolTable = new SymbolTable('FunctionExpression', () => this.parent?.getSymbolTable());
35,996!
273
    }
274

275
    public readonly kind = AstNodeKind.FunctionExpression;
3,885✔
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;
27,744✔
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(
9,920✔
303
            this.tokens.functionType,
304
            this.tokens.leftParen,
305
            ...this.parameters ?? [],
29,760!
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,475✔
314
        let results = [] as TranspileResult;
1,475✔
315
        //'function'|'sub'
316
        results.push(
1,475✔
317
            state.transpileToken(this.tokens.functionType, 'function', false, state.skipLeadingComments)
318
        );
319
        //functionName?
320
        if (name) {
1,475✔
321
            results.push(
1,404✔
322
                ' ',
323
                state.transpileToken(name)
324
            );
325
        }
326
        //leftParen
327
        results.push(
1,475✔
328
            state.transpileToken(this.tokens.leftParen, '(')
329
        );
330
        //parameters
331
        for (let i = 0; i < this.parameters.length; i++) {
1,475✔
332
            let param = this.parameters[i];
2,228✔
333
            //add commas
334
            if (i > 0) {
2,228✔
335
                results.push(', ');
1,098✔
336
            }
337
            //add parameter
338
            results.push(param.transpile(state));
2,228✔
339
        }
340
        //right paren
341
        results.push(
1,475✔
342
            state.transpileToken(this.tokens.rightParen, ')')
343
        );
344
        //as [Type]
345
        if (!state.options.removeParameterTypes && this.returnTypeExpression) {
1,475✔
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,475✔
356
        if (includeBody) {
1,475!
357
            state.lineage.unshift(this);
1,475✔
358
            let body = this.body.transpile(state);
1,475✔
359
            hasBody = body.length > 0;
1,475✔
360
            state.lineage.shift();
1,475✔
361
            results.push(...body);
1,475✔
362
        }
363

364
        const lastLocatable = hasBody ? this.body : this.returnTypeExpression ?? this.tokens.leftParen ?? this.tokens.functionType;
1,475!
365
        results.push(
1,475✔
366
            ...state.transpileEndBlockToken(lastLocatable, this.tokens.endFunctionType, `end ${this.tokens.functionType ?? 'function'}`)
4,425✔
367
        );
368
        return results;
1,475✔
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) {
18,173!
408
            walkArray(this.parameters, visitor, options, this);
18,173✔
409
            walk(this, 'returnTypeExpression', visitor, options);
18,173✔
410
            //This is the core of full-program walking...it allows us to step into sub functions
411
            if (options.walkMode & InternalWalkMode.recurseChildFunctions) {
18,173✔
412
                walk(this, 'body', visitor, options);
18,169✔
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,136✔
422

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

429
        const isSub = this.tokens.functionType?.kind === TokenKind.Sub;
4,136✔
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,136✔
432
            returnType = isSub ? VoidType.instance : DynamicType.instance;
3,426✔
433
        }
434

435
        const resultType = new TypedFunctionType(returnType);
4,136✔
436
        resultType.isSub = isSub;
4,136✔
437
        for (let param of this.parameters) {
4,136✔
438
            resultType.addParameter(param.tokens.name.text, param.getType({ ...options, typeChain: undefined }), !!param.defaultValue);
2,091✔
439
        }
440
        // Figure out this function's name if we can
441
        let funcName = '';
4,136✔
442
        if (isMethodStatement(this.parent) || isInterfaceMethodStatement(this.parent)) {
4,136✔
443
            funcName = this.parent.getName(ParseMode.BrighterScript);
346✔
444
            if (options.typeChain) {
346✔
445
                // Get the typechain info from the parent class
446
                this.parent.parent?.getType(options);
1!
447
            }
448
        } else if (isFunctionStatement(this.parent)) {
3,790✔
449
            funcName = this.parent.getName(ParseMode.BrighterScript);
3,717✔
450
        }
451
        if (funcName) {
4,136✔
452
            resultType.setName(funcName);
4,063✔
453
        }
454
        options.typeChain?.push(new TypeChainEntry({ name: funcName, type: resultType, data: options.data, astNode: this }));
4,136✔
455
        return resultType;
4,136✔
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,033✔
484
        this.tokens = {
3,033✔
485
            name: options.name,
486
            equals: options.equals,
487
            as: options.as
488
        };
489
        this.defaultValue = options.defaultValue;
3,033✔
490
        this.typeExpression = options.typeExpression;
3,033✔
491
    }
492

493
    public readonly kind = AstNodeKind.FunctionParameterExpression;
3,033✔
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,344✔
506
        const paramName = this.tokens.name.text;
5,344✔
507

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

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

520
    public get location(): Location | undefined {
521
        return util.createBoundingLocation(
8,804✔
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,236✔
532
            //name
533
            state.transpileToken(this.tokens.name)
534
        ];
535
        //default value
536
        if (this.defaultValue) {
2,236✔
537
            result.push(' = ');
9✔
538
            result.push(this.defaultValue.transpile(state));
9✔
539
        }
540
        //type declaration
541
        if (this.typeExpression && !state.options.removeParameterTypes) {
2,236✔
542
            result.push(' ');
65✔
543
            result.push(state.transpileToken(this.tokens.as, 'as'));
65✔
544
            result.push(' ');
65✔
545
            result.push(
65✔
546
                ...(this.typeExpression?.transpile(state) ?? [])
390!
547
            );
548
        }
549

550
        return result;
2,236✔
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) {
14,727!
576
            walk(this, 'defaultValue', visitor, options);
14,727✔
577
            walk(this, 'typeExpression', visitor, options);
14,727✔
578
        }
579
    }
580

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

585
    public clone() {
586
        return this.finalizeClone(
8✔
587
            new FunctionParameterExpression({
588
                name: util.cloneToken(this.tokens.name),
589
                as: util.cloneToken(this.tokens.as),
590
                typeExpression: this.typeExpression?.clone(),
24✔
591
                equals: util.cloneToken(this.tokens.equals),
592
                defaultValue: this.defaultValue?.clone()
24✔
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,845✔
609
        this.tokens = {
2,845✔
610
            name: options.name,
611
            dot: options.dot
612
        };
613
        this.obj = options.obj;
2,845✔
614

615
        this.location = util.createBoundingLocation(this.obj, this.tokens.dot, this.tokens.name);
2,845✔
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,845✔
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)) {
930✔
631
            return [
9✔
632
                ...state.transpileLeadingCommentsForAstNode(this),
633
                state.sourceNode(this, this.getName(ParseMode.BrightScript))
634
            ];
635
        } else {
636
            return [
921✔
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) {
11,823!
646
            walk(this, 'obj', visitor, options);
11,823✔
647
        }
648
    }
649

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

654
        if (util.isClassUsedAsFunction(result, this, options)) {
6,392✔
655
            // treat this class constructor as a function
656
            result = FunctionType.instance;
11✔
657
        }
658
        options.typeChain?.push(new TypeChainEntry({
6,392✔
659
            name: this.tokens.name?.text,
8,190!
660
            type: result,
661
            data: options.data,
662
            location: this.tokens.name?.location ?? this.location,
16,380!
663
            astNode: this
664
        }));
665
        if (result ||
6,392✔
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,358✔
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;
34✔
674
    }
675

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

680
    get leadingTrivia(): Token[] {
681
        return this.obj.leadingTrivia;
14,616✔
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();
56✔
861
        this.tokens = {
56✔
862
            rightParen: options.rightParen,
863
            leftParen: options.leftParen
864
        };
865
        this.expression = options.expression;
56✔
866
        this.location = util.createBoundingLocation(this.tokens.leftParen, this.expression, this.tokens.rightParen);
56✔
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;
56✔
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) {
209!
892
            walk(this, 'expression', visitor, options);
209✔
893
        }
894
    }
895

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

900
    get leadingTrivia(): Token[] {
901
        return this.tokens.leftParen?.leadingTrivia;
302!
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,836✔
921
        this.tokens = {
7,836✔
922
            value: options.value
923
        };
924
    }
925

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

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

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

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

940
    transpile(state: BrsTranspileState) {
941
        let text: string;
942
        if (this.tokens.value.kind === TokenKind.TemplateStringQuasi) {
4,741✔
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,713✔
947
            text = this.tokens.value.text;
3,147✔
948
            //add trailing quotemark if it's missing. We will have already generated a diagnostic for this.
949
            if (text.endsWith('"') === false) {
3,147✔
950
                text += '"';
1✔
951
            }
952
        } else {
953
            text = this.tokens.value.text;
1,566✔
954
        }
955

956
        return [
4,741✔
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;
11,998✔
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();
163✔
1071
        this.tokens = {
163✔
1072
            open: options.open,
1073
            close: options.close
1074
        };
1075
        this.elements = options.elements;
163✔
1076
        this.location = util.createBoundingLocation(this.tokens.open, ...this.elements ?? [], this.tokens.close);
163✔
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;
163✔
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) {
823!
1124
            walkArray(this.elements, visitor, options, this);
823✔
1125
        }
1126
    }
1127

1128
    getType(options: GetTypeOptions): BscType {
1129
        const innerTypes = this.elements.map(expr => expr.getType(options));
247✔
1130
        return new ArrayType(...innerTypes);
178✔
1131
    }
1132
    get leadingTrivia(): Token[] {
1133
        return this.tokens.open?.leadingTrivia;
537!
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();
284✔
1161
        this.tokens = {
284✔
1162
            key: options.key,
1163
            colon: options.colon,
1164
            comma: options.comma
1165
        };
1166
        this.value = options.value;
284✔
1167
        this.location = util.createBoundingLocation(this.tokens.key, this.tokens.colon, this.value);
284✔
1168
    }
1169

1170
    public readonly kind = AstNodeKind.AAMemberExpression;
284✔
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);
970✔
1190
    }
1191

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

1196
    get leadingTrivia(): Token[] {
1197
        return this.tokens.key.leadingTrivia;
706✔
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();
280✔
1219
        this.tokens = {
280✔
1220
            open: options.open,
1221
            close: options.close
1222
        };
1223
        this.elements = options.elements;
280✔
1224
        this.location = util.createBoundingLocation(this.tokens.open, ...this.elements ?? [], this.tokens.close);
280✔
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;
280✔
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,077!
1291
            walkArray(this.elements, visitor, options, this);
1,077✔
1292
        }
1293
    }
1294

1295
    getType(options: GetTypeOptions): BscType {
1296
        const resultType = new AssociativeArrayType();
214✔
1297
        resultType.addBuiltInInterfaces();
214✔
1298
        for (const element of this.elements) {
214✔
1299
            if (isAAMemberExpression(element)) {
205!
1300
                resultType.addMember(element.tokens.key.text, { definingNode: element }, element.getType(options), SymbolTypeFlag.runtime);
205✔
1301
            }
1302
        }
1303
        return resultType;
214✔
1304
    }
1305

1306
    public get leadingTrivia(): Token[] {
1307
        return this.tokens.open?.leadingTrivia;
728!
1308
    }
1309

1310
    public get endTrivia(): Token[] {
1311
        return this.tokens.close?.leadingTrivia;
1!
1312
    }
1313

1314
    public clone() {
1315
        return this.finalizeClone(
6✔
1316
            new AALiteralExpression({
1317
                elements: this.elements?.map(e => e?.clone()),
5✔
1318
                open: util.cloneToken(this.tokens.open),
1319
                close: util.cloneToken(this.tokens.close)
1320
            }),
1321
            ['elements']
1322
        );
1323
    }
1324
}
1325

1326
export class UnaryExpression extends Expression {
1✔
1327
    constructor(options: {
1328
        operator: Token;
1329
        right: Expression;
1330
    }) {
1331
        super();
66✔
1332
        this.tokens = {
66✔
1333
            operator: options.operator
1334
        };
1335
        this.right = options.right;
66✔
1336
        this.location = util.createBoundingLocation(this.tokens.operator, this.right);
66✔
1337
    }
1338

1339
    public readonly kind = AstNodeKind.UnaryExpression;
66✔
1340

1341
    public readonly location: Location | undefined;
1342

1343
    public readonly tokens: {
1344
        readonly operator: Token;
1345
    };
1346
    public readonly right: Expression;
1347

1348
    transpile(state: BrsTranspileState) {
1349
        let separatingWhitespace: string | undefined;
1350
        if (isVariableExpression(this.right)) {
12✔
1351
            separatingWhitespace = this.right.tokens.name.leadingWhitespace;
6✔
1352
        } else if (isLiteralExpression(this.right)) {
6✔
1353
            separatingWhitespace = this.right.tokens.value.leadingWhitespace;
2✔
1354
        } else {
1355
            separatingWhitespace = ' ';
4✔
1356
        }
1357

1358
        return [
12✔
1359
            state.transpileToken(this.tokens.operator),
1360
            separatingWhitespace,
1361
            ...this.right.transpile(state)
1362
        ];
1363
    }
1364

1365
    walk(visitor: WalkVisitor, options: WalkOptions) {
1366
        if (options.walkMode & InternalWalkMode.walkExpressions) {
247!
1367
            walk(this, 'right', visitor, options);
247✔
1368
        }
1369
    }
1370

1371
    getType(options: GetTypeOptions): BscType {
1372
        return util.unaryOperatorResultType(this.tokens.operator, this.right.getType(options));
48✔
1373
    }
1374

1375
    public get leadingTrivia(): Token[] {
1376
        return this.tokens.operator.leadingTrivia;
187✔
1377
    }
1378

1379
    public clone() {
1380
        return this.finalizeClone(
2✔
1381
            new UnaryExpression({
1382
                operator: util.cloneToken(this.tokens.operator),
1383
                right: this.right?.clone()
6✔
1384
            }),
1385
            ['right']
1386
        );
1387
    }
1388
}
1389

1390
export class VariableExpression extends Expression {
1✔
1391
    constructor(options: {
1392
        name: Identifier;
1393
    }) {
1394
        super();
11,061✔
1395
        this.tokens = {
11,061✔
1396
            name: options.name
1397
        };
1398
        this.location = util.cloneLocation(this.tokens.name?.location);
11,061!
1399
    }
1400

1401
    public readonly tokens: {
1402
        readonly name: Identifier;
1403
    };
1404

1405
    public readonly kind = AstNodeKind.VariableExpression;
11,061✔
1406

1407
    public readonly location: Location;
1408

1409
    public getName(parseMode?: ParseMode) {
1410
        return this.tokens.name.text;
22,115✔
1411
    }
1412

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

1436
    walk(visitor: WalkVisitor, options: WalkOptions) {
1437
        //nothing to walk
1438
    }
1439

1440

1441
    getType(options: GetTypeOptions) {
1442
        let resultType: BscType = util.tokenToBscType(this.tokens.name);
21,968✔
1443
        const nameKey = this.getName();
21,968✔
1444
        if (!resultType && !options?.onlyAllowLiterals) {
21,968!
1445
            const symbolTable = this.getSymbolTable();
17,595✔
1446
            resultType = symbolTable?.getSymbolType(nameKey, { ...options, fullName: nameKey, tableProvider: () => this.getSymbolTable() });
36,884!
1447

1448
            if (util.isClassUsedAsFunction(resultType, this, options)) {
17,595✔
1449
                resultType = FunctionType.instance;
20✔
1450
            }
1451

1452
        }
1453
        options?.typeChain?.push(new TypeChainEntry({ name: nameKey, type: resultType, data: options.data, astNode: this }));
21,968!
1454
        return resultType;
21,968✔
1455
    }
1456

1457
    get leadingTrivia(): Token[] {
1458
        return this.tokens.name.leadingTrivia;
36,836✔
1459
    }
1460

1461
    public clone() {
1462
        return this.finalizeClone(
67✔
1463
            new VariableExpression({
1464
                name: util.cloneToken(this.tokens.name)
1465
            })
1466
        );
1467
    }
1468
}
1469

1470
export class SourceLiteralExpression extends Expression {
1✔
1471
    constructor(options: {
1472
        value: Token;
1473
    }) {
1474
        super();
37✔
1475
        this.tokens = {
37✔
1476
            value: options.value
1477
        };
1478
        this.location = util.cloneLocation(this.tokens.value?.location);
37!
1479
    }
1480

1481
    public readonly location: Location;
1482

1483
    public readonly kind = AstNodeKind.SourceLiteralExpression;
37✔
1484

1485
    public readonly tokens: {
1486
        readonly value: Token;
1487
    };
1488

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

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

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

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

UNCOV
1560
                text = `"${namespaceParts.join('.')}"`;
×
UNCOV
1561
                break;
×
1562
            case TokenKind.SourceNamespaceRootNameLiteral:
UNCOV
1563
                let namespaceRootParts = this.getFunctionName(state, ParseMode.BrighterScript).split('.');
×
UNCOV
1564
                namespaceRootParts.pop(); // remove the function name
×
1565

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

1586
        }
1587
        return [
31✔
1588
            state.sourceNode(this, text)
1589
        ];
1590
    }
1591

1592
    walk(visitor: WalkVisitor, options: WalkOptions) {
1593
        //nothing to walk
1594
    }
1595

1596
    get leadingTrivia(): Token[] {
1597
        return this.tokens.value.leadingTrivia;
200✔
1598
    }
1599

1600
    public clone() {
1601
        return this.finalizeClone(
1✔
1602
            new SourceLiteralExpression({
1603
                value: util.cloneToken(this.tokens.value)
1604
            })
1605
        );
1606
    }
1607
}
1608

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

1627
    public readonly kind = AstNodeKind.NewExpression;
139✔
1628

1629
    public readonly location: Location | undefined;
1630

1631
    public readonly tokens: {
1632
        readonly new?: Token;
1633
    };
1634
    public readonly call: CallExpression;
1635

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

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

1656
    walk(visitor: WalkVisitor, options: WalkOptions) {
1657
        if (options.walkMode & InternalWalkMode.walkExpressions) {
871!
1658
            walk(this, 'call', visitor, options);
871✔
1659
        }
1660
    }
1661

1662
    getType(options: GetTypeOptions) {
1663
        const result = this.call.getType(options);
349✔
1664
        if (options.typeChain) {
349✔
1665
            // modify last typechain entry to show it is a new ...()
1666
            const lastEntry = options.typeChain[options.typeChain.length - 1];
3✔
1667
            if (lastEntry) {
3!
1668
                lastEntry.astNode = this;
3✔
1669
            }
1670
        }
1671
        return result;
349✔
1672
    }
1673

1674
    get leadingTrivia(): Token[] {
1675
        return this.tokens.new.leadingTrivia;
608✔
1676
    }
1677

1678
    public clone() {
1679
        return this.finalizeClone(
2✔
1680
            new NewExpression({
1681
                new: util.cloneToken(this.tokens.new),
1682
                call: this.call?.clone()
6✔
1683
            }),
1684
            ['call']
1685
        );
1686
    }
1687
}
1688

1689
export class CallfuncExpression extends Expression {
1✔
1690
    constructor(options: {
1691
        callee: Expression;
1692
        operator?: Token;
1693
        methodName: Identifier;
1694
        openingParen?: Token;
1695
        args?: Expression[];
1696
        closingParen?: Token;
1697
    }) {
1698
        super();
32✔
1699
        this.tokens = {
32✔
1700
            operator: options.operator,
1701
            methodName: options.methodName,
1702
            openingParen: options.openingParen,
1703
            closingParen: options.closingParen
1704
        };
1705
        this.callee = options.callee;
32✔
1706
        this.args = options.args ?? [];
32✔
1707

1708
        this.location = util.createBoundingLocation(
32✔
1709
            this.callee,
1710
            this.tokens.operator,
1711
            this.tokens.methodName,
1712
            this.tokens.openingParen,
1713
            ...this.args ?? [],
96!
1714
            this.tokens.closingParen
1715
        );
1716
    }
1717

1718
    public readonly callee: Expression;
1719
    public readonly args: Expression[];
1720

1721
    public readonly tokens: {
1722
        readonly operator: Token;
1723
        readonly methodName: Identifier;
1724
        readonly openingParen?: Token;
1725
        readonly closingParen?: Token;
1726
    };
1727

1728
    public readonly kind = AstNodeKind.CallfuncExpression;
32✔
1729

1730
    public readonly location: Location | undefined;
1731

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

1756
        result.push(
9✔
1757
            state.transpileToken(this.tokens.closingParen, ')')
1758
        );
1759
        return result;
9✔
1760
    }
1761

1762
    walk(visitor: WalkVisitor, options: WalkOptions) {
1763
        if (options.walkMode & InternalWalkMode.walkExpressions) {
142!
1764
            walk(this, 'callee', visitor, options);
142✔
1765
            walkArray(this.args, visitor, options, this);
142✔
1766
        }
1767
    }
1768

1769
    getType(options: GetTypeOptions) {
1770
        let result: BscType = DynamicType.instance;
4✔
1771
        // a little hacky here with checking options.ignoreCall because callFuncExpression has the method name
1772
        // It's nicer for CallExpression, because it's a call on any expression.
1773
        const calleeType = this.callee.getType({ ...options, flags: SymbolTypeFlag.runtime });
4✔
1774
        if (isComponentType(calleeType) || isReferenceType(calleeType)) {
4!
1775
            const funcType = (calleeType as ComponentType).getCallFuncType?.(this.tokens.methodName.text, options);
4!
1776
            if (funcType) {
4✔
1777
                options.typeChain?.push(new TypeChainEntry({
3✔
1778
                    name: this.tokens.methodName.text,
1779
                    type: funcType,
1780
                    data: options.data,
1781
                    location: this.tokens.methodName.location,
1782
                    separatorToken: createToken(TokenKind.Callfunc),
1783
                    astNode: this
1784
                }));
1785
                if (options.ignoreCall) {
3✔
1786
                    result = funcType;
1✔
1787
                }
1788
            }
1789
            /* TODO:
1790
                make callfunc return types work
1791
            else if (isCallableType(funcType) && (!isReferenceType(funcType.returnType) || funcType.returnType.isResolvable())) {
1792
                result = funcType.returnType;
1793
            } else if (!isReferenceType(funcType) && (funcType as any)?.returnType?.isResolvable()) {
1794
                result = (funcType as any).returnType;
1795
            } else {
1796
                return new TypePropertyReferenceType(funcType, 'returnType');
1797
            }
1798
            */
1799
        }
1800

1801
        return result;
4✔
1802
    }
1803

1804
    get leadingTrivia(): Token[] {
1805
        return this.callee.leadingTrivia;
233✔
1806
    }
1807

1808
    public clone() {
1809
        return this.finalizeClone(
3✔
1810
            new CallfuncExpression({
1811
                callee: this.callee?.clone(),
9✔
1812
                operator: util.cloneToken(this.tokens.operator),
1813
                methodName: util.cloneToken(this.tokens.methodName),
1814
                openingParen: util.cloneToken(this.tokens.openingParen),
1815
                args: this.args?.map(e => e?.clone()),
2✔
1816
                closingParen: util.cloneToken(this.tokens.closingParen)
1817
            }),
1818
            ['callee', 'args']
1819
        );
1820
    }
1821
}
1822

1823
/**
1824
 * Since template strings can contain newlines, we need to concatenate multiple strings together with chr() calls.
1825
 * This is a single expression that represents the string contatenation of all parts of a single quasi.
1826
 */
1827
export class TemplateStringQuasiExpression extends Expression {
1✔
1828
    constructor(options: {
1829
        expressions: Array<LiteralExpression | EscapedCharCodeLiteralExpression>;
1830
    }) {
1831
        super();
108✔
1832
        this.expressions = options.expressions;
108✔
1833
        this.location = util.createBoundingLocation(
108✔
1834
            ...this.expressions ?? []
324✔
1835
        );
1836
    }
1837

1838
    public readonly expressions: Array<LiteralExpression | EscapedCharCodeLiteralExpression>;
1839
    public readonly kind = AstNodeKind.TemplateStringQuasiExpression;
108✔
1840

1841
    readonly location: Location | undefined;
1842

1843
    transpile(state: BrsTranspileState, skipEmptyStrings = true) {
35✔
1844
        let result = [] as TranspileResult;
43✔
1845
        let plus = '';
43✔
1846
        for (let expression of this.expressions) {
43✔
1847
            //skip empty strings
1848
            //TODO what does an empty string literal expression look like?
1849
            if (expression.tokens.value.text === '' && skipEmptyStrings === true) {
68✔
1850
                continue;
27✔
1851
            }
1852
            result.push(
41✔
1853
                plus,
1854
                ...expression.transpile(state)
1855
            );
1856
            plus = ' + ';
41✔
1857
        }
1858
        return result;
43✔
1859
    }
1860

1861
    walk(visitor: WalkVisitor, options: WalkOptions) {
1862
        if (options.walkMode & InternalWalkMode.walkExpressions) {
370!
1863
            walkArray(this.expressions, visitor, options, this);
370✔
1864
        }
1865
    }
1866

1867
    public clone() {
1868
        return this.finalizeClone(
15✔
1869
            new TemplateStringQuasiExpression({
1870
                expressions: this.expressions?.map(e => e?.clone())
20✔
1871
            }),
1872
            ['expressions']
1873
        );
1874
    }
1875
}
1876

1877
export class TemplateStringExpression extends Expression {
1✔
1878
    constructor(options: {
1879
        openingBacktick?: Token;
1880
        quasis: TemplateStringQuasiExpression[];
1881
        expressions: Expression[];
1882
        closingBacktick?: Token;
1883
    }) {
1884
        super();
49✔
1885
        this.tokens = {
49✔
1886
            openingBacktick: options.openingBacktick,
1887
            closingBacktick: options.closingBacktick
1888
        };
1889
        this.quasis = options.quasis;
49✔
1890
        this.expressions = options.expressions;
49✔
1891
        this.location = util.createBoundingLocation(
49✔
1892
            this.tokens.openingBacktick,
1893
            this.quasis?.[0],
147✔
1894
            this.quasis?.[this.quasis?.length - 1],
291!
1895
            this.tokens.closingBacktick
1896
        );
1897
    }
1898

1899
    public readonly kind = AstNodeKind.TemplateStringExpression;
49✔
1900

1901
    public readonly tokens: {
1902
        readonly openingBacktick?: Token;
1903
        readonly closingBacktick?: Token;
1904
    };
1905
    public readonly quasis: TemplateStringQuasiExpression[];
1906
    public readonly expressions: Expression[];
1907

1908
    public readonly location: Location | undefined;
1909

1910
    public getType(options: GetTypeOptions) {
1911
        return StringType.instance;
34✔
1912
    }
1913

1914
    transpile(state: BrsTranspileState) {
1915
        if (this.quasis.length === 1 && this.expressions.length === 0) {
20✔
1916
            return this.quasis[0].transpile(state);
10✔
1917
        }
1918
        let result = ['('];
10✔
1919
        let plus = '';
10✔
1920
        //helper function to figure out when to include the plus
1921
        function add(...items) {
1922
            if (items.length > 0) {
40✔
1923
                result.push(
29✔
1924
                    plus,
1925
                    ...items
1926
                );
1927
            }
1928
            //set the plus after the first occurance of a nonzero length set of items
1929
            if (plus === '' && items.length > 0) {
40✔
1930
                plus = ' + ';
10✔
1931
            }
1932
        }
1933

1934
        for (let i = 0; i < this.quasis.length; i++) {
10✔
1935
            let quasi = this.quasis[i];
25✔
1936
            let expression = this.expressions[i];
25✔
1937

1938
            add(
25✔
1939
                ...quasi.transpile(state)
1940
            );
1941
            if (expression) {
25✔
1942
                //skip the toString wrapper around certain expressions
1943
                if (
15✔
1944
                    isEscapedCharCodeLiteralExpression(expression) ||
34✔
1945
                    (isLiteralExpression(expression) && isStringType(expression.getType()))
1946
                ) {
1947
                    add(
3✔
1948
                        ...expression.transpile(state)
1949
                    );
1950

1951
                    //wrap all other expressions with a bslib_toString call to prevent runtime type mismatch errors
1952
                } else {
1953
                    add(
12✔
1954
                        state.bslibPrefix + '_toString(',
1955
                        ...expression.transpile(state),
1956
                        ')'
1957
                    );
1958
                }
1959
            }
1960
        }
1961
        //the expression should be wrapped in parens so it can be used line a single expression at runtime
1962
        result.push(')');
10✔
1963

1964
        return result;
10✔
1965
    }
1966

1967
    walk(visitor: WalkVisitor, options: WalkOptions) {
1968
        if (options.walkMode & InternalWalkMode.walkExpressions) {
180!
1969
            //walk the quasis and expressions in left-to-right order
1970
            for (let i = 0; i < this.quasis?.length; i++) {
180!
1971
                walk(this.quasis, i, visitor, options, this);
306✔
1972

1973
                //this skips the final loop iteration since we'll always have one more quasi than expression
1974
                if (this.expressions[i]) {
306✔
1975
                    walk(this.expressions, i, visitor, options, this);
126✔
1976
                }
1977
            }
1978
        }
1979
    }
1980

1981
    public clone() {
1982
        return this.finalizeClone(
7✔
1983
            new TemplateStringExpression({
1984
                openingBacktick: util.cloneToken(this.tokens.openingBacktick),
1985
                quasis: this.quasis?.map(e => e?.clone()),
12✔
1986
                expressions: this.expressions?.map(e => e?.clone()),
6✔
1987
                closingBacktick: util.cloneToken(this.tokens.closingBacktick)
1988
            }),
1989
            ['quasis', 'expressions']
1990
        );
1991
    }
1992
}
1993

1994
export class TaggedTemplateStringExpression extends Expression {
1✔
1995
    constructor(options: {
1996
        tagName: Identifier;
1997
        openingBacktick?: Token;
1998
        quasis: TemplateStringQuasiExpression[];
1999
        expressions: Expression[];
2000
        closingBacktick?: Token;
2001
    }) {
2002
        super();
12✔
2003
        this.tokens = {
12✔
2004
            tagName: options.tagName,
2005
            openingBacktick: options.openingBacktick,
2006
            closingBacktick: options.closingBacktick
2007
        };
2008
        this.quasis = options.quasis;
12✔
2009
        this.expressions = options.expressions;
12✔
2010

2011
        this.location = util.createBoundingLocation(
12✔
2012
            this.tokens.tagName,
2013
            this.tokens.openingBacktick,
2014
            this.quasis?.[0],
36✔
2015
            this.quasis?.[this.quasis?.length - 1],
69!
2016
            this.tokens.closingBacktick
2017
        );
2018
    }
2019

2020
    public readonly kind = AstNodeKind.TaggedTemplateStringExpression;
12✔
2021

2022
    public readonly tokens: {
2023
        readonly tagName: Identifier;
2024
        readonly openingBacktick?: Token;
2025
        readonly closingBacktick?: Token;
2026
    };
2027

2028
    public readonly quasis: TemplateStringQuasiExpression[];
2029
    public readonly expressions: Expression[];
2030

2031
    public readonly location: Location | undefined;
2032

2033
    transpile(state: BrsTranspileState) {
2034
        let result = [] as TranspileResult;
3✔
2035
        result.push(
3✔
2036
            state.transpileToken(this.tokens.tagName),
2037
            '(['
2038
        );
2039

2040
        //add quasis as the first array
2041
        for (let i = 0; i < this.quasis.length; i++) {
3✔
2042
            let quasi = this.quasis[i];
8✔
2043
            //separate items with a comma
2044
            if (i > 0) {
8✔
2045
                result.push(
5✔
2046
                    ', '
2047
                );
2048
            }
2049
            result.push(
8✔
2050
                ...quasi.transpile(state, false)
2051
            );
2052
        }
2053
        result.push(
3✔
2054
            '], ['
2055
        );
2056

2057
        //add expressions as the second array
2058
        for (let i = 0; i < this.expressions.length; i++) {
3✔
2059
            let expression = this.expressions[i];
5✔
2060
            if (i > 0) {
5✔
2061
                result.push(
2✔
2062
                    ', '
2063
                );
2064
            }
2065
            result.push(
5✔
2066
                ...expression.transpile(state)
2067
            );
2068
        }
2069
        result.push(
3✔
2070
            state.sourceNode(this.tokens.closingBacktick, '])')
2071
        );
2072
        return result;
3✔
2073
    }
2074

2075
    walk(visitor: WalkVisitor, options: WalkOptions) {
2076
        if (options.walkMode & InternalWalkMode.walkExpressions) {
28!
2077
            //walk the quasis and expressions in left-to-right order
2078
            for (let i = 0; i < this.quasis?.length; i++) {
28!
2079
                walk(this.quasis, i, visitor, options, this);
68✔
2080

2081
                //this skips the final loop iteration since we'll always have one more quasi than expression
2082
                if (this.expressions[i]) {
68✔
2083
                    walk(this.expressions, i, visitor, options, this);
40✔
2084
                }
2085
            }
2086
        }
2087
    }
2088

2089
    public clone() {
2090
        return this.finalizeClone(
3✔
2091
            new TaggedTemplateStringExpression({
2092
                tagName: util.cloneToken(this.tokens.tagName),
2093
                openingBacktick: util.cloneToken(this.tokens.openingBacktick),
2094
                quasis: this.quasis?.map(e => e?.clone()),
5✔
2095
                expressions: this.expressions?.map(e => e?.clone()),
3✔
2096
                closingBacktick: util.cloneToken(this.tokens.closingBacktick)
2097
            }),
2098
            ['quasis', 'expressions']
2099
        );
2100
    }
2101
}
2102

2103
export class AnnotationExpression extends Expression {
1✔
2104
    constructor(options: {
2105
        at?: Token;
2106
        name: Token;
2107
        call?: CallExpression;
2108
    }) {
2109
        super();
98✔
2110
        this.tokens = {
98✔
2111
            at: options.at,
2112
            name: options.name
2113
        };
2114
        this.call = options.call;
98✔
2115
        this.name = this.tokens.name.text;
98✔
2116
    }
2117

2118
    public readonly kind = AstNodeKind.AnnotationExpression;
98✔
2119

2120
    public readonly tokens: {
2121
        readonly at: Token;
2122
        readonly name: Token;
2123
    };
2124

2125
    public get location(): Location | undefined {
2126
        return util.createBoundingLocation(
99✔
2127
            this.tokens.at,
2128
            this.tokens.name,
2129
            this.call
2130
        );
2131
    }
2132

2133
    public readonly name: string;
2134

2135
    public call: CallExpression;
2136

2137
    /**
2138
     * Convert annotation arguments to JavaScript types
2139
     * @param strict If false, keep Expression objects not corresponding to JS types
2140
     */
2141
    getArguments(strict = true): ExpressionValue[] {
10✔
2142
        if (!this.call) {
11✔
2143
            return [];
1✔
2144
        }
2145
        return this.call.args.map(e => expressionToValue(e, strict));
20✔
2146
    }
2147

2148
    public get leadingTrivia(): Token[] {
2149
        return this.tokens.at?.leadingTrivia;
95!
2150
    }
2151

2152
    transpile(state: BrsTranspileState) {
2153
        //transpile only our leading comments
2154
        return state.transpileComments(this.leadingTrivia);
16✔
2155
    }
2156

2157
    walk(visitor: WalkVisitor, options: WalkOptions) {
2158
        //nothing to walk
2159
    }
2160
    getTypedef(state: BrsTranspileState) {
2161
        return [
9✔
2162
            '@',
2163
            this.name,
2164
            ...(this.call?.transpile(state) ?? [])
54✔
2165
        ];
2166
    }
2167

2168
    public clone() {
2169
        const clone = this.finalizeClone(
7✔
2170
            new AnnotationExpression({
2171
                at: util.cloneToken(this.tokens.at),
2172
                name: util.cloneToken(this.tokens.name)
2173
            })
2174
        );
2175
        return clone;
7✔
2176
    }
2177
}
2178

2179
export class TernaryExpression extends Expression {
1✔
2180
    constructor(options: {
2181
        test: Expression;
2182
        questionMark?: Token;
2183
        consequent?: Expression;
2184
        colon?: Token;
2185
        alternate?: Expression;
2186
    }) {
2187
        super();
99✔
2188
        this.tokens = {
99✔
2189
            questionMark: options.questionMark,
2190
            colon: options.colon
2191
        };
2192
        this.test = options.test;
99✔
2193
        this.consequent = options.consequent;
99✔
2194
        this.alternate = options.alternate;
99✔
2195
        this.location = util.createBoundingLocation(
99✔
2196
            this.test,
2197
            this.tokens.questionMark,
2198
            this.consequent,
2199
            this.tokens.colon,
2200
            this.alternate
2201
        );
2202
    }
2203

2204
    public readonly kind = AstNodeKind.TernaryExpression;
99✔
2205

2206
    public readonly location: Location | undefined;
2207

2208
    public readonly tokens: {
2209
        readonly questionMark?: Token;
2210
        readonly colon?: Token;
2211
    };
2212

2213
    public readonly test: Expression;
2214
    public readonly consequent?: Expression;
2215
    public readonly alternate?: Expression;
2216

2217
    transpile(state: BrsTranspileState) {
2218
        let result = [] as TranspileResult;
21✔
2219
        const file = state.file;
21✔
2220
        let consequentInfo = util.getExpressionInfo(this.consequent!, file);
21✔
2221
        let alternateInfo = util.getExpressionInfo(this.alternate!, file);
21✔
2222

2223
        //get all unique variable names used in the consequent and alternate, and sort them alphabetically so the output is consistent
2224
        let allUniqueVarNames = [...new Set([...consequentInfo.uniqueVarNames, ...alternateInfo.uniqueVarNames])].sort();
21✔
2225
        let mutatingExpressions = [
21✔
2226
            ...consequentInfo.expressions,
2227
            ...alternateInfo.expressions
2228
        ].filter(e => e instanceof CallExpression || e instanceof CallfuncExpression || e instanceof DottedGetExpression);
104✔
2229

2230
        if (mutatingExpressions.length > 0) {
21✔
2231
            result.push(
9✔
2232
                state.sourceNode(
2233
                    this.tokens.questionMark,
2234
                    //write all the scope variables as parameters.
2235
                    //TODO handle when there are more than 31 parameters
2236
                    `(function(${['__bsCondition', ...allUniqueVarNames].join(', ')})`
2237
                ),
2238
                state.newline,
2239
                //double indent so our `end function` line is still indented one at the end
2240
                state.indent(2),
2241
                state.sourceNode(this.test, `if __bsCondition then`),
2242
                state.newline,
2243
                state.indent(1),
2244
                state.sourceNode(this.consequent ?? this.tokens.questionMark, 'return '),
27!
2245
                ...this.consequent?.transpile(state) ?? [state.sourceNode(this.tokens.questionMark, 'invalid')],
54!
2246
                state.newline,
2247
                state.indent(-1),
2248
                state.sourceNode(this.consequent ?? this.tokens.questionMark, 'else'),
27!
2249
                state.newline,
2250
                state.indent(1),
2251
                state.sourceNode(this.consequent ?? this.tokens.questionMark, 'return '),
27!
2252
                ...this.alternate?.transpile(state) ?? [state.sourceNode(this.consequent ?? this.tokens.questionMark, 'invalid')],
54!
2253
                state.newline,
2254
                state.indent(-1),
2255
                state.sourceNode(this.tokens.questionMark, 'end if'),
2256
                state.newline,
2257
                state.indent(-1),
2258
                state.sourceNode(this.tokens.questionMark, 'end function)('),
2259
                ...this.test.transpile(state),
2260
                state.sourceNode(this.tokens.questionMark, `${['', ...allUniqueVarNames].join(', ')})`)
2261
            );
2262
            state.blockDepth--;
9✔
2263
        } else {
2264
            result.push(
12✔
2265
                state.sourceNode(this.test, state.bslibPrefix + `_ternary(`),
2266
                ...this.test.transpile(state),
2267
                state.sourceNode(this.test, `, `),
2268
                ...this.consequent?.transpile(state) ?? ['invalid'],
72✔
2269
                `, `,
2270
                ...this.alternate?.transpile(state) ?? ['invalid'],
72✔
2271
                `)`
2272
            );
2273
        }
2274
        return result;
21✔
2275
    }
2276

2277
    public walk(visitor: WalkVisitor, options: WalkOptions) {
2278
        if (options.walkMode & InternalWalkMode.walkExpressions) {
387!
2279
            walk(this, 'test', visitor, options);
387✔
2280
            walk(this, 'consequent', visitor, options);
387✔
2281
            walk(this, 'alternate', visitor, options);
387✔
2282
        }
2283
    }
2284

2285
    get leadingTrivia(): Token[] {
2286
        return this.test.leadingTrivia;
254✔
2287
    }
2288

2289
    public clone() {
2290
        return this.finalizeClone(
2✔
2291
            new TernaryExpression({
2292
                test: this.test?.clone(),
6✔
2293
                questionMark: util.cloneToken(this.tokens.questionMark),
2294
                consequent: this.consequent?.clone(),
6✔
2295
                colon: util.cloneToken(this.tokens.colon),
2296
                alternate: this.alternate?.clone()
6✔
2297
            }),
2298
            ['test', 'consequent', 'alternate']
2299
        );
2300
    }
2301
}
2302

2303
export class NullCoalescingExpression extends Expression {
1✔
2304
    constructor(options: {
2305
        consequent: Expression;
2306
        questionQuestion?: Token;
2307
        alternate: Expression;
2308
    }) {
2309
        super();
36✔
2310
        this.tokens = {
36✔
2311
            questionQuestion: options.questionQuestion
2312
        };
2313
        this.consequent = options.consequent;
36✔
2314
        this.alternate = options.alternate;
36✔
2315
        this.location = util.createBoundingLocation(
36✔
2316
            this.consequent,
2317
            this.tokens.questionQuestion,
2318
            this.alternate
2319
        );
2320
    }
2321

2322
    public readonly kind = AstNodeKind.NullCoalescingExpression;
36✔
2323

2324
    public readonly location: Location | undefined;
2325

2326
    public readonly tokens: {
2327
        readonly questionQuestion?: Token;
2328
    };
2329

2330
    public readonly consequent: Expression;
2331
    public readonly alternate: Expression;
2332

2333
    transpile(state: BrsTranspileState) {
2334
        let result = [] as TranspileResult;
10✔
2335
        let consequentInfo = util.getExpressionInfo(this.consequent, state.file);
10✔
2336
        let alternateInfo = util.getExpressionInfo(this.alternate, state.file);
10✔
2337

2338
        //get all unique variable names used in the consequent and alternate, and sort them alphabetically so the output is consistent
2339
        let allUniqueVarNames = [...new Set([...consequentInfo.uniqueVarNames, ...alternateInfo.uniqueVarNames])].sort();
10✔
2340
        let hasMutatingExpression = [
10✔
2341
            ...consequentInfo.expressions,
2342
            ...alternateInfo.expressions
2343
        ].find(e => isCallExpression(e) || isCallfuncExpression(e) || isDottedGetExpression(e));
28✔
2344

2345
        if (hasMutatingExpression) {
10✔
2346
            result.push(
6✔
2347
                `(function(`,
2348
                //write all the scope variables as parameters.
2349
                //TODO handle when there are more than 31 parameters
2350
                allUniqueVarNames.join(', '),
2351
                ')',
2352
                state.newline,
2353
                //double indent so our `end function` line is still indented one at the end
2354
                state.indent(2),
2355
                //evaluate the consequent exactly once, and then use it in the following condition
2356
                `__bsConsequent = `,
2357
                ...this.consequent.transpile(state),
2358
                state.newline,
2359
                state.indent(),
2360
                `if __bsConsequent <> invalid then`,
2361
                state.newline,
2362
                state.indent(1),
2363
                'return __bsConsequent',
2364
                state.newline,
2365
                state.indent(-1),
2366
                'else',
2367
                state.newline,
2368
                state.indent(1),
2369
                'return ',
2370
                ...this.alternate.transpile(state),
2371
                state.newline,
2372
                state.indent(-1),
2373
                'end if',
2374
                state.newline,
2375
                state.indent(-1),
2376
                'end function)(',
2377
                allUniqueVarNames.join(', '),
2378
                ')'
2379
            );
2380
            state.blockDepth--;
6✔
2381
        } else {
2382
            result.push(
4✔
2383
                state.bslibPrefix + `_coalesce(`,
2384
                ...this.consequent.transpile(state),
2385
                ', ',
2386
                ...this.alternate.transpile(state),
2387
                ')'
2388
            );
2389
        }
2390
        return result;
10✔
2391
    }
2392

2393
    public walk(visitor: WalkVisitor, options: WalkOptions) {
2394
        if (options.walkMode & InternalWalkMode.walkExpressions) {
92!
2395
            walk(this, 'consequent', visitor, options);
92✔
2396
            walk(this, 'alternate', visitor, options);
92✔
2397
        }
2398
    }
2399

2400
    get leadingTrivia(): Token[] {
2401
        return this.consequent.leadingTrivia;
54✔
2402
    }
2403

2404
    public clone() {
2405
        return this.finalizeClone(
2✔
2406
            new NullCoalescingExpression({
2407
                consequent: this.consequent?.clone(),
6✔
2408
                questionQuestion: util.cloneToken(this.tokens.questionQuestion),
2409
                alternate: this.alternate?.clone()
6✔
2410
            }),
2411
            ['consequent', 'alternate']
2412
        );
2413
    }
2414
}
2415

2416
export class RegexLiteralExpression extends Expression {
1✔
2417
    constructor(options: {
2418
        regexLiteral: Token;
2419
    }) {
2420
        super();
46✔
2421
        this.tokens = {
46✔
2422
            regexLiteral: options.regexLiteral
2423
        };
2424
    }
2425

2426
    public readonly kind = AstNodeKind.RegexLiteralExpression;
46✔
2427
    public readonly tokens: {
2428
        readonly regexLiteral: Token;
2429
    };
2430

2431
    public get location(): Location {
2432
        return this.tokens?.regexLiteral?.location;
150!
2433
    }
2434

2435
    public transpile(state: BrsTranspileState): TranspileResult {
2436
        let text = this.tokens.regexLiteral?.text ?? '';
42!
2437
        let flags = '';
42✔
2438
        //get any flags from the end
2439
        const flagMatch = /\/([a-z]+)$/i.exec(text);
42✔
2440
        if (flagMatch) {
42✔
2441
            text = text.substring(0, flagMatch.index + 1);
2✔
2442
            flags = flagMatch[1];
2✔
2443
        }
2444
        let pattern = text
42✔
2445
            //remove leading and trailing slashes
2446
            .substring(1, text.length - 1)
2447
            //escape quotemarks
2448
            .split('"').join('" + chr(34) + "');
2449

2450
        return [
42✔
2451
            state.sourceNode(this.tokens.regexLiteral, [
2452
                'CreateObject("roRegex", ',
2453
                `"${pattern}", `,
2454
                `"${flags}"`,
2455
                ')'
2456
            ])
2457
        ];
2458
    }
2459

2460
    walk(visitor: WalkVisitor, options: WalkOptions) {
2461
        //nothing to walk
2462
    }
2463

2464
    public clone() {
2465
        return this.finalizeClone(
1✔
2466
            new RegexLiteralExpression({
2467
                regexLiteral: util.cloneToken(this.tokens.regexLiteral)
2468
            })
2469
        );
2470
    }
2471

2472
    get leadingTrivia(): Token[] {
2473
        return this.tokens.regexLiteral?.leadingTrivia;
1,038!
2474
    }
2475
}
2476

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

2480
function expressionToValue(expr: Expression, strict: boolean): ExpressionValue {
2481
    if (!expr) {
30!
UNCOV
2482
        return null;
×
2483
    }
2484
    if (isUnaryExpression(expr) && isLiteralNumber(expr.right)) {
30✔
2485
        return numberExpressionToValue(expr.right, expr.tokens.operator.text);
1✔
2486
    }
2487
    if (isLiteralString(expr)) {
29✔
2488
        //remove leading and trailing quotes
2489
        return expr.tokens.value.text.replace(/^"/, '').replace(/"$/, '');
5✔
2490
    }
2491
    if (isLiteralNumber(expr)) {
24✔
2492
        return numberExpressionToValue(expr);
11✔
2493
    }
2494

2495
    if (isLiteralBoolean(expr)) {
13✔
2496
        return expr.tokens.value.text.toLowerCase() === 'true';
3✔
2497
    }
2498
    if (isArrayLiteralExpression(expr)) {
10✔
2499
        return expr.elements
3✔
2500
            .map(e => expressionToValue(e, strict));
7✔
2501
    }
2502
    if (isAALiteralExpression(expr)) {
7✔
2503
        return expr.elements.reduce((acc, e) => {
3✔
2504
            acc[e.tokens.key.text] = expressionToValue(e.value, strict);
3✔
2505
            return acc;
3✔
2506
        }, {});
2507
    }
2508
    //for annotations, we only support serializing pure string values
2509
    if (isTemplateStringExpression(expr)) {
4✔
2510
        if (expr.quasis?.length === 1 && expr.expressions.length === 0) {
2!
2511
            return expr.quasis[0].expressions.map(x => x.tokens.value.text).join('');
10✔
2512
        }
2513
    }
2514
    return strict ? null : expr;
2✔
2515
}
2516

2517
function numberExpressionToValue(expr: LiteralExpression, operator = '') {
11✔
2518
    if (isIntegerType(expr.getType()) || isLongIntegerType(expr.getType())) {
12!
2519
        return parseInt(operator + expr.tokens.value.text);
12✔
2520
    } else {
UNCOV
2521
        return parseFloat(operator + expr.tokens.value.text);
×
2522
    }
2523
}
2524

2525
export class TypeExpression extends Expression implements TypedefProvider {
1✔
2526
    constructor(options: {
2527
        /**
2528
         * The standard AST expression that represents the type for this TypeExpression.
2529
         */
2530
        expression: Expression;
2531
    }) {
2532
        super();
1,500✔
2533
        this.expression = options.expression;
1,500✔
2534
        this.location = util.cloneLocation(this.expression?.location);
1,500!
2535
    }
2536

2537
    public readonly kind = AstNodeKind.TypeExpression;
1,500✔
2538

2539
    /**
2540
     * The standard AST expression that represents the type for this TypeExpression.
2541
     */
2542
    public readonly expression: Expression;
2543

2544
    public readonly location: Location;
2545

2546
    public transpile(state: BrsTranspileState): TranspileResult {
2547
        return [this.getType({ flags: SymbolTypeFlag.typetime }).toTypeString()];
106✔
2548
    }
2549
    public walk(visitor: WalkVisitor, options: WalkOptions) {
2550
        if (options.walkMode & InternalWalkMode.walkExpressions) {
7,039✔
2551
            walk(this, 'expression', visitor, options);
6,914✔
2552
        }
2553
    }
2554

2555
    public getType(options: GetTypeOptions): BscType {
2556
        return this.expression.getType({ ...options, flags: SymbolTypeFlag.typetime });
5,373✔
2557
    }
2558

2559
    getTypedef(state: TranspileState): TranspileResult {
2560
        // TypeDefs should pass through any valid type names
2561
        return this.expression.transpile(state as BrsTranspileState);
33✔
2562
    }
2563

2564
    getName(parseMode = ParseMode.BrighterScript): string {
195✔
2565
        //TODO: this may not support Complex Types, eg. generics or Unions
2566
        return util.getAllDottedGetPartsAsString(this.expression, parseMode);
195✔
2567
    }
2568

2569
    getNameParts(): string[] {
2570
        //TODO: really, this code is only used to get Namespaces. It could be more clear.
2571
        return util.getAllDottedGetParts(this.expression).map(x => x.text);
20✔
2572
    }
2573

2574
    public clone() {
2575
        return this.finalizeClone(
15✔
2576
            new TypeExpression({
2577
                expression: this.expression?.clone()
45!
2578
            }),
2579
            ['expression']
2580
        );
2581
    }
2582
}
2583

2584
export class TypecastExpression extends Expression {
1✔
2585
    constructor(options: {
2586
        obj: Expression;
2587
        as?: Token;
2588
        typeExpression?: TypeExpression;
2589
    }) {
2590
        super();
68✔
2591
        this.tokens = {
68✔
2592
            as: options.as
2593
        };
2594
        this.obj = options.obj;
68✔
2595
        this.typeExpression = options.typeExpression;
68✔
2596
        this.location = util.createBoundingLocation(
68✔
2597
            this.obj,
2598
            this.tokens.as,
2599
            this.typeExpression
2600
        );
2601
    }
2602

2603
    public readonly kind = AstNodeKind.TypecastExpression;
68✔
2604

2605
    public readonly obj: Expression;
2606

2607
    public readonly tokens: {
2608
        readonly as?: Token;
2609
    };
2610

2611
    public typeExpression?: TypeExpression;
2612

2613
    public readonly location: Location;
2614

2615
    public transpile(state: BrsTranspileState): TranspileResult {
2616
        return this.obj.transpile(state);
13✔
2617
    }
2618
    public walk(visitor: WalkVisitor, options: WalkOptions) {
2619
        if (options.walkMode & InternalWalkMode.walkExpressions) {
330!
2620
            walk(this, 'obj', visitor, options);
330✔
2621
            walk(this, 'typeExpression', visitor, options);
330✔
2622
        }
2623
    }
2624

2625
    public getType(options: GetTypeOptions): BscType {
2626
        const result = this.typeExpression.getType(options);
80✔
2627
        if (options.typeChain) {
80✔
2628
            // modify last typechain entry to show it is a typecast
2629
            const lastEntry = options.typeChain[options.typeChain.length - 1];
18✔
2630
            if (lastEntry) {
18!
2631
                lastEntry.astNode = this;
18✔
2632
            }
2633
        }
2634
        return result;
80✔
2635
    }
2636

2637
    public clone() {
2638
        return this.finalizeClone(
3✔
2639
            new TypecastExpression({
2640
                obj: this.obj?.clone(),
9✔
2641
                as: util.cloneToken(this.tokens.as),
2642
                typeExpression: this.typeExpression?.clone()
9!
2643
            }),
2644
            ['obj', 'typeExpression']
2645
        );
2646
    }
2647
}
2648

2649
export class TypedArrayExpression extends Expression {
1✔
2650
    constructor(options: {
2651
        innerType: Expression;
2652
        leftBracket?: Token;
2653
        rightBracket?: Token;
2654
    }) {
2655
        super();
29✔
2656
        this.tokens = {
29✔
2657
            leftBracket: options.leftBracket,
2658
            rightBracket: options.rightBracket
2659
        };
2660
        this.innerType = options.innerType;
29✔
2661
        this.location = util.createBoundingLocation(
29✔
2662
            this.innerType,
2663
            this.tokens.leftBracket,
2664
            this.tokens.rightBracket
2665
        );
2666
    }
2667

2668
    public readonly tokens: {
2669
        readonly leftBracket?: Token;
2670
        readonly rightBracket?: Token;
2671
    };
2672

2673
    public readonly innerType: Expression;
2674

2675
    public readonly kind = AstNodeKind.TypedArrayExpression;
29✔
2676

2677
    public readonly location: Location;
2678

2679
    public transpile(state: BrsTranspileState): TranspileResult {
UNCOV
2680
        return [this.getType({ flags: SymbolTypeFlag.typetime }).toTypeString()];
×
2681
    }
2682

2683
    public walk(visitor: WalkVisitor, options: WalkOptions) {
2684
        if (options.walkMode & InternalWalkMode.walkExpressions) {
129!
2685
            walk(this, 'innerType', visitor, options);
129✔
2686
        }
2687
    }
2688

2689
    public getType(options: GetTypeOptions): BscType {
2690
        return new ArrayType(this.innerType.getType(options));
122✔
2691
    }
2692

2693
    public clone() {
UNCOV
2694
        return this.finalizeClone(
×
2695
            new TypedArrayExpression({
2696
                innerType: this.innerType?.clone(),
×
2697
                leftBracket: util.cloneToken(this.tokens.leftBracket),
2698
                rightBracket: util.cloneToken(this.tokens.rightBracket)
2699
            }),
2700
            ['innerType']
2701
        );
2702
    }
2703
}
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