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

rokucommunity / brighterscript / #15245

12 Mar 2026 12:50PM UTC coverage: 88.972% (-0.02%) from 88.989%
#15245

push

web-flow
Merge aa08edc30 into 3cb920f6e

7892 of 9354 branches covered (84.37%)

Branch coverage included in aggregate %.

0 of 2 new or added lines in 2 files covered. (0.0%)

1 existing line in 1 file now uncovered.

10115 of 10885 relevant lines covered (92.93%)

1921.44 hits per line

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

92.25
/src/parser/Expression.ts
1
/* eslint-disable no-bitwise */
2
import type { Token, Identifier } from '../lexer/Token';
3
import { TokenKind } from '../lexer/TokenKind';
1✔
4
import type { Block, CommentStatement, FunctionStatement, NamespaceStatement } from './Statement';
5
import type { Range } from 'vscode-languageserver';
6
import util from '../util';
1✔
7
import type { BrsTranspileState } from './BrsTranspileState';
8
import { ParseMode } from './Parser';
1✔
9
import * as fileUrl from 'file-url';
1✔
10
import type { WalkOptions, WalkVisitor } from '../astUtils/visitors';
11
import { createVisitor, WalkMode } from '../astUtils/visitors';
1✔
12
import { walk, InternalWalkMode, walkArray } from '../astUtils/visitors';
1✔
13
import { isAALiteralExpression, isArrayLiteralExpression, isCallExpression, isCallfuncExpression, isCommentStatement, isDottedGetExpression, isEscapedCharCodeLiteralExpression, isFunctionExpression, isFunctionStatement, isIntegerType, isLiteralBoolean, isLiteralExpression, isLiteralNumber, isLiteralString, isLongIntegerType, isMethodStatement, isNamespaceStatement, isNewExpression, isStringType, isTemplateStringExpression, isTypeCastExpression, isUnaryExpression, isVariableExpression, isVoidType } from '../astUtils/reflection';
1✔
14
import type { TranspileResult, TypedefProvider } from '../interfaces';
15
import { VoidType } from '../types/VoidType';
1✔
16
import { DynamicType } from '../types/DynamicType';
1✔
17
import type { BscType } from '../types/BscType';
18
import { FunctionType } from '../types/FunctionType';
1✔
19
import type { AstNode } from './AstNode';
20
import { Expression } from './AstNode';
1✔
21
import { SymbolTable } from '../SymbolTable';
1✔
22
import { SourceNode } from 'source-map';
1✔
23

24
export type ExpressionVisitor = (expression: Expression, parent: Expression) => void;
25

26
export class BinaryExpression extends Expression {
1✔
27
    constructor(
28
        public left: Expression,
364✔
29
        public operator: Token,
364✔
30
        public right: Expression
364✔
31
    ) {
32
        super();
364✔
33
        this.range = util.createBoundingRange(this.left, this.operator, this.right);
364✔
34
    }
35

36
    public readonly range: Range | undefined;
37

38
    transpile(state: BrsTranspileState) {
39
        return [
159✔
40
            state.sourceNode(this.left, this.left.transpile(state)),
41
            ' ',
42
            state.transpileToken(this.operator),
43
            ' ',
44
            state.sourceNode(this.right, this.right.transpile(state))
45
        ];
46
    }
47

48
    walk(visitor: WalkVisitor, options: WalkOptions) {
49
        if (options.walkMode & InternalWalkMode.walkExpressions) {
714!
50
            walk(this, 'left', visitor, options);
714✔
51
            walk(this, 'right', visitor, options);
714✔
52
        }
53
    }
54

55
    public clone() {
56
        return this.finalizeClone(
3✔
57
            new BinaryExpression(
58
                this.left?.clone(),
9✔
59
                util.cloneToken(this.operator),
60
                this.right?.clone()
9✔
61
            ),
62
            ['left', 'right']
63
        );
64
    }
65
}
66

67
export class CallExpression extends Expression {
1✔
68
    /**
69
     * Number of parameters that can be defined on a function
70
     *
71
     * Prior to Roku OS 11.5, this was 32
72
     * As of Roku OS 11.5, this is 63
73
     */
74
    static MaximumArguments = 63;
1✔
75

76
    constructor(
77
        readonly callee: Expression,
712✔
78
        /**
79
         * Can either be `(`, or `?(` for optional chaining
80
         */
81
        readonly openingParen: Token,
712✔
82
        readonly closingParen: Token,
712✔
83
        readonly args: Expression[],
712✔
84
        unused?: any
85
    ) {
86
        super();
712✔
87
        this.range = util.createBoundingRange(this.callee, this.openingParen, ...args ?? [], this.closingParen);
712✔
88
    }
89

90
    public readonly range: Range | undefined;
91

92
    /**
93
     * Get the name of the wrapping namespace (if it exists)
94
     * @deprecated use `.findAncestor(isNamespaceStatement)` instead.
95
     */
96
    public get namespaceName() {
97
        return this.findAncestor<NamespaceStatement>(isNamespaceStatement)?.nameExpression;
×
98
    }
99

100
    transpile(state: BrsTranspileState, nameOverride?: string) {
101
        let result: TranspileResult = [];
252✔
102

103
        //transpile the name
104
        if (nameOverride) {
252✔
105
            result.push(state.sourceNode(this.callee, nameOverride));
9✔
106
        } else {
107
            result.push(...this.callee.transpile(state));
243✔
108
        }
109

110
        result.push(
252✔
111
            state.transpileToken(this.openingParen)
112
        );
113
        for (let i = 0; i < this.args.length; i++) {
252✔
114
            //add comma between args
115
            if (i > 0) {
142✔
116
                result.push(', ');
14✔
117
            }
118
            let arg = this.args[i];
142✔
119
            result.push(...arg.transpile(state));
142✔
120
        }
121
        if (this.closingParen) {
252!
122
            result.push(
252✔
123
                state.transpileToken(this.closingParen)
124
            );
125
        }
126
        return result;
252✔
127
    }
128

129
    walk(visitor: WalkVisitor, options: WalkOptions) {
130
        if (options.walkMode & InternalWalkMode.walkExpressions) {
1,734!
131
            walk(this, 'callee', visitor, options);
1,734✔
132
            walkArray(this.args, visitor, options, this);
1,734✔
133
        }
134
    }
135

136
    public clone() {
137
        return this.finalizeClone(
13✔
138
            new CallExpression(
139
                this.callee?.clone(),
39✔
140
                util.cloneToken(this.openingParen),
141
                util.cloneToken(this.closingParen),
142
                this.args?.map(e => e?.clone())
12✔
143
            ),
144
            ['callee', 'args']
145
        );
146
    }
147
}
148

149
export class FunctionExpression extends Expression implements TypedefProvider {
1✔
150
    constructor(
151
        readonly parameters: FunctionParameterExpression[],
1,994✔
152
        public body: Block,
1,994✔
153
        readonly functionType: Token | null,
1,994✔
154
        public end: Token,
1,994✔
155
        readonly leftParen: Token,
1,994✔
156
        readonly rightParen: Token,
1,994✔
157
        readonly asToken?: Token,
1,994✔
158
        readonly returnTypeToken?: Token
1,994✔
159
    ) {
160
        super();
1,994✔
161
        this.setReturnType(); // set the initial return type that we parse
1,994✔
162

163
        //if there's a body, and it doesn't have a SymbolTable, assign one
164
        if (this.body && !this.body.symbolTable) {
1,994✔
165
            this.body.symbolTable = new SymbolTable(`Function Body`);
144✔
166
        }
167
        this.symbolTable = new SymbolTable('FunctionExpression', () => this.parent?.getSymbolTable());
1,994!
168
    }
169

170
    /**
171
     * The type this function returns
172
     */
173
    public returnType: BscType;
174

175
    /**
176
     * Does this method require the return type to be present after transpile (useful for `as void` or the `as boolean` in `onKeyEvent`)
177
     */
178
    private requiresReturnType: boolean;
179

180
    /**
181
     * Get the name of the wrapping namespace (if it exists)
182
     * @deprecated use `.findAncestor(isNamespaceStatement)` instead.
183
     */
184
    public get namespaceName() {
185
        return this.findAncestor<NamespaceStatement>(isNamespaceStatement)?.nameExpression;
×
186
    }
187

188
    /**
189
     * Get the name of the wrapping namespace (if it exists)
190
     * @deprecated use `.findAncestor(isFunctionExpression)` instead.
191
     */
192
    public get parentFunction() {
193
        return this.findAncestor<FunctionExpression>(isFunctionExpression);
1,095✔
194
    }
195

196
    /**
197
     * The list of function calls that are declared within this function scope. This excludes CallExpressions
198
     * declared in child functions
199
     */
200
    public callExpressions = [] as CallExpression[];
1,994✔
201

202
    /**
203
     * If this function is part of a FunctionStatement, this will be set. Otherwise this will be undefined
204
     */
205
    public functionStatement?: FunctionStatement;
206

207
    /**
208
     * A list of all child functions declared directly within this function
209
     * @deprecated use `.walk(createVisitor({ FunctionExpression: ()=>{}), { walkMode: WalkMode.visitAllRecursive })` instead
210
     */
211
    public get childFunctionExpressions() {
212
        const expressions = [] as FunctionExpression[];
4✔
213
        this.walk(createVisitor({
4✔
214
            FunctionExpression: (expression) => {
215
                expressions.push(expression);
6✔
216
            }
217
        }), {
218
            walkMode: WalkMode.visitAllRecursive
219
        });
220
        return expressions;
4✔
221
    }
222

223
    /**
224
     * The range of the function, starting at the 'f' in function or 's' in sub (or the open paren if the keyword is missing),
225
     * and ending with the last n' in 'end function' or 'b' in 'end sub'
226
     */
227
    public get range() {
228
        return util.createBoundingRange(
5,117✔
229
            this.functionType, this.leftParen,
230
            ...this.parameters ?? [],
15,351✔
231
            this.rightParen,
232
            this.asToken,
233
            this.returnTypeToken,
234
            this.end
235
        );
236
    }
237

238
    transpile(state: BrsTranspileState, name?: Identifier, includeBody = true) {
377✔
239
        let results = [] as TranspileResult;
377✔
240
        //'function'|'sub'
241
        results.push(
377✔
242
            state.transpileToken(this.functionType!)
243
        );
244
        //functionName?
245
        if (name) {
377✔
246
            results.push(
372✔
247
                ' ',
248
                state.transpileToken(name)
249
            );
250
        }
251
        //leftParen
252
        results.push(
377✔
253
            state.transpileToken(this.leftParen)
254
        );
255
        //parameters
256
        for (let i = 0; i < this.parameters.length; i++) {
377✔
257
            let param = this.parameters[i];
125✔
258
            //add commas
259
            if (i > 0) {
125✔
260
                results.push(', ');
49✔
261
            }
262
            //add parameter
263
            results.push(param.transpile(state));
125✔
264
        }
265
        //right paren
266
        results.push(
377✔
267
            state.transpileToken(this.rightParen)
268
        );
269
        //as [Type]
270
        this.setReturnType(); // check one more time before transpile
377✔
271
        if (this.asToken && !(state.options.removeParameterTypes && !this.requiresReturnType)) {
377✔
272
            results.push(
39✔
273
                ' ',
274
                //as
275
                state.transpileToken(this.asToken),
276
                ' ',
277
                //return type
278
                state.sourceNode(this.returnTypeToken!, this.returnType.toTypeString())
279
            );
280
        }
281
        if (includeBody) {
377!
282
            state.lineage.unshift(this);
377✔
283
            let body = this.body.transpile(state);
377✔
284
            state.lineage.shift();
377✔
285
            results.push(...body);
377✔
286
        }
287
        results.push('\n');
377✔
288
        //'end sub'|'end function'
289
        results.push(
377✔
290
            state.indent(),
291
            state.transpileToken(this.end)
292
        );
293
        return results;
377✔
294
    }
295

296
    getTypedef(state: BrsTranspileState) {
297
        let results = [
34✔
298
            new SourceNode(1, 0, null, [
299
                //'function'|'sub'
300
                this.functionType?.text,
102!
301
                //functionName?
302
                ...(isFunctionStatement(this.parent) || isMethodStatement(this.parent) ? [' ', this.parent.name?.text ?? ''] : []),
295!
303
                //leftParen
304
                '(',
305
                //parameters
306
                ...(
307
                    this.parameters?.map((param, i) => ([
15!
308
                        //separating comma
309
                        i > 0 ? ', ' : '',
15✔
310
                        ...param.getTypedef(state)
311
                    ])) ?? []
34!
312
                ) as any,
313
                //right paren
314
                ')',
315
                //as <ReturnType>
316
                ...(this.asToken ? [
34✔
317
                    ' as ',
318
                    this.returnTypeToken?.text
15!
319
                ] : []),
320
                '\n',
321
                state.indent(),
322
                //'end sub'|'end function'
323
                this.end.text
324
            ])
325
        ];
326
        return results;
34✔
327
    }
328

329
    walk(visitor: WalkVisitor, options: WalkOptions) {
330
        if (options.walkMode & InternalWalkMode.walkExpressions) {
3,679!
331
            walkArray(this.parameters, visitor, options, this);
3,679✔
332

333
            //This is the core of full-program walking...it allows us to step into sub functions
334
            if (options.walkMode & InternalWalkMode.recurseChildFunctions) {
3,679!
335
                walk(this, 'body', visitor, options);
3,679✔
336
            }
337
        }
338
    }
339

340
    getFunctionType(): FunctionType {
341
        let functionType = new FunctionType(this.returnType);
1,855✔
342
        functionType.isSub = this.functionType?.text === 'sub';
1,855!
343
        for (let param of this.parameters) {
1,855✔
344
            functionType.addParameter(param.name.text, param.type, !!param.typeToken);
671✔
345
        }
346
        return functionType;
1,855✔
347
    }
348

349
    private setReturnType() {
350

351
        /**
352
         * RokuOS methods can be written several different ways:
353
         * 1. Function() : return withValue
354
         * 2. Function() as type : return withValue
355
         * 3. Function() as void : return
356
         *
357
         * 4. Sub() : return
358
         * 5. Sub () as void : return
359
         * 6. Sub() as type : return withValue
360
         *
361
         * Formats (1), (2), and (6) throw a compile error if there IS NOT a return value in the function body.
362
         * Formats (3), (4), and (5) throw a compile error if there IS a return value in the function body.
363
         *
364
         * 7. Additionally, as a special case, the OS requires that `onKeyEvent()` be defined with `as boolean`
365
         */
366

367
        const isSub = this.functionType?.text.toLowerCase() === 'sub';
2,371!
368

369
        if (this.returnTypeToken) {
2,371✔
370
            this.returnType = util.tokenToBscType(this.returnTypeToken);
160✔
371
        } else if (isSub) {
2,211✔
372
            this.returnType = new VoidType();
1,792✔
373
        } else {
374
            this.returnType = DynamicType.instance;
419✔
375
        }
376

377
        if ((isFunctionStatement(this.parent) || isMethodStatement(this.parent)) && this.parent?.name?.text.toLowerCase() === 'onkeyevent') {
2,371!
378
            // onKeyEvent() requires 'as Boolean' otherwise RokuOS throws errors
379
            this.requiresReturnType = true;
1✔
380
        } else if (isSub && !isVoidType(this.returnType)) { // format (6)
2,370✔
381
            this.requiresReturnType = true;
57✔
382
        } else if (this.returnTypeToken && isVoidType(this.returnType)) { // format (3)
2,313✔
383
            this.requiresReturnType = true;
17✔
384
        }
385
    }
386

387
    public clone() {
388
        const clone = this.finalizeClone(
106✔
389
            new FunctionExpression(
390
                this.parameters?.map(e => e?.clone()),
7✔
391
                this.body?.clone(),
318✔
392
                util.cloneToken(this.functionType),
393
                util.cloneToken(this.end),
394
                util.cloneToken(this.leftParen),
395
                util.cloneToken(this.rightParen),
396
                util.cloneToken(this.asToken),
397
                util.cloneToken(this.returnTypeToken)
398
            ),
399
            ['body']
400
        );
401

402
        //rebuild the .callExpressions list in the clone
403
        clone.body?.walk?.((node) => {
106✔
404
            if (isCallExpression(node) && !isNewExpression(node.parent)) {
201✔
405
                clone.callExpressions.push(node);
6✔
406
            }
407
        }, { walkMode: WalkMode.visitExpressions });
408
        return clone;
106✔
409
    }
410
}
411

412
export class FunctionParameterExpression extends Expression {
1✔
413
    constructor(
414
        public name: Identifier,
708✔
415
        public typeToken?: Token,
708✔
416
        public defaultValue?: Expression,
708✔
417
        public asToken?: Token
708✔
418
    ) {
419
        super();
708✔
420
        if (typeToken) {
708✔
421
            this.type = util.tokenToBscType(typeToken);
339✔
422
        } else {
423
            this.type = new DynamicType();
369✔
424
        }
425
    }
426

427
    public type: BscType;
428

429
    public get range(): Range | undefined {
430
        return util.createBoundingRange(
456✔
431
            this.name,
432
            this.asToken,
433
            this.typeToken,
434
            this.defaultValue
435
        );
436
    }
437

438
    public transpile(state: BrsTranspileState) {
439
        let result = [
142✔
440
            //name
441
            state.transpileToken(this.name)
442
        ] as any[];
443
        //default value
444
        if (this.defaultValue) {
142✔
445
            result.push(' = ');
9✔
446
            result.push(this.defaultValue.transpile(state));
9✔
447
        }
448
        //type declaration
449
        if (this.asToken && !state.options.removeParameterTypes) {
142✔
450
            result.push(' ');
91✔
451
            result.push(state.transpileToken(this.asToken));
91✔
452
            result.push(' ');
91✔
453
            result.push(state.sourceNode(this.typeToken!, this.type.toTypeString()));
91✔
454
        }
455

456
        return result;
142✔
457
    }
458

459
    public getTypedef(state: BrsTranspileState): TranspileResult {
460
        const results = [this.name.text] as TranspileResult;
15✔
461

462
        if (this.defaultValue) {
15✔
463
            results.push(' = ', ...(this.defaultValue.getTypedef(state) ?? this.defaultValue.transpile(state)));
7!
464
        }
465

466
        if (this.asToken) {
15✔
467
            results.push(' as ');
11✔
468

469
            if (this.typeToken) {
11!
470
                results.push(this.typeToken.text);
11✔
471
            }
472
        }
473

474
        return results;
15✔
475
    }
476

477
    walk(visitor: WalkVisitor, options: WalkOptions) {
478
        // eslint-disable-next-line no-bitwise
479
        if (this.defaultValue && options.walkMode & InternalWalkMode.walkExpressions) {
1,613✔
480
            walk(this, 'defaultValue', visitor, options);
489✔
481
        }
482
    }
483

484
    public clone() {
485
        return this.finalizeClone(
14✔
486
            new FunctionParameterExpression(
487
                util.cloneToken(this.name),
488
                util.cloneToken(this.typeToken),
489
                this.defaultValue?.clone(),
42✔
490
                util.cloneToken(this.asToken)
491
            ),
492
            ['defaultValue']
493
        );
494
    }
495
}
496

497
export class NamespacedVariableNameExpression extends Expression {
1✔
498
    constructor(
499
        //if this is a `DottedGetExpression`, it must be comprised only of `VariableExpression`s
500
        readonly expression: DottedGetExpression | VariableExpression
508✔
501
    ) {
502
        super();
508✔
503
        this.range = expression?.range;
508✔
504
    }
505
    range: Range | undefined;
506

507
    transpile(state: BrsTranspileState) {
508
        return [
4✔
509
            state.sourceNode(this, this.getName(ParseMode.BrightScript))
510
        ];
511
    }
512

513
    getTypedef(state) {
NEW
514
        return [
×
515
            state.sourceNode(this, this.getName(ParseMode.BrighterScript))
516
        ];
517
    }
518

519
    public getNameParts() {
520
        let parts = [] as string[];
3,734✔
521
        if (isVariableExpression(this.expression)) {
3,734✔
522
            parts.push(this.expression.name.text);
2,659✔
523
        } else {
524
            let expr = this.expression;
1,075✔
525

526
            parts.push(expr.name.text);
1,075✔
527

528
            while (isVariableExpression(expr) === false) {
1,075✔
529
                expr = expr.obj as DottedGetExpression;
1,250✔
530
                parts.unshift(expr.name.text);
1,250✔
531
            }
532
        }
533
        return parts;
3,734✔
534
    }
535

536
    getName(parseMode: ParseMode) {
537
        if (parseMode === ParseMode.BrighterScript) {
3,686✔
538
            return this.getNameParts().join('.');
3,446✔
539
        } else {
540
            return this.getNameParts().join('_');
240✔
541
        }
542
    }
543

544
    walk(visitor: WalkVisitor, options: WalkOptions) {
545
        this.expression?.link();
879!
546
        if (options.walkMode & InternalWalkMode.walkExpressions) {
879✔
547
            walk(this, 'expression', visitor, options);
855✔
548
        }
549
    }
550

551
    public clone() {
552
        return this.finalizeClone(
6✔
553
            new NamespacedVariableNameExpression(
554
                this.expression?.clone()
18✔
555
            )
556
        );
557
    }
558
}
559

560
export class DottedGetExpression extends Expression {
1✔
561
    constructor(
562
        readonly obj: Expression,
1,160✔
563
        readonly name: Identifier,
1,160✔
564
        /**
565
         * Can either be `.`, or `?.` for optional chaining
566
         */
567
        readonly dot: Token
1,160✔
568
    ) {
569
        super();
1,160✔
570
        this.range = util.createBoundingRange(this.obj, this.dot, this.name);
1,160✔
571
    }
572

573
    public readonly range: Range | undefined;
574

575
    transpile(state: BrsTranspileState) {
576
        //if the callee starts with a namespace name, transpile the name
577
        if (state.file.calleeStartsWithNamespace(this)) {
245✔
578
            return new NamespacedVariableNameExpression(this as DottedGetExpression | VariableExpression).transpile(state);
3✔
579
        } else {
580
            return [
242✔
581
                ...this.obj.transpile(state),
582
                state.transpileToken(this.dot),
583
                state.transpileToken(this.name)
584
            ];
585
        }
586
    }
587

588
    getTypedef(state: BrsTranspileState) {
589
        //always transpile the dots for typedefs
590
        return [
4✔
591
            ...(this.obj.getTypedef ? this.obj.getTypedef(state) : this.obj.transpile(state)),
4!
592
            state.transpileToken(this.dot),
593
            state.transpileToken(this.name)
594
        ];
595
    }
596

597
    walk(visitor: WalkVisitor, options: WalkOptions) {
598
        if (options.walkMode & InternalWalkMode.walkExpressions) {
2,371!
599
            walk(this, 'obj', visitor, options);
2,371✔
600
        }
601
    }
602

603
    public clone() {
604
        return this.finalizeClone(
6✔
605
            new DottedGetExpression(
606
                this.obj?.clone(),
18✔
607
                util.cloneToken(this.name),
608
                util.cloneToken(this.dot)
609
            ),
610
            ['obj']
611
        );
612
    }
613
}
614

615
export class XmlAttributeGetExpression extends Expression {
1✔
616
    constructor(
617
        readonly obj: Expression,
15✔
618
        readonly name: Identifier,
15✔
619
        /**
620
         * Can either be `@`, or `?@` for optional chaining
621
         */
622
        readonly at: Token
15✔
623
    ) {
624
        super();
15✔
625
        this.range = util.createBoundingRange(this.obj, this.at, this.name);
15✔
626
    }
627

628
    public readonly range: Range | undefined;
629

630
    transpile(state: BrsTranspileState) {
631
        return [
3✔
632
            ...this.obj.transpile(state),
633
            state.transpileToken(this.at),
634
            state.transpileToken(this.name)
635
        ];
636
    }
637

638
    walk(visitor: WalkVisitor, options: WalkOptions) {
639
        if (options.walkMode & InternalWalkMode.walkExpressions) {
22!
640
            walk(this, 'obj', visitor, options);
22✔
641
        }
642
    }
643

644
    public clone() {
645
        return this.finalizeClone(
2✔
646
            new XmlAttributeGetExpression(
647
                this.obj?.clone(),
6✔
648
                util.cloneToken(this.name),
649
                util.cloneToken(this.at)
650
            ),
651
            ['obj']
652
        );
653
    }
654
}
655

656
export class IndexedGetExpression extends Expression {
1✔
657
    constructor(
658
        public obj: Expression,
152✔
659
        public index: Expression,
152✔
660
        /**
661
         * Can either be `[` or `?[`. If `?.[` is used, this will be `[` and `optionalChainingToken` will be `?.`
662
         */
663
        public openingSquare: Token,
152✔
664
        public closingSquare: Token,
152✔
665
        public questionDotToken?: Token, //  ? or ?.
152✔
666
        /**
667
         * More indexes, separated by commas
668
         */
669
        public additionalIndexes?: Expression[]
152✔
670
    ) {
671
        super();
152✔
672
        this.range = util.createBoundingRange(this.obj, this.openingSquare, this.questionDotToken, this.openingSquare, this.index, this.closingSquare);
152✔
673
        this.additionalIndexes ??= [];
152✔
674
    }
675

676
    public readonly range: Range | undefined;
677

678
    transpile(state: BrsTranspileState) {
679
        const result = [];
64✔
680
        result.push(
64✔
681
            ...this.obj.transpile(state),
682
            this.questionDotToken ? state.transpileToken(this.questionDotToken) : '',
64✔
683
            state.transpileToken(this.openingSquare)
684
        );
685
        const indexes = [this.index, ...this.additionalIndexes ?? []];
64!
686
        for (let i = 0; i < indexes.length; i++) {
64✔
687
            //add comma between indexes
688
            if (i > 0) {
72✔
689
                result.push(', ');
8✔
690
            }
691
            let index = indexes[i];
72✔
692
            result.push(
72✔
693
                ...(index?.transpile(state) ?? [])
432!
694
            );
695
        }
696
        result.push(
64✔
697
            this.closingSquare ? state.transpileToken(this.closingSquare) : ''
64!
698
        );
699
        return result;
64✔
700
    }
701

702
    walk(visitor: WalkVisitor, options: WalkOptions) {
703
        if (options.walkMode & InternalWalkMode.walkExpressions) {
263!
704
            walk(this, 'obj', visitor, options);
263✔
705
            walk(this, 'index', visitor, options);
263✔
706
            walkArray(this.additionalIndexes, visitor, options, this);
263✔
707
        }
708
    }
709

710
    public clone() {
711
        return this.finalizeClone(
5✔
712
            new IndexedGetExpression(
713
                this.obj?.clone(),
15✔
714
                this.index?.clone(),
15✔
715
                util.cloneToken(this.openingSquare),
716
                util.cloneToken(this.closingSquare),
717
                util.cloneToken(this.questionDotToken),
718
                this.additionalIndexes?.map(e => e?.clone())
2✔
719
            ),
720
            ['obj', 'index', 'additionalIndexes']
721
        );
722
    }
723
}
724

725
export class GroupingExpression extends Expression {
1✔
726
    constructor(
727
        readonly tokens: {
40✔
728
            left: Token;
729
            right: Token;
730
        },
731
        public expression: Expression
40✔
732
    ) {
733
        super();
40✔
734
        this.range = util.createBoundingRange(this.tokens.left, this.expression, this.tokens.right);
40✔
735
    }
736

737
    public readonly range: Range | undefined;
738

739
    transpile(state: BrsTranspileState) {
740
        if (isTypeCastExpression(this.expression)) {
12✔
741
            return this.expression.transpile(state);
7✔
742
        }
743
        return [
5✔
744
            state.transpileToken(this.tokens.left),
745
            ...this.expression.transpile(state),
746
            state.transpileToken(this.tokens.right)
747
        ];
748
    }
749

750
    walk(visitor: WalkVisitor, options: WalkOptions) {
751
        if (options.walkMode & InternalWalkMode.walkExpressions) {
72!
752
            walk(this, 'expression', visitor, options);
72✔
753
        }
754
    }
755

756
    public clone() {
757
        return this.finalizeClone(
2✔
758
            new GroupingExpression(
759
                {
760
                    left: util.cloneToken(this.tokens.left),
761
                    right: util.cloneToken(this.tokens.right)
762
                },
763
                this.expression?.clone()
6✔
764
            ),
765
            ['expression']
766
        );
767
    }
768
}
769

770
export class LiteralExpression extends Expression {
1✔
771
    constructor(
772
        public token: Token
3,449✔
773
    ) {
774
        super();
3,449✔
775
        this.type = util.tokenToBscType(token);
3,449✔
776
    }
777

778
    public get range() {
779
        return this.token.range;
7,927✔
780
    }
781

782
    /**
783
     * The (data) type of this expression
784
     */
785
    public type: BscType;
786

787
    transpile(state: BrsTranspileState) {
788
        let text: string;
789
        if (this.token.kind === TokenKind.TemplateStringQuasi) {
861✔
790
            //wrap quasis with quotes (and escape inner quotemarks)
791
            text = `"${this.token.text.replace(/"/g, '""')}"`;
35✔
792

793
        } else if (isStringType(this.type)) {
826✔
794
            text = this.token.text;
335✔
795
            //add trailing quotemark if it's missing. We will have already generated a diagnostic for this.
796
            if (text.endsWith('"') === false) {
335✔
797
                text += '"';
1✔
798
            }
799
        } else {
800
            text = this.token.text;
491✔
801
        }
802

803
        return [
861✔
804
            state.sourceNode(this, text)
805
        ];
806
    }
807

808
    walk(visitor: WalkVisitor, options: WalkOptions) {
809
        //nothing to walk
810
    }
811

812
    public clone() {
813
        return this.finalizeClone(
100✔
814
            new LiteralExpression(
815
                util.cloneToken(this.token)
816
            )
817
        );
818
    }
819
}
820

821
/**
822
 * This is a special expression only used within template strings. It exists so we can prevent producing lots of empty strings
823
 * during template string transpile by identifying these expressions explicitly and skipping the bslib_toString around them
824
 */
825
export class EscapedCharCodeLiteralExpression extends Expression {
1✔
826
    constructor(
827
        readonly token: Token & { charCode: number }
37✔
828
    ) {
829
        super();
37✔
830
        this.range = token.range;
37✔
831
    }
832
    readonly range: Range;
833

834
    transpile(state: BrsTranspileState) {
835
        return [
15✔
836
            state.sourceNode(this, `chr(${this.token.charCode})`)
837
        ];
838
    }
839

840
    walk(visitor: WalkVisitor, options: WalkOptions) {
841
        //nothing to walk
842
    }
843

844
    public clone() {
845
        return this.finalizeClone(
3✔
846
            new EscapedCharCodeLiteralExpression(
847
                util.cloneToken(this.token)
848
            )
849
        );
850
    }
851
}
852

853
export class ArrayLiteralExpression extends Expression {
1✔
854
    constructor(
855
        readonly elements: Array<Expression | CommentStatement>,
134✔
856
        readonly open: Token,
134✔
857
        readonly close: Token,
134✔
858
        readonly hasSpread = false
134✔
859
    ) {
860
        super();
134✔
861
        this.range = util.createBoundingRange(this.open, ...this.elements ?? [], this.close);
134✔
862
    }
863

864
    public readonly range: Range | undefined;
865

866
    transpile(state: BrsTranspileState) {
867
        let result = [] as TranspileResult;
60✔
868
        result.push(
60✔
869
            state.transpileToken(this.open)
870
        );
871
        let hasChildren = this.elements.length > 0;
60✔
872
        state.blockDepth++;
60✔
873

874
        for (let i = 0; i < this.elements.length; i++) {
60✔
875
            let previousElement = this.elements[i - 1];
92✔
876
            let element = this.elements[i];
92✔
877

878
            if (isCommentStatement(element)) {
92✔
879
                //if the comment is on the same line as opening square or previous statement, don't add newline
880
                if (util.linesTouch(this.open, element) || util.linesTouch(previousElement, element)) {
7✔
881
                    result.push(' ');
4✔
882
                } else {
883
                    result.push(
3✔
884
                        '\n',
885
                        state.indent()
886
                    );
887
                }
888
                state.lineage.unshift(this);
7✔
889
                result.push(element.transpile(state));
7✔
890
                state.lineage.shift();
7✔
891
            } else {
892
                result.push('\n');
85✔
893

894
                result.push(
85✔
895
                    state.indent(),
896
                    ...element.transpile(state)
897
                );
898
            }
899
        }
900
        state.blockDepth--;
60✔
901
        //add a newline between open and close if there are elements
902
        if (hasChildren) {
60✔
903
            result.push('\n');
38✔
904
            result.push(state.indent());
38✔
905
        }
906
        if (this.close) {
60!
907
            result.push(
60✔
908
                state.transpileToken(this.close)
909
            );
910
        }
911
        return result;
60✔
912
    }
913

914
    walk(visitor: WalkVisitor, options: WalkOptions) {
915
        if (options.walkMode & InternalWalkMode.walkExpressions) {
311!
916
            walkArray(this.elements, visitor, options, this);
311✔
917
        }
918
    }
919

920
    public clone() {
921
        return this.finalizeClone(
4✔
922
            new ArrayLiteralExpression(
923
                this.elements?.map(e => e?.clone()),
6✔
924
                util.cloneToken(this.open),
925
                util.cloneToken(this.close),
926
                this.hasSpread
927
            ),
928
            ['elements']
929
        );
930
    }
931
}
932

933
export class AAMemberExpression extends Expression {
1✔
934
    constructor(
935
        public keyToken: Token,
216✔
936
        public colonToken: Token,
216✔
937
        /** The expression evaluated to determine the member's initial value. */
938
        public value: Expression
216✔
939
    ) {
940
        super();
216✔
941
        this.range = util.createBoundingRange(this.keyToken, this.colonToken, this.value);
216✔
942
    }
943

944
    public range: Range | undefined;
945
    public commaToken?: Token;
946

947
    transpile(state: BrsTranspileState) {
948
        //TODO move the logic from AALiteralExpression loop into this function
949
        return [];
×
950
    }
951

952
    walk(visitor: WalkVisitor, options: WalkOptions) {
953
        walk(this, 'value', visitor, options);
337✔
954
    }
955

956
    public clone() {
957
        return this.finalizeClone(
4✔
958
            new AAMemberExpression(
959
                util.cloneToken(this.keyToken),
960
                util.cloneToken(this.colonToken),
961
                this.value?.clone()
12✔
962
            ),
963
            ['value']
964
        );
965
    }
966

967
}
968

969
export class AALiteralExpression extends Expression {
1✔
970
    constructor(
971
        readonly elements: Array<AAMemberExpression | CommentStatement>,
215✔
972
        readonly open: Token,
215✔
973
        readonly close: Token
215✔
974
    ) {
975
        super();
215✔
976
        this.range = util.createBoundingRange(this.open, ...this.elements ?? [], this.close);
215✔
977
    }
978

979
    public readonly range: Range | undefined;
980

981
    transpile(state: BrsTranspileState) {
982
        let result = [] as TranspileResult;
62✔
983
        //open curly
984
        result.push(
62✔
985
            state.transpileToken(this.open)
986
        );
987
        let hasChildren = this.elements.length > 0;
62✔
988
        //add newline if the object has children and the first child isn't a comment starting on the same line as opening curly
989
        if (hasChildren && (isCommentStatement(this.elements[0]) === false || !util.linesTouch(this.elements[0], this.open))) {
62✔
990
            result.push('\n');
30✔
991
        }
992
        state.blockDepth++;
62✔
993
        for (let i = 0; i < this.elements.length; i++) {
62✔
994
            let element = this.elements[i];
62✔
995
            let previousElement = this.elements[i - 1];
62✔
996
            let nextElement = this.elements[i + 1];
62✔
997

998
            //don't indent if comment is same-line
999
            if (isCommentStatement(element as any) &&
62✔
1000
                (util.linesTouch(this.open, element) || util.linesTouch(previousElement, element))
1001
            ) {
1002
                result.push(' ');
10✔
1003

1004
                //indent line
1005
            } else {
1006
                result.push(state.indent());
52✔
1007
            }
1008

1009
            //render comments
1010
            if (isCommentStatement(element)) {
62✔
1011
                result.push(...element.transpile(state));
14✔
1012
            } else {
1013
                //key
1014
                result.push(
48✔
1015
                    state.transpileToken(element.keyToken)
1016
                );
1017
                //colon
1018
                result.push(
48✔
1019
                    state.transpileToken(element.colonToken),
1020
                    ' '
1021
                );
1022

1023
                //value
1024
                result.push(...element.value.transpile(state));
48✔
1025
            }
1026

1027

1028
            //if next element is a same-line comment, skip the newline
1029
            if (nextElement && isCommentStatement(nextElement) && nextElement.range?.start.line === element.range?.start.line) {
62!
1030

1031
                //add a newline between statements
1032
            } else {
1033
                result.push('\n');
54✔
1034
            }
1035
        }
1036
        state.blockDepth--;
62✔
1037

1038
        //only indent the closing curly if we have children
1039
        if (hasChildren) {
62✔
1040
            result.push(state.indent());
32✔
1041
        }
1042
        //close curly
1043
        if (this.close) {
62!
1044
            result.push(
62✔
1045
                state.transpileToken(this.close)
1046
            );
1047
        }
1048
        return result;
62✔
1049
    }
1050

1051
    walk(visitor: WalkVisitor, options: WalkOptions) {
1052
        if (options.walkMode & InternalWalkMode.walkExpressions) {
359!
1053
            walkArray(this.elements, visitor, options, this);
359✔
1054
        }
1055
    }
1056

1057
    public clone() {
1058
        return this.finalizeClone(
6✔
1059
            new AALiteralExpression(
1060
                this.elements?.map(e => e?.clone()),
5✔
1061
                util.cloneToken(this.open),
1062
                util.cloneToken(this.close)
1063
            ),
1064
            ['elements']
1065
        );
1066
    }
1067
}
1068

1069
export class UnaryExpression extends Expression {
1✔
1070
    constructor(
1071
        public operator: Token,
41✔
1072
        public right: Expression
41✔
1073
    ) {
1074
        super();
41✔
1075
        this.range = util.createBoundingRange(this.operator, this.right);
41✔
1076
    }
1077

1078
    public readonly range: Range | undefined;
1079

1080
    transpile(state: BrsTranspileState) {
1081
        let separatingWhitespace: string | undefined;
1082
        if (isVariableExpression(this.right)) {
14✔
1083
            separatingWhitespace = this.right.name.leadingWhitespace;
6✔
1084
        } else if (isLiteralExpression(this.right)) {
8✔
1085
            separatingWhitespace = this.right.token.leadingWhitespace;
3✔
1086
        }
1087

1088
        return [
14✔
1089
            state.transpileToken(this.operator),
1090
            separatingWhitespace ?? ' ',
42✔
1091
            ...this.right.transpile(state)
1092
        ];
1093
    }
1094

1095
    walk(visitor: WalkVisitor, options: WalkOptions) {
1096
        if (options.walkMode & InternalWalkMode.walkExpressions) {
73!
1097
            walk(this, 'right', visitor, options);
73✔
1098
        }
1099
    }
1100

1101
    public clone() {
1102
        return this.finalizeClone(
2✔
1103
            new UnaryExpression(
1104
                util.cloneToken(this.operator),
1105
                this.right?.clone()
6✔
1106
            ),
1107
            ['right']
1108
        );
1109
    }
1110
}
1111

1112
export class VariableExpression extends Expression {
1✔
1113
    constructor(
1114
        readonly name: Identifier
2,236✔
1115
    ) {
1116
        super();
2,236✔
1117
        this.range = this.name?.range;
2,236!
1118
    }
1119

1120
    public readonly range: Range;
1121

1122
    public getName(parseMode: ParseMode) {
1123
        return this.name.text;
30✔
1124
    }
1125

1126
    transpile(state: BrsTranspileState) {
1127
        let result = [] as TranspileResult;
429✔
1128
        const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
429✔
1129
        //if the callee is the name of a known namespace function
1130
        if (namespace && state.file.calleeIsKnownNamespaceFunction(this, namespace.getName(ParseMode.BrighterScript))) {
429✔
1131
            result.push(
5✔
1132
                state.sourceNode(this, [
1133
                    namespace.getName(ParseMode.BrightScript),
1134
                    '_',
1135
                    this.getName(ParseMode.BrightScript)
1136
                ])
1137
            );
1138

1139
            //transpile  normally
1140
        } else {
1141
            result.push(
424✔
1142
                state.transpileToken(this.name)
1143
            );
1144
        }
1145
        return result;
429✔
1146
    }
1147

1148
    getTypedef(state: BrsTranspileState) {
1149
        return [
4✔
1150
            state.transpileToken(this.name)
1151
        ];
1152
    }
1153

1154
    walk(visitor: WalkVisitor, options: WalkOptions) {
1155
        //nothing to walk
1156
    }
1157

1158
    public clone() {
1159
        return this.finalizeClone(
40✔
1160
            new VariableExpression(
1161
                util.cloneToken(this.name)
1162
            )
1163
        );
1164
    }
1165
}
1166

1167
export class SourceLiteralExpression extends Expression {
1✔
1168
    constructor(
1169
        readonly token: Token
37✔
1170
    ) {
1171
        super();
37✔
1172
        this.range = token?.range;
37!
1173
    }
1174

1175
    public readonly range: Range;
1176

1177
    private getFunctionName(state: BrsTranspileState, parseMode: ParseMode) {
1178
        let func = this.findAncestor<FunctionExpression>(isFunctionExpression);
8✔
1179
        let nameParts = [] as TranspileResult;
8✔
1180
        while (func.parentFunction) {
8✔
1181
            let index = func.parentFunction.childFunctionExpressions.indexOf(func);
4✔
1182
            nameParts.unshift(`anon${index}`);
4✔
1183
            func = func.parentFunction;
4✔
1184
        }
1185
        //get the index of this function in its parent
1186
        nameParts.unshift(
8✔
1187
            func.functionStatement!.getName(parseMode)
1188
        );
1189
        return nameParts.join('$');
8✔
1190
    }
1191

1192
    /**
1193
     * Get the line number from our token or from the closest ancestor that has a range
1194
     */
1195
    private getClosestLineNumber() {
1196
        let node: AstNode = this;
7✔
1197
        while (node) {
7✔
1198
            if (node.range) {
17✔
1199
                return node.range.start.line + 1;
5✔
1200
            }
1201
            node = node.parent;
12✔
1202
        }
1203
        return -1;
2✔
1204
    }
1205

1206
    transpile(state: BrsTranspileState) {
1207
        let text: string;
1208
        switch (this.token.kind) {
31✔
1209
            case TokenKind.SourceFilePathLiteral:
40!
1210
                const pathUrl = fileUrl(state.srcPath);
3✔
1211
                text = `"${pathUrl.substring(0, 4)}" + "${pathUrl.substring(4)}"`;
3✔
1212
                break;
3✔
1213
            case TokenKind.SourceLineNumLiteral:
1214
                //TODO find first parent that has range, or default to -1
1215
                text = `${this.getClosestLineNumber()}`;
4✔
1216
                break;
4✔
1217
            case TokenKind.FunctionNameLiteral:
1218
                text = `"${this.getFunctionName(state, ParseMode.BrightScript)}"`;
4✔
1219
                break;
4✔
1220
            case TokenKind.SourceFunctionNameLiteral:
1221
                text = `"${this.getFunctionName(state, ParseMode.BrighterScript)}"`;
4✔
1222
                break;
4✔
1223
            case TokenKind.SourceNamespaceNameLiteral:
1224
                let namespaceParts = this.getFunctionName(state, ParseMode.BrighterScript).split('.');
×
1225
                namespaceParts.pop(); // remove the function name
×
1226

1227
                text = `"${namespaceParts.join('.')}"`;
×
1228
                break;
×
1229
            case TokenKind.SourceNamespaceRootNameLiteral:
1230
                let namespaceRootParts = this.getFunctionName(state, ParseMode.BrighterScript).split('.');
×
1231
                namespaceRootParts.pop(); // remove the function name
×
1232

1233
                let rootNamespace = namespaceRootParts.shift() ?? '';
×
1234
                text = `"${rootNamespace}"`;
×
1235
                break;
×
1236
            case TokenKind.SourceLocationLiteral:
1237
                const locationUrl = fileUrl(state.srcPath);
3✔
1238
                //TODO find first parent that has range, or default to -1
1239
                text = `"${locationUrl.substring(0, 4)}" + "${locationUrl.substring(4)}:${this.getClosestLineNumber()}"`;
3✔
1240
                break;
3✔
1241
            case TokenKind.PkgPathLiteral:
1242
                let pkgPath1 = `pkg:/${state.file.pkgPath}`
2✔
1243
                    .replace(/\\/g, '/')
1244
                    .replace(/\.bs$/i, '.brs');
1245

1246
                text = `"${pkgPath1}"`;
2✔
1247
                break;
2✔
1248
            case TokenKind.PkgLocationLiteral:
1249
                let pkgPath2 = `pkg:/${state.file.pkgPath}`
2✔
1250
                    .replace(/\\/g, '/')
1251
                    .replace(/\.bs$/i, '.brs');
1252

1253
                text = `"${pkgPath2}:" + str(LINE_NUM)`;
2✔
1254
                break;
2✔
1255
            case TokenKind.LineNumLiteral:
1256
            default:
1257
                //use the original text (because it looks like a variable)
1258
                text = this.token.text;
9✔
1259
                break;
9✔
1260

1261
        }
1262
        return [
31✔
1263
            state.sourceNode(this, text)
1264
        ];
1265
    }
1266

1267
    walk(visitor: WalkVisitor, options: WalkOptions) {
1268
        //nothing to walk
1269
    }
1270

1271
    public clone() {
1272
        return this.finalizeClone(
1✔
1273
            new SourceLiteralExpression(
1274
                util.cloneToken(this.token)
1275
            )
1276
        );
1277
    }
1278
}
1279

1280
/**
1281
 * This expression transpiles and acts exactly like a CallExpression,
1282
 * except we need to uniquely identify these statements so we can
1283
 * do more type checking.
1284
 */
1285
export class NewExpression extends Expression {
1✔
1286
    constructor(
1287
        readonly newKeyword: Token,
43✔
1288
        readonly call: CallExpression
43✔
1289
    ) {
1290
        super();
43✔
1291
        this.range = util.createBoundingRange(this.newKeyword, this.call);
43✔
1292
    }
1293

1294
    /**
1295
     * The name of the class to initialize (with optional namespace prefixed)
1296
     */
1297
    public get className() {
1298
        //the parser guarantees the callee of a new statement's call object will be
1299
        //a NamespacedVariableNameExpression
1300
        return this.call.callee as NamespacedVariableNameExpression;
104✔
1301
    }
1302

1303
    public readonly range: Range | undefined;
1304

1305
    public transpile(state: BrsTranspileState) {
1306
        const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
10✔
1307
        const cls = state.file.getClassFileLink(
10✔
1308
            this.className.getName(ParseMode.BrighterScript),
1309
            namespace?.getName(ParseMode.BrighterScript)
30✔
1310
        )?.item;
10✔
1311
        //new statements within a namespace block can omit the leading namespace if the class resides in that same namespace.
1312
        //So we need to figure out if this is a namespace-omitted class, or if this class exists without a namespace.
1313
        return this.call.transpile(state, cls?.getName(ParseMode.BrightScript));
10✔
1314
    }
1315

1316
    walk(visitor: WalkVisitor, options: WalkOptions) {
1317
        if (options.walkMode & InternalWalkMode.walkExpressions) {
150!
1318
            walk(this, 'call', visitor, options);
150✔
1319
        }
1320
    }
1321

1322
    public clone() {
1323
        return this.finalizeClone(
2✔
1324
            new NewExpression(
1325
                util.cloneToken(this.newKeyword),
1326
                this.call?.clone()
6✔
1327
            ),
1328
            ['call']
1329
        );
1330
    }
1331
}
1332

1333
export class CallfuncExpression extends Expression {
1✔
1334
    constructor(
1335
        readonly callee: Expression,
28✔
1336
        readonly operator: Token,
28✔
1337
        readonly methodName: Identifier,
28✔
1338
        readonly openingParen: Token,
28✔
1339
        readonly args: Expression[],
28✔
1340
        readonly closingParen: Token
28✔
1341
    ) {
1342
        super();
28✔
1343
        this.range = util.createBoundingRange(
28✔
1344
            callee,
1345
            operator,
1346
            methodName,
1347
            openingParen,
1348
            ...args ?? [],
84✔
1349
            closingParen
1350
        );
1351
    }
1352

1353
    public readonly range: Range | undefined;
1354

1355
    /**
1356
     * Get the name of the wrapping namespace (if it exists)
1357
     * @deprecated use `.findAncestor(isNamespaceStatement)` instead.
1358
     */
1359
    public get namespaceName() {
1360
        return this.findAncestor<NamespaceStatement>(isNamespaceStatement)?.nameExpression;
×
1361
    }
1362

1363
    public transpile(state: BrsTranspileState) {
1364
        let result = [] as TranspileResult;
8✔
1365
        result.push(
8✔
1366
            ...this.callee.transpile(state),
1367
            state.sourceNode(this.operator, '.callfunc'),
1368
            state.transpileToken(this.openingParen),
1369
            //the name of the function
1370
            state.sourceNode(this.methodName, ['"', this.methodName.text, '"']),
1371
            ', '
1372
        );
1373
        //transpile args
1374
        //callfunc with zero args never gets called, so pass invalid as the first parameter if there are no args
1375
        if (this.args.length === 0) {
8✔
1376
            result.push('invalid');
5✔
1377
        } else {
1378
            for (let i = 0; i < this.args.length; i++) {
3✔
1379
                //add comma between args
1380
                if (i > 0) {
6✔
1381
                    result.push(', ');
3✔
1382
                }
1383
                let arg = this.args[i];
6✔
1384
                result.push(...arg.transpile(state));
6✔
1385
            }
1386
        }
1387
        result.push(
8✔
1388
            state.transpileToken(this.closingParen)
1389
        );
1390
        return result;
8✔
1391
    }
1392

1393
    walk(visitor: WalkVisitor, options: WalkOptions) {
1394
        if (options.walkMode & InternalWalkMode.walkExpressions) {
51!
1395
            walk(this, 'callee', visitor, options);
51✔
1396
            walkArray(this.args, visitor, options, this);
51✔
1397
        }
1398
    }
1399

1400
    public clone() {
1401
        return this.finalizeClone(
3✔
1402
            new CallfuncExpression(
1403
                this.callee?.clone(),
9✔
1404
                util.cloneToken(this.operator),
1405
                util.cloneToken(this.methodName),
1406
                util.cloneToken(this.openingParen),
1407
                this.args?.map(e => e?.clone()),
2✔
1408
                util.cloneToken(this.closingParen)
1409
            ),
1410
            ['callee', 'args']
1411
        );
1412
    }
1413
}
1414

1415
/**
1416
 * Since template strings can contain newlines, we need to concatenate multiple strings together with chr() calls.
1417
 * This is a single expression that represents the string contatenation of all parts of a single quasi.
1418
 */
1419
export class TemplateStringQuasiExpression extends Expression {
1✔
1420
    constructor(
1421
        readonly expressions: Array<LiteralExpression | EscapedCharCodeLiteralExpression>
114✔
1422
    ) {
1423
        super();
114✔
1424
        this.range = util.createBoundingRange(
114✔
1425
            ...expressions ?? []
342✔
1426
        );
1427
    }
1428
    readonly range: Range | undefined;
1429

1430
    transpile(state: BrsTranspileState, skipEmptyStrings = true) {
41✔
1431
        let result = [] as TranspileResult;
49✔
1432
        let plus = '';
49✔
1433
        for (let expression of this.expressions) {
49✔
1434
            //skip empty strings
1435
            //TODO what does an empty string literal expression look like?
1436
            if (expression.token.text === '' && skipEmptyStrings === true) {
78✔
1437
                continue;
28✔
1438
            }
1439
            result.push(
50✔
1440
                plus,
1441
                ...expression.transpile(state)
1442
            );
1443
            plus = ' + ';
50✔
1444
        }
1445
        return result;
49✔
1446
    }
1447

1448
    walk(visitor: WalkVisitor, options: WalkOptions) {
1449
        if (options.walkMode & InternalWalkMode.walkExpressions) {
213!
1450
            walkArray(this.expressions, visitor, options, this);
213✔
1451
        }
1452
    }
1453

1454
    public clone() {
1455
        return this.finalizeClone(
15✔
1456
            new TemplateStringQuasiExpression(
1457
                this.expressions?.map(e => e?.clone())
20✔
1458
            ),
1459
            ['expressions']
1460
        );
1461
    }
1462
}
1463

1464
export class TemplateStringExpression extends Expression {
1✔
1465
    constructor(
1466
        readonly openingBacktick: Token,
53✔
1467
        readonly quasis: TemplateStringQuasiExpression[],
53✔
1468
        readonly expressions: Expression[],
53✔
1469
        readonly closingBacktick: Token
53✔
1470
    ) {
1471
        super();
53✔
1472
        this.range = util.createBoundingRange(
53✔
1473
            openingBacktick,
1474
            quasis?.[0],
159✔
1475
            quasis?.[quasis?.length - 1],
315!
1476
            closingBacktick
1477
        );
1478
    }
1479

1480
    public readonly range: Range | undefined;
1481

1482
    transpile(state: BrsTranspileState) {
1483
        //if this is essentially just a normal brightscript string but with backticks, transpile it as a normal string without parens
1484
        if (this.expressions.length === 0 && this.quasis.length === 1 && this.quasis[0].expressions.length === 1) {
24✔
1485
            return this.quasis[0].transpile(state);
6✔
1486
        }
1487
        let result = ['('];
18✔
1488
        let plus = '';
18✔
1489
        //helper function to figure out when to include the plus
1490
        function add(...items) {
1491
            if (items.length > 0) {
52✔
1492
                result.push(
40✔
1493
                    plus,
1494
                    ...items
1495
                );
1496
            }
1497
            //set the plus after the first occurance of a nonzero length set of items
1498
            if (plus === '' && items.length > 0) {
52✔
1499
                plus = ' + ';
18✔
1500
            }
1501
        }
1502

1503
        for (let i = 0; i < this.quasis.length; i++) {
18✔
1504
            let quasi = this.quasis[i];
35✔
1505
            let expression = this.expressions[i];
35✔
1506

1507
            add(
35✔
1508
                ...quasi.transpile(state)
1509
            );
1510
            if (expression) {
35✔
1511
                //skip the toString wrapper around certain expressions
1512
                if (
17✔
1513
                    isEscapedCharCodeLiteralExpression(expression) ||
39✔
1514
                    (isLiteralExpression(expression) && isStringType(expression.type))
1515
                ) {
1516
                    add(
3✔
1517
                        ...expression.transpile(state)
1518
                    );
1519

1520
                    //wrap all other expressions with a bslib_toString call to prevent runtime type mismatch errors
1521
                } else {
1522
                    add(
14✔
1523
                        state.bslibPrefix + '_toString(',
1524
                        ...expression.transpile(state),
1525
                        ')'
1526
                    );
1527
                }
1528
            }
1529
        }
1530
        //the expression should be wrapped in parens so it can be used line a single expression at runtime
1531
        result.push(')');
18✔
1532

1533
        return result;
18✔
1534
    }
1535

1536
    walk(visitor: WalkVisitor, options: WalkOptions) {
1537
        if (options.walkMode & InternalWalkMode.walkExpressions) {
104!
1538
            //walk the quasis and expressions in left-to-right order
1539
            for (let i = 0; i < this.quasis?.length; i++) {
104✔
1540
                walk(this.quasis, i, visitor, options, this);
179✔
1541

1542
                //this skips the final loop iteration since we'll always have one more quasi than expression
1543
                if (this.expressions[i]) {
179✔
1544
                    walk(this.expressions, i, visitor, options, this);
75✔
1545
                }
1546
            }
1547
        }
1548
    }
1549

1550
    public clone() {
1551
        return this.finalizeClone(
7✔
1552
            new TemplateStringExpression(
1553
                util.cloneToken(this.openingBacktick),
1554
                this.quasis?.map(e => e?.clone()),
12✔
1555
                this.expressions?.map(e => e?.clone()),
6✔
1556
                util.cloneToken(this.closingBacktick)
1557
            ),
1558
            ['quasis', 'expressions']
1559
        );
1560
    }
1561
}
1562

1563
export class TaggedTemplateStringExpression extends Expression {
1✔
1564
    constructor(
1565
        readonly tagName: Identifier,
12✔
1566
        readonly openingBacktick: Token,
12✔
1567
        readonly quasis: TemplateStringQuasiExpression[],
12✔
1568
        readonly expressions: Expression[],
12✔
1569
        readonly closingBacktick: Token
12✔
1570
    ) {
1571
        super();
12✔
1572
        this.range = util.createBoundingRange(
12✔
1573
            tagName,
1574
            openingBacktick,
1575
            quasis?.[0],
36✔
1576
            quasis?.[quasis?.length - 1],
69!
1577
            closingBacktick
1578
        );
1579
    }
1580

1581
    public readonly range: Range | undefined;
1582

1583
    transpile(state: BrsTranspileState) {
1584
        let result = [] as TranspileResult;
3✔
1585
        result.push(
3✔
1586
            state.transpileToken(this.tagName),
1587
            '(['
1588
        );
1589

1590
        //add quasis as the first array
1591
        for (let i = 0; i < this.quasis.length; i++) {
3✔
1592
            let quasi = this.quasis[i];
8✔
1593
            //separate items with a comma
1594
            if (i > 0) {
8✔
1595
                result.push(
5✔
1596
                    ', '
1597
                );
1598
            }
1599
            result.push(
8✔
1600
                ...quasi.transpile(state, false)
1601
            );
1602
        }
1603
        result.push(
3✔
1604
            '], ['
1605
        );
1606

1607
        //add expressions as the second array
1608
        for (let i = 0; i < this.expressions.length; i++) {
3✔
1609
            let expression = this.expressions[i];
5✔
1610
            if (i > 0) {
5✔
1611
                result.push(
2✔
1612
                    ', '
1613
                );
1614
            }
1615
            result.push(
5✔
1616
                ...expression.transpile(state)
1617
            );
1618
        }
1619
        result.push(
3✔
1620
            state.sourceNode(this.closingBacktick, '])')
1621
        );
1622
        return result;
3✔
1623
    }
1624

1625
    walk(visitor: WalkVisitor, options: WalkOptions) {
1626
        if (options.walkMode & InternalWalkMode.walkExpressions) {
18!
1627
            //walk the quasis and expressions in left-to-right order
1628
            for (let i = 0; i < this.quasis?.length; i++) {
18✔
1629
                walk(this.quasis, i, visitor, options, this);
40✔
1630

1631
                //this skips the final loop iteration since we'll always have one more quasi than expression
1632
                if (this.expressions[i]) {
40✔
1633
                    walk(this.expressions, i, visitor, options, this);
22✔
1634
                }
1635
            }
1636
        }
1637
    }
1638

1639
    public clone() {
1640
        return this.finalizeClone(
3✔
1641
            new TaggedTemplateStringExpression(
1642
                util.cloneToken(this.tagName),
1643
                util.cloneToken(this.openingBacktick),
1644
                this.quasis?.map(e => e?.clone()),
5✔
1645
                this.expressions?.map(e => e?.clone()),
3✔
1646
                util.cloneToken(this.closingBacktick)
1647
            ),
1648
            ['quasis', 'expressions']
1649
        );
1650
    }
1651
}
1652

1653
export class AnnotationExpression extends Expression {
1✔
1654
    constructor(
1655
        readonly atToken: Token,
70✔
1656
        readonly nameToken: Token
70✔
1657
    ) {
1658
        super();
70✔
1659
        this.name = nameToken.text;
70✔
1660
    }
1661

1662
    public get range() {
1663
        return util.createBoundingRange(
32✔
1664
            this.atToken,
1665
            this.nameToken,
1666
            this.call
1667
        );
1668
    }
1669

1670
    public name: string;
1671
    public call: CallExpression | undefined;
1672

1673
    /**
1674
     * Convert annotation arguments to JavaScript types
1675
     * @param strict If false, keep Expression objects not corresponding to JS types
1676
     */
1677
    getArguments(strict = true): ExpressionValue[] {
10✔
1678
        if (!this.call) {
11✔
1679
            return [];
1✔
1680
        }
1681
        return this.call.args.map(e => expressionToValue(e, strict));
20✔
1682
    }
1683

1684
    transpile(state: BrsTranspileState) {
1685
        return [];
3✔
1686
    }
1687

1688
    walk(visitor: WalkVisitor, options: WalkOptions) {
1689
        //nothing to walk
1690
    }
1691
    getTypedef(state: BrsTranspileState) {
1692
        return [
9✔
1693
            '@',
1694
            this.name,
1695
            ...(this.call?.transpile(state) ?? [])
54✔
1696
        ];
1697
    }
1698

1699
    public clone() {
1700
        const clone = this.finalizeClone(
8✔
1701
            new AnnotationExpression(
1702
                util.cloneToken(this.atToken),
1703
                util.cloneToken(this.nameToken)
1704
            )
1705
        );
1706
        return clone;
8✔
1707
    }
1708
}
1709

1710
export class TernaryExpression extends Expression {
1✔
1711
    constructor(
1712
        readonly test: Expression,
96✔
1713
        readonly questionMarkToken: Token,
96✔
1714
        readonly consequent?: Expression,
96✔
1715
        readonly colonToken?: Token,
96✔
1716
        readonly alternate?: Expression
96✔
1717
    ) {
1718
        super();
96✔
1719
        this.range = util.createBoundingRange(
96✔
1720
            test,
1721
            questionMarkToken,
1722
            consequent,
1723
            colonToken,
1724
            alternate
1725
        );
1726
    }
1727

1728
    public range: Range | undefined;
1729

1730
    transpile(state: BrsTranspileState) {
1731
        let result = [] as TranspileResult;
18✔
1732
        const file = state.file;
18✔
1733
        let consequentInfo = util.getExpressionInfo(this.consequent!, file);
18✔
1734
        let alternateInfo = util.getExpressionInfo(this.alternate!, file);
18✔
1735

1736
        //get all unique variable names used in the consequent and alternate, and sort them alphabetically so the output is consistent
1737
        let allUniqueVarNames = [...new Set([...consequentInfo.uniqueVarNames, ...alternateInfo.uniqueVarNames])].sort();
18✔
1738
        //discard names of global functions that cannot be passed by reference
1739
        allUniqueVarNames = allUniqueVarNames.filter(name => {
18✔
1740
            return !nonReferenceableFunctions.includes(name.toLowerCase());
23✔
1741
        });
1742

1743
        let mutatingExpressions = [
18✔
1744
            ...consequentInfo.expressions,
1745
            ...alternateInfo.expressions
1746
        ].filter(e => e instanceof CallExpression || e instanceof CallfuncExpression || e instanceof DottedGetExpression);
120✔
1747

1748
        if (mutatingExpressions.length > 0) {
18✔
1749
            result.push(
10✔
1750
                state.sourceNode(
1751
                    this.questionMarkToken,
1752
                    //write all the scope variables as parameters.
1753
                    //TODO handle when there are more than 31 parameters
1754
                    `(function(${['__bsCondition', ...allUniqueVarNames].join(', ')})`
1755
                ),
1756
                state.newline,
1757
                //double indent so our `end function` line is still indented one at the end
1758
                state.indent(2),
1759
                state.sourceNode(this.test, `if __bsCondition then`),
1760
                state.newline,
1761
                state.indent(1),
1762
                state.sourceNode(this.consequent ?? this.questionMarkToken, 'return '),
30!
1763
                ...this.consequent?.transpile(state) ?? [state.sourceNode(this.questionMarkToken, 'invalid')],
60!
1764
                state.newline,
1765
                state.indent(-1),
1766
                state.sourceNode(this.consequent ?? this.questionMarkToken, 'else'),
30!
1767
                state.newline,
1768
                state.indent(1),
1769
                state.sourceNode(this.consequent ?? this.questionMarkToken, 'return '),
30!
1770
                ...this.alternate?.transpile(state) ?? [state.sourceNode(this.consequent ?? this.questionMarkToken, 'invalid')],
60!
1771
                state.newline,
1772
                state.indent(-1),
1773
                state.sourceNode(this.questionMarkToken, 'end if'),
1774
                state.newline,
1775
                state.indent(-1),
1776
                state.sourceNode(this.questionMarkToken, 'end function)('),
1777
                ...this.test.transpile(state),
1778
                state.sourceNode(this.questionMarkToken, `${['', ...allUniqueVarNames].join(', ')})`)
1779
            );
1780
            state.blockDepth--;
10✔
1781
        } else {
1782
            result.push(
8✔
1783
                state.sourceNode(this.test, state.bslibPrefix + `_ternary(`),
1784
                ...this.test.transpile(state),
1785
                state.sourceNode(this.test, `, `),
1786
                ...this.consequent?.transpile(state) ?? ['invalid'],
48✔
1787
                `, `,
1788
                ...this.alternate?.transpile(state) ?? ['invalid'],
48✔
1789
                `)`
1790
            );
1791
        }
1792
        return result;
18✔
1793
    }
1794

1795
    public walk(visitor: WalkVisitor, options: WalkOptions) {
1796
        if (options.walkMode & InternalWalkMode.walkExpressions) {
191!
1797
            walk(this, 'test', visitor, options);
191✔
1798
            walk(this, 'consequent', visitor, options);
191✔
1799
            walk(this, 'alternate', visitor, options);
191✔
1800
        }
1801
    }
1802

1803
    public clone() {
1804
        return this.finalizeClone(
2✔
1805
            new TernaryExpression(
1806
                this.test?.clone(),
6✔
1807
                util.cloneToken(this.questionMarkToken),
1808
                this.consequent?.clone(),
6✔
1809
                util.cloneToken(this.colonToken),
1810
                this.alternate?.clone()
6✔
1811
            ),
1812
            ['test', 'consequent', 'alternate']
1813
        );
1814
    }
1815
}
1816

1817
export class NullCoalescingExpression extends Expression {
1✔
1818
    constructor(
1819
        public consequent: Expression,
32✔
1820
        public questionQuestionToken: Token,
32✔
1821
        public alternate: Expression
32✔
1822
    ) {
1823
        super();
32✔
1824
        this.range = util.createBoundingRange(
32✔
1825
            consequent,
1826
            questionQuestionToken,
1827
            alternate
1828
        );
1829
    }
1830
    public readonly range: Range | undefined;
1831

1832
    transpile(state: BrsTranspileState) {
1833
        let result = [] as TranspileResult;
11✔
1834
        let consequentInfo = util.getExpressionInfo(this.consequent, state.file);
11✔
1835
        let alternateInfo = util.getExpressionInfo(this.alternate, state.file);
11✔
1836

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

1844
        let hasMutatingExpression = [
11✔
1845
            ...consequentInfo.expressions,
1846
            ...alternateInfo.expressions
1847
        ].find(e => isCallExpression(e) || isCallfuncExpression(e) || isDottedGetExpression(e));
30✔
1848

1849
        if (hasMutatingExpression) {
11✔
1850
            result.push(
7✔
1851
                `(function(`,
1852
                //write all the scope variables as parameters.
1853
                //TODO handle when there are more than 31 parameters
1854
                allUniqueVarNames.join(', '),
1855
                ')',
1856
                state.newline,
1857
                //double indent so our `end function` line is still indented one at the end
1858
                state.indent(2),
1859
                //evaluate the consequent exactly once, and then use it in the following condition
1860
                `__bsConsequent = `,
1861
                ...this.consequent.transpile(state),
1862
                state.newline,
1863
                state.indent(),
1864
                `if __bsConsequent <> invalid then`,
1865
                state.newline,
1866
                state.indent(1),
1867
                'return __bsConsequent',
1868
                state.newline,
1869
                state.indent(-1),
1870
                'else',
1871
                state.newline,
1872
                state.indent(1),
1873
                'return ',
1874
                ...this.alternate.transpile(state),
1875
                state.newline,
1876
                state.indent(-1),
1877
                'end if',
1878
                state.newline,
1879
                state.indent(-1),
1880
                'end function)(',
1881
                allUniqueVarNames.join(', '),
1882
                ')'
1883
            );
1884
            state.blockDepth--;
7✔
1885
        } else {
1886
            result.push(
4✔
1887
                state.bslibPrefix + `_coalesce(`,
1888
                ...this.consequent.transpile(state),
1889
                ', ',
1890
                ...this.alternate.transpile(state),
1891
                ')'
1892
            );
1893
        }
1894
        return result;
11✔
1895
    }
1896

1897
    public walk(visitor: WalkVisitor, options: WalkOptions) {
1898
        if (options.walkMode & InternalWalkMode.walkExpressions) {
53!
1899
            walk(this, 'consequent', visitor, options);
53✔
1900
            walk(this, 'alternate', visitor, options);
53✔
1901
        }
1902
    }
1903

1904
    public clone() {
1905
        return this.finalizeClone(
2✔
1906
            new NullCoalescingExpression(
1907
                this.consequent?.clone(),
6✔
1908
                util.cloneToken(this.questionQuestionToken),
1909
                this.alternate?.clone()
6✔
1910
            ),
1911
            ['consequent', 'alternate']
1912
        );
1913
    }
1914
}
1915

1916
export class RegexLiteralExpression extends Expression {
1✔
1917
    public constructor(
1918
        public tokens: {
46✔
1919
            regexLiteral: Token;
1920
        }
1921
    ) {
1922
        super();
46✔
1923
    }
1924

1925
    public get range() {
1926
        return this.tokens?.regexLiteral?.range;
55!
1927
    }
1928

1929
    public transpile(state: BrsTranspileState): TranspileResult {
1930
        let text = this.tokens.regexLiteral?.text ?? '';
42!
1931
        let flags = '';
42✔
1932
        //get any flags from the end
1933
        const flagMatch = /\/([a-z]+)$/i.exec(text);
42✔
1934
        if (flagMatch) {
42✔
1935
            text = text.substring(0, flagMatch.index + 1);
2✔
1936
            flags = flagMatch[1];
2✔
1937
        }
1938
        let pattern = text
42✔
1939
            //remove leading and trailing slashes
1940
            .substring(1, text.length - 1)
1941
            //escape quotemarks
1942
            .split('"').join('" + chr(34) + "');
1943

1944
        return [
42✔
1945
            state.sourceNode(this.tokens.regexLiteral, [
1946
                'CreateObject("roRegex", ',
1947
                `"${pattern}", `,
1948
                `"${flags}"`,
1949
                ')'
1950
            ])
1951
        ];
1952
    }
1953

1954
    walk(visitor: WalkVisitor, options: WalkOptions) {
1955
        //nothing to walk
1956
    }
1957

1958
    public clone() {
1959
        return this.finalizeClone(
1✔
1960
            new RegexLiteralExpression({
1961
                regexLiteral: util.cloneToken(this.tokens.regexLiteral)
1962
            })
1963
        );
1964
    }
1965
}
1966

1967

1968
export class TypeCastExpression extends Expression {
1✔
1969
    constructor(
1970
        public obj: Expression,
19✔
1971
        public asToken: Token,
19✔
1972
        public typeToken: Token
19✔
1973
    ) {
1974
        super();
19✔
1975
        this.range = util.createBoundingRange(
19✔
1976
            this.obj,
1977
            this.asToken,
1978
            this.typeToken
1979
        );
1980
    }
1981

1982
    public range: Range;
1983

1984
    public transpile(state: BrsTranspileState): TranspileResult {
1985
        return this.obj.transpile(state);
11✔
1986
    }
1987
    public walk(visitor: WalkVisitor, options: WalkOptions) {
1988
        if (options.walkMode & InternalWalkMode.walkExpressions) {
40!
1989
            walk(this, 'obj', visitor, options);
40✔
1990
        }
1991
    }
1992

1993
    public clone() {
1994
        return this.finalizeClone(
2✔
1995
            new TypeCastExpression(
1996
                this.obj?.clone(),
6✔
1997
                util.cloneToken(this.asToken),
1998
                util.cloneToken(this.typeToken)
1999
            ),
2000
            ['obj']
2001
        );
2002
    }
2003
}
2004

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

2008
function expressionToValue(expr: Expression, strict: boolean): ExpressionValue {
2009
    if (!expr) {
30!
2010
        return null;
×
2011
    }
2012
    if (isUnaryExpression(expr) && isLiteralNumber(expr.right)) {
30✔
2013
        return numberExpressionToValue(expr.right, expr.operator.text);
1✔
2014
    }
2015
    if (isLiteralString(expr)) {
29✔
2016
        //remove leading and trailing quotes
2017
        return expr.token.text.replace(/^"/, '').replace(/"$/, '');
5✔
2018
    }
2019
    if (isLiteralNumber(expr)) {
24✔
2020
        return numberExpressionToValue(expr);
11✔
2021
    }
2022

2023
    if (isLiteralBoolean(expr)) {
13✔
2024
        return expr.token.text.toLowerCase() === 'true';
3✔
2025
    }
2026
    if (isArrayLiteralExpression(expr)) {
10✔
2027
        return expr.elements
3✔
2028
            .filter(e => !isCommentStatement(e))
7✔
2029
            .map(e => expressionToValue(e, strict));
7✔
2030
    }
2031
    if (isAALiteralExpression(expr)) {
7✔
2032
        return expr.elements.reduce((acc, e) => {
3✔
2033
            if (!isCommentStatement(e)) {
3!
2034
                acc[e.keyToken.text] = expressionToValue(e.value, strict);
3✔
2035
            }
2036
            return acc;
3✔
2037
        }, {});
2038
    }
2039
    //for annotations, we only support serializing pure string values
2040
    if (isTemplateStringExpression(expr)) {
4✔
2041
        if (expr.quasis?.length === 1 && expr.expressions.length === 0) {
2!
2042
            return expr.quasis[0].expressions.map(x => x.token.text).join('');
10✔
2043
        }
2044
    }
2045
    return strict ? null : expr;
2✔
2046
}
2047

2048
function numberExpressionToValue(expr: LiteralExpression, operator = '') {
11✔
2049
    if (isIntegerType(expr.type) || isLongIntegerType(expr.type)) {
12!
2050
        return parseInt(operator + expr.token.text);
12✔
2051
    } else {
2052
        return parseFloat(operator + expr.token.text);
×
2053
    }
2054
}
2055

2056
/**
2057
 * A list of names of functions that are restricted from being stored to a
2058
 * variable, property, or passed as an argument. (i.e. `type` or `createobject`).
2059
 * Names are stored in lower case.
2060
 */
2061
const nonReferenceableFunctions = [
1✔
2062
    'createobject',
2063
    'type',
2064
    'getglobalaa',
2065
    'box',
2066
    'run',
2067
    'eval',
2068
    'getlastruncompileerror',
2069
    'getlastrunruntimeerror',
2070
    'tab',
2071
    'pos'
2072
];
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