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

rokucommunity / brighterscript / #13247

30 Oct 2024 05:25PM UTC coverage: 86.896%. Remained the same
#13247

push

web-flow
Merge d7ee0e55f into 44b52f55b

11621 of 14129 branches covered (82.25%)

Branch coverage included in aggregate %.

1 of 1 new or added line in 1 file covered. (100.0%)

9 existing lines in 1 file now uncovered.

12730 of 13894 relevant lines covered (91.62%)

31018.42 hits per line

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

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

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

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

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

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

62
    public readonly location: Location | undefined;
63

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

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

81

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

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

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

116

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

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

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

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

154
    public readonly location: Location | undefined;
155

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

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

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

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

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

215
    get leadingTrivia(): Token[] {
216
        return this.callee.leadingTrivia;
8,020✔
217
    }
218

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

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

266
    public readonly kind = AstNodeKind.FunctionExpression;
3,749✔
267

268
    readonly parameters: FunctionParameterExpression[];
269
    public readonly body: Block;
270
    public readonly returnTypeExpression?: TypeExpression;
271

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

280
    public get leadingTrivia(): Token[] {
281
        return this.tokens.functionType?.leadingTrivia;
26,748✔
282
    }
283

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

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

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

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

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

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

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

412
        const docs = brsDocParser.parseNode(this.findAncestor(isFunctionStatement));
3,963✔
413

414
        returnType = util.chooseTypeFromCodeOrDocComment(
3,963✔
415
            this.returnTypeExpression?.getType({ ...options, typeChain: undefined }),
11,889✔
UNCOV
416
            docs.getReturnBscType({ ...options, tableProvider: () => this.getSymbolTable() }),
×
417
            options
418
        );
419

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

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

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

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

484
    public readonly kind = AstNodeKind.FunctionParameterExpression;
2,938✔
485

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

492
    public readonly defaultValue?: Expression;
493
    public readonly typeExpression?: TypeExpression;
494

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

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

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

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

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

538
        return result;
2,164✔
539
    }
540

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

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

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

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

558
        return results;
73✔
559
    }
560

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

569
    get leadingTrivia(): Token[] {
570
        return this.tokens.name.leadingTrivia;
4,442✔
571
    }
572

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

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

603
        this.location = util.createBoundingLocation(this.obj, this.tokens.dot, this.tokens.name);
2,758✔
604
    }
605

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

612
    public readonly kind = AstNodeKind.DottedGetExpression;
2,758✔
613

614
    public readonly location: Location | undefined;
615

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

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

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

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

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

668
    get leadingTrivia(): Token[] {
669
        return this.obj.leadingTrivia;
14,020✔
670
    }
671

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

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

699
    public readonly kind = AstNodeKind.XmlAttributeGetExpression;
14✔
700

701
    public readonly tokens: {
702
        name: Identifier;
703
        at?: Token;
704
    };
705

706
    public readonly obj: Expression;
707

708
    public readonly location: Location | undefined;
709

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

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

724
    get leadingTrivia(): Token[] {
725
        return this.obj.leadingTrivia;
15✔
726
    }
727

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

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

769
    public readonly kind = AstNodeKind.IndexedGetExpression;
159✔
770

771
    public readonly obj: Expression;
772
    public readonly indexes: Expression[];
773

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

783
    public readonly location: Location | undefined;
784

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

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

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

824
    get leadingTrivia(): Token[] {
825
        return this.obj.leadingTrivia;
1,021✔
826
    }
827

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

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

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

863
    public readonly kind = AstNodeKind.GroupingExpression;
52✔
864

865
    public readonly location: Location | undefined;
866

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

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

884
    getType(options: GetTypeOptions) {
885
        return this.expression.getType(options);
69✔
886
    }
887

888
    get leadingTrivia(): Token[] {
889
        return this.tokens.leftParen?.leadingTrivia;
278!
890
    }
891

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

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

914
    public readonly tokens: {
915
        readonly value: Token;
916
    };
917

918
    public readonly kind = AstNodeKind.LiteralExpression;
7,510✔
919

920
    public get location() {
921
        return this.tokens.value.location;
21,314✔
922
    }
923

924
    public getType(options?: GetTypeOptions) {
925
        return util.tokenToBscType(this.tokens.value);
3,304✔
926
    }
927

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

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

944
        return [
4,558✔
945
            state.transpileToken({ ...this.tokens.value, text: text })
946
        ];
947
    }
948

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

953
    get leadingTrivia(): Token[] {
954
        return this.tokens.value.leadingTrivia;
11,050✔
955
    }
956

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

966
/**
967
 * This is a special expression only used within template strings. It exists so we can prevent producing lots of empty strings
968
 * during template string transpile by identifying these expressions explicitly and skipping the bslib_toString around them
969
 */
970
export class EscapedCharCodeLiteralExpression extends Expression {
1✔
971
    constructor(options: {
972
        value: Token & { charCode: number };
973
    }) {
974
        super();
35✔
975
        this.tokens = { value: options.value };
35✔
976
        this.location = util.cloneLocation(this.tokens.value.location);
35✔
977
    }
978

979
    public readonly kind = AstNodeKind.EscapedCharCodeLiteralExpression;
35✔
980

981
    public readonly tokens: {
982
        readonly value: Token & { charCode: number };
983
    };
984

985
    public readonly location: Location;
986

987
    transpile(state: BrsTranspileState) {
988
        return [
13✔
989
            state.sourceNode(this, `chr(${this.tokens.value.charCode})`)
990
        ];
991
    }
992

993
    walk(visitor: WalkVisitor, options: WalkOptions) {
994
        //nothing to walk
995
    }
996

997
    public clone() {
998
        return this.finalizeClone(
3✔
999
            new EscapedCharCodeLiteralExpression({
1000
                value: util.cloneToken(this.tokens.value)
1001
            })
1002
        );
1003
    }
1004
}
1005

1006
export class ArrayLiteralExpression extends Expression {
1✔
1007
    constructor(options: {
1008
        elements: Array<Expression>;
1009
        open?: Token;
1010
        close?: Token;
1011
    }) {
1012
        super();
145✔
1013
        this.tokens = {
145✔
1014
            open: options.open,
1015
            close: options.close
1016
        };
1017
        this.elements = options.elements;
145✔
1018
        this.location = util.createBoundingLocation(this.tokens.open, ...this.elements ?? [], this.tokens.close);
145✔
1019
    }
1020

1021
    public readonly elements: Array<Expression>;
1022

1023
    public readonly tokens: {
1024
        readonly open?: Token;
1025
        readonly close?: Token;
1026
    };
1027

1028
    public readonly kind = AstNodeKind.ArrayLiteralExpression;
145✔
1029

1030
    public readonly location: Location | undefined;
1031

1032
    transpile(state: BrsTranspileState) {
1033
        let result: TranspileResult = [];
52✔
1034
        result.push(
52✔
1035
            state.transpileToken(this.tokens.open, '[')
1036
        );
1037
        let hasChildren = this.elements.length > 0;
52✔
1038
        state.blockDepth++;
52✔
1039

1040
        for (let i = 0; i < this.elements.length; i++) {
52✔
1041
            let previousElement = this.elements[i - 1];
57✔
1042
            let element = this.elements[i];
57✔
1043

1044
            if (util.isLeadingCommentOnSameLine(previousElement ?? this.tokens.open, element)) {
57✔
1045
                result.push(' ');
3✔
1046
            } else {
1047
                result.push(
54✔
1048
                    '\n',
1049
                    state.indent()
1050
                );
1051
            }
1052
            result.push(
57✔
1053
                ...element.transpile(state)
1054
            );
1055
        }
1056
        state.blockDepth--;
52✔
1057
        //add a newline between open and close if there are elements
1058
        const lastLocatable = this.elements[this.elements.length - 1] ?? this.tokens.open;
52✔
1059
        result.push(...state.transpileEndBlockToken(lastLocatable, this.tokens.close, ']', hasChildren));
52✔
1060

1061
        return result;
52✔
1062
    }
1063

1064
    walk(visitor: WalkVisitor, options: WalkOptions) {
1065
        if (options.walkMode & InternalWalkMode.walkExpressions) {
665!
1066
            walkArray(this.elements, visitor, options, this);
665✔
1067
        }
1068
    }
1069

1070
    getType(options: GetTypeOptions): BscType {
1071
        const innerTypes = this.elements.map(expr => expr.getType(options));
174✔
1072
        return new ArrayType(...innerTypes);
134✔
1073
    }
1074
    get leadingTrivia(): Token[] {
1075
        return this.tokens.open?.leadingTrivia;
453!
1076
    }
1077

1078
    get endTrivia(): Token[] {
1079
        return this.tokens.close?.leadingTrivia;
2!
1080
    }
1081

1082
    public clone() {
1083
        return this.finalizeClone(
4✔
1084
            new ArrayLiteralExpression({
1085
                elements: this.elements?.map(e => e?.clone()),
6✔
1086
                open: util.cloneToken(this.tokens.open),
1087
                close: util.cloneToken(this.tokens.close)
1088
            }),
1089
            ['elements']
1090
        );
1091
    }
1092
}
1093

1094
export class AAMemberExpression extends Expression {
1✔
1095
    constructor(options: {
1096
        key: Token;
1097
        colon?: Token;
1098
        /** The expression evaluated to determine the member's initial value. */
1099
        value: Expression;
1100
        comma?: Token;
1101
    }) {
1102
        super();
275✔
1103
        this.tokens = {
275✔
1104
            key: options.key,
1105
            colon: options.colon,
1106
            comma: options.comma
1107
        };
1108
        this.value = options.value;
275✔
1109
        this.location = util.createBoundingLocation(this.tokens.key, this.tokens.colon, this.value);
275✔
1110
    }
1111

1112
    public readonly kind = AstNodeKind.AAMemberExpression;
275✔
1113

1114
    public readonly location: Location | undefined;
1115

1116
    public readonly tokens: {
1117
        readonly key: Token;
1118
        readonly colon?: Token;
1119
        readonly comma?: Token;
1120
    };
1121

1122
    /** The expression evaluated to determine the member's initial value. */
1123
    public readonly value: Expression;
1124

1125
    transpile(state: BrsTranspileState) {
1126
        //TODO move the logic from AALiteralExpression loop into this function
UNCOV
1127
        return [];
×
1128
    }
1129

1130
    walk(visitor: WalkVisitor, options: WalkOptions) {
1131
        walk(this, 'value', visitor, options);
905✔
1132
    }
1133

1134
    getType(options: GetTypeOptions): BscType {
1135
        return this.value.getType(options);
192✔
1136
    }
1137

1138
    get leadingTrivia(): Token[] {
1139
        return this.tokens.key.leadingTrivia;
681✔
1140
    }
1141

1142
    public clone() {
1143
        return this.finalizeClone(
4✔
1144
            new AAMemberExpression({
1145
                key: util.cloneToken(this.tokens.key),
1146
                colon: util.cloneToken(this.tokens.colon),
1147
                value: this.value?.clone()
12✔
1148
            }),
1149
            ['value']
1150
        );
1151
    }
1152
}
1153

1154
export class AALiteralExpression extends Expression {
1✔
1155
    constructor(options: {
1156
        elements: Array<AAMemberExpression>;
1157
        open?: Token;
1158
        close?: Token;
1159
    }) {
1160
        super();
271✔
1161
        this.tokens = {
271✔
1162
            open: options.open,
1163
            close: options.close
1164
        };
1165
        this.elements = options.elements;
271✔
1166
        this.location = util.createBoundingLocation(this.tokens.open, ...this.elements ?? [], this.tokens.close);
271✔
1167
    }
1168

1169
    public readonly elements: Array<AAMemberExpression>;
1170
    public readonly tokens: {
1171
        readonly open?: Token;
1172
        readonly close?: Token;
1173
    };
1174

1175
    public readonly kind = AstNodeKind.AALiteralExpression;
271✔
1176

1177
    public readonly location: Location | undefined;
1178

1179
    transpile(state: BrsTranspileState) {
1180
        let result: TranspileResult = [];
59✔
1181
        //open curly
1182
        result.push(
59✔
1183
            state.transpileToken(this.tokens.open, '{')
1184
        );
1185
        let hasChildren = this.elements.length > 0;
59✔
1186
        //add newline if the object has children and the first child isn't a comment starting on the same line as opening curly
1187
        if (hasChildren && !util.isLeadingCommentOnSameLine(this.tokens.open, this.elements[0])) {
59✔
1188
            result.push('\n');
24✔
1189
        }
1190
        state.blockDepth++;
59✔
1191
        for (let i = 0; i < this.elements.length; i++) {
59✔
1192
            let element = this.elements[i];
36✔
1193
            let previousElement = this.elements[i - 1];
36✔
1194
            let nextElement = this.elements[i + 1];
36✔
1195

1196
            //don't indent if comment is same-line
1197
            if (util.isLeadingCommentOnSameLine(this.tokens.open, element) ||
36✔
1198
                util.isLeadingCommentOnSameLine(previousElement, element)) {
1199
                result.push(' ');
7✔
1200
            } else {
1201
                //indent line
1202
                result.push(state.indent());
29✔
1203
            }
1204

1205
            //key
1206
            result.push(
36✔
1207
                state.transpileToken(element.tokens.key)
1208
            );
1209
            //colon
1210
            result.push(
36✔
1211
                state.transpileToken(element.tokens.colon, ':'),
1212
                ' '
1213
            );
1214
            //value
1215
            result.push(...element.value.transpile(state));
36✔
1216

1217
            //if next element is a same-line comment, skip the newline
1218
            if (nextElement && !util.isLeadingCommentOnSameLine(element, nextElement)) {
36✔
1219
                //add a newline between statements
1220
                result.push('\n');
5✔
1221
            }
1222
        }
1223
        state.blockDepth--;
59✔
1224

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

1228
        return result;
59✔
1229
    }
1230

1231
    walk(visitor: WalkVisitor, options: WalkOptions) {
1232
        if (options.walkMode & InternalWalkMode.walkExpressions) {
971!
1233
            walkArray(this.elements, visitor, options, this);
971✔
1234
        }
1235
    }
1236

1237
    getType(options: GetTypeOptions): BscType {
1238
        const resultType = new AssociativeArrayType();
197✔
1239
        resultType.addBuiltInInterfaces();
197✔
1240
        for (const element of this.elements) {
197✔
1241
            if (isAAMemberExpression(element)) {
192!
1242
                resultType.addMember(element.tokens.key.text, { definingNode: element }, element.getType(options), SymbolTypeFlag.runtime);
192✔
1243
            }
1244
        }
1245
        return resultType;
197✔
1246
    }
1247

1248
    public get leadingTrivia(): Token[] {
1249
        return this.tokens.open?.leadingTrivia;
688!
1250
    }
1251

1252
    public get endTrivia(): Token[] {
1253
        return this.tokens.close?.leadingTrivia;
1!
1254
    }
1255

1256
    public clone() {
1257
        return this.finalizeClone(
6✔
1258
            new AALiteralExpression({
1259
                elements: this.elements?.map(e => e?.clone()),
5✔
1260
                open: util.cloneToken(this.tokens.open),
1261
                close: util.cloneToken(this.tokens.close)
1262
            }),
1263
            ['elements']
1264
        );
1265
    }
1266
}
1267

1268
export class UnaryExpression extends Expression {
1✔
1269
    constructor(options: {
1270
        operator: Token;
1271
        right: Expression;
1272
    }) {
1273
        super();
66✔
1274
        this.tokens = {
66✔
1275
            operator: options.operator
1276
        };
1277
        this.right = options.right;
66✔
1278
        this.location = util.createBoundingLocation(this.tokens.operator, this.right);
66✔
1279
    }
1280

1281
    public readonly kind = AstNodeKind.UnaryExpression;
66✔
1282

1283
    public readonly location: Location | undefined;
1284

1285
    public readonly tokens: {
1286
        readonly operator: Token;
1287
    };
1288
    public readonly right: Expression;
1289

1290
    transpile(state: BrsTranspileState) {
1291
        let separatingWhitespace: string | undefined;
1292
        if (isVariableExpression(this.right)) {
12✔
1293
            separatingWhitespace = this.right.tokens.name.leadingWhitespace;
6✔
1294
        } else if (isLiteralExpression(this.right)) {
6✔
1295
            separatingWhitespace = this.right.tokens.value.leadingWhitespace;
2✔
1296
        } else {
1297
            separatingWhitespace = ' ';
4✔
1298
        }
1299

1300
        return [
12✔
1301
            state.transpileToken(this.tokens.operator),
1302
            separatingWhitespace,
1303
            ...this.right.transpile(state)
1304
        ];
1305
    }
1306

1307
    walk(visitor: WalkVisitor, options: WalkOptions) {
1308
        if (options.walkMode & InternalWalkMode.walkExpressions) {
236!
1309
            walk(this, 'right', visitor, options);
236✔
1310
        }
1311
    }
1312

1313
    getType(options: GetTypeOptions): BscType {
1314
        return util.unaryOperatorResultType(this.tokens.operator, this.right.getType(options));
48✔
1315
    }
1316

1317
    public get leadingTrivia(): Token[] {
1318
        return this.tokens.operator.leadingTrivia;
185✔
1319
    }
1320

1321
    public clone() {
1322
        return this.finalizeClone(
2✔
1323
            new UnaryExpression({
1324
                operator: util.cloneToken(this.tokens.operator),
1325
                right: this.right?.clone()
6✔
1326
            }),
1327
            ['right']
1328
        );
1329
    }
1330
}
1331

1332
export class VariableExpression extends Expression {
1✔
1333
    constructor(options: {
1334
        name: Identifier;
1335
    }) {
1336
        super();
10,669✔
1337
        this.tokens = {
10,669✔
1338
            name: options.name
1339
        };
1340
        this.location = util.cloneLocation(this.tokens.name?.location);
10,669!
1341
    }
1342

1343
    public readonly tokens: {
1344
        readonly name: Identifier;
1345
    };
1346

1347
    public readonly kind = AstNodeKind.VariableExpression;
10,669✔
1348

1349
    public readonly location: Location;
1350

1351
    public getName(parseMode?: ParseMode) {
1352
        return this.tokens.name.text;
21,398✔
1353
    }
1354

1355
    transpile(state: BrsTranspileState) {
1356
        let result: TranspileResult = [];
6,196✔
1357
        const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
6,196✔
1358
        //if the callee is the name of a known namespace function
1359
        if (namespace && util.isCalleeMemberOfNamespace(this.tokens.name.text, this, namespace)) {
6,196✔
1360
            result.push(
17✔
1361
                //transpile leading comments since the token isn't being transpiled directly
1362
                ...state.transpileLeadingCommentsForAstNode(this),
1363
                state.sourceNode(this, [
1364
                    namespace.getName(ParseMode.BrightScript),
1365
                    '_',
1366
                    this.getName(ParseMode.BrightScript)
1367
                ])
1368
            );
1369
            //transpile  normally
1370
        } else {
1371
            result.push(
6,179✔
1372
                state.transpileToken(this.tokens.name)
1373
            );
1374
        }
1375
        return result;
6,196✔
1376
    }
1377

1378
    walk(visitor: WalkVisitor, options: WalkOptions) {
1379
        //nothing to walk
1380
    }
1381

1382

1383
    getType(options: GetTypeOptions) {
1384
        let resultType: BscType = util.tokenToBscType(this.tokens.name);
21,251✔
1385
        const nameKey = this.getName();
21,251✔
1386
        if (!resultType) {
21,251✔
1387
            const symbolTable = this.getSymbolTable();
16,952✔
1388
            resultType = symbolTable?.getSymbolType(nameKey, { ...options, fullName: nameKey, tableProvider: () => this.getSymbolTable() });
33,944!
1389

1390
            if (util.isClassUsedAsFunction(resultType, this, options)) {
16,952✔
1391
                resultType = FunctionType.instance;
20✔
1392
            }
1393

1394
        }
1395
        options?.typeChain?.push(new TypeChainEntry({ name: nameKey, type: resultType, data: options.data, astNode: this }));
21,251!
1396
        return resultType;
21,251✔
1397
    }
1398

1399
    get leadingTrivia(): Token[] {
1400
        return this.tokens.name.leadingTrivia;
35,136✔
1401
    }
1402

1403
    public clone() {
1404
        return this.finalizeClone(
54✔
1405
            new VariableExpression({
1406
                name: util.cloneToken(this.tokens.name)
1407
            })
1408
        );
1409
    }
1410
}
1411

1412
export class SourceLiteralExpression extends Expression {
1✔
1413
    constructor(options: {
1414
        value: Token;
1415
    }) {
1416
        super();
37✔
1417
        this.tokens = {
37✔
1418
            value: options.value
1419
        };
1420
        this.location = util.cloneLocation(this.tokens.value?.location);
37!
1421
    }
1422

1423
    public readonly location: Location;
1424

1425
    public readonly kind = AstNodeKind.SourceLiteralExpression;
37✔
1426

1427
    public readonly tokens: {
1428
        readonly value: Token;
1429
    };
1430

1431
    /**
1432
     * Find the index of the function in its parent
1433
     */
1434
    private findFunctionIndex(parentFunction: FunctionExpression, func: FunctionExpression) {
1435
        let index = -1;
4✔
1436
        parentFunction.findChild((node) => {
4✔
1437
            if (isFunctionExpression(node)) {
12✔
1438
                index++;
4✔
1439
                if (node === func) {
4!
1440
                    return true;
4✔
1441
                }
1442
            }
1443
        }, {
1444
            walkMode: WalkMode.visitAllRecursive
1445
        });
1446
        return index;
4✔
1447
    }
1448

1449
    private getFunctionName(state: BrsTranspileState, parseMode: ParseMode) {
1450
        let func = this.findAncestor<FunctionExpression>(isFunctionExpression);
8✔
1451
        let nameParts = [] as TranspileResult;
8✔
1452
        let parentFunction: FunctionExpression;
1453
        while ((parentFunction = func.findAncestor<FunctionExpression>(isFunctionExpression))) {
8✔
1454
            let index = this.findFunctionIndex(parentFunction, func);
4✔
1455
            nameParts.unshift(`anon${index}`);
4✔
1456
            func = parentFunction;
4✔
1457
        }
1458
        //get the index of this function in its parent
1459
        if (isFunctionStatement(func.parent)) {
8!
1460
            nameParts.unshift(
8✔
1461
                func.parent.getName(parseMode)
1462
            );
1463
        }
1464
        return nameParts.join('$');
8✔
1465
    }
1466

1467
    /**
1468
     * Get the line number from our token or from the closest ancestor that has a range
1469
     */
1470
    private getClosestLineNumber() {
1471
        let node: AstNode = this;
7✔
1472
        while (node) {
7✔
1473
            if (node.location?.range) {
17✔
1474
                return node.location.range.start.line + 1;
5✔
1475
            }
1476
            node = node.parent;
12✔
1477
        }
1478
        return -1;
2✔
1479
    }
1480

1481
    transpile(state: BrsTranspileState) {
1482
        let text: string;
1483
        switch (this.tokens.value.kind) {
31✔
1484
            case TokenKind.SourceFilePathLiteral:
40✔
1485
                const pathUrl = fileUrl(state.srcPath);
3✔
1486
                text = `"${pathUrl.substring(0, 4)}" + "${pathUrl.substring(4)}"`;
3✔
1487
                break;
3✔
1488
            case TokenKind.SourceLineNumLiteral:
1489
                //TODO find first parent that has range, or default to -1
1490
                text = `${this.getClosestLineNumber()}`;
4✔
1491
                break;
4✔
1492
            case TokenKind.FunctionNameLiteral:
1493
                text = `"${this.getFunctionName(state, ParseMode.BrightScript)}"`;
4✔
1494
                break;
4✔
1495
            case TokenKind.SourceFunctionNameLiteral:
1496
                text = `"${this.getFunctionName(state, ParseMode.BrighterScript)}"`;
4✔
1497
                break;
4✔
1498
            case TokenKind.SourceLocationLiteral:
1499
                const locationUrl = fileUrl(state.srcPath);
3✔
1500
                //TODO find first parent that has range, or default to -1
1501
                text = `"${locationUrl.substring(0, 4)}" + "${locationUrl.substring(4)}:${this.getClosestLineNumber()}"`;
3✔
1502
                break;
3✔
1503
            case TokenKind.PkgPathLiteral:
1504
                text = `"${util.sanitizePkgPath(state.file.pkgPath)}"`;
2✔
1505
                break;
2✔
1506
            case TokenKind.PkgLocationLiteral:
1507
                text = `"${util.sanitizePkgPath(state.file.pkgPath)}:" + str(LINE_NUM)`;
2✔
1508
                break;
2✔
1509
            case TokenKind.LineNumLiteral:
1510
            default:
1511
                //use the original text (because it looks like a variable)
1512
                text = this.tokens.value.text;
9✔
1513
                break;
9✔
1514

1515
        }
1516
        return [
31✔
1517
            state.sourceNode(this, text)
1518
        ];
1519
    }
1520

1521
    walk(visitor: WalkVisitor, options: WalkOptions) {
1522
        //nothing to walk
1523
    }
1524

1525
    get leadingTrivia(): Token[] {
1526
        return this.tokens.value.leadingTrivia;
152✔
1527
    }
1528

1529
    public clone() {
1530
        return this.finalizeClone(
1✔
1531
            new SourceLiteralExpression({
1532
                value: util.cloneToken(this.tokens.value)
1533
            })
1534
        );
1535
    }
1536
}
1537

1538
/**
1539
 * This expression transpiles and acts exactly like a CallExpression,
1540
 * except we need to uniquely identify these statements so we can
1541
 * do more type checking.
1542
 */
1543
export class NewExpression extends Expression {
1✔
1544
    constructor(options: {
1545
        new?: Token;
1546
        call: CallExpression;
1547
    }) {
1548
        super();
133✔
1549
        this.tokens = {
133✔
1550
            new: options.new
1551
        };
1552
        this.call = options.call;
133✔
1553
        this.location = util.createBoundingLocation(this.tokens.new, this.call);
133✔
1554
    }
1555

1556
    public readonly kind = AstNodeKind.NewExpression;
133✔
1557

1558
    public readonly location: Location | undefined;
1559

1560
    public readonly tokens: {
1561
        readonly new?: Token;
1562
    };
1563
    public readonly call: CallExpression;
1564

1565
    /**
1566
     * The name of the class to initialize (with optional namespace prefixed)
1567
     */
1568
    public get className() {
1569
        //the parser guarantees the callee of a new statement's call object will be
1570
        //either a VariableExpression or a DottedGet
1571
        return this.call.callee as (VariableExpression | DottedGetExpression);
92✔
1572
    }
1573

1574
    public transpile(state: BrsTranspileState) {
1575
        const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
15✔
1576
        const cls = state.file.getClassFileLink(
15✔
1577
            this.className.getName(ParseMode.BrighterScript),
1578
            namespace?.getName(ParseMode.BrighterScript)
45✔
1579
        )?.item;
15✔
1580
        //new statements within a namespace block can omit the leading namespace if the class resides in that same namespace.
1581
        //So we need to figure out if this is a namespace-omitted class, or if this class exists without a namespace.
1582
        return this.call.transpile(state, cls?.getName(ParseMode.BrightScript));
15✔
1583
    }
1584

1585
    walk(visitor: WalkVisitor, options: WalkOptions) {
1586
        if (options.walkMode & InternalWalkMode.walkExpressions) {
821!
1587
            walk(this, 'call', visitor, options);
821✔
1588
        }
1589
    }
1590

1591
    getType(options: GetTypeOptions) {
1592
        const result = this.call.getType(options);
322✔
1593
        if (options.typeChain) {
322✔
1594
            // modify last typechain entry to show it is a new ...()
1595
            const lastEntry = options.typeChain[options.typeChain.length - 1];
3✔
1596
            if (lastEntry) {
3!
1597
                lastEntry.astNode = this;
3✔
1598
            }
1599
        }
1600
        return result;
322✔
1601
    }
1602

1603
    get leadingTrivia(): Token[] {
1604
        return this.tokens.new.leadingTrivia;
576✔
1605
    }
1606

1607
    public clone() {
1608
        return this.finalizeClone(
2✔
1609
            new NewExpression({
1610
                new: util.cloneToken(this.tokens.new),
1611
                call: this.call?.clone()
6✔
1612
            }),
1613
            ['call']
1614
        );
1615
    }
1616
}
1617

1618
export class CallfuncExpression extends Expression {
1✔
1619
    constructor(options: {
1620
        callee: Expression;
1621
        operator?: Token;
1622
        methodName: Identifier;
1623
        openingParen?: Token;
1624
        args?: Expression[];
1625
        closingParen?: Token;
1626
    }) {
1627
        super();
32✔
1628
        this.tokens = {
32✔
1629
            operator: options.operator,
1630
            methodName: options.methodName,
1631
            openingParen: options.openingParen,
1632
            closingParen: options.closingParen
1633
        };
1634
        this.callee = options.callee;
32✔
1635
        this.args = options.args ?? [];
32✔
1636

1637
        this.location = util.createBoundingLocation(
32✔
1638
            this.callee,
1639
            this.tokens.operator,
1640
            this.tokens.methodName,
1641
            this.tokens.openingParen,
1642
            ...this.args ?? [],
96!
1643
            this.tokens.closingParen
1644
        );
1645
    }
1646

1647
    public readonly callee: Expression;
1648
    public readonly args: Expression[];
1649

1650
    public readonly tokens: {
1651
        readonly operator: Token;
1652
        readonly methodName: Identifier;
1653
        readonly openingParen?: Token;
1654
        readonly closingParen?: Token;
1655
    };
1656

1657
    public readonly kind = AstNodeKind.CallfuncExpression;
32✔
1658

1659
    public readonly location: Location | undefined;
1660

1661
    public transpile(state: BrsTranspileState) {
1662
        let result = [] as TranspileResult;
9✔
1663
        result.push(
9✔
1664
            ...this.callee.transpile(state),
1665
            state.sourceNode(this.tokens.operator, '.callfunc'),
1666
            state.transpileToken(this.tokens.openingParen, '('),
1667
            //the name of the function
1668
            state.sourceNode(this.tokens.methodName, ['"', this.tokens.methodName.text, '"'])
1669
        );
1670
        if (this.args?.length > 0) {
9!
1671
            result.push(', ');
4✔
1672
            //transpile args
1673
            for (let i = 0; i < this.args.length; i++) {
4✔
1674
                //add comma between args
1675
                if (i > 0) {
7✔
1676
                    result.push(', ');
3✔
1677
                }
1678
                let arg = this.args[i];
7✔
1679
                result.push(...arg.transpile(state));
7✔
1680
            }
1681
        } else if (state.options.legacyCallfuncHandling) {
5✔
1682
            result.push(', ', 'invalid');
2✔
1683
        }
1684

1685
        result.push(
9✔
1686
            state.transpileToken(this.tokens.closingParen, ')')
1687
        );
1688
        return result;
9✔
1689
    }
1690

1691
    walk(visitor: WalkVisitor, options: WalkOptions) {
1692
        if (options.walkMode & InternalWalkMode.walkExpressions) {
135!
1693
            walk(this, 'callee', visitor, options);
135✔
1694
            walkArray(this.args, visitor, options, this);
135✔
1695
        }
1696
    }
1697

1698
    getType(options: GetTypeOptions) {
1699
        let result: BscType = DynamicType.instance;
4✔
1700
        // a little hacky here with checking options.ignoreCall because callFuncExpression has the method name
1701
        // It's nicer for CallExpression, because it's a call on any expression.
1702
        const calleeType = this.callee.getType({ ...options, flags: SymbolTypeFlag.runtime });
4✔
1703
        if (isComponentType(calleeType) || isReferenceType(calleeType)) {
4!
1704
            const funcType = (calleeType as ComponentType).getCallFuncType?.(this.tokens.methodName.text, options);
4!
1705
            if (funcType) {
4✔
1706
                options.typeChain?.push(new TypeChainEntry({
3✔
1707
                    name: this.tokens.methodName.text,
1708
                    type: funcType,
1709
                    data: options.data,
1710
                    location: this.tokens.methodName.location,
1711
                    separatorToken: createToken(TokenKind.Callfunc),
1712
                    astNode: this
1713
                }));
1714
                if (options.ignoreCall) {
3✔
1715
                    result = funcType;
1✔
1716
                }
1717
            }
1718
            /* TODO:
1719
                make callfunc return types work
1720
            else if (isCallableType(funcType) && (!isReferenceType(funcType.returnType) || funcType.returnType.isResolvable())) {
1721
                result = funcType.returnType;
1722
            } else if (!isReferenceType(funcType) && (funcType as any)?.returnType?.isResolvable()) {
1723
                result = (funcType as any).returnType;
1724
            } else {
1725
                return new TypePropertyReferenceType(funcType, 'returnType');
1726
            }
1727
            */
1728
        }
1729

1730
        return result;
4✔
1731
    }
1732

1733
    get leadingTrivia(): Token[] {
1734
        return this.callee.leadingTrivia;
233✔
1735
    }
1736

1737
    public clone() {
1738
        return this.finalizeClone(
3✔
1739
            new CallfuncExpression({
1740
                callee: this.callee?.clone(),
9✔
1741
                operator: util.cloneToken(this.tokens.operator),
1742
                methodName: util.cloneToken(this.tokens.methodName),
1743
                openingParen: util.cloneToken(this.tokens.openingParen),
1744
                args: this.args?.map(e => e?.clone()),
2✔
1745
                closingParen: util.cloneToken(this.tokens.closingParen)
1746
            }),
1747
            ['callee', 'args']
1748
        );
1749
    }
1750
}
1751

1752
/**
1753
 * Since template strings can contain newlines, we need to concatenate multiple strings together with chr() calls.
1754
 * This is a single expression that represents the string contatenation of all parts of a single quasi.
1755
 */
1756
export class TemplateStringQuasiExpression extends Expression {
1✔
1757
    constructor(options: {
1758
        expressions: Array<LiteralExpression | EscapedCharCodeLiteralExpression>;
1759
    }) {
1760
        super();
108✔
1761
        this.expressions = options.expressions;
108✔
1762
        this.location = util.createBoundingLocation(
108✔
1763
            ...this.expressions ?? []
324✔
1764
        );
1765
    }
1766

1767
    public readonly expressions: Array<LiteralExpression | EscapedCharCodeLiteralExpression>;
1768
    public readonly kind = AstNodeKind.TemplateStringQuasiExpression;
108✔
1769

1770
    readonly location: Location | undefined;
1771

1772
    transpile(state: BrsTranspileState, skipEmptyStrings = true) {
35✔
1773
        let result = [] as TranspileResult;
43✔
1774
        let plus = '';
43✔
1775
        for (let expression of this.expressions) {
43✔
1776
            //skip empty strings
1777
            //TODO what does an empty string literal expression look like?
1778
            if (expression.tokens.value.text === '' && skipEmptyStrings === true) {
68✔
1779
                continue;
27✔
1780
            }
1781
            result.push(
41✔
1782
                plus,
1783
                ...expression.transpile(state)
1784
            );
1785
            plus = ' + ';
41✔
1786
        }
1787
        return result;
43✔
1788
    }
1789

1790
    walk(visitor: WalkVisitor, options: WalkOptions) {
1791
        if (options.walkMode & InternalWalkMode.walkExpressions) {
333!
1792
            walkArray(this.expressions, visitor, options, this);
333✔
1793
        }
1794
    }
1795

1796
    public clone() {
1797
        return this.finalizeClone(
15✔
1798
            new TemplateStringQuasiExpression({
1799
                expressions: this.expressions?.map(e => e?.clone())
20✔
1800
            }),
1801
            ['expressions']
1802
        );
1803
    }
1804
}
1805

1806
export class TemplateStringExpression extends Expression {
1✔
1807
    constructor(options: {
1808
        openingBacktick?: Token;
1809
        quasis: TemplateStringQuasiExpression[];
1810
        expressions: Expression[];
1811
        closingBacktick?: Token;
1812
    }) {
1813
        super();
49✔
1814
        this.tokens = {
49✔
1815
            openingBacktick: options.openingBacktick,
1816
            closingBacktick: options.closingBacktick
1817
        };
1818
        this.quasis = options.quasis;
49✔
1819
        this.expressions = options.expressions;
49✔
1820
        this.location = util.createBoundingLocation(
49✔
1821
            this.tokens.openingBacktick,
1822
            this.quasis?.[0],
147✔
1823
            this.quasis?.[this.quasis?.length - 1],
291!
1824
            this.tokens.closingBacktick
1825
        );
1826
    }
1827

1828
    public readonly kind = AstNodeKind.TemplateStringExpression;
49✔
1829

1830
    public readonly tokens: {
1831
        readonly openingBacktick?: Token;
1832
        readonly closingBacktick?: Token;
1833
    };
1834
    public readonly quasis: TemplateStringQuasiExpression[];
1835
    public readonly expressions: Expression[];
1836

1837
    public readonly location: Location | undefined;
1838

1839
    public getType(options: GetTypeOptions) {
1840
        return StringType.instance;
34✔
1841
    }
1842

1843
    transpile(state: BrsTranspileState) {
1844
        if (this.quasis.length === 1 && this.expressions.length === 0) {
20✔
1845
            return this.quasis[0].transpile(state);
10✔
1846
        }
1847
        let result = ['('];
10✔
1848
        let plus = '';
10✔
1849
        //helper function to figure out when to include the plus
1850
        function add(...items) {
1851
            if (items.length > 0) {
40✔
1852
                result.push(
29✔
1853
                    plus,
1854
                    ...items
1855
                );
1856
            }
1857
            //set the plus after the first occurance of a nonzero length set of items
1858
            if (plus === '' && items.length > 0) {
40✔
1859
                plus = ' + ';
10✔
1860
            }
1861
        }
1862

1863
        for (let i = 0; i < this.quasis.length; i++) {
10✔
1864
            let quasi = this.quasis[i];
25✔
1865
            let expression = this.expressions[i];
25✔
1866

1867
            add(
25✔
1868
                ...quasi.transpile(state)
1869
            );
1870
            if (expression) {
25✔
1871
                //skip the toString wrapper around certain expressions
1872
                if (
15✔
1873
                    isEscapedCharCodeLiteralExpression(expression) ||
34✔
1874
                    (isLiteralExpression(expression) && isStringType(expression.getType()))
1875
                ) {
1876
                    add(
3✔
1877
                        ...expression.transpile(state)
1878
                    );
1879

1880
                    //wrap all other expressions with a bslib_toString call to prevent runtime type mismatch errors
1881
                } else {
1882
                    add(
12✔
1883
                        state.bslibPrefix + '_toString(',
1884
                        ...expression.transpile(state),
1885
                        ')'
1886
                    );
1887
                }
1888
            }
1889
        }
1890
        //the expression should be wrapped in parens so it can be used line a single expression at runtime
1891
        result.push(')');
10✔
1892

1893
        return result;
10✔
1894
    }
1895

1896
    walk(visitor: WalkVisitor, options: WalkOptions) {
1897
        if (options.walkMode & InternalWalkMode.walkExpressions) {
161!
1898
            //walk the quasis and expressions in left-to-right order
1899
            for (let i = 0; i < this.quasis?.length; i++) {
161!
1900
                walk(this.quasis, i, visitor, options, this);
274✔
1901

1902
                //this skips the final loop iteration since we'll always have one more quasi than expression
1903
                if (this.expressions[i]) {
274✔
1904
                    walk(this.expressions, i, visitor, options, this);
113✔
1905
                }
1906
            }
1907
        }
1908
    }
1909

1910
    public clone() {
1911
        return this.finalizeClone(
7✔
1912
            new TemplateStringExpression({
1913
                openingBacktick: util.cloneToken(this.tokens.openingBacktick),
1914
                quasis: this.quasis?.map(e => e?.clone()),
12✔
1915
                expressions: this.expressions?.map(e => e?.clone()),
6✔
1916
                closingBacktick: util.cloneToken(this.tokens.closingBacktick)
1917
            }),
1918
            ['quasis', 'expressions']
1919
        );
1920
    }
1921
}
1922

1923
export class TaggedTemplateStringExpression extends Expression {
1✔
1924
    constructor(options: {
1925
        tagName: Identifier;
1926
        openingBacktick?: Token;
1927
        quasis: TemplateStringQuasiExpression[];
1928
        expressions: Expression[];
1929
        closingBacktick?: Token;
1930
    }) {
1931
        super();
12✔
1932
        this.tokens = {
12✔
1933
            tagName: options.tagName,
1934
            openingBacktick: options.openingBacktick,
1935
            closingBacktick: options.closingBacktick
1936
        };
1937
        this.quasis = options.quasis;
12✔
1938
        this.expressions = options.expressions;
12✔
1939

1940
        this.location = util.createBoundingLocation(
12✔
1941
            this.tokens.tagName,
1942
            this.tokens.openingBacktick,
1943
            this.quasis?.[0],
36✔
1944
            this.quasis?.[this.quasis?.length - 1],
69!
1945
            this.tokens.closingBacktick
1946
        );
1947
    }
1948

1949
    public readonly kind = AstNodeKind.TaggedTemplateStringExpression;
12✔
1950

1951
    public readonly tokens: {
1952
        readonly tagName: Identifier;
1953
        readonly openingBacktick?: Token;
1954
        readonly closingBacktick?: Token;
1955
    };
1956

1957
    public readonly quasis: TemplateStringQuasiExpression[];
1958
    public readonly expressions: Expression[];
1959

1960
    public readonly location: Location | undefined;
1961

1962
    transpile(state: BrsTranspileState) {
1963
        let result = [] as TranspileResult;
3✔
1964
        result.push(
3✔
1965
            state.transpileToken(this.tokens.tagName),
1966
            '(['
1967
        );
1968

1969
        //add quasis as the first array
1970
        for (let i = 0; i < this.quasis.length; i++) {
3✔
1971
            let quasi = this.quasis[i];
8✔
1972
            //separate items with a comma
1973
            if (i > 0) {
8✔
1974
                result.push(
5✔
1975
                    ', '
1976
                );
1977
            }
1978
            result.push(
8✔
1979
                ...quasi.transpile(state, false)
1980
            );
1981
        }
1982
        result.push(
3✔
1983
            '], ['
1984
        );
1985

1986
        //add expressions as the second array
1987
        for (let i = 0; i < this.expressions.length; i++) {
3✔
1988
            let expression = this.expressions[i];
5✔
1989
            if (i > 0) {
5✔
1990
                result.push(
2✔
1991
                    ', '
1992
                );
1993
            }
1994
            result.push(
5✔
1995
                ...expression.transpile(state)
1996
            );
1997
        }
1998
        result.push(
3✔
1999
            state.sourceNode(this.tokens.closingBacktick, '])')
2000
        );
2001
        return result;
3✔
2002
    }
2003

2004
    walk(visitor: WalkVisitor, options: WalkOptions) {
2005
        if (options.walkMode & InternalWalkMode.walkExpressions) {
26!
2006
            //walk the quasis and expressions in left-to-right order
2007
            for (let i = 0; i < this.quasis?.length; i++) {
26!
2008
                walk(this.quasis, i, visitor, options, this);
63✔
2009

2010
                //this skips the final loop iteration since we'll always have one more quasi than expression
2011
                if (this.expressions[i]) {
63✔
2012
                    walk(this.expressions, i, visitor, options, this);
37✔
2013
                }
2014
            }
2015
        }
2016
    }
2017

2018
    public clone() {
2019
        return this.finalizeClone(
3✔
2020
            new TaggedTemplateStringExpression({
2021
                tagName: util.cloneToken(this.tokens.tagName),
2022
                openingBacktick: util.cloneToken(this.tokens.openingBacktick),
2023
                quasis: this.quasis?.map(e => e?.clone()),
5✔
2024
                expressions: this.expressions?.map(e => e?.clone()),
3✔
2025
                closingBacktick: util.cloneToken(this.tokens.closingBacktick)
2026
            }),
2027
            ['quasis', 'expressions']
2028
        );
2029
    }
2030
}
2031

2032
export class AnnotationExpression extends Expression {
1✔
2033
    constructor(options: {
2034
        at?: Token;
2035
        name: Token;
2036
        call?: CallExpression;
2037
    }) {
2038
        super();
83✔
2039
        this.tokens = {
83✔
2040
            at: options.at,
2041
            name: options.name
2042
        };
2043
        this.call = options.call;
83✔
2044
        this.name = this.tokens.name.text;
83✔
2045
    }
2046

2047
    public readonly kind = AstNodeKind.AnnotationExpression;
83✔
2048

2049
    public readonly tokens: {
2050
        readonly at: Token;
2051
        readonly name: Token;
2052
    };
2053

2054
    public get location(): Location | undefined {
2055
        return util.createBoundingLocation(
75✔
2056
            this.tokens.at,
2057
            this.tokens.name,
2058
            this.call
2059
        );
2060
    }
2061

2062
    public readonly name: string;
2063

2064
    public call: CallExpression;
2065

2066
    /**
2067
     * Convert annotation arguments to JavaScript types
2068
     * @param strict If false, keep Expression objects not corresponding to JS types
2069
     */
2070
    getArguments(strict = true): ExpressionValue[] {
10✔
2071
        if (!this.call) {
11✔
2072
            return [];
1✔
2073
        }
2074
        return this.call.args.map(e => expressionToValue(e, strict));
20✔
2075
    }
2076

2077
    public get leadingTrivia(): Token[] {
2078
        return this.tokens.at?.leadingTrivia;
50!
2079
    }
2080

2081
    transpile(state: BrsTranspileState) {
2082
        //transpile only our leading comments
2083
        return state.transpileComments(this.leadingTrivia);
16✔
2084
    }
2085

2086
    walk(visitor: WalkVisitor, options: WalkOptions) {
2087
        //nothing to walk
2088
    }
2089
    getTypedef(state: BrsTranspileState) {
2090
        return [
9✔
2091
            '@',
2092
            this.name,
2093
            ...(this.call?.transpile(state) ?? [])
54✔
2094
        ];
2095
    }
2096

2097
    public clone() {
2098
        const clone = this.finalizeClone(
7✔
2099
            new AnnotationExpression({
2100
                at: util.cloneToken(this.tokens.at),
2101
                name: util.cloneToken(this.tokens.name)
2102
            })
2103
        );
2104
        return clone;
7✔
2105
    }
2106
}
2107

2108
export class TernaryExpression extends Expression {
1✔
2109
    constructor(options: {
2110
        test: Expression;
2111
        questionMark?: Token;
2112
        consequent?: Expression;
2113
        colon?: Token;
2114
        alternate?: Expression;
2115
    }) {
2116
        super();
82✔
2117
        this.tokens = {
82✔
2118
            questionMark: options.questionMark,
2119
            colon: options.colon
2120
        };
2121
        this.test = options.test;
82✔
2122
        this.consequent = options.consequent;
82✔
2123
        this.alternate = options.alternate;
82✔
2124
        this.location = util.createBoundingLocation(
82✔
2125
            this.test,
2126
            this.tokens.questionMark,
2127
            this.consequent,
2128
            this.tokens.colon,
2129
            this.alternate
2130
        );
2131
    }
2132

2133
    public readonly kind = AstNodeKind.TernaryExpression;
82✔
2134

2135
    public readonly location: Location | undefined;
2136

2137
    public readonly tokens: {
2138
        readonly questionMark?: Token;
2139
        readonly colon?: Token;
2140
    };
2141

2142
    public readonly test: Expression;
2143
    public readonly consequent?: Expression;
2144
    public readonly alternate?: Expression;
2145

2146
    transpile(state: BrsTranspileState) {
2147
        let result = [] as TranspileResult;
31✔
2148
        const file = state.file;
31✔
2149
        let consequentInfo = util.getExpressionInfo(this.consequent!, file);
31✔
2150
        let alternateInfo = util.getExpressionInfo(this.alternate!, file);
31✔
2151

2152
        //get all unique variable names used in the consequent and alternate, and sort them alphabetically so the output is consistent
2153
        let allUniqueVarNames = [...new Set([...consequentInfo.uniqueVarNames, ...alternateInfo.uniqueVarNames])].sort();
31✔
2154
        let mutatingExpressions = [
31✔
2155
            ...consequentInfo.expressions,
2156
            ...alternateInfo.expressions
2157
        ].filter(e => e instanceof CallExpression || e instanceof CallfuncExpression || e instanceof DottedGetExpression);
142✔
2158

2159
        if (mutatingExpressions.length > 0) {
31✔
2160
            result.push(
8✔
2161
                state.sourceNode(
2162
                    this.tokens.questionMark,
2163
                    //write all the scope variables as parameters.
2164
                    //TODO handle when there are more than 31 parameters
2165
                    `(function(${['__bsCondition', ...allUniqueVarNames].join(', ')})`
2166
                ),
2167
                state.newline,
2168
                //double indent so our `end function` line is still indented one at the end
2169
                state.indent(2),
2170
                state.sourceNode(this.test, `if __bsCondition then`),
2171
                state.newline,
2172
                state.indent(1),
2173
                state.sourceNode(this.consequent ?? this.tokens.questionMark, 'return '),
24!
2174
                ...this.consequent?.transpile(state) ?? [state.sourceNode(this.tokens.questionMark, 'invalid')],
48!
2175
                state.newline,
2176
                state.indent(-1),
2177
                state.sourceNode(this.consequent ?? this.tokens.questionMark, 'else'),
24!
2178
                state.newline,
2179
                state.indent(1),
2180
                state.sourceNode(this.consequent ?? this.tokens.questionMark, 'return '),
24!
2181
                ...this.alternate?.transpile(state) ?? [state.sourceNode(this.consequent ?? this.tokens.questionMark, 'invalid')],
48!
2182
                state.newline,
2183
                state.indent(-1),
2184
                state.sourceNode(this.tokens.questionMark, 'end if'),
2185
                state.newline,
2186
                state.indent(-1),
2187
                state.sourceNode(this.tokens.questionMark, 'end function)('),
2188
                ...this.test.transpile(state),
2189
                state.sourceNode(this.tokens.questionMark, `${['', ...allUniqueVarNames].join(', ')})`)
2190
            );
2191
            state.blockDepth--;
8✔
2192
        } else {
2193
            result.push(
23✔
2194
                state.sourceNode(this.test, state.bslibPrefix + `_ternary(`),
2195
                ...this.test.transpile(state),
2196
                state.sourceNode(this.test, `, `),
2197
                ...this.consequent?.transpile(state) ?? ['invalid'],
138✔
2198
                `, `,
2199
                ...this.alternate?.transpile(state) ?? ['invalid'],
138✔
2200
                `)`
2201
            );
2202
        }
2203
        return result;
31✔
2204
    }
2205

2206
    public walk(visitor: WalkVisitor, options: WalkOptions) {
2207
        if (options.walkMode & InternalWalkMode.walkExpressions) {
234!
2208
            walk(this, 'test', visitor, options);
234✔
2209
            walk(this, 'consequent', visitor, options);
234✔
2210
            walk(this, 'alternate', visitor, options);
234✔
2211
        }
2212
    }
2213

2214
    get leadingTrivia(): Token[] {
2215
        return this.test.leadingTrivia;
149✔
2216
    }
2217

2218
    public clone() {
2219
        return this.finalizeClone(
2✔
2220
            new TernaryExpression({
2221
                test: this.test?.clone(),
6✔
2222
                questionMark: util.cloneToken(this.tokens.questionMark),
2223
                consequent: this.consequent?.clone(),
6✔
2224
                colon: util.cloneToken(this.tokens.colon),
2225
                alternate: this.alternate?.clone()
6✔
2226
            }),
2227
            ['test', 'consequent', 'alternate']
2228
        );
2229
    }
2230
}
2231

2232
export class NullCoalescingExpression extends Expression {
1✔
2233
    constructor(options: {
2234
        consequent: Expression;
2235
        questionQuestion?: Token;
2236
        alternate: Expression;
2237
    }) {
2238
        super();
36✔
2239
        this.tokens = {
36✔
2240
            questionQuestion: options.questionQuestion
2241
        };
2242
        this.consequent = options.consequent;
36✔
2243
        this.alternate = options.alternate;
36✔
2244
        this.location = util.createBoundingLocation(
36✔
2245
            this.consequent,
2246
            this.tokens.questionQuestion,
2247
            this.alternate
2248
        );
2249
    }
2250

2251
    public readonly kind = AstNodeKind.NullCoalescingExpression;
36✔
2252

2253
    public readonly location: Location | undefined;
2254

2255
    public readonly tokens: {
2256
        readonly questionQuestion?: Token;
2257
    };
2258

2259
    public readonly consequent: Expression;
2260
    public readonly alternate: Expression;
2261

2262
    transpile(state: BrsTranspileState) {
2263
        let result = [] as TranspileResult;
10✔
2264
        let consequentInfo = util.getExpressionInfo(this.consequent, state.file);
10✔
2265
        let alternateInfo = util.getExpressionInfo(this.alternate, state.file);
10✔
2266

2267
        //get all unique variable names used in the consequent and alternate, and sort them alphabetically so the output is consistent
2268
        let allUniqueVarNames = [...new Set([...consequentInfo.uniqueVarNames, ...alternateInfo.uniqueVarNames])].sort();
10✔
2269
        let hasMutatingExpression = [
10✔
2270
            ...consequentInfo.expressions,
2271
            ...alternateInfo.expressions
2272
        ].find(e => isCallExpression(e) || isCallfuncExpression(e) || isDottedGetExpression(e));
28✔
2273

2274
        if (hasMutatingExpression) {
10✔
2275
            result.push(
6✔
2276
                `(function(`,
2277
                //write all the scope variables as parameters.
2278
                //TODO handle when there are more than 31 parameters
2279
                allUniqueVarNames.join(', '),
2280
                ')',
2281
                state.newline,
2282
                //double indent so our `end function` line is still indented one at the end
2283
                state.indent(2),
2284
                //evaluate the consequent exactly once, and then use it in the following condition
2285
                `__bsConsequent = `,
2286
                ...this.consequent.transpile(state),
2287
                state.newline,
2288
                state.indent(),
2289
                `if __bsConsequent <> invalid then`,
2290
                state.newline,
2291
                state.indent(1),
2292
                'return __bsConsequent',
2293
                state.newline,
2294
                state.indent(-1),
2295
                'else',
2296
                state.newline,
2297
                state.indent(1),
2298
                'return ',
2299
                ...this.alternate.transpile(state),
2300
                state.newline,
2301
                state.indent(-1),
2302
                'end if',
2303
                state.newline,
2304
                state.indent(-1),
2305
                'end function)(',
2306
                allUniqueVarNames.join(', '),
2307
                ')'
2308
            );
2309
            state.blockDepth--;
6✔
2310
        } else {
2311
            result.push(
4✔
2312
                state.bslibPrefix + `_coalesce(`,
2313
                ...this.consequent.transpile(state),
2314
                ', ',
2315
                ...this.alternate.transpile(state),
2316
                ')'
2317
            );
2318
        }
2319
        return result;
10✔
2320
    }
2321

2322
    public walk(visitor: WalkVisitor, options: WalkOptions) {
2323
        if (options.walkMode & InternalWalkMode.walkExpressions) {
84!
2324
            walk(this, 'consequent', visitor, options);
84✔
2325
            walk(this, 'alternate', visitor, options);
84✔
2326
        }
2327
    }
2328

2329
    get leadingTrivia(): Token[] {
2330
        return this.consequent.leadingTrivia;
50✔
2331
    }
2332

2333
    public clone() {
2334
        return this.finalizeClone(
2✔
2335
            new NullCoalescingExpression({
2336
                consequent: this.consequent?.clone(),
6✔
2337
                questionQuestion: util.cloneToken(this.tokens.questionQuestion),
2338
                alternate: this.alternate?.clone()
6✔
2339
            }),
2340
            ['consequent', 'alternate']
2341
        );
2342
    }
2343
}
2344

2345
export class RegexLiteralExpression extends Expression {
1✔
2346
    constructor(options: {
2347
        regexLiteral: Token;
2348
    }) {
2349
        super();
46✔
2350
        this.tokens = {
46✔
2351
            regexLiteral: options.regexLiteral
2352
        };
2353
    }
2354

2355
    public readonly kind = AstNodeKind.RegexLiteralExpression;
46✔
2356
    public readonly tokens: {
2357
        readonly regexLiteral: Token;
2358
    };
2359

2360
    public get location(): Location {
2361
        return this.tokens?.regexLiteral?.location;
150!
2362
    }
2363

2364
    public transpile(state: BrsTranspileState): TranspileResult {
2365
        let text = this.tokens.regexLiteral?.text ?? '';
42!
2366
        let flags = '';
42✔
2367
        //get any flags from the end
2368
        const flagMatch = /\/([a-z]+)$/i.exec(text);
42✔
2369
        if (flagMatch) {
42✔
2370
            text = text.substring(0, flagMatch.index + 1);
2✔
2371
            flags = flagMatch[1];
2✔
2372
        }
2373
        let pattern = text
42✔
2374
            //remove leading and trailing slashes
2375
            .substring(1, text.length - 1)
2376
            //escape quotemarks
2377
            .split('"').join('" + chr(34) + "');
2378

2379
        return [
42✔
2380
            state.sourceNode(this.tokens.regexLiteral, [
2381
                'CreateObject("roRegex", ',
2382
                `"${pattern}", `,
2383
                `"${flags}"`,
2384
                ')'
2385
            ])
2386
        ];
2387
    }
2388

2389
    walk(visitor: WalkVisitor, options: WalkOptions) {
2390
        //nothing to walk
2391
    }
2392

2393
    public clone() {
2394
        return this.finalizeClone(
1✔
2395
            new RegexLiteralExpression({
2396
                regexLiteral: util.cloneToken(this.tokens.regexLiteral)
2397
            })
2398
        );
2399
    }
2400

2401
    get leadingTrivia(): Token[] {
2402
        return this.tokens.regexLiteral?.leadingTrivia;
1,023!
2403
    }
2404
}
2405

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

2409
function expressionToValue(expr: Expression, strict: boolean): ExpressionValue {
2410
    if (!expr) {
30!
UNCOV
2411
        return null;
×
2412
    }
2413
    if (isUnaryExpression(expr) && isLiteralNumber(expr.right)) {
30✔
2414
        return numberExpressionToValue(expr.right, expr.tokens.operator.text);
1✔
2415
    }
2416
    if (isLiteralString(expr)) {
29✔
2417
        //remove leading and trailing quotes
2418
        return expr.tokens.value.text.replace(/^"/, '').replace(/"$/, '');
5✔
2419
    }
2420
    if (isLiteralNumber(expr)) {
24✔
2421
        return numberExpressionToValue(expr);
11✔
2422
    }
2423

2424
    if (isLiteralBoolean(expr)) {
13✔
2425
        return expr.tokens.value.text.toLowerCase() === 'true';
3✔
2426
    }
2427
    if (isArrayLiteralExpression(expr)) {
10✔
2428
        return expr.elements
3✔
2429
            .map(e => expressionToValue(e, strict));
7✔
2430
    }
2431
    if (isAALiteralExpression(expr)) {
7✔
2432
        return expr.elements.reduce((acc, e) => {
3✔
2433
            acc[e.tokens.key.text] = expressionToValue(e.value, strict);
3✔
2434
            return acc;
3✔
2435
        }, {});
2436
    }
2437
    //for annotations, we only support serializing pure string values
2438
    if (isTemplateStringExpression(expr)) {
4✔
2439
        if (expr.quasis?.length === 1 && expr.expressions.length === 0) {
2!
2440
            return expr.quasis[0].expressions.map(x => x.tokens.value.text).join('');
10✔
2441
        }
2442
    }
2443
    return strict ? null : expr;
2✔
2444
}
2445

2446
function numberExpressionToValue(expr: LiteralExpression, operator = '') {
11✔
2447
    if (isIntegerType(expr.getType()) || isLongIntegerType(expr.getType())) {
12!
2448
        return parseInt(operator + expr.tokens.value.text);
12✔
2449
    } else {
UNCOV
2450
        return parseFloat(operator + expr.tokens.value.text);
×
2451
    }
2452
}
2453

2454
export class TypeExpression extends Expression implements TypedefProvider {
1✔
2455
    constructor(options: {
2456
        /**
2457
         * The standard AST expression that represents the type for this TypeExpression.
2458
         */
2459
        expression: Expression;
2460
    }) {
2461
        super();
1,477✔
2462
        this.expression = options.expression;
1,477✔
2463
        this.location = util.cloneLocation(this.expression?.location);
1,477!
2464
    }
2465

2466
    public readonly kind = AstNodeKind.TypeExpression;
1,477✔
2467

2468
    /**
2469
     * The standard AST expression that represents the type for this TypeExpression.
2470
     */
2471
    public readonly expression: Expression;
2472

2473
    public readonly location: Location;
2474

2475
    public transpile(state: BrsTranspileState): TranspileResult {
2476
        return [this.getType({ flags: SymbolTypeFlag.typetime }).toTypeString()];
106✔
2477
    }
2478
    public walk(visitor: WalkVisitor, options: WalkOptions) {
2479
        if (options.walkMode & InternalWalkMode.walkExpressions) {
6,768✔
2480
            walk(this, 'expression', visitor, options);
6,643✔
2481
        }
2482
    }
2483

2484
    public getType(options: GetTypeOptions): BscType {
2485
        return this.expression.getType({ ...options, flags: SymbolTypeFlag.typetime });
5,289✔
2486
    }
2487

2488
    getTypedef(state: TranspileState): TranspileResult {
2489
        // TypeDefs should pass through any valid type names
2490
        return this.expression.transpile(state as BrsTranspileState);
33✔
2491
    }
2492

2493
    getName(parseMode = ParseMode.BrighterScript): string {
195✔
2494
        //TODO: this may not support Complex Types, eg. generics or Unions
2495
        return util.getAllDottedGetPartsAsString(this.expression, parseMode);
195✔
2496
    }
2497

2498
    getNameParts(): string[] {
2499
        //TODO: really, this code is only used to get Namespaces. It could be more clear.
2500
        return util.getAllDottedGetParts(this.expression).map(x => x.text);
20✔
2501
    }
2502

2503
    public clone() {
2504
        return this.finalizeClone(
15✔
2505
            new TypeExpression({
2506
                expression: this.expression?.clone()
45!
2507
            }),
2508
            ['expression']
2509
        );
2510
    }
2511
}
2512

2513
export class TypecastExpression extends Expression {
1✔
2514
    constructor(options: {
2515
        obj: Expression;
2516
        as?: Token;
2517
        typeExpression?: TypeExpression;
2518
    }) {
2519
        super();
68✔
2520
        this.tokens = {
68✔
2521
            as: options.as
2522
        };
2523
        this.obj = options.obj;
68✔
2524
        this.typeExpression = options.typeExpression;
68✔
2525
        this.location = util.createBoundingLocation(
68✔
2526
            this.obj,
2527
            this.tokens.as,
2528
            this.typeExpression
2529
        );
2530
    }
2531

2532
    public readonly kind = AstNodeKind.TypecastExpression;
68✔
2533

2534
    public readonly obj: Expression;
2535

2536
    public readonly tokens: {
2537
        readonly as?: Token;
2538
    };
2539

2540
    public typeExpression?: TypeExpression;
2541

2542
    public readonly location: Location;
2543

2544
    public transpile(state: BrsTranspileState): TranspileResult {
2545
        return this.obj.transpile(state);
13✔
2546
    }
2547
    public walk(visitor: WalkVisitor, options: WalkOptions) {
2548
        if (options.walkMode & InternalWalkMode.walkExpressions) {
318!
2549
            walk(this, 'obj', visitor, options);
318✔
2550
            walk(this, 'typeExpression', visitor, options);
318✔
2551
        }
2552
    }
2553

2554
    public getType(options: GetTypeOptions): BscType {
2555
        const result = this.typeExpression.getType(options);
80✔
2556
        if (options.typeChain) {
80✔
2557
            // modify last typechain entry to show it is a typecast
2558
            const lastEntry = options.typeChain[options.typeChain.length - 1];
18✔
2559
            if (lastEntry) {
18!
2560
                lastEntry.astNode = this;
18✔
2561
            }
2562
        }
2563
        return result;
80✔
2564
    }
2565

2566
    public clone() {
2567
        return this.finalizeClone(
3✔
2568
            new TypecastExpression({
2569
                obj: this.obj?.clone(),
9✔
2570
                as: util.cloneToken(this.tokens.as),
2571
                typeExpression: this.typeExpression?.clone()
9!
2572
            }),
2573
            ['obj', 'typeExpression']
2574
        );
2575
    }
2576
}
2577

2578
export class TypedArrayExpression extends Expression {
1✔
2579
    constructor(options: {
2580
        innerType: Expression;
2581
        leftBracket?: Token;
2582
        rightBracket?: Token;
2583
    }) {
2584
        super();
29✔
2585
        this.tokens = {
29✔
2586
            leftBracket: options.leftBracket,
2587
            rightBracket: options.rightBracket
2588
        };
2589
        this.innerType = options.innerType;
29✔
2590
        this.location = util.createBoundingLocation(
29✔
2591
            this.innerType,
2592
            this.tokens.leftBracket,
2593
            this.tokens.rightBracket
2594
        );
2595
    }
2596

2597
    public readonly tokens: {
2598
        readonly leftBracket?: Token;
2599
        readonly rightBracket?: Token;
2600
    };
2601

2602
    public readonly innerType: Expression;
2603

2604
    public readonly kind = AstNodeKind.TypedArrayExpression;
29✔
2605

2606
    public readonly location: Location;
2607

2608
    public transpile(state: BrsTranspileState): TranspileResult {
UNCOV
2609
        return [this.getType({ flags: SymbolTypeFlag.typetime }).toTypeString()];
×
2610
    }
2611

2612
    public walk(visitor: WalkVisitor, options: WalkOptions) {
2613
        if (options.walkMode & InternalWalkMode.walkExpressions) {
127!
2614
            walk(this, 'innerType', visitor, options);
127✔
2615
        }
2616
    }
2617

2618
    public getType(options: GetTypeOptions): BscType {
2619
        return new ArrayType(this.innerType.getType(options));
122✔
2620
    }
2621

2622
    public clone() {
UNCOV
2623
        return this.finalizeClone(
×
2624
            new TypedArrayExpression({
2625
                innerType: this.innerType?.clone(),
×
2626
                leftBracket: util.cloneToken(this.tokens.leftBracket),
2627
                rightBracket: util.cloneToken(this.tokens.rightBracket)
2628
            }),
2629
            ['innerType']
2630
        );
2631
    }
2632
}
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