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

rokucommunity / brighterscript / #13183

14 Oct 2024 03:45PM UTC coverage: 86.831%. Remained the same
#13183

push

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

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

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

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

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

63
    public readonly location: Location | undefined;
64

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

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

82

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

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

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

117

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

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

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

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

155
    public readonly location: Location | undefined;
156

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

541
        return result;
2,140✔
542
    }
543

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

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

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

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

561
        return results;
73✔
562
    }
563

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

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

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

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

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

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

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

617
    public readonly location: Location | undefined;
618

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

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

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

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

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

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

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

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

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

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

709
    public readonly obj: Expression;
710

711
    public readonly location: Location | undefined;
712

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

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

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

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

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

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

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

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

786
    public readonly location: Location | undefined;
787

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

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

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

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

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

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

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

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

868
    public readonly location: Location | undefined;
869

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

984
    public readonly tokens: {
985
        readonly separator: PrintSeparatorToken;
986
    };
987

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

990
    public location: Location;
991

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

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

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

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

1014

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

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

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

1034
    public readonly location: Location;
1035

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

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

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

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

1070
    public readonly elements: Array<Expression>;
1071

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

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

1079
    public readonly location: Location | undefined;
1080

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

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

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

1110
        return result;
51✔
1111
    }
1112

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

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

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

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

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

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

1163
    public readonly location: Location | undefined;
1164

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

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

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

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

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

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

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

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

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

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

1226
    public readonly location: Location | undefined;
1227

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

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

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

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

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

1277
        return result;
59✔
1278
    }
1279

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

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

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

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

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

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

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

1332
    public readonly location: Location | undefined;
1333

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

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

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

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

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

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

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

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

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

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

1398
    public readonly location: Location;
1399

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

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

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

1431

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

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

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

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

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

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

1472
    public readonly location: Location;
1473

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

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

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

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

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

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

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

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

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

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

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

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

1607
    public readonly location: Location | undefined;
1608

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

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

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

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

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

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

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

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

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

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

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

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

1708
    public readonly location: Location | undefined;
1709

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

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

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

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

1779
        return result;
4✔
1780
    }
1781

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

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

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

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

1819
    readonly location: Location | undefined;
1820

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

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

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

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

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

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

1886
    public readonly location: Location | undefined;
1887

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

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

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

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

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

1942
        return result;
10✔
1943
    }
1944

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

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

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

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

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

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

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

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

2009
    public readonly location: Location | undefined;
2010

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

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

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

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

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

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

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

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

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

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

2111
    public readonly name: string;
2112

2113
    public call: CallExpression;
2114

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

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

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

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

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

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

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

2184
    public readonly location: Location | undefined;
2185

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

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

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

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

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

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

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

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

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

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

2302
    public readonly location: Location | undefined;
2303

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2522
    public readonly location: Location;
2523

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

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

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

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

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

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

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

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

2583
    public readonly obj: Expression;
2584

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

2589
    public typeExpression?: TypeExpression;
2590

2591
    public readonly location: Location;
2592

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

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

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

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

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

2651
    public readonly innerType: Expression;
2652

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

2655
    public readonly location: Location;
2656

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

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

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

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