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

rokucommunity / brighterscript / #13308

22 Nov 2024 02:25PM UTC coverage: 86.801%. Remained the same
#13308

push

web-flow
Merge 332332a1f into 2a6afd921

11833 of 14419 branches covered (82.07%)

Branch coverage included in aggregate %.

191 of 205 new or added lines in 26 files covered. (93.17%)

201 existing lines in 18 files now uncovered.

12868 of 14038 relevant lines covered (91.67%)

32022.22 hits per line

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

89.19
/src/SymbolTable.ts
1
import type { BscType } from './types/BscType';
2
import type { ExtraSymbolData, GetTypeOptions } from './interfaces';
3
import { CacheVerifier } from './CacheVerifier';
1✔
4
import type { ReferenceType } from './types/ReferenceType';
5
import type { UnionType } from './types/UnionType';
6
import { getUniqueType } from './types/helpers';
1✔
7
import { isAnyReferenceType, isNamespaceType, isReferenceType } from './astUtils/reflection';
1✔
8
import { SymbolTypeFlag } from './SymbolTypeFlag';
1✔
9

10
/**
11
 * Stores the types associated with variables and functions in the Brighterscript code
12
 * Can be part of a hierarchy, so lookups can reference parent scopes
13
 */
14
export class SymbolTable implements SymbolTypeGetter {
1✔
15
    constructor(
16
        public name: string,
6,327,547✔
17
        parentProvider?: SymbolTableProvider
18
    ) {
19
        this.resetTypeCache();
6,327,547✔
20
        if (parentProvider) {
6,327,547✔
21
            this.pushParentProvider(parentProvider);
196,399✔
22
        }
23
    }
24

25
    /**
26
     * The map of symbols declared directly in this SymbolTable (excludes parent SymbolTable).
27
     * Indexed by lower symbol name
28
     */
29
    private symbolMap = new Map<string, BscSymbol[]>();
6,327,547✔
30

31
    private parentProviders = [] as SymbolTableProvider[];
6,327,547✔
32

33
    private cacheToken: string;
34

35
    private typeCache: Array<Map<string, TypeCacheEntry>>;
36

37
    /**
38
     * Used to invalidate the cache for all symbol tables.
39
     *
40
     * This is not the most optimized solution as cache will be shared across all instances of SymbolTable across all programs,
41
     * but this is the easiest way to handle nested/linked symbol table cache management so we can optimize this in the future some time...
42
     */
43
    static cacheVerifier = new CacheVerifier();
1✔
44

45
    static referenceTypeFactory: (memberKey: string, fullName, flags: SymbolTypeFlag, tableProvider: SymbolTypeGetterProvider) => ReferenceType;
46
    static unionTypeFactory: (types: BscType[]) => UnionType;
47

48
    /**
49
     * Push a function that will provide a parent SymbolTable when requested
50
     */
51
    public pushParentProvider(provider: SymbolTableProvider) {
52
        this.parentProviders.push(provider);
379,047✔
53
        return () => {
379,047✔
54
            this.popParentProvider();
9,704✔
55
        };
56
    }
57

58
    /**
59
     * Pop the current parentProvider
60
     */
61
    public popParentProvider() {
62
        this.parentProviders.pop();
9,704✔
63
    }
64

65
    /**
66
     * The parent SymbolTable (if there is one)
67
     */
68
    public get parent(): SymbolTable | undefined {
69
        return this.parentProviders[this.parentProviders.length - 1]?.();
576,399✔
70
    }
71

72
    private siblings = new Set<SymbolTable>();
6,327,547✔
73

74
    /**
75
     * Add a sibling symbol table (which will be inspected first before walking upward to the parent
76
     */
77
    public addSibling(sibling: SymbolTable) {
78
        this.siblings.add(sibling);
12,289✔
79
        return () => {
12,289✔
80
            this.siblings.delete(sibling);
8,849✔
81
        };
82
    }
83

84
    /**
85
     * Remove a sibling symbol table
86
     */
87
    public removeSibling(sibling: SymbolTable) {
88
        this.siblings.delete(sibling);
×
89
    }
90

91

92
    public clearSymbols() {
93
        this.symbolMap.clear();
×
94
    }
95

96
    /**
97
     * Checks if the symbol table contains the given symbol by name
98
     * If the identifier is not in this table, it will check the parent
99
     *
100
     * @param name the name to lookup
101
     * @param bitFlags flags to match (See SymbolTypeFlags)
102
     * @returns true if this symbol is in the symbol table
103
     */
104
    hasSymbol(name: string, bitFlags: SymbolTypeFlag): boolean {
105
        let currentTable: SymbolTable = this;
340,549✔
106
        const key = name?.toLowerCase();
340,549!
107
        let result: BscSymbol[];
108
        do {
340,549✔
109
            // look in our map first
110
            if ((result = currentTable.symbolMap.get(key))) {
347,605✔
111
                // eslint-disable-next-line no-bitwise
112
                if (result.find(symbol => symbol.flags & bitFlags)) {
161,563✔
113
                    return true;
161,560✔
114
                }
115
            }
116

117
            //look through any sibling maps next
118
            for (let sibling of currentTable.siblings) {
186,045✔
119
                if ((result = sibling.symbolMap.get(key))) {
2,996✔
120
                    // eslint-disable-next-line no-bitwise
121
                    if (result.find(symbol => symbol.flags & bitFlags)) {
323!
122
                        return true;
323✔
123
                    }
124
                }
125
            }
126
            currentTable = currentTable.parent;
185,722✔
127
        } while (currentTable);
128
        return false;
178,666✔
129
    }
130

131
    /**
132
     * Gets the name/type pair for a given named variable or function name
133
     * If the identifier is not in this table, it will check the parent
134
     *
135
     * @param  name the name to lookup
136
     * @param bitFlags flags to match
137
     * @returns An array of BscSymbols - one for each time this symbol had a type implicitly defined
138
     */
139
    getSymbol(name: string, bitFlags: SymbolTypeFlag): BscSymbol[] {
140
        let currentTable: SymbolTable = this;
513,818✔
141
        const key = name?.toLowerCase();
513,818✔
142
        let result: BscSymbol[];
143
        let memberOfAncestor = false;
513,818✔
144
        const addAncestorInfo = (symbol: BscSymbol) => ({ ...symbol, data: { ...symbol.data, memberOfAncestor: memberOfAncestor } });
513,818✔
145
        do {
513,818✔
146
            // look in our map first
147
            if ((result = currentTable.symbolMap.get(key))) {
608,487✔
148
                // eslint-disable-next-line no-bitwise
149
                result = result.filter(symbol => symbol.flags & bitFlags);
217,304✔
150
                if (result.length > 0) {
217,177✔
151
                    return result.map(addAncestorInfo);
216,662✔
152
                }
153
            }
154
            //look through any sibling maps next
155
            for (let sibling of currentTable.siblings) {
391,825✔
156
                result = sibling.getSymbol(key, bitFlags);
45,745✔
157
                if (result?.length > 0) {
45,745✔
158
                    return result.map(addAncestorInfo);
1,762✔
159
                }
160
            }
161
            currentTable = currentTable.parent;
390,063✔
162
            memberOfAncestor = true;
390,063✔
163
        } while (currentTable);
164
    }
165

166
    /**
167
     * Adds a new symbol to the table
168
     */
169
    addSymbol(name: string, data: ExtraSymbolData, type: BscType, bitFlags: SymbolTypeFlag) {
170
        if (!name) {
6,399,140✔
171
            return;
1✔
172
        }
173
        const key = name?.toLowerCase();
6,399,139!
174
        if (!this.symbolMap.has(key)) {
6,399,139✔
175
            this.symbolMap.set(key, []);
6,370,232✔
176
        }
177
        this.symbolMap.get(key)?.push({
6,399,139!
178
            name: name,
179
            data: data,
180
            type: type,
181
            flags: bitFlags
182
        });
183
    }
184

185
    /**
186
     * Removes a new symbol from the table
187
     */
188
    removeSymbol(name: string) {
189
        const key = name.toLowerCase();
1,953✔
190
        if (!this.symbolMap.has(key)) {
1,953✔
191
            this.symbolMap.set(key, []);
632✔
192
        }
193
        this.symbolMap.delete(key);
1,953✔
194
    }
195

196
    public getSymbolTypes(name: string, options: GetSymbolTypeOptions): TypeCacheEntry[] {
197
        const symbolArray = this.getSymbol(name, options.flags);
463,344✔
198
        if (!symbolArray) {
463,344✔
199
            return undefined;
247,178✔
200
        }
201
        return symbolArray?.map(symbol => ({ type: symbol.type, data: symbol.data, flags: symbol.flags }));
216,283!
202
    }
203

204
    getSymbolType(name: string, options: GetSymbolTypeOptions): BscType {
205
        const cacheEntry = options.ignoreCacheForRetrieval ? undefined : this.getCachedType(name, options);
986,759!
206
        let resolvedType = cacheEntry?.type;
986,759✔
207
        let doSetCache = !resolvedType;
986,759✔
208
        const originalIsReferenceType = isAnyReferenceType(resolvedType);
986,759✔
209
        let data = cacheEntry?.data || {} as ExtraSymbolData;
986,759✔
210
        let foundFlags: SymbolTypeFlag = cacheEntry?.flags;
986,759✔
211
        if (!resolvedType || originalIsReferenceType) {
986,759✔
212
            const symbolTypes = this.getSymbolTypes(name, options);
462,489✔
213
            data = symbolTypes?.[0]?.data;
462,489✔
214
            foundFlags = symbolTypes?.[0].flags;
462,489✔
215
            resolvedType = getUniqueType(symbolTypes?.map(symbol => symbol.type), SymbolTable.unionTypeFactory);
462,489✔
216
        }
217
        if (!resolvedType && options.fullName && options.tableProvider) {
986,759✔
218
            resolvedType = SymbolTable.referenceTypeFactory(name, options.fullName, options.flags, options.tableProvider);
210,553✔
219
        }
220
        const resolvedTypeIsReference = isAnyReferenceType(resolvedType);
986,759✔
221
        const newNonReferenceType = originalIsReferenceType && !isAnyReferenceType(resolvedType);
986,759✔
222
        doSetCache = doSetCache && (options.onlyCacheResolvedTypes ? !resolvedTypeIsReference : true);
986,759✔
223
        if (doSetCache || newNonReferenceType) {
986,759✔
224
            this.setCachedType(name, { type: resolvedType, data: data, flags: foundFlags }, options);
461,122✔
225
        }
226
        options.data ??= {};
986,756✔
227
        if (options.data) {
986,756!
228
            options.data.definingNode = data?.definingNode;
986,756✔
229
            options.data.description = data?.description;
986,756✔
230
            options.data.flags = foundFlags ?? options.flags;
986,756✔
231
            options.data.memberOfAncestor = data?.memberOfAncestor;
986,756✔
232
            options.data.doNotMerge = data?.doNotMerge;
986,756✔
233
            options.data.isAlias = data?.isAlias;
986,756✔
234
            options.data.isInstance = data?.isInstance;
986,756✔
235
            options.data.isFromDocComment = data?.isFromDocComment;
986,756✔
236
            options.data.isFromCallFunc = data?.isFromCallFunc;
986,756✔
237
        }
238
        return resolvedType;
986,756✔
239
    }
240

241
    isSymbolTypeInstance(name: string) {
242
        const data: ExtraSymbolData = {};
4✔
243
        this.getSymbolType(name, { flags: SymbolTypeFlag.runtime, data: data });
4✔
244
        return data?.isInstance;
4!
245
    }
246

247
    /**
248
     * Adds all the symbols from another table to this one
249
     * It will overwrite any existing symbols in this table
250
     */
251
    mergeSymbolTable(symbolTable: SymbolTable) {
252
        for (let [, value] of symbolTable.symbolMap) {
4,499✔
253
            for (const symbol of value) {
4,124✔
254
                if (symbol.data?.doNotMerge) {
4,144✔
255
                    continue;
40✔
256
                }
257
                this.addSymbol(
4,104✔
258
                    symbol.name,
259
                    symbol.data,
260
                    symbol.type,
261
                    symbol.flags
262
                );
263
            }
264
        }
265
    }
266

267
    mergeNamespaceSymbolTables(symbolTable: SymbolTable) {
268
        const disposables = [] as Array<() => void>;
7,790✔
269
        for (let [_name, value] of symbolTable.symbolMap) {
7,790✔
270
            const symbol = value[0];
2,023✔
271
            if (symbol) {
2,023!
272
                if (symbol.data?.doNotMerge) {
2,023!
UNCOV
273
                    continue;
×
274
                }
275
                const existingRuntimeType = this.getSymbolType(symbol.name, { flags: symbol.flags });
2,023✔
276

277
                if (isNamespaceType(existingRuntimeType) && isNamespaceType(symbol.type)) {
2,023✔
278
                    disposables.push(...existingRuntimeType.memberTable.mergeNamespaceSymbolTables(symbol.type.memberTable));
702✔
279
                } else {
280
                    this.addSymbol(
1,321✔
281
                        symbol.name,
282
                        symbol.data,
283
                        symbol.type,
284
                        symbol.flags
285
                    );
286
                    disposables.push(() => this.removeSymbol(symbol.name));
1,321✔
287
                }
288
            }
289
        }
290

291
        for (let siblingTable of symbolTable.siblings) {
7,790✔
292
            disposables.push(...this.mergeNamespaceSymbolTables(siblingTable));
700✔
293
        }
294
        return disposables;
7,790✔
295
    }
296

297
    /**
298
     * Get list of symbols declared directly in this SymbolTable (excludes parent SymbolTable).
299
     */
300
    public getOwnSymbols(bitFlags: SymbolTypeFlag): BscSymbol[] {
301
        let symbols: BscSymbol[] = [].concat(...this.symbolMap.values());
4,788✔
302
        // eslint-disable-next-line no-bitwise
303
        symbols = symbols.filter(symbol => symbol.flags & bitFlags);
25,173✔
304
        return symbols;
4,788✔
305
    }
306

307
    /**
308
     * Get list of all symbols declared in this SymbolTable (includes parent SymbolTable).
309
     */
310
    public getAllSymbols(bitFlags: SymbolTypeFlag): BscSymbol[] {
311
        let symbols: BscSymbol[] = [].concat(...this.symbolMap.values());
469✔
312
        //look through any sibling maps next
313
        for (let sibling of this.siblings) {
469✔
314
            symbols = symbols.concat(sibling.getAllSymbols(bitFlags));
32✔
315
        }
316
        if (this.parent) {
469✔
317
            symbols = symbols.concat(this.parent.getAllSymbols(bitFlags));
145✔
318
        }
319
        // eslint-disable-next-line no-bitwise
320
        symbols = symbols.filter(symbol => symbol.flags & bitFlags);
1,656✔
321

322
        //remove duplicate symbols
323
        const symbolsMap = new Map<string, BscSymbol>();
469✔
324
        for (const symbol of symbols) {
469✔
325
            const lowerSymbolName = symbol.name.toLowerCase();
1,656✔
326
            if (!symbolsMap.has(lowerSymbolName)) {
1,656✔
327
                symbolsMap.set(lowerSymbolName, symbol);
1,651✔
328
            }
329
        }
330
        return [...symbolsMap.values()];
469✔
331
    }
332

333
    private resetTypeCache() {
334
        this.typeCache = [
6,332,638✔
335
            undefined,
336
            new Map<string, TypeCacheEntry>(), // SymbolTypeFlags.runtime
337
            new Map<string, TypeCacheEntry>(), // SymbolTypeFlags.typetime
338
            new Map<string, TypeCacheEntry>() // SymbolTypeFlags.runtime & SymbolTypeFlags.typetime
339
        ];
340
        this.cacheToken = SymbolTable.cacheVerifier?.getToken();
6,332,638!
341
    }
342

343
    getCachedType(name: string, options: GetTypeOptions): TypeCacheEntry {
344
        if (SymbolTable.cacheVerifier) {
986,759!
345
            if (!SymbolTable.cacheVerifier?.checkToken(this.cacheToken)) {
986,759!
346
                // we have a bad token
347
                this.resetTypeCache();
5,091✔
348
                return;
5,091✔
349
            }
350
        } else {
351
            // no cache verifier
UNCOV
352
            return;
×
353
        }
354
        return this.typeCache[options.flags]?.get(name.toLowerCase());
981,668!
355
    }
356

357
    setCachedType(name: string, cacheEntry: TypeCacheEntry, options: GetTypeOptions) {
358
        if (!cacheEntry) {
461,812!
UNCOV
359
            return;
×
360
        }
361
        if (SymbolTable.cacheVerifier) {
461,812!
362
            if (!SymbolTable.cacheVerifier?.checkToken(this.cacheToken)) {
461,812!
363
                // we have a bad token - remove all other caches
UNCOV
364
                this.resetTypeCache();
×
365
            }
366
        } else {
367
            // no cache verifier
UNCOV
368
            return;
×
369
        }
370
        let existingCachedValue = this.typeCache[options.flags]?.get(name.toLowerCase());
461,812!
371
        if (isReferenceType(cacheEntry.type) && !isReferenceType(existingCachedValue)) {
461,809✔
372
            // No need to overwrite a non-referenceType with a referenceType
373
            return;
210,478✔
374
        }
375
        return this.typeCache[options.flags]?.set(name.toLowerCase(), cacheEntry);
251,331!
376
    }
377

378
    /**
379
     * Serialize this SymbolTable to JSON (useful for debugging reasons)
380
     */
381
    private toJSON() {
UNCOV
382
        return {
×
383
            name: this.name,
UNCOV
384
            siblings: [...this.siblings].map(sibling => sibling.toJSON()),
×
385
            parent: this.parent?.toJSON(),
×
386
            symbols: [
387
                ...new Set(
388
                    [...this.symbolMap.entries()].map(([key, symbols]) => {
UNCOV
389
                        return symbols.map(x => {
×
390
                            return { name: x.name, type: (x.type as any)?.__identifier };
×
391
                        });
392
                    }).flat().sort()
393
                )
394
            ]
395
        };
396
    }
397
}
398

399
export interface BscSymbol {
400
    name: string;
401
    data: ExtraSymbolData;
402
    type: BscType;
403
    flags: SymbolTypeFlag;
404
}
405

406
export interface BscSymbolWithSource extends BscSymbol {
407
    comesFromAncestor: boolean; // this symbol comes from an ancestor symbol table
408
}
409

410
export interface SymbolTypeGetter {
411
    name: string;
412
    getSymbolType(name: string, options: GetSymbolTypeOptions): BscType;
413
    setCachedType(name: string, cacheEntry: TypeCacheEntry, options: GetSymbolTypeOptions);
414
    addSibling(symbolTable: SymbolTable);
415
}
416

417
/**
418
 * A function that returns a symbol table.
419
 */
420
export type SymbolTableProvider = () => SymbolTable;
421

422
/**
423
 * A function that returns a symbol types getter - smaller interface used in types
424
 */
425
export type SymbolTypeGetterProvider = () => SymbolTypeGetter;
426

427

428
export interface GetSymbolTypeOptions extends GetTypeOptions {
429
    fullName?: string;
430
    tableProvider?: SymbolTableProvider;
431
}
432

433
export interface TypeCacheEntry {
434
    type: BscType;
435
    data?: ExtraSymbolData;
436
    flags?: SymbolTypeFlag;
437
}
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