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

rokucommunity / brighterscript / #13340

25 Nov 2024 08:39PM UTC coverage: 86.874% (-1.3%) from 88.164%
#13340

push

web-flow
Merge 57fa2ad4d into bc54435b1

11692 of 14218 branches covered (82.23%)

Branch coverage included in aggregate %.

7055 of 7645 new or added lines in 100 files covered. (92.28%)

87 existing lines in 18 files now uncovered.

12784 of 13956 relevant lines covered (91.6%)

31400.83 hits per line

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

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

62
    public readonly location: Location | undefined;
63

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

81

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

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

154
    public readonly location: Location | undefined;
155

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

484
    public readonly kind = AstNodeKind.FunctionParameterExpression;
3,004✔
485

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

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

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

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

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

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

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

538
        return result;
2,230✔
539
    }
540

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

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

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

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

558
        return results;
73✔
559
    }
560

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

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

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

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

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

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

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

614
    public readonly location: Location | undefined;
615

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

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

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

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

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

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

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

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

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

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

706
    public readonly obj: Expression;
707

708
    public readonly location: Location | undefined;
709

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

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

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

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

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

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

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

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

783
    public readonly location: Location | undefined;
784

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

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

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

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

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

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

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

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

865
    public readonly location: Location | undefined;
866

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

985
    public readonly location: Location;
986

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

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

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

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

1021
    public readonly elements: Array<Expression>;
1022

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

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

1030
    public readonly location: Location | undefined;
1031

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

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

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

1061
        return result;
60✔
1062
    }
1063

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

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

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

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

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

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

1114
    public readonly location: Location | undefined;
1115

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

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

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

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

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

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

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

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

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

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

1177
    public readonly location: Location | undefined;
1178

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

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

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

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

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

1228
        return result;
61✔
1229
    }
1230

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

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

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

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

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

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

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

1283
    public readonly location: Location | undefined;
1284

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

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

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

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

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

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

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

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

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

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

1349
    public readonly location: Location;
1350

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

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

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

1382

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

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

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

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

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

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

1423
    public readonly location: Location;
1424

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

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

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

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

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

1481
    transpile(state: BrsTranspileState) {
1482
        let text: string;
1483
        switch (this.tokens.value.kind) {
31✔
1484
            case TokenKind.SourceFilePathLiteral:
40!
1485
                const pathUrl = fileUrl(state.srcPath);
3✔
1486
                text = `"${pathUrl.substring(0, 4)}" + "${pathUrl.substring(4)}"`;
3✔
1487
                break;
3✔
1488
            case TokenKind.SourceLineNumLiteral:
1489
                //TODO find first parent that has range, or default to -1
1490
                text = `${this.getClosestLineNumber()}`;
4✔
1491
                break;
4✔
1492
            case TokenKind.FunctionNameLiteral:
1493
                text = `"${this.getFunctionName(state, ParseMode.BrightScript)}"`;
4✔
1494
                break;
4✔
1495
            case TokenKind.SourceFunctionNameLiteral:
1496
                text = `"${this.getFunctionName(state, ParseMode.BrighterScript)}"`;
4✔
1497
                break;
4✔
1498
            case TokenKind.SourceNamespaceNameLiteral:
1499
                let namespaceParts = this.getFunctionName(state, ParseMode.BrighterScript).split('.');
×
1500
                namespaceParts.pop(); // remove the function name
×
1501

1502
                text = `"${namespaceParts.join('.')}"`;
×
1503
                break;
×
1504
            case TokenKind.SourceNamespaceRootNameLiteral:
1505
                let namespaceRootParts = this.getFunctionName(state, ParseMode.BrighterScript).split('.');
×
1506
                namespaceRootParts.pop(); // remove the function name
×
1507

1508
                let rootNamespace = namespaceRootParts.shift() ?? '';
×
1509
                text = `"${rootNamespace}"`;
×
1510
                break;
×
1511
            case TokenKind.SourceLocationLiteral:
1512
                const locationUrl = fileUrl(state.srcPath);
3✔
1513
                //TODO find first parent that has range, or default to -1
1514
                text = `"${locationUrl.substring(0, 4)}" + "${locationUrl.substring(4)}:${this.getClosestLineNumber()}"`;
3✔
1515
                break;
3✔
1516
            case TokenKind.PkgPathLiteral:
1517
                text = `"${util.sanitizePkgPath(state.file.pkgPath)}"`;
2✔
1518
                break;
2✔
1519
            case TokenKind.PkgLocationLiteral:
1520
                text = `"${util.sanitizePkgPath(state.file.pkgPath)}:" + str(LINE_NUM)`;
2✔
1521
                break;
2✔
1522
            case TokenKind.LineNumLiteral:
1523
            default:
1524
                //use the original text (because it looks like a variable)
1525
                text = this.tokens.value.text;
9✔
1526
                break;
9✔
1527

1528
        }
1529
        return [
31✔
1530
            state.sourceNode(this, text)
1531
        ];
1532
    }
1533

1534
    walk(visitor: WalkVisitor, options: WalkOptions) {
1535
        //nothing to walk
1536
    }
1537

1538
    get leadingTrivia(): Token[] {
1539
        return this.tokens.value.leadingTrivia;
152✔
1540
    }
1541

1542
    public clone() {
1543
        return this.finalizeClone(
1✔
1544
            new SourceLiteralExpression({
1545
                value: util.cloneToken(this.tokens.value)
1546
            })
1547
        );
1548
    }
1549
}
1550

1551
/**
1552
 * This expression transpiles and acts exactly like a CallExpression,
1553
 * except we need to uniquely identify these statements so we can
1554
 * do more type checking.
1555
 */
1556
export class NewExpression extends Expression {
1✔
1557
    constructor(options: {
1558
        new?: Token;
1559
        call: CallExpression;
1560
    }) {
1561
        super();
133✔
1562
        this.tokens = {
133✔
1563
            new: options.new
1564
        };
1565
        this.call = options.call;
133✔
1566
        this.location = util.createBoundingLocation(this.tokens.new, this.call);
133✔
1567
    }
1568

1569
    public readonly kind = AstNodeKind.NewExpression;
133✔
1570

1571
    public readonly location: Location | undefined;
1572

1573
    public readonly tokens: {
1574
        readonly new?: Token;
1575
    };
1576
    public readonly call: CallExpression;
1577

1578
    /**
1579
     * The name of the class to initialize (with optional namespace prefixed)
1580
     */
1581
    public get className() {
1582
        //the parser guarantees the callee of a new statement's call object will be
1583
        //either a VariableExpression or a DottedGet
1584
        return this.call.callee as (VariableExpression | DottedGetExpression);
92✔
1585
    }
1586

1587
    public transpile(state: BrsTranspileState) {
1588
        const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
15✔
1589
        const cls = state.file.getClassFileLink(
15✔
1590
            this.className.getName(ParseMode.BrighterScript),
1591
            namespace?.getName(ParseMode.BrighterScript)
45✔
1592
        )?.item;
15✔
1593
        //new statements within a namespace block can omit the leading namespace if the class resides in that same namespace.
1594
        //So we need to figure out if this is a namespace-omitted class, or if this class exists without a namespace.
1595
        return this.call.transpile(state, cls?.getName(ParseMode.BrightScript));
15✔
1596
    }
1597

1598
    walk(visitor: WalkVisitor, options: WalkOptions) {
1599
        if (options.walkMode & InternalWalkMode.walkExpressions) {
835!
1600
            walk(this, 'call', visitor, options);
835✔
1601
        }
1602
    }
1603

1604
    getType(options: GetTypeOptions) {
1605
        const result = this.call.getType(options);
322✔
1606
        if (options.typeChain) {
322✔
1607
            // modify last typechain entry to show it is a new ...()
1608
            const lastEntry = options.typeChain[options.typeChain.length - 1];
3✔
1609
            if (lastEntry) {
3!
1610
                lastEntry.astNode = this;
3✔
1611
            }
1612
        }
1613
        return result;
322✔
1614
    }
1615

1616
    get leadingTrivia(): Token[] {
1617
        return this.tokens.new.leadingTrivia;
576✔
1618
    }
1619

1620
    public clone() {
1621
        return this.finalizeClone(
2✔
1622
            new NewExpression({
1623
                new: util.cloneToken(this.tokens.new),
1624
                call: this.call?.clone()
6✔
1625
            }),
1626
            ['call']
1627
        );
1628
    }
1629
}
1630

1631
export class CallfuncExpression extends Expression {
1✔
1632
    constructor(options: {
1633
        callee: Expression;
1634
        operator?: Token;
1635
        methodName: Identifier;
1636
        openingParen?: Token;
1637
        args?: Expression[];
1638
        closingParen?: Token;
1639
    }) {
1640
        super();
32✔
1641
        this.tokens = {
32✔
1642
            operator: options.operator,
1643
            methodName: options.methodName,
1644
            openingParen: options.openingParen,
1645
            closingParen: options.closingParen
1646
        };
1647
        this.callee = options.callee;
32✔
1648
        this.args = options.args ?? [];
32✔
1649

1650
        this.location = util.createBoundingLocation(
32✔
1651
            this.callee,
1652
            this.tokens.operator,
1653
            this.tokens.methodName,
1654
            this.tokens.openingParen,
1655
            ...this.args ?? [],
96!
1656
            this.tokens.closingParen
1657
        );
1658
    }
1659

1660
    public readonly callee: Expression;
1661
    public readonly args: Expression[];
1662

1663
    public readonly tokens: {
1664
        readonly operator: Token;
1665
        readonly methodName: Identifier;
1666
        readonly openingParen?: Token;
1667
        readonly closingParen?: Token;
1668
    };
1669

1670
    public readonly kind = AstNodeKind.CallfuncExpression;
32✔
1671

1672
    public readonly location: Location | undefined;
1673

1674
    public transpile(state: BrsTranspileState) {
1675
        let result = [] as TranspileResult;
9✔
1676
        result.push(
9✔
1677
            ...this.callee.transpile(state),
1678
            state.sourceNode(this.tokens.operator, '.callfunc'),
1679
            state.transpileToken(this.tokens.openingParen, '('),
1680
            //the name of the function
1681
            state.sourceNode(this.tokens.methodName, ['"', this.tokens.methodName.text, '"'])
1682
        );
1683
        if (this.args?.length > 0) {
9!
1684
            result.push(', ');
4✔
1685
            //transpile args
1686
            for (let i = 0; i < this.args.length; i++) {
4✔
1687
                //add comma between args
1688
                if (i > 0) {
7✔
1689
                    result.push(', ');
3✔
1690
                }
1691
                let arg = this.args[i];
7✔
1692
                result.push(...arg.transpile(state));
7✔
1693
            }
1694
        } else if (state.options.legacyCallfuncHandling) {
5✔
1695
            result.push(', ', 'invalid');
2✔
1696
        }
1697

1698
        result.push(
9✔
1699
            state.transpileToken(this.tokens.closingParen, ')')
1700
        );
1701
        return result;
9✔
1702
    }
1703

1704
    walk(visitor: WalkVisitor, options: WalkOptions) {
1705
        if (options.walkMode & InternalWalkMode.walkExpressions) {
142!
1706
            walk(this, 'callee', visitor, options);
142✔
1707
            walkArray(this.args, visitor, options, this);
142✔
1708
        }
1709
    }
1710

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

1743
        return result;
4✔
1744
    }
1745

1746
    get leadingTrivia(): Token[] {
1747
        return this.callee.leadingTrivia;
233✔
1748
    }
1749

1750
    public clone() {
1751
        return this.finalizeClone(
3✔
1752
            new CallfuncExpression({
1753
                callee: this.callee?.clone(),
9✔
1754
                operator: util.cloneToken(this.tokens.operator),
1755
                methodName: util.cloneToken(this.tokens.methodName),
1756
                openingParen: util.cloneToken(this.tokens.openingParen),
1757
                args: this.args?.map(e => e?.clone()),
2✔
1758
                closingParen: util.cloneToken(this.tokens.closingParen)
1759
            }),
1760
            ['callee', 'args']
1761
        );
1762
    }
1763
}
1764

1765
/**
1766
 * Since template strings can contain newlines, we need to concatenate multiple strings together with chr() calls.
1767
 * This is a single expression that represents the string contatenation of all parts of a single quasi.
1768
 */
1769
export class TemplateStringQuasiExpression extends Expression {
1✔
1770
    constructor(options: {
1771
        expressions: Array<LiteralExpression | EscapedCharCodeLiteralExpression>;
1772
    }) {
1773
        super();
108✔
1774
        this.expressions = options.expressions;
108✔
1775
        this.location = util.createBoundingLocation(
108✔
1776
            ...this.expressions ?? []
324✔
1777
        );
1778
    }
1779

1780
    public readonly expressions: Array<LiteralExpression | EscapedCharCodeLiteralExpression>;
1781
    public readonly kind = AstNodeKind.TemplateStringQuasiExpression;
108✔
1782

1783
    readonly location: Location | undefined;
1784

1785
    transpile(state: BrsTranspileState, skipEmptyStrings = true) {
35✔
1786
        let result = [] as TranspileResult;
43✔
1787
        let plus = '';
43✔
1788
        for (let expression of this.expressions) {
43✔
1789
            //skip empty strings
1790
            //TODO what does an empty string literal expression look like?
1791
            if (expression.tokens.value.text === '' && skipEmptyStrings === true) {
68✔
1792
                continue;
27✔
1793
            }
1794
            result.push(
41✔
1795
                plus,
1796
                ...expression.transpile(state)
1797
            );
1798
            plus = ' + ';
41✔
1799
        }
1800
        return result;
43✔
1801
    }
1802

1803
    walk(visitor: WalkVisitor, options: WalkOptions) {
1804
        if (options.walkMode & InternalWalkMode.walkExpressions) {
370!
1805
            walkArray(this.expressions, visitor, options, this);
370✔
1806
        }
1807
    }
1808

1809
    public clone() {
1810
        return this.finalizeClone(
15✔
1811
            new TemplateStringQuasiExpression({
1812
                expressions: this.expressions?.map(e => e?.clone())
20✔
1813
            }),
1814
            ['expressions']
1815
        );
1816
    }
1817
}
1818

1819
export class TemplateStringExpression extends Expression {
1✔
1820
    constructor(options: {
1821
        openingBacktick?: Token;
1822
        quasis: TemplateStringQuasiExpression[];
1823
        expressions: Expression[];
1824
        closingBacktick?: Token;
1825
    }) {
1826
        super();
49✔
1827
        this.tokens = {
49✔
1828
            openingBacktick: options.openingBacktick,
1829
            closingBacktick: options.closingBacktick
1830
        };
1831
        this.quasis = options.quasis;
49✔
1832
        this.expressions = options.expressions;
49✔
1833
        this.location = util.createBoundingLocation(
49✔
1834
            this.tokens.openingBacktick,
1835
            this.quasis?.[0],
147✔
1836
            this.quasis?.[this.quasis?.length - 1],
291!
1837
            this.tokens.closingBacktick
1838
        );
1839
    }
1840

1841
    public readonly kind = AstNodeKind.TemplateStringExpression;
49✔
1842

1843
    public readonly tokens: {
1844
        readonly openingBacktick?: Token;
1845
        readonly closingBacktick?: Token;
1846
    };
1847
    public readonly quasis: TemplateStringQuasiExpression[];
1848
    public readonly expressions: Expression[];
1849

1850
    public readonly location: Location | undefined;
1851

1852
    public getType(options: GetTypeOptions) {
1853
        return StringType.instance;
34✔
1854
    }
1855

1856
    transpile(state: BrsTranspileState) {
1857
        if (this.quasis.length === 1 && this.expressions.length === 0) {
20✔
1858
            return this.quasis[0].transpile(state);
10✔
1859
        }
1860
        let result = ['('];
10✔
1861
        let plus = '';
10✔
1862
        //helper function to figure out when to include the plus
1863
        function add(...items) {
1864
            if (items.length > 0) {
40✔
1865
                result.push(
29✔
1866
                    plus,
1867
                    ...items
1868
                );
1869
            }
1870
            //set the plus after the first occurance of a nonzero length set of items
1871
            if (plus === '' && items.length > 0) {
40✔
1872
                plus = ' + ';
10✔
1873
            }
1874
        }
1875

1876
        for (let i = 0; i < this.quasis.length; i++) {
10✔
1877
            let quasi = this.quasis[i];
25✔
1878
            let expression = this.expressions[i];
25✔
1879

1880
            add(
25✔
1881
                ...quasi.transpile(state)
1882
            );
1883
            if (expression) {
25✔
1884
                //skip the toString wrapper around certain expressions
1885
                if (
15✔
1886
                    isEscapedCharCodeLiteralExpression(expression) ||
34✔
1887
                    (isLiteralExpression(expression) && isStringType(expression.getType()))
1888
                ) {
1889
                    add(
3✔
1890
                        ...expression.transpile(state)
1891
                    );
1892

1893
                    //wrap all other expressions with a bslib_toString call to prevent runtime type mismatch errors
1894
                } else {
1895
                    add(
12✔
1896
                        state.bslibPrefix + '_toString(',
1897
                        ...expression.transpile(state),
1898
                        ')'
1899
                    );
1900
                }
1901
            }
1902
        }
1903
        //the expression should be wrapped in parens so it can be used line a single expression at runtime
1904
        result.push(')');
10✔
1905

1906
        return result;
10✔
1907
    }
1908

1909
    walk(visitor: WalkVisitor, options: WalkOptions) {
1910
        if (options.walkMode & InternalWalkMode.walkExpressions) {
180!
1911
            //walk the quasis and expressions in left-to-right order
1912
            for (let i = 0; i < this.quasis?.length; i++) {
180!
1913
                walk(this.quasis, i, visitor, options, this);
306✔
1914

1915
                //this skips the final loop iteration since we'll always have one more quasi than expression
1916
                if (this.expressions[i]) {
306✔
1917
                    walk(this.expressions, i, visitor, options, this);
126✔
1918
                }
1919
            }
1920
        }
1921
    }
1922

1923
    public clone() {
1924
        return this.finalizeClone(
7✔
1925
            new TemplateStringExpression({
1926
                openingBacktick: util.cloneToken(this.tokens.openingBacktick),
1927
                quasis: this.quasis?.map(e => e?.clone()),
12✔
1928
                expressions: this.expressions?.map(e => e?.clone()),
6✔
1929
                closingBacktick: util.cloneToken(this.tokens.closingBacktick)
1930
            }),
1931
            ['quasis', 'expressions']
1932
        );
1933
    }
1934
}
1935

1936
export class TaggedTemplateStringExpression extends Expression {
1✔
1937
    constructor(options: {
1938
        tagName: Identifier;
1939
        openingBacktick?: Token;
1940
        quasis: TemplateStringQuasiExpression[];
1941
        expressions: Expression[];
1942
        closingBacktick?: Token;
1943
    }) {
1944
        super();
12✔
1945
        this.tokens = {
12✔
1946
            tagName: options.tagName,
1947
            openingBacktick: options.openingBacktick,
1948
            closingBacktick: options.closingBacktick
1949
        };
1950
        this.quasis = options.quasis;
12✔
1951
        this.expressions = options.expressions;
12✔
1952

1953
        this.location = util.createBoundingLocation(
12✔
1954
            this.tokens.tagName,
1955
            this.tokens.openingBacktick,
1956
            this.quasis?.[0],
36✔
1957
            this.quasis?.[this.quasis?.length - 1],
69!
1958
            this.tokens.closingBacktick
1959
        );
1960
    }
1961

1962
    public readonly kind = AstNodeKind.TaggedTemplateStringExpression;
12✔
1963

1964
    public readonly tokens: {
1965
        readonly tagName: Identifier;
1966
        readonly openingBacktick?: Token;
1967
        readonly closingBacktick?: Token;
1968
    };
1969

1970
    public readonly quasis: TemplateStringQuasiExpression[];
1971
    public readonly expressions: Expression[];
1972

1973
    public readonly location: Location | undefined;
1974

1975
    transpile(state: BrsTranspileState) {
1976
        let result = [] as TranspileResult;
3✔
1977
        result.push(
3✔
1978
            state.transpileToken(this.tokens.tagName),
1979
            '(['
1980
        );
1981

1982
        //add quasis as the first array
1983
        for (let i = 0; i < this.quasis.length; i++) {
3✔
1984
            let quasi = this.quasis[i];
8✔
1985
            //separate items with a comma
1986
            if (i > 0) {
8✔
1987
                result.push(
5✔
1988
                    ', '
1989
                );
1990
            }
1991
            result.push(
8✔
1992
                ...quasi.transpile(state, false)
1993
            );
1994
        }
1995
        result.push(
3✔
1996
            '], ['
1997
        );
1998

1999
        //add expressions as the second array
2000
        for (let i = 0; i < this.expressions.length; i++) {
3✔
2001
            let expression = this.expressions[i];
5✔
2002
            if (i > 0) {
5✔
2003
                result.push(
2✔
2004
                    ', '
2005
                );
2006
            }
2007
            result.push(
5✔
2008
                ...expression.transpile(state)
2009
            );
2010
        }
2011
        result.push(
3✔
2012
            state.sourceNode(this.tokens.closingBacktick, '])')
2013
        );
2014
        return result;
3✔
2015
    }
2016

2017
    walk(visitor: WalkVisitor, options: WalkOptions) {
2018
        if (options.walkMode & InternalWalkMode.walkExpressions) {
28!
2019
            //walk the quasis and expressions in left-to-right order
2020
            for (let i = 0; i < this.quasis?.length; i++) {
28!
2021
                walk(this.quasis, i, visitor, options, this);
68✔
2022

2023
                //this skips the final loop iteration since we'll always have one more quasi than expression
2024
                if (this.expressions[i]) {
68✔
2025
                    walk(this.expressions, i, visitor, options, this);
40✔
2026
                }
2027
            }
2028
        }
2029
    }
2030

2031
    public clone() {
2032
        return this.finalizeClone(
3✔
2033
            new TaggedTemplateStringExpression({
2034
                tagName: util.cloneToken(this.tokens.tagName),
2035
                openingBacktick: util.cloneToken(this.tokens.openingBacktick),
2036
                quasis: this.quasis?.map(e => e?.clone()),
5✔
2037
                expressions: this.expressions?.map(e => e?.clone()),
3✔
2038
                closingBacktick: util.cloneToken(this.tokens.closingBacktick)
2039
            }),
2040
            ['quasis', 'expressions']
2041
        );
2042
    }
2043
}
2044

2045
export class AnnotationExpression extends Expression {
1✔
2046
    constructor(options: {
2047
        at?: Token;
2048
        name: Token;
2049
        call?: CallExpression;
2050
    }) {
2051
        super();
83✔
2052
        this.tokens = {
83✔
2053
            at: options.at,
2054
            name: options.name
2055
        };
2056
        this.call = options.call;
83✔
2057
        this.name = this.tokens.name.text;
83✔
2058
    }
2059

2060
    public readonly kind = AstNodeKind.AnnotationExpression;
83✔
2061

2062
    public readonly tokens: {
2063
        readonly at: Token;
2064
        readonly name: Token;
2065
    };
2066

2067
    public get location(): Location | undefined {
2068
        return util.createBoundingLocation(
75✔
2069
            this.tokens.at,
2070
            this.tokens.name,
2071
            this.call
2072
        );
2073
    }
2074

2075
    public readonly name: string;
2076

2077
    public call: CallExpression;
2078

2079
    /**
2080
     * Convert annotation arguments to JavaScript types
2081
     * @param strict If false, keep Expression objects not corresponding to JS types
2082
     */
2083
    getArguments(strict = true): ExpressionValue[] {
10✔
2084
        if (!this.call) {
11✔
2085
            return [];
1✔
2086
        }
2087
        return this.call.args.map(e => expressionToValue(e, strict));
20✔
2088
    }
2089

2090
    public get leadingTrivia(): Token[] {
2091
        return this.tokens.at?.leadingTrivia;
50!
2092
    }
2093

2094
    transpile(state: BrsTranspileState) {
2095
        //transpile only our leading comments
2096
        return state.transpileComments(this.leadingTrivia);
16✔
2097
    }
2098

2099
    walk(visitor: WalkVisitor, options: WalkOptions) {
2100
        //nothing to walk
2101
    }
2102
    getTypedef(state: BrsTranspileState) {
2103
        return [
9✔
2104
            '@',
2105
            this.name,
2106
            ...(this.call?.transpile(state) ?? [])
54✔
2107
        ];
2108
    }
2109

2110
    public clone() {
2111
        const clone = this.finalizeClone(
7✔
2112
            new AnnotationExpression({
2113
                at: util.cloneToken(this.tokens.at),
2114
                name: util.cloneToken(this.tokens.name)
2115
            })
2116
        );
2117
        return clone;
7✔
2118
    }
2119
}
2120

2121
export class TernaryExpression extends Expression {
1✔
2122
    constructor(options: {
2123
        test: Expression;
2124
        questionMark?: Token;
2125
        consequent?: Expression;
2126
        colon?: Token;
2127
        alternate?: Expression;
2128
    }) {
2129
        super();
97✔
2130
        this.tokens = {
97✔
2131
            questionMark: options.questionMark,
2132
            colon: options.colon
2133
        };
2134
        this.test = options.test;
97✔
2135
        this.consequent = options.consequent;
97✔
2136
        this.alternate = options.alternate;
97✔
2137
        this.location = util.createBoundingLocation(
97✔
2138
            this.test,
2139
            this.tokens.questionMark,
2140
            this.consequent,
2141
            this.tokens.colon,
2142
            this.alternate
2143
        );
2144
    }
2145

2146
    public readonly kind = AstNodeKind.TernaryExpression;
97✔
2147

2148
    public readonly location: Location | undefined;
2149

2150
    public readonly tokens: {
2151
        readonly questionMark?: Token;
2152
        readonly colon?: Token;
2153
    };
2154

2155
    public readonly test: Expression;
2156
    public readonly consequent?: Expression;
2157
    public readonly alternate?: Expression;
2158

2159
    transpile(state: BrsTranspileState) {
2160
        let result = [] as TranspileResult;
19✔
2161
        const file = state.file;
19✔
2162
        let consequentInfo = util.getExpressionInfo(this.consequent!, file);
19✔
2163
        let alternateInfo = util.getExpressionInfo(this.alternate!, file);
19✔
2164

2165
        //get all unique variable names used in the consequent and alternate, and sort them alphabetically so the output is consistent
2166
        let allUniqueVarNames = [...new Set([...consequentInfo.uniqueVarNames, ...alternateInfo.uniqueVarNames])].sort();
19✔
2167
        let mutatingExpressions = [
19✔
2168
            ...consequentInfo.expressions,
2169
            ...alternateInfo.expressions
2170
        ].filter(e => e instanceof CallExpression || e instanceof CallfuncExpression || e instanceof DottedGetExpression);
94✔
2171

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

2219
    public walk(visitor: WalkVisitor, options: WalkOptions) {
2220
        if (options.walkMode & InternalWalkMode.walkExpressions) {
373!
2221
            walk(this, 'test', visitor, options);
373✔
2222
            walk(this, 'consequent', visitor, options);
373✔
2223
            walk(this, 'alternate', visitor, options);
373✔
2224
        }
2225
    }
2226

2227
    get leadingTrivia(): Token[] {
2228
        return this.test.leadingTrivia;
228✔
2229
    }
2230

2231
    public clone() {
2232
        return this.finalizeClone(
2✔
2233
            new TernaryExpression({
2234
                test: this.test?.clone(),
6✔
2235
                questionMark: util.cloneToken(this.tokens.questionMark),
2236
                consequent: this.consequent?.clone(),
6✔
2237
                colon: util.cloneToken(this.tokens.colon),
2238
                alternate: this.alternate?.clone()
6✔
2239
            }),
2240
            ['test', 'consequent', 'alternate']
2241
        );
2242
    }
2243
}
2244

2245
export class NullCoalescingExpression extends Expression {
1✔
2246
    constructor(options: {
2247
        consequent: Expression;
2248
        questionQuestion?: Token;
2249
        alternate: Expression;
2250
    }) {
2251
        super();
36✔
2252
        this.tokens = {
36✔
2253
            questionQuestion: options.questionQuestion
2254
        };
2255
        this.consequent = options.consequent;
36✔
2256
        this.alternate = options.alternate;
36✔
2257
        this.location = util.createBoundingLocation(
36✔
2258
            this.consequent,
2259
            this.tokens.questionQuestion,
2260
            this.alternate
2261
        );
2262
    }
2263

2264
    public readonly kind = AstNodeKind.NullCoalescingExpression;
36✔
2265

2266
    public readonly location: Location | undefined;
2267

2268
    public readonly tokens: {
2269
        readonly questionQuestion?: Token;
2270
    };
2271

2272
    public readonly consequent: Expression;
2273
    public readonly alternate: Expression;
2274

2275
    transpile(state: BrsTranspileState) {
2276
        let result = [] as TranspileResult;
10✔
2277
        let consequentInfo = util.getExpressionInfo(this.consequent, state.file);
10✔
2278
        let alternateInfo = util.getExpressionInfo(this.alternate, state.file);
10✔
2279

2280
        //get all unique variable names used in the consequent and alternate, and sort them alphabetically so the output is consistent
2281
        let allUniqueVarNames = [...new Set([...consequentInfo.uniqueVarNames, ...alternateInfo.uniqueVarNames])].sort();
10✔
2282
        let hasMutatingExpression = [
10✔
2283
            ...consequentInfo.expressions,
2284
            ...alternateInfo.expressions
2285
        ].find(e => isCallExpression(e) || isCallfuncExpression(e) || isDottedGetExpression(e));
28✔
2286

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

2335
    public walk(visitor: WalkVisitor, options: WalkOptions) {
2336
        if (options.walkMode & InternalWalkMode.walkExpressions) {
92!
2337
            walk(this, 'consequent', visitor, options);
92✔
2338
            walk(this, 'alternate', visitor, options);
92✔
2339
        }
2340
    }
2341

2342
    get leadingTrivia(): Token[] {
2343
        return this.consequent.leadingTrivia;
50✔
2344
    }
2345

2346
    public clone() {
2347
        return this.finalizeClone(
2✔
2348
            new NullCoalescingExpression({
2349
                consequent: this.consequent?.clone(),
6✔
2350
                questionQuestion: util.cloneToken(this.tokens.questionQuestion),
2351
                alternate: this.alternate?.clone()
6✔
2352
            }),
2353
            ['consequent', 'alternate']
2354
        );
2355
    }
2356
}
2357

2358
export class RegexLiteralExpression extends Expression {
1✔
2359
    constructor(options: {
2360
        regexLiteral: Token;
2361
    }) {
2362
        super();
46✔
2363
        this.tokens = {
46✔
2364
            regexLiteral: options.regexLiteral
2365
        };
2366
    }
2367

2368
    public readonly kind = AstNodeKind.RegexLiteralExpression;
46✔
2369
    public readonly tokens: {
2370
        readonly regexLiteral: Token;
2371
    };
2372

2373
    public get location(): Location {
2374
        return this.tokens?.regexLiteral?.location;
150!
2375
    }
2376

2377
    public transpile(state: BrsTranspileState): TranspileResult {
2378
        let text = this.tokens.regexLiteral?.text ?? '';
42!
2379
        let flags = '';
42✔
2380
        //get any flags from the end
2381
        const flagMatch = /\/([a-z]+)$/i.exec(text);
42✔
2382
        if (flagMatch) {
42✔
2383
            text = text.substring(0, flagMatch.index + 1);
2✔
2384
            flags = flagMatch[1];
2✔
2385
        }
2386
        let pattern = text
42✔
2387
            //remove leading and trailing slashes
2388
            .substring(1, text.length - 1)
2389
            //escape quotemarks
2390
            .split('"').join('" + chr(34) + "');
2391

2392
        return [
42✔
2393
            state.sourceNode(this.tokens.regexLiteral, [
2394
                'CreateObject("roRegex", ',
2395
                `"${pattern}", `,
2396
                `"${flags}"`,
2397
                ')'
2398
            ])
2399
        ];
2400
    }
2401

2402
    walk(visitor: WalkVisitor, options: WalkOptions) {
2403
        //nothing to walk
2404
    }
2405

2406
    public clone() {
2407
        return this.finalizeClone(
1✔
2408
            new RegexLiteralExpression({
2409
                regexLiteral: util.cloneToken(this.tokens.regexLiteral)
2410
            })
2411
        );
2412
    }
2413

2414
    get leadingTrivia(): Token[] {
2415
        return this.tokens.regexLiteral?.leadingTrivia;
1,023!
2416
    }
2417
}
2418

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

2422
function expressionToValue(expr: Expression, strict: boolean): ExpressionValue {
2423
    if (!expr) {
30!
2424
        return null;
×
2425
    }
2426
    if (isUnaryExpression(expr) && isLiteralNumber(expr.right)) {
30✔
2427
        return numberExpressionToValue(expr.right, expr.tokens.operator.text);
1✔
2428
    }
2429
    if (isLiteralString(expr)) {
29✔
2430
        //remove leading and trailing quotes
2431
        return expr.tokens.value.text.replace(/^"/, '').replace(/"$/, '');
5✔
2432
    }
2433
    if (isLiteralNumber(expr)) {
24✔
2434
        return numberExpressionToValue(expr);
11✔
2435
    }
2436

2437
    if (isLiteralBoolean(expr)) {
13✔
2438
        return expr.tokens.value.text.toLowerCase() === 'true';
3✔
2439
    }
2440
    if (isArrayLiteralExpression(expr)) {
10✔
2441
        return expr.elements
3✔
2442
            .map(e => expressionToValue(e, strict));
7✔
2443
    }
2444
    if (isAALiteralExpression(expr)) {
7✔
2445
        return expr.elements.reduce((acc, e) => {
3✔
2446
            acc[e.tokens.key.text] = expressionToValue(e.value, strict);
3✔
2447
            return acc;
3✔
2448
        }, {});
2449
    }
2450
    //for annotations, we only support serializing pure string values
2451
    if (isTemplateStringExpression(expr)) {
4✔
2452
        if (expr.quasis?.length === 1 && expr.expressions.length === 0) {
2!
2453
            return expr.quasis[0].expressions.map(x => x.tokens.value.text).join('');
10✔
2454
        }
2455
    }
2456
    return strict ? null : expr;
2✔
2457
}
2458

2459
function numberExpressionToValue(expr: LiteralExpression, operator = '') {
11✔
2460
    if (isIntegerType(expr.getType()) || isLongIntegerType(expr.getType())) {
12!
2461
        return parseInt(operator + expr.tokens.value.text);
12✔
2462
    } else {
NEW
2463
        return parseFloat(operator + expr.tokens.value.text);
×
2464
    }
2465
}
2466

2467
export class TypeExpression extends Expression implements TypedefProvider {
1✔
2468
    constructor(options: {
2469
        /**
2470
         * The standard AST expression that represents the type for this TypeExpression.
2471
         */
2472
        expression: Expression;
2473
    }) {
2474
        super();
1,479✔
2475
        this.expression = options.expression;
1,479✔
2476
        this.location = util.cloneLocation(this.expression?.location);
1,479!
2477
    }
2478

2479
    public readonly kind = AstNodeKind.TypeExpression;
1,479✔
2480

2481
    /**
2482
     * The standard AST expression that represents the type for this TypeExpression.
2483
     */
2484
    public readonly expression: Expression;
2485

2486
    public readonly location: Location;
2487

2488
    public transpile(state: BrsTranspileState): TranspileResult {
2489
        return [this.getType({ flags: SymbolTypeFlag.typetime }).toTypeString()];
106✔
2490
    }
2491
    public walk(visitor: WalkVisitor, options: WalkOptions) {
2492
        if (options.walkMode & InternalWalkMode.walkExpressions) {
6,933✔
2493
            walk(this, 'expression', visitor, options);
6,808✔
2494
        }
2495
    }
2496

2497
    public getType(options: GetTypeOptions): BscType {
2498
        return this.expression.getType({ ...options, flags: SymbolTypeFlag.typetime });
5,289✔
2499
    }
2500

2501
    getTypedef(state: TranspileState): TranspileResult {
2502
        // TypeDefs should pass through any valid type names
2503
        return this.expression.transpile(state as BrsTranspileState);
33✔
2504
    }
2505

2506
    getName(parseMode = ParseMode.BrighterScript): string {
195✔
2507
        //TODO: this may not support Complex Types, eg. generics or Unions
2508
        return util.getAllDottedGetPartsAsString(this.expression, parseMode);
195✔
2509
    }
2510

2511
    getNameParts(): string[] {
2512
        //TODO: really, this code is only used to get Namespaces. It could be more clear.
2513
        return util.getAllDottedGetParts(this.expression).map(x => x.text);
20✔
2514
    }
2515

2516
    public clone() {
2517
        return this.finalizeClone(
15✔
2518
            new TypeExpression({
2519
                expression: this.expression?.clone()
45!
2520
            }),
2521
            ['expression']
2522
        );
2523
    }
2524
}
2525

2526
export class TypecastExpression extends Expression {
1✔
2527
    constructor(options: {
2528
        obj: Expression;
2529
        as?: Token;
2530
        typeExpression?: TypeExpression;
2531
    }) {
2532
        super();
68✔
2533
        this.tokens = {
68✔
2534
            as: options.as
2535
        };
2536
        this.obj = options.obj;
68✔
2537
        this.typeExpression = options.typeExpression;
68✔
2538
        this.location = util.createBoundingLocation(
68✔
2539
            this.obj,
2540
            this.tokens.as,
2541
            this.typeExpression
2542
        );
2543
    }
2544

2545
    public readonly kind = AstNodeKind.TypecastExpression;
68✔
2546

2547
    public readonly obj: Expression;
2548

2549
    public readonly tokens: {
2550
        readonly as?: Token;
2551
    };
2552

2553
    public typeExpression?: TypeExpression;
2554

2555
    public readonly location: Location;
2556

2557
    public transpile(state: BrsTranspileState): TranspileResult {
2558
        return this.obj.transpile(state);
13✔
2559
    }
2560
    public walk(visitor: WalkVisitor, options: WalkOptions) {
2561
        if (options.walkMode & InternalWalkMode.walkExpressions) {
330!
2562
            walk(this, 'obj', visitor, options);
330✔
2563
            walk(this, 'typeExpression', visitor, options);
330✔
2564
        }
2565
    }
2566

2567
    public getType(options: GetTypeOptions): BscType {
2568
        const result = this.typeExpression.getType(options);
80✔
2569
        if (options.typeChain) {
80✔
2570
            // modify last typechain entry to show it is a typecast
2571
            const lastEntry = options.typeChain[options.typeChain.length - 1];
18✔
2572
            if (lastEntry) {
18!
2573
                lastEntry.astNode = this;
18✔
2574
            }
2575
        }
2576
        return result;
80✔
2577
    }
2578

2579
    public clone() {
2580
        return this.finalizeClone(
3✔
2581
            new TypecastExpression({
2582
                obj: this.obj?.clone(),
9✔
2583
                as: util.cloneToken(this.tokens.as),
2584
                typeExpression: this.typeExpression?.clone()
9!
2585
            }),
2586
            ['obj', 'typeExpression']
2587
        );
2588
    }
2589
}
2590

2591
export class TypedArrayExpression extends Expression {
1✔
2592
    constructor(options: {
2593
        innerType: Expression;
2594
        leftBracket?: Token;
2595
        rightBracket?: Token;
2596
    }) {
2597
        super();
29✔
2598
        this.tokens = {
29✔
2599
            leftBracket: options.leftBracket,
2600
            rightBracket: options.rightBracket
2601
        };
2602
        this.innerType = options.innerType;
29✔
2603
        this.location = util.createBoundingLocation(
29✔
2604
            this.innerType,
2605
            this.tokens.leftBracket,
2606
            this.tokens.rightBracket
2607
        );
2608
    }
2609

2610
    public readonly tokens: {
2611
        readonly leftBracket?: Token;
2612
        readonly rightBracket?: Token;
2613
    };
2614

2615
    public readonly innerType: Expression;
2616

2617
    public readonly kind = AstNodeKind.TypedArrayExpression;
29✔
2618

2619
    public readonly location: Location;
2620

2621
    public transpile(state: BrsTranspileState): TranspileResult {
NEW
2622
        return [this.getType({ flags: SymbolTypeFlag.typetime }).toTypeString()];
×
2623
    }
2624

2625
    public walk(visitor: WalkVisitor, options: WalkOptions) {
2626
        if (options.walkMode & InternalWalkMode.walkExpressions) {
129!
2627
            walk(this, 'innerType', visitor, options);
129✔
2628
        }
2629
    }
2630

2631
    public getType(options: GetTypeOptions): BscType {
2632
        return new ArrayType(this.innerType.getType(options));
122✔
2633
    }
2634

2635
    public clone() {
NEW
2636
        return this.finalizeClone(
×
2637
            new TypedArrayExpression({
2638
                innerType: this.innerType?.clone(),
×
2639
                leftBracket: util.cloneToken(this.tokens.leftBracket),
2640
                rightBracket: util.cloneToken(this.tokens.rightBracket)
2641
            }),
2642
            ['innerType']
2643
        );
2644
    }
2645
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc