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

rokucommunity / brighterscript / #13232

24 Oct 2024 01:02PM UTC coverage: 86.866% (-1.3%) from 88.214%
#13232

push

web-flow
Merge cc3491b40 into 7cfaaa047

11613 of 14131 branches covered (82.18%)

Branch coverage included in aggregate %.

7028 of 7618 new or added lines in 100 files covered. (92.26%)

87 existing lines in 18 files now uncovered.

12732 of 13895 relevant lines covered (91.63%)

30016.17 hits per line

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

97.09
/src/astUtils/CachedLookups.ts
1
import type { AALiteralExpression, BinaryExpression, CallExpression, CallfuncExpression, DottedGetExpression, FunctionExpression, VariableExpression } from '../parser/Expression';
2
import type { AliasStatement, AssignmentStatement, AugmentedAssignmentStatement, ClassStatement, ConstStatement, EnumStatement, FunctionStatement, ImportStatement, InterfaceStatement, LibraryStatement, NamespaceStatement, TypecastStatement } from '../parser/Statement';
3
import { Cache } from '../Cache';
1✔
4
import { InternalWalkMode, WalkMode, createVisitor } from './visitors';
1✔
5
import type { Expression } from '../parser/AstNode';
6
import { isAAMemberExpression, isBinaryExpression, isCallExpression, isDottedGetExpression, isFunctionExpression, isGroupingExpression, isIndexedGetExpression, isLiteralExpression, isMethodStatement, isNamespaceStatement, isNewExpression, isVariableExpression } from './reflection';
1✔
7
import type { Parser } from '../parser/Parser';
8
import { ParseMode } from '../parser/Parser';
1✔
9
import type { Token } from '../lexer/Token';
10
import { isToken } from '../lexer/Token';
1✔
11
import type { Program } from '../Program';
12
import util from '../util';
1✔
13

14

15
export interface BscFileLike {
16
    program: Program;
17
    _parser: Parser;
18
}
19

20
export class CachedLookups {
1✔
21

22
    private cache = new Cache();
2,534✔
23

24
    constructor(private file: BscFileLike) { }
2,534✔
25

26
    get namespaceStatements(): NamespaceStatement[] {
27
        return this.getFromCache<Array<NamespaceStatement>>('namespaceStatements');
13,388✔
28
    }
29

30
    get functionStatements(): FunctionStatement[] {
31
        return this.getFromCache<Array<FunctionStatement>>('functionStatements');
1,592✔
32
    }
33

34
    get functionStatementMap() {
35
        return this.cache.getOrAdd('functionStatementMap', () => {
17✔
36
            const funcMap = new Map<string, FunctionStatement>();
15✔
37
            for (const stmt of this.functionStatements) {
15✔
38
                funcMap.set(stmt.getName(ParseMode.BrighterScript).toLowerCase(), stmt);
13✔
39
            }
40
            return funcMap;
15✔
41
        });
42
    }
43

44
    get functionExpressions(): FunctionExpression[] {
45
        return this.getFromCache<Array<FunctionExpression>>('functionExpressions');
77✔
46
    }
47

48
    get libraryStatements(): LibraryStatement[] {
49
        return this.getFromCache<Array<LibraryStatement>>('libraryStatements');
1,634✔
50
    }
51

52
    get importStatements(): ImportStatement[] {
53
        return this.getFromCache<Array<ImportStatement>>('importStatements');
5,113✔
54
    }
55

56
    get typecastStatements(): TypecastStatement[] {
57
        return this.getFromCache<Array<TypecastStatement>>('typecastStatements');
1,634✔
58
    }
59

60
    get aliasStatements(): AliasStatement[] {
61
        return this.getFromCache<Array<AliasStatement>>('aliasStatements');
11,806✔
62
    }
63

64
    /**
65
     * A collection of full expressions. This excludes intermediary expressions.
66
     *
67
     * Example 1:
68
     * `a.b.c` is composed of `a` (variableExpression)  `.b` (DottedGetExpression) `.c` (DottedGetExpression)
69
     * This will only contain the final `.c` DottedGetExpression because `.b` and `a` can both be derived by walking back from the `.c` DottedGetExpression.
70
     *
71
     * Example 2:
72
     * `name.space.doSomething(a.b.c)` will result in 2 entries in this list. the `CallExpression` for `doSomething`, and the `.c` DottedGetExpression.
73
     *
74
     * Example 3:
75
     * `value = SomeEnum.value > 2 or SomeEnum.otherValue < 10` will result in 4 entries. `SomeEnum.value`, `2`, `SomeEnum.otherValue`, `10`
76
     */
77
    get expressions(): Set<Expression> {
78
        return this.getFromCache<Set<Expression>>('expressions');
663✔
79
    }
80

81
    get classStatements(): ClassStatement[] {
82
        return this.getFromCache<Array<ClassStatement>>('classStatements');
2,891✔
83
    }
84

85
    get classStatementMap() {
86
        return this.cache.getOrAdd('classStatementMap', () => {
2,270✔
87
            const classMap = new Map<string, ClassStatement>();
868✔
88
            for (const stmt of this.classStatements) {
868✔
89
                classMap.set(stmt.getName(ParseMode.BrighterScript).toLowerCase(), stmt);
226✔
90
            }
91
            return classMap;
868✔
92
        });
93
    }
94

95
    get assignmentStatements(): AssignmentStatement[] {
96
        return this.getFromCache<Array<AssignmentStatement>>('assignmentStatements');
77✔
97
    }
98

99
    get augmentedAssignmentStatements(): AugmentedAssignmentStatement[] {
NEW
100
        return this.getFromCache<Array<AugmentedAssignmentStatement>>('augmentedAssignmentStatements');
×
101
    }
102

103
    get enumStatements(): EnumStatement[] {
104
        return this.getFromCache<Array<EnumStatement>>('enumStatements');
255✔
105
    }
106

107
    get enumStatementMap() {
108
        return this.cache.getOrAdd('enumStatementMap', () => {
1,020✔
109
            const enumMap = new Map<string, EnumStatement>();
214✔
110
            for (const stmt of this.enumStatements) {
214✔
111
                enumMap.set(stmt.fullName.toLowerCase(), stmt);
49✔
112
            }
113
            return enumMap;
214✔
114
        });
115
    }
116

117
    get constStatements(): ConstStatement[] {
118
        return this.getFromCache<Array<ConstStatement>>('constStatements');
248✔
119
    }
120

121
    get constStatementMap() {
122
        return this.cache.getOrAdd('constStatementMap', () => {
761✔
123
            const constMap = new Map<string, ConstStatement>();
208✔
124
            for (const stmt of this.constStatements) {
208✔
125
                constMap.set(stmt.fullName.toLowerCase(), stmt);
27✔
126
            }
127
            return constMap;
208✔
128
        });
129
    }
130

131
    get interfaceStatements(): InterfaceStatement[] {
132
        return this.getFromCache<Array<InterfaceStatement>>('interfaceStatements');
10✔
133
    }
134

135
    get interfaceStatementMap() {
136
        return this.cache.getOrAdd('interfaceStatementMap', () => {
4✔
137
            const ifaceMap = new Map<string, InterfaceStatement>();
4✔
138
            for (const stmt of this.interfaceStatements) {
4✔
139
                ifaceMap.set(stmt.fullName.toLowerCase(), stmt);
2✔
140
            }
141
            return ifaceMap;
4✔
142
        });
143
    }
144

145
    get propertyHints(): Record<string, string> {
146
        return this.getFromCache<Record<string, string>>('propertyHints');
7✔
147
    }
148

149

150
    invalidate() {
151
        this.cache.clear();
1,636✔
152
    }
153

154

155
    private getFromCache<T>(cacheKey: string): T {
156
        if (!this.cache.has(cacheKey)) {
39,395✔
157
            this.rehydrate();
3,863✔
158
        }
159
        if (!this.cache.has(cacheKey)) {
39,395!
NEW
160
            this.file.program.logger.info('Unable to get cached lookup', cacheKey);
×
161
        }
162
        return this.cache.get(cacheKey);
39,395✔
163
    }
164

165
    private rehydrate() {
166
        const expressions = new Set<Expression>();
3,863✔
167
        const classStatements: ClassStatement[] = [];
3,863✔
168
        const namespaceStatements: NamespaceStatement[] = [];
3,863✔
169
        const enumStatements: EnumStatement[] = [];
3,863✔
170
        const constStatements: ConstStatement[] = [];
3,863✔
171
        const interfaceStatements: InterfaceStatement[] = [];
3,863✔
172
        const assignmentStatements: AssignmentStatement[] = [];
3,863✔
173
        const libraryStatements: LibraryStatement[] = [];
3,863✔
174
        const importStatements: ImportStatement[] = [];
3,863✔
175
        const typecastStatements: TypecastStatement[] = [];
3,863✔
176
        const aliasStatements: AliasStatement[] = [];
3,863✔
177
        const functionStatements: FunctionStatement[] = [];
3,863✔
178
        const functionExpressions: FunctionExpression[] = [];
3,863✔
179

180
        const augmentedAssignmentStatements: AugmentedAssignmentStatement[] = [];
3,863✔
181

182
        const propertyHints: Record<string, string> = {};
3,863✔
183
        const addPropertyHints = (item: Token | AALiteralExpression) => {
3,863✔
184
            if (isToken(item)) {
4,173✔
185
                const name = item.text;
3,892✔
186
                propertyHints[name.toLowerCase()] = name;
3,892✔
187
            } else {
188
                for (const member of item.elements) {
281✔
189
                    const name = member.tokens.key.text;
259✔
190
                    if (!name.startsWith('"')) {
259✔
191
                        propertyHints[name.toLowerCase()] = name;
248✔
192
                    }
193

194
                }
195
            }
196
        };
197

198
        const excludedExpressions = new Set<Expression>();
3,863✔
199

200
        const visitBinaryExpression = (e: BinaryExpression) => {
3,863✔
201
            //walk the chain of binary expressions and add each one to the list of expressions
202
            const expressionsParts: Expression[] = [e];
3,241✔
203
            let expression: Expression;
204
            while ((expression = expressionsParts.pop())) {
3,241✔
205
                if (isBinaryExpression(expression)) {
11,191✔
206
                    expressionsParts.push(expression.left, expression.right);
3,975✔
207
                } else {
208
                    expressions.add(expression);
7,216✔
209
                }
210
            }
211
        };
212

213
        const visitCallExpression = (e: CallExpression | CallfuncExpression) => {
3,863✔
214
            for (const p of e.args) {
3,107✔
215
                expressions.add(p);
2,257✔
216
            }
217
            //add calls that were not excluded (from loop below)
218
            if (!excludedExpressions.has(e)) {
3,107✔
219
                expressions.add(e);
3,027✔
220
            }
221

222
            //if this call is part of a longer expression that includes a call higher up, find that higher one and remove it
223
            if (e.callee) {
3,107!
224
                let node: Expression = e.callee;
3,107✔
225
                while (node) {
3,107✔
226
                    //the primary goal for this loop. If we found a parent call expression, remove it from `references`
227
                    if (isCallExpression(node)) {
4,673✔
228
                        expressions.delete(node);
80✔
229
                        excludedExpressions.add(node);
80✔
230
                        //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.
231
                        break;
80✔
232

233
                        //when we hit a variable expression, we're definitely at the leftmost expression so stop
234
                    } else if (isVariableExpression(node) || isLiteralExpression(node) || isGroupingExpression(node)) {
4,593✔
235
                        break;
2,960✔
236
                    } else if (isDottedGetExpression(node) || isIndexedGetExpression(node)) {
1,633✔
237
                        node = node.obj;
1,566✔
238
                    } else {
239
                        //some expression we don't understand. log it and quit the loop
240
                        this.file.program.logger.debug('Encountered unknown expression while calculating function expression chain', node.kind, node.location);
67✔
241
                        break;
67✔
242
                    }
243
                }
244
            }
245
        };
246
        const visitVariableNameExpression = (e: VariableExpression | DottedGetExpression) => {
3,863✔
247
            if (!isDottedGetExpression(e.parent) && // not last expression in a dotted-get chain
16,766✔
248
                !isFunctionExpression(e) && // don't include function expressions
249
                !isNamespaceStatement(e.parent) && // don't include the name of namespace
250
                !isNewExpression(e.parent) && // don't include the inside of a new expression
251
                !(isCallExpression(e.parent) && e.parent?.callee === e) && // don't include the callee
27,876!
252
                !util.isInTypeExpression(e)) {
253
                expressions.add(e);
6,783✔
254
            }
255
        };
256

257
        // eslint-disable-next-line @typescript-eslint/dot-notation
258
        this.file['_parser']?.ast.walk(createVisitor({
3,863✔
259
            AssignmentStatement: s => {
260
                assignmentStatements.push(s);
1,617✔
261
                expressions.add(s.value);
1,617✔
262
            },
263
            ClassStatement: s => {
264
                classStatements.push(s);
831✔
265
            },
266
            InterfaceStatement: s => {
267
                interfaceStatements.push(s);
256✔
268
            },
269
            FieldStatement: s => {
270
                if (s.initialValue) {
419✔
271
                    expressions.add(s.initialValue);
136✔
272
                }
273
            },
274
            NamespaceStatement: s => {
275
                namespaceStatements.push(s);
1,183✔
276
            },
277
            FunctionStatement: s => {
278
                functionStatements.push(s);
4,328✔
279
            },
280
            ImportStatement: s => {
281
                importStatements.push(s);
388✔
282
            },
283
            TypecastStatement: s => {
284
                typecastStatements.push(s);
38✔
285
            },
286
            LibraryStatement: s => {
287
                libraryStatements.push(s);
11✔
288
            },
289
            AliasStatement: s => {
290
                aliasStatements.push(s);
60✔
291
            },
292
            FunctionExpression: (expression, parent) => {
293
                if (!isMethodStatement(parent)) {
4,886✔
294
                    functionExpressions.push(expression);
4,408✔
295
                }
296
            },
297
            ExpressionStatement: s => {
298
                expressions.add(s.expression);
837✔
299
            },
300
            CallfuncExpression: e => {
301
                visitCallExpression(e);
40✔
302
            },
303
            CallExpression: e => {
304
                visitCallExpression(e);
3,067✔
305
            },
306
            AALiteralExpression: e => {
307
                addPropertyHints(e);
281✔
308
                expressions.add(e);
281✔
309
                for (const member of e.elements) {
281✔
310
                    if (isAAMemberExpression(member)) {
259!
311
                        expressions.add(member.value);
259✔
312
                    }
313
                }
314
            },
315
            BinaryExpression: (e, parent) => {
316
                visitBinaryExpression(e);
3,241✔
317
            },
318
            ArrayLiteralExpression: e => {
319
                for (const element of e.elements) {
192✔
320
                    //keep everything except comments
321
                    expressions.add(element);
249✔
322
                }
323
            },
324
            DottedGetExpression: e => {
325
                visitVariableNameExpression(e);
3,708✔
326
                addPropertyHints(e.tokens.name);
3,708✔
327
            },
328
            DottedSetStatement: e => {
329
                addPropertyHints(e.tokens.name);
184✔
330
            },
331
            EnumStatement: e => {
332
                enumStatements.push(e);
281✔
333
            },
334
            ConstStatement: s => {
335
                constStatements.push(s);
288✔
336
            },
337
            UnaryExpression: e => {
338
                expressions.add(e);
67✔
339
            },
340
            IncrementStatement: e => {
341
                expressions.add(e);
20✔
342
            },
343
            VariableExpression: e => {
344
                visitVariableNameExpression(e);
13,058✔
345
            },
346
            AugmentedAssignmentStatement: e => {
347
                augmentedAssignmentStatements.push(e);
93✔
348
            }
349
        }), {
350
            // eslint-disable-next-line no-bitwise
351
            walkMode: WalkMode.visitAllRecursive | InternalWalkMode.visitFalseConditionalCompilationBlocks
352
        });
353

354
        this.cache.set('expressions', expressions);
3,863✔
355
        this.cache.set('classStatements', classStatements);
3,863✔
356
        this.cache.set('namespaceStatements', namespaceStatements);
3,863✔
357
        this.cache.set('namespaceStatements', namespaceStatements);
3,863✔
358
        this.cache.set('enumStatements', enumStatements);
3,863✔
359
        this.cache.set('constStatements', constStatements);
3,863✔
360
        this.cache.set('interfaceStatements', interfaceStatements);
3,863✔
361
        this.cache.set('assignmentStatements', assignmentStatements);
3,863✔
362
        this.cache.set('libraryStatements', libraryStatements);
3,863✔
363
        this.cache.set('importStatements', importStatements);
3,863✔
364
        this.cache.set('typecastStatements', typecastStatements);
3,863✔
365
        this.cache.set('aliasStatements', aliasStatements);
3,863✔
366
        this.cache.set('functionStatements', functionStatements);
3,863✔
367
        this.cache.set('functionExpressions', functionExpressions);
3,863✔
368
        this.cache.set('propertyHints', propertyHints);
3,863✔
369
        this.cache.set('augmentedAssignmentStatements', augmentedAssignmentStatements);
3,863✔
370
    }
371
}
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