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

rokucommunity / brighterscript / #15046

03 Oct 2022 01:55PM UTC coverage: 87.532% (-0.3%) from 87.808%
#15046

push

TwitchBronBron
0.59.0

5452 of 6706 branches covered (81.3%)

Branch coverage included in aggregate %.

8259 of 8958 relevant lines covered (92.2%)

1521.92 hits per line

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

93.4
/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 { walk, InternalWalkMode, walkArray } from '../astUtils/visitors';
1✔
12
import { isAALiteralExpression, isArrayLiteralExpression, isCallExpression, isCallfuncExpression, isCommentStatement, isDottedGetExpression, isEscapedCharCodeLiteralExpression, isIntegerType, isLiteralBoolean, isLiteralExpression, isLiteralNumber, isLiteralString, isLongIntegerType, isNamespaceStatement, isStringType, isUnaryExpression, isVariableExpression } from '../astUtils/reflection';
1✔
13
import type { TranspileResult, TypedefProvider } from '../interfaces';
14
import { VoidType } from '../types/VoidType';
1✔
15
import { DynamicType } from '../types/DynamicType';
1✔
16
import type { BscType } from '../types/BscType';
17
import { FunctionType } from '../types/FunctionType';
1✔
18
import { Expression } from './AstNode';
1✔
19

20
export type ExpressionVisitor = (expression: Expression, parent: Expression) => void;
21

22
export class BinaryExpression extends Expression {
1✔
23
    constructor(
24
        public left: Expression,
318✔
25
        public operator: Token,
318✔
26
        public right: Expression
318✔
27
    ) {
28
        super();
318✔
29
        this.range = util.createRangeFromPositions(this.left.range.start, this.right.range.end);
318✔
30
    }
31

32
    public readonly range: Range;
33

34
    transpile(state: BrsTranspileState) {
35
        return [
136✔
36
            state.sourceNode(this.left, this.left.transpile(state)),
37
            ' ',
38
            state.transpileToken(this.operator),
39
            ' ',
40
            state.sourceNode(this.right, this.right.transpile(state))
41
        ];
42
    }
43

44
    walk(visitor: WalkVisitor, options: WalkOptions) {
45
        if (options.walkMode & InternalWalkMode.walkExpressions) {
165!
46
            walk(this, 'left', visitor, options);
165✔
47
            walk(this, 'right', visitor, options);
165✔
48
        }
49
    }
50
}
51

52
export class CallExpression extends Expression {
1✔
53
    static MaximumArguments = 32;
1✔
54

55
    constructor(
56
        readonly callee: Expression,
496✔
57
        /**
58
         * Can either be `(`, or `?(` for optional chaining
59
         */
60
        readonly openingParen: Token,
496✔
61
        readonly closingParen: Token,
496✔
62
        readonly args: Expression[]
496✔
63
    ) {
64
        super();
496✔
65
        this.range = util.createBoundingRange(this.callee, this.openingParen, ...args, this.closingParen);
496✔
66
    }
67

68
    public readonly range: Range;
69

70
    transpile(state: BrsTranspileState, nameOverride?: string) {
71
        let result = [];
202✔
72

73
        //transpile the name
74
        if (nameOverride) {
202✔
75
            result.push(state.sourceNode(this.callee, nameOverride));
8✔
76
        } else {
77
            result.push(...this.callee.transpile(state));
194✔
78
        }
79

80
        result.push(
202✔
81
            state.transpileToken(this.openingParen)
82
        );
83
        for (let i = 0; i < this.args.length; i++) {
202✔
84
            //add comma between args
85
            if (i > 0) {
104✔
86
                result.push(', ');
6✔
87
            }
88
            let arg = this.args[i];
104✔
89
            result.push(...arg.transpile(state));
104✔
90
        }
91
        if (this.closingParen) {
202!
92
            result.push(
202✔
93
                state.transpileToken(this.closingParen)
94
            );
95
        }
96
        return result;
202✔
97
    }
98

99
    walk(visitor: WalkVisitor, options: WalkOptions) {
100
        if (options.walkMode & InternalWalkMode.walkExpressions) {
390!
101
            walk(this, 'callee', visitor, options);
390✔
102
            walkArray(this.args, visitor, options, this);
390✔
103
        }
104
    }
105
}
106

107
export class FunctionExpression extends Expression implements TypedefProvider {
1✔
108
    constructor(
109
        readonly parameters: FunctionParameterExpression[],
1,323✔
110
        public body: Block,
1,323✔
111
        readonly functionType: Token | null,
1,323✔
112
        public end: Token,
1,323✔
113
        readonly leftParen: Token,
1,323✔
114
        readonly rightParen: Token,
1,323✔
115
        readonly asToken?: Token,
1,323✔
116
        readonly returnTypeToken?: Token,
1,323✔
117
        /**
118
         * If this function is enclosed within another function, this will reference that parent function
119
         */
120
        readonly parentFunction?: FunctionExpression
1,323✔
121
    ) {
122
        super();
1,323✔
123
        if (this.returnTypeToken) {
1,323✔
124
            this.returnType = util.tokenToBscType(this.returnTypeToken);
64✔
125
        } else if (this.functionType.text.toLowerCase() === 'sub') {
1,259✔
126
            this.returnType = new VoidType();
953✔
127
        } else {
128
            this.returnType = DynamicType.instance;
306✔
129
        }
130
    }
131

132
    /**
133
     * The type this function returns
134
     */
135
    public returnType: BscType;
136

137
    /**
138
     * The list of function calls that are declared within this function scope. This excludes CallExpressions
139
     * declared in child functions
140
     */
141
    public callExpressions = [] as CallExpression[];
1,323✔
142

143
    /**
144
     * If this function is part of a FunctionStatement, this will be set. Otherwise this will be undefined
145
     */
146
    public functionStatement?: FunctionStatement;
147

148
    /**
149
     * A list of all child functions declared directly within this function
150
     */
151
    public childFunctionExpressions = [] as FunctionExpression[];
1,323✔
152

153
    /**
154
     * The range of the function, starting at the 'f' in function or 's' in sub (or the open paren if the keyword is missing),
155
     * and ending with the last n' in 'end function' or 'b' in 'end sub'
156
     */
157
    public get range() {
158
        return util.createRangeFromPositions(
3,553✔
159
            (this.functionType ?? this.leftParen).range.start,
10,659!
160
            (this.end ?? this.body ?? this.returnTypeToken ?? this.asToken ?? this.rightParen).range.end
42,636!
161
        );
162
    }
163

164
    transpile(state: BrsTranspileState, name?: Identifier, includeBody = true) {
268✔
165
        let results = [];
296✔
166
        //'function'|'sub'
167
        results.push(
296✔
168
            state.transpileToken(this.functionType)
169
        );
170
        //functionName?
171
        if (name) {
296✔
172
            results.push(
250✔
173
                ' ',
174
                state.transpileToken(name)
175
            );
176
        }
177
        //leftParen
178
        results.push(
296✔
179
            state.transpileToken(this.leftParen)
180
        );
181
        //parameters
182
        for (let i = 0; i < this.parameters.length; i++) {
296✔
183
            let param = this.parameters[i];
75✔
184
            //add commas
185
            if (i > 0) {
75✔
186
                results.push(', ');
31✔
187
            }
188
            //add parameter
189
            results.push(param.transpile(state));
75✔
190
        }
191
        //right paren
192
        results.push(
296✔
193
            state.transpileToken(this.rightParen)
194
        );
195
        //as [Type]
196
        if (this.asToken) {
296✔
197
            results.push(
33✔
198
                ' ',
199
                //as
200
                state.transpileToken(this.asToken),
201
                ' ',
202
                //return type
203
                state.sourceNode(this.returnTypeToken, this.returnType.toTypeString())
204
            );
205
        }
206
        if (includeBody) {
296✔
207
            state.lineage.unshift(this);
268✔
208
            let body = this.body.transpile(state);
268✔
209
            state.lineage.shift();
268✔
210
            results.push(...body);
268✔
211
        }
212
        results.push('\n');
296✔
213
        //'end sub'|'end function'
214
        results.push(
296✔
215
            state.indent(),
216
            state.transpileToken(this.end)
217
        );
218
        return results;
296✔
219
    }
220

221
    getTypedef(state: BrsTranspileState, name?: Identifier) {
222
        return this.transpile(state, name, false);
28✔
223
    }
224

225
    walk(visitor: WalkVisitor, options: WalkOptions) {
226
        if (options.walkMode & InternalWalkMode.walkExpressions) {
712!
227
            walkArray(this.parameters, visitor, options, this);
712✔
228

229
            //This is the core of full-program walking...it allows us to step into sub functions
230
            if (options.walkMode & InternalWalkMode.recurseChildFunctions) {
712!
231
                walk(this, 'body', visitor, options);
712✔
232
            }
233
        }
234
    }
235

236
    getFunctionType(): FunctionType {
237
        let functionType = new FunctionType(this.returnType);
1,279✔
238
        functionType.isSub = this.functionType.text === 'sub';
1,279✔
239
        for (let param of this.parameters) {
1,279✔
240
            functionType.addParameter(param.name.text, param.type, !!param.typeToken);
265✔
241
        }
242
        return functionType;
1,279✔
243
    }
244
}
245

246
export class FunctionParameterExpression extends Expression {
1✔
247
    constructor(
248
        public name: Identifier,
290✔
249
        public typeToken?: Token,
290✔
250
        public defaultValue?: Expression,
290✔
251
        public asToken?: Token
290✔
252
    ) {
253
        super();
290✔
254
        if (typeToken) {
290✔
255
            this.type = util.tokenToBscType(typeToken);
138✔
256
        } else {
257
            this.type = new DynamicType();
152✔
258
        }
259
    }
260

261
    public type: BscType;
262

263
    public get range(): Range {
264
        return {
20✔
265
            start: this.name.range.start,
266
            end: this.typeToken ? this.typeToken.range.end : this.name.range.end
20✔
267
        };
268
    }
269

270
    public transpile(state: BrsTranspileState) {
271
        let result = [
82✔
272
            //name
273
            state.transpileToken(this.name)
274
        ] as any[];
275
        //default value
276
        if (this.defaultValue) {
82✔
277
            result.push(' = ');
6✔
278
            result.push(this.defaultValue.transpile(state));
6✔
279
        }
280
        //type declaration
281
        if (this.asToken) {
82✔
282
            result.push(' ');
62✔
283
            result.push(state.transpileToken(this.asToken));
62✔
284
            result.push(' ');
62✔
285
            result.push(state.sourceNode(this.typeToken, this.type.toTypeString()));
62✔
286
        }
287

288
        return result;
82✔
289
    }
290

291
    walk(visitor: WalkVisitor, options: WalkOptions) {
292
        // eslint-disable-next-line no-bitwise
293
        if (this.defaultValue && options.walkMode & InternalWalkMode.walkExpressions) {
199✔
294
            walk(this, 'defaultValue', visitor, options);
14✔
295
        }
296
    }
297
}
298

299
export class NamespacedVariableNameExpression extends Expression {
1✔
300
    constructor(
301
        //if this is a `DottedGetExpression`, it must be comprised only of `VariableExpression`s
302
        readonly expression: DottedGetExpression | VariableExpression
296✔
303
    ) {
304
        super();
296✔
305
        this.range = expression.range;
296✔
306
    }
307
    range: Range;
308

309
    transpile(state: BrsTranspileState) {
310
        return [
4✔
311
            state.sourceNode(this, this.getName(ParseMode.BrightScript))
312
        ];
313
    }
314

315
    public getNameParts() {
316
        let parts = [] as string[];
1,289✔
317
        if (isVariableExpression(this.expression)) {
1,289✔
318
            parts.push(this.expression.name.text);
858✔
319
        } else {
320
            let expr = this.expression;
431✔
321

322
            parts.push(expr.name.text);
431✔
323

324
            while (isVariableExpression(expr) === false) {
431✔
325
                expr = expr.obj as DottedGetExpression;
518✔
326
                parts.unshift(expr.name.text);
518✔
327
            }
328
        }
329
        return parts;
1,289✔
330
    }
331

332
    getName(parseMode: ParseMode) {
333
        if (parseMode === ParseMode.BrighterScript) {
1,282✔
334
            return this.getNameParts().join('.');
1,139✔
335
        } else {
336
            return this.getNameParts().join('_');
143✔
337
        }
338
    }
339

340
    walk(visitor: WalkVisitor, options: WalkOptions) {
341
        if (options.walkMode & InternalWalkMode.walkExpressions) {
136!
342
            walk(this, 'expression', visitor, options);
136✔
343
        }
344
    }
345
}
346

347
export class DottedGetExpression extends Expression {
1✔
348
    constructor(
349
        readonly obj: Expression,
848✔
350
        readonly name: Identifier,
848✔
351
        /**
352
         * Can either be `.`, or `?.` for optional chaining
353
         */
354
        readonly dot: Token
848✔
355
    ) {
356
        super();
848✔
357
        this.range = util.createRangeFromPositions(this.obj.range.start, this.name.range.end);
848✔
358
    }
359

360
    public readonly range: Range;
361

362
    transpile(state: BrsTranspileState) {
363
        //if the callee starts with a namespace name, transpile the name
364
        if (state.file.calleeStartsWithNamespace(this)) {
205✔
365
            return new NamespacedVariableNameExpression(this as DottedGetExpression | VariableExpression).transpile(state);
3✔
366
        } else {
367
            return [
202✔
368
                ...this.obj.transpile(state),
369
                state.transpileToken(this.dot),
370
                state.transpileToken(this.name)
371
            ];
372
        }
373
    }
374

375
    walk(visitor: WalkVisitor, options: WalkOptions) {
376
        if (options.walkMode & InternalWalkMode.walkExpressions) {
412!
377
            walk(this, 'obj', visitor, options);
412✔
378
        }
379
    }
380
}
381

382
export class XmlAttributeGetExpression extends Expression {
1✔
383
    constructor(
384
        readonly obj: Expression,
8✔
385
        readonly name: Identifier,
8✔
386
        /**
387
         * Can either be `@`, or `?@` for optional chaining
388
         */
389
        readonly at: Token
8✔
390
    ) {
391
        super();
8✔
392
        this.range = util.createRangeFromPositions(this.obj.range.start, this.name.range.end);
8✔
393
    }
394

395
    public readonly range: Range;
396

397
    transpile(state: BrsTranspileState) {
398
        return [
2✔
399
            ...this.obj.transpile(state),
400
            state.transpileToken(this.at),
401
            state.transpileToken(this.name)
402
        ];
403
    }
404

405
    walk(visitor: WalkVisitor, options: WalkOptions) {
406
        if (options.walkMode & InternalWalkMode.walkExpressions) {
3!
407
            walk(this, 'obj', visitor, options);
3✔
408
        }
409
    }
410
}
411

412
export class IndexedGetExpression extends Expression {
1✔
413
    constructor(
414
        public obj: Expression,
96✔
415
        public index: Expression,
96✔
416
        /**
417
         * Can either be `[` or `?[`. If `?.[` is used, this will be `[` and `optionalChainingToken` will be `?.`
418
         */
419
        public openingSquare: Token,
96✔
420
        public closingSquare: Token,
96✔
421
        public questionDotToken?: Token //  ? or ?.
96✔
422
    ) {
423
        super();
96✔
424
        this.range = util.createBoundingRange(this.obj, this.openingSquare, this.questionDotToken, this.openingSquare, this.index, this.closingSquare);
96✔
425
    }
426

427
    public readonly range: Range;
428

429
    transpile(state: BrsTranspileState) {
430
        return [
52✔
431
            ...this.obj.transpile(state),
432
            this.questionDotToken ? state.transpileToken(this.questionDotToken) : '',
52✔
433
            state.transpileToken(this.openingSquare),
434
            ...(this.index?.transpile(state) ?? []),
312!
435
            this.closingSquare ? state.transpileToken(this.closingSquare) : ''
52!
436
        ];
437
    }
438

439
    walk(visitor: WalkVisitor, options: WalkOptions) {
440
        if (options.walkMode & InternalWalkMode.walkExpressions) {
54!
441
            walk(this, 'obj', visitor, options);
54✔
442
            walk(this, 'index', visitor, options);
54✔
443
        }
444
    }
445
}
446

447
export class GroupingExpression extends Expression {
1✔
448
    constructor(
449
        readonly tokens: {
23✔
450
            left: Token;
451
            right: Token;
452
        },
453
        public expression: Expression
23✔
454
    ) {
455
        super();
23✔
456
        this.range = util.createRangeFromPositions(this.tokens.left.range.start, this.tokens.right.range.end);
23✔
457
    }
458

459
    public readonly range: Range;
460

461
    transpile(state: BrsTranspileState) {
462
        return [
4✔
463
            state.transpileToken(this.tokens.left),
464
            ...this.expression.transpile(state),
465
            state.transpileToken(this.tokens.right)
466
        ];
467
    }
468

469
    walk(visitor: WalkVisitor, options: WalkOptions) {
470
        if (options.walkMode & InternalWalkMode.walkExpressions) {
5!
471
            walk(this, 'expression', visitor, options);
5✔
472
        }
473
    }
474
}
475

476
export class LiteralExpression extends Expression {
1✔
477
    constructor(
478
        public token: Token
2,365✔
479
    ) {
480
        super();
2,365✔
481
        this.type = util.tokenToBscType(token);
2,365✔
482
    }
483

484
    public get range() {
485
        return this.token.range;
3,832✔
486
    }
487

488
    /**
489
     * The (data) type of this expression
490
     */
491
    public type: BscType;
492

493
    transpile(state: BrsTranspileState) {
494
        let text: string;
495
        if (this.token.kind === TokenKind.TemplateStringQuasi) {
619✔
496
            //wrap quasis with quotes (and escape inner quotemarks)
497
            text = `"${this.token.text.replace(/"/g, '""')}"`;
24✔
498

499
        } else if (isStringType(this.type)) {
595✔
500
            text = this.token.text;
256✔
501
            //add trailing quotemark if it's missing. We will have already generated a diagnostic for this.
502
            if (text.endsWith('"') === false) {
256✔
503
                text += '"';
1✔
504
            }
505
        } else {
506
            text = this.token.text;
339✔
507
        }
508

509
        return [
619✔
510
            state.sourceNode(this, text)
511
        ];
512
    }
513

514
    walk(visitor: WalkVisitor, options: WalkOptions) {
515
        //nothing to walk
516
    }
517
}
518

519
/**
520
 * This is a special expression only used within template strings. It exists so we can prevent producing lots of empty strings
521
 * during template string transpile by identifying these expressions explicitly and skipping the bslib_toString around them
522
 */
523
export class EscapedCharCodeLiteralExpression extends Expression {
1✔
524
    constructor(
525
        readonly token: Token & { charCode: number }
19✔
526
    ) {
527
        super();
19✔
528
        this.range = token.range;
19✔
529
    }
530
    readonly range: Range;
531

532
    transpile(state: BrsTranspileState) {
533
        return [
12✔
534
            state.sourceNode(this, `chr(${this.token.charCode})`)
535
        ];
536
    }
537

538
    walk(visitor: WalkVisitor, options: WalkOptions) {
539
        //nothing to walk
540
    }
541
}
542

543
export class ArrayLiteralExpression extends Expression {
1✔
544
    constructor(
545
        readonly elements: Array<Expression | CommentStatement>,
70✔
546
        readonly open: Token,
70✔
547
        readonly close: Token,
70✔
548
        readonly hasSpread = false
70✔
549
    ) {
550
        super();
70✔
551
        this.range = util.createBoundingRange(this.open, ...this.elements, this.close);
70✔
552
    }
553

554
    public readonly range: Range;
555

556
    transpile(state: BrsTranspileState) {
557
        let result = [];
33✔
558
        result.push(
33✔
559
            state.transpileToken(this.open)
560
        );
561
        let hasChildren = this.elements.length > 0;
33✔
562
        state.blockDepth++;
33✔
563

564
        for (let i = 0; i < this.elements.length; i++) {
33✔
565
            let previousElement = this.elements[i - 1];
39✔
566
            let element = this.elements[i];
39✔
567

568
            if (isCommentStatement(element)) {
39✔
569
                //if the comment is on the same line as opening square or previous statement, don't add newline
570
                if (util.linesTouch(this.open, element) || util.linesTouch(previousElement, element)) {
5✔
571
                    result.push(' ');
4✔
572
                } else {
573
                    result.push(
1✔
574
                        '\n',
575
                        state.indent()
576
                    );
577
                }
578
                state.lineage.unshift(this);
5✔
579
                result.push(element.transpile(state));
5✔
580
                state.lineage.shift();
5✔
581
            } else {
582
                result.push('\n');
34✔
583

584
                result.push(
34✔
585
                    state.indent(),
586
                    ...element.transpile(state)
587
                );
588
            }
589
        }
590
        state.blockDepth--;
33✔
591
        //add a newline between open and close if there are elements
592
        if (hasChildren) {
33✔
593
            result.push('\n');
13✔
594
            result.push(state.indent());
13✔
595
        }
596
        if (this.close) {
33!
597
            result.push(
33✔
598
                state.transpileToken(this.close)
599
            );
600
        }
601
        return result;
33✔
602
    }
603

604
    walk(visitor: WalkVisitor, options: WalkOptions) {
605
        if (options.walkMode & InternalWalkMode.walkExpressions) {
37!
606
            walkArray(this.elements, visitor, options, this);
37✔
607
        }
608
    }
609
}
610

611
export class AAMemberExpression extends Expression {
1✔
612
    constructor(
613
        public keyToken: Token,
181✔
614
        public colonToken: Token,
181✔
615
        /** The expression evaluated to determine the member's initial value. */
616
        public value: Expression
181✔
617
    ) {
618
        super();
181✔
619
        this.range = util.createRangeFromPositions(keyToken.range.start, this.value.range.end);
181✔
620
    }
621

622
    public range: Range;
623
    public commaToken?: Token;
624

625
    transpile(state: BrsTranspileState) {
626
        //TODO move the logic from AALiteralExpression loop into this function
627
        return [];
×
628
    }
629

630
    walk(visitor: WalkVisitor, options: WalkOptions) {
631
        walk(this, 'value', visitor, options);
61✔
632
    }
633

634
}
635

636
export class AALiteralExpression extends Expression {
1✔
637
    constructor(
638
        readonly elements: Array<AAMemberExpression | CommentStatement>,
181✔
639
        readonly open: Token,
181✔
640
        readonly close: Token
181✔
641
    ) {
642
        super();
181✔
643
        this.range = util.createBoundingRange(this.open, ...this.elements, this.close);
181✔
644
    }
645

646
    public readonly range: Range;
647

648
    transpile(state: BrsTranspileState) {
649
        let result = [];
49✔
650
        //open curly
651
        result.push(
49✔
652
            state.transpileToken(this.open)
653
        );
654
        let hasChildren = this.elements.length > 0;
49✔
655
        //add newline if the object has children and the first child isn't a comment starting on the same line as opening curly
656
        if (hasChildren && (isCommentStatement(this.elements[0]) === false || !util.linesTouch(this.elements[0], this.open))) {
49✔
657
            result.push('\n');
20✔
658
        }
659
        state.blockDepth++;
49✔
660
        for (let i = 0; i < this.elements.length; i++) {
49✔
661
            let element = this.elements[i];
44✔
662
            let previousElement = this.elements[i - 1];
44✔
663
            let nextElement = this.elements[i + 1];
44✔
664

665
            //don't indent if comment is same-line
666
            if (isCommentStatement(element as any) &&
44✔
667
                (util.linesTouch(this.open, element) || util.linesTouch(previousElement, element))
668
            ) {
669
                result.push(' ');
10✔
670

671
                //indent line
672
            } else {
673
                result.push(state.indent());
34✔
674
            }
675

676
            //render comments
677
            if (isCommentStatement(element)) {
44✔
678
                result.push(...element.transpile(state));
13✔
679
            } else {
680
                //key
681
                result.push(
31✔
682
                    state.transpileToken(element.keyToken)
683
                );
684
                //colon
685
                result.push(
31✔
686
                    state.transpileToken(element.colonToken),
687
                    ' '
688
                );
689

690
                //value
691
                result.push(...element.value.transpile(state));
31✔
692
            }
693

694

695
            //if next element is a same-line comment, skip the newline
696
            if (nextElement && isCommentStatement(nextElement) && nextElement.range.start.line === element.range.start.line) {
44✔
697

698
                //add a newline between statements
699
            } else {
700
                result.push('\n');
36✔
701
            }
702
        }
703
        state.blockDepth--;
49✔
704

705
        //only indent the closing curly if we have children
706
        if (hasChildren) {
49✔
707
            result.push(state.indent());
22✔
708
        }
709
        //close curly
710
        if (this.close) {
49!
711
            result.push(
49✔
712
                state.transpileToken(this.close)
713
            );
714
        }
715
        return result;
49✔
716
    }
717

718
    walk(visitor: WalkVisitor, options: WalkOptions) {
719
        if (options.walkMode & InternalWalkMode.walkExpressions) {
71!
720
            walkArray(this.elements, visitor, options, this);
71✔
721
        }
722
    }
723
}
724

725
export class UnaryExpression extends Expression {
1✔
726
    constructor(
727
        public operator: Token,
22✔
728
        public right: Expression
22✔
729
    ) {
730
        super();
22✔
731
        this.range = util.createRangeFromPositions(this.operator.range.start, this.right.range.end);
22✔
732
    }
733

734
    public readonly range: Range;
735

736
    transpile(state: BrsTranspileState) {
737
        return [
2✔
738
            state.transpileToken(this.operator),
739
            ' ',
740
            ...this.right.transpile(state)
741
        ];
742
    }
743

744
    walk(visitor: WalkVisitor, options: WalkOptions) {
745
        if (options.walkMode & InternalWalkMode.walkExpressions) {
6!
746
            walk(this, 'right', visitor, options);
6✔
747
        }
748
    }
749
}
750

751
export class VariableExpression extends Expression {
1✔
752
    constructor(
753
        readonly name: Identifier
1,516✔
754
    ) {
755
        super();
1,516✔
756
        this.range = this.name.range;
1,516✔
757
    }
758

759
    public readonly range: Range;
760

761
    public getName(parseMode: ParseMode) {
762
        return this.name.text;
15✔
763
    }
764

765
    transpile(state: BrsTranspileState) {
766
        let result = [];
289✔
767
        const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
289✔
768
        //if the callee is the name of a known namespace function
769
        if (state.file.calleeIsKnownNamespaceFunction(this, namespace?.getName(ParseMode.BrighterScript))) {
289✔
770
            result.push(
3✔
771
                state.sourceNode(this, [
772
                    namespace.getName(ParseMode.BrightScript),
773
                    '_',
774
                    this.getName(ParseMode.BrightScript)
775
                ])
776
            );
777

778
            //transpile  normally
779
        } else {
780
            result.push(
286✔
781
                state.transpileToken(this.name)
782
            );
783
        }
784
        return result;
289✔
785
    }
786

787
    walk(visitor: WalkVisitor, options: WalkOptions) {
788
        //nothing to walk
789
    }
790
}
791

792
export class SourceLiteralExpression extends Expression {
1✔
793
    constructor(
794
        readonly token: Token
24✔
795
    ) {
796
        super();
24✔
797
        this.range = token.range;
24✔
798
    }
799

800
    public readonly range: Range;
801

802
    private getFunctionName(state: BrsTranspileState, parseMode: ParseMode) {
803
        let func = state.file.getFunctionScopeAtPosition(this.token.range.start).func;
6✔
804
        let nameParts = [];
6✔
805
        while (func.parentFunction) {
6✔
806
            let index = func.parentFunction.childFunctionExpressions.indexOf(func);
4✔
807
            nameParts.unshift(`anon${index}`);
4✔
808
            func = func.parentFunction;
4✔
809
        }
810
        //get the index of this function in its parent
811
        nameParts.unshift(
6✔
812
            func.functionStatement.getName(parseMode)
813
        );
814
        return nameParts.join('$');
6✔
815
    }
816

817
    transpile(state: BrsTranspileState) {
818
        let text: string;
819
        switch (this.token.kind) {
21✔
820
            case TokenKind.SourceFilePathLiteral:
27✔
821
                const pathUrl = fileUrl(state.srcPath);
2✔
822
                text = `"${pathUrl.substring(0, 4)}" + "${pathUrl.substring(4)}"`;
2✔
823
                break;
2✔
824
            case TokenKind.SourceLineNumLiteral:
825
                text = `${this.token.range.start.line + 1}`;
3✔
826
                break;
3✔
827
            case TokenKind.FunctionNameLiteral:
828
                text = `"${this.getFunctionName(state, ParseMode.BrightScript)}"`;
3✔
829
                break;
3✔
830
            case TokenKind.SourceFunctionNameLiteral:
831
                text = `"${this.getFunctionName(state, ParseMode.BrighterScript)}"`;
3✔
832
                break;
3✔
833
            case TokenKind.SourceLocationLiteral:
834
                const locationUrl = fileUrl(state.srcPath);
2✔
835
                text = `"${locationUrl.substring(0, 4)}" + "${locationUrl.substring(4)}:${this.token.range.start.line + 1}"`;
2✔
836
                break;
2✔
837
            case TokenKind.PkgPathLiteral:
838
                let pkgPath1 = `pkg:/${state.file.pkgPath}`
1✔
839
                    .replace(/\\/g, '/')
840
                    .replace(/\.bs$/i, '.brs');
841

842
                text = `"${pkgPath1}"`;
1✔
843
                break;
1✔
844
            case TokenKind.PkgLocationLiteral:
845
                let pkgPath2 = `pkg:/${state.file.pkgPath}`
1✔
846
                    .replace(/\\/g, '/')
847
                    .replace(/\.bs$/i, '.brs');
848

849
                text = `"${pkgPath2}:" + str(LINE_NUM)`;
1✔
850
                break;
1✔
851
            case TokenKind.LineNumLiteral:
852
            default:
853
                //use the original text (because it looks like a variable)
854
                text = this.token.text;
6✔
855
                break;
6✔
856

857
        }
858
        return [
21✔
859
            state.sourceNode(this, text)
860
        ];
861
    }
862

863
    walk(visitor: WalkVisitor, options: WalkOptions) {
864
        //nothing to walk
865
    }
866
}
867

868
/**
869
 * This expression transpiles and acts exactly like a CallExpression,
870
 * except we need to uniquely identify these statements so we can
871
 * do more type checking.
872
 */
873
export class NewExpression extends Expression {
1✔
874
    constructor(
875
        readonly newKeyword: Token,
36✔
876
        readonly call: CallExpression
36✔
877
    ) {
878
        super();
36✔
879
        this.range = util.createRangeFromPositions(this.newKeyword.range.start, this.call.range.end);
36✔
880
    }
881

882
    /**
883
     * The name of the class to initialize (with optional namespace prefixed)
884
     */
885
    public get className() {
886
        //the parser guarantees the callee of a new statement's call object will be
887
        //a NamespacedVariableNameExpression
888
        return this.call.callee as NamespacedVariableNameExpression;
39✔
889
    }
890

891
    public readonly range: Range;
892

893
    public transpile(state: BrsTranspileState) {
894
        const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
9✔
895
        const cls = state.file.getClassFileLink(
9✔
896
            this.className.getName(ParseMode.BrighterScript),
897
            namespace?.getName(ParseMode.BrighterScript)
27✔
898
        )?.item;
9✔
899
        //new statements within a namespace block can omit the leading namespace if the class resides in that same namespace.
900
        //So we need to figure out if this is a namespace-omitted class, or if this class exists without a namespace.
901
        return this.call.transpile(state, cls?.getName(ParseMode.BrightScript));
9✔
902
    }
903

904
    walk(visitor: WalkVisitor, options: WalkOptions) {
905
        if (options.walkMode & InternalWalkMode.walkExpressions) {
31!
906
            walk(this, 'call', visitor, options);
31✔
907
        }
908
    }
909
}
910

911
export class CallfuncExpression extends Expression {
1✔
912
    constructor(
913
        readonly callee: Expression,
17✔
914
        readonly operator: Token,
17✔
915
        readonly methodName: Identifier,
17✔
916
        readonly openingParen: Token,
17✔
917
        readonly args: Expression[],
17✔
918
        readonly closingParen: Token
17✔
919
    ) {
920
        super();
17✔
921
        this.range = util.createRangeFromPositions(
17✔
922
            callee.range.start,
923
            (closingParen ?? args[args.length - 1] ?? openingParen ?? methodName ?? operator).range.end
204!
924
        );
925
    }
926

927
    public readonly range: Range;
928

929
    public transpile(state: BrsTranspileState) {
930
        let result = [];
6✔
931
        result.push(
6✔
932
            ...this.callee.transpile(state),
933
            state.sourceNode(this.operator, '.callfunc'),
934
            state.transpileToken(this.openingParen),
935
            //the name of the function
936
            state.sourceNode(this.methodName, ['"', this.methodName.text, '"']),
937
            ', '
938
        );
939
        //transpile args
940
        //callfunc with zero args never gets called, so pass invalid as the first parameter if there are no args
941
        if (this.args.length === 0) {
6✔
942
            result.push('invalid');
4✔
943
        } else {
944
            for (let i = 0; i < this.args.length; i++) {
2✔
945
                //add comma between args
946
                if (i > 0) {
4✔
947
                    result.push(', ');
2✔
948
                }
949
                let arg = this.args[i];
4✔
950
                result.push(...arg.transpile(state));
4✔
951
            }
952
        }
953
        result.push(
6✔
954
            state.transpileToken(this.closingParen)
955
        );
956
        return result;
6✔
957
    }
958

959
    walk(visitor: WalkVisitor, options: WalkOptions) {
960
        if (options.walkMode & InternalWalkMode.walkExpressions) {
14!
961
            walk(this, 'callee', visitor, options);
14✔
962
            walkArray(this.args, visitor, options, this);
14✔
963
        }
964
    }
965
}
966

967
/**
968
 * Since template strings can contain newlines, we need to concatenate multiple strings together with chr() calls.
969
 * This is a single expression that represents the string contatenation of all parts of a single quasi.
970
 */
971
export class TemplateStringQuasiExpression extends Expression {
1✔
972
    constructor(
973
        readonly expressions: Array<LiteralExpression | EscapedCharCodeLiteralExpression>
59✔
974
    ) {
975
        super();
59✔
976
        this.range = util.createRangeFromPositions(
59✔
977
            this.expressions[0].range.start,
978
            this.expressions[this.expressions.length - 1].range.end
979
        );
980
    }
981
    readonly range: Range;
982

983
    transpile(state: BrsTranspileState, skipEmptyStrings = true) {
32✔
984
        let result = [];
37✔
985
        let plus = '';
37✔
986
        for (let expression of this.expressions) {
37✔
987
            //skip empty strings
988
            //TODO what does an empty string literal expression look like?
989
            if (expression.token.text === '' && skipEmptyStrings === true) {
60✔
990
                continue;
24✔
991
            }
992
            result.push(
36✔
993
                plus,
994
                ...expression.transpile(state)
995
            );
996
            plus = ' + ';
36✔
997
        }
998
        return result;
37✔
999
    }
1000

1001
    walk(visitor: WalkVisitor, options: WalkOptions) {
1002
        if (options.walkMode & InternalWalkMode.walkExpressions) {
45!
1003
            walkArray(this.expressions, visitor, options, this);
45✔
1004
        }
1005
    }
1006
}
1007

1008
export class TemplateStringExpression extends Expression {
1✔
1009
    constructor(
1010
        readonly openingBacktick: Token,
28✔
1011
        readonly quasis: TemplateStringQuasiExpression[],
28✔
1012
        readonly expressions: Expression[],
28✔
1013
        readonly closingBacktick: Token
28✔
1014
    ) {
1015
        super();
28✔
1016
        this.range = util.createRangeFromPositions(
28✔
1017
            quasis[0].range.start,
1018
            quasis[quasis.length - 1].range.end
1019
        );
1020
    }
1021

1022
    public readonly range: Range;
1023

1024
    transpile(state: BrsTranspileState) {
1025
        if (this.quasis.length === 1 && this.expressions.length === 0) {
19✔
1026
            return this.quasis[0].transpile(state);
10✔
1027
        }
1028
        let result = [];
9✔
1029
        let plus = '';
9✔
1030
        //helper function to figure out when to include the plus
1031
        function add(...items) {
1032
            if (items.length > 0) {
35✔
1033
                result.push(
25✔
1034
                    plus,
1035
                    ...items
1036
                );
1037
            }
1038
            //set the plus after the first occurance of a nonzero length set of items
1039
            if (plus === '' && items.length > 0) {
35✔
1040
                plus = ' + ';
9✔
1041
            }
1042
        }
1043

1044
        for (let i = 0; i < this.quasis.length; i++) {
9✔
1045
            let quasi = this.quasis[i];
22✔
1046
            let expression = this.expressions[i];
22✔
1047

1048
            add(
22✔
1049
                ...quasi.transpile(state)
1050
            );
1051
            if (expression) {
22✔
1052
                //skip the toString wrapper around certain expressions
1053
                if (
13✔
1054
                    isEscapedCharCodeLiteralExpression(expression) ||
29✔
1055
                    (isLiteralExpression(expression) && isStringType(expression.type))
1056
                ) {
1057
                    add(
3✔
1058
                        ...expression.transpile(state)
1059
                    );
1060

1061
                    //wrap all other expressions with a bslib_toString call to prevent runtime type mismatch errors
1062
                } else {
1063
                    add(
10✔
1064
                        state.bslibPrefix + '_toString(',
1065
                        ...expression.transpile(state),
1066
                        ')'
1067
                    );
1068
                }
1069
            }
1070
        }
1071

1072
        return result;
9✔
1073
    }
1074

1075
    walk(visitor: WalkVisitor, options: WalkOptions) {
1076
        if (options.walkMode & InternalWalkMode.walkExpressions) {
23!
1077
            //walk the quasis and expressions in left-to-right order
1078
            for (let i = 0; i < this.quasis.length; i++) {
23✔
1079
                walk(this.quasis, i, visitor, options, this);
38✔
1080

1081
                //this skips the final loop iteration since we'll always have one more quasi than expression
1082
                if (this.expressions[i]) {
38✔
1083
                    walk(this.expressions, i, visitor, options, this);
15✔
1084
                }
1085
            }
1086
        }
1087
    }
1088
}
1089

1090
export class TaggedTemplateStringExpression extends Expression {
1✔
1091
    constructor(
1092
        readonly tagName: Identifier,
4✔
1093
        readonly openingBacktick: Token,
4✔
1094
        readonly quasis: TemplateStringQuasiExpression[],
4✔
1095
        readonly expressions: Expression[],
4✔
1096
        readonly closingBacktick: Token
4✔
1097
    ) {
1098
        super();
4✔
1099
        this.range = util.createRangeFromPositions(
4✔
1100
            quasis[0].range.start,
1101
            quasis[quasis.length - 1].range.end
1102
        );
1103
    }
1104

1105
    public readonly range: Range;
1106

1107
    transpile(state: BrsTranspileState) {
1108
        let result = [];
2✔
1109
        result.push(
2✔
1110
            state.transpileToken(this.tagName),
1111
            '(['
1112
        );
1113

1114
        //add quasis as the first array
1115
        for (let i = 0; i < this.quasis.length; i++) {
2✔
1116
            let quasi = this.quasis[i];
5✔
1117
            //separate items with a comma
1118
            if (i > 0) {
5✔
1119
                result.push(
3✔
1120
                    ', '
1121
                );
1122
            }
1123
            result.push(
5✔
1124
                ...quasi.transpile(state, false)
1125
            );
1126
        }
1127
        result.push(
2✔
1128
            '], ['
1129
        );
1130

1131
        //add expressions as the second array
1132
        for (let i = 0; i < this.expressions.length; i++) {
2✔
1133
            let expression = this.expressions[i];
3✔
1134
            if (i > 0) {
3✔
1135
                result.push(
1✔
1136
                    ', '
1137
                );
1138
            }
1139
            result.push(
3✔
1140
                ...expression.transpile(state)
1141
            );
1142
        }
1143
        result.push(
2✔
1144
            state.sourceNode(this.closingBacktick, '])')
1145
        );
1146
        return result;
2✔
1147
    }
1148

1149
    walk(visitor: WalkVisitor, options: WalkOptions) {
1150
        if (options.walkMode & InternalWalkMode.walkExpressions) {
3!
1151
            //walk the quasis and expressions in left-to-right order
1152
            for (let i = 0; i < this.quasis.length; i++) {
3✔
1153
                walk(this.quasis, i, visitor, options, this);
7✔
1154

1155
                //this skips the final loop iteration since we'll always have one more quasi than expression
1156
                if (this.expressions[i]) {
7✔
1157
                    walk(this.expressions, i, visitor, options, this);
4✔
1158
                }
1159
            }
1160
        }
1161
    }
1162
}
1163

1164
export class AnnotationExpression extends Expression {
1✔
1165
    constructor(
1166
        readonly atToken: Token,
44✔
1167
        readonly nameToken: Token
44✔
1168
    ) {
1169
        super();
44✔
1170
        this.name = nameToken.text;
44✔
1171
        this.range = util.createRangeFromPositions(
43✔
1172
            atToken.range.start,
1173
            nameToken.range.end
1174
        );
1175
    }
1176

1177
    public name: string;
1178
    public range: Range;
1179
    public call: CallExpression;
1180

1181
    /**
1182
     * Convert annotation arguments to JavaScript types
1183
     * @param strict If false, keep Expression objects not corresponding to JS types
1184
     */
1185
    getArguments(strict = true): ExpressionValue[] {
3✔
1186
        if (!this.call) {
4✔
1187
            return [];
1✔
1188
        }
1189
        return this.call.args.map(e => expressionToValue(e, strict));
13✔
1190
    }
1191

1192
    transpile(state: BrsTranspileState) {
1193
        return [];
3✔
1194
    }
1195

1196
    walk(visitor: WalkVisitor, options: WalkOptions) {
1197
        //nothing to walk
1198
    }
1199
    getTypedef(state: BrsTranspileState) {
1200
        return [
9✔
1201
            '@',
1202
            this.name,
1203
            ...(this.call?.transpile(state) ?? [])
54✔
1204
        ];
1205
    }
1206
}
1207

1208
export class TernaryExpression extends Expression {
1✔
1209
    constructor(
1210
        readonly test: Expression,
69✔
1211
        readonly questionMarkToken: Token,
69✔
1212
        readonly consequent?: Expression,
69✔
1213
        readonly colonToken?: Token,
69✔
1214
        readonly alternate?: Expression
69✔
1215
    ) {
1216
        super();
69✔
1217
        this.range = util.createRangeFromPositions(
69✔
1218
            test.range.start,
1219
            (alternate ?? colonToken ?? consequent ?? questionMarkToken ?? test).range.end
828!
1220
        );
1221
    }
1222

1223
    public range: Range;
1224

1225
    transpile(state: BrsTranspileState) {
1226
        let result = [];
23✔
1227
        let consequentInfo = util.getExpressionInfo(this.consequent);
23✔
1228
        let alternateInfo = util.getExpressionInfo(this.alternate);
23✔
1229

1230
        //get all unique variable names used in the consequent and alternate, and sort them alphabetically so the output is consistent
1231
        let allUniqueVarNames = [...new Set([...consequentInfo.uniqueVarNames, ...alternateInfo.uniqueVarNames])].sort();
23✔
1232
        let mutatingExpressions = [
23✔
1233
            ...consequentInfo.expressions,
1234
            ...alternateInfo.expressions
1235
        ].filter(e => e instanceof CallExpression || e instanceof CallfuncExpression || e instanceof DottedGetExpression);
106✔
1236

1237
        if (mutatingExpressions.length > 0) {
23✔
1238
            result.push(
5✔
1239
                state.sourceNode(
1240
                    this.questionMarkToken,
1241
                    //write all the scope variables as parameters.
1242
                    //TODO handle when there are more than 31 parameters
1243
                    `(function(__bsCondition, ${allUniqueVarNames.join(', ')})`
1244
                ),
1245
                state.newline,
1246
                //double indent so our `end function` line is still indented one at the end
1247
                state.indent(2),
1248
                state.sourceNode(this.test, `if __bsCondition then`),
1249
                state.newline,
1250
                state.indent(1),
1251
                state.sourceNode(this.consequent ?? this.questionMarkToken, 'return '),
15!
1252
                ...this.consequent?.transpile(state) ?? [state.sourceNode(this.questionMarkToken, 'invalid')],
30!
1253
                state.newline,
1254
                state.indent(-1),
1255
                state.sourceNode(this.consequent ?? this.questionMarkToken, 'else'),
15!
1256
                state.newline,
1257
                state.indent(1),
1258
                state.sourceNode(this.consequent ?? this.questionMarkToken, 'return '),
15!
1259
                ...this.alternate?.transpile(state) ?? [state.sourceNode(this.consequent ?? this.questionMarkToken, 'invalid')],
30!
1260
                state.newline,
1261
                state.indent(-1),
1262
                state.sourceNode(this.questionMarkToken, 'end if'),
1263
                state.newline,
1264
                state.indent(-1),
1265
                state.sourceNode(this.questionMarkToken, 'end function)('),
1266
                ...this.test.transpile(state),
1267
                state.sourceNode(this.questionMarkToken, `, ${allUniqueVarNames.join(', ')})`)
1268
            );
1269
            state.blockDepth--;
5✔
1270
        } else {
1271
            result.push(
18✔
1272
                state.sourceNode(this.test, state.bslibPrefix + `_ternary(`),
1273
                ...this.test.transpile(state),
1274
                state.sourceNode(this.test, `, `),
1275
                ...this.consequent?.transpile(state) ?? ['invalid'],
108✔
1276
                `, `,
1277
                ...this.alternate?.transpile(state) ?? ['invalid'],
108✔
1278
                `)`
1279
            );
1280
        }
1281
        return result;
23✔
1282
    }
1283

1284
    public walk(visitor: WalkVisitor, options: WalkOptions) {
1285
        if (options.walkMode & InternalWalkMode.walkExpressions) {
24!
1286
            walk(this, 'test', visitor, options);
24✔
1287
            walk(this, 'consequent', visitor, options);
24✔
1288
            walk(this, 'alternate', visitor, options);
24✔
1289
        }
1290
    }
1291
}
1292

1293
export class NullCoalescingExpression extends Expression {
1✔
1294
    constructor(
1295
        public consequent: Expression,
22✔
1296
        public questionQuestionToken: Token,
22✔
1297
        public alternate: Expression
22✔
1298
    ) {
1299
        super();
22✔
1300
        this.range = util.createRangeFromPositions(
22✔
1301
            consequent.range.start,
1302
            (alternate ?? questionQuestionToken ?? consequent).range.end
132!
1303
        );
1304
    }
1305
    public readonly range: Range;
1306

1307
    transpile(state: BrsTranspileState) {
1308
        let result = [];
6✔
1309
        let consequentInfo = util.getExpressionInfo(this.consequent);
6✔
1310
        let alternateInfo = util.getExpressionInfo(this.alternate);
6✔
1311

1312
        //get all unique variable names used in the consequent and alternate, and sort them alphabetically so the output is consistent
1313
        let allUniqueVarNames = [...new Set([...consequentInfo.uniqueVarNames, ...alternateInfo.uniqueVarNames])].sort();
6✔
1314
        let hasMutatingExpression = [
6✔
1315
            ...consequentInfo.expressions,
1316
            ...alternateInfo.expressions
1317
        ].find(e => isCallExpression(e) || isCallfuncExpression(e) || isDottedGetExpression(e));
19✔
1318

1319
        if (hasMutatingExpression) {
6✔
1320
            result.push(
3✔
1321
                `(function(`,
1322
                //write all the scope variables as parameters.
1323
                //TODO handle when there are more than 31 parameters
1324
                allUniqueVarNames.join(', '),
1325
                ')',
1326
                state.newline,
1327
                //double indent so our `end function` line is still indented one at the end
1328
                state.indent(2),
1329
                //evaluate the consequent exactly once, and then use it in the following condition
1330
                `__bsConsequent = `,
1331
                ...this.consequent.transpile(state),
1332
                state.newline,
1333
                state.indent(),
1334
                `if __bsConsequent <> invalid then`,
1335
                state.newline,
1336
                state.indent(1),
1337
                'return __bsConsequent',
1338
                state.newline,
1339
                state.indent(-1),
1340
                'else',
1341
                state.newline,
1342
                state.indent(1),
1343
                'return ',
1344
                ...this.alternate.transpile(state),
1345
                state.newline,
1346
                state.indent(-1),
1347
                'end if',
1348
                state.newline,
1349
                state.indent(-1),
1350
                'end function)(',
1351
                allUniqueVarNames.join(', '),
1352
                ')'
1353
            );
1354
            state.blockDepth--;
3✔
1355
        } else {
1356
            result.push(
3✔
1357
                state.bslibPrefix + `_coalesce(`,
1358
                ...this.consequent.transpile(state),
1359
                ', ',
1360
                ...this.alternate.transpile(state),
1361
                ')'
1362
            );
1363
        }
1364
        return result;
6✔
1365
    }
1366

1367
    public walk(visitor: WalkVisitor, options: WalkOptions) {
1368
        if (options.walkMode & InternalWalkMode.walkExpressions) {
6!
1369
            walk(this, 'consequent', visitor, options);
6✔
1370
            walk(this, 'alternate', visitor, options);
6✔
1371
        }
1372
    }
1373
}
1374

1375
export class RegexLiteralExpression extends Expression {
1✔
1376
    public constructor(
1377
        public tokens: {
42✔
1378
            regexLiteral: Token;
1379
        }
1380
    ) {
1381
        super();
42✔
1382
    }
1383

1384
    public get range() {
1385
        return this.tokens.regexLiteral.range;
50✔
1386
    }
1387

1388
    public transpile(state: BrsTranspileState): TranspileResult {
1389
        let text = this.tokens.regexLiteral?.text ?? '';
41!
1390
        let flags = '';
41✔
1391
        //get any flags from the end
1392
        const flagMatch = /\/([a-z]+)$/i.exec(text);
41✔
1393
        if (flagMatch) {
41✔
1394
            text = text.substring(0, flagMatch.index + 1);
1✔
1395
            flags = flagMatch[1];
1✔
1396
        }
1397
        let pattern = text
41✔
1398
            //remove leading and trailing slashes
1399
            .substring(1, text.length - 1)
1400
            //escape quotemarks
1401
            .split('"').join('" + chr(34) + "');
1402

1403
        return [
41✔
1404
            state.sourceNode(this.tokens.regexLiteral, [
1405
                'CreateObject("roRegex", ',
1406
                `"${pattern}", `,
1407
                `"${flags}"`,
1408
                ')'
1409
            ])
1410
        ];
1411
    }
1412

1413
    walk(visitor: WalkVisitor, options: WalkOptions) {
1414
        //nothing to walk
1415
    }
1416
}
1417

1418
// eslint-disable-next-line @typescript-eslint/consistent-indexed-object-style
1419
type ExpressionValue = string | number | boolean | Expression | ExpressionValue[] | { [key: string]: ExpressionValue };
1420

1421
function expressionToValue(expr: Expression, strict: boolean): ExpressionValue {
1422
    if (!expr) {
19!
1423
        return null;
×
1424
    }
1425
    if (isUnaryExpression(expr) && isLiteralNumber(expr.right)) {
19✔
1426
        return numberExpressionToValue(expr.right, expr.operator.text);
1✔
1427
    }
1428
    if (isLiteralString(expr)) {
18✔
1429
        //remove leading and trailing quotes
1430
        return expr.token.text.replace(/^"/, '').replace(/"$/, '');
4✔
1431
    }
1432
    if (isLiteralNumber(expr)) {
14✔
1433
        return numberExpressionToValue(expr);
6✔
1434
    }
1435

1436
    if (isLiteralBoolean(expr)) {
8✔
1437
        return expr.token.text.toLowerCase() === 'true';
2✔
1438
    }
1439
    if (isArrayLiteralExpression(expr)) {
6✔
1440
        return expr.elements
2✔
1441
            .filter(e => !isCommentStatement(e))
4✔
1442
            .map(e => expressionToValue(e, strict));
4✔
1443
    }
1444
    if (isAALiteralExpression(expr)) {
4✔
1445
        return expr.elements.reduce((acc, e) => {
2✔
1446
            if (!isCommentStatement(e)) {
2!
1447
                acc[e.keyToken.text] = expressionToValue(e.value, strict);
2✔
1448
            }
1449
            return acc;
2✔
1450
        }, {});
1451
    }
1452
    return strict ? null : expr;
2✔
1453
}
1454

1455
function numberExpressionToValue(expr: LiteralExpression, operator = '') {
6✔
1456
    if (isIntegerType(expr.type) || isLongIntegerType(expr.type)) {
7!
1457
        return parseInt(operator + expr.token.text);
7✔
1458
    } else {
1459
        return parseFloat(operator + expr.token.text);
×
1460
    }
1461
}
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