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

rokucommunity / brighterscript / #15021

11 Dec 2025 06:16PM UTC coverage: 88.959% (-0.002%) from 88.961%
#15021

push

web-flow
Backports TypeStatement syntax from v1 to v0 (#1600)

7877 of 9338 branches covered (84.35%)

Branch coverage included in aggregate %.

51 of 54 new or added lines in 6 files covered. (94.44%)

1 existing line in 1 file now uncovered.

10090 of 10859 relevant lines covered (92.92%)

1900.91 hits per line

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

92.15
/src/parser/Parser.ts
1
import type { Token, Identifier } from '../lexer/Token';
2
import { isToken } from '../lexer/Token';
1✔
3
import type { BlockTerminator } from '../lexer/TokenKind';
4
import { Lexer } from '../lexer/Lexer';
1✔
5
import {
1✔
6
    AllowedLocalIdentifiers,
7
    AllowedProperties,
8
    AssignmentOperators,
9
    BrighterScriptSourceLiterals,
10
    DeclarableTypes,
11
    DisallowedFunctionIdentifiersText,
12
    DisallowedLocalIdentifiersText,
13
    TokenKind
14
} from '../lexer/TokenKind';
15
import type {
16
    PrintSeparatorSpace,
17
    PrintSeparatorTab
18
} from './Statement';
19
import {
1✔
20
    AssignmentStatement,
21
    Block,
22
    Body,
23
    CatchStatement,
24
    ContinueStatement,
25
    ClassStatement,
26
    ConstStatement,
27
    CommentStatement,
28
    DimStatement,
29
    DottedSetStatement,
30
    EndStatement,
31
    EnumMemberStatement,
32
    EnumStatement,
33
    ExitForStatement,
34
    ExitWhileStatement,
35
    ExpressionStatement,
36
    FieldStatement,
37
    ForEachStatement,
38
    ForStatement,
39
    FunctionStatement,
40
    GotoStatement,
41
    IfStatement,
42
    ImportStatement,
43
    IncrementStatement,
44
    IndexedSetStatement,
45
    InterfaceFieldStatement,
46
    InterfaceMethodStatement,
47
    InterfaceStatement,
48
    LabelStatement,
49
    LibraryStatement,
50
    MethodStatement,
51
    NamespaceStatement,
52
    PrintStatement,
53
    ReturnStatement,
54
    StopStatement,
55
    ThrowStatement,
56
    TryCatchStatement,
57
    WhileStatement,
58
    TypecastStatement,
59
    AliasStatement,
60
    TypeStatement
61
} from './Statement';
62
import type { DiagnosticInfo } from '../DiagnosticMessages';
63
import { DiagnosticMessages } from '../DiagnosticMessages';
1✔
64
import { util } from '../util';
1✔
65
import {
1✔
66
    AALiteralExpression,
67
    AAMemberExpression,
68
    AnnotationExpression,
69
    ArrayLiteralExpression,
70
    BinaryExpression,
71
    CallExpression,
72
    CallfuncExpression,
73
    DottedGetExpression,
74
    EscapedCharCodeLiteralExpression,
75
    FunctionExpression,
76
    FunctionParameterExpression,
77
    GroupingExpression,
78
    IndexedGetExpression,
79
    LiteralExpression,
80
    NamespacedVariableNameExpression,
81
    NewExpression,
82
    NullCoalescingExpression,
83
    RegexLiteralExpression,
84
    SourceLiteralExpression,
85
    TaggedTemplateStringExpression,
86
    TemplateStringExpression,
87
    TemplateStringQuasiExpression,
88
    TernaryExpression,
89
    TypeCastExpression,
90
    UnaryExpression,
91
    VariableExpression,
92
    XmlAttributeGetExpression
93
} from './Expression';
94
import type { Diagnostic, Range } from 'vscode-languageserver';
95
import type { Logger } from '../logging';
96
import { createLogger } from '../logging';
1✔
97
import { isAAMemberExpression, isAnnotationExpression, isBinaryExpression, isCallExpression, isCallfuncExpression, isMethodStatement, isCommentStatement, isDottedGetExpression, isIfStatement, isIndexedGetExpression, isVariableExpression, isXmlAttributeGetExpression } from '../astUtils/reflection';
1✔
98
import { createVisitor, WalkMode } from '../astUtils/visitors';
1✔
99
import { createStringLiteral, createToken } from '../astUtils/creators';
1✔
100
import { Cache } from '../Cache';
1✔
101
import type { Expression, Statement } from './AstNode';
102
import { SymbolTable } from '../SymbolTable';
1✔
103
import type { BscType } from '../types/BscType';
104

105
export class Parser {
1✔
106
    /**
107
     * The array of tokens passed to `parse()`
108
     */
109
    public tokens = [] as Token[];
2,236✔
110

111
    /**
112
     * The current token index
113
     */
114
    public current: number;
115

116
    /**
117
     * The list of statements for the parsed file
118
     */
119
    public ast = new Body([]);
2,236✔
120

121
    public get statements() {
122
        return this.ast.statements;
497✔
123
    }
124

125
    /**
126
     * The top-level symbol table for the body of this file.
127
     */
128
    public get symbolTable() {
129
        return this.ast.symbolTable;
8,668✔
130
    }
131

132
    /**
133
     * References for significant statements/expressions in the parser.
134
     * These are initially extracted during parse-time to improve performance, but will also be dynamically regenerated if need be.
135
     *
136
     * If a plugin modifies the AST, then the plugin should call Parser#invalidateReferences() to force this object to refresh
137
     */
138
    public get references() {
139
        //build the references object if it's missing.
140
        if (!this._references) {
48,301✔
141
            this.findReferences();
7✔
142
        }
143
        return this._references;
48,301✔
144
    }
145

146
    private _references = new References();
2,236✔
147

148
    /**
149
     * Invalidates (clears) the references collection. This should be called anytime the AST has been manipulated.
150
     */
151
    invalidateReferences() {
152
        this._references = undefined;
7✔
153
    }
154

155
    private addPropertyHints(item: Token | AALiteralExpression) {
156
        if (isToken(item)) {
1,227✔
157
            const name = item.text;
1,013✔
158
            this._references.propertyHints[name.toLowerCase()] = name;
1,013✔
159
        } else {
160
            for (const member of item.elements) {
214✔
161
                if (!isCommentStatement(member)) {
248✔
162
                    const name = member.keyToken.text;
226✔
163
                    if (!name.startsWith('"')) {
226✔
164
                        this._references.propertyHints[name.toLowerCase()] = name;
194✔
165
                    }
166
                }
167
            }
168
        }
169
    }
170

171
    /**
172
     * The list of diagnostics found during the parse process
173
     */
174
    public diagnostics: Diagnostic[];
175

176
    /**
177
     * The depth of the calls to function declarations. Helps some checks know if they are at the root or not.
178
     */
179
    private namespaceAndFunctionDepth: number;
180

181
    /**
182
     * The options used to parse the file
183
     */
184
    public options: ParseOptions;
185

186
    private globalTerminators = [] as TokenKind[][];
2,236✔
187

188
    /**
189
     * A list of identifiers that are permitted to be used as local variables. We store this in a property because we augment the list in the constructor
190
     * based on the parse mode
191
     */
192
    private allowedLocalIdentifiers: TokenKind[];
193

194
    /**
195
     * Annotations collected which should be attached to the next statement
196
     */
197
    private pendingAnnotations: AnnotationExpression[];
198

199
    /**
200
     * Get the currently active global terminators
201
     */
202
    private peekGlobalTerminators() {
203
        return this.globalTerminators[this.globalTerminators.length - 1] ?? [];
7,262✔
204
    }
205

206
    /**
207
     * Static wrapper around creating a new parser and parsing a list of tokens
208
     */
209
    public static parse(toParse: Token[] | string, options?: ParseOptions): Parser {
210
        return new Parser().parse(toParse, options);
2,209✔
211
    }
212

213
    /**
214
     * Parses an array of `Token`s into an abstract syntax tree
215
     * @param toParse the array of tokens to parse. May not contain any whitespace tokens
216
     * @returns the same instance of the parser which contains the diagnostics and statements
217
     */
218
    public parse(toParse: Token[] | string, options?: ParseOptions) {
219
        this.logger = options?.logger ?? createLogger();
2,210✔
220
        options = this.sanitizeParseOptions(options);
2,210✔
221
        this.options = options;
2,210✔
222

223
        let tokens: Token[];
224
        if (typeof toParse === 'string') {
2,210✔
225
            tokens = Lexer.scan(toParse, { trackLocations: options.trackLocations }).tokens;
293✔
226
        } else {
227
            tokens = toParse;
1,917✔
228
        }
229
        this.tokens = tokens;
2,210✔
230
        this.allowedLocalIdentifiers = [
2,210✔
231
            ...AllowedLocalIdentifiers,
232
            //when in plain brightscript mode, the BrighterScript source literals can be used as regular variables
233
            ...(this.options.mode === ParseMode.BrightScript ? BrighterScriptSourceLiterals : [])
2,210✔
234
        ];
235
        this.current = 0;
2,210✔
236
        this.diagnostics = [];
2,210✔
237
        this.namespaceAndFunctionDepth = 0;
2,210✔
238
        this.pendingAnnotations = [];
2,210✔
239

240
        this.ast = this.body();
2,210✔
241

242
        //now that we've built the AST, link every node to its parent
243
        this.ast.link();
2,210✔
244
        return this;
2,210✔
245
    }
246

247
    private logger: Logger;
248

249
    private body() {
250
        const parentAnnotations = this.enterAnnotationBlock();
2,470✔
251

252
        let body = new Body([]);
2,470✔
253
        if (this.tokens.length > 0) {
2,470✔
254
            this.consumeStatementSeparators(true);
2,469✔
255

256
            try {
2,469✔
257
                while (
2,469✔
258
                    //not at end of tokens
259
                    !this.isAtEnd() &&
8,962✔
260
                    //the next token is not one of the end terminators
261
                    !this.checkAny(...this.peekGlobalTerminators())
262
                ) {
263
                    let dec = this.declaration();
3,117✔
264
                    if (dec) {
3,117✔
265
                        if (!isAnnotationExpression(dec)) {
3,070✔
266
                            this.consumePendingAnnotations(dec);
3,033✔
267
                            body.statements.push(dec);
3,033✔
268
                            //ensure statement separator
269
                            this.consumeStatementSeparators(false);
3,033✔
270
                        } else {
271
                            this.consumeStatementSeparators(true);
37✔
272
                        }
273
                    }
274
                }
275
            } catch (parseError) {
276
                //do nothing with the parse error for now. perhaps we can remove this?
277
                console.error(parseError);
×
278
            }
279
        }
280

281
        this.exitAnnotationBlock(parentAnnotations);
2,470✔
282
        return body;
2,470✔
283
    }
284

285
    private sanitizeParseOptions(options: ParseOptions) {
286
        options ??= {};
2,210✔
287
        options.mode ??= ParseMode.BrightScript;
2,210✔
288
        options.trackLocations ??= true;
2,210✔
289
        return options;
2,210✔
290
    }
291

292
    /**
293
     * Determine if the parser is currently parsing tokens at the root level.
294
     */
295
    private isAtRootLevel() {
296
        return this.namespaceAndFunctionDepth === 0;
15,698✔
297
    }
298

299
    /**
300
     * Throws an error if the input file type is not BrighterScript
301
     */
302
    private warnIfNotBrighterScriptMode(featureName: string) {
303
        if (this.options.mode !== ParseMode.BrighterScript) {
1,377✔
304
            let diagnostic = {
170✔
305
                ...DiagnosticMessages.bsFeatureNotSupportedInBrsFiles(featureName),
306
                range: this.peek().range
307
            } as Diagnostic;
308
            this.diagnostics.push(diagnostic);
170✔
309
        }
310
    }
311

312
    /**
313
     * Throws an exception using the last diagnostic message
314
     */
315
    private lastDiagnosticAsError() {
316
        let error = new Error(this.diagnostics[this.diagnostics.length - 1]?.message ?? 'Unknown error');
117!
317
        (error as any).isDiagnostic = true;
117✔
318
        return error;
117✔
319
    }
320

321
    private declaration(): Statement | AnnotationExpression | undefined {
322
        try {
5,646✔
323
            if (this.checkAny(TokenKind.Sub, TokenKind.Function)) {
5,646✔
324
                return this.functionDeclaration(false);
1,470✔
325
            }
326

327
            if (this.checkLibrary()) {
4,176✔
328
                return this.libraryStatement();
14✔
329
            }
330

331
            if (this.check(TokenKind.Const) && this.checkAnyNext(TokenKind.Identifier, ...this.allowedLocalIdentifiers)) {
4,162✔
332
                return this.constDeclaration();
82✔
333
            }
334

335
            if (this.check(TokenKind.At) && this.checkNext(TokenKind.Identifier)) {
4,080✔
336
                return this.annotationExpression();
44✔
337
            }
338

339
            if (this.check(TokenKind.Comment)) {
4,036✔
340
                return this.commentStatement();
216✔
341
            }
342

343
            //catch certain global terminators to prevent unnecessary lookahead (i.e. like `end namespace`, no need to continue)
344
            if (this.checkAny(...this.peekGlobalTerminators())) {
3,820!
345
                return;
×
346
            }
347

348
            return this.statement();
3,820✔
349
        } catch (error: any) {
350
            //if the error is not a diagnostic, then log the error for debugging purposes
351
            if (!error.isDiagnostic) {
112!
352
                this.logger.error(error);
×
353
            }
354
            this.synchronize();
112✔
355
        }
356
    }
357

358
    /**
359
     * Try to get an identifier. If not found, add diagnostic and return undefined
360
     */
361
    private tryIdentifier(...additionalTokenKinds: TokenKind[]): Identifier | undefined {
362
        const identifier = this.tryConsume(
119✔
363
            DiagnosticMessages.expectedIdentifier(),
364
            TokenKind.Identifier,
365
            ...additionalTokenKinds
366
        ) as Identifier;
367
        if (identifier) {
119✔
368
            // force the name into an identifier so the AST makes some sense
369
            identifier.kind = TokenKind.Identifier;
118✔
370
            return identifier;
118✔
371
        }
372
    }
373

374
    private identifier(...additionalTokenKinds: TokenKind[]) {
375
        const identifier = this.consume(
418✔
376
            DiagnosticMessages.expectedIdentifier(),
377
            TokenKind.Identifier,
378
            ...additionalTokenKinds
379
        ) as Identifier;
380
        // force the name into an identifier so the AST makes some sense
381
        identifier.kind = TokenKind.Identifier;
418✔
382
        return identifier;
418✔
383
    }
384

385
    private enumMemberStatement() {
386
        const statement = new EnumMemberStatement({} as any);
207✔
387
        statement.tokens.name = this.consume(
207✔
388
            DiagnosticMessages.expectedClassFieldIdentifier(),
389
            TokenKind.Identifier,
390
            ...AllowedProperties
391
        ) as Identifier;
392
        //look for `= SOME_EXPRESSION`
393
        if (this.check(TokenKind.Equal)) {
207✔
394
            statement.tokens.equal = this.advance();
115✔
395
            statement.value = this.expression();
115✔
396
        }
397
        return statement;
207✔
398
    }
399

400
    /**
401
     * Create a new InterfaceMethodStatement. This should only be called from within `interfaceDeclaration`
402
     */
403
    private interfaceFieldStatement(optionalKeyword?: Token) {
404
        const name = this.identifier(...AllowedProperties);
57✔
405
        let asToken: Token;
406
        let typeToken: Token;
407
        let type: BscType;
408
        if (this.check(TokenKind.As)) {
57!
409
            asToken = this.consumeToken(TokenKind.As);
57✔
410
            typeToken = this.typeToken();
57✔
411
            type = util.tokenToBscType(typeToken);
57✔
412
        }
413

414
        if (!type) {
57!
415
            this.diagnostics.push({
×
416
                ...DiagnosticMessages.functionParameterTypeIsInvalid(name.text, typeToken.text),
417
                range: typeToken.range
418
            });
419
            throw this.lastDiagnosticAsError();
×
420
        }
421

422
        return new InterfaceFieldStatement(name, asToken, typeToken, type, optionalKeyword);
57✔
423
    }
424

425
    /**
426
     * Create a new InterfaceMethodStatement. This should only be called from within `interfaceDeclaration()`
427
     */
428
    private interfaceMethodStatement(optionalKeyword?: Token) {
429
        const functionType = this.advance();
23✔
430
        const name = this.identifier(...AllowedProperties);
23✔
431
        const leftParen = this.consume(DiagnosticMessages.expectedToken(TokenKind.LeftParen), TokenKind.LeftParen);
23✔
432

433
        let params = [] as FunctionParameterExpression[];
23✔
434
        if (!this.check(TokenKind.RightParen)) {
23✔
435
            do {
5✔
436
                if (params.length >= CallExpression.MaximumArguments) {
7!
437
                    this.diagnostics.push({
×
438
                        ...DiagnosticMessages.tooManyCallableParameters(params.length, CallExpression.MaximumArguments),
439
                        range: this.peek().range
440
                    });
441
                }
442

443
                params.push(this.functionParameter());
7✔
444
            } while (this.match(TokenKind.Comma));
445
        }
446
        const rightParen = this.consumeToken(TokenKind.RightParen);
23✔
447
        let asToken = null as Token;
23✔
448
        let returnTypeToken = null as Token;
23✔
449
        if (this.check(TokenKind.As)) {
23✔
450
            asToken = this.advance();
20✔
451
            returnTypeToken = this.typeToken();
20✔
452
            const returnType = util.tokenToBscType(returnTypeToken);
20✔
453
            if (!returnType) {
20!
454
                this.diagnostics.push({
×
455
                    ...DiagnosticMessages.functionParameterTypeIsInvalid(name.text, returnTypeToken.text),
456
                    range: returnTypeToken.range
457
                });
458
                throw this.lastDiagnosticAsError();
×
459
            }
460
        }
461

462
        return new InterfaceMethodStatement(
23✔
463
            functionType,
464
            name,
465
            leftParen,
466
            params,
467
            rightParen,
468
            asToken,
469
            returnTypeToken,
470
            util.tokenToBscType(returnTypeToken),
471
            optionalKeyword
472
        );
473
    }
474

475
    private interfaceDeclaration(): InterfaceStatement {
476
        this.warnIfNotBrighterScriptMode('interface declarations');
56✔
477

478
        const parentAnnotations = this.enterAnnotationBlock();
56✔
479

480
        const interfaceToken = this.consume(
56✔
481
            DiagnosticMessages.expectedKeyword(TokenKind.Interface),
482
            TokenKind.Interface
483
        );
484
        const nameToken = this.identifier(...this.allowedLocalIdentifiers);
56✔
485

486
        let extendsToken: Token;
487
        let parentInterfaceName: NamespacedVariableNameExpression;
488

489
        if (this.peek().text.toLowerCase() === 'extends') {
56✔
490
            extendsToken = this.advance();
2✔
491
            parentInterfaceName = this.getNamespacedVariableNameExpression();
2✔
492
        }
493
        this.consumeStatementSeparators();
56✔
494
        //gather up all interface members (Fields, Methods)
495
        let body = [] as Statement[];
56✔
496
        while (this.checkAny(TokenKind.Comment, TokenKind.Identifier, TokenKind.At, ...AllowedProperties)) {
56✔
497
            try {
139✔
498
                //break out of this loop if we encountered the `EndInterface` token not followed by `as`
499
                if (this.check(TokenKind.EndInterface) && !this.checkNext(TokenKind.As)) {
139✔
500
                    break;
56✔
501
                }
502

503
                let decl: Statement;
504

505
                //collect leading annotations
506
                if (this.check(TokenKind.At)) {
83✔
507
                    this.annotationExpression();
2✔
508
                }
509

510
                const optionalKeyword = this.consumeTokenIf(TokenKind.Optional);
83✔
511
                //fields
512
                if (this.checkAny(TokenKind.Identifier, ...AllowedProperties) && this.checkAnyNext(TokenKind.As, TokenKind.Newline, TokenKind.Comment)) {
83✔
513
                    decl = this.interfaceFieldStatement(optionalKeyword);
57✔
514
                    //field with name = 'optional'
515
                } else if (optionalKeyword && this.checkAny(TokenKind.As, TokenKind.Newline, TokenKind.Comment)) {
26!
516
                    //rewind one place, so that 'optional' is the field name
517
                    this.current--;
×
518
                    decl = this.interfaceFieldStatement();
×
519

520
                    //methods (function/sub keyword followed by opening paren)
521
                } else if (this.checkAny(TokenKind.Function, TokenKind.Sub) && this.checkAnyNext(TokenKind.Identifier, ...AllowedProperties)) {
26✔
522
                    decl = this.interfaceMethodStatement(optionalKeyword);
23✔
523

524
                    //comments
525
                } else if (this.check(TokenKind.Comment)) {
3✔
526
                    decl = this.commentStatement();
1✔
527
                }
528
                if (decl) {
80✔
529
                    this.consumePendingAnnotations(decl);
78✔
530
                    body.push(decl);
78✔
531
                } else {
532
                    //we didn't find a declaration...flag tokens until next line
533
                    this.flagUntil(TokenKind.Newline, TokenKind.Colon, TokenKind.Eof);
2✔
534
                }
535
            } catch (e) {
536
                //throw out any failed members and move on to the next line
537
                this.flagUntil(TokenKind.Newline, TokenKind.Colon, TokenKind.Eof);
3✔
538
            }
539

540
            //ensure statement separator
541
            this.consumeStatementSeparators();
83✔
542
        }
543

544
        //consume the final `end interface` token
545
        const endInterfaceToken = this.consumeToken(TokenKind.EndInterface);
56✔
546

547
        const statement = new InterfaceStatement(
56✔
548
            interfaceToken,
549
            nameToken,
550
            extendsToken,
551
            parentInterfaceName,
552
            body,
553
            endInterfaceToken
554
        );
555
        this._references.interfaceStatements.push(statement);
56✔
556
        this.exitAnnotationBlock(parentAnnotations);
56✔
557
        return statement;
56✔
558
    }
559

560
    private enumDeclaration(): EnumStatement {
561
        const result = new EnumStatement({} as any, []);
119✔
562
        this.warnIfNotBrighterScriptMode('enum declarations');
119✔
563

564
        const parentAnnotations = this.enterAnnotationBlock();
119✔
565

566
        result.tokens.enum = this.consume(
119✔
567
            DiagnosticMessages.expectedKeyword(TokenKind.Enum),
568
            TokenKind.Enum
569
        );
570

571
        result.tokens.name = this.tryIdentifier(...this.allowedLocalIdentifiers);
119✔
572

573
        this.consumeStatementSeparators();
119✔
574
        //gather up all members
575
        while (this.checkAny(TokenKind.Comment, TokenKind.Identifier, TokenKind.At, ...AllowedProperties)) {
119✔
576
            try {
213✔
577
                let decl: EnumMemberStatement | CommentStatement;
578

579
                //collect leading annotations
580
                if (this.check(TokenKind.At)) {
213!
581
                    this.annotationExpression();
×
582
                }
583

584
                //members
585
                if (this.checkAny(TokenKind.Identifier, ...AllowedProperties)) {
213✔
586
                    decl = this.enumMemberStatement();
207✔
587

588
                    //comments
589
                } else if (this.check(TokenKind.Comment)) {
6!
590
                    decl = this.commentStatement();
6✔
591
                }
592

593
                if (decl) {
213!
594
                    this.consumePendingAnnotations(decl);
213✔
595
                    result.body.push(decl);
213✔
596
                } else {
597
                    //we didn't find a declaration...flag tokens until next line
598
                    this.flagUntil(TokenKind.Newline, TokenKind.Colon, TokenKind.Eof);
×
599
                }
600
            } catch (e) {
601
                //throw out any failed members and move on to the next line
602
                this.flagUntil(TokenKind.Newline, TokenKind.Colon, TokenKind.Eof);
×
603
            }
604

605
            //ensure statement separator
606
            this.consumeStatementSeparators();
213✔
607
            //break out of this loop if we encountered the `EndEnum` token
608
            if (this.check(TokenKind.EndEnum)) {
213✔
609
                break;
111✔
610
            }
611
        }
612

613
        //consume the final `end interface` token
614
        result.tokens.endEnum = this.consumeToken(TokenKind.EndEnum);
119✔
615

616
        this._references.enumStatements.push(result);
119✔
617
        this.exitAnnotationBlock(parentAnnotations);
119✔
618
        return result;
119✔
619
    }
620

621
    /**
622
     * A BrighterScript class declaration
623
     */
624
    private classDeclaration(): ClassStatement {
625
        this.warnIfNotBrighterScriptMode('class declarations');
487✔
626

627
        const parentAnnotations = this.enterAnnotationBlock();
487✔
628

629
        let classKeyword = this.consume(
487✔
630
            DiagnosticMessages.expectedKeyword(TokenKind.Class),
631
            TokenKind.Class
632
        );
633
        let extendsKeyword: Token;
634
        let parentClassName: NamespacedVariableNameExpression;
635

636
        //get the class name
637
        let className = this.tryConsume(DiagnosticMessages.expectedIdentifierAfterKeyword('class'), TokenKind.Identifier, ...this.allowedLocalIdentifiers) as Identifier;
487✔
638

639
        //see if the class inherits from parent
640
        if (this.peek().text.toLowerCase() === 'extends') {
487✔
641
            extendsKeyword = this.advance();
76✔
642
            parentClassName = this.getNamespacedVariableNameExpression();
76✔
643
        }
644

645
        //ensure statement separator
646
        this.consumeStatementSeparators();
486✔
647

648
        //gather up all class members (Fields, Methods)
649
        let body = [] as Statement[];
486✔
650
        while (this.checkAny(TokenKind.Public, TokenKind.Protected, TokenKind.Private, TokenKind.Function, TokenKind.Sub, TokenKind.Comment, TokenKind.Identifier, TokenKind.At, ...AllowedProperties)) {
486✔
651
            try {
473✔
652
                let decl: Statement;
653
                let accessModifier: Token;
654

655
                if (this.check(TokenKind.At)) {
473✔
656
                    this.annotationExpression();
15✔
657
                }
658

659
                if (this.checkAny(TokenKind.Public, TokenKind.Protected, TokenKind.Private)) {
472✔
660
                    //use actual access modifier
661
                    accessModifier = this.advance();
65✔
662
                }
663

664
                let overrideKeyword: Token;
665
                if (this.peek().text.toLowerCase() === 'override') {
472✔
666
                    overrideKeyword = this.advance();
17✔
667
                }
668

669
                //methods (function/sub keyword OR identifier followed by opening paren)
670
                if (this.checkAny(TokenKind.Function, TokenKind.Sub) || (this.checkAny(TokenKind.Identifier, ...AllowedProperties) && this.checkNext(TokenKind.LeftParen))) {
472✔
671
                    const funcDeclaration = this.functionDeclaration(false, false);
266✔
672

673
                    //remove this function from the lists because it's not a callable
674
                    const functionStatement = this._references.functionStatements.pop();
266✔
675

676
                    //if we have an overrides keyword AND this method is called 'new', that's not allowed
677
                    if (overrideKeyword && funcDeclaration.name.text.toLowerCase() === 'new') {
266!
678
                        this.diagnostics.push({
×
679
                            ...DiagnosticMessages.cannotUseOverrideKeywordOnConstructorFunction(),
680
                            range: overrideKeyword.range
681
                        });
682
                    }
683

684
                    decl = new MethodStatement(
266✔
685
                        accessModifier,
686
                        funcDeclaration.name,
687
                        funcDeclaration.func,
688
                        overrideKeyword
689
                    );
690

691
                    //refer to this statement as parent of the expression
692
                    functionStatement.func.functionStatement = decl as MethodStatement;
266✔
693

694
                    //fields
695
                } else if (this.checkAny(TokenKind.Identifier, ...AllowedProperties)) {
206✔
696

697
                    decl = this.fieldDeclaration(accessModifier);
184✔
698

699
                    //class fields cannot be overridden
700
                    if (overrideKeyword) {
183!
701
                        this.diagnostics.push({
×
702
                            ...DiagnosticMessages.classFieldCannotBeOverridden(),
703
                            range: overrideKeyword.range
704
                        });
705
                    }
706

707
                    //comments
708
                } else if (this.check(TokenKind.Comment)) {
22✔
709
                    decl = this.commentStatement();
8✔
710
                }
711

712
                if (decl) {
471✔
713
                    this.consumePendingAnnotations(decl);
457✔
714
                    body.push(decl);
457✔
715
                }
716
            } catch (e) {
717
                //throw out any failed members and move on to the next line
718
                this.flagUntil(TokenKind.Newline, TokenKind.Colon, TokenKind.Eof);
2✔
719
            }
720

721
            //ensure statement separator
722
            this.consumeStatementSeparators();
473✔
723
        }
724

725
        let endingKeyword = this.advance();
486✔
726
        if (endingKeyword.kind !== TokenKind.EndClass) {
486✔
727
            this.diagnostics.push({
3✔
728
                ...DiagnosticMessages.couldNotFindMatchingEndKeyword('class'),
729
                range: endingKeyword.range
730
            });
731
        }
732

733
        const result = new ClassStatement(
486✔
734
            classKeyword,
735
            className,
736
            body,
737
            endingKeyword,
738
            extendsKeyword,
739
            parentClassName
740
        );
741

742
        this._references.classStatements.push(result);
486✔
743
        this.exitAnnotationBlock(parentAnnotations);
486✔
744
        return result;
486✔
745
    }
746

747
    private fieldDeclaration(accessModifier: Token | null) {
748

749
        let optionalKeyword = this.consumeTokenIf(TokenKind.Optional);
184✔
750

751
        if (this.checkAny(TokenKind.Identifier, ...AllowedProperties)) {
184✔
752
            if (this.check(TokenKind.As)) {
183✔
753
                if (this.checkAnyNext(TokenKind.Comment, TokenKind.Newline)) {
5✔
754
                    // as <EOL>
755
                    // `as` is the field name
756
                } else if (this.checkNext(TokenKind.As)) {
4✔
757
                    //  as as ____
758
                    // first `as` is the field name
759
                } else if (optionalKeyword) {
2!
760
                    // optional as ____
761
                    // optional is the field name, `as` starts type
762
                    // rewind current token
763
                    optionalKeyword = null;
2✔
764
                    this.current--;
2✔
765
                }
766
            }
767
        } else {
768
            // no name after `optional` ... optional is the name
769
            // rewind current token
770
            optionalKeyword = null;
1✔
771
            this.current--;
1✔
772
        }
773

774
        let name = this.consume(
184✔
775
            DiagnosticMessages.expectedClassFieldIdentifier(),
776
            TokenKind.Identifier,
777
            ...AllowedProperties
778
        ) as Identifier;
779
        let asToken: Token;
780
        let fieldType: Token;
781
        //look for `as SOME_TYPE`
782
        if (this.check(TokenKind.As)) {
184✔
783
            asToken = this.advance();
129✔
784
            fieldType = this.typeToken();
129✔
785

786
            //no field type specified
787
            if (!util.tokenToBscType(fieldType)) {
129✔
788
                this.diagnostics.push({
1✔
789
                    ...DiagnosticMessages.expectedValidTypeToFollowAsKeyword(),
790
                    range: this.peek().range
791
                });
792
            }
793
        }
794

795
        let initialValue: Expression;
796
        let equal: Token;
797
        //if there is a field initializer
798
        if (this.check(TokenKind.Equal)) {
184✔
799
            equal = this.advance();
40✔
800
            initialValue = this.expression();
40✔
801
        }
802

803
        return new FieldStatement(
183✔
804
            accessModifier,
805
            name,
806
            asToken,
807
            fieldType,
808
            equal,
809
            initialValue,
810
            optionalKeyword
811
        );
812
    }
813

814
    /**
815
     * An array of CallExpression for the current function body
816
     */
817
    private callExpressions = [];
2,236✔
818

819
    private functionDeclaration(isAnonymous: true, checkIdentifier?: boolean, onlyCallableAsMember?: boolean): FunctionExpression;
820
    private functionDeclaration(isAnonymous: false, checkIdentifier?: boolean, onlyCallableAsMember?: boolean): FunctionStatement;
821
    private functionDeclaration(isAnonymous: boolean, checkIdentifier = true, onlyCallableAsMember = false) {
3,364✔
822
        let previousCallExpressions = this.callExpressions;
1,815✔
823
        this.callExpressions = [];
1,815✔
824
        try {
1,815✔
825
            //track depth to help certain statements need to know if they are contained within a function body
826
            this.namespaceAndFunctionDepth++;
1,815✔
827
            let functionType: Token;
828
            if (this.checkAny(TokenKind.Sub, TokenKind.Function)) {
1,815✔
829
                functionType = this.advance();
1,813✔
830
            } else {
831
                this.diagnostics.push({
2✔
832
                    ...DiagnosticMessages.missingCallableKeyword(),
833
                    range: this.peek().range
834
                });
835
                functionType = {
2✔
836
                    isReserved: true,
837
                    kind: TokenKind.Function,
838
                    text: 'function',
839
                    //zero-length location means derived
840
                    range: {
841
                        start: this.peek().range.start,
842
                        end: this.peek().range.start
843
                    },
844
                    leadingWhitespace: ''
845
                };
846
            }
847
            let isSub = functionType?.kind === TokenKind.Sub;
1,815!
848
            let functionTypeText = isSub ? 'sub' : 'function';
1,815✔
849
            let name: Identifier;
850
            let leftParen: Token;
851

852
            if (isAnonymous) {
1,815✔
853
                leftParen = this.consume(
79✔
854
                    DiagnosticMessages.expectedLeftParenAfterCallable(functionTypeText),
855
                    TokenKind.LeftParen
856
                );
857
            } else {
858
                name = this.consume(
1,736✔
859
                    DiagnosticMessages.expectedNameAfterCallableKeyword(functionTypeText),
860
                    TokenKind.Identifier,
861
                    ...AllowedProperties
862
                ) as Identifier;
863
                leftParen = this.consume(
1,734✔
864
                    DiagnosticMessages.expectedLeftParenAfterCallableName(functionTypeText),
865
                    TokenKind.LeftParen
866
                );
867

868
                //prevent functions from ending with type designators
869
                let lastChar = name.text[name.text.length - 1];
1,731✔
870
                if (['$', '%', '!', '#', '&'].includes(lastChar)) {
1,731✔
871
                    //don't throw this error; let the parser continue
872
                    this.diagnostics.push({
8✔
873
                        ...DiagnosticMessages.functionNameCannotEndWithTypeDesignator(functionTypeText, name.text, lastChar),
874
                        range: name.range
875
                    });
876
                }
877

878
                //flag functions with keywords for names (only for standard functions)
879
                if (checkIdentifier && DisallowedFunctionIdentifiersText.has(name.text.toLowerCase())) {
1,731✔
880
                    this.diagnostics.push({
2✔
881
                        ...DiagnosticMessages.cannotUseReservedWordAsIdentifier(name.text),
882
                        range: name.range
883
                    });
884
                }
885
            }
886

887
            let params = [] as FunctionParameterExpression[];
1,810✔
888
            let asToken: Token;
889
            let typeToken: Token;
890
            if (!this.check(TokenKind.RightParen)) {
1,810✔
891
                do {
295✔
892
                    params.push(this.functionParameter());
642✔
893
                } while (this.match(TokenKind.Comma));
894
            }
895
            let rightParen = this.advance();
1,809✔
896

897
            if (this.check(TokenKind.As)) {
1,809✔
898
                asToken = this.advance();
105✔
899

900
                typeToken = this.typeToken();
105✔
901

902
                if (!util.tokenToBscType(typeToken, this.options.mode === ParseMode.BrighterScript)) {
105✔
903
                    this.diagnostics.push({
2✔
904
                        ...DiagnosticMessages.invalidFunctionReturnType(typeToken.text ?? ''),
6!
905
                        range: typeToken.range
906
                    });
907
                }
908
            }
909

910
            params.reduce((haveFoundOptional: boolean, param: FunctionParameterExpression) => {
1,809✔
911
                if (haveFoundOptional && !param.defaultValue) {
640!
912
                    this.diagnostics.push({
×
913
                        ...DiagnosticMessages.requiredParameterMayNotFollowOptionalParameter(param.name.text),
914
                        range: param.range
915
                    });
916
                }
917

918
                return haveFoundOptional || !!param.defaultValue;
640✔
919
            }, false);
920

921
            this.consumeStatementSeparators(true);
1,809✔
922

923
            let func = new FunctionExpression(
1,809✔
924
                params,
925
                undefined, //body
926
                functionType,
927
                undefined, //ending keyword
928
                leftParen,
929
                rightParen,
930
                asToken,
931
                typeToken
932
            );
933

934
            // add the function to the relevant symbol tables
935
            if (!onlyCallableAsMember && name) {
1,809✔
936
                const funcType = func.getFunctionType();
1,730✔
937
                funcType.setName(name.text);
1,730✔
938
            }
939

940
            this._references.functionExpressions.push(func);
1,809✔
941

942
            //support ending the function with `end sub` OR `end function`
943
            func.body = this.block();
1,809✔
944
            //if the parser was unable to produce a block, make an empty one so the AST makes some sense...
945
            if (!func.body) {
1,809✔
946
                func.body = new Block([], util.createRangeFromPositions(func.range.start, func.range.start));
3✔
947
            }
948
            func.body.symbolTable = new SymbolTable(`Block: Function '${name?.text ?? ''}'`, () => func.getSymbolTable());
1,809✔
949

950
            if (!func.body) {
1,809!
951
                this.diagnostics.push({
×
952
                    ...DiagnosticMessages.callableBlockMissingEndKeyword(functionTypeText),
953
                    range: this.peek().range
954
                });
955
                throw this.lastDiagnosticAsError();
×
956
            }
957

958
            // consume 'end sub' or 'end function'
959
            func.end = this.advance();
1,809✔
960
            let expectedEndKind = isSub ? TokenKind.EndSub : TokenKind.EndFunction;
1,809✔
961

962
            //if `function` is ended with `end sub`, or `sub` is ended with `end function`, then
963
            //add an error but don't hard-fail so the AST can continue more gracefully
964
            if (func.end.kind !== expectedEndKind) {
1,809✔
965
                this.diagnostics.push({
9✔
966
                    ...DiagnosticMessages.mismatchedEndCallableKeyword(functionTypeText, func.end.text),
967
                    range: func.end.range
968
                });
969
            }
970
            func.callExpressions = this.callExpressions;
1,809✔
971

972
            if (isAnonymous) {
1,809✔
973
                return func;
79✔
974
            } else {
975
                let result = new FunctionStatement(name, func);
1,730✔
976
                func.symbolTable.name += `: '${name?.text}'`;
1,730!
977
                func.functionStatement = result;
1,730✔
978
                this._references.functionStatements.push(result);
1,730✔
979

980
                return result;
1,730✔
981
            }
982
        } finally {
983
            this.namespaceAndFunctionDepth--;
1,815✔
984
            //restore the previous CallExpression list
985
            this.callExpressions = previousCallExpressions;
1,815✔
986
        }
987
    }
988

989
    private functionParameter(): FunctionParameterExpression {
990
        if (!this.checkAny(TokenKind.Identifier, ...this.allowedLocalIdentifiers)) {
649✔
991
            this.diagnostics.push({
1✔
992
                ...DiagnosticMessages.expectedParameterNameButFound(this.peek().text),
993
                range: this.peek().range
994
            });
995
            throw this.lastDiagnosticAsError();
1✔
996
        }
997

998
        let name = this.advance() as Identifier;
648✔
999
        // force the name into an identifier so the AST makes some sense
1000
        name.kind = TokenKind.Identifier;
648✔
1001

1002
        //add diagnostic if name is a reserved word that cannot be used as an identifier
1003
        if (DisallowedLocalIdentifiersText.has(name.text.toLowerCase())) {
648✔
1004
            this.diagnostics.push({
3✔
1005
                ...DiagnosticMessages.cannotUseReservedWordAsIdentifier(name.text),
1006
                range: name.range
1007
            });
1008
        }
1009

1010
        let typeToken: Token | undefined;
1011
        let defaultValue;
1012

1013
        // parse argument default value
1014
        if (this.match(TokenKind.Equal)) {
648✔
1015
            // it seems any expression is allowed here -- including ones that operate on other arguments!
1016
            defaultValue = this.expression(false);
251✔
1017
        }
1018

1019
        let asToken = null;
648✔
1020
        if (this.check(TokenKind.As)) {
648✔
1021
            asToken = this.advance();
291✔
1022

1023
            typeToken = this.typeToken();
291✔
1024

1025
            if (!util.tokenToBscType(typeToken, this.options.mode === ParseMode.BrighterScript)) {
291✔
1026
                this.diagnostics.push({
4✔
1027
                    ...DiagnosticMessages.functionParameterTypeIsInvalid(name.text, typeToken.text),
1028
                    range: typeToken.range
1029
                });
1030
            }
1031
        }
1032
        return new FunctionParameterExpression(
648✔
1033
            name,
1034
            typeToken,
1035
            defaultValue,
1036
            asToken
1037
        );
1038
    }
1039

1040
    private assignment(): AssignmentStatement {
1041
        let name = this.advance() as Identifier;
939✔
1042
        //add diagnostic if name is a reserved word that cannot be used as an identifier
1043
        if (DisallowedLocalIdentifiersText.has(name.text.toLowerCase())) {
939✔
1044
            this.diagnostics.push({
13✔
1045
                ...DiagnosticMessages.cannotUseReservedWordAsIdentifier(name.text),
1046
                range: name.range
1047
            });
1048
        }
1049
        if (this.check(TokenKind.As)) {
939✔
1050
            // v1 syntax allows type declaration on lhs of assignment
1051
            this.warnIfNotBrighterScriptMode('typed assignment');
4✔
1052

1053
            this.advance(); // skip 'as'
4✔
1054
            this.typeToken(); // skip typeToken;
4✔
1055
        }
1056

1057
        let operator = this.consume(
939✔
1058
            DiagnosticMessages.expectedOperatorAfterIdentifier(AssignmentOperators, name.text),
1059
            ...AssignmentOperators
1060
        );
1061
        let value = this.expression();
937✔
1062

1063
        let result: AssignmentStatement;
1064
        if (operator.kind === TokenKind.Equal) {
930✔
1065
            result = new AssignmentStatement(operator, name, value);
884✔
1066
        } else {
1067
            const nameExpression = new VariableExpression(name);
46✔
1068
            result = new AssignmentStatement(
46✔
1069
                { kind: TokenKind.Equal, text: '=', range: operator.range },
1070
                name,
1071
                new BinaryExpression(nameExpression, operator, value)
1072
            );
1073
            this.addExpressionsToReferences(nameExpression);
46✔
1074
            if (isBinaryExpression(value)) {
46✔
1075
                //remove the right-hand-side expression from this assignment operator, and replace with the full assignment expression
1076
                this._references.expressions.delete(value);
3✔
1077
            }
1078
            this._references.expressions.add(result);
46✔
1079
        }
1080

1081
        this._references.assignmentStatements.push(result);
930✔
1082
        return result;
930✔
1083
    }
1084

1085
    private checkLibrary() {
1086
        let isLibraryToken = this.check(TokenKind.Library);
8,050✔
1087

1088
        //if we are at the top level, any line that starts with "library" should be considered a library statement
1089
        if (this.isAtRootLevel() && isLibraryToken) {
8,050✔
1090
            return true;
13✔
1091

1092
            //not at root level, library statements are all invalid here, but try to detect if the tokens look
1093
            //like a library statement (and let the libraryStatement function handle emitting the diagnostics)
1094
        } else if (isLibraryToken && this.checkNext(TokenKind.StringLiteral)) {
8,037✔
1095
            return true;
1✔
1096

1097
            //definitely not a library statement
1098
        } else {
1099
            return false;
8,036✔
1100
        }
1101
    }
1102

1103
    private checkAlias() {
1104
        let isAliasToken = this.check(TokenKind.Alias);
3,825✔
1105

1106
        //if we are at the top level, any line that starts with "alias" should be considered a alias statement
1107
        if (this.isAtRootLevel() && isAliasToken) {
3,825✔
1108
            return true;
2✔
1109

1110
            //not at root level, alias statements are all invalid here, but try to detect if the tokens look
1111
            //like a alias statement (and let the alias function handle emitting the diagnostics)
1112
        } else if (isAliasToken && this.checkNext(TokenKind.Identifier)) {
3,823!
1113
            return true;
×
1114

1115
            //definitely not a alias statement
1116
        } else {
1117
            return false;
3,823✔
1118
        }
1119
    }
1120

1121
    private checkTypeStatement() {
1122
        let isTypeToken = this.check(TokenKind.Type);
3,823✔
1123

1124
        //if we are at the top level, any line that starts with "type" should be considered a type statement
1125
        if (this.isAtRootLevel() && isTypeToken) {
3,823✔
1126
            return true;
7✔
1127

1128
            //not at root level, type statements are all invalid here, but try to detect if the tokens look
1129
            //like a type statement (and let the type function handle emitting the diagnostics)
1130
        } else if (isTypeToken && this.checkNext(TokenKind.Identifier)) {
3,816✔
1131
            return true;
3✔
1132

1133
            //definitely not a type statement
1134
        } else {
1135
            return false;
3,813✔
1136
        }
1137
    }
1138

1139
    private statement(): Statement | undefined {
1140
        if (this.checkLibrary()) {
3,874!
1141
            return this.libraryStatement();
×
1142
        }
1143

1144
        if (this.check(TokenKind.Import)) {
3,874✔
1145
            return this.importStatement();
44✔
1146
        }
1147

1148
        if (this.check(TokenKind.Typecast) && this.checkAnyNext(TokenKind.Identifier, ...this.allowedLocalIdentifiers)) {
3,830✔
1149
            return this.typecastStatement();
5✔
1150
        }
1151

1152
        if (this.checkAlias()) {
3,825✔
1153
            return this.aliasStatement();
2✔
1154
        }
1155

1156
        if (this.checkTypeStatement()) {
3,823✔
1157
            return this.typeStatement();
10✔
1158
        }
1159

1160
        if (this.check(TokenKind.Stop)) {
3,813✔
1161
            return this.stopStatement();
16✔
1162
        }
1163

1164
        if (this.check(TokenKind.If)) {
3,797✔
1165
            return this.ifStatement();
171✔
1166
        }
1167

1168
        //`try` must be followed by a block, otherwise it could be a local variable
1169
        if (this.check(TokenKind.Try) && this.checkAnyNext(TokenKind.Newline, TokenKind.Colon, TokenKind.Comment)) {
3,626✔
1170
            return this.tryCatchStatement();
27✔
1171
        }
1172

1173
        if (this.check(TokenKind.Throw)) {
3,599✔
1174
            return this.throwStatement();
11✔
1175
        }
1176

1177
        if (this.checkAny(TokenKind.Print, TokenKind.Question)) {
3,588✔
1178
            return this.printStatement();
687✔
1179
        }
1180
        if (this.check(TokenKind.Dim)) {
2,901✔
1181
            return this.dimStatement();
43✔
1182
        }
1183

1184
        if (this.check(TokenKind.While)) {
2,858✔
1185
            return this.whileStatement();
23✔
1186
        }
1187

1188
        if (this.check(TokenKind.ExitWhile)) {
2,835✔
1189
            return this.exitWhile();
7✔
1190
        }
1191

1192
        if (this.check(TokenKind.For)) {
2,828✔
1193
            return this.forStatement();
34✔
1194
        }
1195

1196
        if (this.check(TokenKind.ForEach)) {
2,794✔
1197
            return this.forEachStatement();
20✔
1198
        }
1199

1200
        if (this.check(TokenKind.ExitFor)) {
2,774✔
1201
            return this.exitFor();
4✔
1202
        }
1203

1204
        if (this.check(TokenKind.End)) {
2,770✔
1205
            return this.endStatement();
8✔
1206
        }
1207

1208
        if (this.match(TokenKind.Return)) {
2,762✔
1209
            return this.returnStatement();
216✔
1210
        }
1211

1212
        if (this.check(TokenKind.Goto)) {
2,546✔
1213
            return this.gotoStatement();
12✔
1214
        }
1215

1216
        //the continue keyword (followed by `for`, `while`, or a statement separator)
1217
        if (this.check(TokenKind.Continue) && this.checkAnyNext(TokenKind.While, TokenKind.For, TokenKind.Newline, TokenKind.Colon, TokenKind.Comment)) {
2,534✔
1218
            return this.continueStatement();
12✔
1219
        }
1220

1221
        //does this line look like a label? (i.e.  `someIdentifier:` )
1222
        if (this.check(TokenKind.Identifier) && this.checkNext(TokenKind.Colon) && this.checkPrevious(TokenKind.Newline)) {
2,522✔
1223
            try {
12✔
1224
                return this.labelStatement();
12✔
1225
            } catch (err) {
1226
                if (!(err instanceof CancelStatementError)) {
2!
1227
                    throw err;
×
1228
                }
1229
                //not a label, try something else
1230
            }
1231
        }
1232

1233
        // BrightScript is like python, in that variables can be declared without a `var`,
1234
        // `let`, (...) keyword. As such, we must check the token *after* an identifier to figure
1235
        // out what to do with it.
1236
        if (
2,512✔
1237
            this.checkAny(TokenKind.Identifier, ...this.allowedLocalIdentifiers)
1238
        ) {
1239
            if (this.checkAnyNext(...AssignmentOperators)) {
2,335✔
1240
                return this.assignment();
901✔
1241
            } else if (this.checkNext(TokenKind.As)) {
1,434✔
1242
                // may be a typed assignment - this is v1 syntax
1243
                const backtrack = this.current;
5✔
1244
                let validTypeExpression = false;
5✔
1245
                try {
5✔
1246
                    // skip the identifier, and check for valid type expression
1247
                    this.advance();
5✔
1248
                    // skip the 'as'
1249
                    this.advance();
5✔
1250
                    // check if there is a valid type
1251
                    const typeToken = this.typeToken(true);
5✔
1252
                    const allowedNameKinds = [TokenKind.Identifier, ...DeclarableTypes, ...this.allowedLocalIdentifiers];
5✔
1253
                    validTypeExpression = allowedNameKinds.includes(typeToken.kind);
5✔
1254
                } catch (e) {
1255
                    // ignore any errors
1256
                } finally {
1257
                    this.current = backtrack;
5✔
1258
                }
1259
                if (validTypeExpression) {
5✔
1260
                    // there is a valid 'as' and type expression
1261
                    return this.assignment();
4✔
1262
                }
1263
            }
1264
        }
1265

1266
        //some BrighterScript keywords are allowed as a local identifiers, so we need to check for them AFTER the assignment check
1267
        if (this.check(TokenKind.Interface)) {
1,607✔
1268
            return this.interfaceDeclaration();
56✔
1269
        }
1270

1271
        if (this.check(TokenKind.Class)) {
1,551✔
1272
            return this.classDeclaration();
487✔
1273
        }
1274

1275
        if (this.check(TokenKind.Namespace)) {
1,064✔
1276
            return this.namespaceStatement();
261✔
1277
        }
1278

1279
        if (this.check(TokenKind.Enum)) {
803✔
1280
            return this.enumDeclaration();
119✔
1281
        }
1282

1283
        // TODO: support multi-statements
1284
        return this.setStatement();
684✔
1285
    }
1286

1287
    private whileStatement(): WhileStatement {
1288
        const whileKeyword = this.advance();
23✔
1289
        const condition = this.expression();
23✔
1290

1291
        this.consumeStatementSeparators();
22✔
1292

1293
        const whileBlock = this.block(TokenKind.EndWhile);
22✔
1294
        let endWhile: Token;
1295
        if (!whileBlock || this.peek().kind !== TokenKind.EndWhile) {
22✔
1296
            this.diagnostics.push({
1✔
1297
                ...DiagnosticMessages.couldNotFindMatchingEndKeyword('while'),
1298
                range: this.peek().range
1299
            });
1300
            if (!whileBlock) {
1!
1301
                throw this.lastDiagnosticAsError();
×
1302
            }
1303
        } else {
1304
            endWhile = this.advance();
21✔
1305
        }
1306

1307
        return new WhileStatement(
22✔
1308
            { while: whileKeyword, endWhile: endWhile },
1309
            condition,
1310
            whileBlock
1311
        );
1312
    }
1313

1314
    private exitWhile(): ExitWhileStatement {
1315
        let keyword = this.advance();
7✔
1316

1317
        return new ExitWhileStatement({ exitWhile: keyword });
7✔
1318
    }
1319

1320
    private forStatement(): ForStatement {
1321
        const forToken = this.advance();
34✔
1322
        const initializer = this.assignment();
34✔
1323

1324
        //TODO: newline allowed?
1325

1326
        const toToken = this.advance();
33✔
1327
        const finalValue = this.expression();
33✔
1328
        let incrementExpression: Expression | undefined;
1329
        let stepToken: Token | undefined;
1330

1331
        if (this.check(TokenKind.Step)) {
33✔
1332
            stepToken = this.advance();
10✔
1333
            incrementExpression = this.expression();
10✔
1334
        } else {
1335
            // BrightScript for/to/step loops default to a step of 1 if no `step` is provided
1336
        }
1337

1338
        this.consumeStatementSeparators();
33✔
1339

1340
        let body = this.block(TokenKind.EndFor, TokenKind.Next);
33✔
1341
        let endForToken: Token;
1342
        if (!body || !this.checkAny(TokenKind.EndFor, TokenKind.Next)) {
33✔
1343
            this.diagnostics.push({
1✔
1344
                ...DiagnosticMessages.expectedEndForOrNextToTerminateForLoop(),
1345
                range: this.peek().range
1346
            });
1347
            if (!body) {
1!
1348
                throw this.lastDiagnosticAsError();
×
1349
            }
1350
        } else {
1351
            endForToken = this.advance();
32✔
1352
        }
1353

1354
        // WARNING: BrightScript doesn't delete the loop initial value after a for/to loop! It just
1355
        // stays around in scope with whatever value it was when the loop exited.
1356
        return new ForStatement(
33✔
1357
            forToken,
1358
            initializer,
1359
            toToken,
1360
            finalValue,
1361
            body,
1362
            endForToken,
1363
            stepToken,
1364
            incrementExpression
1365
        );
1366
    }
1367

1368
    private forEachStatement(): ForEachStatement {
1369
        let forEach = this.advance();
20✔
1370
        let name = this.advance();
20✔
1371

1372
        let maybeIn = this.peek();
20✔
1373
        if (this.check(TokenKind.Identifier) && maybeIn.text.toLowerCase() === 'in') {
20!
1374
            this.advance();
20✔
1375
        } else {
1376
            this.diagnostics.push({
×
1377
                ...DiagnosticMessages.expectedInAfterForEach(name.text),
1378
                range: this.peek().range
1379
            });
1380
            throw this.lastDiagnosticAsError();
×
1381
        }
1382

1383
        let target = this.expression();
20✔
1384
        if (!target) {
20!
1385
            this.diagnostics.push({
×
1386
                ...DiagnosticMessages.expectedExpressionAfterForEachIn(),
1387
                range: this.peek().range
1388
            });
1389
            throw this.lastDiagnosticAsError();
×
1390
        }
1391

1392
        this.consumeStatementSeparators();
20✔
1393

1394
        let body = this.block(TokenKind.EndFor, TokenKind.Next);
20✔
1395
        if (!body) {
20!
1396
            this.diagnostics.push({
×
1397
                ...DiagnosticMessages.expectedEndForOrNextToTerminateForLoop(),
1398
                range: this.peek().range
1399
            });
1400
            throw this.lastDiagnosticAsError();
×
1401
        }
1402

1403
        let endFor = this.advance();
20✔
1404

1405
        return new ForEachStatement(
20✔
1406
            {
1407
                forEach: forEach,
1408
                in: maybeIn,
1409
                endFor: endFor
1410
            },
1411
            name,
1412
            target,
1413
            body
1414
        );
1415
    }
1416

1417
    private exitFor(): ExitForStatement {
1418
        let keyword = this.advance();
4✔
1419

1420
        return new ExitForStatement({ exitFor: keyword });
4✔
1421
    }
1422

1423
    private commentStatement() {
1424
        //if this comment is on the same line as the previous statement,
1425
        //then this comment should be treated as a single-line comment
1426
        let prev = this.previous();
231✔
1427
        if (prev?.range?.end.line === this.peek().range?.start.line) {
231✔
1428
            return new CommentStatement([this.advance()]);
128✔
1429
        } else {
1430
            let comments = [this.advance()];
103✔
1431
            while (this.check(TokenKind.Newline) && this.checkNext(TokenKind.Comment)) {
103✔
1432
                this.advance();
20✔
1433
                comments.push(this.advance());
20✔
1434
            }
1435
            return new CommentStatement(comments);
103✔
1436
        }
1437
    }
1438

1439
    private namespaceStatement(): NamespaceStatement | undefined {
1440
        this.warnIfNotBrighterScriptMode('namespace');
261✔
1441
        let keyword = this.advance();
261✔
1442

1443
        this.namespaceAndFunctionDepth++;
261✔
1444

1445
        let name = this.getNamespacedVariableNameExpression();
261✔
1446
        //set the current namespace name
1447
        let result = new NamespaceStatement(keyword, name, null, null);
260✔
1448

1449
        this.globalTerminators.push([TokenKind.EndNamespace]);
260✔
1450
        let body = this.body();
260✔
1451
        this.globalTerminators.pop();
260✔
1452

1453
        let endKeyword: Token;
1454
        if (this.check(TokenKind.EndNamespace)) {
260✔
1455
            endKeyword = this.advance();
259✔
1456
        } else {
1457
            //the `end namespace` keyword is missing. add a diagnostic, but keep parsing
1458
            this.diagnostics.push({
1✔
1459
                ...DiagnosticMessages.couldNotFindMatchingEndKeyword('namespace'),
1460
                range: keyword.range
1461
            });
1462
        }
1463

1464
        this.namespaceAndFunctionDepth--;
260✔
1465

1466
        result.body = body;
260✔
1467
        result.endKeyword = endKeyword;
260✔
1468
        this._references.namespaceStatements.push(result);
260✔
1469
        //cache the range property so that plugins can't affect it
1470
        result.cacheRange();
260✔
1471
        result.body.symbolTable.name += `: namespace '${result.name}'`;
260✔
1472
        return result;
260✔
1473
    }
1474

1475
    /**
1476
     * Get an expression with identifiers separated by periods. Useful for namespaces and class extends
1477
     */
1478
    private getNamespacedVariableNameExpression(ignoreDiagnostics = false) {
383✔
1479
        let firstIdentifier: Identifier;
1480
        if (ignoreDiagnostics) {
480✔
1481
            if (this.checkAny(...this.allowedLocalIdentifiers)) {
2!
1482
                firstIdentifier = this.advance() as Identifier;
×
1483
            } else {
1484
                throw new Error();
2✔
1485
            }
1486
        } else {
1487
            firstIdentifier = this.consume(
478✔
1488
                DiagnosticMessages.expectedIdentifierAfterKeyword(this.previous().text),
1489
                TokenKind.Identifier,
1490
                ...this.allowedLocalIdentifiers
1491
            ) as Identifier;
1492
        }
1493
        let expr: DottedGetExpression | VariableExpression;
1494

1495
        if (firstIdentifier) {
474!
1496
            // force it into an identifier so the AST makes some sense
1497
            firstIdentifier.kind = TokenKind.Identifier;
474✔
1498
            const varExpr = new VariableExpression(firstIdentifier);
474✔
1499
            expr = varExpr;
474✔
1500

1501
            //consume multiple dot identifiers (i.e. `Name.Space.Can.Have.Many.Parts`)
1502
            while (this.check(TokenKind.Dot)) {
474✔
1503
                let dot = this.tryConsume(
152✔
1504
                    DiagnosticMessages.unexpectedToken(this.peek().text),
1505
                    TokenKind.Dot
1506
                );
1507
                if (!dot) {
152!
1508
                    break;
×
1509
                }
1510
                let identifier = this.tryConsume(
152✔
1511
                    DiagnosticMessages.expectedIdentifier(),
1512
                    TokenKind.Identifier,
1513
                    ...this.allowedLocalIdentifiers,
1514
                    ...AllowedProperties
1515
                ) as Identifier;
1516

1517
                if (!identifier) {
152✔
1518
                    break;
3✔
1519
                }
1520
                // force it into an identifier so the AST makes some sense
1521
                identifier.kind = TokenKind.Identifier;
149✔
1522
                expr = new DottedGetExpression(expr, identifier, dot);
149✔
1523
            }
1524
        }
1525
        return new NamespacedVariableNameExpression(expr);
474✔
1526
    }
1527

1528
    /**
1529
     * Add an 'unexpected token' diagnostic for any token found between current and the first stopToken found.
1530
     */
1531
    private flagUntil(...stopTokens: TokenKind[]) {
1532
        while (!this.checkAny(...stopTokens) && !this.isAtEnd()) {
7!
1533
            let token = this.advance();
×
1534
            this.diagnostics.push({
×
1535
                ...DiagnosticMessages.unexpectedToken(token.text),
1536
                range: token.range
1537
            });
1538
        }
1539
    }
1540

1541
    /**
1542
     * Consume tokens until one of the `stopTokenKinds` is encountered
1543
     * @param stopTokenKinds a list of tokenKinds where any tokenKind in this list will result in a match
1544
     * @returns - the list of tokens consumed, EXCLUDING the `stopTokenKind` (you can use `this.peek()` to see which one it was)
1545
     */
1546
    private consumeUntil(...stopTokenKinds: TokenKind[]) {
1547
        let result = [] as Token[];
65✔
1548
        //take tokens until we encounter one of the stopTokenKinds
1549
        while (!stopTokenKinds.includes(this.peek().kind)) {
65✔
1550
            result.push(this.advance());
170✔
1551
        }
1552
        return result;
65✔
1553
    }
1554

1555
    private constDeclaration(): ConstStatement | undefined {
1556
        this.warnIfNotBrighterScriptMode('const declaration');
82✔
1557
        const constToken = this.advance();
82✔
1558
        const nameToken = this.identifier(...this.allowedLocalIdentifiers);
82✔
1559
        const equalToken = this.consumeToken(TokenKind.Equal);
82✔
1560
        const expression = this.expression();
82✔
1561
        const statement = new ConstStatement({
82✔
1562
            const: constToken,
1563
            name: nameToken,
1564
            equals: equalToken
1565
        }, expression);
1566
        this._references.constStatements.push(statement);
82✔
1567
        return statement;
82✔
1568
    }
1569

1570
    private libraryStatement(): LibraryStatement | undefined {
1571
        let libStatement = new LibraryStatement({
14✔
1572
            library: this.advance(),
1573
            //grab the next token only if it's a string
1574
            filePath: this.tryConsume(
1575
                DiagnosticMessages.expectedStringLiteralAfterKeyword('library'),
1576
                TokenKind.StringLiteral
1577
            )
1578
        });
1579

1580
        this._references.libraryStatements.push(libStatement);
14✔
1581
        return libStatement;
14✔
1582
    }
1583

1584
    private importStatement() {
1585
        this.warnIfNotBrighterScriptMode('import statements');
44✔
1586
        let importStatement = new ImportStatement(
44✔
1587
            this.advance(),
1588
            //grab the next token only if it's a string
1589
            this.tryConsume(
1590
                DiagnosticMessages.expectedStringLiteralAfterKeyword('import'),
1591
                TokenKind.StringLiteral
1592
            )
1593
        );
1594

1595
        this._references.importStatements.push(importStatement);
44✔
1596
        return importStatement;
44✔
1597
    }
1598

1599
    private typecastStatement() {
1600
        this.warnIfNotBrighterScriptMode('typecast statements');
5✔
1601
        const typecastToken = this.advance();
5✔
1602
        const obj = this.identifier(...this.allowedLocalIdentifiers);
5✔
1603
        const asToken = this.advance();
5✔
1604
        const typeToken = this.typeToken();
5✔
1605
        return new TypecastStatement({
5✔
1606
            typecast: typecastToken,
1607
            obj: obj,
1608
            as: asToken,
1609
            type: typeToken
1610
        });
1611
    }
1612

1613
    private aliasStatement() {
1614
        this.warnIfNotBrighterScriptMode('alias statements');
2✔
1615
        const aliasToken = this.advance();
2✔
1616
        const name = this.identifier(...this.allowedLocalIdentifiers);
2✔
1617
        const equals = this.consumeToken(TokenKind.Equal);
2✔
1618
        const value = this.identifier(...this.allowedLocalIdentifiers);
2✔
1619
        return new AliasStatement({
2✔
1620
            alias: aliasToken,
1621
            name: name,
1622
            equals: equals,
1623
            value: value
1624
        });
1625
    }
1626

1627
    private annotationExpression() {
1628
        const atToken = this.advance();
61✔
1629
        const identifier = this.tryConsume(DiagnosticMessages.expectedIdentifier(), TokenKind.Identifier, ...AllowedProperties);
61✔
1630
        if (identifier) {
61✔
1631
            identifier.kind = TokenKind.Identifier;
60✔
1632
        }
1633
        let annotation = new AnnotationExpression(atToken, identifier);
61✔
1634
        this.pendingAnnotations.push(annotation);
60✔
1635

1636
        //optional arguments
1637
        if (this.check(TokenKind.LeftParen)) {
60✔
1638
            let leftParen = this.advance();
24✔
1639
            annotation.call = this.finishCall(leftParen, annotation, false);
24✔
1640
        }
1641
        return annotation;
60✔
1642
    }
1643

1644
    private typeStatement(): TypeStatement | undefined {
1645
        this.warnIfNotBrighterScriptMode('type statements');
10✔
1646
        const typeToken = this.advance();
10✔
1647
        const name = this.identifier(...this.allowedLocalIdentifiers);
10✔
1648
        const equals = this.tryConsume(
10✔
1649
            DiagnosticMessages.expectedToken(TokenKind.Equal),
1650
            TokenKind.Equal
1651
        );
1652
        let value = this.typeToken();
10✔
1653

1654
        let typeStmt = new TypeStatement({
10✔
1655
            type: typeToken,
1656
            name: name,
1657
            equals: equals,
1658
            value: value
1659

1660
        });
1661
        this._references.typeStatements.push(typeStmt);
10✔
1662
        return typeStmt;
10✔
1663
    }
1664

1665
    private ternaryExpression(test?: Expression): TernaryExpression {
1666
        this.warnIfNotBrighterScriptMode('ternary operator');
94✔
1667
        if (!test) {
94!
1668
            test = this.expression();
×
1669
        }
1670
        const questionMarkToken = this.advance();
94✔
1671

1672
        //consume newlines or comments
1673
        while (this.checkAny(TokenKind.Newline, TokenKind.Comment)) {
94✔
1674
            this.advance();
8✔
1675
        }
1676

1677
        let consequent: Expression;
1678
        try {
94✔
1679
            consequent = this.expression();
94✔
1680
        } catch { }
1681

1682
        //consume newlines or comments
1683
        while (this.checkAny(TokenKind.Newline, TokenKind.Comment)) {
94✔
1684
            this.advance();
6✔
1685
        }
1686

1687
        const colonToken = this.tryConsumeToken(TokenKind.Colon);
94✔
1688

1689
        //consume newlines
1690
        while (this.checkAny(TokenKind.Newline, TokenKind.Comment)) {
94✔
1691
            this.advance();
12✔
1692
        }
1693
        let alternate: Expression;
1694
        try {
94✔
1695
            alternate = this.expression();
94✔
1696
        } catch { }
1697

1698
        return new TernaryExpression(test, questionMarkToken, consequent, colonToken, alternate);
94✔
1699
    }
1700

1701
    private nullCoalescingExpression(test: Expression): NullCoalescingExpression {
1702
        this.warnIfNotBrighterScriptMode('null coalescing operator');
30✔
1703
        const questionQuestionToken = this.advance();
30✔
1704
        const alternate = this.expression();
30✔
1705
        return new NullCoalescingExpression(test, questionQuestionToken, alternate);
30✔
1706
    }
1707

1708
    private regexLiteralExpression() {
1709
        this.warnIfNotBrighterScriptMode('regular expression literal');
45✔
1710
        return new RegexLiteralExpression({
45✔
1711
            regexLiteral: this.advance()
1712
        });
1713
    }
1714

1715
    private templateString(isTagged: boolean): TemplateStringExpression | TaggedTemplateStringExpression {
1716
        this.warnIfNotBrighterScriptMode('template string');
55✔
1717

1718
        //get the tag name
1719
        let tagName: Identifier;
1720
        if (isTagged) {
55✔
1721
            tagName = this.consume(DiagnosticMessages.expectedIdentifier(), TokenKind.Identifier, ...AllowedProperties) as Identifier;
8✔
1722
            // force it into an identifier so the AST makes some sense
1723
            tagName.kind = TokenKind.Identifier;
8✔
1724
        }
1725

1726
        let quasis = [] as TemplateStringQuasiExpression[];
55✔
1727
        let expressions = [];
55✔
1728
        let openingBacktick = this.peek();
55✔
1729
        this.advance();
55✔
1730
        let currentQuasiExpressionParts = [];
55✔
1731
        while (!this.isAtEnd() && !this.check(TokenKind.BackTick)) {
55✔
1732
            let next = this.peek();
206✔
1733
            if (next.kind === TokenKind.TemplateStringQuasi) {
206✔
1734
                //a quasi can actually be made up of multiple quasis when it includes char literals
1735
                currentQuasiExpressionParts.push(
130✔
1736
                    new LiteralExpression(next)
1737
                );
1738
                this.advance();
130✔
1739
            } else if (next.kind === TokenKind.EscapedCharCodeLiteral) {
76✔
1740
                currentQuasiExpressionParts.push(
33✔
1741
                    new EscapedCharCodeLiteralExpression(<any>next)
1742
                );
1743
                this.advance();
33✔
1744
            } else {
1745
                //finish up the current quasi
1746
                quasis.push(
43✔
1747
                    new TemplateStringQuasiExpression(currentQuasiExpressionParts)
1748
                );
1749
                currentQuasiExpressionParts = [];
43✔
1750

1751
                if (next.kind === TokenKind.TemplateStringExpressionBegin) {
43!
1752
                    this.advance();
43✔
1753
                }
1754
                //now keep this expression
1755
                expressions.push(this.expression());
43✔
1756
                if (!this.isAtEnd() && this.check(TokenKind.TemplateStringExpressionEnd)) {
43!
1757
                    //TODO is it an error if this is not present?
1758
                    this.advance();
43✔
1759
                } else {
1760
                    this.diagnostics.push({
×
1761
                        ...DiagnosticMessages.unterminatedTemplateExpression(),
1762
                        range: util.getRange(openingBacktick, this.peek())
1763
                    });
1764
                    throw this.lastDiagnosticAsError();
×
1765
                }
1766
            }
1767
        }
1768

1769
        //store the final set of quasis
1770
        quasis.push(
55✔
1771
            new TemplateStringQuasiExpression(currentQuasiExpressionParts)
1772
        );
1773

1774
        if (this.isAtEnd()) {
55✔
1775
            //error - missing backtick
1776
            this.diagnostics.push({
2✔
1777
                ...DiagnosticMessages.unterminatedTemplateStringAtEndOfFile(),
1778
                range: util.getRange(openingBacktick, this.peek())
1779
            });
1780
            throw this.lastDiagnosticAsError();
2✔
1781

1782
        } else {
1783
            let closingBacktick = this.advance();
53✔
1784
            if (isTagged) {
53✔
1785
                return new TaggedTemplateStringExpression(tagName, openingBacktick, quasis, expressions, closingBacktick);
8✔
1786
            } else {
1787
                return new TemplateStringExpression(openingBacktick, quasis, expressions, closingBacktick);
45✔
1788
            }
1789
        }
1790
    }
1791

1792
    private tryCatchStatement(): TryCatchStatement {
1793
        const tryToken = this.advance();
27✔
1794
        const statement = new TryCatchStatement(
27✔
1795
            { try: tryToken }
1796
        );
1797

1798
        //ensure statement separator
1799
        this.consumeStatementSeparators();
27✔
1800

1801
        statement.tryBranch = this.block(TokenKind.Catch, TokenKind.EndTry);
27✔
1802

1803
        const peek = this.peek();
27✔
1804
        if (peek.kind !== TokenKind.Catch) {
27✔
1805
            this.diagnostics.push({
2✔
1806
                ...DiagnosticMessages.expectedCatchBlockInTryCatch(),
1807
                range: this.peek().range
1808
            });
1809
            //gracefully handle end-try
1810
            if (peek.kind === TokenKind.EndTry) {
2✔
1811
                statement.tokens.endTry = this.advance();
1✔
1812
            }
1813
            return statement;
2✔
1814
        }
1815
        const catchStmt = new CatchStatement({ catch: this.advance() });
25✔
1816
        statement.catchStatement = catchStmt;
25✔
1817

1818
        const exceptionVarToken = this.tryConsume(DiagnosticMessages.missingExceptionVarToFollowCatch(), TokenKind.Identifier, ...this.allowedLocalIdentifiers);
25✔
1819
        if (exceptionVarToken) {
25✔
1820
            // force it into an identifier so the AST makes some sense
1821
            exceptionVarToken.kind = TokenKind.Identifier;
23✔
1822
            catchStmt.exceptionVariable = exceptionVarToken as Identifier;
23✔
1823
        }
1824

1825
        //ensure statement sepatator
1826
        this.consumeStatementSeparators();
25✔
1827

1828
        catchStmt.catchBranch = this.block(TokenKind.EndTry);
25✔
1829

1830
        if (this.peek().kind !== TokenKind.EndTry) {
25✔
1831
            this.diagnostics.push({
1✔
1832
                ...DiagnosticMessages.expectedEndTryToTerminateTryCatch(),
1833
                range: this.peek().range
1834
            });
1835
        } else {
1836
            statement.tokens.endTry = this.advance();
24✔
1837
        }
1838
        return statement;
25✔
1839
    }
1840

1841
    private throwStatement() {
1842
        const throwToken = this.advance();
11✔
1843
        let expression: Expression;
1844
        if (this.checkAny(TokenKind.Newline, TokenKind.Colon)) {
11✔
1845
            this.diagnostics.push({
1✔
1846
                ...DiagnosticMessages.missingExceptionExpressionAfterThrowKeyword(),
1847
                range: throwToken.range
1848
            });
1849
        } else {
1850
            expression = this.expression();
10✔
1851
        }
1852
        return new ThrowStatement(throwToken, expression);
9✔
1853
    }
1854

1855
    private dimStatement() {
1856
        const dim = this.advance();
43✔
1857

1858
        let identifier = this.tryConsume(DiagnosticMessages.expectedIdentifierAfterKeyword('dim'), TokenKind.Identifier, ...this.allowedLocalIdentifiers) as Identifier;
43✔
1859
        // force to an identifier so the AST makes some sense
1860
        if (identifier) {
43✔
1861
            identifier.kind = TokenKind.Identifier;
41✔
1862
        }
1863

1864
        let leftSquareBracket = this.tryConsume(DiagnosticMessages.missingLeftSquareBracketAfterDimIdentifier(), TokenKind.LeftSquareBracket);
43✔
1865

1866
        let expressions: Expression[] = [];
43✔
1867
        let expression: Expression;
1868
        do {
43✔
1869
            try {
82✔
1870
                expression = this.expression();
82✔
1871
                expressions.push(expression);
77✔
1872
                if (this.check(TokenKind.Comma)) {
77✔
1873
                    this.advance();
39✔
1874
                } else {
1875
                    // will also exit for right square braces
1876
                    break;
38✔
1877
                }
1878
            } catch (error) {
1879
            }
1880
        } while (expression);
1881

1882
        if (expressions.length === 0) {
43✔
1883
            this.diagnostics.push({
5✔
1884
                ...DiagnosticMessages.missingExpressionsInDimStatement(),
1885
                range: this.peek().range
1886
            });
1887
        }
1888
        let rightSquareBracket = this.tryConsume(DiagnosticMessages.missingRightSquareBracketAfterDimIdentifier(), TokenKind.RightSquareBracket);
43✔
1889
        return new DimStatement(dim, identifier, leftSquareBracket, expressions, rightSquareBracket);
43✔
1890
    }
1891

1892
    private ifStatement(): IfStatement {
1893
        // colon before `if` is usually not allowed, unless it's after `then`
1894
        if (this.current > 0) {
215✔
1895
            const prev = this.previous();
210✔
1896
            if (prev.kind === TokenKind.Colon) {
210✔
1897
                if (this.current > 1 && this.tokens[this.current - 2].kind !== TokenKind.Then) {
3✔
1898
                    this.diagnostics.push({
1✔
1899
                        ...DiagnosticMessages.unexpectedColonBeforeIfStatement(),
1900
                        range: prev.range
1901
                    });
1902
                }
1903
            }
1904
        }
1905

1906
        const ifToken = this.advance();
215✔
1907
        const startingRange = ifToken.range;
215✔
1908

1909
        const condition = this.expression();
215✔
1910
        let thenBranch: Block;
1911
        let elseBranch: IfStatement | Block | undefined;
1912

1913
        let thenToken: Token | undefined;
1914
        let endIfToken: Token | undefined;
1915
        let elseToken: Token | undefined;
1916

1917
        //optional `then`
1918
        if (this.check(TokenKind.Then)) {
213✔
1919
            thenToken = this.advance();
148✔
1920
        }
1921

1922
        //is it inline or multi-line if?
1923
        const isInlineIfThen = !this.checkAny(TokenKind.Newline, TokenKind.Colon, TokenKind.Comment);
213✔
1924

1925
        if (isInlineIfThen) {
213✔
1926
            /*** PARSE INLINE IF STATEMENT ***/
1927

1928
            thenBranch = this.inlineConditionalBranch(TokenKind.Else, TokenKind.EndIf);
32✔
1929

1930
            if (!thenBranch) {
32!
1931
                this.diagnostics.push({
×
1932
                    ...DiagnosticMessages.expectedStatementToFollowConditionalCondition(ifToken.text),
1933
                    range: this.peek().range
1934
                });
1935
                throw this.lastDiagnosticAsError();
×
1936
            } else {
1937
                this.ensureInline(thenBranch.statements);
32✔
1938
            }
1939

1940
            //else branch
1941
            if (this.check(TokenKind.Else)) {
32✔
1942
                elseToken = this.advance();
19✔
1943

1944
                if (this.check(TokenKind.If)) {
19✔
1945
                    // recurse-read `else if`
1946
                    elseBranch = this.ifStatement();
4✔
1947

1948
                    //no multi-line if chained with an inline if
1949
                    if (!elseBranch.isInline) {
4✔
1950
                        this.diagnostics.push({
2✔
1951
                            ...DiagnosticMessages.expectedInlineIfStatement(),
1952
                            range: elseBranch.range
1953
                        });
1954
                    }
1955

1956
                } else if (this.checkAny(TokenKind.Newline, TokenKind.Colon)) {
15!
1957
                    //expecting inline else branch
1958
                    this.diagnostics.push({
×
1959
                        ...DiagnosticMessages.expectedInlineIfStatement(),
1960
                        range: this.peek().range
1961
                    });
1962
                    throw this.lastDiagnosticAsError();
×
1963
                } else {
1964
                    elseBranch = this.inlineConditionalBranch(TokenKind.Else, TokenKind.EndIf);
15✔
1965

1966
                    if (elseBranch) {
15!
1967
                        this.ensureInline(elseBranch.statements);
15✔
1968
                    }
1969
                }
1970

1971
                if (!elseBranch) {
19!
1972
                    //missing `else` branch
1973
                    this.diagnostics.push({
×
1974
                        ...DiagnosticMessages.expectedStatementToFollowElse(),
1975
                        range: this.peek().range
1976
                    });
1977
                    throw this.lastDiagnosticAsError();
×
1978
                }
1979
            }
1980

1981
            if (!elseBranch || !isIfStatement(elseBranch)) {
32✔
1982
                //enforce newline at the end of the inline if statement
1983
                const peek = this.peek();
28✔
1984
                if (peek.kind !== TokenKind.Newline && peek.kind !== TokenKind.Comment && !this.isAtEnd()) {
28✔
1985
                    //ignore last error if it was about a colon
1986
                    if (this.previous().kind === TokenKind.Colon) {
3!
1987
                        this.diagnostics.pop();
3✔
1988
                        this.current--;
3✔
1989
                    }
1990
                    //newline is required
1991
                    this.diagnostics.push({
3✔
1992
                        ...DiagnosticMessages.expectedFinalNewline(),
1993
                        range: this.peek().range
1994
                    });
1995
                }
1996
            }
1997

1998
        } else {
1999
            /*** PARSE MULTI-LINE IF STATEMENT ***/
2000

2001
            thenBranch = this.blockConditionalBranch(ifToken);
181✔
2002

2003
            //ensure newline/colon before next keyword
2004
            this.ensureNewLineOrColon();
179✔
2005

2006
            //else branch
2007
            if (this.check(TokenKind.Else)) {
179✔
2008
                elseToken = this.advance();
92✔
2009

2010
                if (this.check(TokenKind.If)) {
92✔
2011
                    // recurse-read `else if`
2012
                    elseBranch = this.ifStatement();
40✔
2013

2014
                } else {
2015
                    elseBranch = this.blockConditionalBranch(ifToken);
52✔
2016

2017
                    //ensure newline/colon before next keyword
2018
                    this.ensureNewLineOrColon();
52✔
2019
                }
2020
            }
2021

2022
            if (!isIfStatement(elseBranch)) {
179✔
2023
                if (this.check(TokenKind.EndIf)) {
139✔
2024
                    endIfToken = this.advance();
137✔
2025

2026
                } else {
2027
                    //missing endif
2028
                    this.diagnostics.push({
2✔
2029
                        ...DiagnosticMessages.expectedEndIfToCloseIfStatement(startingRange.start),
2030
                        range: ifToken.range
2031
                    });
2032
                }
2033
            }
2034
        }
2035

2036
        return new IfStatement(
211✔
2037
            {
2038
                if: ifToken,
2039
                then: thenToken,
2040
                endIf: endIfToken,
2041
                else: elseToken
2042
            },
2043
            condition,
2044
            thenBranch,
2045
            elseBranch,
2046
            isInlineIfThen
2047
        );
2048
    }
2049

2050
    //consume a `then` or `else` branch block of an `if` statement
2051
    private blockConditionalBranch(ifToken: Token) {
2052
        //keep track of the current error count, because if the then branch fails,
2053
        //we will trash them in favor of a single error on if
2054
        let diagnosticsLengthBeforeBlock = this.diagnostics.length;
233✔
2055

2056
        // we're parsing a multi-line ("block") form of the BrightScript if/then and must find
2057
        // a trailing "end if" or "else if"
2058
        let branch = this.block(TokenKind.EndIf, TokenKind.Else);
233✔
2059

2060
        if (!branch) {
233✔
2061
            //throw out any new diagnostics created as a result of a `then` block parse failure.
2062
            //the block() function will discard the current line, so any discarded diagnostics will
2063
            //resurface if they are legitimate, and not a result of a malformed if statement
2064
            this.diagnostics.splice(diagnosticsLengthBeforeBlock, this.diagnostics.length - diagnosticsLengthBeforeBlock);
2✔
2065

2066
            //this whole if statement is bogus...add error to the if token and hard-fail
2067
            this.diagnostics.push({
2✔
2068
                ...DiagnosticMessages.expectedEndIfElseIfOrElseToTerminateThenBlock(),
2069
                range: ifToken.range
2070
            });
2071
            throw this.lastDiagnosticAsError();
2✔
2072
        }
2073
        return branch;
231✔
2074
    }
2075

2076
    private ensureNewLineOrColon(silent = false) {
231✔
2077
        const prev = this.previous().kind;
425✔
2078
        if (prev !== TokenKind.Newline && prev !== TokenKind.Colon) {
425✔
2079
            if (!silent) {
125✔
2080
                this.diagnostics.push({
6✔
2081
                    ...DiagnosticMessages.expectedNewlineOrColon(),
2082
                    range: this.peek().range
2083
                });
2084
            }
2085
            return false;
125✔
2086
        }
2087
        return true;
300✔
2088
    }
2089

2090
    //ensure each statement of an inline block is single-line
2091
    private ensureInline(statements: Statement[]) {
2092
        for (const stat of statements) {
47✔
2093
            if (isIfStatement(stat) && !stat.isInline) {
54✔
2094
                this.diagnostics.push({
2✔
2095
                    ...DiagnosticMessages.expectedInlineIfStatement(),
2096
                    range: stat.range
2097
                });
2098
            }
2099
        }
2100
    }
2101

2102
    //consume inline branch of an `if` statement
2103
    private inlineConditionalBranch(...additionalTerminators: BlockTerminator[]): Block | undefined {
2104
        let statements = [];
54✔
2105
        //attempt to get the next statement without using `this.declaration`
2106
        //which seems a bit hackish to get to work properly
2107
        let statement = this.statement();
54✔
2108
        if (!statement) {
54!
2109
            return undefined;
×
2110
        }
2111
        statements.push(statement);
54✔
2112
        const startingRange = statement.range;
54✔
2113

2114
        //look for colon statement separator
2115
        let foundColon = false;
54✔
2116
        while (this.match(TokenKind.Colon)) {
54✔
2117
            foundColon = true;
12✔
2118
        }
2119

2120
        //if a colon was found, add the next statement or err if unexpected
2121
        if (foundColon) {
54✔
2122
            if (!this.checkAny(TokenKind.Newline, ...additionalTerminators)) {
12✔
2123
                //if not an ending keyword, add next statement
2124
                let extra = this.inlineConditionalBranch(...additionalTerminators);
7✔
2125
                if (!extra) {
7!
2126
                    return undefined;
×
2127
                }
2128
                statements.push(...extra.statements);
7✔
2129
            } else {
2130
                //error: colon before next keyword
2131
                const colon = this.previous();
5✔
2132
                this.diagnostics.push({
5✔
2133
                    ...DiagnosticMessages.unexpectedToken(colon.text),
2134
                    range: colon.range
2135
                });
2136
            }
2137
        }
2138
        return new Block(statements, startingRange);
54✔
2139
    }
2140

2141
    private expressionStatement(expr: Expression): ExpressionStatement | IncrementStatement {
2142
        let expressionStart = this.peek();
367✔
2143

2144
        if (this.checkAny(TokenKind.PlusPlus, TokenKind.MinusMinus)) {
367✔
2145
            let operator = this.advance();
20✔
2146

2147
            if (this.checkAny(TokenKind.PlusPlus, TokenKind.MinusMinus)) {
20✔
2148
                this.diagnostics.push({
1✔
2149
                    ...DiagnosticMessages.consecutiveIncrementDecrementOperatorsAreNotAllowed(),
2150
                    range: this.peek().range
2151
                });
2152
                throw this.lastDiagnosticAsError();
1✔
2153
            } else if (isCallExpression(expr)) {
19✔
2154
                this.diagnostics.push({
1✔
2155
                    ...DiagnosticMessages.incrementDecrementOperatorsAreNotAllowedAsResultOfFunctionCall(),
2156
                    range: expressionStart.range
2157
                });
2158
                throw this.lastDiagnosticAsError();
1✔
2159
            }
2160

2161
            const result = new IncrementStatement(expr, operator);
18✔
2162
            this._references.expressions.add(result);
18✔
2163
            return result;
18✔
2164
        }
2165

2166
        if (isCallExpression(expr) || isCallfuncExpression(expr)) {
347✔
2167
            return new ExpressionStatement(expr);
280✔
2168
        }
2169

2170

2171
        //you're not allowed to do dottedGet or XmlAttrGet after a function call
2172
        if (isDottedGetExpression(expr)) {
67✔
2173
            this.diagnostics.push({
21✔
2174
                ...DiagnosticMessages.propAccessNotPermittedAfterFunctionCallInExpressionStatement('Property'),
2175
                range: util.createBoundingRange(expr.dot, expr.name)
2176
            });
2177
            //we can recover gracefully here even though it's invalid syntax
2178
            return new ExpressionStatement(expr);
21✔
2179

2180
            //you're not allowed to do indexedGet expressions after a function call
2181
        } else if (isIndexedGetExpression(expr)) {
46✔
2182
            this.diagnostics.push({
1✔
2183
                ...DiagnosticMessages.propAccessNotPermittedAfterFunctionCallInExpressionStatement('Index'),
2184
                range: util.createBoundingRange(expr.openingSquare, expr.index, expr.closingSquare)
2185
            });
2186
            //we can recover gracefully here even though it's invalid syntax
2187
            return new ExpressionStatement(expr);
1✔
2188
            //you're not allowed to do XmlAttrGet after a function call
2189
        } else if (isXmlAttributeGetExpression(expr)) {
45✔
2190
            this.diagnostics.push({
1✔
2191
                ...DiagnosticMessages.propAccessNotPermittedAfterFunctionCallInExpressionStatement('XML attribute'),
2192
                range: util.createBoundingRange(expr.at, expr.name)
2193
            });
2194
            //we can recover gracefully here even though it's invalid syntax
2195
            return new ExpressionStatement(expr);
1✔
2196
        }
2197

2198

2199
        //at this point, it's probably an error. However, we recover a little more gracefully by creating an assignment
2200
        this.diagnostics.push({
44✔
2201
            ...DiagnosticMessages.expectedStatementOrFunctionCallButReceivedExpression(),
2202
            range: expressionStart.range
2203
        });
2204

2205
        throw this.lastDiagnosticAsError();
44✔
2206
    }
2207

2208
    private setStatement(): DottedSetStatement | IndexedSetStatement | ExpressionStatement | IncrementStatement | AssignmentStatement {
2209
        /**
2210
         * Attempts to find an expression-statement or an increment statement.
2211
         * While calls are valid expressions _and_ statements, increment (e.g. `foo++`)
2212
         * statements aren't valid expressions. They _do_ however fall under the same parsing
2213
         * priority as standalone function calls though, so we can parse them in the same way.
2214
         */
2215
        let expr = this.call();
684✔
2216
        if (this.checkAny(...AssignmentOperators) && !(isCallExpression(expr))) {
646✔
2217
            let left = expr;
282✔
2218
            let operator = this.advance();
282✔
2219
            let right = this.expression();
282✔
2220

2221
            // Create a dotted or indexed "set" based on the left-hand side's type
2222
            if (isIndexedGetExpression(left)) {
282✔
2223
                return new IndexedSetStatement(
40✔
2224
                    left.obj,
2225
                    left.index,
2226
                    operator.kind === TokenKind.Equal
2227
                        ? right
40✔
2228
                        : new BinaryExpression(left, operator, right),
2229
                    left.openingSquare,
2230
                    left.closingSquare,
2231
                    left.additionalIndexes,
2232
                    operator.kind === TokenKind.Equal
2233
                        ? operator
40✔
2234
                        : { kind: TokenKind.Equal, text: '=', range: operator.range }
2235
                );
2236
            } else if (isDottedGetExpression(left)) {
242✔
2237
                return new DottedSetStatement(
239✔
2238
                    left.obj,
2239
                    left.name,
2240
                    operator.kind === TokenKind.Equal
2241
                        ? right
239✔
2242
                        : new BinaryExpression(left, operator, right),
2243
                    left.dot,
2244
                    operator.kind === TokenKind.Equal
2245
                        ? operator
239✔
2246
                        : { kind: TokenKind.Equal, text: '=', range: operator.range }
2247
                );
2248
            }
2249
        }
2250
        return this.expressionStatement(expr);
367✔
2251
    }
2252

2253
    private printStatement(): PrintStatement {
2254
        let printKeyword = this.advance();
687✔
2255

2256
        let values: (
2257
            | Expression
2258
            | PrintSeparatorTab
2259
            | PrintSeparatorSpace)[] = [];
687✔
2260

2261
        while (!this.checkEndOfStatement()) {
687✔
2262
            if (this.check(TokenKind.Semicolon)) {
771✔
2263
                values.push(this.advance() as PrintSeparatorSpace);
20✔
2264
            } else if (this.check(TokenKind.Comma)) {
751✔
2265
                values.push(this.advance() as PrintSeparatorTab);
13✔
2266
            } else if (this.check(TokenKind.Else)) {
738✔
2267
                break; // inline branch
7✔
2268
            } else {
2269
                values.push(this.expression());
731✔
2270
            }
2271
        }
2272

2273
        //print statements can be empty, so look for empty print conditions
2274
        if (!values.length) {
686✔
2275
            let emptyStringLiteral = createStringLiteral('');
4✔
2276
            values.push(emptyStringLiteral);
4✔
2277
        }
2278

2279
        let last = values[values.length - 1];
686✔
2280
        if (isToken(last)) {
686✔
2281
            // TODO: error, expected value
2282
        }
2283

2284
        return new PrintStatement({ print: printKeyword }, values);
686✔
2285
    }
2286

2287
    /**
2288
     * Parses a return statement with an optional return value.
2289
     * @returns an AST representation of a return statement.
2290
     */
2291
    private returnStatement(): ReturnStatement {
2292
        let tokens = { return: this.previous() };
216✔
2293

2294
        if (this.checkEndOfStatement()) {
216✔
2295
            return new ReturnStatement(tokens);
14✔
2296
        }
2297

2298
        let toReturn = this.check(TokenKind.Else) ? undefined : this.expression();
202✔
2299
        return new ReturnStatement(tokens, toReturn);
201✔
2300
    }
2301

2302
    /**
2303
     * Parses a `label` statement
2304
     * @returns an AST representation of an `label` statement.
2305
     */
2306
    private labelStatement() {
2307
        let tokens = {
12✔
2308
            identifier: this.advance(),
2309
            colon: this.advance()
2310
        };
2311

2312
        //label must be alone on its line, this is probably not a label
2313
        if (!this.checkAny(TokenKind.Newline, TokenKind.Comment)) {
12✔
2314
            //rewind and cancel
2315
            this.current -= 2;
2✔
2316
            throw new CancelStatementError();
2✔
2317
        }
2318

2319
        return new LabelStatement(tokens);
10✔
2320
    }
2321

2322
    /**
2323
     * Parses a `continue` statement
2324
     */
2325
    private continueStatement() {
2326
        return new ContinueStatement({
12✔
2327
            continue: this.advance(),
2328
            loopType: this.tryConsume(
2329
                DiagnosticMessages.expectedToken(TokenKind.While, TokenKind.For),
2330
                TokenKind.While, TokenKind.For
2331
            )
2332
        });
2333
    }
2334

2335
    /**
2336
     * Parses a `goto` statement
2337
     * @returns an AST representation of an `goto` statement.
2338
     */
2339
    private gotoStatement() {
2340
        let tokens = {
12✔
2341
            goto: this.advance(),
2342
            label: this.consume(
2343
                DiagnosticMessages.expectedLabelIdentifierAfterGotoKeyword(),
2344
                TokenKind.Identifier
2345
            )
2346
        };
2347

2348
        return new GotoStatement(tokens);
10✔
2349
    }
2350

2351
    /**
2352
     * Parses an `end` statement
2353
     * @returns an AST representation of an `end` statement.
2354
     */
2355
    private endStatement() {
2356
        let endTokens = { end: this.advance() };
8✔
2357

2358
        return new EndStatement(endTokens);
8✔
2359
    }
2360
    /**
2361
     * Parses a `stop` statement
2362
     * @returns an AST representation of a `stop` statement
2363
     */
2364
    private stopStatement() {
2365
        let tokens = { stop: this.advance() };
16✔
2366

2367
        return new StopStatement(tokens);
16✔
2368
    }
2369

2370
    /**
2371
     * Parses a block, looking for a specific terminating TokenKind to denote completion.
2372
     * Always looks for `end sub`/`end function` to handle unterminated blocks.
2373
     * @param terminators the token(s) that signifies the end of this block; all other terminators are
2374
     *                    ignored.
2375
     */
2376
    private block(...terminators: BlockTerminator[]): Block | undefined {
2377
        const parentAnnotations = this.enterAnnotationBlock();
2,169✔
2378

2379
        this.consumeStatementSeparators(true);
2,169✔
2380
        let startingToken = this.peek();
2,169✔
2381

2382
        const statements: Statement[] = [];
2,169✔
2383
        while (!this.isAtEnd() && !this.checkAny(TokenKind.EndSub, TokenKind.EndFunction, ...terminators)) {
2,169✔
2384
            //grab the location of the current token
2385
            let loopCurrent = this.current;
2,529✔
2386
            let dec = this.declaration();
2,529✔
2387
            if (dec) {
2,529✔
2388
                if (!isAnnotationExpression(dec)) {
2,464✔
2389
                    this.consumePendingAnnotations(dec);
2,457✔
2390
                    statements.push(dec);
2,457✔
2391
                }
2392

2393
                //ensure statement separator
2394
                this.consumeStatementSeparators();
2,464✔
2395

2396
            } else {
2397
                //something went wrong. reset to the top of the loop
2398
                this.current = loopCurrent;
65✔
2399

2400
                //scrap the entire line (hopefully whatever failed has added a diagnostic)
2401
                this.consumeUntil(TokenKind.Newline, TokenKind.Colon, TokenKind.Eof);
65✔
2402

2403
                //trash the next token. this prevents an infinite loop. not exactly sure why we need this,
2404
                //but there's already an error in the file being parsed, so just leave this line here
2405
                this.advance();
65✔
2406

2407
                //consume potential separators
2408
                this.consumeStatementSeparators(true);
65✔
2409
            }
2410
        }
2411

2412
        if (this.isAtEnd()) {
2,169✔
2413
            return undefined;
5✔
2414
            // TODO: Figure out how to handle unterminated blocks well
2415
        } else if (terminators.length > 0) {
2,164✔
2416
            //did we hit end-sub / end-function while looking for some other terminator?
2417
            //if so, we need to restore the statement separator
2418
            let prev = this.previous().kind;
358✔
2419
            let peek = this.peek().kind;
358✔
2420
            if (
358✔
2421
                (peek === TokenKind.EndSub || peek === TokenKind.EndFunction) &&
720!
2422
                (prev === TokenKind.Newline || prev === TokenKind.Colon)
2423
            ) {
2424
                this.current--;
6✔
2425
            }
2426
        }
2427

2428
        this.exitAnnotationBlock(parentAnnotations);
2,164✔
2429
        return new Block(statements, startingToken.range);
2,164✔
2430
    }
2431

2432
    /**
2433
     * Attach pending annotations to the provided statement,
2434
     * and then reset the annotations array
2435
     */
2436
    consumePendingAnnotations(statement: Statement) {
2437
        if (this.pendingAnnotations.length) {
6,238✔
2438
            statement.annotations = this.pendingAnnotations;
45✔
2439
            this.pendingAnnotations = [];
45✔
2440
        }
2441
    }
2442

2443
    enterAnnotationBlock() {
2444
        const pending = this.pendingAnnotations;
5,301✔
2445
        this.pendingAnnotations = [];
5,301✔
2446
        return pending;
5,301✔
2447
    }
2448

2449
    exitAnnotationBlock(parentAnnotations: AnnotationExpression[]) {
2450
        // non consumed annotations are an error
2451
        if (this.pendingAnnotations.length) {
5,295✔
2452
            for (const annotation of this.pendingAnnotations) {
4✔
2453
                this.diagnostics.push({
6✔
2454
                    ...DiagnosticMessages.unusedAnnotation(),
2455
                    range: annotation.range
2456
                });
2457
            }
2458
        }
2459
        this.pendingAnnotations = parentAnnotations;
5,295✔
2460
    }
2461

2462
    private expression(findTypeCast = true): Expression {
4,177✔
2463
        let expression = this.anonymousFunction();
4,428✔
2464
        let asToken: Token;
2465
        let typeToken: Token;
2466
        if (findTypeCast) {
4,392✔
2467
            do {
4,141✔
2468
                if (this.check(TokenKind.As)) {
4,155✔
2469
                    this.warnIfNotBrighterScriptMode('type cast');
14✔
2470
                    // Check if this expression is wrapped in any type casts
2471
                    // allows for multiple casts:
2472
                    // myVal = foo() as dynamic as string
2473

2474
                    asToken = this.advance();
14✔
2475
                    typeToken = this.typeToken();
14✔
2476
                    if (asToken && typeToken) {
14!
2477
                        expression = new TypeCastExpression(expression, asToken, typeToken);
14✔
2478
                    }
2479
                } else {
2480
                    break;
4,141✔
2481
                }
2482

2483
            } while (asToken && typeToken);
28✔
2484
        }
2485
        this._references.expressions.add(expression);
4,392✔
2486
        return expression;
4,392✔
2487
    }
2488

2489
    private anonymousFunction(): Expression {
2490
        if (this.checkAny(TokenKind.Sub, TokenKind.Function)) {
4,428✔
2491
            const func = this.functionDeclaration(true);
79✔
2492
            //if there's an open paren after this, this is an IIFE
2493
            if (this.check(TokenKind.LeftParen)) {
79✔
2494
                return this.finishCall(this.advance(), func);
3✔
2495
            } else {
2496
                return func;
76✔
2497
            }
2498
        }
2499

2500
        let expr = this.boolean();
4,349✔
2501

2502
        if (this.check(TokenKind.Question)) {
4,313✔
2503
            return this.ternaryExpression(expr);
94✔
2504
        } else if (this.check(TokenKind.QuestionQuestion)) {
4,219✔
2505
            return this.nullCoalescingExpression(expr);
30✔
2506
        } else {
2507
            return expr;
4,189✔
2508
        }
2509
    }
2510

2511
    private boolean(): Expression {
2512
        let expr = this.relational();
4,349✔
2513

2514
        while (this.matchAny(TokenKind.And, TokenKind.Or)) {
4,313✔
2515
            let operator = this.previous();
28✔
2516
            let right = this.relational();
28✔
2517
            this.addExpressionsToReferences(expr, right);
28✔
2518
            expr = new BinaryExpression(expr, operator, right);
28✔
2519
        }
2520

2521
        return expr;
4,313✔
2522
    }
2523

2524
    private relational(): Expression {
2525
        let expr = this.additive();
4,393✔
2526

2527
        while (
4,357✔
2528
            this.matchAny(
2529
                TokenKind.Equal,
2530
                TokenKind.LessGreater,
2531
                TokenKind.Greater,
2532
                TokenKind.GreaterEqual,
2533
                TokenKind.Less,
2534
                TokenKind.LessEqual
2535
            )
2536
        ) {
2537
            let operator = this.previous();
150✔
2538
            let right = this.additive();
150✔
2539
            this.addExpressionsToReferences(expr, right);
150✔
2540
            expr = new BinaryExpression(expr, operator, right);
150✔
2541
        }
2542

2543
        return expr;
4,357✔
2544
    }
2545

2546
    private addExpressionsToReferences(...expressions: Expression[]) {
2547
        for (const expression of expressions) {
338✔
2548
            if (!isBinaryExpression(expression)) {
630✔
2549
                this.references.expressions.add(expression);
588✔
2550
            }
2551
        }
2552
    }
2553

2554
    // TODO: bitshift
2555

2556
    private additive(): Expression {
2557
        let expr = this.multiplicative();
4,543✔
2558

2559
        while (this.matchAny(TokenKind.Plus, TokenKind.Minus)) {
4,507✔
2560
            let operator = this.previous();
87✔
2561
            let right = this.multiplicative();
87✔
2562
            this.addExpressionsToReferences(expr, right);
87✔
2563
            expr = new BinaryExpression(expr, operator, right);
87✔
2564
        }
2565

2566
        return expr;
4,507✔
2567
    }
2568

2569
    private multiplicative(): Expression {
2570
        let expr = this.exponential();
4,630✔
2571

2572
        while (this.matchAny(
4,594✔
2573
            TokenKind.Forwardslash,
2574
            TokenKind.Backslash,
2575
            TokenKind.Star,
2576
            TokenKind.Mod,
2577
            TokenKind.LeftShift,
2578
            TokenKind.RightShift
2579
        )) {
2580
            let operator = this.previous();
21✔
2581
            let right = this.exponential();
21✔
2582
            this.addExpressionsToReferences(expr, right);
21✔
2583
            expr = new BinaryExpression(expr, operator, right);
21✔
2584
        }
2585

2586
        return expr;
4,594✔
2587
    }
2588

2589
    private exponential(): Expression {
2590
        let expr = this.prefixUnary();
4,651✔
2591

2592
        while (this.match(TokenKind.Caret)) {
4,615✔
2593
            let operator = this.previous();
6✔
2594
            let right = this.prefixUnary();
6✔
2595
            this.addExpressionsToReferences(expr, right);
6✔
2596
            expr = new BinaryExpression(expr, operator, right);
6✔
2597
        }
2598

2599
        return expr;
4,615✔
2600
    }
2601

2602
    private prefixUnary(): Expression {
2603
        const nextKind = this.peek().kind;
4,679✔
2604
        if (nextKind === TokenKind.Not) {
4,679✔
2605
            this.current++; //advance
16✔
2606
            let operator = this.previous();
16✔
2607
            let right = this.relational();
16✔
2608
            return new UnaryExpression(operator, right);
16✔
2609
        } else if (nextKind === TokenKind.Minus || nextKind === TokenKind.Plus) {
4,663✔
2610
            this.current++; //advance
22✔
2611
            let operator = this.previous();
22✔
2612
            let right = this.prefixUnary();
22✔
2613
            return new UnaryExpression(operator, right);
22✔
2614
        }
2615
        return this.call();
4,641✔
2616
    }
2617

2618
    private indexedGet(expr: Expression) {
2619
        let openingSquare = this.previous();
146✔
2620
        let questionDotToken = this.getMatchingTokenAtOffset(-2, TokenKind.QuestionDot);
146✔
2621
        let indexes: Expression[] = [];
146✔
2622

2623

2624
        //consume leading newlines
2625
        while (this.match(TokenKind.Newline)) { }
146✔
2626

2627
        try {
146✔
2628
            indexes.push(
146✔
2629
                this.expression()
2630
            );
2631
            //consume additional indexes separated by commas
2632
            while (this.check(TokenKind.Comma)) {
145✔
2633
                //discard the comma
2634
                this.advance();
17✔
2635
                indexes.push(
17✔
2636
                    this.expression()
2637
                );
2638
            }
2639
        } catch (error) {
2640
            this.rethrowNonDiagnosticError(error);
1✔
2641
        }
2642
        //consume trailing newlines
2643
        while (this.match(TokenKind.Newline)) { }
146✔
2644

2645
        const closingSquare = this.tryConsume(
146✔
2646
            DiagnosticMessages.expectedRightSquareBraceAfterArrayOrObjectIndex(),
2647
            TokenKind.RightSquareBracket
2648
        );
2649

2650
        return new IndexedGetExpression(expr, indexes.shift(), openingSquare, closingSquare, questionDotToken, indexes);
146✔
2651
    }
2652

2653
    private newExpression() {
2654
        this.warnIfNotBrighterScriptMode(`using 'new' keyword to construct a class`);
44✔
2655
        let newToken = this.advance();
44✔
2656

2657
        let nameExpr = this.getNamespacedVariableNameExpression();
44✔
2658
        let leftParen = this.consume(
44✔
2659
            DiagnosticMessages.unexpectedToken(this.peek().text),
2660
            TokenKind.LeftParen,
2661
            TokenKind.QuestionLeftParen
2662
        );
2663
        let call = this.finishCall(leftParen, nameExpr);
40✔
2664
        //pop the call from the  callExpressions list because this is technically something else
2665
        this.callExpressions.pop();
40✔
2666
        let result = new NewExpression(newToken, call);
40✔
2667
        this._references.newExpressions.push(result);
40✔
2668
        return result;
40✔
2669
    }
2670

2671
    /**
2672
     * A callfunc expression (i.e. `node@.someFunctionOnNode()`)
2673
     */
2674
    private callfunc(callee: Expression): Expression {
2675
        this.warnIfNotBrighterScriptMode('callfunc operator');
25✔
2676
        let operator = this.previous();
25✔
2677
        let methodName = this.consume(DiagnosticMessages.expectedIdentifier(), TokenKind.Identifier, ...AllowedProperties);
25✔
2678
        // force it into an identifier so the AST makes some sense
2679
        methodName.kind = TokenKind.Identifier;
24✔
2680
        let openParen = this.consume(DiagnosticMessages.expectedOpenParenToFollowCallfuncIdentifier(), TokenKind.LeftParen);
24✔
2681
        let call = this.finishCall(openParen, callee, false);
24✔
2682

2683
        return new CallfuncExpression(callee, operator, methodName as Identifier, openParen, call.args, call.closingParen);
24✔
2684
    }
2685

2686
    private call(): Expression {
2687
        if (this.check(TokenKind.New) && this.checkAnyNext(TokenKind.Identifier, ...this.allowedLocalIdentifiers)) {
5,325✔
2688
            return this.newExpression();
44✔
2689
        }
2690
        let expr = this.primary();
5,281✔
2691
        //an expression to keep for _references
2692
        let referenceCallExpression: Expression;
2693
        while (true) {
5,212✔
2694
            if (this.matchAny(TokenKind.LeftParen, TokenKind.QuestionLeftParen)) {
6,944✔
2695
                expr = this.finishCall(this.previous(), expr);
576✔
2696
                //store this call expression in references
2697
                referenceCallExpression = expr;
576✔
2698

2699
            } else if (this.matchAny(TokenKind.LeftSquareBracket, TokenKind.QuestionLeftSquare) || this.matchSequence(TokenKind.QuestionDot, TokenKind.LeftSquareBracket)) {
6,368✔
2700
                expr = this.indexedGet(expr);
144✔
2701

2702
            } else if (this.match(TokenKind.Callfunc)) {
6,224✔
2703
                expr = this.callfunc(expr);
25✔
2704
                //store this callfunc expression in references
2705
                referenceCallExpression = expr;
24✔
2706

2707
            } else if (this.matchAny(TokenKind.Dot, TokenKind.QuestionDot)) {
6,199✔
2708
                if (this.match(TokenKind.LeftSquareBracket)) {
1,012✔
2709
                    expr = this.indexedGet(expr);
2✔
2710
                } else {
2711
                    let dot = this.previous();
1,010✔
2712
                    let name = this.tryConsume(
1,010✔
2713
                        DiagnosticMessages.expectedPropertyNameAfterPeriod(),
2714
                        TokenKind.Identifier,
2715
                        ...AllowedProperties
2716
                    );
2717
                    if (!name) {
1,010✔
2718
                        break;
24✔
2719
                    }
2720

2721
                    // force it into an identifier so the AST makes some sense
2722
                    name.kind = TokenKind.Identifier;
986✔
2723
                    expr = new DottedGetExpression(expr, name as Identifier, dot);
986✔
2724

2725
                    this.addPropertyHints(name);
986✔
2726
                }
2727

2728
            } else if (this.checkAny(TokenKind.At, TokenKind.QuestionAt)) {
5,187✔
2729
                let dot = this.advance();
12✔
2730
                let name = this.tryConsume(
12✔
2731
                    DiagnosticMessages.expectedAttributeNameAfterAtSymbol(),
2732
                    TokenKind.Identifier,
2733
                    ...AllowedProperties
2734
                );
2735

2736
                // force it into an identifier so the AST makes some sense
2737
                name.kind = TokenKind.Identifier;
12✔
2738
                if (!name) {
12!
2739
                    break;
×
2740
                }
2741
                expr = new XmlAttributeGetExpression(expr, name as Identifier, dot);
12✔
2742
                //only allow a single `@` expression
2743
                break;
12✔
2744

2745
            } else {
2746
                break;
5,175✔
2747
            }
2748
        }
2749
        //if we found a callExpression, add it to `expressions` in references
2750
        if (referenceCallExpression) {
5,211✔
2751
            this._references.expressions.add(referenceCallExpression);
562✔
2752
        }
2753
        return expr;
5,211✔
2754
    }
2755

2756
    private finishCall(openingParen: Token, callee: Expression, addToCallExpressionList = true) {
619✔
2757
        let args = [] as Expression[];
667✔
2758
        while (this.match(TokenKind.Newline)) { }
667✔
2759

2760
        if (!this.check(TokenKind.RightParen)) {
667✔
2761
            do {
344✔
2762
                while (this.match(TokenKind.Newline)) { }
517✔
2763

2764
                if (args.length >= CallExpression.MaximumArguments) {
517!
2765
                    this.diagnostics.push({
×
2766
                        ...DiagnosticMessages.tooManyCallableArguments(args.length, CallExpression.MaximumArguments),
2767
                        range: this.peek().range
2768
                    });
2769
                    throw this.lastDiagnosticAsError();
×
2770
                }
2771
                try {
517✔
2772
                    args.push(this.expression());
517✔
2773
                } catch (error) {
2774
                    this.rethrowNonDiagnosticError(error);
5✔
2775
                    // we were unable to get an expression, so don't continue
2776
                    break;
5✔
2777
                }
2778
            } while (this.match(TokenKind.Comma));
2779
        }
2780

2781
        while (this.match(TokenKind.Newline)) { }
667✔
2782

2783
        const closingParen = this.tryConsume(
667✔
2784
            DiagnosticMessages.expectedRightParenAfterFunctionCallArguments(),
2785
            TokenKind.RightParen
2786
        );
2787

2788
        let expression = new CallExpression(callee, openingParen, closingParen, args);
667✔
2789
        if (addToCallExpressionList) {
667✔
2790
            this.callExpressions.push(expression);
619✔
2791
        }
2792
        return expression;
667✔
2793
    }
2794

2795
    /**
2796
     * Tries to get the next token as a type
2797
     * Allows for built-in types (double, string, etc.) or namespaced custom types in Brighterscript mode
2798
     * Will return a token of whatever is next to be parsed
2799
     * Will allow v1 type syntax (typed arrays, union types), but there is no validation on types used this way
2800
     */
2801
    private typeToken(ignoreDiagnostics = false): Token {
653✔
2802
        let typeToken: Token;
2803
        let lookForUnions = true;
658✔
2804
        let isAUnion = false;
658✔
2805
        let resultToken;
2806
        while (lookForUnions) {
658✔
2807
            lookForUnions = false;
673✔
2808

2809
            if (this.checkAny(...DeclarableTypes)) {
673✔
2810
                // Token is a built in type
2811
                typeToken = this.advance();
534✔
2812
            } else if (this.options.mode === ParseMode.BrighterScript) {
139✔
2813
                try {
113✔
2814
                    if (this.check(TokenKind.LeftCurlyBrace)) {
113✔
2815
                        // could be an inline interface
2816
                        typeToken = this.inlineInterface();
16✔
2817
                    } else {
2818
                        // see if we can get a namespaced identifer
2819
                        const qualifiedType = this.getNamespacedVariableNameExpression(ignoreDiagnostics);
97✔
2820
                        typeToken = createToken(TokenKind.Identifier, qualifiedType.getName(this.options.mode), qualifiedType.range);
93✔
2821
                    }
2822
                } catch {
2823
                    //could not get an identifier - just get whatever's next
2824
                    typeToken = this.advance();
4✔
2825
                }
2826
            } else {
2827
                // just get whatever's next
2828
                typeToken = this.advance();
26✔
2829
            }
2830
            resultToken = resultToken ?? typeToken;
673✔
2831
            if (resultToken && this.options.mode === ParseMode.BrighterScript) {
673✔
2832
                // check for brackets for typed arrays
2833
                while (this.check(TokenKind.LeftSquareBracket) && this.peekNext().kind === TokenKind.RightSquareBracket) {
559✔
2834
                    const leftBracket = this.advance();
10✔
2835
                    const rightBracket = this.advance();
10✔
2836
                    typeToken = createToken(TokenKind.Identifier, typeToken.text + leftBracket.text + rightBracket.text, util.createBoundingRange(typeToken, leftBracket, rightBracket));
10✔
2837
                    resultToken = createToken(TokenKind.Dynamic, null, typeToken.range);
10✔
2838
                }
2839

2840
                if (this.check(TokenKind.Or)) {
559✔
2841
                    lookForUnions = true;
15✔
2842
                    let orToken = this.advance();
15✔
2843
                    resultToken = createToken(TokenKind.Dynamic, null, util.createBoundingRange(resultToken, typeToken, orToken));
15✔
2844
                    isAUnion = true;
15✔
2845
                }
2846
            }
2847
        }
2848
        if (isAUnion) {
658✔
2849
            resultToken = createToken(TokenKind.Dynamic, null, util.createBoundingRange(resultToken, typeToken));
12✔
2850
        }
2851
        return resultToken;
658✔
2852
    }
2853

2854
    private inlineInterface() {
2855
        const openToken = this.advance();
16✔
2856
        const memberTokens: Token[] = [];
16✔
2857
        memberTokens.push(openToken);
16✔
2858
        while (this.matchAny(TokenKind.Newline, TokenKind.Comment)) { }
16✔
2859
        while (this.checkAny(TokenKind.Identifier, ...AllowedProperties, TokenKind.StringLiteral, TokenKind.Optional)) {
16✔
2860
            let optionalKeyword = this.consumeTokenIf(TokenKind.Optional);
20✔
2861
            if (this.checkAny(TokenKind.Identifier, ...AllowedProperties, TokenKind.StringLiteral)) {
20!
2862
                if (this.check(TokenKind.As)) {
20!
2863
                    if (this.checkAnyNext(TokenKind.Comment, TokenKind.Newline)) {
×
2864
                        // as <EOL>
2865
                        // `as` is the field name
2866
                    } else if (this.checkNext(TokenKind.As)) {
×
2867
                        //  as as ____
2868
                        // first `as` is the field name
2869
                    } else if (optionalKeyword) {
×
2870
                        // optional as ____
2871
                        // optional is the field name, `as` starts type
2872
                        // rewind current token
2873
                        optionalKeyword = null;
×
2874
                        this.current--;
×
2875
                    }
2876
                }
2877
            } else {
2878
                // no name after `optional` ... optional is the name
2879
                // rewind current token
2880
                optionalKeyword = null;
×
2881
                this.current--;
×
2882
            }
2883
            if (optionalKeyword) {
20✔
2884
                memberTokens.push(optionalKeyword);
1✔
2885
            }
2886
            if (!this.checkAny(TokenKind.Identifier, ...this.allowedLocalIdentifiers, TokenKind.StringLiteral)) {
20!
2887
                this.diagnostics.push({
×
2888
                    ...DiagnosticMessages.unexpectedToken(this.peek().text),
2889
                    range: this.peek().range
2890
                });
2891
                throw this.lastDiagnosticAsError();
×
2892
            }
2893
            if (this.checkAny(TokenKind.Identifier, ...AllowedProperties, TokenKind.StringLiteral)) {
20!
2894
                this.advance();
20✔
2895
            } else {
2896
                this.diagnostics.push({
×
2897
                    ...DiagnosticMessages.unexpectedToken(this.peek().text),
2898
                    range: this.peek().range
2899
                });
2900
                throw this.lastDiagnosticAsError();
×
2901
            }
2902

2903
            if (this.check(TokenKind.As)) {
20✔
2904
                memberTokens.push(this.advance()); // as
18✔
2905
                memberTokens.push(this.typeToken()); // type
18✔
2906
            }
2907
            while (this.matchAny(TokenKind.Comma, TokenKind.Newline, TokenKind.Comment)) { }
20✔
2908
        }
2909
        if (!this.check(TokenKind.RightCurlyBrace)) {
16!
2910
            this.diagnostics.push({
×
2911
                ...DiagnosticMessages.unexpectedToken(this.peek().text),
2912
                range: this.peek().range
2913
            });
2914
            throw this.lastDiagnosticAsError();
×
2915
        }
2916
        const closeToken = this.advance();
16✔
2917
        memberTokens.push(closeToken);
16✔
2918

2919
        const completeInlineInterfaceToken = createToken(TokenKind.Dynamic, null, util.createBoundingRange(...memberTokens));
16✔
2920

2921
        return completeInlineInterfaceToken;
16✔
2922
    }
2923

2924
    private primary(): Expression {
2925
        switch (true) {
5,281✔
2926
            case this.matchAny(
5,281!
2927
                TokenKind.False,
2928
                TokenKind.True,
2929
                TokenKind.Invalid,
2930
                TokenKind.IntegerLiteral,
2931
                TokenKind.LongIntegerLiteral,
2932
                TokenKind.FloatLiteral,
2933
                TokenKind.DoubleLiteral,
2934
                TokenKind.StringLiteral
2935
            ):
2936
                return new LiteralExpression(this.previous());
3,122✔
2937

2938
            //capture source literals (LINE_NUM if brightscript, or a bunch of them if brighterscript)
2939
            case this.matchAny(TokenKind.LineNumLiteral, ...(this.options.mode === ParseMode.BrightScript ? [] : BrighterScriptSourceLiterals)):
2,159✔
2940
                return new SourceLiteralExpression(this.previous());
35✔
2941

2942
            //template string
2943
            case this.check(TokenKind.BackTick):
2944
                return this.templateString(false);
47✔
2945

2946
            //tagged template string (currently we do not support spaces between the identifier and the backtick)
2947
            case this.checkAny(TokenKind.Identifier, ...AllowedLocalIdentifiers) && this.checkNext(TokenKind.BackTick):
3,661✔
2948
                return this.templateString(true);
8✔
2949

2950
            case this.matchAny(TokenKind.Identifier, ...this.allowedLocalIdentifiers):
2951
                return new VariableExpression(this.previous() as Identifier);
1,583✔
2952

2953
            case this.match(TokenKind.LeftParen):
2954
                let left = this.previous();
38✔
2955
                let expr = this.expression();
38✔
2956
                let right = this.consume(
37✔
2957
                    DiagnosticMessages.unmatchedLeftParenAfterExpression(),
2958
                    TokenKind.RightParen
2959
                );
2960
                return new GroupingExpression({ left: left, right: right }, expr);
37✔
2961

2962
            case this.matchAny(TokenKind.LeftSquareBracket):
2963
                return this.arrayLiteral();
128✔
2964

2965
            case this.match(TokenKind.LeftCurlyBrace):
2966
                return this.aaLiteral();
206✔
2967

2968
            case this.matchAny(TokenKind.Pos, TokenKind.Tab):
2969
                let token = Object.assign(this.previous(), {
×
2970
                    kind: TokenKind.Identifier
2971
                }) as Identifier;
2972
                return new VariableExpression(token);
×
2973

2974
            case this.checkAny(TokenKind.Function, TokenKind.Sub):
2975
                return this.anonymousFunction();
×
2976

2977
            case this.check(TokenKind.RegexLiteral):
2978
                return this.regexLiteralExpression();
45✔
2979

2980
            case this.check(TokenKind.Comment):
2981
                return new CommentStatement([this.advance()]);
3✔
2982

2983
            default:
2984
                //if we found an expected terminator, don't throw a diagnostic...just return undefined
2985
                if (this.checkAny(...this.peekGlobalTerminators())) {
66!
2986
                    //don't throw a diagnostic, just return undefined
2987

2988
                    //something went wrong...throw an error so the upstream processor can scrap this line and move on
2989
                } else {
2990
                    this.diagnostics.push({
66✔
2991
                        ...DiagnosticMessages.unexpectedToken(this.peek().text),
2992
                        range: this.peek().range
2993
                    });
2994
                    throw this.lastDiagnosticAsError();
66✔
2995
                }
2996
        }
2997
    }
2998

2999
    private arrayLiteral() {
3000
        let elements: Array<Expression | CommentStatement> = [];
128✔
3001
        let openingSquare = this.previous();
128✔
3002

3003
        //add any comment found right after the opening square
3004
        if (this.check(TokenKind.Comment)) {
128✔
3005
            elements.push(new CommentStatement([this.advance()]));
1✔
3006
        }
3007

3008
        while (this.match(TokenKind.Newline)) {
128✔
3009
        }
3010
        let closingSquare: Token;
3011

3012
        if (!this.match(TokenKind.RightSquareBracket)) {
128✔
3013
            try {
97✔
3014
                elements.push(this.expression());
97✔
3015

3016
                while (this.matchAny(TokenKind.Comma, TokenKind.Newline, TokenKind.Comment)) {
96✔
3017
                    if (this.checkPrevious(TokenKind.Comment) || this.check(TokenKind.Comment)) {
142✔
3018
                        let comment = this.check(TokenKind.Comment) ? this.advance() : this.previous();
4✔
3019
                        elements.push(new CommentStatement([comment]));
4✔
3020
                    }
3021
                    while (this.match(TokenKind.Newline)) {
142✔
3022

3023
                    }
3024

3025
                    if (this.check(TokenKind.RightSquareBracket)) {
142✔
3026
                        break;
32✔
3027
                    }
3028

3029
                    elements.push(this.expression());
110✔
3030
                }
3031
            } catch (error: any) {
3032
                this.rethrowNonDiagnosticError(error);
2✔
3033
            }
3034

3035
            closingSquare = this.tryConsume(
97✔
3036
                DiagnosticMessages.unmatchedLeftSquareBraceAfterArrayLiteral(),
3037
                TokenKind.RightSquareBracket
3038
            );
3039
        } else {
3040
            closingSquare = this.previous();
31✔
3041
        }
3042

3043
        //this.consume("Expected newline or ':' after array literal", TokenKind.Newline, TokenKind.Colon, TokenKind.Eof);
3044
        return new ArrayLiteralExpression(elements, openingSquare, closingSquare);
128✔
3045
    }
3046

3047
    private aaLiteral() {
3048
        let openingBrace = this.previous();
206✔
3049
        let members: Array<AAMemberExpression | CommentStatement> = [];
206✔
3050

3051
        let key = () => {
206✔
3052
            let result = {
212✔
3053
                colonToken: null as Token,
3054
                keyToken: null as Token,
3055
                range: null as Range
3056
            };
3057
            if (this.checkAny(TokenKind.Identifier, ...AllowedProperties)) {
212✔
3058
                result.keyToken = this.identifier(...AllowedProperties);
181✔
3059
            } else if (this.check(TokenKind.StringLiteral)) {
31!
3060
                result.keyToken = this.advance();
31✔
3061
            } else {
3062
                this.diagnostics.push({
×
3063
                    ...DiagnosticMessages.unexpectedAAKey(),
3064
                    range: this.peek().range
3065
                });
3066
                throw this.lastDiagnosticAsError();
×
3067
            }
3068

3069
            result.colonToken = this.consume(
212✔
3070
                DiagnosticMessages.expectedColonBetweenAAKeyAndvalue(),
3071
                TokenKind.Colon
3072
            );
3073
            result.range = util.getRange(result.keyToken, result.colonToken);
211✔
3074
            return result;
211✔
3075
        };
3076

3077
        while (this.match(TokenKind.Newline)) { }
206✔
3078
        let closingBrace: Token;
3079
        if (!this.match(TokenKind.RightCurlyBrace)) {
206✔
3080
            let lastAAMember: AAMemberExpression;
3081
            try {
158✔
3082
                if (this.check(TokenKind.Comment)) {
158✔
3083
                    lastAAMember = null;
7✔
3084
                    members.push(new CommentStatement([this.advance()]));
7✔
3085
                } else {
3086
                    let k = key();
151✔
3087
                    let expr = this.expression();
151✔
3088
                    lastAAMember = new AAMemberExpression(
150✔
3089
                        k.keyToken,
3090
                        k.colonToken,
3091
                        expr
3092
                    );
3093
                    members.push(lastAAMember);
150✔
3094
                }
3095

3096
                while (this.matchAny(TokenKind.Comma, TokenKind.Newline, TokenKind.Colon, TokenKind.Comment)) {
157✔
3097
                    // collect comma at end of expression
3098
                    if (lastAAMember && this.checkPrevious(TokenKind.Comma)) {
196✔
3099
                        lastAAMember.commaToken = this.previous();
38✔
3100
                    }
3101

3102
                    //check for comment at the end of the current line
3103
                    if (this.check(TokenKind.Comment) || this.checkPrevious(TokenKind.Comment)) {
196✔
3104
                        let token = this.checkPrevious(TokenKind.Comment) ? this.previous() : this.advance();
14✔
3105
                        members.push(new CommentStatement([token]));
14✔
3106
                    } else {
3107
                        this.consumeStatementSeparators(true);
182✔
3108

3109
                        //check for a comment on its own line
3110
                        if (this.check(TokenKind.Comment) || this.checkPrevious(TokenKind.Comment)) {
182✔
3111
                            let token = this.checkPrevious(TokenKind.Comment) ? this.previous() : this.advance();
1!
3112
                            lastAAMember = null;
1✔
3113
                            members.push(new CommentStatement([token]));
1✔
3114
                            continue;
1✔
3115
                        }
3116

3117
                        if (this.check(TokenKind.RightCurlyBrace)) {
181✔
3118
                            break;
120✔
3119
                        }
3120
                        let k = key();
61✔
3121
                        let expr = this.expression();
60✔
3122
                        lastAAMember = new AAMemberExpression(
60✔
3123
                            k.keyToken,
3124
                            k.colonToken,
3125
                            expr
3126
                        );
3127
                        members.push(lastAAMember);
60✔
3128
                    }
3129
                }
3130
            } catch (error: any) {
3131
                this.rethrowNonDiagnosticError(error);
2✔
3132
            }
3133

3134
            closingBrace = this.tryConsume(
158✔
3135
                DiagnosticMessages.unmatchedLeftCurlyAfterAALiteral(),
3136
                TokenKind.RightCurlyBrace
3137
            );
3138
        } else {
3139
            closingBrace = this.previous();
48✔
3140
        }
3141

3142
        const aaExpr = new AALiteralExpression(members, openingBrace, closingBrace);
206✔
3143
        this.addPropertyHints(aaExpr);
206✔
3144
        return aaExpr;
206✔
3145
    }
3146

3147
    /**
3148
     * Pop token if we encounter specified token
3149
     */
3150
    private match(tokenKind: TokenKind) {
3151
        if (this.check(tokenKind)) {
20,750✔
3152
            this.current++; //advance
1,580✔
3153
            return true;
1,580✔
3154
        }
3155
        return false;
19,170✔
3156
    }
3157

3158
    /**
3159
     * Pop token if we encounter a token in the specified list
3160
     * @param tokenKinds a list of tokenKinds where any tokenKind in this list will result in a match
3161
     */
3162
    private matchAny(...tokenKinds: TokenKind[]) {
3163
        for (let tokenKind of tokenKinds) {
72,433✔
3164
            if (this.check(tokenKind)) {
213,764✔
3165
                this.current++; //advance
18,348✔
3166
                return true;
18,348✔
3167
            }
3168
        }
3169
        return false;
54,085✔
3170
    }
3171

3172
    /**
3173
     * If the next series of tokens matches the given set of tokens, pop them all
3174
     * @param tokenKinds a list of tokenKinds used to match the next set of tokens
3175
     */
3176
    private matchSequence(...tokenKinds: TokenKind[]) {
3177
        const endIndex = this.current + tokenKinds.length;
6,227✔
3178
        for (let i = 0; i < tokenKinds.length; i++) {
6,227✔
3179
            if (tokenKinds[i] !== this.tokens[this.current + i]?.kind) {
6,251!
3180
                return false;
6,224✔
3181
            }
3182
        }
3183
        this.current = endIndex;
3✔
3184
        return true;
3✔
3185
    }
3186

3187
    /**
3188
     * Get next token matching a specified list, or fail with an error
3189
     */
3190
    private consume(diagnosticInfo: DiagnosticInfo, ...tokenKinds: TokenKind[]): Token {
3191
        let token = this.tryConsume(diagnosticInfo, ...tokenKinds);
7,161✔
3192
        if (token) {
7,161✔
3193
            return token;
7,142✔
3194
        } else {
3195
            let error = new Error(diagnosticInfo.message);
19✔
3196
            (error as any).isDiagnostic = true;
19✔
3197
            throw error;
19✔
3198
        }
3199
    }
3200

3201
    /**
3202
     * Consume next token IF it matches the specified kind. Otherwise, do nothing and return undefined
3203
     */
3204
    private consumeTokenIf(tokenKind: TokenKind) {
3205
        if (this.match(tokenKind)) {
287✔
3206
            return this.previous();
12✔
3207
        }
3208
    }
3209

3210
    private consumeToken(tokenKind: TokenKind) {
3211
        return this.consume(
339✔
3212
            DiagnosticMessages.expectedToken(tokenKind),
3213
            tokenKind
3214
        );
3215
    }
3216

3217
    /**
3218
     * Consume, or add a message if not found. But then continue and return undefined
3219
     */
3220
    private tryConsume(diagnostic: DiagnosticInfo, ...tokenKinds: TokenKind[]): Token | undefined {
3221
        const nextKind = this.peek().kind;
10,550✔
3222
        let foundTokenKind = tokenKinds.some(tokenKind => nextKind === tokenKind);
33,276✔
3223

3224
        if (foundTokenKind) {
10,550✔
3225
            return this.advance();
10,460✔
3226
        }
3227
        this.diagnostics.push({
90✔
3228
            ...diagnostic,
3229
            range: this.peek().range
3230
        });
3231
    }
3232

3233
    private tryConsumeToken(tokenKind: TokenKind) {
3234
        return this.tryConsume(
94✔
3235
            DiagnosticMessages.expectedToken(tokenKind),
3236
            tokenKind
3237
        );
3238
    }
3239

3240
    private consumeStatementSeparators(optional = false) {
4,021✔
3241
        //a comment or EOF mark the end of the statement
3242
        if (this.isAtEnd() || this.check(TokenKind.Comment)) {
13,785✔
3243
            return true;
591✔
3244
        }
3245
        let consumed = false;
13,194✔
3246
        //consume any newlines and colons
3247
        while (this.matchAny(TokenKind.Newline, TokenKind.Colon)) {
13,194✔
3248
            consumed = true;
11,115✔
3249
        }
3250
        if (!optional && !consumed) {
13,194✔
3251
            this.diagnostics.push({
38✔
3252
                ...DiagnosticMessages.expectedNewlineOrColon(),
3253
                range: this.peek().range
3254
            });
3255
        }
3256
        return consumed;
13,194✔
3257
    }
3258

3259
    private advance(): Token {
3260
        if (!this.isAtEnd()) {
23,844✔
3261
            this.current++;
23,826✔
3262
        }
3263
        return this.previous();
23,844✔
3264
    }
3265

3266
    private checkEndOfStatement(): boolean {
3267
        const nextKind = this.peek().kind;
1,666✔
3268
        return [TokenKind.Colon, TokenKind.Newline, TokenKind.Comment, TokenKind.Eof].includes(nextKind);
1,666✔
3269
    }
3270

3271
    private checkPrevious(tokenKind: TokenKind): boolean {
3272
        return this.previous()?.kind === tokenKind;
729!
3273
    }
3274

3275
    /**
3276
     * Check that the next token kind is the expected kind
3277
     * @param tokenKind the expected next kind
3278
     * @returns true if the next tokenKind is the expected value
3279
     */
3280
    private check(tokenKind: TokenKind): boolean {
3281
        const nextKind = this.peek().kind;
366,330✔
3282
        if (nextKind === TokenKind.Eof) {
366,330✔
3283
            return false;
7,705✔
3284
        }
3285
        return nextKind === tokenKind;
358,625✔
3286
    }
3287

3288
    private checkAny(...tokenKinds: TokenKind[]): boolean {
3289
        const nextKind = this.peek().kind;
43,781✔
3290
        if (nextKind === TokenKind.Eof) {
43,781✔
3291
            return false;
215✔
3292
        }
3293
        return tokenKinds.includes(nextKind);
43,566✔
3294
    }
3295

3296
    private checkNext(tokenKind: TokenKind): boolean {
3297
        if (this.isAtEnd()) {
4,937!
3298
            return false;
×
3299
        }
3300
        return this.peekNext().kind === tokenKind;
4,937✔
3301
    }
3302

3303
    private checkAnyNext(...tokenKinds: TokenKind[]): boolean {
3304
        if (this.isAtEnd()) {
2,618!
3305
            return false;
×
3306
        }
3307
        const nextKind = this.peekNext().kind;
2,618✔
3308
        return tokenKinds.includes(nextKind);
2,618✔
3309
    }
3310

3311
    private isAtEnd(): boolean {
3312
        return this.peek().kind === TokenKind.Eof;
65,795✔
3313
    }
3314

3315
    private peekNext(): Token {
3316
        if (this.isAtEnd()) {
7,565!
3317
            return this.peek();
×
3318
        }
3319
        return this.tokens[this.current + 1];
7,565✔
3320
    }
3321

3322
    private peek(): Token {
3323
        return this.tokens[this.current];
498,335✔
3324
    }
3325

3326
    private previous(): Token {
3327
        return this.tokens[this.current - 1];
33,838✔
3328
    }
3329

3330
    /**
3331
     * Sometimes we catch an error that is a diagnostic.
3332
     * If that's the case, we want to continue parsing.
3333
     * Otherwise, re-throw the error
3334
     *
3335
     * @param error error caught in a try/catch
3336
     */
3337
    private rethrowNonDiagnosticError(error) {
3338
        if (!error.isDiagnostic) {
10!
3339
            throw error;
×
3340
        }
3341
    }
3342

3343
    /**
3344
     * Get the token that is {offset} indexes away from {this.current}
3345
     * @param offset the number of index steps away from current index to fetch
3346
     * @param tokenKinds the desired token must match one of these
3347
     * @example
3348
     * getToken(-1); //returns the previous token.
3349
     * getToken(0);  //returns current token.
3350
     * getToken(1);  //returns next token
3351
     */
3352
    private getMatchingTokenAtOffset(offset: number, ...tokenKinds: TokenKind[]): Token {
3353
        const token = this.tokens[this.current + offset];
146✔
3354
        if (tokenKinds.includes(token.kind)) {
146✔
3355
            return token;
3✔
3356
        }
3357
    }
3358

3359
    private synchronize() {
3360
        this.advance(); // skip the erroneous token
112✔
3361

3362
        while (!this.isAtEnd()) {
112✔
3363
            if (this.ensureNewLineOrColon(true)) {
194✔
3364
                // end of statement reached
3365
                return;
75✔
3366
            }
3367

3368
            switch (this.peek().kind) { //eslint-disable-line @typescript-eslint/switch-exhaustiveness-check
119✔
3369
                case TokenKind.Namespace:
8!
3370
                case TokenKind.Class:
3371
                case TokenKind.Function:
3372
                case TokenKind.Sub:
3373
                case TokenKind.If:
3374
                case TokenKind.For:
3375
                case TokenKind.ForEach:
3376
                case TokenKind.While:
3377
                case TokenKind.Print:
3378
                case TokenKind.Return:
3379
                    // start parsing again from the next block starter or obvious
3380
                    // expression start
3381
                    return;
1✔
3382
            }
3383

3384
            this.advance();
118✔
3385
        }
3386
    }
3387

3388
    /**
3389
     * References are found during the initial parse.
3390
     * However, sometimes plugins can modify the AST, requiring a full walk to re-compute all references.
3391
     * This does that walk.
3392
     */
3393
    private findReferences() {
3394
        this._references = new References();
7✔
3395
        const excludedExpressions = new Set<Expression>();
7✔
3396

3397
        const visitCallExpression = (e: CallExpression | CallfuncExpression) => {
7✔
3398
            for (const p of e.args) {
14✔
3399
                this._references.expressions.add(p);
7✔
3400
            }
3401
            //add calls that were not excluded (from loop below)
3402
            if (!excludedExpressions.has(e)) {
14✔
3403
                this._references.expressions.add(e);
12✔
3404
            }
3405

3406
            //if this call is part of a longer expression that includes a call higher up, find that higher one and remove it
3407
            if (e.callee) {
14!
3408
                let node: Expression = e.callee;
14✔
3409
                while (node) {
14✔
3410
                    //the primary goal for this loop. If we found a parent call expression, remove it from `references`
3411
                    if (isCallExpression(node)) {
22✔
3412
                        this.references.expressions.delete(node);
2✔
3413
                        excludedExpressions.add(node);
2✔
3414
                        //stop here. even if there are multiple calls in the chain, each child will find and remove its closest parent, so that reduces excess walking.
3415
                        break;
2✔
3416

3417
                        //when we hit a variable expression, we're definitely at the leftmost expression so stop
3418
                    } else if (isVariableExpression(node)) {
20✔
3419
                        break;
12✔
3420
                        //if
3421

3422
                    } else if (isDottedGetExpression(node) || isIndexedGetExpression(node)) {
8!
3423
                        node = node.obj;
8✔
3424
                    } else {
3425
                        //some expression we don't understand. log it and quit the loop
3426
                        this.logger.info('Encountered unknown expression while calculating function expression chain', node);
×
3427
                        break;
×
3428
                    }
3429
                }
3430
            }
3431
        };
3432

3433
        this.ast.walk(createVisitor({
7✔
3434
            AssignmentStatement: s => {
3435
                this._references.assignmentStatements.push(s);
11✔
3436
                this.references.expressions.add(s.value);
11✔
3437
            },
3438
            ClassStatement: s => {
3439
                this._references.classStatements.push(s);
1✔
3440
            },
3441
            ClassFieldStatement: s => {
3442
                if (s.initialValue) {
1!
3443
                    this._references.expressions.add(s.initialValue);
1✔
3444
                }
3445
            },
3446
            NamespaceStatement: s => {
3447
                this._references.namespaceStatements.push(s);
×
3448
            },
3449
            FunctionStatement: s => {
3450
                this._references.functionStatements.push(s);
4✔
3451
            },
3452
            ImportStatement: s => {
3453
                this._references.importStatements.push(s);
1✔
3454
            },
3455
            LibraryStatement: s => {
3456
                this._references.libraryStatements.push(s);
×
3457
            },
3458
            FunctionExpression: (expression, parent) => {
3459
                if (!isMethodStatement(parent)) {
4!
3460
                    this._references.functionExpressions.push(expression);
4✔
3461
                }
3462
            },
3463
            NewExpression: e => {
3464
                this._references.newExpressions.push(e);
×
3465
                for (const p of e.call.args) {
×
3466
                    this._references.expressions.add(p);
×
3467
                }
3468
            },
3469
            ExpressionStatement: s => {
3470
                this._references.expressions.add(s.expression);
7✔
3471
            },
3472
            CallfuncExpression: e => {
3473
                visitCallExpression(e);
1✔
3474
            },
3475
            CallExpression: e => {
3476
                visitCallExpression(e);
13✔
3477
            },
3478
            AALiteralExpression: e => {
3479
                this.addPropertyHints(e);
8✔
3480
                this._references.expressions.add(e);
8✔
3481
                for (const member of e.elements) {
8✔
3482
                    if (isAAMemberExpression(member)) {
16!
3483
                        this._references.expressions.add(member.value);
16✔
3484
                    }
3485
                }
3486
            },
3487
            BinaryExpression: (e, parent) => {
3488
                //walk the chain of binary expressions and add each one to the list of expressions
3489
                const expressions: Expression[] = [e];
14✔
3490
                let expression: Expression;
3491
                while ((expression = expressions.pop())) {
14✔
3492
                    if (isBinaryExpression(expression)) {
64✔
3493
                        expressions.push(expression.left, expression.right);
25✔
3494
                    } else {
3495
                        this._references.expressions.add(expression);
39✔
3496
                    }
3497
                }
3498
            },
3499
            ArrayLiteralExpression: e => {
3500
                for (const element of e.elements) {
1✔
3501
                    //keep everything except comments
3502
                    if (!isCommentStatement(element)) {
1!
3503
                        this._references.expressions.add(element);
1✔
3504
                    }
3505
                }
3506
            },
3507
            DottedGetExpression: e => {
3508
                this.addPropertyHints(e.name);
23✔
3509
            },
3510
            DottedSetStatement: e => {
3511
                this.addPropertyHints(e.name);
4✔
3512
            },
3513
            EnumStatement: e => {
3514
                this._references.enumStatements.push(e);
×
3515
            },
3516
            ConstStatement: s => {
3517
                this._references.constStatements.push(s);
×
3518
            },
3519
            UnaryExpression: e => {
3520
                this._references.expressions.add(e);
×
3521
            },
3522
            IncrementStatement: e => {
3523
                this._references.expressions.add(e);
2✔
3524
            },
3525
            TypeStatement: (s) => {
NEW
3526
                this._references.typeStatements.push(s);
×
3527
            }
3528
        }), {
3529
            walkMode: WalkMode.visitAllRecursive
3530
        });
3531
    }
3532

3533
    public dispose() {
3534
    }
3535
}
3536

3537
export enum ParseMode {
1✔
3538
    BrightScript = 'BrightScript',
1✔
3539
    BrighterScript = 'BrighterScript'
1✔
3540
}
3541

3542
export interface ParseOptions {
3543
    /**
3544
     * The parse mode. When in 'BrightScript' mode, no BrighterScript syntax is allowed, and will emit diagnostics.
3545
     */
3546
    mode?: ParseMode;
3547
    /**
3548
     * A logger that should be used for logging. If omitted, a default logger is used
3549
     */
3550
    logger?: Logger;
3551
    /**
3552
     * Should locations be tracked. If false, the `range` property will be omitted
3553
     * @default true
3554
     */
3555
    trackLocations?: boolean;
3556
}
3557

3558
export class References {
1✔
3559
    private cache = new Cache();
2,243✔
3560
    public assignmentStatements = [] as AssignmentStatement[];
2,243✔
3561
    public classStatements = [] as ClassStatement[];
2,243✔
3562

3563
    public get classStatementLookup() {
3564
        if (!this._classStatementLookup) {
17✔
3565
            this._classStatementLookup = new Map();
15✔
3566
            for (const stmt of this.classStatements) {
15✔
3567
                this._classStatementLookup.set(stmt.getName(ParseMode.BrighterScript).toLowerCase(), stmt);
2✔
3568
            }
3569
        }
3570
        return this._classStatementLookup;
17✔
3571
    }
3572
    private _classStatementLookup: Map<string, ClassStatement>;
3573

3574
    public functionExpressions = [] as FunctionExpression[];
2,243✔
3575
    public functionStatements = [] as FunctionStatement[];
2,243✔
3576
    /**
3577
     * A map of function statements, indexed by fully-namespaced lower function name.
3578
     */
3579
    public get functionStatementLookup() {
3580
        if (!this._functionStatementLookup) {
17✔
3581
            this._functionStatementLookup = new Map();
15✔
3582
            for (const stmt of this.functionStatements) {
15✔
3583
                this._functionStatementLookup.set(stmt.getName(ParseMode.BrighterScript).toLowerCase(), stmt);
13✔
3584
            }
3585
        }
3586
        return this._functionStatementLookup;
17✔
3587
    }
3588
    private _functionStatementLookup: Map<string, FunctionStatement>;
3589

3590
    public interfaceStatements = [] as InterfaceStatement[];
2,243✔
3591

3592
    public get interfaceStatementLookup() {
3593
        if (!this._interfaceStatementLookup) {
×
3594
            this._interfaceStatementLookup = new Map();
×
3595
            for (const stmt of this.interfaceStatements) {
×
3596
                this._interfaceStatementLookup.set(stmt.fullName.toLowerCase(), stmt);
×
3597
            }
3598
        }
3599
        return this._interfaceStatementLookup;
×
3600
    }
3601
    private _interfaceStatementLookup: Map<string, InterfaceStatement>;
3602

3603
    public enumStatements = [] as EnumStatement[];
2,243✔
3604

3605
    public get enumStatementLookup() {
3606
        return this.cache.getOrAdd('enums', () => {
18✔
3607
            const result = new Map<string, EnumStatement>();
16✔
3608
            for (const stmt of this.enumStatements) {
16✔
3609
                result.set(stmt.fullName.toLowerCase(), stmt);
1✔
3610
            }
3611
            return result;
16✔
3612
        });
3613
    }
3614

3615
    public constStatements = [] as ConstStatement[];
2,243✔
3616

3617
    public get constStatementLookup() {
3618
        return this.cache.getOrAdd('consts', () => {
×
3619
            const result = new Map<string, ConstStatement>();
×
3620
            for (const stmt of this.constStatements) {
×
3621
                result.set(stmt.fullName.toLowerCase(), stmt);
×
3622
            }
3623
            return result;
×
3624
        });
3625
    }
3626

3627
    /**
3628
     * A collection of full expressions. This excludes intermediary expressions.
3629
     *
3630
     * Example 1:
3631
     * `a.b.c` is composed of `a` (variableExpression)  `.b` (DottedGetExpression) `.c` (DottedGetExpression)
3632
     * This will only contain the final `.c` DottedGetExpression because `.b` and `a` can both be derived by walking back from the `.c` DottedGetExpression.
3633
     *
3634
     * Example 2:
3635
     * `name.space.doSomething(a.b.c)` will result in 2 entries in this list. the `CallExpression` for `doSomething`, and the `.c` DottedGetExpression.
3636
     *
3637
     * Example 3:
3638
     * `value = SomeEnum.value > 2 or SomeEnum.otherValue < 10` will result in 4 entries. `SomeEnum.value`, `2`, `SomeEnum.otherValue`, `10`
3639
     */
3640
    public expressions = new Set<Expression>();
2,243✔
3641

3642
    public importStatements = [] as ImportStatement[];
2,243✔
3643
    public libraryStatements = [] as LibraryStatement[];
2,243✔
3644
    public namespaceStatements = [] as NamespaceStatement[];
2,243✔
3645
    public typeStatements = [] as TypeStatement[];
2,243✔
3646
    public newExpressions = [] as NewExpression[];
2,243✔
3647
    public propertyHints = {} as Record<string, string>;
2,243✔
3648
}
3649

3650
class CancelStatementError extends Error {
3651
    constructor() {
3652
        super('CancelStatement');
2✔
3653
    }
3654
}
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