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

rokucommunity / brighterscript / #13113

30 Sep 2024 04:35PM UTC coverage: 86.842% (-1.4%) from 88.193%
#13113

push

web-flow
Merge 25fc06528 into 3a2dc7282

11525 of 14034 branches covered (82.12%)

Branch coverage included in aggregate %.

6990 of 7581 new or added lines in 100 files covered. (92.2%)

83 existing lines in 18 files now uncovered.

12691 of 13851 relevant lines covered (91.63%)

29449.57 hits per line

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

97.04
/src/astUtils/CachedLookups.ts
1
import type { AALiteralExpression, 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 { WalkMode, createVisitor } from './visitors';
1✔
5
import type { Expression } from '../parser/AstNode';
6
import { isAAMemberExpression, isBinaryExpression, isCallExpression, isDottedGetExpression, isFunctionExpression, 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,490✔
23

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

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

30
    get functionStatements(): FunctionStatement[] {
31
        return this.getFromCache<Array<FunctionStatement>>('functionStatements');
1,560✔
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,601✔
50
    }
51

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

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

60
    get aliasStatements(): AliasStatement[] {
61
        return this.getFromCache<Array<AliasStatement>>('aliasStatements');
11,604✔
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');
648✔
79
    }
80

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

85
    get classStatementMap() {
86
        return this.cache.getOrAdd('classStatementMap', () => {
1,982✔
87
            const classMap = new Map<string, ClassStatement>();
815✔
88
            for (const stmt of this.classStatements) {
815✔
89
                classMap.set(stmt.getName(ParseMode.BrighterScript).toLowerCase(), stmt);
222✔
90
            }
91
            return classMap;
815✔
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');
249✔
105
    }
106

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

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

121
    get constStatementMap() {
122
        return this.cache.getOrAdd('constStatementMap', () => {
716✔
123
            const constMap = new Map<string, ConstStatement>();
204✔
124
            for (const stmt of this.constStatements) {
204✔
125
                constMap.set(stmt.fullName.toLowerCase(), stmt);
27✔
126
            }
127
            return constMap;
204✔
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,603✔
152
    }
153

154

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

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

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

182
        const propertyHints: Record<string, string> = {};
3,791✔
183
        const addPropertyHints = (item: Token | AALiteralExpression) => {
3,791✔
184
            if (isToken(item)) {
4,118✔
185
                const name = item.text;
3,845✔
186
                propertyHints[name.toLowerCase()] = name;
3,845✔
187
            } else {
188
                for (const member of item.elements) {
273✔
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,791✔
199
        const visitCallExpression = (e: CallExpression | CallfuncExpression) => {
3,791✔
200
            for (const p of e.args) {
3,040✔
201
                expressions.add(p);
2,208✔
202
            }
203
            //add calls that were not excluded (from loop below)
204
            if (!excludedExpressions.has(e)) {
3,040✔
205
                expressions.add(e);
2,960✔
206
            }
207

208
            //if this call is part of a longer expression that includes a call higher up, find that higher one and remove it
209
            if (e.callee) {
3,040!
210
                let node: Expression = e.callee;
3,040✔
211
                while (node) {
3,040✔
212
                    //the primary goal for this loop. If we found a parent call expression, remove it from `references`
213
                    if (isCallExpression(node)) {
4,591✔
214
                        expressions.delete(node);
80✔
215
                        excludedExpressions.add(node);
80✔
216
                        //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.
217
                        break;
80✔
218

219
                        //when we hit a variable expression, we're definitely at the leftmost expression so stop
220
                    } else if (isVariableExpression(node) || isLiteralExpression(node)) {
4,511✔
221
                        break;
2,880✔
222

223
                    } else if (isDottedGetExpression(node) || isIndexedGetExpression(node)) {
1,631✔
224
                        node = node.obj;
1,551✔
225
                    } else {
226
                        //some expression we don't understand. log it and quit the loop
227
                        this.file.program.logger.info('Encountered unknown expression while calculating function expression chain', node);
80✔
228
                        break;
80✔
229
                    }
230
                }
231
            }
232
        };
233
        const visitVariableNameExpression = (e: VariableExpression | DottedGetExpression) => {
3,791✔
234
            if (!isDottedGetExpression(e.parent) && // not last expression in a dotted-get chain
16,432✔
235
                !isFunctionExpression(e) && // don't include function expressions
236
                !isNamespaceStatement(e.parent) && // don't include the name of namespace
237
                !isNewExpression(e.parent) && // don't include the inside of a new expression
238
                !(isCallExpression(e.parent) && e.parent?.callee === e) && // don't include the callee
27,245!
239
                !util.isInTypeExpression(e)) {
240
                expressions.add(e);
6,634✔
241
            }
242
        };
243

244
        // eslint-disable-next-line @typescript-eslint/dot-notation
245
        this.file['_parser']?.ast.walk(createVisitor({
3,791✔
246
            AssignmentStatement: s => {
247
                assignmentStatements.push(s);
1,596✔
248
                expressions.add(s.value);
1,596✔
249
            },
250
            ClassStatement: s => {
251
                classStatements.push(s);
831✔
252
            },
253
            InterfaceStatement: s => {
254
                interfaceStatements.push(s);
254✔
255
            },
256
            FieldStatement: s => {
257
                if (s.initialValue) {
414✔
258
                    expressions.add(s.initialValue);
136✔
259
                }
260
            },
261
            NamespaceStatement: s => {
262
                namespaceStatements.push(s);
1,154✔
263
            },
264
            FunctionStatement: s => {
265
                functionStatements.push(s);
4,211✔
266
            },
267
            ImportStatement: s => {
268
                importStatements.push(s);
388✔
269
            },
270
            TypecastStatement: s => {
271
                typecastStatements.push(s);
38✔
272
            },
273
            LibraryStatement: s => {
274
                libraryStatements.push(s);
11✔
275
            },
276
            AliasStatement: s => {
277
                aliasStatements.push(s);
60✔
278
            },
279
            FunctionExpression: (expression, parent) => {
280
                if (!isMethodStatement(parent)) {
4,765✔
281
                    functionExpressions.push(expression);
4,287✔
282
                }
283
            },
284
            ExpressionStatement: s => {
285
                expressions.add(s.expression);
821✔
286
            },
287
            CallfuncExpression: e => {
288
                visitCallExpression(e);
40✔
289
            },
290
            CallExpression: e => {
291
                visitCallExpression(e);
3,000✔
292
            },
293
            AALiteralExpression: e => {
294
                addPropertyHints(e);
273✔
295
                expressions.add(e);
273✔
296
                for (const member of e.elements) {
273✔
297
                    if (isAAMemberExpression(member)) {
259!
298
                        expressions.add(member.value);
259✔
299
                    }
300
                }
301
            },
302
            BinaryExpression: (e, parent) => {
303
                //walk the chain of binary expressions and add each one to the list of expressions
304
                const expressionsParts: Expression[] = [e];
3,172✔
305
                let expression: Expression;
306
                while ((expression = expressionsParts.pop())) {
3,172✔
307
                    if (isBinaryExpression(expression)) {
10,964✔
308
                        expressionsParts.push(expression.left, expression.right);
3,896✔
309
                    } else {
310
                        expressions.add(expression);
7,068✔
311
                    }
312
                }
313
            },
314
            ArrayLiteralExpression: e => {
315
                for (const element of e.elements) {
188✔
316
                    //keep everything except comments
317
                    expressions.add(element);
241✔
318
                }
319
            },
320
            DottedGetExpression: e => {
321
                visitVariableNameExpression(e);
3,667✔
322
                addPropertyHints(e.tokens.name);
3,667✔
323
            },
324
            DottedSetStatement: e => {
325
                addPropertyHints(e.tokens.name);
178✔
326
            },
327
            EnumStatement: e => {
328
                enumStatements.push(e);
281✔
329
            },
330
            ConstStatement: s => {
331
                constStatements.push(s);
288✔
332
            },
333
            UnaryExpression: e => {
334
                expressions.add(e);
67✔
335
            },
336
            IncrementStatement: e => {
337
                expressions.add(e);
20✔
338
            },
339
            VariableExpression: e => {
340
                visitVariableNameExpression(e);
12,765✔
341
            },
342
            AugmentedAssignmentStatement: e => {
343
                augmentedAssignmentStatements.push(e);
93✔
344
            }
345
        }), {
346
            walkMode: WalkMode.visitAllRecursive
347
        });
348

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