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

rokucommunity / brighterscript / #15903

11 May 2026 06:41PM UTC coverage: 86.896% (-2.2%) from 89.094%
#15903

push

web-flow
Merge 70dfd6181 into ce68f5cb7

15597 of 18958 branches covered (82.27%)

Branch coverage included in aggregate %.

9 of 9 new or added lines in 3 files covered. (100.0%)

955 existing lines in 53 files now uncovered.

16351 of 17808 relevant lines covered (91.82%)

27326.16 hits per line

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

95.1
/src/astUtils/visitors.ts
1
/* eslint-disable no-bitwise */
2
import type { CancellationToken } from 'vscode-languageserver';
3
import type { Body, AssignmentStatement, Block, ExpressionStatement, FunctionStatement, IfStatement, IncrementStatement, PrintStatement, GotoStatement, LabelStatement, ReturnStatement, EndStatement, StopStatement, ForStatement, ForEachStatement, WhileStatement, DottedSetStatement, IndexedSetStatement, LibraryStatement, NamespaceStatement, ImportStatement, ClassStatement, EnumStatement, EnumMemberStatement, DimStatement, TryCatchStatement, CatchStatement, ThrowStatement, InterfaceStatement, InterfaceFieldStatement, InterfaceMethodStatement, FieldStatement, MethodStatement, ConstStatement, ContinueStatement, TypecastStatement, AliasStatement, ConditionalCompileStatement, ConditionalCompileErrorStatement, ConditionalCompileConstStatement, AugmentedAssignmentStatement, ExitStatement, TypeStatement } from '../parser/Statement';
4
import type { AAIndexedMemberExpression, AALiteralExpression, AAMemberExpression, AnnotationExpression, ArrayLiteralExpression, BinaryExpression, CallExpression, CallfuncExpression, DottedGetExpression, EscapedCharCodeLiteralExpression, FunctionExpression, FunctionParameterExpression, GroupingExpression, IndexedGetExpression, LiteralExpression, NewExpression, NullCoalescingExpression, RegexLiteralExpression, SourceLiteralExpression, TaggedTemplateStringExpression, TemplateStringExpression, TemplateStringQuasiExpression, TernaryExpression, TypecastExpression, TypeExpression, UnaryExpression, VariableExpression, XmlAttributeGetExpression } from '../parser/Expression';
5
import { isExpression, isStatement } from './reflection';
1✔
6
import type { Editor } from './Editor';
7
import type { Statement, Expression, AstNode } from '../parser/AstNode';
8

9
/**
10
 * Walks the statements of a block and descendent sub-blocks, and allow replacing statements
11
 */
12
export function walkStatements(
1✔
13
    statement: Statement,
14
    visitor: (statement: Statement, parent?: Statement, owner?: any, key?: any) => Statement | void,
15
    cancel?: CancellationToken
16
): void {
17
    statement.walk(visitor as any, {
9✔
18
        walkMode: WalkMode.visitStatements,
19
        cancel: cancel
20
    });
21
}
22

23
export type WalkVisitor = <T = AstNode>(node: AstNode, parent?: AstNode, owner?: any, key?: any) => void | T;
24

25
/**
26
 * A helper function for Statement and Expression `walkAll` calls.
27
 * @returns a new AstNode if it was changed by returning from the visitor, or undefined if not
28
 */
29
export function walk<T>(owner: T, key: keyof T, visitor: WalkVisitor, options: WalkOptions, parent?: AstNode): AstNode | void {
1✔
30
    let returnValue: AstNode | void;
31

32
    //stop processing if canceled
33
    if (options.cancel?.isCancellationRequested) {
432,010✔
34
        return returnValue;
429✔
35
    }
36

37
    //the object we're visiting
38
    let element = owner[key] as any as AstNode;
431,581✔
39
    if (!element) {
431,581✔
40
        return returnValue;
67,095✔
41
    }
42

43
    //link this node to its parent
44
    parent = parent ?? owner as unknown as AstNode;
364,486✔
45
    element.parent = parent;
364,486✔
46

47
    //get current bsConsts
48
    if (!options.bsConsts) {
364,486✔
49
        options.bsConsts = element.getBsConsts();
33,772✔
50
    }
51

52
    //notify the visitor of this element
53
    if (element.visitMode & options.walkMode) {
364,486✔
54
        returnValue = visitor?.(element, element.parent as any, owner, key);
348,123✔
55

56
        //replace the value on the parent if the visitor returned a Statement or Expression (this is how visitors can edit AST)
57
        if (returnValue && (isExpression(returnValue) || isStatement(returnValue))) {
348,123✔
58
            //if we have an editor, use that to modify the AST
59
            if (options.editor) {
4✔
60
                options.editor.setProperty(owner, key, returnValue as any);
2✔
61

62
                //we don't have an editor, modify the AST directly
63
            } else {
64
                (owner as any)[key] = returnValue;
2✔
65
            }
66
        }
67
    }
68

69
    //stop processing if canceled
70
    if (options.cancel?.isCancellationRequested) {
364,486✔
71
        return returnValue;
1,873✔
72
    }
73

74
    //do not walk children if skipped
75
    if (options.skipChildren?.shouldSkipChildren) {
362,613✔
76
        options.skipChildren.reset();
5,034✔
77
        return;
5,034✔
78
    }
79

80
    //get the element again in case it was replaced by the visitor
81
    element = owner[key] as any as AstNode;
357,579✔
82
    if (!element) {
357,579✔
83
        return returnValue;
2✔
84
    }
85

86
    //set the parent of this new expression
87
    element.parent = parent;
357,577✔
88

89
    if (!element.walk) {
357,577!
UNCOV
90
        throw new Error(`${owner.constructor.name}["${String(key)}"]${parent ? ` for ${parent.constructor.name}` : ''} does not contain a "walk" method`);
×
91
    }
92
    //walk the child expressions
93
    element.walk(visitor, options);
357,577✔
94

95
    return returnValue;
357,577✔
96
}
97

98
/**
99
 * Helper for AST elements to walk arrays when visitors might change the array size (to delete/insert items).
100
 * @param array the array to walk
101
 * @param visitor the visitor function to call on match
102
 * @param options the walk optoins
103
 * @param parent the parent AstNode of each item in the array
104
 * @param filter a function used to filter items from the array. return true if that item should be walked
105
 */
106
export function walkArray<T extends AstNode = AstNode>(array: Array<T>, visitor: WalkVisitor, options: WalkOptions, parent?: AstNode, filter?: <T>(element: T) => boolean) {
1✔
107
    let processedNodes = new Set<AstNode>();
120,756✔
108

109
    for (let i = 0; i < array?.length; i++) {
120,756!
110
        if (!filter || filter(array[i])) {
135,447!
111
            let item = array[i];
135,447✔
112
            //skip already processed nodes for this array walk
113
            if (processedNodes.has(item)) {
135,447✔
114
                continue;
22✔
115
            }
116
            processedNodes.add(item);
135,425✔
117

118
            //if the walk produced a new node, we will assume the original node was handled, and the new node's children were walked, so we can skip it if we enter recovery mode
119
            const newNode = walk(array, i, visitor, options, parent);
135,425✔
120
            if (newNode) {
135,425✔
121
                processedNodes.add(newNode);
18✔
122
            }
123

124
            //if the current item changed, restart the entire loop (we'll skip any already-processed items)
125
            if (array[i] !== item) {
135,425✔
126
                i = -1;
38✔
127
            }
128
        }
129
    }
130
}
131

132
/**
133
 * Creates an optimized visitor function.
134
 * Conventional visitors will need to inspect each incoming Statement/Expression, leading to many if statements.
135
 * This function will compare the constructor of the Statement/Expression, and perform a SINGLE logical check
136
 * to know which function to call.
137
 */
138
export function createVisitor(
1✔
139
    visitor: {
140
        /**
141
         * Called for every Statement or Expression encountered by a walker (while still honoring the WalkMode options)
142
         * The more specific visitor functions will still be called.
143
         */
144
        AstNode?: (node: Statement | Expression, parent?: AstNode, owner?: any, key?: any) => AstNode | void;
145
        //statements
146
        Body?: (statement: Body, parent?: Statement, owner?: any, key?: any) => Statement | void;
147
        AssignmentStatement?: (statement: AssignmentStatement, parent?: Statement, owner?: any, key?: any) => Statement | void;
148
        Block?: (statement: Block, parent?: Statement, owner?: any, key?: any) => Statement | void;
149
        ExpressionStatement?: (statement: ExpressionStatement, parent?: Statement, owner?: any, key?: any) => Statement | void;
150
        ExitStatement?: (statement: ExitStatement, parent?: Statement, owner?: any, key?: any) => Statement | void;
151
        FunctionStatement?: (statement: FunctionStatement, parent?: Statement, owner?: any, key?: any) => Statement | void;
152
        IfStatement?: (statement: IfStatement, parent?: Statement, owner?: any, key?: any) => Statement | void;
153
        IncrementStatement?: (statement: IncrementStatement, parent?: Statement, owner?: any, key?: any) => Statement | void;
154
        PrintStatement?: (statement: PrintStatement, parent?: Statement, owner?: any, key?: any) => Statement | void;
155
        DimStatement?: (statement: DimStatement, parent?: Statement, owner?: any, key?: any) => Statement | void;
156
        GotoStatement?: (statement: GotoStatement, parent?: Statement, owner?: any, key?: any) => Statement | void;
157
        LabelStatement?: (statement: LabelStatement, parent?: Statement, owner?: any, key?: any) => Statement | void;
158
        ReturnStatement?: (statement: ReturnStatement, parent?: Statement, owner?: any, key?: any) => Statement | void;
159
        EndStatement?: (statement: EndStatement, parent?: Statement, owner?: any, key?: any) => Statement | void;
160
        StopStatement?: (statement: StopStatement, parent?: Statement, owner?: any, key?: any) => Statement | void;
161
        ForStatement?: (statement: ForStatement, parent?: Statement, owner?: any, key?: any) => Statement | void;
162
        ForEachStatement?: (statement: ForEachStatement, parent?: Statement, owner?: any, key?: any) => Statement | void;
163
        WhileStatement?: (statement: WhileStatement, parent?: Statement, owner?: any, key?: any) => Statement | void;
164
        DottedSetStatement?: (statement: DottedSetStatement, parent?: Statement, owner?: any, key?: any) => Statement | void;
165
        IndexedSetStatement?: (statement: IndexedSetStatement, parent?: Statement, owner?: any, key?: any) => Statement | void;
166
        LibraryStatement?: (statement: LibraryStatement, parent?: Statement, owner?: any, key?: any) => Statement | void;
167
        NamespaceStatement?: (statement: NamespaceStatement, parent?: Statement, owner?: any, key?: any) => Statement | void;
168
        ImportStatement?: (statement: ImportStatement, parent?: Statement, owner?: any, key?: any) => Statement | void;
169
        TypecastStatement?: (statement: TypecastStatement, parent?: Statement, owner?: any, key?: any) => Statement | void;
170
        InterfaceStatement?: (statement: InterfaceStatement, parent?: Statement, owner?: any, key?: any) => Statement | void;
171
        InterfaceFieldStatement?: (statement: InterfaceFieldStatement, parent?: Statement) => Statement | void;
172
        InterfaceMethodStatement?: (statement: InterfaceMethodStatement, parent?: Statement, owner?: any, key?: any) => Statement | void;
173
        ClassStatement?: (statement: ClassStatement, parent?: Statement, owner?: any, key?: any) => Statement | void;
174
        ContinueStatement?: (statement: ContinueStatement, parent?: Statement, owner?: any, key?: any) => Statement | void;
175
        MethodStatement?: (statement: MethodStatement, parent?: Statement, owner?: any, key?: any) => Statement | void;
176
        FieldStatement?: (statement: FieldStatement, parent?: Statement, owner?: any, key?: any) => Statement | void;
177
        TryCatchStatement?: (statement: TryCatchStatement, parent?: Statement, owner?: any, key?: any) => Statement | void;
178
        CatchStatement?: (statement: CatchStatement, parent?: Statement, owner?: any, key?: any) => Statement | void;
179
        ThrowStatement?: (statement: ThrowStatement, parent?: Statement, owner?: any, key?: any) => Statement | void;
180
        EnumStatement?: (statement: EnumStatement, parent?: Statement, owner?: any, key?: any) => Statement | void;
181
        EnumMemberStatement?: (statement: EnumMemberStatement, parent?: Statement, owner?: any, key?: any) => Statement | void;
182
        ConstStatement?: (statement: ConstStatement, parent?: Statement, owner?: any, key?: any) => Statement | void;
183
        ConditionalCompileStatement?: (statement: ConditionalCompileStatement, parent?: Statement, owner?: any, key?: any) => Statement | void;
184
        ConditionalCompileConstStatement?: (statement: ConditionalCompileConstStatement, parent?: Statement, owner?: any, key?: any) => Statement | void;
185
        ConditionalCompileErrorStatement?: (statement: ConditionalCompileErrorStatement, parent?: Statement, owner?: any, key?: any) => Statement | void;
186
        AliasStatement?: (statement: AliasStatement, parent?: AstNode, owner?: any, key?: any) => Statement | void;
187
        AugmentedAssignmentStatement?: (statement: AugmentedAssignmentStatement, parent?: AstNode, owner?: any, key?: any) => Statement | void;
188
        //expressions
189
        BinaryExpression?: (expression: BinaryExpression, parent?: AstNode, owner?: any, key?: any) => Expression | void;
190
        CallExpression?: (expression: CallExpression, parent?: AstNode, owner?: any, key?: any) => Expression | void;
191
        FunctionExpression?: (expression: FunctionExpression, parent?: AstNode, owner?: any, key?: any) => Expression | void;
192
        FunctionParameterExpression?: (expression: FunctionParameterExpression, parent?: AstNode, owner?: any, key?: any) => Expression | void;
193
        DottedGetExpression?: (expression: DottedGetExpression, parent?: AstNode, owner?: any, key?: any) => Expression | void;
194
        XmlAttributeGetExpression?: (expression: XmlAttributeGetExpression, parent?: AstNode, owner?: any, key?: any) => Expression | void;
195
        IndexedGetExpression?: (expression: IndexedGetExpression, parent?: AstNode, owner?: any, key?: any) => Expression | void;
196
        GroupingExpression?: (expression: GroupingExpression, parent?: AstNode, owner?: any, key?: any) => Expression | void;
197
        LiteralExpression?: (expression: LiteralExpression, parent?: AstNode, owner?: any, key?: any) => Expression | void;
198
        EscapedCharCodeLiteralExpression?: (expression: EscapedCharCodeLiteralExpression, parent?: AstNode, owner?: any, key?: any) => Expression | void;
199
        ArrayLiteralExpression?: (expression: ArrayLiteralExpression, parent?: AstNode, owner?: any, key?: any) => Expression | void;
200
        AAMemberExpression?: (expression: AAMemberExpression, parent?: AstNode, owner?: any, key?: any) => Expression | void;
201
        AAIndexedMemberExpression?: (expression: AAIndexedMemberExpression, parent?: AstNode, owner?: any, key?: any) => Expression | void;
202
        AALiteralExpression?: (expression: AALiteralExpression, parent?: AstNode, owner?: any, key?: any) => Expression | void;
203
        UnaryExpression?: (expression: UnaryExpression, parent?: AstNode, owner?: any, key?: any) => Expression | void;
204
        VariableExpression?: (expression: VariableExpression, parent?: AstNode, owner?: any, key?: any) => Expression | void;
205
        SourceLiteralExpression?: (expression: SourceLiteralExpression, parent?: AstNode, owner?: any, key?: any) => Expression | void;
206
        NewExpression?: (expression: NewExpression, parent?: AstNode, owner?: any, key?: any) => Expression | void;
207
        CallfuncExpression?: (expression: CallfuncExpression, parent?: AstNode, owner?: any, key?: any) => Expression | void;
208
        TemplateStringQuasiExpression?: (expression: TemplateStringQuasiExpression, parent?: AstNode, owner?: any, key?: any) => Expression | void;
209
        TemplateStringExpression?: (expression: TemplateStringExpression, parent?: AstNode, owner?: any, key?: any) => Expression | void;
210
        TaggedTemplateStringExpression?: (expression: TaggedTemplateStringExpression, parent?: AstNode, owner?: any, key?: any) => Expression | void;
211
        AnnotationExpression?: (expression: AnnotationExpression, parent?: AstNode, owner?: any, key?: any) => Expression | void;
212
        TernaryExpression?: (expression: TernaryExpression, parent?: AstNode, owner?: any, key?: any) => Expression | void;
213
        NullCoalescingExpression?: (expression: NullCoalescingExpression, parent?: AstNode, owner?: any, key?: any) => Expression | void;
214
        RegexLiteralExpression?: (expression: RegexLiteralExpression, parent?: AstNode, owner?: any, key?: any) => Expression | void;
215
        TypeExpression?: (expression: TypeExpression, parent?: AstNode, owner?: any, key?: any) => Expression | void;
216
        TypecastExpression?: (expression: TypecastExpression, parent?: AstNode, owner?: any, key?: any) => Expression | void;
217
        TypeStatement?: (statement: TypeStatement, parent?: Statement, owner?: any, key?: any) => Statement | void;
218
    }
219
) {
220
    return <WalkVisitor>((statement: Statement, parent?: Statement, owner?: any, key?: any): Statement | void => {
17,443✔
221
        //call the generic AstNode visitor first (if defined)
222
        visitor.AstNode?.(statement, parent, owner, key);
251,983✔
223
        //now call the specifically-named visitor
224
        return visitor[statement.constructor.name]?.(statement, parent, owner, key);
251,983✔
225
    });
226
}
227

228
export interface WalkOptions {
229
    /**
230
     * What mode should the walker walk?
231
     * You can use the unique enums, or apply bitwise and to combine the various modes you're interested in
232
     */
233
    walkMode: WalkMode;
234
    /**
235
     * A token that can be used to cancel the walk operation
236
     */
237
    cancel?: CancellationToken;
238
    /**
239
     * If provided, any AST replacements will be done using this Editor instead of directly against the AST itself
240
     */
241
    editor?: Editor;
242
    /**
243
     * A token that can be used to stop the walk from going any deeper in the current node,
244
     * but will continue walking sibling nodes
245
     */
246
    skipChildren?: ChildrenSkipper;
247
    /**
248
     * Map of Conditional compilation flags, with names in lowercase
249
     */
250
    bsConsts?: Map<string, boolean>;
251
}
252

253
export class ChildrenSkipper {
1✔
254
    private isSkipped = false;
4,148✔
255

256
    public reset() {
257
        this.isSkipped = false;
5,034✔
258
    }
259

260
    public skip() {
261
        this.isSkipped = true;
5,034✔
262
    }
263

264
    get shouldSkipChildren() {
265
        return this.isSkipped;
21,798✔
266
    }
267
}
268

269

270
/**
271
 * An enum used to denote the specific WalkMode options (without
272
 */
273
export enum InternalWalkMode {
1✔
274
    /**
275
     * Walk statements
276
     */
277
    walkStatements = 1,
1✔
278
    /**
279
     * Call the visitor for every statement encountered by a walker
280
     */
281
    visitStatements = 2,
1✔
282
    /**
283
     * Walk expressions.
284
     */
285
    walkExpressions = 4,
1✔
286
    /**
287
     * Call the visitor for every expression encountered by a walker
288
     */
289
    visitExpressions = 8,
1✔
290
    /**
291
     * If child function expressions are encountered, this will allow the walker to step into them.
292
     */
293
    recurseChildFunctions = 16,
1✔
294
    /**
295
     * Step into conditional compilation blocks that are guarded by a flag that evaluates to false
296
     */
297
    visitFalseConditionalCompilationBlocks = 64
1✔
298
}
299

300
/* eslint-disable @typescript-eslint/prefer-literal-enum-member */
301
export enum WalkMode {
1✔
302
    /**
303
     * Walk statements, but does NOT step into child functions
304
     */
305
    walkStatements = InternalWalkMode.walkStatements,
1✔
306
    /**
307
     * Walk and visit statements, but does NOT step into child functions
308
     */
309
    visitStatements = InternalWalkMode.walkStatements | InternalWalkMode.visitStatements,
1✔
310
    /**
311
     * Walk expressions, but does NOT step into child functions
312
     */
313
    walkExpressions = InternalWalkMode.walkExpressions,
1✔
314
    /**
315
     * Walk and visit expressions of the statement, but doesn't walk child statements
316
     */
317
    visitLocalExpressions = InternalWalkMode.walkExpressions | InternalWalkMode.visitExpressions,
1✔
318
    /**
319
     * Walk and visit expressions, but does NOT step into child functions
320
     */
321
    visitExpressions = InternalWalkMode.walkStatements | InternalWalkMode.walkExpressions | InternalWalkMode.visitExpressions,
1✔
322
    /**
323
     * Visit all descendent statements and expressions, but does NOT step into child functions
324
     */
325
    visitAll = InternalWalkMode.walkStatements | InternalWalkMode.visitStatements | InternalWalkMode.walkExpressions | InternalWalkMode.visitExpressions,
1✔
326
    /**
327
     * If child function expressions are encountered, this will allow the walker to step into them.
328
     * This includes `WalkMode.walkExpressions`
329
     */
330
    recurseChildFunctions = InternalWalkMode.recurseChildFunctions | InternalWalkMode.walkExpressions,
1✔
331
    /**
332
     * Visit all descendent statements, and DOES step into child functions
333
     */
334
    visitStatementsRecursive = InternalWalkMode.walkStatements | InternalWalkMode.visitStatements | InternalWalkMode.walkExpressions | InternalWalkMode.recurseChildFunctions,
1✔
335
    /**
336
     * Visit all descendent expressions, and DOES step into child functions
337
     */
338
    visitExpressionsRecursive = InternalWalkMode.walkStatements | InternalWalkMode.walkExpressions | InternalWalkMode.visitExpressions | InternalWalkMode.recurseChildFunctions,
1✔
339
    /**
340
     * Visit all descendent statements and expressions, and DOES step into child functions
341
     */
342
    visitAllRecursive = InternalWalkMode.walkStatements | InternalWalkMode.visitStatements | InternalWalkMode.walkExpressions | InternalWalkMode.visitExpressions | InternalWalkMode.recurseChildFunctions
1✔
343
}
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