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

rokucommunity / brighterscript / #15028

13 Dec 2025 01:37PM UTC coverage: 87.29% (-0.006%) from 87.296%
#15028

push

web-flow
Merge 549ba37ce into a65ebfcad

14407 of 17439 branches covered (82.61%)

Branch coverage included in aggregate %.

36 of 36 new or added lines in 4 files covered. (100.0%)

28 existing lines in 4 files now uncovered.

15090 of 16353 relevant lines covered (92.28%)

24234.91 hits per line

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

93.26
/src/parser/AstNode.ts
1
import type { WalkVisitor, WalkOptions } from '../astUtils/visitors';
2
import { WalkMode } from '../astUtils/visitors';
1✔
3
import type { Location, Position } from 'vscode-languageserver';
4
import { CancellationTokenSource } from 'vscode-languageserver';
1✔
5
import { InternalWalkMode } from '../astUtils/visitors';
1✔
6
import type { SymbolTable } from '../SymbolTable';
7
import type { BrsTranspileState } from './BrsTranspileState';
8
import type { GetTypeOptions, TranspileResult } from '../interfaces';
9
import type { AnnotationExpression } from './Expression';
10
import util from '../util';
1✔
11
import { DynamicType } from '../types/DynamicType';
1✔
12
import type { BscType } from '../types/BscType';
13
import type { Token } from '../lexer/Token';
14
import { isBlock, isBody, isFunctionParameterExpression } from '../astUtils/reflection';
1✔
15

16
/**
17
 * A BrightScript AST node
18
 */
19
export abstract class AstNode {
1✔
20
    public abstract kind: AstNodeKind;
21
    /**
22
     *  The starting and ending location of the node.
23
     */
24
    public abstract location?: Location | undefined;
25

26
    public abstract transpile(state: BrsTranspileState): TranspileResult;
27

28
    /**
29
     * Optional property, set at the top level with a map of conditional compile consts and their values
30
     */
31
    public bsConsts?: Map<string, boolean>;
32

33
    /**
34
     * Get the typedef for this node. (defaults to transpiling the node, should be overridden by subclasses if there's a more specific typedef requirement)
35
     */
36
    public getTypedef(state: BrsTranspileState) {
37
        return this.transpile(state);
×
38
    }
39

40
    /**
41
     * When being considered by the walk visitor, this describes what type of element the current class is.
42
     */
43
    public visitMode = InternalWalkMode.visitStatements;
78,592✔
44

45
    public abstract walk(visitor: WalkVisitor, options: WalkOptions);
46

47
    /**
48
     * The parent node for this statement. This is set dynamically during `onFileValidate`, and should not be set directly.
49
     */
50
    public parent?: AstNode;
51

52
    /**
53
     * Certain expressions or statements can have a symbol table (such as blocks, functions, namespace bodies, etc).
54
     * If you're interested in getting the closest SymbolTable, use `getSymbolTable` instead.
55
     */
56
    public symbolTable?: SymbolTable;
57

58
    /**
59
     * Get the closest symbol table for this node
60
     */
61
    public getSymbolTable(): SymbolTable {
62
        let node: AstNode = this;
215,697✔
63
        while (node) {
215,697✔
64
            if (node.symbolTable) {
560,396✔
65
                return node.symbolTable;
215,697✔
66
            }
67
            node = node.parent!;
344,699✔
68
        }
69

70
        //justification: we are following a chain of nodes until we get to one with a SymbolTable,
71
        //and the top-level node will always have a SymbolTable. So we'll never hit this undefined,
72
        //but it is not so easy to convince the typechecker of this.
73
        return undefined as any;
×
74
    }
75

76
    /**
77
     * Walk upward and return the first node that results in `true` from the matcher.
78
     * @param matcher a function called for each node. If you return true, this function returns the specified node. If you return a node, that node is returned. all other return values continue the loop
79
     *                The function's second parameter is a cancellation token. If you'd like to short-circuit the walk, call `cancellationToken.cancel()`, then this function will return `undefined`
80
     */
81
    public findAncestor<TNode extends AstNode = AstNode>(matcher: (node: AstNode, cancellationToken: CancellationTokenSource) => boolean | AstNode | undefined | void): TNode | undefined {
82
        let node = this.parent;
84,799✔
83

84
        const cancel = new CancellationTokenSource();
84,799✔
85
        while (node) {
84,799✔
86
            let matcherValue = matcher(node, cancel);
314,798✔
87
            if (cancel.token.isCancellationRequested) {
314,798✔
88
                return;
1✔
89
            }
90
            if (matcherValue) {
314,797✔
91
                cancel.cancel();
25,216✔
92
                return (matcherValue === true ? node : matcherValue) as TNode;
25,216✔
93

94
            }
95
            node = node.parent;
289,581✔
96
        }
97
    }
98

99
    /**
100
     * Find the first child where the matcher evaluates to true.
101
     * @param matcher a function called for each node. If you return true, this function returns the specified node. If you return a node, that node is returned. all other return values continue the loop
102
     */
103
    public findChild<TNode = AstNode>(matcher: (node: AstNode, cancellationSource) => boolean | AstNode | undefined | void, options?: WalkOptions): TNode | undefined {
104
        const cancel = new CancellationTokenSource();
1,925✔
105
        let result: AstNode | undefined;
106
        this.walk((node) => {
1,925✔
107
            const matcherValue = matcher(node, cancel);
4,531✔
108
            if (matcherValue) {
4,531✔
109
                cancel.cancel();
1,724✔
110
                result = matcherValue === true ? node : matcherValue;
1,724✔
111
            }
112
        }, {
113
            walkMode: WalkMode.visitAllRecursive,
114
            ...options ?? {},
5,775✔
115
            cancel: cancel.token
116
        });
117
        return result as unknown as TNode;
1,925✔
118
    }
119

120
    /**
121
     * Find a list of all children first child where the matcher evaluates to true.
122
     * @param matcher a function called for each node. If you return true, the specified node is included in the results. If you return a node,
123
     * that node is returned. all other return values exclude that value and continue the loop
124
     */
125
    public findChildren<TNode extends AstNode = AstNode>(matcher: (node: AstNode, cancellationSource: CancellationTokenSource) => boolean | AstNode | undefined | void, options?: WalkOptions): Array<TNode> {
126
        const cancel = new CancellationTokenSource();
44✔
127
        let result: Array<AstNode> = [];
44✔
128
        this.walk((node) => {
44✔
129
            const matcherValue = matcher(node, cancel);
680✔
130
            if (matcherValue) {
680✔
131
                result.push(matcherValue === true ? node : matcherValue);
94!
132
            }
133
        }, {
134
            walkMode: WalkMode.visitAllRecursive,
135
            ...options ?? {},
132✔
136
            cancel: cancel.token
137
        });
138
        return result as TNode[];
44✔
139
    }
140

141
    /**
142
     * FInd the deepest child that includes the given position
143
     */
144
    public findChildAtPosition<TNodeType extends AstNode = AstNode>(position: Position, options?: WalkOptions): TNodeType | undefined {
145
        return this.findChild<TNodeType>((node) => {
1,469✔
146
            //if the current node includes this range, keep that node
147
            if (util.rangeContains(node?.location.range, position)) {
3,046!
148
                return node.findChildAtPosition(position, options) ?? node;
1,282✔
149
            }
150
        }, options);
151
    }
152

153
    /**
154
     * Get the BscType of this node.
155
     */
156
    public getType(options: GetTypeOptions): BscType {
157
        return DynamicType.instance;
509✔
158
    }
159

160
    /**
161
     * Links all child nodes to their parent AstNode, and the same with symbol tables. This performs a full AST walk, so you should use this sparingly
162
     */
163
    public link() {
164
        //the act of walking causes the nodes to be linked
165
        this.walk(() => { }, {
4,095✔
166
            // eslint-disable-next-line no-bitwise
167
            walkMode: WalkMode.visitAllRecursive | InternalWalkMode.visitFalseConditionalCompilationBlocks
168
        });
169
    }
170

171
    /**
172
     * Walk upward and return the root node
173
     */
174
    public getRoot() {
175
        let node = this as AstNode;
15,004✔
176

177
        while (node) {
15,004✔
178
            if (!node.parent) {
77,520✔
179
                return node;
15,004✔
180
            }
181
            node = node.parent;
62,516✔
182
        }
183
    }
184

185

186
    /**
187
     * Gets all the trivia (comments, whitespace) that is directly before the start of this node
188
     * Note: this includes all trivia that might start on the line of the previous node
189
     */
190
    public get leadingTrivia(): Token[] {
191
        return [];
7,711✔
192
    }
193

194
    /**
195
     * Gets any trivia that is directly before the end of the node
196
     * For example, this would return all trivia before a `end function` token of a FunctionExpression
197
     */
198
    public get endTrivia(): Token[] {
199
        return [];
112✔
200
    }
201

202
    public getBsConsts() {
203
        return this.bsConsts ?? this.parent?.getBsConsts?.();
96,670✔
204
    }
205

206
    /**
207
     * Clone this node and all of its children. This creates a completely detached and identical copy of the AST.
208
     * All tokens, statements, expressions, range, and location are cloned.
209
     */
210
    public abstract clone();
211

212
    /**
213
     * Helper function for creating a clone. This will clone any attached annotations, as well as reparent the cloned node's children to the clone
214
     */
215
    protected finalizeClone<T extends AstNode>(
216
        clone: T,
217
        propsToReparent?: Array<{ [K in keyof T]: T[K] extends AstNode | AstNode[] ? K : never }[keyof T]>
218
    ) {
219
        //clone the annotations if they exist
220
        if (Array.isArray((this as unknown as Statement).annotations)) {
976✔
221
            (clone as unknown as Statement).annotations = (this as unknown as Statement).annotations?.map(x => x.clone());
7!
222
        }
223
        //reparent all of the supplied props
224
        for (let key of propsToReparent ?? []) {
976✔
225
            const children = (Array.isArray(clone?.[key]) ? clone[key] : [clone?.[key]]) as any[];
1,027!
226
            for (let child of children ?? []) {
1,027!
227
                if (child) {
1,048✔
228
                    (clone[key as any] as AstNode).parent = clone;
797✔
229
                }
230
            }
231
        }
232

233
        //reapply the location if we have one but the clone doesn't
234
        if (!clone.location && this.location) {
976✔
235
            clone.location = util.cloneLocation(this.location);
25✔
236
        }
237
        return clone;
976✔
238
    }
239

240
    /**
241
     * The index of the statement containing this node (or in the case of a statement, itself) within the containing block.
242
     */
243
    public get statementIndex(): number {
244
        if (isBody(this)) {
28,739!
245
            return 0;
×
246
        }
247
        if (!this.parent) {
28,739!
248
            return -1;
×
249
        }
250
        let currentNode: AstNode = this;
28,739✔
251
        if (isFunctionParameterExpression(currentNode)) {
28,739✔
252
            // function parameters are not part of statement lists
253
            return -1;
2,872✔
254
        }
255
        while (currentNode && !(isBlock(currentNode?.parent) || isBody(currentNode?.parent))) {
25,867!
256
            currentNode = currentNode.parent;
63,168✔
257

258
        }
259
        if (isBlock(currentNode?.parent) || isBody(currentNode?.parent)) {
25,867!
260
            return currentNode.parent.statements.indexOf(currentNode);
25,867✔
261
        }
UNCOV
262
        return -1;
×
263
    }
264
}
265

266
export abstract class Statement extends AstNode {
1✔
267
    /**
268
     * When being considered by the walk visitor, this describes what type of element the current class is.
269
     */
270
    public visitMode = InternalWalkMode.visitStatements;
36,028✔
271
    /**
272
     * Annotations for this statement
273
     */
274
    public annotations?: AnnotationExpression[] | undefined;
275
}
276

277

278
/** A BrightScript expression */
279
export abstract class Expression extends AstNode {
1✔
280
    /**
281
     * When being considered by the walk visitor, this describes what type of element the current class is.
282
     */
283
    public visitMode = InternalWalkMode.visitExpressions;
42,564✔
284
}
285

286
export enum AstNodeKind {
1✔
287
    Body = 'Body',
1✔
288
    BinaryExpression = 'BinaryExpression',
1✔
289
    CallExpression = 'CallExpression',
1✔
290
    FunctionExpression = 'FunctionExpression',
1✔
291
    FunctionParameterExpression = 'FunctionParameterExpression',
1✔
292
    NamespacedVariableNameExpression = 'NamespacedVariableNameExpression',
1✔
293
    DottedGetExpression = 'DottedGetExpression',
1✔
294
    XmlAttributeGetExpression = 'XmlAttributeGetExpression',
1✔
295
    IndexedGetExpression = 'IndexedGetExpression',
1✔
296
    GroupingExpression = 'GroupingExpression',
1✔
297
    LiteralExpression = 'LiteralExpression',
1✔
298
    EscapedCharCodeLiteralExpression = 'EscapedCharCodeLiteralExpression',
1✔
299
    ArrayLiteralExpression = 'ArrayLiteralExpression',
1✔
300
    AAMemberExpression = 'AAMemberExpression',
1✔
301
    AALiteralExpression = 'AALiteralExpression',
1✔
302
    UnaryExpression = 'UnaryExpression',
1✔
303
    VariableExpression = 'VariableExpression',
1✔
304
    SourceLiteralExpression = 'SourceLiteralExpression',
1✔
305
    NewExpression = 'NewExpression',
1✔
306
    CallfuncExpression = 'CallfuncExpression',
1✔
307
    TemplateStringQuasiExpression = 'TemplateStringQuasiExpression',
1✔
308
    TemplateStringExpression = 'TemplateStringExpression',
1✔
309
    TaggedTemplateStringExpression = 'TaggedTemplateStringExpression',
1✔
310
    AnnotationExpression = 'AnnotationExpression',
1✔
311
    TernaryExpression = 'TernaryExpression',
1✔
312
    NullCoalescingExpression = 'NullCoalescingExpression',
1✔
313
    RegexLiteralExpression = 'RegexLiteralExpression',
1✔
314
    EmptyStatement = 'EmptyStatement',
1✔
315
    AssignmentStatement = 'AssignmentStatement',
1✔
316
    ExpressionStatement = 'ExpressionStatement',
1✔
317
    ExitStatement = 'ExitStatement',
1✔
318
    FunctionStatement = 'FunctionStatement',
1✔
319
    IfStatement = 'IfStatement',
1✔
320
    IncrementStatement = 'IncrementStatement',
1✔
321
    PrintStatement = 'PrintStatement',
1✔
322
    DimStatement = 'DimStatement',
1✔
323
    GotoStatement = 'GotoStatement',
1✔
324
    LabelStatement = 'LabelStatement',
1✔
325
    ReturnStatement = 'ReturnStatement',
1✔
326
    EndStatement = 'EndStatement',
1✔
327
    StopStatement = 'StopStatement',
1✔
328
    ForStatement = 'ForStatement',
1✔
329
    ForEachStatement = 'ForEachStatement',
1✔
330
    WhileStatement = 'WhileStatement',
1✔
331
    DottedSetStatement = 'DottedSetStatement',
1✔
332
    IndexedSetStatement = 'IndexedSetStatement',
1✔
333
    LibraryStatement = 'LibraryStatement',
1✔
334
    NamespaceStatement = 'NamespaceStatement',
1✔
335
    ImportStatement = 'ImportStatement',
1✔
336
    InterfaceStatement = 'InterfaceStatement',
1✔
337
    InterfaceFieldStatement = 'InterfaceFieldStatement',
1✔
338
    InterfaceMethodStatement = 'InterfaceMethodStatement',
1✔
339
    ClassStatement = 'ClassStatement',
1✔
340
    MethodStatement = 'MethodStatement',
1✔
341
    ClassMethodStatement = 'ClassMethodStatement',
1✔
342
    FieldStatement = 'FieldStatement',
1✔
343
    ClassFieldStatement = 'ClassFieldStatement',
1✔
344
    TryCatchStatement = 'TryCatchStatement',
1✔
345
    CatchStatement = 'CatchStatement',
1✔
346
    ThrowStatement = 'ThrowStatement',
1✔
347
    EnumStatement = 'EnumStatement',
1✔
348
    EnumMemberStatement = 'EnumMemberStatement',
1✔
349
    ConstStatement = 'ConstStatement',
1✔
350
    ContinueStatement = 'ContinueStatement',
1✔
351
    Block = 'Block',
1✔
352
    TypeExpression = 'TypeExpression',
1✔
353
    TypecastExpression = 'TypecastExpression',
1✔
354
    TypedArrayExpression = 'TypedArrayExpression',
1✔
355
    TypecastStatement = 'TypecastStatement',
1✔
356
    AliasStatement = 'AliasStatement',
1✔
357
    ConditionalCompileStatement = 'ConditionalCompileStatement',
1✔
358
    ConditionalCompileConstStatement = 'ConditionalCompileConstStatement',
1✔
359
    ConditionalCompileErrorStatement = 'ConditionalCompileErrorStatement',
1✔
360
    AugmentedAssignmentStatement = 'AugmentedAssignmentStatement',
1✔
361
    PrintSeparatorExpression = 'PrintSeparatorExpression',
1✔
362
    InlineInterfaceExpression = 'InlineInterfaceExpression',
1✔
363
    InlineInterfaceMemberExpression = 'InlineInterfaceMemberExpression',
1✔
364
    TypeStatement = 'TypeStatement'
1✔
365
}
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

© 2025 Coveralls, Inc