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

rokucommunity / brighterscript / #15219

22 Feb 2026 02:27AM UTC coverage: 87.193% (-0.006%) from 87.199%
#15219

push

web-flow
Merge d0c9a16a7 into 1556715dd

14749 of 17875 branches covered (82.51%)

Branch coverage included in aggregate %.

107 of 117 new or added lines in 19 files covered. (91.45%)

161 existing lines in 16 files now uncovered.

15493 of 16809 relevant lines covered (92.17%)

25604.58 hits per line

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

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

40
export type ExpressionVisitor = (expression: Expression, parent: Expression) => void;
41

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

60
    public readonly left: Expression;
61
    public readonly right: Expression;
62

63
    public readonly kind = AstNodeKind.BinaryExpression;
3,763✔
64

65
    public readonly location: Location | undefined;
66

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

77
    walk(visitor: WalkVisitor, options: WalkOptions) {
78
        if (options.walkMode & InternalWalkMode.walkExpressions) {
16,136!
79
            walk(this, 'left', visitor, options);
16,136✔
80
            walk(this, 'right', visitor, options);
16,136✔
81
        }
82
    }
83

84

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

105
    get leadingTrivia(): Token[] {
106
        return this.left.leadingTrivia;
2,546✔
107
    }
108

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

121

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

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

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

157
    public readonly kind = AstNodeKind.CallExpression;
2,862✔
158

159
    public readonly location: Location | undefined;
160

161
    transpile(state: BrsTranspileState, nameOverride?: string) {
162
        let result: TranspileResult = [];
1,799✔
163

164
        //transpile the name
165
        if (nameOverride) {
1,799✔
166
            result.push(
12✔
167
                //transpile leading comments since we're bypassing callee.transpile (which would normally do this)
168
                ...state.transpileLeadingCommentsForAstNode(this),
169
                state.sourceNode(this.callee, nameOverride)
170
            );
171
        } else {
172
            result.push(...this.callee.transpile(state));
1,787✔
173
        }
174

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

192
    walk(visitor: WalkVisitor, options: WalkOptions) {
193
        if (options.walkMode & InternalWalkMode.walkExpressions) {
13,825!
194
            walk(this, 'callee', visitor, options);
13,825✔
195
            walkArray(this.args, visitor, options, this);
13,825✔
196
        }
197
    }
198

199
    getType(options: GetTypeOptions) {
200
        let calleeType = this.callee.getType(options);
1,121✔
201
        while (isTypeStatementType(calleeType)) {
1,121✔
202
            calleeType = calleeType.wrappedType;
4✔
203
        }
204
        if (options.ignoreCall) {
1,121!
UNCOV
205
            return calleeType;
×
206
        }
207
        if (isNewExpression(this.parent)) {
1,121✔
208
            return calleeType;
348✔
209
        }
210

211
        const specialCaseReturnType = util.getSpecialCaseCallExpressionReturnType(this, options);
773✔
212
        if (specialCaseReturnType) {
773✔
213
            return specialCaseReturnType;
133✔
214
        }
215
        if (isCallableType(calleeType) && (!isReferenceType(calleeType.returnType) || calleeType.returnType?.isResolvable())) {
640!
216
            if (isVoidType(calleeType.returnType)) {
589✔
217
                if (options.data?.isBuiltIn) {
10✔
218
                    // built in functions that return `as void` will not initialize the result
219
                    return UninitializedType.instance;
3✔
220
                }
221
                // non-built in functions with return type`as void` actually return `invalid`
222
                return InvalidType.instance;
7✔
223
            }
224
            return calleeType.returnType;
579✔
225
        }
226
        if (util.isUnionOfFunctions(calleeType, true)) {
51✔
227
            return util.getReturnTypeOfUnionOfFunctions(calleeType);
1✔
228
        }
229
        if (!isReferenceType(calleeType) && (calleeType as BaseFunctionType)?.returnType?.isResolvable()) {
50!
UNCOV
230
            return (calleeType as BaseFunctionType).returnType;
×
231
        }
232
        return new TypePropertyReferenceType(calleeType, 'returnType');
50✔
233
    }
234

235
    get leadingTrivia(): Token[] {
236
        return this.callee.leadingTrivia;
10,332✔
237
    }
238

239
    public clone() {
240
        return this.finalizeClone(
13✔
241
            new CallExpression({
242
                callee: this.callee?.clone(),
39✔
243
                openingParen: util.cloneToken(this.tokens.openingParen),
244
                closingParen: util.cloneToken(this.tokens.closingParen),
245
                args: this.args?.map(e => e?.clone())
12✔
246
            }),
247
            ['callee', 'args']
248
        );
249
    }
250
}
251

252
export class FunctionExpression extends Expression implements TypedefProvider {
1✔
253
    constructor(options: {
254
        functionType?: Token;
255
        leftParen?: Token;
256
        parameters?: FunctionParameterExpression[];
257
        rightParen?: Token;
258
        as?: Token;
259
        returnTypeExpression?: TypeExpression;
260
        body: Block;
261
        endFunctionType?: Token;
262
    }) {
263
        super();
4,515✔
264
        this.tokens = {
4,515✔
265
            functionType: options.functionType,
266
            leftParen: options.leftParen,
267
            rightParen: options.rightParen,
268
            as: options.as,
269
            endFunctionType: options.endFunctionType
270
        };
271
        this.parameters = options.parameters ?? [];
4,515✔
272
        this.body = options.body;
4,515✔
273
        this.returnTypeExpression = options.returnTypeExpression;
4,515✔
274

275
        if (this.body) {
4,515✔
276
            this.body.parent = this;
4,514✔
277
        }
278
        this.symbolTable = new SymbolTable('FunctionExpression', () => this.parent?.getSymbolTable());
54,354!
279
    }
280

281
    public readonly kind = AstNodeKind.FunctionExpression;
4,515✔
282

283
    readonly parameters: FunctionParameterExpression[];
284
    public readonly body: Block;
285
    public readonly returnTypeExpression?: TypeExpression;
286

287
    readonly tokens: {
288
        readonly functionType?: Token;
289
        readonly endFunctionType?: Token;
290
        readonly leftParen?: Token;
291
        readonly rightParen?: Token;
292
        readonly as?: Token;
293
    };
294

295
    public get leadingTrivia(): Token[] {
296
        return this.tokens.functionType?.leadingTrivia;
34,691✔
297
    }
298

299
    public get endTrivia(): Token[] {
300
        return this.tokens.endFunctionType?.leadingTrivia;
20!
301
    }
302

303
    /**
304
     * The range of the function, starting at the 'f' in function or 's' in sub (or the open paren if the keyword is missing),
305
     * and ending with the last n' in 'end function' or 'b' in 'end sub'
306
     */
307
    public get location(): Location {
308
        return util.createBoundingLocation(
11,749✔
309
            this.tokens.functionType,
310
            this.tokens.leftParen,
311
            ...this.parameters ?? [],
35,247!
312
            this.tokens.rightParen,
313
            this.tokens.as,
314
            this.returnTypeExpression,
315
            this.tokens.endFunctionType
316
        );
317
    }
318

319
    transpile(state: BrsTranspileState, name?: Identifier, includeBody = true) {
1,615✔
320
        let results = [] as TranspileResult;
1,615✔
321
        //'function'|'sub'
322
        results.push(
1,615✔
323
            state.transpileToken(this.tokens.functionType, 'function', false, state.skipLeadingComments)
324
        );
325
        //functionName?
326
        if (name) {
1,615✔
327
            results.push(
1,609✔
328
                ' ',
329
                state.transpileToken(name)
330
            );
331
        }
332
        //leftParen
333
        results.push(
1,615✔
334
            state.transpileToken(this.tokens.leftParen, '(')
335
        );
336
        //parameters
337
        for (let i = 0; i < this.parameters.length; i++) {
1,615✔
338
            let param = this.parameters[i];
2,434✔
339
            //add commas
340
            if (i > 0) {
2,434✔
341
                results.push(', ');
1,194✔
342
            }
343
            //add parameter
344
            results.push(param.transpile(state));
2,434✔
345
        }
346
        //right paren
347
        results.push(
1,615✔
348
            state.transpileToken(this.tokens.rightParen, ')')
349
        );
350
        //as [Type]
351

352
        if (this.tokens.as && this.returnTypeExpression && (this.requiresReturnType || !state.options.removeParameterTypes)) {
1,615✔
353
            results.push(
51✔
354
                ' ',
355
                //as
356
                state.transpileToken(this.tokens.as, 'as'),
357
                ' ',
358
                //return type
359
                ...this.returnTypeExpression.transpile(state)
360
            );
361
        }
362
        let hasBody = false;
1,615✔
363
        if (includeBody) {
1,615!
364
            state.lineage.unshift(this);
1,615✔
365
            let body = this.body.transpile(state);
1,615✔
366
            hasBody = body.length > 0;
1,615✔
367
            state.lineage.shift();
1,615✔
368
            results.push(...body);
1,615✔
369
        }
370

371
        const lastLocatable = hasBody ? this.body : this.returnTypeExpression ?? this.tokens.leftParen ?? this.tokens.functionType;
1,615!
372
        results.push(
1,615✔
373
            ...state.transpileEndBlockToken(lastLocatable, this.tokens.endFunctionType, `end ${this.tokens.functionType ?? 'function'}`)
4,845✔
374
        );
375
        return results;
1,615✔
376
    }
377

378
    getTypedef(state: BrsTranspileState) {
379
        let results = [
68✔
380
            new SourceNode(1, 0, null, [
381
                //'function'|'sub'
382
                this.tokens.functionType?.text ?? 'function',
408!
383
                //functionName?
384
                ...(isFunctionStatement(this.parent) || isMethodStatement(this.parent) ? [' ', this.parent.tokens.name?.text ?? ''] : []),
567!
385
                //leftParen
386
                '(',
387
                //parameters
388
                ...(
389
                    this.parameters?.map((param, i) => ([
82!
390
                        //separating comma
391
                        i > 0 ? ', ' : '',
82✔
392
                        ...param.getTypedef(state)
393
                    ])) ?? []
68!
394
                ) as any,
395
                //right paren
396
                ')',
397
                //as <ReturnType>
398
                ...(this.returnTypeExpression ? [
68✔
399
                    ' ',
400
                    this.tokens.as?.text ?? 'as',
18!
401
                    ' ',
402
                    ...this.returnTypeExpression.getTypedef(state)
403
                ] : []),
404
                '\n',
405
                state.indent(),
406
                //'end sub'|'end function'
407
                this.tokens.endFunctionType?.text ?? `end ${this.tokens.functionType ?? 'function'}`
408!
408
            ])
409
        ];
410
        return results;
68✔
411
    }
412

413
    walk(visitor: WalkVisitor, options: WalkOptions) {
414
        if (options.walkMode & InternalWalkMode.walkExpressions) {
21,636!
415
            walkArray(this.parameters, visitor, options, this);
21,636✔
416
            walk(this, 'returnTypeExpression', visitor, options);
21,636✔
417
            //This is the core of full-program walking...it allows us to step into sub functions
418
            if (options.walkMode & InternalWalkMode.recurseChildFunctions) {
21,636✔
419
                walk(this, 'body', visitor, options);
21,630✔
420
            }
421
        }
422
    }
423

424
    public getType(options: GetTypeOptions): TypedFunctionType {
425
        //if there's a defined return type, use that
426
        let returnType: BscType;
427

428
        const docs = brsDocParser.parseNode(this.findAncestor(isFunctionStatement));
5,237✔
429

430
        returnType = util.chooseTypeFromCodeOrDocComment(
5,237✔
431
            this.returnTypeExpression?.getType({ ...options, typeChain: undefined }),
15,711✔
UNCOV
432
            docs.getReturnBscType({ ...options, tableProvider: () => this.getSymbolTable() }),
×
433
            options
434
        );
435

436
        const isSub = this.tokens.functionType?.kind === TokenKind.Sub;
5,237✔
437
        //if we don't have a return type and this is a sub, set the return type to `void`. else use `dynamic`
438
        if (!returnType) {
5,237✔
439
            returnType = isSub ? VoidType.instance : DynamicType.instance;
4,157✔
440
        }
441

442
        const resultType = new TypedFunctionType(returnType);
5,237✔
443
        resultType.isSub = isSub;
5,237✔
444
        for (let param of this.parameters) {
5,237✔
445
            resultType.addParameter(param.tokens.name.text, param.getType({ ...options, typeChain: undefined }), !!param.defaultValue);
2,760✔
446
        }
447
        // Figure out this function's name if we can
448
        let funcName = '';
5,237✔
449
        if (isMethodStatement(this.parent) || isInterfaceMethodStatement(this.parent)) {
5,237✔
450
            funcName = this.parent.getName(ParseMode.BrighterScript);
361✔
451
            if (options.typeChain) {
361✔
452
                // Get the typechain info from the parent class
453
                this.parent.parent?.getType(options);
1!
454
            }
455
        } else if (isFunctionStatement(this.parent)) {
4,876✔
456
            funcName = this.parent.getName(ParseMode.BrighterScript);
4,749✔
457
        }
458
        if (funcName) {
5,237✔
459
            resultType.setName(funcName);
5,110✔
460
        }
461
        options.typeChain?.push(new TypeChainEntry({ name: funcName, type: resultType, data: options.data, astNode: this }));
5,237✔
462
        return resultType;
5,237✔
463
    }
464

465
    private get requiresReturnType() {
466
        /**
467
         * RokuOS methods can be written several different ways:
468
         * 1. Function() : return withValue
469
         * 2. Function() as type : return withValue
470
         * 3. Function() as void : return
471
         *
472
         * 4. Sub() : return
473
         * 5. Sub () as void : return
474
         * 6. Sub() as type : return withValue
475
         *
476
         * Formats (1), (2), and (6) throw a compile error if there IS NOT a return value in the function body.
477
         * Formats (3), (4), and (5) throw a compile error if there IS a return value in the function body.
478
         *
479
         * 7. Additionally, as a special case, the OS requires that `onKeyEvent()` be defined with `as boolean`
480
         */
481

482

483
        if ((isFunctionStatement(this.parent) || isMethodStatement(this.parent)) && this.parent?.tokens?.name?.text.toLowerCase() === 'onkeyevent') {
53!
484
            // onKeyEvent() requires 'as Boolean' otherwise RokuOS throws errors
485
            return true;
1✔
486
        }
487
        const isSub = this.tokens.functionType?.text.toLowerCase() === 'sub';
52!
488
        const returnType = this.returnTypeExpression?.getType({ flags: SymbolTypeFlag.typetime });
52!
489
        const isVoidReturnType = isVoidType(returnType);
52✔
490

491

492
        if (isSub && !isVoidReturnType) { // format (6)
52✔
493
            return true;
25✔
494
        } else if (isVoidReturnType) { // format (3)
27✔
495
            return true;
4✔
496
        }
497

498
        return false;
23✔
499
    }
500

501
    public clone() {
502
        return this.finalizeClone(
109✔
503
            new FunctionExpression({
504
                parameters: this.parameters?.map(e => e?.clone()),
6✔
505
                body: this.body?.clone(),
327✔
506
                functionType: util.cloneToken(this.tokens.functionType),
507
                endFunctionType: util.cloneToken(this.tokens.endFunctionType),
508
                leftParen: util.cloneToken(this.tokens.leftParen),
509
                rightParen: util.cloneToken(this.tokens.rightParen),
510
                as: util.cloneToken(this.tokens.as),
511
                returnTypeExpression: this.returnTypeExpression?.clone()
327✔
512
            }),
513
            ['body', 'returnTypeExpression']
514
        );
515
    }
516
}
517

518
export class FunctionParameterExpression extends Expression {
1✔
519
    constructor(options: {
520
        name: Identifier;
521
        equals?: Token;
522
        defaultValue?: Expression;
523
        as?: Token;
524
        typeExpression?: TypeExpression;
525
    }) {
526
        super();
3,618✔
527
        this.tokens = {
3,618✔
528
            name: options.name,
529
            equals: options.equals,
530
            as: options.as
531
        };
532
        this.defaultValue = options.defaultValue;
3,618✔
533
        this.typeExpression = options.typeExpression;
3,618✔
534
    }
535

536
    public readonly kind = AstNodeKind.FunctionParameterExpression;
3,618✔
537

538
    readonly tokens: {
539
        readonly name: Identifier;
540
        readonly equals?: Token;
541
        readonly as?: Token;
542
    };
543

544
    public readonly defaultValue?: Expression;
545
    public readonly typeExpression?: TypeExpression;
546

547
    public getType(options: GetTypeOptions) {
548
        const docs = brsDocParser.parseNode(this.findAncestor(isFunctionStatement));
7,093✔
549
        const paramName = this.tokens.name.text;
7,093✔
550

551
        let paramTypeFromCode = this.typeExpression?.getType({ ...options, flags: SymbolTypeFlag.typetime, typeChain: undefined }) ??
7,093✔
552
            util.getDefaultTypeFromValueType(this.defaultValue?.getType({ ...options, flags: SymbolTypeFlag.runtime, typeChain: undefined }));
9,093✔
553
        if (isInvalidType(paramTypeFromCode) || isVoidType(paramTypeFromCode)) {
7,093✔
554
            paramTypeFromCode = undefined;
26✔
555
        }
556
        const paramTypeFromDoc = docs.getParamBscType(paramName, { ...options, fullName: paramName, typeChain: undefined, tableProvider: () => this.getSymbolTable() });
7,093✔
557
        const paramType = util.chooseTypeFromCodeOrDocComment(paramTypeFromCode, paramTypeFromDoc, options) ?? DynamicType.instance;
7,093✔
558

559
        const docDescription = docs.getParam(paramName)?.description;
7,093✔
560
        if (docDescription) {
7,093✔
561
            options.data = options.data ?? {};
26✔
562
            options.data.description = docDescription;
26✔
563
        }
564
        options.typeChain?.push(new TypeChainEntry({ name: paramName, type: paramType, data: options.data, astNode: this }));
7,093✔
565
        return paramType;
7,093✔
566
    }
567

568
    public get location(): Location | undefined {
569
        return util.createBoundingLocation(
10,472✔
570
            this.tokens.name,
571
            this.tokens.as,
572
            this.typeExpression,
573
            this.tokens.equals,
574
            this.defaultValue
575
        );
576
    }
577

578
    public transpile(state: BrsTranspileState) {
579
        let result: TranspileResult = [
2,451✔
580
            //name
581
            state.transpileToken(this.tokens.name)
582
        ];
583
        //default value
584
        if (this.defaultValue) {
2,451✔
585
            result.push(' = ');
12✔
586
            result.push(this.defaultValue.transpile(state));
12✔
587
        }
588
        //type declaration
589
        if (this.typeExpression && !state.options.removeParameterTypes) {
2,451✔
590
            result.push(' ');
73✔
591
            result.push(state.transpileToken(this.tokens.as, 'as'));
73✔
592
            result.push(' ');
73✔
593
            result.push(
73✔
594
                ...(this.typeExpression?.transpile(state) ?? [])
438!
595
            );
596
        }
597

598
        return result;
2,451✔
599
    }
600

601
    public getTypedef(state: BrsTranspileState): TranspileResult {
602
        const results = [this.tokens.name.text] as TranspileResult;
82✔
603

604
        if (this.defaultValue) {
82✔
605
            results.push(' = ', ...(this.defaultValue.getTypedef(state) ?? this.defaultValue.transpile(state)));
3!
606
        }
607

608
        if (this.tokens.as) {
82✔
609
            results.push(' as ');
6✔
610

611
            // TODO: Is this conditional needed? Will typeToken always exist
612
            // so long as `asToken` exists?
613
            if (this.typeExpression) {
6!
614
                results.push(...(this.typeExpression?.getTypedef(state) ?? ['']));
6!
615
            }
616
        }
617

618
        return results;
82✔
619
    }
620

621
    walk(visitor: WalkVisitor, options: WalkOptions) {
622
        // eslint-disable-next-line no-bitwise
623
        if (options.walkMode & InternalWalkMode.walkExpressions) {
17,560!
624
            walk(this, 'defaultValue', visitor, options);
17,560✔
625
            walk(this, 'typeExpression', visitor, options);
17,560✔
626
        }
627
    }
628

629
    get leadingTrivia(): Token[] {
630
        return this.tokens.name.leadingTrivia;
5,891✔
631
    }
632

633
    public clone() {
634
        return this.finalizeClone(
13✔
635
            new FunctionParameterExpression({
636
                name: util.cloneToken(this.tokens.name),
637
                as: util.cloneToken(this.tokens.as),
638
                typeExpression: this.typeExpression?.clone(),
39✔
639
                equals: util.cloneToken(this.tokens.equals),
640
                defaultValue: this.defaultValue?.clone()
39✔
641
            }),
642
            ['typeExpression', 'defaultValue']
643
        );
644
    }
645
}
646

647
export class DottedGetExpression extends Expression {
1✔
648
    constructor(options: {
649
        obj: Expression;
650
        name: Identifier;
651
        /**
652
         * Can either be `.`, or `?.` for optional chaining - defaults in transpile to '.'
653
         */
654
        dot?: Token;
655
    }) {
656
        super();
3,163✔
657
        this.tokens = {
3,163✔
658
            name: options.name,
659
            dot: options.dot
660
        };
661
        this.obj = options.obj;
3,163✔
662

663
        this.location = util.createBoundingLocation(this.obj, this.tokens.dot, this.tokens.name);
3,163✔
664
    }
665

666
    readonly tokens: {
667
        readonly name: Identifier;
668
        readonly dot?: Token;
669
    };
670
    readonly obj: Expression;
671

672
    public readonly kind = AstNodeKind.DottedGetExpression;
3,163✔
673

674
    public readonly location: Location | undefined;
675

676
    transpile(state: BrsTranspileState) {
677
        //if the callee starts with a namespace name, transpile the name
678
        if (state.file.calleeStartsWithNamespace(this)) {
1,005✔
679
            return [
11✔
680
                ...state.transpileLeadingCommentsForAstNode(this),
681
                state.sourceNode(this, this.getName(ParseMode.BrightScript))
682
            ];
683
        } else {
684
            return [
994✔
685
                ...this.obj.transpile(state),
686
                state.transpileToken(this.tokens.dot, '.'),
687
                state.transpileToken(this.tokens.name)
688
            ];
689
        }
690
    }
691

692
    getTypedef(state: BrsTranspileState) {
693
        //always transpile the dots for typedefs
694
        return [
2✔
695
            ...this.obj.transpile(state),
696
            state.transpileToken(this.tokens.dot),
697
            state.transpileToken(this.tokens.name)
698
        ];
699
    }
700

701
    walk(visitor: WalkVisitor, options: WalkOptions) {
702
        if (options.walkMode & InternalWalkMode.walkExpressions) {
13,359!
703
            walk(this, 'obj', visitor, options);
13,359✔
704
        }
705
    }
706

707
    getType(options: GetTypeOptions) {
708
        const objType = this.obj?.getType(options);
7,499!
709
        let result = objType?.getMemberType(this.tokens.name?.text, options);
7,499!
710

711
        if (util.isClassUsedAsFunction(result, this, options)) {
7,499✔
712
            // treat this class constructor as a function
713
            result = FunctionType.instance;
11✔
714
        }
715
        options.typeChain?.push(new TypeChainEntry({
7,499✔
716
            name: this.tokens.name?.text,
9,396!
717
            type: result,
718
            data: options.data,
719
            location: this.tokens.name?.location ?? this.location,
18,792!
720
            astNode: this
721
        }));
722
        if (result ||
7,499✔
723
            options.flags & SymbolTypeFlag.typetime ||
724
            (isPrimitiveType(objType) || isCallableType(objType))) {
725
            // All types should be known at typeTime, or the obj is well known
726
            return result;
7,459✔
727
        }
728
        // It is possible at runtime that a value has been added dynamically to an object, or something
729
        // TODO: maybe have a strict flag on this?
730
        return DynamicType.instance;
40✔
731
    }
732

733
    getName(parseMode: ParseMode) {
734
        return util.getAllDottedGetPartsAsString(this, parseMode);
37✔
735
    }
736

737
    get leadingTrivia(): Token[] {
738
        return this.obj.leadingTrivia;
16,841✔
739
    }
740

741
    public clone() {
742
        return this.finalizeClone(
7✔
743
            new DottedGetExpression({
744
                obj: this.obj?.clone(),
21✔
745
                dot: util.cloneToken(this.tokens.dot),
746
                name: util.cloneToken(this.tokens.name)
747
            }),
748
            ['obj']
749
        );
750
    }
751
}
752

753
export class XmlAttributeGetExpression extends Expression {
1✔
754
    constructor(options: {
755
        obj: Expression;
756
        /**
757
         * Can either be `@`, or `?@` for optional chaining - defaults to '@'
758
         */
759
        at?: Token;
760
        name: Identifier;
761
    }) {
762
        super();
14✔
763
        this.obj = options.obj;
14✔
764
        this.tokens = { at: options.at, name: options.name };
14✔
765
        this.location = util.createBoundingLocation(this.obj, this.tokens.at, this.tokens.name);
14✔
766
    }
767

768
    public readonly kind = AstNodeKind.XmlAttributeGetExpression;
14✔
769

770
    public readonly tokens: {
771
        name: Identifier;
772
        at?: Token;
773
    };
774

775
    public readonly obj: Expression;
776

777
    public readonly location: Location | undefined;
778

779
    transpile(state: BrsTranspileState) {
780
        return [
3✔
781
            ...this.obj.transpile(state),
782
            state.transpileToken(this.tokens.at, '@'),
783
            state.transpileToken(this.tokens.name)
784
        ];
785
    }
786

787
    walk(visitor: WalkVisitor, options: WalkOptions) {
788
        if (options.walkMode & InternalWalkMode.walkExpressions) {
32!
789
            walk(this, 'obj', visitor, options);
32✔
790
        }
791
    }
792

793
    get leadingTrivia(): Token[] {
794
        return this.obj.leadingTrivia;
21✔
795
    }
796

797
    public clone() {
798
        return this.finalizeClone(
2✔
799
            new XmlAttributeGetExpression({
800
                obj: this.obj?.clone(),
6✔
801
                at: util.cloneToken(this.tokens.at),
802
                name: util.cloneToken(this.tokens.name)
803
            }),
804
            ['obj']
805
        );
806
    }
807
}
808

809
export class IndexedGetExpression extends Expression {
1✔
810
    constructor(options: {
811
        obj: Expression;
812
        indexes: Expression[];
813
        /**
814
         * Can either be `[` or `?[`. If `?.[` is used, this will be `[` and `optionalChainingToken` will be `?.` - defaults to '[' in transpile
815
         */
816
        openingSquare?: Token;
817
        closingSquare?: Token;
818
        questionDot?: Token;//  ? or ?.
819
    }) {
820
        super();
168✔
821
        this.tokens = {
168✔
822
            openingSquare: options.openingSquare,
823
            closingSquare: options.closingSquare,
824
            questionDot: options.questionDot
825
        };
826
        this.obj = options.obj;
168✔
827
        this.indexes = options.indexes;
168✔
828
        this.location = util.createBoundingLocation(
168✔
829
            this.obj,
830
            this.tokens.openingSquare,
831
            this.tokens.questionDot,
832
            this.tokens.openingSquare,
833
            ...this.indexes ?? [],
504✔
834
            this.tokens.closingSquare
835
        );
836
    }
837

838
    public readonly kind = AstNodeKind.IndexedGetExpression;
168✔
839

840
    public readonly obj: Expression;
841
    public readonly indexes: Expression[];
842

843
    readonly tokens: {
844
        /**
845
         * Can either be `[` or `?[`. If `?.[` is used, this will be `[` and `optionalChainingToken` will be `?.` - defaults to '[' in transpile
846
         */
847
        readonly openingSquare?: Token;
848
        readonly closingSquare?: Token;
849
        readonly questionDot?: Token; //  ? or ?.
850
    };
851

852
    public readonly location: Location | undefined;
853

854
    transpile(state: BrsTranspileState) {
855
        const result = [];
67✔
856
        result.push(
67✔
857
            ...this.obj.transpile(state),
858
            this.tokens.questionDot ? state.transpileToken(this.tokens.questionDot) : '',
67✔
859
            state.transpileToken(this.tokens.openingSquare, '[')
860
        );
861
        for (let i = 0; i < this.indexes.length; i++) {
67✔
862
            //add comma between indexes
863
            if (i > 0) {
75✔
864
                result.push(', ');
8✔
865
            }
866
            let index = this.indexes[i];
75✔
867
            result.push(
75✔
868
                ...(index?.transpile(state) ?? [])
450!
869
            );
870
        }
871
        result.push(
67✔
872
            state.transpileToken(this.tokens.closingSquare, ']')
873
        );
874
        return result;
67✔
875
    }
876

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

884
    getType(options: GetTypeOptions): BscType {
885
        const objType = this.obj.getType(options);
204✔
886
        if (isArrayType(objType)) {
204✔
887
            // This is used on an array. What is the default type of that array?
888
            return objType.defaultType;
10✔
889
        }
890
        return super.getType(options);
194✔
891
    }
892

893
    get leadingTrivia(): Token[] {
894
        return this.obj.leadingTrivia;
1,071✔
895
    }
896

897
    public clone() {
898
        return this.finalizeClone(
6✔
899
            new IndexedGetExpression({
900
                obj: this.obj?.clone(),
18✔
901
                questionDot: util.cloneToken(this.tokens.questionDot),
902
                openingSquare: util.cloneToken(this.tokens.openingSquare),
903
                indexes: this.indexes?.map(x => x?.clone()),
7✔
904
                closingSquare: util.cloneToken(this.tokens.closingSquare)
905
            }),
906
            ['obj', 'indexes']
907
        );
908
    }
909
}
910

911
export class GroupingExpression extends Expression {
1✔
912
    constructor(options: {
913
        leftParen?: Token;
914
        rightParen?: Token;
915
        expression: Expression;
916
    }) {
917
        super();
85✔
918
        this.tokens = {
85✔
919
            rightParen: options.rightParen,
920
            leftParen: options.leftParen
921
        };
922
        this.expression = options.expression;
85✔
923
        this.location = util.createBoundingLocation(this.tokens.leftParen, this.expression, this.tokens.rightParen);
85✔
924
    }
925

926
    public readonly tokens: {
927
        readonly leftParen?: Token;
928
        readonly rightParen?: Token;
929
    };
930
    public readonly expression: Expression;
931

932
    public readonly kind = AstNodeKind.GroupingExpression;
85✔
933

934
    public readonly location: Location | undefined;
935

936
    transpile(state: BrsTranspileState) {
937
        if (isTypecastExpression(this.expression)) {
13✔
938
            return this.expression.transpile(state);
7✔
939
        }
940
        return [
6✔
941
            state.transpileToken(this.tokens.leftParen, '('),
942
            ...this.expression.transpile(state),
943
            state.transpileToken(this.tokens.rightParen, ')')
944
        ];
945
    }
946

947
    walk(visitor: WalkVisitor, options: WalkOptions) {
948
        if (options.walkMode & InternalWalkMode.walkExpressions) {
301!
949
            walk(this, 'expression', visitor, options);
301✔
950
        }
951
    }
952

953
    getType(options: GetTypeOptions) {
954
        return this.expression.getType(options);
135✔
955
    }
956

957
    get leadingTrivia(): Token[] {
958
        return this.tokens.leftParen?.leadingTrivia;
419!
959
    }
960

961
    public clone() {
962
        return this.finalizeClone(
2✔
963
            new GroupingExpression({
964
                leftParen: util.cloneToken(this.tokens.leftParen),
965
                expression: this.expression?.clone(),
6✔
966
                rightParen: util.cloneToken(this.tokens.rightParen)
967
            }),
968
            ['expression']
969
        );
970
    }
971
}
972

973
export class LiteralExpression extends Expression {
1✔
974
    constructor(options: {
975
        value: Token;
976
    }) {
977
        super();
8,682✔
978
        this.tokens = {
8,682✔
979
            value: options.value
980
        };
981
    }
982

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

987
    public readonly kind = AstNodeKind.LiteralExpression;
8,682✔
988

989
    public get location() {
990
        return this.tokens.value.location;
24,583✔
991
    }
992

993
    public getType(options?: GetTypeOptions) {
994
        return util.tokenToBscType(this.tokens.value);
4,056✔
995
    }
996

997
    transpile(state: BrsTranspileState) {
998
        let text: string;
999
        if (this.tokens.value.kind === TokenKind.TemplateStringQuasi) {
5,140✔
1000
            //wrap quasis with quotes (and escape inner quotemarks)
1001
            text = `"${this.tokens.value.text.replace(/"/g, '""')}"`;
35✔
1002

1003
        } else if (this.tokens.value.kind === TokenKind.StringLiteral) {
5,105✔
1004
            text = this.tokens.value.text;
3,425✔
1005
            //add trailing quotemark if it's missing. We will have already generated a diagnostic for this.
1006
            if (text.endsWith('"') === false) {
3,425✔
1007
                text += '"';
1✔
1008
            }
1009
        } else {
1010
            text = this.tokens.value.text;
1,680✔
1011
        }
1012

1013
        return [
5,140✔
1014
            state.transpileToken({ ...this.tokens.value, text: text })
1015
        ];
1016
    }
1017

1018
    walk(visitor: WalkVisitor, options: WalkOptions) {
1019
        //nothing to walk
1020
    }
1021

1022
    get leadingTrivia(): Token[] {
1023
        return this.tokens.value.leadingTrivia;
14,439✔
1024
    }
1025

1026
    public clone() {
1027
        return this.finalizeClone(
109✔
1028
            new LiteralExpression({
1029
                value: util.cloneToken(this.tokens.value)
1030
            })
1031
        );
1032
    }
1033
}
1034

1035
/**
1036
 * The print statement can have a mix of expressions and separators. These separators represent actual output to the screen,
1037
 * so this AstNode represents those separators (comma, semicolon, and whitespace)
1038
 */
1039
export class PrintSeparatorExpression extends Expression {
1✔
1040
    constructor(options: {
1041
        separator: PrintSeparatorToken;
1042
    }) {
1043
        super();
49✔
1044
        this.tokens = {
49✔
1045
            separator: options.separator
1046
        };
1047
        this.location = this.tokens.separator.location;
49✔
1048
    }
1049

1050
    public readonly tokens: {
1051
        readonly separator: PrintSeparatorToken;
1052
    };
1053

1054
    public readonly kind = AstNodeKind.PrintSeparatorExpression;
49✔
1055

1056
    public location: Location;
1057

1058
    transpile(state: BrsTranspileState) {
1059
        return [
26✔
1060
            ...this.tokens.separator.leadingWhitespace ?? [],
78!
1061
            ...state.transpileToken(this.tokens.separator)
1062
        ];
1063
    }
1064

1065
    walk(visitor: WalkVisitor, options: WalkOptions) {
1066
        //nothing to walk
1067
    }
1068

1069
    get leadingTrivia(): Token[] {
1070
        return this.tokens.separator.leadingTrivia;
194✔
1071
    }
1072

1073
    public clone() {
UNCOV
1074
        return new PrintSeparatorExpression({
×
1075
            separator: util.cloneToken(this.tokens?.separator)
×
1076
        });
1077
    }
1078
}
1079

1080

1081
/**
1082
 * This is a special expression only used within template strings. It exists so we can prevent producing lots of empty strings
1083
 * during template string transpile by identifying these expressions explicitly and skipping the bslib_toString around them
1084
 */
1085
export class EscapedCharCodeLiteralExpression extends Expression {
1✔
1086
    constructor(options: {
1087
        value: Token & { charCode: number };
1088
    }) {
1089
        super();
37✔
1090
        this.tokens = { value: options.value };
37✔
1091
        this.location = util.cloneLocation(this.tokens.value.location);
37✔
1092
    }
1093

1094
    public readonly kind = AstNodeKind.EscapedCharCodeLiteralExpression;
37✔
1095

1096
    public readonly tokens: {
1097
        readonly value: Token & { charCode: number };
1098
    };
1099

1100
    public readonly location: Location;
1101

1102
    transpile(state: BrsTranspileState) {
1103
        return [
15✔
1104
            state.sourceNode(this, `chr(${this.tokens.value.charCode})`)
1105
        ];
1106
    }
1107

1108
    walk(visitor: WalkVisitor, options: WalkOptions) {
1109
        //nothing to walk
1110
    }
1111

1112
    public clone() {
1113
        return this.finalizeClone(
3✔
1114
            new EscapedCharCodeLiteralExpression({
1115
                value: util.cloneToken(this.tokens.value)
1116
            })
1117
        );
1118
    }
1119
}
1120

1121
export class ArrayLiteralExpression extends Expression {
1✔
1122
    constructor(options: {
1123
        elements: Array<Expression>;
1124
        open?: Token;
1125
        close?: Token;
1126
    }) {
1127
        super();
179✔
1128
        this.tokens = {
179✔
1129
            open: options.open,
1130
            close: options.close
1131
        };
1132
        this.elements = options.elements;
179✔
1133
        this.location = util.createBoundingLocation(this.tokens.open, ...this.elements ?? [], this.tokens.close);
179✔
1134
    }
1135

1136
    public readonly elements: Array<Expression>;
1137

1138
    public readonly tokens: {
1139
        readonly open?: Token;
1140
        readonly close?: Token;
1141
    };
1142

1143
    public readonly kind = AstNodeKind.ArrayLiteralExpression;
179✔
1144

1145
    public readonly location: Location | undefined;
1146

1147
    transpile(state: BrsTranspileState) {
1148
        let result: TranspileResult = [];
63✔
1149
        result.push(
63✔
1150
            state.transpileToken(this.tokens.open, '[')
1151
        );
1152
        let hasChildren = this.elements.length > 0;
63✔
1153
        state.blockDepth++;
63✔
1154

1155
        for (let i = 0; i < this.elements.length; i++) {
63✔
1156
            let previousElement = this.elements[i - 1];
88✔
1157
            let element = this.elements[i];
88✔
1158

1159
            if (util.isLeadingCommentOnSameLine(previousElement ?? this.tokens.open, element)) {
88✔
1160
                result.push(' ');
3✔
1161
            } else {
1162
                result.push(
85✔
1163
                    '\n',
1164
                    state.indent()
1165
                );
1166
            }
1167
            result.push(
88✔
1168
                ...element.transpile(state)
1169
            );
1170
        }
1171
        state.blockDepth--;
63✔
1172
        //add a newline between open and close if there are elements
1173
        const lastLocatable = this.elements[this.elements.length - 1] ?? this.tokens.open;
63✔
1174
        result.push(...state.transpileEndBlockToken(lastLocatable, this.tokens.close, ']', hasChildren));
63✔
1175

1176
        return result;
63✔
1177
    }
1178

1179
    walk(visitor: WalkVisitor, options: WalkOptions) {
1180
        if (options.walkMode & InternalWalkMode.walkExpressions) {
945!
1181
            walkArray(this.elements, visitor, options, this);
945✔
1182
        }
1183
    }
1184

1185
    getType(options: GetTypeOptions): BscType {
1186
        const innerTypes = this.elements.map(expr => expr.getType(options));
312✔
1187
        return new ArrayType(...innerTypes);
214✔
1188
    }
1189
    get leadingTrivia(): Token[] {
1190
        return this.tokens.open?.leadingTrivia;
627!
1191
    }
1192

1193
    get endTrivia(): Token[] {
1194
        return this.tokens.close?.leadingTrivia;
2!
1195
    }
1196

1197
    public clone() {
1198
        return this.finalizeClone(
4✔
1199
            new ArrayLiteralExpression({
1200
                elements: this.elements?.map(e => e?.clone()),
6✔
1201
                open: util.cloneToken(this.tokens.open),
1202
                close: util.cloneToken(this.tokens.close)
1203
            }),
1204
            ['elements']
1205
        );
1206
    }
1207
}
1208

1209
export class AAMemberExpression extends Expression {
1✔
1210
    constructor(options: {
1211
        key: Token;
1212
        colon?: Token;
1213
        /** The expression evaluated to determine the member's initial value. */
1214
        value: Expression;
1215
        comma?: Token;
1216
    }) {
1217
        super();
361✔
1218
        this.tokens = {
361✔
1219
            key: options.key,
1220
            colon: options.colon,
1221
            comma: options.comma
1222
        };
1223
        this.value = options.value;
361✔
1224
        this.location = util.createBoundingLocation(this.tokens.key, this.tokens.colon, this.value);
361✔
1225
    }
1226

1227
    public readonly kind = AstNodeKind.AAMemberExpression;
361✔
1228

1229
    public readonly location: Location | undefined;
1230

1231
    public readonly tokens: {
1232
        readonly key: Token;
1233
        readonly colon?: Token;
1234
        readonly comma?: Token;
1235
    };
1236

1237
    /** The expression evaluated to determine the member's initial value. */
1238
    public readonly value: Expression;
1239

1240
    transpile(state: BrsTranspileState) {
1241
        //TODO move the logic from AALiteralExpression loop into this function
UNCOV
1242
        return [];
×
1243
    }
1244

1245
    walk(visitor: WalkVisitor, options: WalkOptions) {
1246
        walk(this, 'value', visitor, options);
1,473✔
1247
    }
1248

1249
    getType(options: GetTypeOptions): BscType {
1250
        return this.value.getType(options);
296✔
1251
    }
1252

1253
    get leadingTrivia(): Token[] {
1254
        return this.tokens.key.leadingTrivia;
1,108✔
1255
    }
1256

1257
    public clone() {
1258
        return this.finalizeClone(
4✔
1259
            new AAMemberExpression({
1260
                key: util.cloneToken(this.tokens.key),
1261
                colon: util.cloneToken(this.tokens.colon),
1262
                value: this.value?.clone()
12✔
1263
            }),
1264
            ['value']
1265
        );
1266
    }
1267
}
1268

1269
export class AALiteralExpression extends Expression {
1✔
1270
    constructor(options: {
1271
        elements: Array<AAMemberExpression>;
1272
        open?: Token;
1273
        close?: Token;
1274
    }) {
1275
        super();
337✔
1276
        this.tokens = {
337✔
1277
            open: options.open,
1278
            close: options.close
1279
        };
1280
        this.elements = options.elements;
337✔
1281
        this.location = util.createBoundingLocation(this.tokens.open, ...this.elements ?? [], this.tokens.close);
337✔
1282
    }
1283

1284
    public readonly elements: Array<AAMemberExpression>;
1285
    public readonly tokens: {
1286
        readonly open?: Token;
1287
        readonly close?: Token;
1288
    };
1289

1290
    public readonly kind = AstNodeKind.AALiteralExpression;
337✔
1291

1292
    public readonly location: Location | undefined;
1293

1294
    transpile(state: BrsTranspileState) {
1295
        let result: TranspileResult = [];
76✔
1296
        //open curly
1297
        result.push(
76✔
1298
            state.transpileToken(this.tokens.open, '{')
1299
        );
1300
        let hasChildren = this.elements.length > 0;
76✔
1301
        //add newline if the object has children and the first child isn't a comment starting on the same line as opening curly
1302
        if (hasChildren && !util.isLeadingCommentOnSameLine(this.tokens.open, this.elements[0])) {
76✔
1303
            result.push('\n');
33✔
1304
        }
1305
        state.blockDepth++;
76✔
1306
        for (let i = 0; i < this.elements.length; i++) {
76✔
1307
            let element = this.elements[i];
51✔
1308
            let previousElement = this.elements[i - 1];
51✔
1309
            let nextElement = this.elements[i + 1];
51✔
1310

1311
            //don't indent if comment is same-line
1312
            if (util.isLeadingCommentOnSameLine(this.tokens.open, element) ||
51✔
1313
                util.isLeadingCommentOnSameLine(previousElement, element)) {
1314
                result.push(' ');
7✔
1315
            } else {
1316
                //indent line
1317
                result.push(state.indent());
44✔
1318
            }
1319

1320
            //key
1321
            result.push(
51✔
1322
                state.transpileToken(element.tokens.key)
1323
            );
1324
            //colon
1325
            result.push(
51✔
1326
                state.transpileToken(element.tokens.colon, ':'),
1327
                ' '
1328
            );
1329
            //value
1330
            result.push(...element.value.transpile(state));
51✔
1331

1332
            //if next element is a same-line comment, skip the newline
1333
            if (nextElement && !util.isLeadingCommentOnSameLine(element, nextElement)) {
51✔
1334
                //add a newline between statements
1335
                result.push('\n');
11✔
1336
            }
1337
        }
1338
        state.blockDepth--;
76✔
1339

1340
        const lastElement = this.elements[this.elements.length - 1] ?? this.tokens.open;
76✔
1341
        result.push(...state.transpileEndBlockToken(lastElement, this.tokens.close, '}', hasChildren));
76✔
1342

1343
        return result;
76✔
1344
    }
1345

1346
    walk(visitor: WalkVisitor, options: WalkOptions) {
1347
        if (options.walkMode & InternalWalkMode.walkExpressions) {
1,436!
1348
            walkArray(this.elements, visitor, options, this);
1,436✔
1349
        }
1350
    }
1351

1352
    getType(options: GetTypeOptions): BscType {
1353
        const resultType = new AssociativeArrayType();
273✔
1354
        resultType.addBuiltInInterfaces();
273✔
1355
        for (const element of this.elements) {
273✔
1356
            if (isAAMemberExpression(element)) {
296!
1357
                let memberName = element.tokens?.key?.text ?? '';
296!
1358
                if (element.tokens.key.kind === TokenKind.StringLiteral) {
296✔
1359
                    memberName = memberName.replace(/"/g, ''); // remove quotes if it was a stringLiteral
7✔
1360
                }
1361
                if (memberName) {
296!
1362
                    resultType.addMember(memberName, { definingNode: element }, element.getType(options), SymbolTypeFlag.runtime);
296✔
1363
                }
1364
            }
1365
        }
1366
        return resultType;
273✔
1367
    }
1368

1369
    public get leadingTrivia(): Token[] {
1370
        return this.tokens.open?.leadingTrivia;
988!
1371
    }
1372

1373
    public get endTrivia(): Token[] {
1374
        return this.tokens.close?.leadingTrivia;
1!
1375
    }
1376

1377
    public clone() {
1378
        return this.finalizeClone(
6✔
1379
            new AALiteralExpression({
1380
                elements: this.elements?.map(e => e?.clone()),
5✔
1381
                open: util.cloneToken(this.tokens.open),
1382
                close: util.cloneToken(this.tokens.close)
1383
            }),
1384
            ['elements']
1385
        );
1386
    }
1387
}
1388

1389
export class UnaryExpression extends Expression {
1✔
1390
    constructor(options: {
1391
        operator: Token;
1392
        right: Expression;
1393
    }) {
1394
        super();
69✔
1395
        this.tokens = {
69✔
1396
            operator: options.operator
1397
        };
1398
        this.right = options.right;
69✔
1399
        this.location = util.createBoundingLocation(this.tokens.operator, this.right);
69✔
1400
    }
1401

1402
    public readonly kind = AstNodeKind.UnaryExpression;
69✔
1403

1404
    public readonly location: Location | undefined;
1405

1406
    public readonly tokens: {
1407
        readonly operator: Token;
1408
    };
1409
    public readonly right: Expression;
1410

1411
    transpile(state: BrsTranspileState) {
1412
        let separatingWhitespace: string | undefined;
1413
        if (isVariableExpression(this.right)) {
12✔
1414
            separatingWhitespace = this.right.tokens.name.leadingWhitespace;
6✔
1415
        } else if (isLiteralExpression(this.right)) {
6✔
1416
            separatingWhitespace = this.right.tokens.value.leadingWhitespace;
2✔
1417
        } else {
1418
            separatingWhitespace = ' ';
4✔
1419
        }
1420

1421
        return [
12✔
1422
            state.transpileToken(this.tokens.operator),
1423
            separatingWhitespace,
1424
            ...this.right.transpile(state)
1425
        ];
1426
    }
1427

1428
    walk(visitor: WalkVisitor, options: WalkOptions) {
1429
        if (options.walkMode & InternalWalkMode.walkExpressions) {
265!
1430
            walk(this, 'right', visitor, options);
265✔
1431
        }
1432
    }
1433

1434
    getType(options: GetTypeOptions): BscType {
1435
        return util.unaryOperatorResultType(this.tokens.operator, this.right.getType(options));
48✔
1436
    }
1437

1438
    public get leadingTrivia(): Token[] {
1439
        return this.tokens.operator.leadingTrivia;
202✔
1440
    }
1441

1442
    public clone() {
1443
        return this.finalizeClone(
2✔
1444
            new UnaryExpression({
1445
                operator: util.cloneToken(this.tokens.operator),
1446
                right: this.right?.clone()
6✔
1447
            }),
1448
            ['right']
1449
        );
1450
    }
1451
}
1452

1453
export class VariableExpression extends Expression {
1✔
1454
    constructor(options: {
1455
        name: Identifier;
1456
    }) {
1457
        super();
13,291✔
1458
        this.tokens = {
13,291✔
1459
            name: options.name
1460
        };
1461
        this.location = util.cloneLocation(this.tokens.name?.location);
13,291!
1462
    }
1463

1464
    public readonly tokens: {
1465
        readonly name: Identifier;
1466
    };
1467

1468
    public readonly kind = AstNodeKind.VariableExpression;
13,291✔
1469

1470
    public readonly location: Location;
1471

1472
    public getName(parseMode?: ParseMode) {
1473
        return this.tokens.name.text;
31,064✔
1474
    }
1475

1476
    transpile(state: BrsTranspileState) {
1477
        let result: TranspileResult = [];
7,115✔
1478
        const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
7,115✔
1479
        //if the callee is the name of a known namespace function
1480
        if (namespace && util.isCalleeMemberOfNamespace(this.tokens.name.text, this, namespace)) {
7,115✔
1481
            result.push(
18✔
1482
                //transpile leading comments since the token isn't being transpiled directly
1483
                ...state.transpileLeadingCommentsForAstNode(this),
1484
                state.sourceNode(this, [
1485
                    namespace.getName(ParseMode.BrightScript),
1486
                    '_',
1487
                    this.getName(ParseMode.BrightScript)
1488
                ])
1489
            );
1490
            //transpile  normally
1491
        } else {
1492
            result.push(
7,097✔
1493
                state.transpileToken(this.tokens.name)
1494
            );
1495
        }
1496
        return result;
7,115✔
1497
    }
1498

1499
    getTypedef(state: BrsTranspileState) {
1500
        return [
1✔
1501
            state.transpileToken(this.tokens.name)
1502
        ];
1503
    }
1504

1505
    walk(visitor: WalkVisitor, options: WalkOptions) {
1506
        //nothing to walk
1507
    }
1508

1509

1510
    getType(options: GetTypeOptions) {
1511
        let resultType: BscType = util.tokenToBscType(this.tokens.name);
30,910✔
1512
        const nameKey = this.getName();
30,910✔
1513
        if (!resultType) {
30,910✔
1514
            const symbolTable = this.getSymbolTable();
22,640✔
1515
            resultType = symbolTable?.getSymbolType(nameKey, {
22,640!
1516
                ...options,
1517
                statementIndex: this.statementIndex,
1518
                fullName: nameKey,
1519
                tableProvider: () => this.getSymbolTable()
66,950✔
1520
            });
1521

1522
            if (util.isClassUsedAsFunction(resultType, this, options)) {
22,640✔
1523
                resultType = FunctionType.instance;
20✔
1524
            }
1525

1526
        }
1527
        options?.typeChain?.push(new TypeChainEntry({ name: nameKey, type: resultType, data: options.data, astNode: this }));
30,910!
1528
        return resultType;
30,910✔
1529
    }
1530

1531
    get leadingTrivia(): Token[] {
1532
        return this.tokens.name.leadingTrivia;
48,361✔
1533
    }
1534

1535
    public clone() {
1536
        return this.finalizeClone(
70✔
1537
            new VariableExpression({
1538
                name: util.cloneToken(this.tokens.name)
1539
            })
1540
        );
1541
    }
1542
}
1543

1544
export class SourceLiteralExpression extends Expression {
1✔
1545
    constructor(options: {
1546
        value: Token;
1547
    }) {
1548
        super();
37✔
1549
        this.tokens = {
37✔
1550
            value: options.value
1551
        };
1552
        this.location = util.cloneLocation(this.tokens.value?.location);
37!
1553
    }
1554

1555
    public readonly location: Location;
1556

1557
    public readonly kind = AstNodeKind.SourceLiteralExpression;
37✔
1558

1559
    public readonly tokens: {
1560
        readonly value: Token;
1561
    };
1562

1563
    /**
1564
     * Find the index of the function in its parent
1565
     */
1566
    private findFunctionIndex(parentFunction: FunctionExpression, func: FunctionExpression) {
1567
        let index = -1;
4✔
1568
        parentFunction.findChild((node) => {
4✔
1569
            if (isFunctionExpression(node)) {
12✔
1570
                index++;
4✔
1571
                if (node === func) {
4!
1572
                    return true;
4✔
1573
                }
1574
            }
1575
        }, {
1576
            walkMode: WalkMode.visitAllRecursive
1577
        });
1578
        return index;
4✔
1579
    }
1580

1581
    private getFunctionName(state: BrsTranspileState, parseMode: ParseMode) {
1582
        let func = this.findAncestor<FunctionExpression>(isFunctionExpression);
8✔
1583
        let nameParts = [] as TranspileResult;
8✔
1584
        let parentFunction: FunctionExpression;
1585
        while ((parentFunction = func.findAncestor<FunctionExpression>(isFunctionExpression))) {
8✔
1586
            let index = this.findFunctionIndex(parentFunction, func);
4✔
1587
            nameParts.unshift(`anon${index}`);
4✔
1588
            func = parentFunction;
4✔
1589
        }
1590
        //get the index of this function in its parent
1591
        if (isFunctionStatement(func.parent)) {
8!
1592
            nameParts.unshift(
8✔
1593
                func.parent.getName(parseMode)
1594
            );
1595
        }
1596
        return nameParts.join('$');
8✔
1597
    }
1598

1599
    /**
1600
     * Get the line number from our token or from the closest ancestor that has a range
1601
     */
1602
    private getClosestLineNumber() {
1603
        let node: AstNode = this;
7✔
1604
        while (node) {
7✔
1605
            if (node.location?.range) {
17✔
1606
                return node.location.range.start.line + 1;
5✔
1607
            }
1608
            node = node.parent;
12✔
1609
        }
1610
        return -1;
2✔
1611
    }
1612

1613
    transpile(state: BrsTranspileState) {
1614
        let text: string;
1615
        switch (this.tokens.value.kind) {
31✔
1616
            case TokenKind.SourceFilePathLiteral:
40!
1617
                const pathUrl = fileUrl(state.srcPath);
3✔
1618
                text = `"${pathUrl.substring(0, 4)}" + "${pathUrl.substring(4)}"`;
3✔
1619
                break;
3✔
1620
            case TokenKind.SourceLineNumLiteral:
1621
                //TODO find first parent that has range, or default to -1
1622
                text = `${this.getClosestLineNumber()}`;
4✔
1623
                break;
4✔
1624
            case TokenKind.FunctionNameLiteral:
1625
                text = `"${this.getFunctionName(state, ParseMode.BrightScript)}"`;
4✔
1626
                break;
4✔
1627
            case TokenKind.SourceFunctionNameLiteral:
1628
                text = `"${this.getFunctionName(state, ParseMode.BrighterScript)}"`;
4✔
1629
                break;
4✔
1630
            case TokenKind.SourceNamespaceNameLiteral:
UNCOV
1631
                let namespaceParts = this.getFunctionName(state, ParseMode.BrighterScript).split('.');
×
UNCOV
1632
                namespaceParts.pop(); // remove the function name
×
1633

1634
                text = `"${namespaceParts.join('.')}"`;
×
1635
                break;
×
1636
            case TokenKind.SourceNamespaceRootNameLiteral:
1637
                let namespaceRootParts = this.getFunctionName(state, ParseMode.BrighterScript).split('.');
×
1638
                namespaceRootParts.pop(); // remove the function name
×
1639

1640
                let rootNamespace = namespaceRootParts.shift() ?? '';
×
1641
                text = `"${rootNamespace}"`;
×
UNCOV
1642
                break;
×
1643
            case TokenKind.SourceLocationLiteral:
1644
                const locationUrl = fileUrl(state.srcPath);
3✔
1645
                //TODO find first parent that has range, or default to -1
1646
                text = `"${locationUrl.substring(0, 4)}" + "${locationUrl.substring(4)}:${this.getClosestLineNumber()}"`;
3✔
1647
                break;
3✔
1648
            case TokenKind.PkgPathLiteral:
1649
                text = `"${util.sanitizePkgPath(state.file.pkgPath)}"`;
2✔
1650
                break;
2✔
1651
            case TokenKind.PkgLocationLiteral:
1652
                text = `"${util.sanitizePkgPath(state.file.pkgPath)}:" + str(LINE_NUM)`;
2✔
1653
                break;
2✔
1654
            case TokenKind.LineNumLiteral:
1655
            default:
1656
                //use the original text (because it looks like a variable)
1657
                text = this.tokens.value.text;
9✔
1658
                break;
9✔
1659

1660
        }
1661
        return [
31✔
1662
            state.sourceNode(this, text)
1663
        ];
1664
    }
1665

1666
    walk(visitor: WalkVisitor, options: WalkOptions) {
1667
        //nothing to walk
1668
    }
1669

1670
    get leadingTrivia(): Token[] {
1671
        return this.tokens.value.leadingTrivia;
200✔
1672
    }
1673

1674
    public clone() {
1675
        return this.finalizeClone(
1✔
1676
            new SourceLiteralExpression({
1677
                value: util.cloneToken(this.tokens.value)
1678
            })
1679
        );
1680
    }
1681
}
1682

1683
/**
1684
 * This expression transpiles and acts exactly like a CallExpression,
1685
 * except we need to uniquely identify these statements so we can
1686
 * do more type checking.
1687
 */
1688
export class NewExpression extends Expression {
1✔
1689
    constructor(options: {
1690
        new?: Token;
1691
        call: CallExpression;
1692
    }) {
1693
        super();
140✔
1694
        this.tokens = {
140✔
1695
            new: options.new
1696
        };
1697
        this.call = options.call;
140✔
1698
        this.location = util.createBoundingLocation(this.tokens.new, this.call);
140✔
1699
    }
1700

1701
    public readonly kind = AstNodeKind.NewExpression;
140✔
1702

1703
    public readonly location: Location | undefined;
1704

1705
    public readonly tokens: {
1706
        readonly new?: Token;
1707
    };
1708
    public readonly call: CallExpression;
1709

1710
    /**
1711
     * The name of the class to initialize (with optional namespace prefixed)
1712
     */
1713
    public get className() {
1714
        //the parser guarantees the callee of a new statement's call object will be
1715
        //either a VariableExpression or a DottedGet
1716
        return this.call.callee as (VariableExpression | DottedGetExpression);
92✔
1717
    }
1718

1719
    public transpile(state: BrsTranspileState) {
1720
        const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
15✔
1721
        const cls = state.file.getClassFileLink(
15✔
1722
            this.className.getName(ParseMode.BrighterScript),
1723
            namespace?.getName(ParseMode.BrighterScript)
45✔
1724
        )?.item;
15✔
1725
        //new statements within a namespace block can omit the leading namespace if the class resides in that same namespace.
1726
        //So we need to figure out if this is a namespace-omitted class, or if this class exists without a namespace.
1727
        return this.call.transpile(state, cls?.getName(ParseMode.BrightScript));
15✔
1728
    }
1729

1730
    walk(visitor: WalkVisitor, options: WalkOptions) {
1731
        if (options.walkMode & InternalWalkMode.walkExpressions) {
876!
1732
            walk(this, 'call', visitor, options);
876✔
1733
        }
1734
    }
1735

1736
    getType(options: GetTypeOptions) {
1737
        const result = this.call.getType(options);
348✔
1738
        if (options.typeChain) {
348✔
1739
            // modify last typechain entry to show it is a new ...()
1740
            const lastEntry = options.typeChain[options.typeChain.length - 1];
3✔
1741
            if (lastEntry) {
3!
1742
                lastEntry.astNode = this;
3✔
1743
            }
1744
        }
1745
        return result;
348✔
1746
    }
1747

1748
    get leadingTrivia(): Token[] {
1749
        return this.tokens.new.leadingTrivia;
611✔
1750
    }
1751

1752
    public clone() {
1753
        return this.finalizeClone(
2✔
1754
            new NewExpression({
1755
                new: util.cloneToken(this.tokens.new),
1756
                call: this.call?.clone()
6✔
1757
            }),
1758
            ['call']
1759
        );
1760
    }
1761
}
1762

1763
export class CallfuncExpression extends Expression {
1✔
1764
    constructor(options: {
1765
        callee: Expression;
1766
        operator?: Token;
1767
        methodName: Identifier;
1768
        openingParen?: Token;
1769
        args?: Expression[];
1770
        closingParen?: Token;
1771
    }) {
1772
        super();
83✔
1773
        this.tokens = {
83✔
1774
            operator: options.operator,
1775
            methodName: options.methodName,
1776
            openingParen: options.openingParen,
1777
            closingParen: options.closingParen
1778
        };
1779
        this.callee = options.callee;
83✔
1780
        this.args = options.args ?? [];
83✔
1781

1782
        this.location = util.createBoundingLocation(
83✔
1783
            this.callee,
1784
            this.tokens.operator,
1785
            this.tokens.methodName,
1786
            this.tokens.openingParen,
1787
            ...this.args ?? [],
249!
1788
            this.tokens.closingParen
1789
        );
1790
    }
1791

1792
    public readonly callee: Expression;
1793
    public readonly args: Expression[];
1794

1795
    public readonly tokens: {
1796
        readonly operator: Token;
1797
        readonly methodName: Identifier;
1798
        readonly openingParen?: Token;
1799
        readonly closingParen?: Token;
1800
    };
1801

1802
    public readonly kind = AstNodeKind.CallfuncExpression;
83✔
1803

1804
    public readonly location: Location | undefined;
1805

1806
    public transpile(state: BrsTranspileState) {
1807
        let result = [] as TranspileResult;
9✔
1808
        result.push(
9✔
1809
            ...this.callee.transpile(state),
1810
            state.sourceNode(this.tokens.operator, '.callfunc'),
1811
            state.transpileToken(this.tokens.openingParen, '('),
1812
            //the name of the function
1813
            state.sourceNode(this.tokens.methodName, ['"', this.tokens.methodName.text, '"'])
1814
        );
1815
        if (this.args?.length > 0) {
9!
1816
            result.push(', ');
4✔
1817
            //transpile args
1818
            for (let i = 0; i < this.args.length; i++) {
4✔
1819
                //add comma between args
1820
                if (i > 0) {
7✔
1821
                    result.push(', ');
3✔
1822
                }
1823
                let arg = this.args[i];
7✔
1824
                result.push(...arg.transpile(state));
7✔
1825
            }
1826
        } else if (state.options.legacyCallfuncHandling) {
5✔
1827
            result.push(', ', 'invalid');
2✔
1828
        }
1829

1830
        result.push(
9✔
1831
            state.transpileToken(this.tokens.closingParen, ')')
1832
        );
1833
        return result;
9✔
1834
    }
1835

1836
    walk(visitor: WalkVisitor, options: WalkOptions) {
1837
        if (options.walkMode & InternalWalkMode.walkExpressions) {
454!
1838
            walk(this, 'callee', visitor, options);
454✔
1839
            walkArray(this.args, visitor, options, this);
454✔
1840
        }
1841
    }
1842

1843
    getType(options: GetTypeOptions) {
1844
        const result = util.getCallFuncType(this, this.tokens.methodName, options) ?? DynamicType.instance;
38!
1845
        return result;
38✔
1846
    }
1847

1848
    get leadingTrivia(): Token[] {
1849
        return this.callee.leadingTrivia;
695✔
1850
    }
1851

1852
    public clone() {
1853
        return this.finalizeClone(
3✔
1854
            new CallfuncExpression({
1855
                callee: this.callee?.clone(),
9✔
1856
                operator: util.cloneToken(this.tokens.operator),
1857
                methodName: util.cloneToken(this.tokens.methodName),
1858
                openingParen: util.cloneToken(this.tokens.openingParen),
1859
                args: this.args?.map(e => e?.clone()),
2✔
1860
                closingParen: util.cloneToken(this.tokens.closingParen)
1861
            }),
1862
            ['callee', 'args']
1863
        );
1864
    }
1865
}
1866

1867
/**
1868
 * Since template strings can contain newlines, we need to concatenate multiple strings together with chr() calls.
1869
 * This is a single expression that represents the string contatenation of all parts of a single quasi.
1870
 */
1871
export class TemplateStringQuasiExpression extends Expression {
1✔
1872
    constructor(options: {
1873
        expressions: Array<LiteralExpression | EscapedCharCodeLiteralExpression>;
1874
    }) {
1875
        super();
114✔
1876
        this.expressions = options.expressions;
114✔
1877
        this.location = util.createBoundingLocation(
114✔
1878
            ...this.expressions ?? []
342✔
1879
        );
1880
    }
1881

1882
    public readonly expressions: Array<LiteralExpression | EscapedCharCodeLiteralExpression>;
1883
    public readonly kind = AstNodeKind.TemplateStringQuasiExpression;
114✔
1884

1885
    readonly location: Location | undefined;
1886

1887
    transpile(state: BrsTranspileState, skipEmptyStrings = true) {
41✔
1888
        let result = [] as TranspileResult;
49✔
1889
        let plus = '';
49✔
1890
        for (let expression of this.expressions) {
49✔
1891
            //skip empty strings
1892
            //TODO what does an empty string literal expression look like?
1893
            if (expression.tokens.value.text === '' && skipEmptyStrings === true) {
78✔
1894
                continue;
28✔
1895
            }
1896
            result.push(
50✔
1897
                plus,
1898
                ...expression.transpile(state)
1899
            );
1900
            plus = ' + ';
50✔
1901
        }
1902
        return result;
49✔
1903
    }
1904

1905
    walk(visitor: WalkVisitor, options: WalkOptions) {
1906
        if (options.walkMode & InternalWalkMode.walkExpressions) {
412!
1907
            walkArray(this.expressions, visitor, options, this);
412✔
1908
        }
1909
    }
1910

1911
    public clone() {
1912
        return this.finalizeClone(
15✔
1913
            new TemplateStringQuasiExpression({
1914
                expressions: this.expressions?.map(e => e?.clone())
20✔
1915
            }),
1916
            ['expressions']
1917
        );
1918
    }
1919
}
1920

1921
export class TemplateStringExpression extends Expression {
1✔
1922
    constructor(options: {
1923
        openingBacktick?: Token;
1924
        quasis: TemplateStringQuasiExpression[];
1925
        expressions: Expression[];
1926
        closingBacktick?: Token;
1927
    }) {
1928
        super();
53✔
1929
        this.tokens = {
53✔
1930
            openingBacktick: options.openingBacktick,
1931
            closingBacktick: options.closingBacktick
1932
        };
1933
        this.quasis = options.quasis;
53✔
1934
        this.expressions = options.expressions;
53✔
1935
        this.location = util.createBoundingLocation(
53✔
1936
            this.tokens.openingBacktick,
1937
            this.quasis?.[0],
159✔
1938
            this.quasis?.[this.quasis?.length - 1],
315!
1939
            this.tokens.closingBacktick
1940
        );
1941
    }
1942

1943
    public readonly kind = AstNodeKind.TemplateStringExpression;
53✔
1944

1945
    public readonly tokens: {
1946
        readonly openingBacktick?: Token;
1947
        readonly closingBacktick?: Token;
1948
    };
1949
    public readonly quasis: TemplateStringQuasiExpression[];
1950
    public readonly expressions: Expression[];
1951

1952
    public readonly location: Location | undefined;
1953

1954
    public getType(options: GetTypeOptions) {
1955
        return StringType.instance;
34✔
1956
    }
1957

1958
    transpile(state: BrsTranspileState) {
1959
        //if this is essentially just a normal brightscript string but with backticks, transpile it as a normal string without parens
1960
        if (this.expressions.length === 0 && this.quasis.length === 1 && this.quasis[0].expressions.length === 1) {
24✔
1961
            return this.quasis[0].transpile(state);
6✔
1962
        }
1963
        let result = ['('];
18✔
1964
        let plus = '';
18✔
1965
        //helper function to figure out when to include the plus
1966
        function add(...items) {
1967
            if (items.length > 0) {
52✔
1968
                result.push(
40✔
1969
                    plus,
1970
                    ...items
1971
                );
1972
            }
1973
            //set the plus after the first occurance of a nonzero length set of items
1974
            if (plus === '' && items.length > 0) {
52✔
1975
                plus = ' + ';
18✔
1976
            }
1977
        }
1978

1979
        for (let i = 0; i < this.quasis.length; i++) {
18✔
1980
            let quasi = this.quasis[i];
35✔
1981
            let expression = this.expressions[i];
35✔
1982

1983
            add(
35✔
1984
                ...quasi.transpile(state)
1985
            );
1986
            if (expression) {
35✔
1987
                //skip the toString wrapper around certain expressions
1988
                if (
17✔
1989
                    isEscapedCharCodeLiteralExpression(expression) ||
39✔
1990
                    (isLiteralExpression(expression) && isStringType(expression.getType()))
1991
                ) {
1992
                    add(
3✔
1993
                        ...expression.transpile(state)
1994
                    );
1995

1996
                    //wrap all other expressions with a bslib_toString call to prevent runtime type mismatch errors
1997
                } else {
1998
                    add(
14✔
1999
                        state.bslibPrefix + '_toString(',
2000
                        ...expression.transpile(state),
2001
                        ')'
2002
                    );
2003
                }
2004
            }
2005
        }
2006
        //the expression should be wrapped in parens so it can be used line a single expression at runtime
2007
        result.push(')');
18✔
2008

2009
        return result;
18✔
2010
    }
2011

2012
    walk(visitor: WalkVisitor, options: WalkOptions) {
2013
        if (options.walkMode & InternalWalkMode.walkExpressions) {
208!
2014
            //walk the quasis and expressions in left-to-right order
2015
            for (let i = 0; i < this.quasis?.length; i++) {
208!
2016
                walk(this.quasis, i, visitor, options, this);
348✔
2017

2018
                //this skips the final loop iteration since we'll always have one more quasi than expression
2019
                if (this.expressions[i]) {
348✔
2020
                    walk(this.expressions, i, visitor, options, this);
140✔
2021
                }
2022
            }
2023
        }
2024
    }
2025

2026
    public clone() {
2027
        return this.finalizeClone(
7✔
2028
            new TemplateStringExpression({
2029
                openingBacktick: util.cloneToken(this.tokens.openingBacktick),
2030
                quasis: this.quasis?.map(e => e?.clone()),
12✔
2031
                expressions: this.expressions?.map(e => e?.clone()),
6✔
2032
                closingBacktick: util.cloneToken(this.tokens.closingBacktick)
2033
            }),
2034
            ['quasis', 'expressions']
2035
        );
2036
    }
2037
}
2038

2039
export class TaggedTemplateStringExpression extends Expression {
1✔
2040
    constructor(options: {
2041
        tagName: Identifier;
2042
        openingBacktick?: Token;
2043
        quasis: TemplateStringQuasiExpression[];
2044
        expressions: Expression[];
2045
        closingBacktick?: Token;
2046
    }) {
2047
        super();
12✔
2048
        this.tokens = {
12✔
2049
            tagName: options.tagName,
2050
            openingBacktick: options.openingBacktick,
2051
            closingBacktick: options.closingBacktick
2052
        };
2053
        this.quasis = options.quasis;
12✔
2054
        this.expressions = options.expressions;
12✔
2055

2056
        this.location = util.createBoundingLocation(
12✔
2057
            this.tokens.tagName,
2058
            this.tokens.openingBacktick,
2059
            this.quasis?.[0],
36✔
2060
            this.quasis?.[this.quasis?.length - 1],
69!
2061
            this.tokens.closingBacktick
2062
        );
2063
    }
2064

2065
    public readonly kind = AstNodeKind.TaggedTemplateStringExpression;
12✔
2066

2067
    public readonly tokens: {
2068
        readonly tagName: Identifier;
2069
        readonly openingBacktick?: Token;
2070
        readonly closingBacktick?: Token;
2071
    };
2072

2073
    public readonly quasis: TemplateStringQuasiExpression[];
2074
    public readonly expressions: Expression[];
2075

2076
    public readonly location: Location | undefined;
2077

2078
    transpile(state: BrsTranspileState) {
2079
        let result = [] as TranspileResult;
3✔
2080
        result.push(
3✔
2081
            state.transpileToken(this.tokens.tagName),
2082
            '(['
2083
        );
2084

2085
        //add quasis as the first array
2086
        for (let i = 0; i < this.quasis.length; i++) {
3✔
2087
            let quasi = this.quasis[i];
8✔
2088
            //separate items with a comma
2089
            if (i > 0) {
8✔
2090
                result.push(
5✔
2091
                    ', '
2092
                );
2093
            }
2094
            result.push(
8✔
2095
                ...quasi.transpile(state, false)
2096
            );
2097
        }
2098
        result.push(
3✔
2099
            '], ['
2100
        );
2101

2102
        //add expressions as the second array
2103
        for (let i = 0; i < this.expressions.length; i++) {
3✔
2104
            let expression = this.expressions[i];
5✔
2105
            if (i > 0) {
5✔
2106
                result.push(
2✔
2107
                    ', '
2108
                );
2109
            }
2110
            result.push(
5✔
2111
                ...expression.transpile(state)
2112
            );
2113
        }
2114
        result.push(
3✔
2115
            state.sourceNode(this.tokens.closingBacktick, '])')
2116
        );
2117
        return result;
3✔
2118
    }
2119

2120
    walk(visitor: WalkVisitor, options: WalkOptions) {
2121
        if (options.walkMode & InternalWalkMode.walkExpressions) {
28!
2122
            //walk the quasis and expressions in left-to-right order
2123
            for (let i = 0; i < this.quasis?.length; i++) {
28!
2124
                walk(this.quasis, i, visitor, options, this);
68✔
2125

2126
                //this skips the final loop iteration since we'll always have one more quasi than expression
2127
                if (this.expressions[i]) {
68✔
2128
                    walk(this.expressions, i, visitor, options, this);
40✔
2129
                }
2130
            }
2131
        }
2132
    }
2133

2134
    public clone() {
2135
        return this.finalizeClone(
3✔
2136
            new TaggedTemplateStringExpression({
2137
                tagName: util.cloneToken(this.tokens.tagName),
2138
                openingBacktick: util.cloneToken(this.tokens.openingBacktick),
2139
                quasis: this.quasis?.map(e => e?.clone()),
5✔
2140
                expressions: this.expressions?.map(e => e?.clone()),
3✔
2141
                closingBacktick: util.cloneToken(this.tokens.closingBacktick)
2142
            }),
2143
            ['quasis', 'expressions']
2144
        );
2145
    }
2146
}
2147

2148
export class AnnotationExpression extends Expression {
1✔
2149
    constructor(options: {
2150
        at?: Token;
2151
        name: Token;
2152
        call?: CallExpression;
2153
    }) {
2154
        super();
83✔
2155
        this.tokens = {
83✔
2156
            at: options.at,
2157
            name: options.name
2158
        };
2159
        this.call = options.call;
83✔
2160
        this.name = this.tokens.name.text;
83✔
2161
    }
2162

2163
    public readonly kind = AstNodeKind.AnnotationExpression;
83✔
2164

2165
    public readonly tokens: {
2166
        readonly at: Token;
2167
        readonly name: Token;
2168
    };
2169

2170
    public get location(): Location | undefined {
2171
        return util.createBoundingLocation(
75✔
2172
            this.tokens.at,
2173
            this.tokens.name,
2174
            this.call
2175
        );
2176
    }
2177

2178
    public readonly name: string;
2179

2180
    public call: CallExpression;
2181

2182
    /**
2183
     * Convert annotation arguments to JavaScript types
2184
     * @param strict If false, keep Expression objects not corresponding to JS types
2185
     */
2186
    getArguments(strict = true): ExpressionValue[] {
10✔
2187
        if (!this.call) {
11✔
2188
            return [];
1✔
2189
        }
2190
        return this.call.args.map(e => expressionToValue(e, strict));
20✔
2191
    }
2192

2193
    public get leadingTrivia(): Token[] {
2194
        return this.tokens.at?.leadingTrivia;
50!
2195
    }
2196

2197
    transpile(state: BrsTranspileState) {
2198
        //transpile only our leading comments
2199
        return state.transpileComments(this.leadingTrivia);
16✔
2200
    }
2201

2202
    walk(visitor: WalkVisitor, options: WalkOptions) {
2203
        //nothing to walk
2204
    }
2205
    getTypedef(state: BrsTranspileState) {
2206
        return [
9✔
2207
            '@',
2208
            this.name,
2209
            ...(this.call?.transpile(state) ?? [])
54✔
2210
        ];
2211
    }
2212

2213
    public clone() {
2214
        const clone = this.finalizeClone(
7✔
2215
            new AnnotationExpression({
2216
                at: util.cloneToken(this.tokens.at),
2217
                name: util.cloneToken(this.tokens.name)
2218
            })
2219
        );
2220
        return clone;
7✔
2221
    }
2222
}
2223

2224
export class TernaryExpression extends Expression {
1✔
2225
    constructor(options: {
2226
        test: Expression;
2227
        questionMark?: Token;
2228
        consequent?: Expression;
2229
        colon?: Token;
2230
        alternate?: Expression;
2231
    }) {
2232
        super();
100✔
2233
        this.tokens = {
100✔
2234
            questionMark: options.questionMark,
2235
            colon: options.colon
2236
        };
2237
        this.test = options.test;
100✔
2238
        this.consequent = options.consequent;
100✔
2239
        this.alternate = options.alternate;
100✔
2240
        this.location = util.createBoundingLocation(
100✔
2241
            this.test,
2242
            this.tokens.questionMark,
2243
            this.consequent,
2244
            this.tokens.colon,
2245
            this.alternate
2246
        );
2247
    }
2248

2249
    public readonly kind = AstNodeKind.TernaryExpression;
100✔
2250

2251
    public readonly location: Location | undefined;
2252

2253
    public readonly tokens: {
2254
        readonly questionMark?: Token;
2255
        readonly colon?: Token;
2256
    };
2257

2258
    public readonly test: Expression;
2259
    public readonly consequent?: Expression;
2260
    public readonly alternate?: Expression;
2261

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

2268
        //get all unique variable names used in the consequent and alternate, and sort them alphabetically so the output is consistent
2269
        let allUniqueVarNames = [...new Set([...consequentInfo.uniqueVarNames, ...alternateInfo.uniqueVarNames])].sort();
22✔
2270
        //discard names of global functions that cannot be passed by reference
2271
        allUniqueVarNames = allUniqueVarNames.filter(name => {
22✔
2272
            return !nonReferenceableFunctions.includes(name.toLowerCase());
23✔
2273
        });
2274

2275
        let mutatingExpressions = [
22✔
2276
            ...consequentInfo.expressions,
2277
            ...alternateInfo.expressions
2278
        ].filter(e => e instanceof CallExpression || e instanceof CallfuncExpression || e instanceof DottedGetExpression);
136✔
2279

2280
        if (mutatingExpressions.length > 0) {
22✔
2281
            result.push(
10✔
2282
                state.sourceNode(
2283
                    this.tokens.questionMark,
2284
                    //write all the scope variables as parameters.
2285
                    //TODO handle when there are more than 31 parameters
2286
                    `(function(${['__bsCondition', ...allUniqueVarNames].join(', ')})`
2287
                ),
2288
                state.newline,
2289
                //double indent so our `end function` line is still indented one at the end
2290
                state.indent(2),
2291
                state.sourceNode(this.test, `if __bsCondition then`),
2292
                state.newline,
2293
                state.indent(1),
2294
                state.sourceNode(this.consequent ?? this.tokens.questionMark, 'return '),
30!
2295
                ...this.consequent?.transpile(state) ?? [state.sourceNode(this.tokens.questionMark, 'invalid')],
60!
2296
                state.newline,
2297
                state.indent(-1),
2298
                state.sourceNode(this.consequent ?? this.tokens.questionMark, 'else'),
30!
2299
                state.newline,
2300
                state.indent(1),
2301
                state.sourceNode(this.consequent ?? this.tokens.questionMark, 'return '),
30!
2302
                ...this.alternate?.transpile(state) ?? [state.sourceNode(this.consequent ?? this.tokens.questionMark, 'invalid')],
60!
2303
                state.newline,
2304
                state.indent(-1),
2305
                state.sourceNode(this.tokens.questionMark, 'end if'),
2306
                state.newline,
2307
                state.indent(-1),
2308
                state.sourceNode(this.tokens.questionMark, 'end function)('),
2309
                ...this.test.transpile(state),
2310
                state.sourceNode(this.tokens.questionMark, `${['', ...allUniqueVarNames].join(', ')})`)
2311
            );
2312
            state.blockDepth--;
10✔
2313
        } else {
2314
            result.push(
12✔
2315
                state.sourceNode(this.test, state.bslibPrefix + `_ternary(`),
2316
                ...this.test.transpile(state),
2317
                state.sourceNode(this.test, `, `),
2318
                ...this.consequent?.transpile(state) ?? ['invalid'],
72✔
2319
                `, `,
2320
                ...this.alternate?.transpile(state) ?? ['invalid'],
72✔
2321
                `)`
2322
            );
2323
        }
2324
        return result;
22✔
2325
    }
2326

2327
    public walk(visitor: WalkVisitor, options: WalkOptions) {
2328
        if (options.walkMode & InternalWalkMode.walkExpressions) {
394!
2329
            walk(this, 'test', visitor, options);
394✔
2330
            walk(this, 'consequent', visitor, options);
394✔
2331
            walk(this, 'alternate', visitor, options);
394✔
2332
        }
2333
    }
2334

2335
    get leadingTrivia(): Token[] {
2336
        return this.test.leadingTrivia;
258✔
2337
    }
2338

2339
    public clone() {
2340
        return this.finalizeClone(
2✔
2341
            new TernaryExpression({
2342
                test: this.test?.clone(),
6✔
2343
                questionMark: util.cloneToken(this.tokens.questionMark),
2344
                consequent: this.consequent?.clone(),
6✔
2345
                colon: util.cloneToken(this.tokens.colon),
2346
                alternate: this.alternate?.clone()
6✔
2347
            }),
2348
            ['test', 'consequent', 'alternate']
2349
        );
2350
    }
2351
}
2352

2353
export class NullCoalescingExpression extends Expression {
1✔
2354
    constructor(options: {
2355
        consequent: Expression;
2356
        questionQuestion?: Token;
2357
        alternate: Expression;
2358
    }) {
2359
        super();
37✔
2360
        this.tokens = {
37✔
2361
            questionQuestion: options.questionQuestion
2362
        };
2363
        this.consequent = options.consequent;
37✔
2364
        this.alternate = options.alternate;
37✔
2365
        this.location = util.createBoundingLocation(
37✔
2366
            this.consequent,
2367
            this.tokens.questionQuestion,
2368
            this.alternate
2369
        );
2370
    }
2371

2372
    public readonly kind = AstNodeKind.NullCoalescingExpression;
37✔
2373

2374
    public readonly location: Location | undefined;
2375

2376
    public readonly tokens: {
2377
        readonly questionQuestion?: Token;
2378
    };
2379

2380
    public readonly consequent: Expression;
2381
    public readonly alternate: Expression;
2382

2383
    transpile(state: BrsTranspileState) {
2384
        let result = [] as TranspileResult;
11✔
2385
        let consequentInfo = util.getExpressionInfo(this.consequent, state.file);
11✔
2386
        let alternateInfo = util.getExpressionInfo(this.alternate, state.file);
11✔
2387

2388
        //get all unique variable names used in the consequent and alternate, and sort them alphabetically so the output is consistent
2389
        let allUniqueVarNames = [...new Set([...consequentInfo.uniqueVarNames, ...alternateInfo.uniqueVarNames])].sort();
11✔
2390
        //discard names of global functions that cannot be passed by reference
2391
        allUniqueVarNames = allUniqueVarNames.filter(name => {
11✔
2392
            return !nonReferenceableFunctions.includes(name.toLowerCase());
22✔
2393
        });
2394

2395
        let hasMutatingExpression = [
11✔
2396
            ...consequentInfo.expressions,
2397
            ...alternateInfo.expressions
2398
        ].find(e => isCallExpression(e) || isCallfuncExpression(e) || isDottedGetExpression(e));
30✔
2399

2400
        if (hasMutatingExpression) {
11✔
2401
            result.push(
7✔
2402
                `(function(`,
2403
                //write all the scope variables as parameters.
2404
                //TODO handle when there are more than 31 parameters
2405
                allUniqueVarNames.join(', '),
2406
                ')',
2407
                state.newline,
2408
                //double indent so our `end function` line is still indented one at the end
2409
                state.indent(2),
2410
                //evaluate the consequent exactly once, and then use it in the following condition
2411
                `__bsConsequent = `,
2412
                ...this.consequent.transpile(state),
2413
                state.newline,
2414
                state.indent(),
2415
                `if __bsConsequent <> invalid then`,
2416
                state.newline,
2417
                state.indent(1),
2418
                'return __bsConsequent',
2419
                state.newline,
2420
                state.indent(-1),
2421
                'else',
2422
                state.newline,
2423
                state.indent(1),
2424
                'return ',
2425
                ...this.alternate.transpile(state),
2426
                state.newline,
2427
                state.indent(-1),
2428
                'end if',
2429
                state.newline,
2430
                state.indent(-1),
2431
                'end function)(',
2432
                allUniqueVarNames.join(', '),
2433
                ')'
2434
            );
2435
            state.blockDepth--;
7✔
2436
        } else {
2437
            result.push(
4✔
2438
                state.bslibPrefix + `_coalesce(`,
2439
                ...this.consequent.transpile(state),
2440
                ', ',
2441
                ...this.alternate.transpile(state),
2442
                ')'
2443
            );
2444
        }
2445
        return result;
11✔
2446
    }
2447

2448
    public walk(visitor: WalkVisitor, options: WalkOptions) {
2449
        if (options.walkMode & InternalWalkMode.walkExpressions) {
99!
2450
            walk(this, 'consequent', visitor, options);
99✔
2451
            walk(this, 'alternate', visitor, options);
99✔
2452
        }
2453
    }
2454

2455
    get leadingTrivia(): Token[] {
2456
        return this.consequent.leadingTrivia;
58✔
2457
    }
2458

2459
    public clone() {
2460
        return this.finalizeClone(
2✔
2461
            new NullCoalescingExpression({
2462
                consequent: this.consequent?.clone(),
6✔
2463
                questionQuestion: util.cloneToken(this.tokens.questionQuestion),
2464
                alternate: this.alternate?.clone()
6✔
2465
            }),
2466
            ['consequent', 'alternate']
2467
        );
2468
    }
2469
}
2470

2471
export class RegexLiteralExpression extends Expression {
1✔
2472
    constructor(options: {
2473
        regexLiteral: Token;
2474
    }) {
2475
        super();
46✔
2476
        this.tokens = {
46✔
2477
            regexLiteral: options.regexLiteral
2478
        };
2479
    }
2480

2481
    public readonly kind = AstNodeKind.RegexLiteralExpression;
46✔
2482
    public readonly tokens: {
2483
        readonly regexLiteral: Token;
2484
    };
2485

2486
    public get location(): Location {
2487
        return this.tokens?.regexLiteral?.location;
150!
2488
    }
2489

2490
    public transpile(state: BrsTranspileState): TranspileResult {
2491
        let text = this.tokens.regexLiteral?.text ?? '';
42!
2492
        let flags = '';
42✔
2493
        //get any flags from the end
2494
        const flagMatch = /\/([a-z]+)$/i.exec(text);
42✔
2495
        if (flagMatch) {
42✔
2496
            text = text.substring(0, flagMatch.index + 1);
2✔
2497
            flags = flagMatch[1];
2✔
2498
        }
2499
        let pattern = text
42✔
2500
            //remove leading and trailing slashes
2501
            .substring(1, text.length - 1)
2502
            //escape quotemarks
2503
            .split('"').join('" + chr(34) + "');
2504

2505
        return [
42✔
2506
            state.sourceNode(this.tokens.regexLiteral, [
2507
                'CreateObject("roRegex", ',
2508
                `"${pattern}", `,
2509
                `"${flags}"`,
2510
                ')'
2511
            ])
2512
        ];
2513
    }
2514

2515
    walk(visitor: WalkVisitor, options: WalkOptions) {
2516
        //nothing to walk
2517
    }
2518

2519
    public clone() {
2520
        return this.finalizeClone(
1✔
2521
            new RegexLiteralExpression({
2522
                regexLiteral: util.cloneToken(this.tokens.regexLiteral)
2523
            })
2524
        );
2525
    }
2526

2527
    get leadingTrivia(): Token[] {
2528
        return this.tokens.regexLiteral?.leadingTrivia;
1,038!
2529
    }
2530
}
2531

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

2535
function expressionToValue(expr: Expression, strict: boolean): ExpressionValue {
2536
    if (!expr) {
30!
UNCOV
2537
        return null;
×
2538
    }
2539
    if (isUnaryExpression(expr) && isLiteralNumber(expr.right)) {
30✔
2540
        return numberExpressionToValue(expr.right, expr.tokens.operator.text);
1✔
2541
    }
2542
    if (isLiteralString(expr)) {
29✔
2543
        //remove leading and trailing quotes
2544
        return expr.tokens.value.text.replace(/^"/, '').replace(/"$/, '');
5✔
2545
    }
2546
    if (isLiteralNumber(expr)) {
24✔
2547
        return numberExpressionToValue(expr);
11✔
2548
    }
2549

2550
    if (isLiteralBoolean(expr)) {
13✔
2551
        return expr.tokens.value.text.toLowerCase() === 'true';
3✔
2552
    }
2553
    if (isArrayLiteralExpression(expr)) {
10✔
2554
        return expr.elements
3✔
2555
            .map(e => expressionToValue(e, strict));
7✔
2556
    }
2557
    if (isAALiteralExpression(expr)) {
7✔
2558
        return expr.elements.reduce((acc, e) => {
3✔
2559
            acc[e.tokens.key.text] = expressionToValue(e.value, strict);
3✔
2560
            return acc;
3✔
2561
        }, {});
2562
    }
2563
    //for annotations, we only support serializing pure string values
2564
    if (isTemplateStringExpression(expr)) {
4✔
2565
        if (expr.quasis?.length === 1 && expr.expressions.length === 0) {
2!
2566
            return expr.quasis[0].expressions.map(x => x.tokens.value.text).join('');
10✔
2567
        }
2568
    }
2569
    return strict ? null : expr;
2✔
2570
}
2571

2572
function numberExpressionToValue(expr: LiteralExpression, operator = '') {
11✔
2573
    if (isIntegerType(expr.getType()) || isLongIntegerType(expr.getType())) {
12!
2574
        return parseInt(operator + expr.tokens.value.text);
12✔
2575
    } else {
UNCOV
2576
        return parseFloat(operator + expr.tokens.value.text);
×
2577
    }
2578
}
2579

2580
export class TypeExpression extends Expression implements TypedefProvider {
1✔
2581
    constructor(options: {
2582
        /**
2583
         * The standard AST expression that represents the type for this TypeExpression.
2584
         */
2585
        expression: Expression;
2586
    }) {
2587
        super();
2,319✔
2588
        this.expression = options.expression;
2,319✔
2589
        this.location = util.cloneLocation(this.expression?.location);
2,319!
2590
    }
2591

2592
    public readonly kind = AstNodeKind.TypeExpression;
2,319✔
2593

2594
    /**
2595
     * The standard AST expression that represents the type for this TypeExpression.
2596
     */
2597
    public readonly expression: Expression;
2598

2599
    public readonly location: Location;
2600

2601
    public transpile(state: BrsTranspileState): TranspileResult {
2602
        const exprType = this.getType({ flags: SymbolTypeFlag.typetime });
124✔
2603
        if (isNativeType(exprType)) {
124✔
2604
            return this.expression.transpile(state);
108✔
2605
        }
2606
        return [exprType.toTypeString()];
16✔
2607
    }
2608
    public walk(visitor: WalkVisitor, options: WalkOptions) {
2609
        if (options.walkMode & InternalWalkMode.walkExpressions) {
10,698✔
2610
            walk(this, 'expression', visitor, options);
10,564✔
2611
        }
2612
    }
2613

2614
    public getType(options: GetTypeOptions): BscType {
2615
        return this.expression.getType({ ...options, flags: SymbolTypeFlag.typetime });
10,380✔
2616
    }
2617

2618
    getTypedef(state: TranspileState): TranspileResult {
2619
        // TypeDefs should pass through any valid type names
2620
        return this.expression.transpile(state as BrsTranspileState);
33✔
2621
    }
2622

2623
    getName(parseMode = ParseMode.BrighterScript): string {
272✔
2624
        //TODO: this may not support Complex Types, eg. generics or Unions
2625
        return util.getAllDottedGetPartsAsString(this.expression, parseMode);
274✔
2626
    }
2627

2628
    getNameParts(): string[] {
2629
        //TODO: really, this code is only used to get Namespaces. It could be more clear.
2630
        return util.getAllDottedGetParts(this.expression).map(x => x.text);
20✔
2631
    }
2632

2633
    public clone() {
2634
        return this.finalizeClone(
18✔
2635
            new TypeExpression({
2636
                expression: this.expression?.clone()
54!
2637
            }),
2638
            ['expression']
2639
        );
2640
    }
2641
}
2642

2643
export class TypecastExpression extends Expression {
1✔
2644
    constructor(options: {
2645
        obj: Expression;
2646
        as?: Token;
2647
        typeExpression?: TypeExpression;
2648
    }) {
2649
        super();
90✔
2650
        this.tokens = {
90✔
2651
            as: options.as
2652
        };
2653
        this.obj = options.obj;
90✔
2654
        this.typeExpression = options.typeExpression;
90✔
2655
        this.location = util.createBoundingLocation(
90✔
2656
            this.obj,
2657
            this.tokens.as,
2658
            this.typeExpression
2659
        );
2660
    }
2661

2662
    public readonly kind = AstNodeKind.TypecastExpression;
90✔
2663

2664
    public readonly obj: Expression;
2665

2666
    public readonly tokens: {
2667
        readonly as?: Token;
2668
    };
2669

2670
    public typeExpression?: TypeExpression;
2671

2672
    public readonly location: Location;
2673

2674
    public transpile(state: BrsTranspileState): TranspileResult {
2675
        return this.obj.transpile(state);
13✔
2676
    }
2677
    public walk(visitor: WalkVisitor, options: WalkOptions) {
2678
        if (options.walkMode & InternalWalkMode.walkExpressions) {
457!
2679
            walk(this, 'obj', visitor, options);
457✔
2680
            walk(this, 'typeExpression', visitor, options);
457✔
2681
        }
2682
    }
2683

2684
    public getType(options: GetTypeOptions): BscType {
2685
        const result = this.typeExpression.getType(options);
111✔
2686
        if (options.typeChain) {
111✔
2687
            // modify last typechain entry to show it is a typecast
2688
            const lastEntry = options.typeChain[options.typeChain.length - 1];
25✔
2689
            if (lastEntry) {
25!
2690
                lastEntry.astNode = this;
25✔
2691
            }
2692
        }
2693
        return result;
111✔
2694
    }
2695

2696
    public clone() {
2697
        return this.finalizeClone(
3✔
2698
            new TypecastExpression({
2699
                obj: this.obj?.clone(),
9✔
2700
                as: util.cloneToken(this.tokens.as),
2701
                typeExpression: this.typeExpression?.clone()
9!
2702
            }),
2703
            ['obj', 'typeExpression']
2704
        );
2705
    }
2706
}
2707

2708
export class TypedArrayExpression extends Expression {
1✔
2709
    constructor(options: {
2710
        innerType: Expression;
2711
        leftBracket?: Token;
2712
        rightBracket?: Token;
2713
    }) {
2714
        super();
48✔
2715
        this.tokens = {
48✔
2716
            leftBracket: options.leftBracket,
2717
            rightBracket: options.rightBracket
2718
        };
2719
        this.innerType = options.innerType;
48✔
2720
        this.location = util.createBoundingLocation(
48✔
2721
            this.innerType,
2722
            this.tokens.leftBracket,
2723
            this.tokens.rightBracket
2724
        );
2725
    }
2726

2727
    public readonly tokens: {
2728
        readonly leftBracket?: Token;
2729
        readonly rightBracket?: Token;
2730
    };
2731

2732
    public readonly innerType: Expression;
2733

2734
    public readonly kind = AstNodeKind.TypedArrayExpression;
48✔
2735

2736
    public readonly location: Location;
2737

2738
    public transpile(state: BrsTranspileState): TranspileResult {
UNCOV
2739
        return [this.getType({ flags: SymbolTypeFlag.typetime }).toTypeString()];
×
2740
    }
2741

2742
    public walk(visitor: WalkVisitor, options: WalkOptions) {
2743
        if (options.walkMode & InternalWalkMode.walkExpressions) {
221!
2744
            walk(this, 'innerType', visitor, options);
221✔
2745
        }
2746
    }
2747

2748
    public getType(options: GetTypeOptions): BscType {
2749
        return new ArrayType(this.innerType.getType(options));
250✔
2750
    }
2751

2752
    public clone() {
UNCOV
2753
        return this.finalizeClone(
×
2754
            new TypedArrayExpression({
2755
                innerType: this.innerType?.clone(),
×
2756
                leftBracket: util.cloneToken(this.tokens.leftBracket),
2757
                rightBracket: util.cloneToken(this.tokens.rightBracket)
2758
            }),
2759
            ['innerType']
2760
        );
2761
    }
2762
}
2763

2764
export class InlineInterfaceExpression extends Expression {
1✔
2765
    constructor(options: {
2766
        open?: Token;
2767
        members: InlineInterfaceMemberExpression[];
2768
        close?: Token;
2769
    }) {
2770
        super();
61✔
2771
        this.tokens = {
61✔
2772
            open: options.open,
2773
            close: options.close
2774
        };
2775
        this.members = options.members;
61✔
2776
        this.location = util.createBoundingLocation(
61✔
2777
            this.tokens.open,
2778
            ...this.members,
2779
            this.tokens.close
2780
        );
2781
    }
2782

2783
    public readonly tokens: {
2784
        readonly open?: Token;
2785
        readonly close?: Token;
2786
    };
2787

2788
    public readonly members: InlineInterfaceMemberExpression[];
2789

2790
    public readonly kind = AstNodeKind.InlineInterfaceExpression;
61✔
2791

2792
    public readonly location: Location;
2793

2794
    get leadingTrivia(): Token[] {
2795
        return this.tokens.open?.leadingTrivia;
269!
2796
    }
2797

2798
    public transpile(state: BrsTranspileState): TranspileResult {
UNCOV
2799
        return [this.getType({ flags: SymbolTypeFlag.typetime }).toTypeString()];
×
2800
    }
2801

2802
    public walk(visitor: WalkVisitor, options: WalkOptions) {
2803
        if (options.walkMode & InternalWalkMode.walkExpressions) {
240!
2804
            walkArray(this.members, visitor, options, this);
240✔
2805
        }
2806
    }
2807

2808
    public getType(options: GetTypeOptions): BscType {
2809
        const resultType = new InlineInterfaceType();
232✔
2810
        for (const member of this.members) {
232✔
2811
            let memberName = member.tokens?.name?.text ?? '';
244!
2812
            if (member.tokens.name.kind === TokenKind.StringLiteral) {
244✔
2813
                memberName = memberName.replace(/"/g, ''); // remove quotes if it was a stringLiteral
7✔
2814
            }
2815
            if (memberName) {
244!
2816
                const memberType = member?.getType({ ...options, typeChain: undefined }); // no typechain info needed
244!
2817
                let flag = SymbolTypeFlag.runtime;
244✔
2818
                if (member.isOptional) {
244!
UNCOV
2819
                    flag |= SymbolTypeFlag.optional;
×
2820
                }
2821
                resultType.addMember(memberName, { definingNode: member }, memberType, flag);
244✔
2822
            }
2823
        }
2824
        return resultType;
232✔
2825
    }
2826

2827
    public clone() {
UNCOV
2828
        return this.finalizeClone(
×
2829
            new InlineInterfaceExpression({
2830
                open: util.cloneToken(this.tokens.open),
2831
                members: this.members?.map(x => x?.clone()),
×
2832
                close: util.cloneToken(this.tokens.close)
2833
            }),
2834
            ['members']
2835
        );
2836
    }
2837
}
2838

2839
export class InlineInterfaceMemberExpression extends Expression {
1✔
2840
    constructor(options: {
2841
        optional?: Token;
2842
        name: Token;
2843
        as?: Token;
2844
        typeExpression?: TypeExpression;
2845
    }) {
2846
        super();
70✔
2847
        this.tokens = {
70✔
2848
            name: options.name,
2849
            as: options.as,
2850
            optional: options.optional
2851
        };
2852
        this.typeExpression = options.typeExpression;
70✔
2853
        this.location = util.createBoundingLocation(
70✔
2854
            this.tokens.optional,
2855
            this.tokens.name,
2856
            this.tokens.as,
2857
            this.typeExpression
2858
        );
2859
    }
2860

2861
    public readonly kind = AstNodeKind.InlineInterfaceMemberExpression;
70✔
2862

2863
    readonly tokens: {
2864
        readonly optional?: Token;
2865
        readonly name: Token;
2866
        readonly as?: Token;
2867
    };
2868

2869
    public readonly typeExpression?: TypeExpression;
2870

2871
    public readonly location: Location;
2872

2873
    get leadingTrivia(): Token[] {
2874
        return this.tokens.optional?.leadingTrivia ?? this.tokens.name.leadingTrivia;
194!
2875
    }
2876

2877
    public transpile(state: BrsTranspileState): TranspileResult {
UNCOV
2878
        throw new Error('Method not implemented.');
×
2879
    }
2880

2881
    public walk(visitor: WalkVisitor, options: WalkOptions) {
2882
        if (options.walkMode & InternalWalkMode.walkExpressions) {
268!
2883
            walk(this, 'typeExpression', visitor, options);
268✔
2884
        }
2885
    }
2886

2887
    public getType(options: GetTypeOptions): BscType {
2888
        return this.typeExpression?.getType(options) ?? DynamicType.instance;
246✔
2889
    }
2890

2891
    public get isOptional() {
2892
        return !!this.tokens.optional;
245✔
2893
    }
2894

2895
    public clone() {
UNCOV
2896
        return this.finalizeClone(
×
2897
            new InlineInterfaceMemberExpression({
2898
                typeExpression: this.typeExpression?.clone(),
×
2899
                as: util.cloneToken(this.tokens.as),
2900
                name: util.cloneToken(this.tokens.name),
2901
                optional: util.cloneToken(this.tokens.optional)
2902
            }),
2903
            ['typeExpression']
2904
        );
2905
    }
2906
}
2907

2908
export class TypedFunctionTypeExpression extends Expression {
1✔
2909
    constructor(options: {
2910
        functionType?: Token;
2911
        leftParen?: Token;
2912
        params?: FunctionParameterExpression[];
2913
        rightParen?: Token;
2914
        as?: Token;
2915
        returnType?: TypeExpression;
2916

2917
    }) {
2918
        super();
44✔
2919
        this.tokens = {
44✔
2920
            functionType: options.functionType,
2921
            leftParen: options.leftParen,
2922
            rightParen: options.rightParen,
2923
            as: options.as
2924
        };
2925
        this.params = options.params;
44✔
2926
        this.returnType = options.returnType;
44✔
2927
        this.location = util.createBoundingLocation(
44✔
2928
            this.tokens.functionType,
2929
            this.tokens.leftParen,
2930
            ...this.params,
2931
            this.tokens.rightParen,
2932
            this.tokens.as,
2933
            this.returnType
2934
        );
2935
    }
2936

2937
    public readonly kind = AstNodeKind.TypedFunctionTypeExpression;
44✔
2938

2939
    public readonly tokens: {
2940
        readonly functionType?: Token;
2941
        readonly leftParen?: Token;
2942
        readonly rightParen?: Token;
2943
        readonly as?: Token;
2944
    };
2945

2946
    public readonly params: FunctionParameterExpression[];
2947
    public readonly returnType?: TypeExpression;
2948

2949
    public readonly location: Location;
2950

2951
    public transpile(state: BrsTranspileState): TranspileResult {
NEW
2952
        return [this.getType({ flags: SymbolTypeFlag.typetime }).toTypeString()];
×
2953
    }
2954

2955
    public walk(visitor: WalkVisitor, options: WalkOptions) {
2956
        if (options.walkMode & InternalWalkMode.walkExpressions) {
181!
2957
            walkArray(this.params, visitor, options, this);
181✔
2958
            walk(this, 'returnType', visitor, options);
181✔
2959
        }
2960
    }
2961

2962
    public getType(options: GetTypeOptions): BscType {
2963
        const returnType = this.returnType?.getType({ ...options, typeChain: undefined }) ?? DynamicType.instance;
171!
2964
        const functionType = new TypedFunctionType(returnType);
171✔
2965
        for (const param of this.params) {
171✔
2966
            functionType.addParameter(param.tokens.name.text, param.getType({ ...options, typeChain: undefined }), !!param.defaultValue);
169✔
2967
        }
2968
        functionType.setSub(this.tokens.functionType?.kind === TokenKind.Sub);
171!
2969

2970
        options.typeChain?.push(new TypeChainEntry({
171✔
2971
            name: '',
2972
            type: functionType,
2973
            astNode: this,
2974
            data: options.data
2975
        }));
2976

2977
        return functionType;
171✔
2978
    }
2979

2980
    public clone() {
NEW
2981
        return this.finalizeClone(
×
2982
            new TypedFunctionTypeExpression({
2983
                functionType: util.cloneToken(this.tokens.functionType),
2984
                leftParen: util.cloneToken(this.tokens.leftParen),
NEW
2985
                params: this.params?.map(x => x?.clone()),
×
2986
                rightParen: util.cloneToken(this.tokens.rightParen),
2987
                as: util.cloneToken(this.tokens.as),
2988
                returnType: this.returnType?.clone()
×
2989
            }),
2990
            ['params', 'returnType']
2991
        );
2992
    }
2993
}
2994

2995

2996
/**
2997
 * A list of names of functions that are restricted from being stored to a
2998
 * variable, property, or passed as an argument. (i.e. `type` or `createobject`).
2999
 * Names are stored in lower case.
3000
 */
3001
const nonReferenceableFunctions = [
1✔
3002
    'createobject',
3003
    'type',
3004
    'getglobalaa',
3005
    'box',
3006
    'run',
3007
    'eval',
3008
    'getlastruncompileerror',
3009
    'getlastrunruntimeerror',
3010
    'tab',
3011
    'pos'
3012
];
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