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

rokucommunity / brighterscript / #13182

14 Oct 2024 03:33PM UTC coverage: 86.831%. Remained the same
#13182

push

web-flow
Merge 7db2dd331 into d585c29e9

11596 of 14122 branches covered (82.11%)

Branch coverage included in aggregate %.

26 of 27 new or added lines in 6 files covered. (96.3%)

112 existing lines in 4 files now uncovered.

12721 of 13883 relevant lines covered (91.63%)

29829.73 hits per line

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

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

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

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

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

60
    public readonly kind = AstNodeKind.BinaryExpression;
3,139✔
61

62
    public readonly location: Location | undefined;
63

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

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

81

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

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

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

116

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

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

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

152
    public readonly kind = AstNodeKind.CallExpression;
2,340✔
153

154
    public readonly location: Location | undefined;
155

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

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

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

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

196
    getType(options: GetTypeOptions) {
197
        const calleeType = this.callee.getType(options);
965✔
198
        if (options.ignoreCall) {
965!
199
            return calleeType;
×
200
        }
201
        if (isNewExpression(this.parent)) {
965✔
202
            return calleeType;
322✔
203
        }
204
        const specialCaseReturnType = util.getSpecialCaseCallExpressionReturnType(this);
643✔
205
        if (specialCaseReturnType) {
643✔
206
            return specialCaseReturnType;
102✔
207
        }
208
        if (isCallableType(calleeType) && (!isReferenceType(calleeType.returnType) || calleeType.returnType?.isResolvable())) {
541!
209
            return calleeType.returnType;
240✔
210
        }
211
        if (!isReferenceType(calleeType) && (calleeType as BaseFunctionType)?.returnType?.isResolvable()) {
301✔
212
            return (calleeType as BaseFunctionType).returnType;
257✔
213
        }
214
        return new TypePropertyReferenceType(calleeType, 'returnType');
44✔
215
    }
216

217
    get leadingTrivia(): Token[] {
218
        return this.callee.leadingTrivia;
8,049✔
219
    }
220

221
    public clone() {
222
        return this.finalizeClone(
7✔
223
            new CallExpression({
224
                callee: this.callee?.clone(),
21✔
225
                openingParen: util.cloneToken(this.tokens.openingParen),
226
                closingParen: util.cloneToken(this.tokens.closingParen),
227
                args: this.args?.map(e => e?.clone())
6✔
228
            }),
229
            ['callee', 'args']
230
        );
231
    }
232
}
233

234
export class FunctionExpression extends Expression implements TypedefProvider {
1✔
235
    constructor(options: {
236
        functionType?: Token;
237
        leftParen?: Token;
238
        parameters?: FunctionParameterExpression[];
239
        rightParen?: Token;
240
        as?: Token;
241
        returnTypeExpression?: TypeExpression;
242
        body: Block;
243
        endFunctionType?: Token;
244
    }) {
245
        super();
3,717✔
246
        this.tokens = {
3,717✔
247
            functionType: options.functionType,
248
            leftParen: options.leftParen,
249
            rightParen: options.rightParen,
250
            as: options.as,
251
            endFunctionType: options.endFunctionType
252
        };
253
        this.parameters = options.parameters ?? [];
3,717✔
254
        this.body = options.body;
3,717✔
255
        this.returnTypeExpression = options.returnTypeExpression;
3,717✔
256
        //if there's a body, and it doesn't have a SymbolTable, assign one
257
        if (this.body) {
3,717✔
258
            if (!this.body.symbolTable) {
3,716!
259
                this.body.symbolTable = new SymbolTable(`Block`, () => this.getSymbolTable());
26,925✔
260
            } else {
261
                this.body.symbolTable.pushParentProvider(() => this.getSymbolTable());
×
262
            }
263
            this.body.parent = this;
3,716✔
264
        }
265
        this.symbolTable = new SymbolTable('FunctionExpression', () => this.parent?.getSymbolTable());
33,274!
266
    }
267

268
    public readonly kind = AstNodeKind.FunctionExpression;
3,717✔
269

270
    readonly parameters: FunctionParameterExpression[];
271
    public readonly body: Block;
272
    public readonly returnTypeExpression?: TypeExpression;
273

274
    readonly tokens: {
275
        readonly functionType?: Token;
276
        readonly endFunctionType?: Token;
277
        readonly leftParen?: Token;
278
        readonly rightParen?: Token;
279
        readonly as?: Token;
280
    };
281

282
    public get leadingTrivia(): Token[] {
283
        return this.tokens.functionType?.leadingTrivia;
26,583!
284
    }
285

286
    public get endTrivia(): Token[] {
287
        return this.tokens.endFunctionType?.leadingTrivia;
2!
288
    }
289

290
    /**
291
     * The range of the function, starting at the 'f' in function or 's' in sub (or the open paren if the keyword is missing),
292
     * and ending with the last n' in 'end function' or 'b' in 'end sub'
293
     */
294
    public get location(): Location {
295
        return util.createBoundingLocation(
9,557✔
296
            this.tokens.functionType,
297
            this.tokens.leftParen,
298
            ...this.parameters ?? [],
28,671!
299
            this.tokens.rightParen,
300
            this.tokens.as,
301
            this.returnTypeExpression,
302
            this.tokens.endFunctionType
303
        );
304
    }
305

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

357
        const lastLocatable = hasBody ? this.body : this.returnTypeExpression ?? this.tokens.leftParen ?? this.tokens.functionType;
1,410!
358
        results.push(
1,410✔
359
            ...state.transpileEndBlockToken(lastLocatable, this.tokens.endFunctionType, `end ${this.tokens.functionType ?? 'function'}`)
4,230!
360
        );
361
        return results;
1,410✔
362
    }
363

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

399
    walk(visitor: WalkVisitor, options: WalkOptions) {
400
        if (options.walkMode & InternalWalkMode.walkExpressions) {
15,984!
401
            walkArray(this.parameters, visitor, options, this);
15,984✔
402
            walk(this, 'returnTypeExpression', visitor, options);
15,984✔
403
            //This is the core of full-program walking...it allows us to step into sub functions
404
            if (options.walkMode & InternalWalkMode.recurseChildFunctions) {
15,984✔
405
                walk(this, 'body', visitor, options);
15,980✔
406
            }
407
        }
408
    }
409

410
    public getType(options: GetTypeOptions): TypedFunctionType {
411
        //if there's a defined return type, use that
412
        let returnType: BscType;
413

414
        const docs = brsDocParser.parseNode(this.findAncestor(isFunctionStatement));
3,941✔
415

416
        returnType = util.chooseTypeFromCodeOrDocComment(
3,941✔
417
            this.returnTypeExpression?.getType({ ...options, typeChain: undefined }),
11,823✔
418
            docs.getReturnBscType({ ...options, tableProvider: () => this.getSymbolTable() }),
×
419
            options
420
        );
421

422
        const isSub = this.tokens.functionType?.kind === TokenKind.Sub;
3,941!
423
        //if we don't have a return type and this is a sub, set the return type to `void`. else use `dynamic`
424
        if (!returnType) {
3,941✔
425
            returnType = isSub ? VoidType.instance : DynamicType.instance;
3,253✔
426
        }
427

428
        const resultType = new TypedFunctionType(returnType);
3,941✔
429
        resultType.isSub = isSub;
3,941✔
430
        for (let param of this.parameters) {
3,941✔
431
            resultType.addParameter(param.tokens.name.text, param.getType({ ...options, typeChain: undefined }), !!param.defaultValue);
2,038✔
432
        }
433
        // Figure out this function's name if we can
434
        let funcName = '';
3,941✔
435
        if (isMethodStatement(this.parent) || isInterfaceMethodStatement(this.parent)) {
3,941✔
436
            funcName = this.parent.getName(ParseMode.BrighterScript);
343✔
437
            if (options.typeChain) {
343✔
438
                // Get the typechain info from the parent class
439
                this.parent.parent?.getType(options);
1!
440
            }
441
        } else if (isFunctionStatement(this.parent)) {
3,598✔
442
            funcName = this.parent.getName(ParseMode.BrighterScript);
3,530✔
443
        }
444
        if (funcName) {
3,941✔
445
            resultType.setName(funcName);
3,873✔
446
        }
447
        options.typeChain?.push(new TypeChainEntry({ name: funcName, type: resultType, data: options.data, astNode: this }));
3,941✔
448
        return resultType;
3,941✔
449
    }
450

451
    public clone() {
452
        return this.finalizeClone(
110✔
453
            new FunctionExpression({
454
                parameters: this.parameters?.map(e => e?.clone()),
7✔
455
                body: this.body?.clone(),
330✔
456
                functionType: util.cloneToken(this.tokens.functionType),
457
                endFunctionType: util.cloneToken(this.tokens.endFunctionType),
458
                leftParen: util.cloneToken(this.tokens.leftParen),
459
                rightParen: util.cloneToken(this.tokens.rightParen),
460
                as: util.cloneToken(this.tokens.as),
461
                returnTypeExpression: this.returnTypeExpression?.clone()
330✔
462
            }),
463
            ['body', 'returnTypeExpression']
464
        );
465
    }
466
}
467

468
export class FunctionParameterExpression extends Expression {
1✔
469
    constructor(options: {
470
        name: Identifier;
471
        equals?: Token;
472
        defaultValue?: Expression;
473
        as?: Token;
474
        typeExpression?: TypeExpression;
475
    }) {
476
        super();
2,900✔
477
        this.tokens = {
2,900✔
478
            name: options.name,
479
            equals: options.equals,
480
            as: options.as
481
        };
482
        this.defaultValue = options.defaultValue;
2,900✔
483
        this.typeExpression = options.typeExpression;
2,900✔
484
    }
485

486
    public readonly kind = AstNodeKind.FunctionParameterExpression;
2,900✔
487

488
    readonly tokens: {
489
        readonly name: Identifier;
490
        readonly equals?: Token;
491
        readonly as?: Token;
492
    };
493

494
    public readonly defaultValue?: Expression;
495
    public readonly typeExpression?: TypeExpression;
496

497
    public getType(options: GetTypeOptions) {
498
        const docs = brsDocParser.parseNode(this.findAncestor(isFunctionStatement));
5,216✔
499
        const paramName = this.tokens.name.text;
5,216✔
500

501
        const paramTypeFromCode = this.typeExpression?.getType({ ...options, flags: SymbolTypeFlag.typetime, typeChain: undefined }) ??
5,216✔
502
            this.defaultValue?.getType({ ...options, flags: SymbolTypeFlag.runtime, typeChain: undefined });
8,175✔
503
        const paramTypeFromDoc = docs.getParamBscType(paramName, { ...options, fullName: paramName, tableProvider: () => this.getSymbolTable() });
5,216✔
504

505
        let paramType = util.chooseTypeFromCodeOrDocComment(paramTypeFromCode, paramTypeFromDoc, options) ?? DynamicType.instance;
5,216✔
506
        options.typeChain?.push(new TypeChainEntry({ name: paramName, type: paramType, data: options.data, astNode: this }));
5,216✔
507
        return paramType;
5,216✔
508
    }
509

510
    public get location(): Location | undefined {
511
        return util.createBoundingLocation(
8,502✔
512
            this.tokens.name,
513
            this.tokens.as,
514
            this.typeExpression,
515
            this.tokens.equals,
516
            this.defaultValue
517
        );
518
    }
519

520
    public transpile(state: BrsTranspileState) {
521
        let result: TranspileResult = [
2,140✔
522
            //name
523
            state.transpileToken(this.tokens.name)
524
        ];
525
        //default value
526
        if (this.defaultValue) {
2,140✔
527
            result.push(' = ');
9✔
528
            result.push(this.defaultValue.transpile(state));
9✔
529
        }
530
        //type declaration
531
        if (this.typeExpression && !state.options.removeParameterTypes) {
2,140✔
532
            result.push(' ');
65✔
533
            result.push(state.transpileToken(this.tokens.as, 'as'));
65✔
534
            result.push(' ');
65✔
535
            result.push(
65✔
536
                ...(this.typeExpression?.transpile(state) ?? [])
390!
537
            );
538
        }
539

540
        return result;
2,140✔
541
    }
542

543
    public getTypedef(state: BrsTranspileState): TranspileResult {
544
        const results = [this.tokens.name.text] as TranspileResult;
73✔
545

546
        if (this.defaultValue) {
73!
547
            results.push(' = ', ...this.defaultValue.transpile(state));
×
548
        }
549

550
        if (this.tokens.as) {
73✔
551
            results.push(' as ');
6✔
552

553
            // TODO: Is this conditional needed? Will typeToken always exist
554
            // so long as `asToken` exists?
555
            if (this.typeExpression) {
6!
556
                results.push(...(this.typeExpression?.getTypedef(state) ?? ['']));
6!
557
            }
558
        }
559

560
        return results;
73✔
561
    }
562

563
    walk(visitor: WalkVisitor, options: WalkOptions) {
564
        // eslint-disable-next-line no-bitwise
565
        if (options.walkMode & InternalWalkMode.walkExpressions) {
12,045!
566
            walk(this, 'defaultValue', visitor, options);
12,045✔
567
            walk(this, 'typeExpression', visitor, options);
12,045✔
568
        }
569
    }
570

571
    get leadingTrivia(): Token[] {
572
        return this.tokens.name.leadingTrivia;
4,422✔
573
    }
574

575
    public clone() {
576
        return this.finalizeClone(
8✔
577
            new FunctionParameterExpression({
578
                name: util.cloneToken(this.tokens.name),
579
                as: util.cloneToken(this.tokens.as),
580
                typeExpression: this.typeExpression?.clone(),
24✔
581
                equals: util.cloneToken(this.tokens.equals),
582
                defaultValue: this.defaultValue?.clone()
24✔
583
            }),
584
            ['typeExpression', 'defaultValue']
585
        );
586
    }
587
}
588

589
export class DottedGetExpression extends Expression {
1✔
590
    constructor(options: {
591
        obj: Expression;
592
        name: Identifier;
593
        /**
594
         * Can either be `.`, or `?.` for optional chaining - defaults in transpile to '.'
595
         */
596
        dot?: Token;
597
    }) {
598
        super();
2,737✔
599
        this.tokens = {
2,737✔
600
            name: options.name,
601
            dot: options.dot
602
        };
603
        this.obj = options.obj;
2,737✔
604

605
        this.location = util.createBoundingLocation(this.obj, this.tokens.dot, this.tokens.name);
2,737✔
606
    }
607

608
    readonly tokens: {
609
        readonly name: Identifier;
610
        readonly dot?: Token;
611
    };
612
    readonly obj: Expression;
613

614
    public readonly kind = AstNodeKind.DottedGetExpression;
2,737✔
615

616
    public readonly location: Location | undefined;
617

618
    transpile(state: BrsTranspileState) {
619
        //if the callee starts with a namespace name, transpile the name
620
        if (state.file.calleeStartsWithNamespace(this)) {
891✔
621
            return [
9✔
622
                ...state.transpileLeadingCommentsForAstNode(this),
623
                state.sourceNode(this, this.getName(ParseMode.BrightScript))
624
            ];
625
        } else {
626
            return [
882✔
627
                ...this.obj.transpile(state),
628
                state.transpileToken(this.tokens.dot, '.'),
629
                state.transpileToken(this.tokens.name)
630
            ];
631
        }
632
    }
633

634
    walk(visitor: WalkVisitor, options: WalkOptions) {
635
        if (options.walkMode & InternalWalkMode.walkExpressions) {
10,347!
636
            walk(this, 'obj', visitor, options);
10,347✔
637
        }
638
    }
639

640
    getType(options: GetTypeOptions) {
641
        const objType = this.obj?.getType(options);
6,138!
642
        let result = objType?.getMemberType(this.tokens.name?.text, options);
6,138!
643

644
        if (util.isClassUsedAsFunction(result, this, options)) {
6,138✔
645
            // treat this class constructor as a function
646
            result = FunctionType.instance;
11✔
647
        }
648
        options.typeChain?.push(new TypeChainEntry({
6,138✔
649
            name: this.tokens.name?.text,
7,827!
650
            type: result,
651
            data: options.data,
652
            location: this.tokens.name?.location ?? this.location,
15,654!
653
            astNode: this
654
        }));
655
        if (result ||
6,138✔
656
            options.flags & SymbolTypeFlag.typetime ||
657
            (isPrimitiveType(objType) || isCallableType(objType))) {
658
            // All types should be known at typeTime, or the obj is well known
659
            return result;
6,107✔
660
        }
661
        // It is possible at runtime that a value has been added dynamically to an object, or something
662
        // TODO: maybe have a strict flag on this?
663
        return DynamicType.instance;
31✔
664
    }
665

666
    getName(parseMode: ParseMode) {
667
        return util.getAllDottedGetPartsAsString(this, parseMode);
35✔
668
    }
669

670
    get leadingTrivia(): Token[] {
671
        return this.obj.leadingTrivia;
14,127✔
672
    }
673

674
    public clone() {
675
        return this.finalizeClone(
7✔
676
            new DottedGetExpression({
677
                obj: this.obj?.clone(),
21✔
678
                dot: util.cloneToken(this.tokens.dot),
679
                name: util.cloneToken(this.tokens.name)
680
            }),
681
            ['obj']
682
        );
683
    }
684
}
685

686
export class XmlAttributeGetExpression extends Expression {
1✔
687
    constructor(options: {
688
        obj: Expression;
689
        /**
690
         * Can either be `@`, or `?@` for optional chaining - defaults to '@'
691
         */
692
        at?: Token;
693
        name: Identifier;
694
    }) {
695
        super();
14✔
696
        this.obj = options.obj;
14✔
697
        this.tokens = { at: options.at, name: options.name };
14✔
698
        this.location = util.createBoundingLocation(this.obj, this.tokens.at, this.tokens.name);
14✔
699
    }
700

701
    public readonly kind = AstNodeKind.XmlAttributeGetExpression;
14✔
702

703
    public readonly tokens: {
704
        name: Identifier;
705
        at?: Token;
706
    };
707

708
    public readonly obj: Expression;
709

710
    public readonly location: Location | undefined;
711

712
    transpile(state: BrsTranspileState) {
713
        return [
3✔
714
            ...this.obj.transpile(state),
715
            state.transpileToken(this.tokens.at, '@'),
716
            state.transpileToken(this.tokens.name)
717
        ];
718
    }
719

720
    walk(visitor: WalkVisitor, options: WalkOptions) {
721
        if (options.walkMode & InternalWalkMode.walkExpressions) {
30!
722
            walk(this, 'obj', visitor, options);
30✔
723
        }
724
    }
725

726
    get leadingTrivia(): Token[] {
727
        return this.obj.leadingTrivia;
21✔
728
    }
729

730
    public clone() {
731
        return this.finalizeClone(
2✔
732
            new XmlAttributeGetExpression({
733
                obj: this.obj?.clone(),
6✔
734
                at: util.cloneToken(this.tokens.at),
735
                name: util.cloneToken(this.tokens.name)
736
            }),
737
            ['obj']
738
        );
739
    }
740
}
741

742
export class IndexedGetExpression extends Expression {
1✔
743
    constructor(options: {
744
        obj: Expression;
745
        indexes: Expression[];
746
        /**
747
         * Can either be `[` or `?[`. If `?.[` is used, this will be `[` and `optionalChainingToken` will be `?.` - defaults to '[' in transpile
748
         */
749
        openingSquare?: Token;
750
        closingSquare?: Token;
751
        questionDot?: Token;//  ? or ?.
752
    }) {
753
        super();
159✔
754
        this.tokens = {
159✔
755
            openingSquare: options.openingSquare,
756
            closingSquare: options.closingSquare,
757
            questionDot: options.questionDot
758
        };
759
        this.obj = options.obj;
159✔
760
        this.indexes = options.indexes;
159✔
761
        this.location = util.createBoundingLocation(
159✔
762
            this.obj,
763
            this.tokens.openingSquare,
764
            this.tokens.questionDot,
765
            this.tokens.openingSquare,
766
            ...this.indexes ?? [],
477✔
767
            this.tokens.closingSquare
768
        );
769
    }
770

771
    public readonly kind = AstNodeKind.IndexedGetExpression;
159✔
772

773
    public readonly obj: Expression;
774
    public readonly indexes: Expression[];
775

776
    readonly tokens: {
777
        /**
778
         * Can either be `[` or `?[`. If `?.[` is used, this will be `[` and `optionalChainingToken` will be `?.` - defaults to '[' in transpile
779
         */
780
        readonly openingSquare?: Token;
781
        readonly closingSquare?: Token;
782
        readonly questionDot?: Token; //  ? or ?.
783
    };
784

785
    public readonly location: Location | undefined;
786

787
    transpile(state: BrsTranspileState) {
788
        const result = [];
64✔
789
        result.push(
64✔
790
            ...this.obj.transpile(state),
791
            this.tokens.questionDot ? state.transpileToken(this.tokens.questionDot) : '',
64✔
792
            state.transpileToken(this.tokens.openingSquare, '[')
793
        );
794
        for (let i = 0; i < this.indexes.length; i++) {
64✔
795
            //add comma between indexes
796
            if (i > 0) {
72✔
797
                result.push(', ');
8✔
798
            }
799
            let index = this.indexes[i];
72✔
800
            result.push(
72✔
801
                ...(index?.transpile(state) ?? [])
432!
802
            );
803
        }
804
        result.push(
64✔
805
            state.transpileToken(this.tokens.closingSquare, ']')
806
        );
807
        return result;
64✔
808
    }
809

810
    walk(visitor: WalkVisitor, options: WalkOptions) {
811
        if (options.walkMode & InternalWalkMode.walkExpressions) {
500!
812
            walk(this, 'obj', visitor, options);
500✔
813
            walkArray(this.indexes, visitor, options, this);
500✔
814
        }
815
    }
816

817
    getType(options: GetTypeOptions): BscType {
818
        const objType = this.obj.getType(options);
201✔
819
        if (isArrayType(objType)) {
201✔
820
            // This is used on an array. What is the default type of that array?
821
            return objType.defaultType;
10✔
822
        }
823
        return super.getType(options);
191✔
824
    }
825

826
    get leadingTrivia(): Token[] {
827
        return this.obj.leadingTrivia;
1,040✔
828
    }
829

830
    public clone() {
831
        return this.finalizeClone(
5✔
832
            new IndexedGetExpression({
833
                obj: this.obj?.clone(),
15✔
834
                questionDot: util.cloneToken(this.tokens.questionDot),
835
                openingSquare: util.cloneToken(this.tokens.openingSquare),
836
                indexes: this.indexes?.map(x => x?.clone()),
6✔
837
                closingSquare: util.cloneToken(this.tokens.closingSquare)
838
            }),
839
            ['obj', 'indexes']
840
        );
841
    }
842
}
843

844
export class GroupingExpression extends Expression {
1✔
845
    constructor(options: {
846
        leftParen?: Token;
847
        rightParen?: Token;
848
        expression: Expression;
849
    }) {
850
        super();
50✔
851
        this.tokens = {
50✔
852
            rightParen: options.rightParen,
853
            leftParen: options.leftParen
854
        };
855
        this.expression = options.expression;
50✔
856
        this.location = util.createBoundingLocation(this.tokens.leftParen, this.expression, this.tokens.rightParen);
50✔
857
    }
858

859
    public readonly tokens: {
860
        readonly leftParen?: Token;
861
        readonly rightParen?: Token;
862
    };
863
    public readonly expression: Expression;
864

865
    public readonly kind = AstNodeKind.GroupingExpression;
50✔
866

867
    public readonly location: Location | undefined;
868

869
    transpile(state: BrsTranspileState) {
870
        if (isTypecastExpression(this.expression)) {
13✔
871
            return this.expression.transpile(state);
7✔
872
        }
873
        return [
6✔
874
            state.transpileToken(this.tokens.leftParen),
875
            ...this.expression.transpile(state),
876
            state.transpileToken(this.tokens.rightParen)
877
        ];
878
    }
879

880
    walk(visitor: WalkVisitor, options: WalkOptions) {
881
        if (options.walkMode & InternalWalkMode.walkExpressions) {
156!
882
            walk(this, 'expression', visitor, options);
156✔
883
        }
884
    }
885

886
    getType(options: GetTypeOptions) {
887
        return this.expression.getType(options);
62✔
888
    }
889

890
    get leadingTrivia(): Token[] {
891
        return this.tokens.leftParen?.leadingTrivia;
259!
892
    }
893

894
    public clone() {
895
        return this.finalizeClone(
2✔
896
            new GroupingExpression({
897
                leftParen: util.cloneToken(this.tokens.leftParen),
898
                expression: this.expression?.clone(),
6✔
899
                rightParen: util.cloneToken(this.tokens.rightParen)
900
            }),
901
            ['expression']
902
        );
903
    }
904
}
905

906
export class LiteralExpression extends Expression {
1✔
907
    constructor(options: {
908
        value: Token;
909
    }) {
910
        super();
7,459✔
911
        this.tokens = {
7,459✔
912
            value: options.value
913
        };
914
    }
915

916
    public readonly tokens: {
917
        readonly value: Token;
918
    };
919

920
    public readonly kind = AstNodeKind.LiteralExpression;
7,459✔
921

922
    public get location() {
923
        return this.tokens.value.location;
21,138✔
924
    }
925

926
    public getType(options?: GetTypeOptions) {
927
        return util.tokenToBscType(this.tokens.value);
3,294✔
928
    }
929

930
    transpile(state: BrsTranspileState) {
931
        let text: string;
932
        if (this.tokens.value.kind === TokenKind.TemplateStringQuasi) {
4,511✔
933
            //wrap quasis with quotes (and escape inner quotemarks)
934
            text = `"${this.tokens.value.text.replace(/"/g, '""')}"`;
28✔
935

936
        } else if (this.tokens.value.kind === TokenKind.StringLiteral) {
4,483✔
937
            text = this.tokens.value.text;
2,998✔
938
            //add trailing quotemark if it's missing. We will have already generated a diagnostic for this.
939
            if (text.endsWith('"') === false) {
2,998✔
940
                text += '"';
1✔
941
            }
942
        } else {
943
            text = this.tokens.value.text;
1,485✔
944
        }
945

946
        return [
4,511✔
947
            state.transpileToken({ ...this.tokens.value, text: text })
948
        ];
949
    }
950

951
    walk(visitor: WalkVisitor, options: WalkOptions) {
952
        //nothing to walk
953
    }
954

955
    get leadingTrivia(): Token[] {
956
        return this.tokens.value.leadingTrivia;
11,198✔
957
    }
958

959
    public clone() {
960
        return this.finalizeClone(
99✔
961
            new LiteralExpression({
962
                value: util.cloneToken(this.tokens.value)
963
            })
964
        );
965
    }
966
}
967

968
/**
969
 * The print statement can have a mix of expressions and separators. These separators represent actual output to the screen,
970
 * so this AstNode represents those separators (comma, semicolon, and whitespace)
971
 */
972
export class PrintSeparatorExpression extends Expression {
1✔
973
    constructor(options: {
974
        separator: Token;
975
    }) {
976
        super();
42✔
977
        this.tokens = {
42✔
978
            separator: options.separator
979
        };
980
        this.location = this.tokens.separator.location;
42✔
981
    }
982

983
    public readonly tokens: {
984
        readonly separator: Token;
985
    };
986

987
    public readonly kind = AstNodeKind.PrintSeparatorExpression;
42✔
988

989
    public location: Location;
990

991
    transpile(state: BrsTranspileState) {
992
        return [
26✔
993
            ...this.tokens.separator.leadingWhitespace ?? [],
78!
994
            ...state.transpileToken(this.tokens.separator)
995
        ];
996
    }
997

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

1002
    get leadingTrivia(): Token[] {
1003
        return this.tokens.separator.leadingTrivia;
166✔
1004
    }
1005

1006
    public clone() {
NEW
1007
        return new PrintSeparatorExpression({
×
1008
            separator: util.cloneToken(this.tokens?.separator)
×
1009
        });
1010
    }
1011
}
1012

1013

1014
/**
1015
 * This is a special expression only used within template strings. It exists so we can prevent producing lots of empty strings
1016
 * during template string transpile by identifying these expressions explicitly and skipping the bslib_toString around them
1017
 */
1018
export class EscapedCharCodeLiteralExpression extends Expression {
1✔
1019
    constructor(options: {
1020
        value: Token & { charCode: number };
1021
    }) {
1022
        super();
35✔
1023
        this.tokens = { value: options.value };
35✔
1024
        this.location = util.cloneLocation(this.tokens.value.location);
35✔
1025
    }
1026

1027
    public readonly kind = AstNodeKind.EscapedCharCodeLiteralExpression;
35✔
1028

1029
    public readonly tokens: {
1030
        readonly value: Token & { charCode: number };
1031
    };
1032

1033
    public readonly location: Location;
1034

1035
    transpile(state: BrsTranspileState) {
1036
        return [
13✔
1037
            state.sourceNode(this, `chr(${this.tokens.value.charCode})`)
1038
        ];
1039
    }
1040

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

1045
    public clone() {
1046
        return this.finalizeClone(
3✔
1047
            new EscapedCharCodeLiteralExpression({
1048
                value: util.cloneToken(this.tokens.value)
1049
            })
1050
        );
1051
    }
1052
}
1053

1054
export class ArrayLiteralExpression extends Expression {
1✔
1055
    constructor(options: {
1056
        elements: Array<Expression>;
1057
        open?: Token;
1058
        close?: Token;
1059
    }) {
1060
        super();
144✔
1061
        this.tokens = {
144✔
1062
            open: options.open,
1063
            close: options.close
1064
        };
1065
        this.elements = options.elements;
144✔
1066
        this.location = util.createBoundingLocation(this.tokens.open, ...this.elements ?? [], this.tokens.close);
144✔
1067
    }
1068

1069
    public readonly elements: Array<Expression>;
1070

1071
    public readonly tokens: {
1072
        readonly open?: Token;
1073
        readonly close?: Token;
1074
    };
1075

1076
    public readonly kind = AstNodeKind.ArrayLiteralExpression;
144✔
1077

1078
    public readonly location: Location | undefined;
1079

1080
    transpile(state: BrsTranspileState) {
1081
        let result: TranspileResult = [];
51✔
1082
        result.push(
51✔
1083
            state.transpileToken(this.tokens.open, '[')
1084
        );
1085
        let hasChildren = this.elements.length > 0;
51✔
1086
        state.blockDepth++;
51✔
1087

1088
        for (let i = 0; i < this.elements.length; i++) {
51✔
1089
            let previousElement = this.elements[i - 1];
55✔
1090
            let element = this.elements[i];
55✔
1091

1092
            if (util.isLeadingCommentOnSameLine(previousElement ?? this.tokens.open, element)) {
55✔
1093
                result.push(' ');
3✔
1094
            } else {
1095
                result.push(
52✔
1096
                    '\n',
1097
                    state.indent()
1098
                );
1099
            }
1100
            result.push(
55✔
1101
                ...element.transpile(state)
1102
            );
1103
        }
1104
        state.blockDepth--;
51✔
1105
        //add a newline between open and close if there are elements
1106
        const lastLocatable = this.elements[this.elements.length - 1] ?? this.tokens.open;
51✔
1107
        result.push(...state.transpileEndBlockToken(lastLocatable, this.tokens.close, ']', hasChildren));
51✔
1108

1109
        return result;
51✔
1110
    }
1111

1112
    walk(visitor: WalkVisitor, options: WalkOptions) {
1113
        if (options.walkMode & InternalWalkMode.walkExpressions) {
660!
1114
            walkArray(this.elements, visitor, options, this);
660✔
1115
        }
1116
    }
1117

1118
    getType(options: GetTypeOptions): BscType {
1119
        const innerTypes = this.elements.map(expr => expr.getType(options));
174✔
1120
        return new ArrayType(...innerTypes);
134✔
1121
    }
1122
    get leadingTrivia(): Token[] {
1123
        return this.tokens.open?.leadingTrivia;
454!
1124
    }
1125

1126
    get endTrivia(): Token[] {
1127
        return this.tokens.close?.leadingTrivia;
2!
1128
    }
1129

1130
    public clone() {
1131
        return this.finalizeClone(
4✔
1132
            new ArrayLiteralExpression({
1133
                elements: this.elements?.map(e => e?.clone()),
6✔
1134
                open: util.cloneToken(this.tokens.open),
1135
                close: util.cloneToken(this.tokens.close)
1136
            }),
1137
            ['elements']
1138
        );
1139
    }
1140
}
1141

1142
export class AAMemberExpression extends Expression {
1✔
1143
    constructor(options: {
1144
        key: Token;
1145
        colon?: Token;
1146
        /** The expression evaluated to determine the member's initial value. */
1147
        value: Expression;
1148
        comma?: Token;
1149
    }) {
1150
        super();
275✔
1151
        this.tokens = {
275✔
1152
            key: options.key,
1153
            colon: options.colon,
1154
            comma: options.comma
1155
        };
1156
        this.value = options.value;
275✔
1157
        this.location = util.createBoundingLocation(this.tokens.key, this.tokens.colon, this.value);
275✔
1158
    }
1159

1160
    public readonly kind = AstNodeKind.AAMemberExpression;
275✔
1161

1162
    public readonly location: Location | undefined;
1163

1164
    public readonly tokens: {
1165
        readonly key: Token;
1166
        readonly colon?: Token;
1167
        readonly comma?: Token;
1168
    };
1169

1170
    /** The expression evaluated to determine the member's initial value. */
1171
    public readonly value: Expression;
1172

1173
    transpile(state: BrsTranspileState) {
1174
        //TODO move the logic from AALiteralExpression loop into this function
UNCOV
1175
        return [];
×
1176
    }
1177

1178
    walk(visitor: WalkVisitor, options: WalkOptions) {
1179
        walk(this, 'value', visitor, options);
905✔
1180
    }
1181

1182
    getType(options: GetTypeOptions): BscType {
1183
        return this.value.getType(options);
192✔
1184
    }
1185

1186
    get leadingTrivia(): Token[] {
1187
        return this.tokens.key.leadingTrivia;
681✔
1188
    }
1189

1190
    public clone() {
1191
        return this.finalizeClone(
4✔
1192
            new AAMemberExpression({
1193
                key: util.cloneToken(this.tokens.key),
1194
                colon: util.cloneToken(this.tokens.colon),
1195
                value: this.value?.clone()
12✔
1196
            }),
1197
            ['value']
1198
        );
1199
    }
1200
}
1201

1202
export class AALiteralExpression extends Expression {
1✔
1203
    constructor(options: {
1204
        elements: Array<AAMemberExpression>;
1205
        open?: Token;
1206
        close?: Token;
1207
    }) {
1208
        super();
271✔
1209
        this.tokens = {
271✔
1210
            open: options.open,
1211
            close: options.close
1212
        };
1213
        this.elements = options.elements;
271✔
1214
        this.location = util.createBoundingLocation(this.tokens.open, ...this.elements ?? [], this.tokens.close);
271✔
1215
    }
1216

1217
    public readonly elements: Array<AAMemberExpression>;
1218
    public readonly tokens: {
1219
        readonly open?: Token;
1220
        readonly close?: Token;
1221
    };
1222

1223
    public readonly kind = AstNodeKind.AALiteralExpression;
271✔
1224

1225
    public readonly location: Location | undefined;
1226

1227
    transpile(state: BrsTranspileState) {
1228
        let result: TranspileResult = [];
59✔
1229
        //open curly
1230
        result.push(
59✔
1231
            state.transpileToken(this.tokens.open, '{')
1232
        );
1233
        let hasChildren = this.elements.length > 0;
59✔
1234
        //add newline if the object has children and the first child isn't a comment starting on the same line as opening curly
1235
        if (hasChildren && !util.isLeadingCommentOnSameLine(this.tokens.open, this.elements[0])) {
59✔
1236
            result.push('\n');
24✔
1237
        }
1238
        state.blockDepth++;
59✔
1239
        for (let i = 0; i < this.elements.length; i++) {
59✔
1240
            let element = this.elements[i];
36✔
1241
            let previousElement = this.elements[i - 1];
36✔
1242
            let nextElement = this.elements[i + 1];
36✔
1243

1244
            //don't indent if comment is same-line
1245
            if (util.isLeadingCommentOnSameLine(this.tokens.open, element) ||
36✔
1246
                util.isLeadingCommentOnSameLine(previousElement, element)) {
1247
                result.push(' ');
7✔
1248
            } else {
1249
                //indent line
1250
                result.push(state.indent());
29✔
1251
            }
1252

1253
            //key
1254
            result.push(
36✔
1255
                state.transpileToken(element.tokens.key)
1256
            );
1257
            //colon
1258
            result.push(
36✔
1259
                state.transpileToken(element.tokens.colon, ':'),
1260
                ' '
1261
            );
1262
            //value
1263
            result.push(...element.value.transpile(state));
36✔
1264

1265
            //if next element is a same-line comment, skip the newline
1266
            if (nextElement && !util.isLeadingCommentOnSameLine(element, nextElement)) {
36✔
1267
                //add a newline between statements
1268
                result.push('\n');
5✔
1269
            }
1270
        }
1271
        state.blockDepth--;
59✔
1272

1273
        const lastElement = this.elements[this.elements.length - 1] ?? this.tokens.open;
59✔
1274
        result.push(...state.transpileEndBlockToken(lastElement, this.tokens.close, '}', hasChildren));
59✔
1275

1276
        return result;
59✔
1277
    }
1278

1279
    walk(visitor: WalkVisitor, options: WalkOptions) {
1280
        if (options.walkMode & InternalWalkMode.walkExpressions) {
971!
1281
            walkArray(this.elements, visitor, options, this);
971✔
1282
        }
1283
    }
1284

1285
    getType(options: GetTypeOptions): BscType {
1286
        const resultType = new AssociativeArrayType();
197✔
1287
        resultType.addBuiltInInterfaces();
197✔
1288
        for (const element of this.elements) {
197✔
1289
            if (isAAMemberExpression(element)) {
192!
1290
                resultType.addMember(element.tokens.key.text, { definingNode: element }, element.getType(options), SymbolTypeFlag.runtime);
192✔
1291
            }
1292
        }
1293
        return resultType;
197✔
1294
    }
1295

1296
    public get leadingTrivia(): Token[] {
1297
        return this.tokens.open?.leadingTrivia;
692!
1298
    }
1299

1300
    public get endTrivia(): Token[] {
1301
        return this.tokens.close?.leadingTrivia;
1!
1302
    }
1303

1304
    public clone() {
1305
        return this.finalizeClone(
6✔
1306
            new AALiteralExpression({
1307
                elements: this.elements?.map(e => e?.clone()),
5✔
1308
                open: util.cloneToken(this.tokens.open),
1309
                close: util.cloneToken(this.tokens.close)
1310
            }),
1311
            ['elements']
1312
        );
1313
    }
1314
}
1315

1316
export class UnaryExpression extends Expression {
1✔
1317
    constructor(options: {
1318
        operator: Token;
1319
        right: Expression;
1320
    }) {
1321
        super();
66✔
1322
        this.tokens = {
66✔
1323
            operator: options.operator
1324
        };
1325
        this.right = options.right;
66✔
1326
        this.location = util.createBoundingLocation(this.tokens.operator, this.right);
66✔
1327
    }
1328

1329
    public readonly kind = AstNodeKind.UnaryExpression;
66✔
1330

1331
    public readonly location: Location | undefined;
1332

1333
    public readonly tokens: {
1334
        readonly operator: Token;
1335
    };
1336
    public readonly right: Expression;
1337

1338
    transpile(state: BrsTranspileState) {
1339
        let separatingWhitespace: string | undefined;
1340
        if (isVariableExpression(this.right)) {
12✔
1341
            separatingWhitespace = this.right.tokens.name.leadingWhitespace;
6✔
1342
        } else if (isLiteralExpression(this.right)) {
6✔
1343
            separatingWhitespace = this.right.tokens.value.leadingWhitespace;
2✔
1344
        } else {
1345
            separatingWhitespace = ' ';
4✔
1346
        }
1347

1348
        return [
12✔
1349
            state.transpileToken(this.tokens.operator),
1350
            separatingWhitespace,
1351
            ...this.right.transpile(state)
1352
        ];
1353
    }
1354

1355
    walk(visitor: WalkVisitor, options: WalkOptions) {
1356
        if (options.walkMode & InternalWalkMode.walkExpressions) {
236!
1357
            walk(this, 'right', visitor, options);
236✔
1358
        }
1359
    }
1360

1361
    getType(options: GetTypeOptions): BscType {
1362
        return util.unaryOperatorResultType(this.tokens.operator, this.right.getType(options));
48✔
1363
    }
1364

1365
    public get leadingTrivia(): Token[] {
1366
        return this.tokens.operator.leadingTrivia;
187✔
1367
    }
1368

1369
    public clone() {
1370
        return this.finalizeClone(
2✔
1371
            new UnaryExpression({
1372
                operator: util.cloneToken(this.tokens.operator),
1373
                right: this.right?.clone()
6✔
1374
            }),
1375
            ['right']
1376
        );
1377
    }
1378
}
1379

1380
export class VariableExpression extends Expression {
1✔
1381
    constructor(options: {
1382
        name: Identifier;
1383
    }) {
1384
        super();
10,533✔
1385
        this.tokens = {
10,533✔
1386
            name: options.name
1387
        };
1388
        this.location = util.cloneLocation(this.tokens.name?.location);
10,533!
1389
    }
1390

1391
    public readonly tokens: {
1392
        readonly name: Identifier;
1393
    };
1394

1395
    public readonly kind = AstNodeKind.VariableExpression;
10,533✔
1396

1397
    public readonly location: Location;
1398

1399
    public getName(parseMode?: ParseMode) {
1400
        return this.tokens.name.text;
21,243✔
1401
    }
1402

1403
    transpile(state: BrsTranspileState) {
1404
        let result: TranspileResult = [];
6,126✔
1405
        const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
6,126✔
1406
        //if the callee is the name of a known namespace function
1407
        if (namespace && util.isCalleeMemberOfNamespace(this.tokens.name.text, this, namespace)) {
6,126✔
1408
            result.push(
15✔
1409
                //transpile leading comments since the token isn't being transpiled directly
1410
                ...state.transpileLeadingCommentsForAstNode(this),
1411
                state.sourceNode(this, [
1412
                    namespace.getName(ParseMode.BrightScript),
1413
                    '_',
1414
                    this.getName(ParseMode.BrightScript)
1415
                ])
1416
            );
1417
            //transpile  normally
1418
        } else {
1419
            result.push(
6,111✔
1420
                state.transpileToken(this.tokens.name)
1421
            );
1422
        }
1423
        return result;
6,126✔
1424
    }
1425

1426
    walk(visitor: WalkVisitor, options: WalkOptions) {
1427
        //nothing to walk
1428
    }
1429

1430

1431
    getType(options: GetTypeOptions) {
1432
        let resultType: BscType = util.tokenToBscType(this.tokens.name);
21,098✔
1433
        const nameKey = this.getName();
21,098✔
1434
        if (!resultType) {
21,098✔
1435
            const symbolTable = this.getSymbolTable();
16,806✔
1436
            resultType = symbolTable?.getSymbolType(nameKey, { ...options, fullName: nameKey, tableProvider: () => this.getSymbolTable() });
33,473!
1437

1438
            if (util.isClassUsedAsFunction(resultType, this, options)) {
16,806✔
1439
                resultType = FunctionType.instance;
20✔
1440
            }
1441

1442
        }
1443
        options?.typeChain?.push(new TypeChainEntry({ name: nameKey, type: resultType, data: options.data, astNode: this }));
21,098!
1444
        return resultType;
21,098✔
1445
    }
1446

1447
    get leadingTrivia(): Token[] {
1448
        return this.tokens.name.leadingTrivia;
35,240✔
1449
    }
1450

1451
    public clone() {
1452
        return this.finalizeClone(
54✔
1453
            new VariableExpression({
1454
                name: util.cloneToken(this.tokens.name)
1455
            })
1456
        );
1457
    }
1458
}
1459

1460
export class SourceLiteralExpression extends Expression {
1✔
1461
    constructor(options: {
1462
        value: Token;
1463
    }) {
1464
        super();
37✔
1465
        this.tokens = {
37✔
1466
            value: options.value
1467
        };
1468
        this.location = util.cloneLocation(this.tokens.value?.location);
37!
1469
    }
1470

1471
    public readonly location: Location;
1472

1473
    public readonly kind = AstNodeKind.SourceLiteralExpression;
37✔
1474

1475
    public readonly tokens: {
1476
        readonly value: Token;
1477
    };
1478

1479
    /**
1480
     * Find the index of the function in its parent
1481
     */
1482
    private findFunctionIndex(parentFunction: FunctionExpression, func: FunctionExpression) {
1483
        let index = -1;
4✔
1484
        parentFunction.findChild((node) => {
4✔
1485
            if (isFunctionExpression(node)) {
12✔
1486
                index++;
4✔
1487
                if (node === func) {
4!
1488
                    return true;
4✔
1489
                }
1490
            }
1491
        }, {
1492
            walkMode: WalkMode.visitAllRecursive
1493
        });
1494
        return index;
4✔
1495
    }
1496

1497
    private getFunctionName(state: BrsTranspileState, parseMode: ParseMode) {
1498
        let func = this.findAncestor<FunctionExpression>(isFunctionExpression);
8✔
1499
        let nameParts = [] as TranspileResult;
8✔
1500
        let parentFunction: FunctionExpression;
1501
        while ((parentFunction = func.findAncestor<FunctionExpression>(isFunctionExpression))) {
8✔
1502
            let index = this.findFunctionIndex(parentFunction, func);
4✔
1503
            nameParts.unshift(`anon${index}`);
4✔
1504
            func = parentFunction;
4✔
1505
        }
1506
        //get the index of this function in its parent
1507
        if (isFunctionStatement(func.parent)) {
8!
1508
            nameParts.unshift(
8✔
1509
                func.parent.getName(parseMode)
1510
            );
1511
        }
1512
        return nameParts.join('$');
8✔
1513
    }
1514

1515
    /**
1516
     * Get the line number from our token or from the closest ancestor that has a range
1517
     */
1518
    private getClosestLineNumber() {
1519
        let node: AstNode = this;
7✔
1520
        while (node) {
7✔
1521
            if (node.location?.range) {
17✔
1522
                return node.location.range.start.line + 1;
5✔
1523
            }
1524
            node = node.parent;
12✔
1525
        }
1526
        return -1;
2✔
1527
    }
1528

1529
    transpile(state: BrsTranspileState) {
1530
        let text: string;
1531
        switch (this.tokens.value.kind) {
31✔
1532
            case TokenKind.SourceFilePathLiteral:
40✔
1533
                const pathUrl = fileUrl(state.srcPath);
3✔
1534
                text = `"${pathUrl.substring(0, 4)}" + "${pathUrl.substring(4)}"`;
3✔
1535
                break;
3✔
1536
            case TokenKind.SourceLineNumLiteral:
1537
                //TODO find first parent that has range, or default to -1
1538
                text = `${this.getClosestLineNumber()}`;
4✔
1539
                break;
4✔
1540
            case TokenKind.FunctionNameLiteral:
1541
                text = `"${this.getFunctionName(state, ParseMode.BrightScript)}"`;
4✔
1542
                break;
4✔
1543
            case TokenKind.SourceFunctionNameLiteral:
1544
                text = `"${this.getFunctionName(state, ParseMode.BrighterScript)}"`;
4✔
1545
                break;
4✔
1546
            case TokenKind.SourceLocationLiteral:
1547
                const locationUrl = fileUrl(state.srcPath);
3✔
1548
                //TODO find first parent that has range, or default to -1
1549
                text = `"${locationUrl.substring(0, 4)}" + "${locationUrl.substring(4)}:${this.getClosestLineNumber()}"`;
3✔
1550
                break;
3✔
1551
            case TokenKind.PkgPathLiteral:
1552
                text = `"${util.sanitizePkgPath(state.file.pkgPath)}"`;
2✔
1553
                break;
2✔
1554
            case TokenKind.PkgLocationLiteral:
1555
                text = `"${util.sanitizePkgPath(state.file.pkgPath)}:" + str(LINE_NUM)`;
2✔
1556
                break;
2✔
1557
            case TokenKind.LineNumLiteral:
1558
            default:
1559
                //use the original text (because it looks like a variable)
1560
                text = this.tokens.value.text;
9✔
1561
                break;
9✔
1562

1563
        }
1564
        return [
31✔
1565
            state.sourceNode(this, text)
1566
        ];
1567
    }
1568

1569
    walk(visitor: WalkVisitor, options: WalkOptions) {
1570
        //nothing to walk
1571
    }
1572

1573
    get leadingTrivia(): Token[] {
1574
        return this.tokens.value.leadingTrivia;
200✔
1575
    }
1576

1577
    public clone() {
1578
        return this.finalizeClone(
1✔
1579
            new SourceLiteralExpression({
1580
                value: util.cloneToken(this.tokens.value)
1581
            })
1582
        );
1583
    }
1584
}
1585

1586
/**
1587
 * This expression transpiles and acts exactly like a CallExpression,
1588
 * except we need to uniquely identify these statements so we can
1589
 * do more type checking.
1590
 */
1591
export class NewExpression extends Expression {
1✔
1592
    constructor(options: {
1593
        new?: Token;
1594
        call: CallExpression;
1595
    }) {
1596
        super();
133✔
1597
        this.tokens = {
133✔
1598
            new: options.new
1599
        };
1600
        this.call = options.call;
133✔
1601
        this.location = util.createBoundingLocation(this.tokens.new, this.call);
133✔
1602
    }
1603

1604
    public readonly kind = AstNodeKind.NewExpression;
133✔
1605

1606
    public readonly location: Location | undefined;
1607

1608
    public readonly tokens: {
1609
        readonly new?: Token;
1610
    };
1611
    public readonly call: CallExpression;
1612

1613
    /**
1614
     * The name of the class to initialize (with optional namespace prefixed)
1615
     */
1616
    public get className() {
1617
        //the parser guarantees the callee of a new statement's call object will be
1618
        //either a VariableExpression or a DottedGet
1619
        return this.call.callee as (VariableExpression | DottedGetExpression);
92✔
1620
    }
1621

1622
    public transpile(state: BrsTranspileState) {
1623
        const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
15✔
1624
        const cls = state.file.getClassFileLink(
15✔
1625
            this.className.getName(ParseMode.BrighterScript),
1626
            namespace?.getName(ParseMode.BrighterScript)
45✔
1627
        )?.item;
15✔
1628
        //new statements within a namespace block can omit the leading namespace if the class resides in that same namespace.
1629
        //So we need to figure out if this is a namespace-omitted class, or if this class exists without a namespace.
1630
        return this.call.transpile(state, cls?.getName(ParseMode.BrightScript));
15✔
1631
    }
1632

1633
    walk(visitor: WalkVisitor, options: WalkOptions) {
1634
        if (options.walkMode & InternalWalkMode.walkExpressions) {
821!
1635
            walk(this, 'call', visitor, options);
821✔
1636
        }
1637
    }
1638

1639
    getType(options: GetTypeOptions) {
1640
        const result = this.call.getType(options);
322✔
1641
        if (options.typeChain) {
322✔
1642
            // modify last typechain entry to show it is a new ...()
1643
            const lastEntry = options.typeChain[options.typeChain.length - 1];
3✔
1644
            if (lastEntry) {
3!
1645
                lastEntry.astNode = this;
3✔
1646
            }
1647
        }
1648
        return result;
322✔
1649
    }
1650

1651
    get leadingTrivia(): Token[] {
1652
        return this.tokens.new.leadingTrivia;
578✔
1653
    }
1654

1655
    public clone() {
1656
        return this.finalizeClone(
2✔
1657
            new NewExpression({
1658
                new: util.cloneToken(this.tokens.new),
1659
                call: this.call?.clone()
6✔
1660
            }),
1661
            ['call']
1662
        );
1663
    }
1664
}
1665

1666
export class CallfuncExpression extends Expression {
1✔
1667
    constructor(options: {
1668
        callee: Expression;
1669
        operator?: Token;
1670
        methodName: Identifier;
1671
        openingParen?: Token;
1672
        args?: Expression[];
1673
        closingParen?: Token;
1674
    }) {
1675
        super();
32✔
1676
        this.tokens = {
32✔
1677
            operator: options.operator,
1678
            methodName: options.methodName,
1679
            openingParen: options.openingParen,
1680
            closingParen: options.closingParen
1681
        };
1682
        this.callee = options.callee;
32✔
1683
        this.args = options.args ?? [];
32✔
1684

1685
        this.location = util.createBoundingLocation(
32✔
1686
            this.callee,
1687
            this.tokens.operator,
1688
            this.tokens.methodName,
1689
            this.tokens.openingParen,
1690
            ...this.args ?? [],
96!
1691
            this.tokens.closingParen
1692
        );
1693
    }
1694

1695
    public readonly callee: Expression;
1696
    public readonly args: Expression[];
1697

1698
    public readonly tokens: {
1699
        readonly operator: Token;
1700
        readonly methodName: Identifier;
1701
        readonly openingParen?: Token;
1702
        readonly closingParen?: Token;
1703
    };
1704

1705
    public readonly kind = AstNodeKind.CallfuncExpression;
32✔
1706

1707
    public readonly location: Location | undefined;
1708

1709
    public transpile(state: BrsTranspileState) {
1710
        let result = [] as TranspileResult;
9✔
1711
        result.push(
9✔
1712
            ...this.callee.transpile(state),
1713
            state.sourceNode(this.tokens.operator, '.callfunc'),
1714
            state.transpileToken(this.tokens.openingParen, '('),
1715
            //the name of the function
1716
            state.sourceNode(this.tokens.methodName, ['"', this.tokens.methodName.text, '"'])
1717
        );
1718
        if (this.args?.length > 0) {
9!
1719
            result.push(', ');
4✔
1720
            //transpile args
1721
            for (let i = 0; i < this.args.length; i++) {
4✔
1722
                //add comma between args
1723
                if (i > 0) {
7✔
1724
                    result.push(', ');
3✔
1725
                }
1726
                let arg = this.args[i];
7✔
1727
                result.push(...arg.transpile(state));
7✔
1728
            }
1729
        } else if (state.options.legacyCallfuncHandling) {
5✔
1730
            result.push(', ', 'invalid');
2✔
1731
        }
1732

1733
        result.push(
9✔
1734
            state.transpileToken(this.tokens.closingParen, ')')
1735
        );
1736
        return result;
9✔
1737
    }
1738

1739
    walk(visitor: WalkVisitor, options: WalkOptions) {
1740
        if (options.walkMode & InternalWalkMode.walkExpressions) {
135!
1741
            walk(this, 'callee', visitor, options);
135✔
1742
            walkArray(this.args, visitor, options, this);
135✔
1743
        }
1744
    }
1745

1746
    getType(options: GetTypeOptions) {
1747
        let result: BscType = DynamicType.instance;
4✔
1748
        // a little hacky here with checking options.ignoreCall because callFuncExpression has the method name
1749
        // It's nicer for CallExpression, because it's a call on any expression.
1750
        const calleeType = this.callee.getType({ ...options, flags: SymbolTypeFlag.runtime });
4✔
1751
        if (isComponentType(calleeType) || isReferenceType(calleeType)) {
4!
1752
            const funcType = (calleeType as ComponentType).getCallFuncType?.(this.tokens.methodName.text, options);
4!
1753
            if (funcType) {
4✔
1754
                options.typeChain?.push(new TypeChainEntry({
3✔
1755
                    name: this.tokens.methodName.text,
1756
                    type: funcType,
1757
                    data: options.data,
1758
                    location: this.tokens.methodName.location,
1759
                    separatorToken: createToken(TokenKind.Callfunc),
1760
                    astNode: this
1761
                }));
1762
                if (options.ignoreCall) {
3✔
1763
                    result = funcType;
1✔
1764
                }
1765
            }
1766
            /* TODO:
1767
                make callfunc return types work
1768
            else if (isCallableType(funcType) && (!isReferenceType(funcType.returnType) || funcType.returnType.isResolvable())) {
1769
                result = funcType.returnType;
1770
            } else if (!isReferenceType(funcType) && (funcType as any)?.returnType?.isResolvable()) {
1771
                result = (funcType as any).returnType;
1772
            } else {
1773
                return new TypePropertyReferenceType(funcType, 'returnType');
1774
            }
1775
            */
1776
        }
1777

1778
        return result;
4✔
1779
    }
1780

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

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

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

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

1818
    readonly location: Location | undefined;
1819

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

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

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

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

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

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

1885
    public readonly location: Location | undefined;
1886

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

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

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

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

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

1941
        return result;
10✔
1942
    }
1943

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

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

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

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

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

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

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

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

2008
    public readonly location: Location | undefined;
2009

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

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

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

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

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

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

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

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

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

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

2110
    public readonly name: string;
2111

2112
    public call: CallExpression;
2113

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

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

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

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

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

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

2181
    public readonly kind = AstNodeKind.TernaryExpression;
82✔
2182

2183
    public readonly location: Location | undefined;
2184

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

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

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

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

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

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

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

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

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

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

2301
    public readonly location: Location | undefined;
2302

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2514
    public readonly kind = AstNodeKind.TypeExpression;
1,483✔
2515

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

2521
    public readonly location: Location;
2522

2523
    public transpile(state: BrsTranspileState): TranspileResult {
2524
        return [this.getType({ flags: SymbolTypeFlag.typetime }).toTypeString()];
106✔
2525
    }
2526
    public walk(visitor: WalkVisitor, options: WalkOptions) {
2527
        if (options.walkMode & InternalWalkMode.walkExpressions) {
6,774✔
2528
            walk(this, 'expression', visitor, options);
6,649✔
2529
        }
2530
    }
2531

2532
    public getType(options: GetTypeOptions): BscType {
2533
        return this.expression.getType({ ...options, flags: SymbolTypeFlag.typetime });
5,289✔
2534
    }
2535

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

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

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

2551
    public clone() {
2552
        return this.finalizeClone(
16✔
2553
            new TypeExpression({
2554
                expression: this.expression?.clone()
48!
2555
            }),
2556
            ['expression']
2557
        );
2558
    }
2559
}
2560

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

2580
    public readonly kind = AstNodeKind.TypecastExpression;
69✔
2581

2582
    public readonly obj: Expression;
2583

2584
    public readonly tokens: {
2585
        readonly as?: Token;
2586
    };
2587

2588
    public typeExpression?: TypeExpression;
2589

2590
    public readonly location: Location;
2591

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

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

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

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

2645
    public readonly tokens: {
2646
        readonly leftBracket?: Token;
2647
        readonly rightBracket?: Token;
2648
    };
2649

2650
    public readonly innerType: Expression;
2651

2652
    public readonly kind = AstNodeKind.TypedArrayExpression;
29✔
2653

2654
    public readonly location: Location;
2655

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

2660
    public walk(visitor: WalkVisitor, options: WalkOptions) {
2661
        if (options.walkMode & InternalWalkMode.walkExpressions) {
127!
2662
            walk(this, 'innerType', visitor, options);
127✔
2663
        }
2664
    }
2665

2666
    public getType(options: GetTypeOptions): BscType {
2667
        return new ArrayType(this.innerType.getType(options));
122✔
2668
    }
2669

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