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

rokucommunity / brighterscript / #13113

30 Sep 2024 04:35PM UTC coverage: 86.842% (-1.4%) from 88.193%
#13113

push

web-flow
Merge 25fc06528 into 3a2dc7282

11525 of 14034 branches covered (82.12%)

Branch coverage included in aggregate %.

6990 of 7581 new or added lines in 100 files covered. (92.2%)

83 existing lines in 18 files now uncovered.

12691 of 13851 relevant lines covered (91.63%)

29449.57 hits per line

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

92.38
/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,096✔
46
        this.tokens = {
3,096✔
47
            operator: options.operator
48
        };
49
        this.left = options.left;
3,096✔
50
        this.right = options.right;
3,096✔
51
        this.location = util.createBoundingLocation(this.left, this.tokens.operator, this.right);
3,096✔
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,096✔
61

62
    public readonly location: Location | undefined;
63

64
    transpile(state: BrsTranspileState): TranspileResult {
65
        return [
3,079✔
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,049!
76
            walk(this, 'left', visitor, options);
10,049✔
77
            walk(this, 'right', visitor, options);
10,049✔
78
        }
79
    }
80

81

82
    public getType(options: GetTypeOptions): BscType {
83
        const operatorKind = this.tokens.operator.kind;
339✔
84
        if (options.flags & SymbolTypeFlag.typetime) {
339✔
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) {
260!
92
            return util.binaryOperatorResultType(
260✔
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,581✔
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,305✔
133
        this.tokens = {
2,305✔
134
            openingParen: options.openingParen,
135
            closingParen: options.closingParen
136
        };
137
        this.callee = options.callee;
2,305✔
138
        this.args = options.args ?? [];
2,305✔
139
        this.location = util.createBoundingLocation(this.callee, this.tokens.openingParen, ...this.args ?? [], this.tokens.closingParen);
2,305!
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,305✔
153

154
    public readonly location: Location | undefined;
155

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

159
        //transpile the name
160
        if (nameOverride) {
1,536✔
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,524✔
168
        }
169

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

540
        return result;
2,092✔
541
    }
542

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

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

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

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

560
        return results;
73✔
561
    }
562

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

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

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

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

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

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

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

616
    public readonly location: Location | undefined;
617

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

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

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

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

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

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

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

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

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

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

708
    public readonly obj: Expression;
709

710
    public readonly location: Location | undefined;
711

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

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

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

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

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

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

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

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

785
    public readonly location: Location | undefined;
786

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

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

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

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

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

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

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

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

867
    public readonly location: Location | undefined;
868

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

981
    public readonly kind = AstNodeKind.EscapedCharCodeLiteralExpression;
35✔
982

983
    public readonly tokens: {
984
        readonly value: Token & { charCode: number };
985
    };
986

987
    public readonly location: Location;
988

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

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

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

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

1023
    public readonly elements: Array<Expression>;
1024

1025
    public readonly tokens: {
1026
        readonly open?: Token;
1027
        readonly close?: Token;
1028
    };
1029

1030
    public readonly kind = AstNodeKind.ArrayLiteralExpression;
143✔
1031

1032
    public readonly location: Location | undefined;
1033

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

1042
        for (let i = 0; i < this.elements.length; i++) {
50✔
1043
            let previousElement = this.elements[i - 1];
53✔
1044
            let element = this.elements[i];
53✔
1045

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

1063
        return result;
50✔
1064
    }
1065

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

1072
    getType(options: GetTypeOptions): BscType {
1073
        const innerTypes = this.elements.map(expr => expr.getType(options));
172✔
1074
        return new ArrayType(...innerTypes);
133✔
1075
    }
1076
    get leadingTrivia(): Token[] {
1077
        return this.tokens.open?.leadingTrivia;
445!
1078
    }
1079

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

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

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

1114
    public readonly kind = AstNodeKind.AAMemberExpression;
275✔
1115

1116
    public readonly location: Location | undefined;
1117

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

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

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

1132
    walk(visitor: WalkVisitor, options: WalkOptions) {
1133
        walk(this, 'value', visitor, options);
901✔
1134
    }
1135

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

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

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

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

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

1177
    public readonly kind = AstNodeKind.AALiteralExpression;
267✔
1178

1179
    public readonly location: Location | undefined;
1180

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

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

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

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

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

1230
        return result;
55✔
1231
    }
1232

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

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

1250
    public get leadingTrivia(): Token[] {
1251
        return this.tokens.open?.leadingTrivia;
668!
1252
    }
1253

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

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

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

1283
    public readonly kind = AstNodeKind.UnaryExpression;
66✔
1284

1285
    public readonly location: Location | undefined;
1286

1287
    public readonly tokens: {
1288
        readonly operator: Token;
1289
    };
1290
    public readonly right: Expression;
1291

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

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

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

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

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

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

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

1345
    public readonly tokens: {
1346
        readonly name: Identifier;
1347
    };
1348

1349
    public readonly kind = AstNodeKind.VariableExpression;
10,381✔
1350

1351
    public readonly location: Location;
1352

1353
    public getName(parseMode?: ParseMode) {
1354
        return this.tokens.name.text;
20,559✔
1355
    }
1356

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

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

1384

1385
    getType(options: GetTypeOptions) {
1386
        let resultType: BscType = util.tokenToBscType(this.tokens.name);
20,421✔
1387
        const nameKey = this.getName();
20,421✔
1388
        if (!resultType) {
20,421✔
1389
            const symbolTable = this.getSymbolTable();
16,466✔
1390
            resultType = symbolTable?.getSymbolType(nameKey, { ...options, fullName: nameKey, tableProvider: () => this.getSymbolTable() });
33,102!
1391

1392
            if (util.isClassUsedAsFunction(resultType, this, options)) {
16,466✔
1393
                resultType = FunctionType.instance;
20✔
1394
            }
1395

1396
        }
1397
        options?.typeChain?.push(new TypeChainEntry({ name: nameKey, type: resultType, data: options.data, astNode: this }));
20,421!
1398
        return resultType;
20,421✔
1399
    }
1400

1401
    get leadingTrivia(): Token[] {
1402
        return this.tokens.name.leadingTrivia;
34,434✔
1403
    }
1404

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

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

1425
    public readonly location: Location;
1426

1427
    public readonly kind = AstNodeKind.SourceLiteralExpression;
37✔
1428

1429
    public readonly tokens: {
1430
        readonly value: Token;
1431
    };
1432

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

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

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

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

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

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

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

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

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

1558
    public readonly kind = AstNodeKind.NewExpression;
132✔
1559

1560
    public readonly location: Location | undefined;
1561

1562
    public readonly tokens: {
1563
        readonly new?: Token;
1564
    };
1565
    public readonly call: CallExpression;
1566

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

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

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

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

1605
    get leadingTrivia(): Token[] {
1606
        return this.tokens.new.leadingTrivia;
571✔
1607
    }
1608

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

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

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

1649
    public readonly callee: Expression;
1650
    public readonly args: Expression[];
1651

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

1659
    public readonly kind = AstNodeKind.CallfuncExpression;
32✔
1660

1661
    public readonly location: Location | undefined;
1662

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

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

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

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

1732
        return result;
4✔
1733
    }
1734

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

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

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

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

1772
    readonly location: Location | undefined;
1773

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

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

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

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

1830
    public readonly kind = AstNodeKind.TemplateStringExpression;
49✔
1831

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

1839
    public readonly location: Location | undefined;
1840

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

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

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

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

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

1895
        return result;
10✔
1896
    }
1897

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

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

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

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

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

1951
    public readonly kind = AstNodeKind.TaggedTemplateStringExpression;
12✔
1952

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

1959
    public readonly quasis: TemplateStringQuasiExpression[];
1960
    public readonly expressions: Expression[];
1961

1962
    public readonly location: Location | undefined;
1963

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

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

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

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

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

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

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

2049
    public readonly kind = AstNodeKind.AnnotationExpression;
83✔
2050

2051
    public readonly tokens: {
2052
        readonly at: Token;
2053
        readonly name: Token;
2054
    };
2055

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

2064
    public readonly name: string;
2065

2066
    public call: CallExpression;
2067

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

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

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

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

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

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

2135
    public readonly kind = AstNodeKind.TernaryExpression;
80✔
2136

2137
    public readonly location: Location | undefined;
2138

2139
    public readonly tokens: {
2140
        readonly questionMark?: Token;
2141
        readonly colon?: Token;
2142
    };
2143

2144
    public readonly test: Expression;
2145
    public readonly consequent?: Expression;
2146
    public readonly alternate?: Expression;
2147

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

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

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

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

2216
    get leadingTrivia(): Token[] {
2217
        return this.test.leadingTrivia;
139✔
2218
    }
2219

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

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

2253
    public readonly kind = AstNodeKind.NullCoalescingExpression;
36✔
2254

2255
    public readonly location: Location | undefined;
2256

2257
    public readonly tokens: {
2258
        readonly questionQuestion?: Token;
2259
    };
2260

2261
    public readonly consequent: Expression;
2262
    public readonly alternate: Expression;
2263

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

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

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

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

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

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

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

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

2362
    public get location(): Location {
2363
        return this.tokens?.regexLiteral?.location;
84!
2364
    }
2365

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

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

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

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

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

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

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

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

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

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

2468
    public readonly kind = AstNodeKind.TypeExpression;
1,457✔
2469

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

2475
    public readonly location: Location;
2476

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

2486
    public getType(options: GetTypeOptions): BscType {
2487
        return this.expression.getType({ ...options, flags: SymbolTypeFlag.typetime });
4,903✔
2488
    }
2489

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

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

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

2505
    public clone() {
2506
        return this.finalizeClone(
16✔
2507
            new TypeExpression({
2508
                expression: this.expression?.clone()
48!
2509
            }),
2510
            ['expression']
2511
        );
2512
    }
2513
}
2514

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

2534
    public readonly kind = AstNodeKind.TypecastExpression;
69✔
2535

2536
    public readonly obj: Expression;
2537

2538
    public readonly tokens: {
2539
        readonly as?: Token;
2540
    };
2541

2542
    public typeExpression?: TypeExpression;
2543

2544
    public readonly location: Location;
2545

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

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

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

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

2599
    public readonly tokens: {
2600
        readonly leftBracket?: Token;
2601
        readonly rightBracket?: Token;
2602
    };
2603

2604
    public readonly innerType: Expression;
2605

2606
    public readonly kind = AstNodeKind.TypedArrayExpression;
29✔
2607

2608
    public readonly location: Location;
2609

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

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

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

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

© 2025 Coveralls, Inc