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

rokucommunity / brighterscript / #15908

12 May 2026 07:29PM UTC coverage: 86.923% (+0.006%) from 86.917%
#15908

push

web-flow
Merge 39c1aae01 into ce68f5cb7

15646 of 19004 branches covered (82.33%)

Branch coverage included in aggregate %.

28 of 28 new or added lines in 8 files covered. (100.0%)

112 existing lines in 4 files now uncovered.

16359 of 17816 relevant lines covered (91.82%)

27323.13 hits per line

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

69.24
/src/Scope.ts
1
/* eslint-disable @typescript-eslint/dot-notation */
2
import * as path from 'path';
1✔
3
import chalk from 'chalk';
1✔
4
import type { Range } from 'vscode-languageserver-protocol';
5
import type { CallableContainer, FileReference, FileLink, Callable, ScopeValidationOptions } from './interfaces';
6
import type { Program } from './Program';
7
import type { NamespaceStatement, FunctionStatement, ClassStatement, EnumStatement, InterfaceStatement, EnumMemberStatement, ConstStatement, TypeStatement } from './parser/Statement';
8
import { ParseMode } from './parser/Parser';
1✔
9
import { util } from './util';
1✔
10
import { Cache } from './Cache';
1✔
11
import type { BrsFile } from './files/BrsFile';
12
import type { Identifier } from './lexer/Token';
13
import type { DependencyGraph, DependencyChangedEvent } from './DependencyGraph';
14
import { isBrsFile, isXmlFile, isEnumMemberStatement, isNamespaceStatement, isTypeStatement, isXmlScope } from './astUtils/reflection';
1✔
15
import { WalkMode } from './astUtils/visitors';
1✔
16
import { SymbolTable } from './SymbolTable';
1✔
17
import { SymbolTypeFlag } from './SymbolTypeFlag';
1✔
18
import type { BscFile } from './files/BscFile';
19
import { referenceTypeFactory } from './types/ReferenceType';
1✔
20
import { unionTypeFactory } from './types/UnionType';
1✔
21
import { AssociativeArrayType } from './types/AssociativeArrayType';
1✔
22
import type { Statement } from './parser/AstNode';
23
import { performance } from 'perf_hooks';
1✔
24
import { LogLevel } from './logging';
1✔
25
import { uninitializedTypeFactory } from './types/UninitializedType';
1✔
26
import { ScopeNamespaceLookup } from './ScopeNamespaceLookup';
1✔
27

28
/**
29
 * Assign some few factories to the SymbolTable to prevent cyclical imports. This file seems like the most intuitive place to do the linking
30
 * since Scope will be used by pretty much everything
31
 */
32
SymbolTable.referenceTypeFactory = referenceTypeFactory;
1✔
33
SymbolTable.unionTypeFactory = unionTypeFactory;
1✔
34
SymbolTable.uninitializedTypeFactory = uninitializedTypeFactory;
1✔
35
/**
36
 * A class to keep track of all declarations within a given scope (like source scope, component scope)
37
 */
38
export class Scope {
1✔
39
    constructor(
40
        public name: string,
5,653✔
41
        public program: Program,
5,653✔
42
        private _dependencyGraphKey?: string
5,653✔
43
    ) {
44
        this.isValidated = false;
5,653✔
45
        //used for improved logging performance
46
        this._debugLogComponentName = `Scope '${chalk.redBright(this.name)}'`;
5,653✔
47
    }
48

49
    /**
50
     * Indicates whether this scope needs to be validated.
51
     * Will be true when first constructed, or anytime one of its dependencies changes
52
     */
53
    public readonly isValidated: boolean;
54

55
    protected cache = new Cache();
5,653✔
56

57
    public get dependencyGraphKey() {
58
        return this._dependencyGraphKey;
10,984✔
59
    }
60

61
    /**
62
     * A dictionary of namespaces, indexed by the lower case full name of each namespace.
63
     * If a namespace is declared as "NameA.NameB.NameC", there will be 3 entries in this dictionary,
64
     * "namea", "namea.nameb", "namea.nameb.namec"
65
     */
66
    public get namespaceLookup() {
67
        let allFilesValidated = true;
1,779✔
68
        for (const file of this.getAllFiles()) {
1,779✔
69
            if (isBrsFile(file) && !file.hasTypedef) {
3,670✔
70
                allFilesValidated = allFilesValidated && file.isValidated;
2,881✔
71
                if (!allFilesValidated) {
2,881✔
72
                    break;
12✔
73
                }
74
            }
75
        }
76
        if (!allFilesValidated) {
1,779✔
77
            // This is not fit to cache
78
            // Since the files have not been validated, all namespace info might not have been available
79
            return this.buildNamespaceLookup();
12✔
80
        }
81
        return this.cache.getOrAdd('namespaceLookup', () => this.buildNamespaceLookup());
1,767✔
82
    }
83

84
    /**
85
     * A dictionary of namespaces, indexed by the lower case full name of each namespace.
86
     * If a namespace is declared as "NameA.NameB.NameC", there will be 3 entries in this dictionary,
87
     * "namea", "namea.nameb", "namea.nameb.namec"
88
     */
89
    public get namespaceNameSet() {
90
        return this.cache.getOrAdd('namespaceNameSet', () => {
1✔
91
            const lowerNamespaceNames = new Set<string>();
1✔
92

93
            this.enumerateBrsFiles((file) => {
1✔
94
                const fileNamespaceLookup = file.getNamespaceLookupObject();
3✔
95
                for (const [lowerNamespaceName, _] of fileNamespaceLookup) {
3✔
96
                    lowerNamespaceNames.add(lowerNamespaceName);
2✔
97
                }
98
            });
99
            return lowerNamespaceNames;
1✔
100
        });
101
    }
102

103

104
    /**
105
     * Get a NamespaceContainer by its name, looking for a fully qualified version first, then global version next if not found
106
     */
107
    public getNamespace(name: string, containingNamespace?: string) {
108
        const nameLower = name?.toLowerCase();
50!
109
        const lookup = this.namespaceLookup;
50✔
110

111
        let ns: NamespaceContainer;
112
        if (containingNamespace) {
50!
113
            ns = lookup.get(`${containingNamespace?.toLowerCase()}.${nameLower}`);
×
114
        }
115
        //if we couldn't find the namespace by its full namespaced name, look for a global version
116
        if (!ns) {
50!
117
            ns = lookup.get(nameLower);
50✔
118
        }
119
        return ns;
50✔
120
    }
121

122
    /**
123
     * Get a NamespaceContainer by its name, looking for a fully qualified version first, then global version next if not found
124
     */
125
    public getNamespacesWithRoot(rootName: string, containingNamespace?: string) {
126
        const nameLower = rootName?.toLowerCase();
×
127
        const lookup = this.namespaceLookup;
×
128
        const lookupKeys = [...lookup.keys()];
×
129
        let lookupName = nameLower;
×
130
        if (containingNamespace) {
×
131
            lookupName = `${containingNamespace?.toLowerCase()}.${nameLower}`;
×
132
        }
133
        const nsList = lookupKeys.filter(key => key === lookupName).map(key => lookup.get(key));
×
134
        return nsList;
×
135
    }
136

137

138
    /**
139
     * Get a NamespaceContainer by its name, looking for a fully qualified version first, then global version next if not found
140
     */
141
    public getFirstNamespaceWithRoot(rootName: string, containingNamespace?: string) {
142
        const nameLower = rootName?.toLowerCase();
×
143

144
        let lookupName = nameLower;
×
145
        if (containingNamespace) {
×
146
            lookupName = `${containingNamespace?.toLowerCase()}.${nameLower}`;
×
147
        }
148
        return this.namespaceLookup.get(lookupName);
×
149
    }
150

151
    /**
152
     * Get the class with the specified name.
153
     * @param className - The class name, including the namespace of the class if possible
154
     * @param containingNamespace - The namespace used to resolve relative class names. (i.e. the namespace around the current statement trying to find a class)
155
     */
156
    public getClass(className: string, containingNamespace?: string): ClassStatement {
157
        return this.getClassFileLink(className, containingNamespace)?.item;
9!
158
    }
159

160
    /**
161
     * Get the interface with the specified name.
162
     * @param ifaceName - The interface name, including the namespace of the interface if possible
163
     * @param containingNamespace - The namespace used to resolve relative interface names. (i.e. the namespace around the current statement trying to find a interface)
164
     */
165
    public getInterface(ifaceName: string, containingNamespace?: string): InterfaceStatement {
166
        return this.getInterfaceFileLink(ifaceName, containingNamespace)?.item;
×
167
    }
168

169
    /**
170
     * Get the enum with the specified name.
171
     * @param enumName - The enum name, including the namespace if possible
172
     * @param containingNamespace - The namespace used to resolve relative enum names. (i.e. the namespace around the current statement trying to find an enum)
173
     */
174
    public getEnum(enumName: string, containingNamespace?: string): EnumStatement {
175
        return this.getEnumFileLink(enumName, containingNamespace)?.item;
27✔
176
    }
177

178
    private useFileCachesForFileLinkLookups = false;
5,653✔
179

180
    private getFileLinkFromFileMap<T>(cachedMapName: string, itemName: string, containingNamespace?: string): FileLink<T> {
181
        let result: FileLink<T>;
182
        const fullNameLower = util.getFullyQualifiedClassName(itemName, containingNamespace)?.toLowerCase();
313!
183
        const itemNameLower = itemName?.toLowerCase();
313!
184
        if (fullNameLower) {
313✔
185
            this.enumerateBrsFilesWithBreak((file) => {
288✔
186
                let stmt = file['_cachedLookups'][cachedMapName].get(fullNameLower);
440✔
187
                if (stmt) {
440✔
188
                    result = { item: stmt, file: file };
4✔
189
                }
190
                return !!stmt;
440✔
191
            });
192
        }
193
        if (!result && itemNameLower && fullNameLower !== itemNameLower) {
313!
194
            this.enumerateBrsFilesWithBreak((file) => {
×
195
                let stmt = file['_cachedLookups'][cachedMapName].get(itemNameLower);
×
196
                if (stmt) {
×
197
                    result = { item: stmt, file: file };
×
198
                }
199
                return !!stmt;
×
200
            });
201
        }
202
        return result;
313✔
203
    }
204

205
    public getTypeStatement(typeName: string, containingNamespace?: string): Statement {
206
        return this.getTypeStatementFileLink(typeName, containingNamespace)?.item;
×
207
    }
208

209
    /**
210
     * Get a class and its containing file by the class name
211
     * @param className - The class name, including the namespace of the class if possible
212
     * @param containingNamespace - The namespace used to resolve relative class names. (i.e. the namespace around the current statement trying to find a class)
213
     */
214
    public getClassFileLink(className: string, containingNamespace?: string): FileLink<ClassStatement> {
215
        if (this.useFileCachesForFileLinkLookups) {
2,736✔
216
            return this.getFileLinkFromFileMap('classStatementMap', className, containingNamespace);
214✔
217
        }
218
        const lowerName = className?.toLowerCase();
2,522✔
219
        const fullNameLower = util.getFullyQualifiedClassName(lowerName, containingNamespace)?.toLowerCase();
2,522✔
220
        const classMap = this.getClassMap();
2,522✔
221

222
        let cls = classMap.get(fullNameLower);
2,522✔
223
        //if we couldn't find the class by its full namespaced name, look for a global class with that name
224
        if (!cls && lowerName && lowerName !== fullNameLower) {
2,522✔
225
            cls = classMap.get(lowerName);
6✔
226
        }
227
        return cls;
2,522✔
228
    }
229

230
    /**
231
     * Get an interface and its containing file by the interface name
232
     * @param ifaceName - The interface name, including the namespace of the interface if possible
233
     * @param containingNamespace - The namespace used to resolve relative interface names. (i.e. the namespace around the current statement trying to find a interface)
234
     */
235
    public getInterfaceFileLink(ifaceName: string, containingNamespace?: string): FileLink<InterfaceStatement> {
236
        if (this.useFileCachesForFileLinkLookups) {
8!
237
            return this.getFileLinkFromFileMap('interfaceStatementMap', ifaceName, containingNamespace);
×
238
        }
239
        const lowerName = ifaceName?.toLowerCase();
8✔
240
        const fullNameLower = util.getFullyQualifiedClassName(lowerName, containingNamespace)?.toLowerCase();
8✔
241
        const ifaceMap = this.getInterfaceMap();
8✔
242

243
        let iface = ifaceMap.get(fullNameLower);
8✔
244
        //if we couldn't find the iface by its full namespaced name, look for a global class with that name
245
        if (!iface && lowerName && lowerName !== fullNameLower) {
8!
246
            iface = ifaceMap.get(lowerName);
×
247
        }
248
        return iface;
8✔
249
    }
250

251
    /**
252
     * Get an Enum and its containing file by the Enum name
253
     * @param enumName - The Enum name, including the namespace of the enum if possible
254
     * @param containingNamespace - The namespace used to resolve relative enum names. (i.e. the namespace around the current statement trying to find a enum)
255
     */
256
    public getEnumFileLink(enumName: string, containingNamespace?: string): FileLink<EnumStatement> {
257
        if (this.useFileCachesForFileLinkLookups) {
1,708✔
258
            return this.getFileLinkFromFileMap('enumStatementMap', enumName, containingNamespace);
67✔
259
        }
260
        const lowerName = enumName?.toLowerCase();
1,641✔
261
        const fullNameLower = util.getFullyQualifiedClassName(lowerName, containingNamespace)?.toLowerCase();
1,641✔
262
        const enumMap = this.getEnumMap();
1,641✔
263

264
        let enumeration = enumMap.get(fullNameLower);
1,641✔
265
        //if we couldn't find the enum by its full namespaced name, look for a global enum with that name
266
        if (!enumeration && lowerName && lowerName !== fullNameLower) {
1,641✔
267
            enumeration = enumMap.get(lowerName);
95✔
268
        }
269
        return enumeration;
1,641✔
270
    }
271

272
    /**
273
     * Get an Enum and its containing file by the Enum name
274
     * @param enumMemberName - The Enum name, including the namespace of the enum if possible
275
     * @param containingNamespace - The namespace used to resolve relative enum names. (i.e. the namespace around the current statement trying to find a enum)
276
     */
277
    public getEnumMemberFileLink(enumMemberName: string, containingNamespace?: string): FileLink<EnumMemberStatement> {
278
        let lowerNameParts = enumMemberName?.toLowerCase()?.split('.');
29✔
279
        let memberName = lowerNameParts?.splice(lowerNameParts.length - 1, 1)?.[0];
29✔
280
        let lowerName = lowerNameParts?.join('.').toLowerCase();
29✔
281
        const enumMap = this.getEnumMap();
29✔
282

283
        let enumeration = enumMap.get(
29✔
284
            util.getFullyQualifiedClassName(lowerName, containingNamespace?.toLowerCase())
87✔
285
        );
286
        //if we couldn't find the enum by its full namespaced name, look for a global enum with that name
287
        if (!enumeration) {
29✔
288
            enumeration = enumMap.get(lowerName);
18✔
289
        }
290
        if (enumeration) {
29✔
291
            let member = enumeration.item.findChild<EnumMemberStatement>((child) => isEnumMemberStatement(child) && child.name?.toLowerCase() === memberName);
15!
292
            return member ? { item: member, file: enumeration.file } : undefined;
11!
293
        }
294
    }
295

296
    /**
297
     * Get a constant and its containing file by the constant name
298
     * @param constName - The constant name, including the namespace of the constant if possible
299
     * @param containingNamespace - The namespace used to resolve relative constant names. (i.e. the namespace around the current statement trying to find a constant)
300
     */
301
    public getConstFileLink(constName: string, containingNamespace?: string): FileLink<ConstStatement> {
302
        if (this.useFileCachesForFileLinkLookups) {
1,250✔
303
            return this.getFileLinkFromFileMap('constStatementMap', constName, containingNamespace);
32✔
304
        }
305
        const lowerName = constName?.toLowerCase();
1,218✔
306
        const fullNameLower = util.getFullyQualifiedClassName(lowerName, containingNamespace)?.toLowerCase();
1,218✔
307

308
        const constMap = this.getConstMap();
1,218✔
309

310
        let result = constMap.get(fullNameLower);
1,218✔
311
        //if we couldn't find the constant by its full namespaced name, look for a global constant with that name
312
        if (!result && lowerName !== fullNameLower) {
1,218✔
313
            result = constMap.get(lowerName);
133✔
314
        }
315
        return result;
1,218✔
316
    }
317

318
    public getAllFileLinks(name: string, containingNamespace?: string, includeNamespaces = false, includeNameShadowsOutsideNamespace = false): FileLink<Statement>[] {
×
319
        let links: FileLink<Statement>[] = [];
×
320

321
        links.push(this.getClassFileLink(name, containingNamespace),
×
322
            this.getInterfaceFileLink(name, containingNamespace),
323
            this.getConstFileLink(name, containingNamespace),
324
            this.getEnumFileLink(name, containingNamespace));
325
        if (includeNameShadowsOutsideNamespace && containingNamespace) {
×
326
            links.push(this.getClassFileLink(name),
×
327
                this.getInterfaceFileLink(name),
328
                this.getConstFileLink(name),
329
                this.getEnumFileLink(name));
330
        }
331
        if (includeNamespaces) {
×
332
            const nameSpaceContainer = this.getFirstNamespaceWithRoot(name, containingNamespace);
×
333
            if (nameSpaceContainer) {
×
334
                links.push({ item: nameSpaceContainer.namespaceStatements?.[0], file: nameSpaceContainer?.file as BrsFile });
×
335
            }
336
        }
337
        const fullNameLower = (containingNamespace ? `${containingNamespace}.${name}` : name).toLowerCase();
×
338
        const callable = this.getCallableByName(name);
×
339
        if (callable) {
×
340
            if ((!callable.hasNamespace && includeNameShadowsOutsideNamespace) || callable.getName(ParseMode.BrighterScript).toLowerCase() === fullNameLower) {
×
341
                // this callable has no namespace, or has same namespace
342
                links.push({ item: callable.functionStatement, file: callable.file as BrsFile });
×
343
            }
344
        }
345
        // remove empty links
346
        return links.filter(link => link);
×
347
    }
348

349
    /**
350
     * Get an TypeStatement and its containing file by the type name
351
     * @param typeName - The TypeStatement name, including the namespace of the type if possible
352
     * @param containingNamespace - The namespace used to resolve relative type names. (i.e. the namespace around the current statement trying to find a type)
353
     */
354
    public getTypeStatementFileLink(typeName: string, containingNamespace?: string): FileLink<TypeStatement> {
355
        const lowerName = typeName?.toLowerCase();
×
356
        const typeStatementMap = this.getTypeStatementMap();
×
357

358
        let typeStatement = typeStatementMap.get(
×
359
            util.getFullyQualifiedClassName(lowerName, containingNamespace?.toLowerCase())
×
360
        );
361
        //if we couldn't find the enum by its full namespaced name, look for a global enum with that name
362
        if (!typeStatement) {
×
363
            typeStatement = typeStatementMap.get(lowerName);
×
364
        }
365
        return typeStatement;
×
366
    }
367

368
    /**
369
     * Get a map of all enums by their member name.
370
     * The keys are lower-case fully-qualified paths to the enum and its member. For example:
371
     * namespace.enum.value
372
     */
373
    public getEnumMemberMap() {
374
        return this.cache.getOrAdd('enumMemberMap', () => {
×
375
            const result = new Map<string, EnumMemberStatement>();
×
376
            for (const [key, eenum] of this.getEnumMap()) {
×
377
                for (const member of eenum.item.getMembers()) {
×
378
                    result.set(`${key}.${member.name.toLowerCase()}`, member);
×
379
                }
380
            }
381
            return result;
×
382
        });
383
    }
384

385

386
    /**
387
     * Tests if a class exists with the specified name
388
     * @param className - the all-lower-case namespace-included class name
389
     * @param namespaceName - The namespace used to resolve relative class names. (i.e. the namespace around the current statement trying to find a class)
390
     */
391
    public hasClass(className: string, namespaceName?: string): boolean {
392
        return !!this.getClass(className, namespaceName);
×
393
    }
394

395
    /**
396
     * Tests if an interface exists with the specified name
397
     * @param ifaceName - the all-lower-case namespace-included interface name
398
     * @param namespaceName - the current namespace name
399
     */
400
    public hasInterface(ifaceName: string, namespaceName?: string): boolean {
401
        return !!this.getInterface(ifaceName, namespaceName);
×
402
    }
403

404
    /**
405
     * Tests if an enum exists with the specified name
406
     * @param enumName - the all-lower-case namespace-included enum name
407
     * @param namespaceName - the current namespace name
408
     */
409
    public hasEnum(enumName: string, namespaceName?: string): boolean {
410
        return !!this.getEnum(enumName, namespaceName);
×
411
    }
412

413
    public hasTypeStatementType(typeName: string, namespaceName?: string): boolean {
414
        return !!this.getTypeStatement(typeName, namespaceName);
×
415
    }
416

417
    /**
418
     * A dictionary of all classes in this scope. This includes namespaced classes always with their full name.
419
     * The key is stored in lower case
420
     */
421
    public getClassMap(): Map<string, FileLink<ClassStatement>> {
422
        return this.cache.getOrAdd('classMap', () => {
2,522✔
423
            const map = new Map<string, FileLink<ClassStatement>>();
1,115✔
424
            this.enumerateBrsFiles((file) => {
1,115✔
425
                if (isBrsFile(file)) {
1,323!
426
                    for (let cls of file['_cachedLookups'].classStatements) {
1,323✔
427
                        const className = cls.getName(ParseMode.BrighterScript);
258✔
428
                        //only track classes with a defined name (i.e. exclude nameless malformed classes)
429
                        if (className) {
258!
430
                            map.set(className.toLowerCase(), { item: cls, file: file });
258✔
431
                        }
432
                    }
433
                }
434
            });
435
            return map;
1,115✔
436
        });
437
    }
438

439
    /**
440
     * A dictionary of all Interfaces in this scope. This includes namespaced Interfaces always with their full name.
441
     * The key is stored in lower case
442
     */
443
    public getInterfaceMap(): Map<string, FileLink<InterfaceStatement>> {
444
        return this.cache.getOrAdd('interfaceMap', () => {
8✔
445
            const map = new Map<string, FileLink<InterfaceStatement>>();
8✔
446
            this.enumerateBrsFiles((file) => {
8✔
447
                if (isBrsFile(file)) {
11!
448
                    for (let iface of file['_cachedLookups'].interfaceStatements) {
11✔
449
                        const ifaceName = iface.getName(ParseMode.BrighterScript);
2✔
450
                        //only track classes with a defined name (i.e. exclude nameless malformed classes)
451
                        if (ifaceName) {
2!
452
                            map.set(ifaceName.toLowerCase(), { item: iface, file: file });
2✔
453
                        }
454
                    }
455
                }
456
            });
457
            return map;
8✔
458
        });
459
    }
460

461
    /**
462
     * A dictionary of all enums in this scope. This includes namespaced enums always with their full name.
463
     * The key is stored in lower case
464
     */
465
    public getEnumMap(): Map<string, FileLink<EnumStatement>> {
466
        return this.cache.getOrAdd('enumMap', () => {
1,724✔
467
            const map = new Map<string, FileLink<EnumStatement>>();
248✔
468
            this.enumerateBrsFiles((file) => {
248✔
469
                for (let enumStmt of file['_cachedLookups'].enumStatements) {
291✔
470
                    //only track enums with a defined name (i.e. exclude nameless malformed enums)
471
                    if (enumStmt.fullName) {
77!
472
                        map.set(enumStmt.fullName.toLowerCase(), { item: enumStmt, file: file });
77✔
473
                    }
474
                }
475
            });
476
            return map;
248✔
477
        });
478
    }
479

480
    /**
481
     * A dictionary of all constants in this scope. This includes namespaced constants always with their full name.
482
     * The key is stored in lower case
483
     */
484
    public getConstMap(): Map<string, FileLink<ConstStatement>> {
485
        return this.cache.getOrAdd('constMap', () => {
3,672✔
486
            const map = new Map<string, FileLink<ConstStatement>>();
2,426✔
487
            this.enumerateBrsFiles((file) => {
2,426✔
488
                for (let stmt of file['_cachedLookups'].constStatements) {
2,915✔
489
                    //only track enums with a defined name (i.e. exclude nameless malformed enums)
490
                    if (stmt.fullName) {
409!
491
                        map.set(stmt.fullName.toLowerCase(), { item: stmt, file: file });
409✔
492
                    }
493
                }
494
            });
495
            return map;
2,426✔
496
        });
497
    }
498

499
    /**
500
     * A dictionary of all TypeStatements in this scope. This includes namespaced types always with their full name.
501
     * The key is stored in lower case
502
     */
503
    public getTypeStatementMap(): Map<string, FileLink<TypeStatement>> {
504
        return this.cache.getOrAdd('typeStatementMap', () => {
×
505
            const map = new Map<string, FileLink<TypeStatement>>();
×
506
            this.enumerateBrsFiles((file) => {
×
507
                file.ast.walk((node) => {
×
508
                    if (isTypeStatement(node)) {
×
509
                        const namespaceName = node.findAncestor<NamespaceStatement>(isNamespaceStatement)?.getName(ParseMode.BrighterScript);
×
510
                        const typeName = node.tokens.name?.text;
×
511
                        if (typeName) {
×
512
                            const fullName = (namespaceName ? `${namespaceName}.${typeName}` : typeName).toLowerCase();
×
513
                            map.set(fullName, { item: node, file: file });
×
514
                        }
515
                    }
516
                }, { walkMode: WalkMode.visitAllRecursive });
517
            });
518
            return map;
×
519
        });
520
    }
521

522
    protected onDependenciesChanged(event: DependencyChangedEvent) {
523
        this.logDebug('invalidated because dependency graph said [', event.sourceKey, '] changed');
3,234✔
524
        this.invalidate();
3,234✔
525
    }
526

527
    /**
528
     * Clean up all event handles
529
     */
530
    public dispose() {
531
        this.unlinkSymbolTable();
7,233✔
532
        this.cache.clear();
7,233✔
533
        this.unsubscribeFromDependencyGraph?.();
7,233!
534
    }
535

536
    /**
537
     * Does this scope know about the given namespace name?
538
     * @param namespaceName - the name of the namespace (i.e. "NameA", or "NameA.NameB", etc...)
539
     */
540
    public isKnownNamespace(namespaceName: string) {
UNCOV
541
        let namespaceNameLower = namespaceName.toLowerCase();
×
UNCOV
542
        this.enumerateBrsFiles((file) => {
×
543
            for (let namespace of file['_cachedLookups'].namespaceStatements) {
×
544
                let loopNamespaceNameLower = namespace.name.toLowerCase();
×
545
                if (loopNamespaceNameLower === namespaceNameLower || loopNamespaceNameLower.startsWith(namespaceNameLower + '.')) {
×
546
                    return true;
×
547
                }
548
            }
549
        });
UNCOV
550
        return false;
×
551
    }
552

553
    /**
554
     * Get the parent scope for this scope (for source scope this will always be the globalScope).
555
     * XmlScope overrides this to return the parent xml scope if available.
556
     * For globalScope this will return null.
557
     */
558
    public getParentScope(): Scope | null {
559
        let scope: Scope | undefined;
560
        //use the global scope if we didn't find a sope and this is not the global scope
561
        if (this.program.globalScope !== this) {
447,753✔
562
            scope = this.program.globalScope;
12,269✔
563
        }
564
        if (scope) {
447,753✔
565
            return scope;
12,269✔
566
        } else {
567
            //passing null to the cache allows it to skip the factory function in the future
568
            return null;
435,484✔
569
        }
570
    }
571

572
    private dependencyGraph: DependencyGraph;
573
    /**
574
     * An unsubscribe function for the dependencyGraph subscription
575
     */
576
    private unsubscribeFromDependencyGraph: () => void;
577

578
    public attachDependencyGraph(dependencyGraph: DependencyGraph) {
579
        this.dependencyGraph = dependencyGraph;
5,187✔
580
        if (this.unsubscribeFromDependencyGraph) {
5,187✔
581
            this.unsubscribeFromDependencyGraph();
2✔
582
        }
583

584
        //anytime a dependency for this scope changes, we need to be revalidated
585
        this.unsubscribeFromDependencyGraph = this.dependencyGraph.onchange(this.dependencyGraphKey, this.onDependenciesChanged.bind(this));
5,187✔
586

587
        //invalidate immediately since this is a new scope
588
        this.invalidate();
5,187✔
589
    }
590

591
    /**
592
     * Get the file from this scope with the given path.
593
     * @param filePath can be a srcPath or destPath
594
     * @param normalizePath should this function repair and standardize the path? Passing false should have a performance boost if you can guarantee your path is already sanitized
595
     */
596
    public getFile<TFile extends BscFile>(filePath: string, normalizePath = true) {
11✔
597
        if (typeof filePath !== 'string') {
11!
UNCOV
598
            return undefined;
×
599
        }
600

601
        const key: keyof Pick<BscFile, 'srcPath' | 'destPath'> = path.isAbsolute(filePath) ? 'srcPath' : 'destPath';
11✔
602
        let map = this.cache.getOrAdd('fileMaps-srcPath', () => {
11✔
603
            const result = new Map<string, BscFile>();
7✔
604
            for (const file of this.getAllFiles()) {
7✔
605
                result.set(file[key].toLowerCase(), file);
11✔
606
            }
607
            return result;
7✔
608
        });
609
        return map.get(
11✔
610
            (normalizePath ? util.standardizePath(filePath) : filePath).toLowerCase()
11!
611
        ) as TFile;
612
    }
613

614
    /**
615
     * Get the list of files referenced by this scope that are actually loaded in the program.
616
     * Excludes files from ancestor scopes
617
     */
618
    public getOwnFiles() {
619
        //source scope only inherits files from global, so just return all files. This function mostly exists to assist XmlScope
620
        return this.getAllFiles();
33,195✔
621
    }
622

623
    /**
624
     * Get the list of files referenced by this scope that are actually loaded in the program.
625
     * Includes files from this scope and all ancestor scopes
626
     */
627
    public getAllFiles(): BscFile[] {
628
        return this.cache.getOrAdd('getAllFiles', () => {
44,098✔
629
            let result = [] as BscFile[];
7,522✔
630
            let dependencies = this.dependencyGraph.getAllDependencies(this.dependencyGraphKey);
7,522✔
631
            for (let dependency of dependencies) {
7,522✔
632
                //load components by their name
633
                if (dependency.startsWith('component:')) {
9,912✔
634
                    let comp = this.program.getComponent(dependency.replace(/^component:/, ''));
1,214✔
635
                    if (comp) {
1,214✔
636
                        result.push(comp.file);
69✔
637
                    }
638
                } else {
639
                    let file = this.program.getFile(dependency);
8,698✔
640
                    if (file) {
8,698✔
641
                        result.push(file);
6,418✔
642
                    }
643
                }
644
            }
645
            return result;
7,522✔
646
        });
647
    }
648

649
    /**
650
     * Gets a list of all files in this scope, but not imported files, and not from ancestor scopes
651
     */
652
    public getImmediateFiles(): BscFile[] {
UNCOV
653
        return this.cache.getOrAdd('getImmediateFiles', () => {
×
UNCOV
654
            let result = [] as BscFile[];
×
655
            if (isXmlScope(this)) {
×
656
                result.push(this.xmlFile);
×
657
            }
658
            let dependencies = this.dependencyGraph.getImmediateDependencies(this.dependencyGraphKey);
×
UNCOV
659
            for (let dependency of dependencies) {
×
660
                //load components by their name
661
                if (dependency.startsWith('component:')) {
×
UNCOV
662
                    let comp = this.program.getComponent(dependency.replace(/^component:/, ''));
×
663
                    if (comp) {
×
664
                        result.push(...comp.scope.getImmediateFiles());
×
665
                        result.push(comp.file);
×
666
                    }
667
                } else {
UNCOV
668
                    let file = this.program.getFile(dependency);
×
UNCOV
669
                    if (file) {
×
670
                        result.push(file);
×
671
                    }
672
                }
673
            }
UNCOV
674
            this.logDebug('getImmediateFiles', () => result.map(x => x.destPath));
×
UNCOV
675
            return result;
×
676
        });
677
    }
678

679
    /**
680
     * Get the list of callables available in this scope (either declared in this scope or in a parent scope)
681
     */
682
    public getAllCallables(): CallableContainer[] {
683
        //get callables from parent scopes
684
        let parentScope = this.getParentScope();
4,878✔
685
        if (parentScope) {
4,878✔
686
            return [...this.getOwnCallables(), ...parentScope.getAllCallables()];
2,457✔
687
        } else {
688
            return [...this.getOwnCallables()];
2,421✔
689
        }
690
    }
691

692
    /**
693
     * Get the callable with the specified name.
694
     * If there are overridden callables with the same name, the closest callable to this scope is returned
695
     */
696
    public getCallableByName(name: string) {
UNCOV
697
        return this.getCallableMap().get(
×
698
            name.toLowerCase()
699
        );
700
    }
701

702
    public getCallableMap() {
UNCOV
703
        return this.cache.getOrAdd('callableMap', () => {
×
UNCOV
704
            const result = new Map<string, Callable>();
×
705
            for (let callable of this.getAllCallables()) {
×
706
                const callableName = callable.callable.getName(ParseMode.BrighterScript)?.toLowerCase();
×
707
                result.set(callableName, callable.callable);
×
708
                result.set(
×
709
                    // Split by `.` and check the last term to consider namespaces.
710
                    callableName.split('.').pop()?.toLowerCase(),
×
711
                    callable.callable
712
                );
713
            }
UNCOV
714
            return result;
×
715
        });
716
    }
717

718
    public getCallableContainerMap() {
719
        return this.cache.getOrAdd('callableContainerMap', () => {
4,943✔
720
            let callables = this.getAllCallables();
2,404✔
721

722
            //get a list of all callables, indexed by their lower case names
723
            return util.getCallableContainersByLowerName(callables);
2,404✔
724
        });
725
    }
726

727
    /**
728
     * Iterate over Brs files not shadowed by typedefs
729
     */
730
    public enumerateBrsFiles(callback: (file: BrsFile) => void) {
731
        const files = this.getAllFiles();
24,801✔
732
        for (const file of files) {
24,801✔
733
            //only brs files without a typedef
734
            if (isBrsFile(file) && !file.hasTypedef) {
35,600✔
735
                callback(file);
29,029✔
736
            }
737
        }
738
    }
739

740
    /**
741
     * Iterate over Brs files not shadowed by typedefs
742
     */
743
    public enumerateBrsFilesWithBreak(callback: (file: BrsFile) => boolean) {
744
        const files = this.getAllFiles();
288✔
745
        for (const file of files) {
288✔
746
            //only brs files without a typedef
747
            if (isBrsFile(file) && !file.hasTypedef) {
573✔
748
                if (callback(file)) {
440✔
749
                    break;
4✔
750
                }
751
            }
752
        }
753
    }
754

755
    /**
756
     * Call a function for each file directly included in this scope (excluding files found only in parent scopes).
757
     */
758
    public enumerateOwnFiles(callback: (file: BscFile) => void) {
759
        const files = this.getOwnFiles();
9,689✔
760
        for (const file of files) {
9,689✔
761
            //either XML components or files without a typedef
762
            if (isXmlFile(file) || (isBrsFile(file) && !file.hasTypedef)) {
13,042✔
763
                callback(file);
13,019✔
764
            }
765
        }
766
    }
767

768
    /**
769
     * Get the list of callables explicitly defined in files in this scope.
770
     * This excludes ancestor callables
771
     */
772
    public getOwnCallables(): CallableContainer[] {
773
        let result = [] as CallableContainer[];
4,881✔
774
        //get callables from own files
775
        this.enumerateOwnFiles((file) => {
4,881✔
776
            if (isBrsFile(file)) {
6,019✔
777
                for (let callable of file?.callables ?? []) {
5,347!
778
                    result.push({
189,080✔
779
                        callable: callable,
780
                        scope: this
781
                    });
782
                }
783
            }
784
        });
785
        return result;
4,881✔
786
    }
787

788
    /**
789
     * Build the namespace lookup for this scope.
790
     *
791
     * The lookup is now backed by `ScopeNamespaceLookup`, which queries the
792
     * Program-level `getNamespaceContributors` map lazily on each
793
     * `.get(name)` call. Per-file contributions are pre-built by
794
     * `BrsFile.getNamespaceContributions` and shared across every scope that
795
     * pulls in the file, so single-contribution containers reuse the file's
796
     * pre-built statement collections and symbolTable instead of allocating
797
     * per-scope copies.
798
     *
799
     * The return type is `Map<string, NamespaceContainer>` for backward
800
     * compatibility with plugins that consume the public API.
801
     */
802
    public buildNamespaceLookup(): Map<string, NamespaceContainer> {
803
        return new ScopeNamespaceLookup(this);
1,070✔
804
    }
805

806
    public getAllNamespaceStatements() {
UNCOV
807
        let result = [] as NamespaceStatement[];
×
UNCOV
808
        this.enumerateBrsFiles((file) => {
×
809
            result.push(...file['_cachedLookups'].namespaceStatements);
×
810
        });
811
        return result;
×
812
    }
813

814
    protected logDebug(...args: any[]) {
815
        this.program.logger.debug(this._debugLogComponentName, ...args);
5,446✔
816
    }
817
    private _debugLogComponentName: string;
818

819

820
    public validationMetrics = {
5,653✔
821
        linkTime: 0,
822
        validationTime: 0
823
    };
824

825
    public shouldValidate(validationOptions: ScopeValidationOptions = { force: false }) {
×
826
        //if this scope is already validated, no need to revalidate
827
        if (this.isValidated === true && !validationOptions.force) {
9,524✔
828
            this.logDebug('validate(): already validated');
2,160✔
829
            return false;
2,160✔
830
        }
831

832
        if (!validationOptions.initialValidation && validationOptions.filesToBeValidatedInScopeContext?.size === 0) {
7,364✔
833
            // There was no need to validate this scope.
834
            (this as any).isValidated = true;
22✔
835
            return false;
22✔
836
        }
837
        return true;
7,342✔
838
    }
839

840

841
    public validate(validationOptions: ScopeValidationOptions = { force: false }) {
2,507✔
842
        this.validationMetrics = {
4,951✔
843
            linkTime: 0,
844
            validationTime: 0
845
        };
846

847
        //if this scope is already validated, no need to revalidate
848
        if (!this.shouldValidate(validationOptions)) {
4,951✔
849
            this.logDebug('validate(): already validated');
39✔
850
            // There was no need to validate this scope.
851
            (this as any).isValidated = true;
39✔
852
            return false;
39✔
853
        }
854

855
        this.useFileCachesForFileLinkLookups = !validationOptions.initialValidation;
4,912✔
856

857
        this.program.logger.time(LogLevel.debug, [this._debugLogComponentName, 'validate()'], () => {
4,912✔
858

859
            let parentScope = this.getParentScope();
4,912✔
860

861
            //validate our parent before we validate ourself
862
            if (parentScope && parentScope.isValidated === false) {
4,912✔
863
                this.logDebug('validate(): validating parent first');
13✔
864
                parentScope.validate(validationOptions);
13✔
865
            }
866

867
            //Since statements from files are shared across multiple scopes, we need to link those statements to the current scope
868

869
            let t0 = performance.now();
4,912✔
870
            this.linkSymbolTable();
4,912✔
871
            this.validationMetrics.linkTime = performance.now() - t0;
4,912✔
872
            const scopeValidateEvent = {
4,912✔
873
                program: this.program,
874
                scope: this,
875
                changedFiles: validationOptions?.changedFiles ?? [],
29,472!
876
                changedSymbols: validationOptions?.changedSymbols
14,736!
877
            };
878
            t0 = performance.now();
4,912✔
879
            this.program.plugins.emit('validateScope', scopeValidateEvent);
4,912✔
880
            this.validationMetrics.validationTime = performance.now() - t0;
4,912✔
881
            //unlink all symbol tables from this scope (so they don't accidentally stick around)
882
            this.unlinkSymbolTable();
4,912✔
883
            (this as any).isValidated = true;
4,912✔
884
        });
885
        for (let file of this.getAllFiles()) {
4,912✔
886
            validationOptions.filesToBeValidatedInScopeContext?.delete(file);
6,063✔
887
        }
888
        return true;
4,912✔
889
    }
890

891
    /**
892
     * Mark this scope as invalid, which means its `validate()` function needs to be called again before use.
893
     */
894
    public invalidate() {
895
        (this as any).isValidated = false;
11,295✔
896
        //clear out various lookups (they'll get regenerated on demand the next time they're requested)
897
        this.cache.clear();
11,295✔
898
    }
899

900
    public get symbolTable(): SymbolTable {
901
        return this.cache.getOrAdd('symbolTable', () => {
2,389,293✔
902
            const result = new SymbolTable(`Scope: '${this.name}'`, () => this.getParentScope()?.symbolTable);
441,912✔
903
            result.addSymbol('m', undefined, new AssociativeArrayType(), SymbolTypeFlag.runtime);
5,826✔
904
            for (let file of this.getOwnFiles()) {
5,826✔
905
                if (isBrsFile(file)) {
5,073✔
906
                    result.mergeSymbolTable(file.parser?.symbolTable);
4,034!
907
                }
908
            }
909
            return result;
5,826✔
910
        });
911
    }
912

913
    /**
914
     * A list of functions that will be called whenever `unlinkSymbolTable` is called
915
     */
916
    private linkSymbolTableDisposables = [];
5,653✔
917

918
    private symbolsAddedDuringLinking: { symbolTable: SymbolTable; name: string; flags: number }[] = [];
5,653✔
919

920
    public get allNamespaceTypeTable() {
921
        return this._allNamespaceTypeTable;
30✔
922
    }
923

924
    private _allNamespaceTypeTable: SymbolTable;
925

926
    /**
927
     * Builds the current symbol table for the scope, by merging the tables for all the files in this scope.
928
     * Also links all file symbols tables to this new table
929
     * This will only rebuilt if the symbol table has not been built before
930
     *
931
     *  Tree of symbol tables:
932
     *  ```
933
     *  Global Scope Symbol Table
934
     *      -  Source Scope Symbol Table :: Aggregate Namespaces Symbol Table (Siblings)
935
     *          - File 1 Symbol Table
936
     *          - File 2 Symbol Table
937
     *      -  Component A Scope Symbol Table :: Aggregate Namespaces Symbol Table (Siblings)
938
     *          - File 1 Symbol Table
939
     *          - File 2 Symbol Table
940
     *      -  Component B Scope Symbol Table :: Aggregate Namespaces Symbol Table (Siblings)
941
     *          - File 1 Symbol Table
942
     *          - File 2 Symbol Table
943
     * ```
944
     */
945
    public linkSymbolTable() {
946
        SymbolTable.cacheVerifier.generateToken();
7,950✔
947
        this._allNamespaceTypeTable = new SymbolTable(`Scope NamespaceTypes ${this.name}`);
7,950✔
948
        for (const file of this.getAllFiles()) {
7,950✔
949
            if (isBrsFile(file)) {
10,050✔
950
                this.linkSymbolTableDisposables.push(
8,743✔
951
                    file.parser.symbolTable.pushParentProvider(() => this.symbolTable)
12,830✔
952
                );
953

954
                //link each NamespaceStatement's SymbolTable with the aggregate NamespaceLookup SymbolTable.
955
                //Leaf containers always have symbolTable populated, so the lookup below is safe; the null
956
                //guard tolerates edge cases like a namespace that failed to register.
957
                for (const namespace of file['_cachedLookups'].namespaceStatements) {
8,743✔
958
                    const namespaceNameLower = namespace.getName(ParseMode.BrighterScript).toLowerCase();
1,706✔
959
                    const aggregate = this.namespaceLookup.get(namespaceNameLower)?.symbolTable;
1,706✔
960
                    if (aggregate) {
1,706✔
961
                        this.linkSymbolTableDisposables.push(
1,702✔
962
                            namespace.getSymbolTable().addSibling(aggregate)
963
                        );
964
                    }
965
                }
966
            }
967
        }
968
        this.enumerateBrsFiles((file) => {
7,950✔
969
            const namespaceTypes = file.getNamespaceSymbolTable();
8,727✔
970

971
            this.linkSymbolTableDisposables.push(
8,727✔
972
                ...this._allNamespaceTypeTable.mergeNamespaceSymbolTables(namespaceTypes)
973
            );
974
        });
975
        this.linkSymbolTableDisposables.push(
7,950✔
976
            this.symbolTable.addSibling(this._allNamespaceTypeTable)
977
        );
978
    }
979

980
    public unlinkSymbolTable() {
981
        for (const symbolToRemove of this.symbolsAddedDuringLinking) {
15,135✔
UNCOV
982
            this.symbolTable.removeSymbol(symbolToRemove.name);
×
983
        }
984
        this.symbolsAddedDuringLinking = [];
15,135✔
985
        for (const dispose of this.linkSymbolTableDisposables) {
15,135✔
986
            dispose();
19,933✔
987
        }
988
        this.linkSymbolTableDisposables = [];
15,135✔
989

990
        this.cache.delete('namespaceLookup');
15,135✔
991
    }
992

993
    /**
994
     * Get the list of all script imports for this scope
995
     */
996
    public getOwnScriptImports() {
997
        let result = [] as FileReference[];
2,404✔
998
        this.enumerateOwnFiles((file) => {
2,404✔
999
            if (isBrsFile(file)) {
3,500✔
1000
                result.push(...file.ownScriptImports);
2,873✔
1001
            } else if (isXmlFile(file)) {
627!
1002
                result.push(...file.scriptTagImports);
627✔
1003
            }
1004
        });
1005
        return result;
2,404✔
1006
    }
1007

1008

1009
    /**
1010
     * Find the file with the specified relative path
1011
     */
1012
    public getFileByRelativePath(relativePath: string) {
1013
        if (!relativePath) {
740!
UNCOV
1014
            return;
×
1015
        }
1016
        let files = this.getAllFiles();
740✔
1017
        for (let file of files) {
740✔
1018
            if (file.destPath.toLowerCase() === relativePath.toLowerCase()) {
1,190✔
1019
                return file;
718✔
1020
            }
1021
        }
1022
    }
1023

1024
    /**
1025
     * Determine if this file is included in this scope (excluding parent scopes)
1026
     */
1027
    public hasFile(file: BscFile) {
1028
        let files = this.getOwnFiles();
63,910✔
1029
        let hasFile = files.includes(file);
63,910✔
1030
        return hasFile;
63,910✔
1031
    }
1032

1033
    /**
1034
     * @param className - The name of the class (including namespace if possible)
1035
     * @param callsiteNamespace - the name of the namespace where the call site resides (this is NOT the known namespace of the class).
1036
     *                            This is used to help resolve non-namespaced class names that reside in the same namespac as the call site.
1037
     */
1038
    public getClassHierarchy(className: string, callsiteNamespace?: string) {
1039
        let items = [] as FileLink<ClassStatement>[];
16✔
1040
        let link = this.getClassFileLink(className, callsiteNamespace);
16✔
1041
        while (link) {
16✔
1042
            items.push(link);
17✔
1043
            link = this.getClassFileLink(link.item.parentClassName?.getName()?.toLowerCase(), callsiteNamespace);
17✔
1044
        }
1045
        return items;
16✔
1046
    }
1047
}
1048

1049
/**
1050
 * A single file's contribution to a namespace name. Cached on `BrsFile` and shared
1051
 * across every scope that pulls in this file. The fields here are intrinsic to the
1052
 * file's parsed AST, so they never need to be rebuilt per scope.
1053
 *
1054
 * Pure-intermediate contributions (a namespace name part that this file only references
1055
 * as a dotted prefix, e.g. `A` from `namespace A.B`) carry only the structural fields
1056
 * (file, fullName, lastPartName, nameRange). Leaf contributions populate the relevant
1057
 * statement collections and the per-file `symbolTable`.
1058
 *
1059
 * The `symbolTable` here has no parent provider; sibling resolution in `linkSymbolTable`
1060
 * does not walk into a sibling's parent, so the parent-provider plumbing was dead code.
1061
 */
1062
export interface NamespaceFileContribution {
1063
    file: BrsFile;
1064
    fullName: string;
1065
    lastPartName: string;
1066
    nameRange: Range;
1067
    namespaceStatements?: NamespaceStatement[];
1068
    statements?: Statement[];
1069
    classStatements?: Record<string, ClassStatement>;
1070
    functionStatements?: Record<string, FunctionStatement>;
1071
    enumStatements?: Map<string, EnumStatement>;
1072
    constStatements?: Map<string, ConstStatement>;
1073
    symbolTable?: SymbolTable;
1074
}
1075

1076
/**
1077
 * A node in the per-scope namespace tree.
1078
 *
1079
 * `namespaces` is always allocated so parent-child wiring works for every container.
1080
 * The remaining collections are lazily allocated by `buildNamespaceLookup` only when
1081
 * a corresponding declaration is encountered, so pure-intermediate containers and
1082
 * sparsely-populated leaves do not pay the cost of empty Maps/Records they will never use.
1083
 *
1084
 * Consumers must handle these fields being undefined.
1085
 */
1086
export interface NamespaceContainer {
1087
    file: BscFile;
1088
    fullName: string;
1089
    nameRange: Range;
1090
    lastPartName: string;
1091
    namespaces?: Map<string, NamespaceContainer>;
1092
    namespaceStatements?: NamespaceStatement[];
1093
    statements?: Statement[];
1094
    classStatements?: Record<string, ClassStatement>;
1095
    functionStatements?: Record<string, FunctionStatement>;
1096
    enumStatements?: Map<string, EnumStatement>;
1097
    constStatements?: Map<string, ConstStatement>;
1098
    symbolTable?: SymbolTable;
1099
    //fields used by per-file namespace lookups; optional so scope-level callers don't have to populate them
1100
    fullNameLower?: string;
1101
    parentNameLower?: string;
1102
    nameParts?: Identifier[];
1103
    lastPartNameLower?: string;
1104
    isTopLevel?: boolean;
1105
}
1106

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