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

rokucommunity / brighterscript / #15038

17 Dec 2025 01:49PM UTC coverage: 87.035% (+0.01%) from 87.023%
#15038

push

web-flow
Merge 723b94596 into 2ea4d2108

14488 of 17585 branches covered (82.39%)

Branch coverage included in aggregate %.

161 of 231 new or added lines in 11 files covered. (69.7%)

134 existing lines in 8 files now uncovered.

15231 of 16561 relevant lines covered (91.97%)

24122.14 hits per line

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

90.49
/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, 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,698✔
49
        this.tokens = {
3,698✔
50
            operator: options.operator
51
        };
52
        this.left = options.left;
3,698✔
53
        this.right = options.right;
3,698✔
54
        this.location = util.createBoundingLocation(this.left, this.tokens.operator, this.right);
3,698✔
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,698✔
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) {
15,831!
79
            walk(this, 'left', visitor, options);
15,831✔
80
            walk(this, 'right', visitor, options);
15,831✔
81
        }
82
    }
83

84

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

105
    get leadingTrivia(): Token[] {
106
        return this.left.leadingTrivia;
2,268✔
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,785✔
138
        this.tokens = {
2,785✔
139
            openingParen: options.openingParen,
140
            closingParen: options.closingParen
141
        };
142
        this.callee = options.callee;
2,785✔
143
        this.args = options.args ?? [];
2,785✔
144
        this.location = util.createBoundingLocation(this.callee, this.tokens.openingParen, ...this.args ?? [], this.tokens.closingParen);
2,785!
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,785✔
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,431!
194
            walk(this, 'callee', visitor, options);
13,431✔
195
            walkArray(this.args, visitor, options, this);
13,431✔
196
        }
197
    }
198

199
    getType(options: GetTypeOptions) {
200
        const calleeType = this.callee.getType(options);
1,084✔
201
        if (options.ignoreCall) {
1,084!
UNCOV
202
            return calleeType;
×
203
        }
204
        if (isNewExpression(this.parent)) {
1,084✔
205
            return calleeType;
348✔
206
        }
207

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

232
    get leadingTrivia(): Token[] {
233
        return this.callee.leadingTrivia;
9,813✔
234
    }
235

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

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

272
        if (this.body) {
4,378✔
273
            this.body.parent = this;
4,377✔
274
        }
275
        this.symbolTable = new SymbolTable('FunctionExpression', () => this.parent?.getSymbolTable());
49,332!
276
    }
277

278
    public readonly kind = AstNodeKind.FunctionExpression;
4,378✔
279

280
    readonly parameters: FunctionParameterExpression[];
281
    public readonly body: Block;
282
    public readonly returnTypeExpression?: TypeExpression;
283

284
    readonly tokens: {
285
        readonly functionType?: Token;
286
        readonly endFunctionType?: Token;
287
        readonly leftParen?: Token;
288
        readonly rightParen?: Token;
289
        readonly as?: Token;
290
    };
291

292
    public get leadingTrivia(): Token[] {
293
        return this.tokens.functionType?.leadingTrivia;
32,794✔
294
    }
295

296
    public get endTrivia(): Token[] {
297
        return this.tokens.endFunctionType?.leadingTrivia;
20!
298
    }
299

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

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

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

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

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

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

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

425
        const docs = brsDocParser.parseNode(this.findAncestor(isFunctionStatement));
4,962✔
426

427
        returnType = util.chooseTypeFromCodeOrDocComment(
4,962✔
428
            this.returnTypeExpression?.getType({ ...options, typeChain: undefined }),
14,886✔
UNCOV
429
            docs.getReturnBscType({ ...options, tableProvider: () => this.getSymbolTable() }),
×
430
            options
431
        );
432

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

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

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

479

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

488

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

495
        return false;
23✔
496
    }
497

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

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

533
    public readonly kind = AstNodeKind.FunctionParameterExpression;
3,440✔
534

535
    readonly tokens: {
536
        readonly name: Identifier;
537
        readonly equals?: Token;
538
        readonly as?: Token;
539
    };
540

541
    public readonly defaultValue?: Expression;
542
    public readonly typeExpression?: TypeExpression;
543

544
    public getType(options: GetTypeOptions) {
545
        const docs = brsDocParser.parseNode(this.findAncestor(isFunctionStatement));
6,356✔
546
        const paramName = this.tokens.name.text;
6,356✔
547

548
        let paramTypeFromCode = this.typeExpression?.getType({ ...options, flags: SymbolTypeFlag.typetime, typeChain: undefined }) ??
6,356✔
549
            util.getDefaultTypeFromValueType(this.defaultValue?.getType({ ...options, flags: SymbolTypeFlag.runtime, typeChain: undefined }));
8,934✔
550
        if (isInvalidType(paramTypeFromCode) || isVoidType(paramTypeFromCode)) {
6,356✔
551
            paramTypeFromCode = undefined;
25✔
552
        }
553
        const paramTypeFromDoc = docs.getParamBscType(paramName, { ...options, fullName: paramName, typeChain: undefined, tableProvider: () => this.getSymbolTable() });
6,356✔
554
        const paramType = util.chooseTypeFromCodeOrDocComment(paramTypeFromCode, paramTypeFromDoc, options) ?? DynamicType.instance;
6,356✔
555

556
        const docDescription = docs.getParam(paramName)?.description;
6,356✔
557
        if (docDescription) {
6,356✔
558
            options.data = options.data ?? {};
26✔
559
            options.data.description = docDescription;
26✔
560
        }
561
        options.typeChain?.push(new TypeChainEntry({ name: paramName, type: paramType, data: options.data, astNode: this }));
6,356✔
562
        return paramType;
6,356✔
563
    }
564

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

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

595
        return result;
2,451✔
596
    }
597

598
    public getTypedef(state: BrsTranspileState): TranspileResult {
599
        const results = [this.tokens.name.text] as TranspileResult;
82✔
600

601
        if (this.defaultValue) {
82✔
602
            results.push(' = ', ...(this.defaultValue.getTypedef(state) ?? this.defaultValue.transpile(state)));
3!
603
        }
604

605
        if (this.tokens.as) {
82✔
606
            results.push(' as ');
6✔
607

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

615
        return results;
82✔
616
    }
617

618
    walk(visitor: WalkVisitor, options: WalkOptions) {
619
        // eslint-disable-next-line no-bitwise
620
        if (options.walkMode & InternalWalkMode.walkExpressions) {
16,702!
621
            walk(this, 'defaultValue', visitor, options);
16,702✔
622
            walk(this, 'typeExpression', visitor, options);
16,702✔
623
        }
624
    }
625

626
    get leadingTrivia(): Token[] {
627
        return this.tokens.name.leadingTrivia;
5,319✔
628
    }
629

630
    public clone() {
631
        return this.finalizeClone(
14✔
632
            new FunctionParameterExpression({
633
                name: util.cloneToken(this.tokens.name),
634
                as: util.cloneToken(this.tokens.as),
635
                typeExpression: this.typeExpression?.clone(),
42✔
636
                equals: util.cloneToken(this.tokens.equals),
637
                defaultValue: this.defaultValue?.clone()
42✔
638
            }),
639
            ['typeExpression', 'defaultValue']
640
        );
641
    }
642
}
643

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

660
        this.location = util.createBoundingLocation(this.obj, this.tokens.dot, this.tokens.name);
3,124✔
661
    }
662

663
    readonly tokens: {
664
        readonly name: Identifier;
665
        readonly dot?: Token;
666
    };
667
    readonly obj: Expression;
668

669
    public readonly kind = AstNodeKind.DottedGetExpression;
3,124✔
670

671
    public readonly location: Location | undefined;
672

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

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

698
    walk(visitor: WalkVisitor, options: WalkOptions) {
699
        if (options.walkMode & InternalWalkMode.walkExpressions) {
13,165!
700
            walk(this, 'obj', visitor, options);
13,165✔
701
        }
702
    }
703

704
    getType(options: GetTypeOptions) {
705
        const objType = this.obj?.getType(options);
7,346!
706
        let result = objType?.getMemberType(this.tokens.name?.text, options);
7,346!
707

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

730
    getName(parseMode: ParseMode) {
731
        return util.getAllDottedGetPartsAsString(this, parseMode);
37✔
732
    }
733

734
    get leadingTrivia(): Token[] {
735
        return this.obj.leadingTrivia;
16,540✔
736
    }
737

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

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

765
    public readonly kind = AstNodeKind.XmlAttributeGetExpression;
14✔
766

767
    public readonly tokens: {
768
        name: Identifier;
769
        at?: Token;
770
    };
771

772
    public readonly obj: Expression;
773

774
    public readonly location: Location | undefined;
775

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

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

790
    get leadingTrivia(): Token[] {
791
        return this.obj.leadingTrivia;
21✔
792
    }
793

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

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

835
    public readonly kind = AstNodeKind.IndexedGetExpression;
168✔
836

837
    public readonly obj: Expression;
838
    public readonly indexes: Expression[];
839

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

849
    public readonly location: Location | undefined;
850

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

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

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

890
    get leadingTrivia(): Token[] {
891
        return this.obj.leadingTrivia;
1,071✔
892
    }
893

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

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

923
    public readonly tokens: {
924
        readonly leftParen?: Token;
925
        readonly rightParen?: Token;
926
    };
927
    public readonly expression: Expression;
928

929
    public readonly kind = AstNodeKind.GroupingExpression;
76✔
930

931
    public readonly location: Location | undefined;
932

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

944
    walk(visitor: WalkVisitor, options: WalkOptions) {
945
        if (options.walkMode & InternalWalkMode.walkExpressions) {
266!
946
            walk(this, 'expression', visitor, options);
266✔
947
        }
948
    }
949

950
    getType(options: GetTypeOptions) {
951
        return this.expression.getType(options);
102✔
952
    }
953

954
    get leadingTrivia(): Token[] {
955
        return this.tokens.leftParen?.leadingTrivia;
379!
956
    }
957

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

970
export class LiteralExpression extends Expression {
1✔
971
    constructor(options: {
972
        value: Token;
973
    }) {
974
        super();
8,559✔
975
        this.tokens = {
8,559✔
976
            value: options.value
977
        };
978
    }
979

980
    public readonly tokens: {
981
        readonly value: Token;
982
    };
983

984
    public readonly kind = AstNodeKind.LiteralExpression;
8,559✔
985

986
    public get location() {
987
        return this.tokens.value.location;
24,407✔
988
    }
989

990
    public getType(options?: GetTypeOptions) {
991
        return util.tokenToBscType(this.tokens.value);
3,855✔
992
    }
993

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

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

1010
        return [
5,140✔
1011
            state.transpileToken({ ...this.tokens.value, text: text })
1012
        ];
1013
    }
1014

1015
    walk(visitor: WalkVisitor, options: WalkOptions) {
1016
        //nothing to walk
1017
    }
1018

1019
    get leadingTrivia(): Token[] {
1020
        return this.tokens.value.leadingTrivia;
13,891✔
1021
    }
1022

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

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

1047
    public readonly tokens: {
1048
        readonly separator: PrintSeparatorToken;
1049
    };
1050

1051
    public readonly kind = AstNodeKind.PrintSeparatorExpression;
49✔
1052

1053
    public location: Location;
1054

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

1062
    walk(visitor: WalkVisitor, options: WalkOptions) {
1063
        //nothing to walk
1064
    }
1065

1066
    get leadingTrivia(): Token[] {
1067
        return this.tokens.separator.leadingTrivia;
194✔
1068
    }
1069

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

1077

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

1091
    public readonly kind = AstNodeKind.EscapedCharCodeLiteralExpression;
37✔
1092

1093
    public readonly tokens: {
1094
        readonly value: Token & { charCode: number };
1095
    };
1096

1097
    public readonly location: Location;
1098

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

1105
    walk(visitor: WalkVisitor, options: WalkOptions) {
1106
        //nothing to walk
1107
    }
1108

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

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

1133
    public readonly elements: Array<Expression>;
1134

1135
    public readonly tokens: {
1136
        readonly open?: Token;
1137
        readonly close?: Token;
1138
    };
1139

1140
    public readonly kind = AstNodeKind.ArrayLiteralExpression;
174✔
1141

1142
    public readonly location: Location | undefined;
1143

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

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

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

1173
        return result;
63✔
1174
    }
1175

1176
    walk(visitor: WalkVisitor, options: WalkOptions) {
1177
        if (options.walkMode & InternalWalkMode.walkExpressions) {
914!
1178
            walkArray(this.elements, visitor, options, this);
914✔
1179
        }
1180
    }
1181

1182
    getType(options: GetTypeOptions): BscType {
1183
        const innerTypes = this.elements.map(expr => expr.getType(options));
274✔
1184
        return new ArrayType(...innerTypes);
198✔
1185
    }
1186
    get leadingTrivia(): Token[] {
1187
        return this.tokens.open?.leadingTrivia;
602!
1188
    }
1189

1190
    get endTrivia(): Token[] {
1191
        return this.tokens.close?.leadingTrivia;
2!
1192
    }
1193

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

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

1224
    public readonly kind = AstNodeKind.AAMemberExpression;
332✔
1225

1226
    public readonly location: Location | undefined;
1227

1228
    public readonly tokens: {
1229
        readonly key: Token;
1230
        readonly colon?: Token;
1231
        readonly comma?: Token;
1232
    };
1233

1234
    /** The expression evaluated to determine the member's initial value. */
1235
    public readonly value: Expression;
1236

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

1242
    walk(visitor: WalkVisitor, options: WalkOptions) {
1243
        walk(this, 'value', visitor, options);
1,310✔
1244
    }
1245

1246
    getType(options: GetTypeOptions): BscType {
1247
        return this.value.getType(options);
259✔
1248
    }
1249

1250
    get leadingTrivia(): Token[] {
1251
        return this.tokens.key.leadingTrivia;
987✔
1252
    }
1253

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

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

1281
    public readonly elements: Array<AAMemberExpression>;
1282
    public readonly tokens: {
1283
        readonly open?: Token;
1284
        readonly close?: Token;
1285
    };
1286

1287
    public readonly kind = AstNodeKind.AALiteralExpression;
320✔
1288

1289
    public readonly location: Location | undefined;
1290

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

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

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

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

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

1340
        return result;
76✔
1341
    }
1342

1343
    walk(visitor: WalkVisitor, options: WalkOptions) {
1344
        if (options.walkMode & InternalWalkMode.walkExpressions) {
1,342!
1345
            walkArray(this.elements, visitor, options, this);
1,342✔
1346
        }
1347
    }
1348

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

1366
    public get leadingTrivia(): Token[] {
1367
        return this.tokens.open?.leadingTrivia;
921!
1368
    }
1369

1370
    public get endTrivia(): Token[] {
1371
        return this.tokens.close?.leadingTrivia;
1!
1372
    }
1373

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

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

1399
    public readonly kind = AstNodeKind.UnaryExpression;
69✔
1400

1401
    public readonly location: Location | undefined;
1402

1403
    public readonly tokens: {
1404
        readonly operator: Token;
1405
    };
1406
    public readonly right: Expression;
1407

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

1418
        return [
12✔
1419
            state.transpileToken(this.tokens.operator),
1420
            separatingWhitespace,
1421
            ...this.right.transpile(state)
1422
        ];
1423
    }
1424

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

1431
    getType(options: GetTypeOptions): BscType {
1432
        return util.unaryOperatorResultType(this.tokens.operator, this.right.getType(options));
48✔
1433
    }
1434

1435
    public get leadingTrivia(): Token[] {
1436
        return this.tokens.operator.leadingTrivia;
202✔
1437
    }
1438

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

1450
export class VariableExpression extends Expression {
1✔
1451
    constructor(options: {
1452
        name: Identifier;
1453
    }) {
1454
        super();
12,723✔
1455
        this.tokens = {
12,723✔
1456
            name: options.name
1457
        };
1458
        this.location = util.cloneLocation(this.tokens.name?.location);
12,723!
1459
    }
1460

1461
    public readonly tokens: {
1462
        readonly name: Identifier;
1463
    };
1464

1465
    public readonly kind = AstNodeKind.VariableExpression;
12,723✔
1466

1467
    public readonly location: Location;
1468

1469
    public getName(parseMode?: ParseMode) {
1470
        return this.tokens.name.text;
28,706✔
1471
    }
1472

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

1496
    getTypedef(state: BrsTranspileState) {
1497
        return [
1✔
1498
            state.transpileToken(this.tokens.name)
1499
        ];
1500
    }
1501

1502
    walk(visitor: WalkVisitor, options: WalkOptions) {
1503
        //nothing to walk
1504
    }
1505

1506

1507
    getType(options: GetTypeOptions) {
1508
        let resultType: BscType = util.tokenToBscType(this.tokens.name);
28,552✔
1509
        const nameKey = this.getName();
28,552✔
1510
        if (!resultType) {
28,552✔
1511
            const symbolTable = this.getSymbolTable();
21,536✔
1512
            resultType = symbolTable?.getSymbolType(nameKey, {
21,536!
1513
                ...options,
1514
                statementIndex: this.statementIndex,
1515
                fullName: nameKey,
1516
                tableProvider: () => this.getSymbolTable()
58,163✔
1517
            });
1518

1519
            if (util.isClassUsedAsFunction(resultType, this, options)) {
21,536✔
1520
                resultType = FunctionType.instance;
20✔
1521
            }
1522

1523
        }
1524
        options?.typeChain?.push(new TypeChainEntry({ name: nameKey, type: resultType, data: options.data, astNode: this }));
28,552!
1525
        return resultType;
28,552✔
1526
    }
1527

1528
    get leadingTrivia(): Token[] {
1529
        return this.tokens.name.leadingTrivia;
45,070✔
1530
    }
1531

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

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

1552
    public readonly location: Location;
1553

1554
    public readonly kind = AstNodeKind.SourceLiteralExpression;
37✔
1555

1556
    public readonly tokens: {
1557
        readonly value: Token;
1558
    };
1559

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

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

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

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

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

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

1657
        }
1658
        return [
31✔
1659
            state.sourceNode(this, text)
1660
        ];
1661
    }
1662

1663
    walk(visitor: WalkVisitor, options: WalkOptions) {
1664
        //nothing to walk
1665
    }
1666

1667
    get leadingTrivia(): Token[] {
1668
        return this.tokens.value.leadingTrivia;
200✔
1669
    }
1670

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

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

1698
    public readonly kind = AstNodeKind.NewExpression;
140✔
1699

1700
    public readonly location: Location | undefined;
1701

1702
    public readonly tokens: {
1703
        readonly new?: Token;
1704
    };
1705
    public readonly call: CallExpression;
1706

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

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

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

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

1745
    get leadingTrivia(): Token[] {
1746
        return this.tokens.new.leadingTrivia;
611✔
1747
    }
1748

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

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

1779
        this.location = util.createBoundingLocation(
78✔
1780
            this.callee,
1781
            this.tokens.operator,
1782
            this.tokens.methodName,
1783
            this.tokens.openingParen,
1784
            ...this.args ?? [],
234!
1785
            this.tokens.closingParen
1786
        );
1787
    }
1788

1789
    public readonly callee: Expression;
1790
    public readonly args: Expression[];
1791

1792
    public readonly tokens: {
1793
        readonly operator: Token;
1794
        readonly methodName: Identifier;
1795
        readonly openingParen?: Token;
1796
        readonly closingParen?: Token;
1797
    };
1798

1799
    public readonly kind = AstNodeKind.CallfuncExpression;
78✔
1800

1801
    public readonly location: Location | undefined;
1802

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

1827
        result.push(
9✔
1828
            state.transpileToken(this.tokens.closingParen, ')')
1829
        );
1830
        return result;
9✔
1831
    }
1832

1833
    walk(visitor: WalkVisitor, options: WalkOptions) {
1834
        if (options.walkMode & InternalWalkMode.walkExpressions) {
424!
1835
            walk(this, 'callee', visitor, options);
424✔
1836
            walkArray(this.args, visitor, options, this);
424✔
1837
        }
1838
    }
1839

1840
    getType(options: GetTypeOptions) {
1841
        const result = util.getCallFuncType(this, this.tokens.methodName, options) ?? DynamicType.instance;
28!
1842
        return result;
28✔
1843
    }
1844

1845
    get leadingTrivia(): Token[] {
1846
        return this.callee.leadingTrivia;
670✔
1847
    }
1848

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

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

1879
    public readonly expressions: Array<LiteralExpression | EscapedCharCodeLiteralExpression>;
1880
    public readonly kind = AstNodeKind.TemplateStringQuasiExpression;
114✔
1881

1882
    readonly location: Location | undefined;
1883

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

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

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

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

1940
    public readonly kind = AstNodeKind.TemplateStringExpression;
53✔
1941

1942
    public readonly tokens: {
1943
        readonly openingBacktick?: Token;
1944
        readonly closingBacktick?: Token;
1945
    };
1946
    public readonly quasis: TemplateStringQuasiExpression[];
1947
    public readonly expressions: Expression[];
1948

1949
    public readonly location: Location | undefined;
1950

1951
    public getType(options: GetTypeOptions) {
1952
        return StringType.instance;
34✔
1953
    }
1954

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

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

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

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

2006
        return result;
18✔
2007
    }
2008

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

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

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

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

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

2062
    public readonly kind = AstNodeKind.TaggedTemplateStringExpression;
12✔
2063

2064
    public readonly tokens: {
2065
        readonly tagName: Identifier;
2066
        readonly openingBacktick?: Token;
2067
        readonly closingBacktick?: Token;
2068
    };
2069

2070
    public readonly quasis: TemplateStringQuasiExpression[];
2071
    public readonly expressions: Expression[];
2072

2073
    public readonly location: Location | undefined;
2074

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

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

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

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

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

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

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

2160
    public readonly kind = AstNodeKind.AnnotationExpression;
83✔
2161

2162
    public readonly tokens: {
2163
        readonly at: Token;
2164
        readonly name: Token;
2165
    };
2166

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

2175
    public readonly name: string;
2176

2177
    public call: CallExpression;
2178

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

2190
    public get leadingTrivia(): Token[] {
2191
        return this.tokens.at?.leadingTrivia;
50!
2192
    }
2193

2194
    transpile(state: BrsTranspileState) {
2195
        //transpile only our leading comments
2196
        return state.transpileComments(this.leadingTrivia);
16✔
2197
    }
2198

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

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

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

2246
    public readonly kind = AstNodeKind.TernaryExpression;
100✔
2247

2248
    public readonly location: Location | undefined;
2249

2250
    public readonly tokens: {
2251
        readonly questionMark?: Token;
2252
        readonly colon?: Token;
2253
    };
2254

2255
    public readonly test: Expression;
2256
    public readonly consequent?: Expression;
2257
    public readonly alternate?: Expression;
2258

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

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

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

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

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

2332
    get leadingTrivia(): Token[] {
2333
        return this.test.leadingTrivia;
258✔
2334
    }
2335

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

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

2369
    public readonly kind = AstNodeKind.NullCoalescingExpression;
37✔
2370

2371
    public readonly location: Location | undefined;
2372

2373
    public readonly tokens: {
2374
        readonly questionQuestion?: Token;
2375
    };
2376

2377
    public readonly consequent: Expression;
2378
    public readonly alternate: Expression;
2379

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

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

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

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

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

2452
    get leadingTrivia(): Token[] {
2453
        return this.consequent.leadingTrivia;
58✔
2454
    }
2455

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

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

2478
    public readonly kind = AstNodeKind.RegexLiteralExpression;
46✔
2479
    public readonly tokens: {
2480
        readonly regexLiteral: Token;
2481
    };
2482

2483
    public get location(): Location {
2484
        return this.tokens?.regexLiteral?.location;
150!
2485
    }
2486

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

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

2512
    walk(visitor: WalkVisitor, options: WalkOptions) {
2513
        //nothing to walk
2514
    }
2515

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

2524
    get leadingTrivia(): Token[] {
2525
        return this.tokens.regexLiteral?.leadingTrivia;
1,038!
2526
    }
2527
}
2528

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

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

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

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

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

2589
    public readonly kind = AstNodeKind.TypeExpression;
1,956✔
2590

2591
    /**
2592
     * The standard AST expression that represents the type for this TypeExpression.
2593
     */
2594
    public readonly expression: Expression;
2595

2596
    public readonly location: Location;
2597

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

2611
    public getType(options: GetTypeOptions): BscType {
2612
        return this.expression.getType({ ...options, flags: SymbolTypeFlag.typetime });
8,867✔
2613
    }
2614

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

2620
    getName(parseMode = ParseMode.BrighterScript): string {
271✔
2621
        //TODO: this may not support Complex Types, eg. generics or Unions
2622
        return util.getAllDottedGetPartsAsString(this.expression, parseMode);
273✔
2623
    }
2624

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

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

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

2659
    public readonly kind = AstNodeKind.TypecastExpression;
84✔
2660

2661
    public readonly obj: Expression;
2662

2663
    public readonly tokens: {
2664
        readonly as?: Token;
2665
    };
2666

2667
    public typeExpression?: TypeExpression;
2668

2669
    public readonly location: Location;
2670

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

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

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

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

2724
    public readonly tokens: {
2725
        readonly leftBracket?: Token;
2726
        readonly rightBracket?: Token;
2727
    };
2728

2729
    public readonly innerType: Expression;
2730

2731
    public readonly kind = AstNodeKind.TypedArrayExpression;
37✔
2732

2733
    public readonly location: Location;
2734

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

2739
    public walk(visitor: WalkVisitor, options: WalkOptions) {
2740
        if (options.walkMode & InternalWalkMode.walkExpressions) {
161!
2741
            walk(this, 'innerType', visitor, options);
161✔
2742
        }
2743
    }
2744

2745
    public getType(options: GetTypeOptions): BscType {
2746
        return new ArrayType(this.innerType.getType(options));
187✔
2747
    }
2748

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

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

2780
    public readonly tokens: {
2781
        readonly open?: Token;
2782
        readonly close?: Token;
2783
    };
2784

2785
    public readonly members: InlineInterfaceMemberExpression[];
2786

2787
    public readonly kind = AstNodeKind.InlineInterfaceExpression;
35✔
2788

2789
    public readonly location: Location;
2790

2791
    get leadingTrivia(): Token[] {
2792
        return this.tokens.open?.leadingTrivia;
95!
2793
    }
2794

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

2799
    public walk(visitor: WalkVisitor, options: WalkOptions) {
2800
        if (options.walkMode & InternalWalkMode.walkExpressions) {
119!
2801
            walkArray(this.members, visitor, options, this);
119✔
2802
        }
2803
    }
2804

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

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

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

2858
    public readonly kind = AstNodeKind.InlineInterfaceMemberExpression;
39✔
2859

2860
    readonly tokens: {
2861
        readonly optional?: Token;
2862
        readonly name: Token;
2863
        readonly as?: Token;
2864
    };
2865

2866
    public readonly typeExpression?: TypeExpression;
2867

2868
    public readonly location: Location;
2869

2870
    get leadingTrivia(): Token[] {
2871
        return this.tokens.optional?.leadingTrivia ?? this.tokens.name.leadingTrivia;
79!
2872
    }
2873

2874
    public transpile(state: BrsTranspileState): TranspileResult {
UNCOV
2875
        throw new Error('Method not implemented.');
×
2876
    }
2877

2878
    public walk(visitor: WalkVisitor, options: WalkOptions) {
2879
        if (options.walkMode & InternalWalkMode.walkExpressions) {
123!
2880
            walk(this, 'typeExpression', visitor, options);
123✔
2881
        }
2882
    }
2883

2884
    public getType(options: GetTypeOptions): BscType {
2885
        return this.typeExpression?.getType(options) ?? DynamicType.instance;
119✔
2886
    }
2887

2888
    public get isOptional() {
2889
        return !!this.tokens.optional;
118✔
2890
    }
2891

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

2905
/**
2906
 * A list of names of functions that are restricted from being stored to a
2907
 * variable, property, or passed as an argument. (i.e. `type` or `createobject`).
2908
 * Names are stored in lower case.
2909
 */
2910
const nonReferenceableFunctions = [
1✔
2911
    'createobject',
2912
    'type',
2913
    'getglobalaa',
2914
    'box',
2915
    'run',
2916
    'eval',
2917
    'getlastruncompileerror',
2918
    'getlastrunruntimeerror',
2919
    'tab',
2920
    'pos'
2921
];
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