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

rokucommunity / brighterscript / #13306

22 Nov 2024 02:25PM UTC coverage: 86.801% (-1.4%) from 88.237%
#13306

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%)

797 existing lines in 45 files now uncovered.

12868 of 14038 relevant lines covered (91.67%)

32022.05 hits per line

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

96.74
/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

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

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

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

32
    /**
33
     * When being considered by the walk visitor, this describes what type of element the current class is.
34
     */
35
    public visitMode = InternalWalkMode.visitStatements;
68,070✔
36

37
    public abstract walk(visitor: WalkVisitor, options: WalkOptions);
38

39
    /**
40
     * The parent node for this statement. This is set dynamically during `onFileValidate`, and should not be set directly.
41
     */
42
    public parent?: AstNode;
43

44
    /**
45
     * Certain expressions or statements can have a symbol table (such as blocks, functions, namespace bodies, etc).
46
     * If you're interested in getting the closest SymbolTable, use `getSymbolTable` instead.
47
     */
48
    public symbolTable?: SymbolTable;
49

50
    /**
51
     * Get the closest symbol table for this node
52
     */
53
    public getSymbolTable(): SymbolTable {
54
        let node: AstNode = this;
142,228✔
55
        while (node) {
142,228✔
56
            if (node.symbolTable) {
391,584✔
57
                return node.symbolTable;
142,228✔
58
            }
59
            node = node.parent!;
249,356✔
60
        }
61

62
        //justification: we are following a chain of nodes until we get to one with a SymbolTable,
63
        //and the top-level node will always have a SymbolTable. So we'll never hit this undefined,
64
        //but it is not so easy to convince the typechecker of this.
UNCOV
65
        return undefined as any;
×
66
    }
67

68
    /**
69
     * Walk upward and return the first node that results in `true` from the matcher.
70
     * @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
71
     *                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`
72
     */
73
    public findAncestor<TNode extends AstNode = AstNode>(matcher: (node: AstNode, cancellationToken: CancellationTokenSource) => boolean | AstNode | undefined | void): TNode | undefined {
74
        let node = this.parent;
69,383✔
75

76
        const cancel = new CancellationTokenSource();
69,383✔
77
        while (node) {
69,383✔
78
            let matcherValue = matcher(node, cancel);
268,240✔
79
            if (cancel.token.isCancellationRequested) {
268,240✔
80
                return;
1✔
81
            }
82
            if (matcherValue) {
268,239✔
83
                cancel.cancel();
19,280✔
84
                return (matcherValue === true ? node : matcherValue) as TNode;
19,280✔
85

86
            }
87
            node = node.parent;
248,959✔
88
        }
89
    }
90

91
    /**
92
     * Find the first child where the matcher evaluates to true.
93
     * @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
94
     */
95
    public findChild<TNode = AstNode>(matcher: (node: AstNode, cancellationSource) => boolean | AstNode | undefined | void, options?: WalkOptions): TNode | undefined {
96
        const cancel = new CancellationTokenSource();
1,830✔
97
        let result: AstNode | undefined;
98
        this.walk((node) => {
1,830✔
99
            const matcherValue = matcher(node, cancel);
4,252✔
100
            if (matcherValue) {
4,252✔
101
                cancel.cancel();
1,638✔
102
                result = matcherValue === true ? node : matcherValue;
1,638✔
103
            }
104
        }, {
105
            walkMode: WalkMode.visitAllRecursive,
106
            ...options ?? {},
5,490✔
107
            cancel: cancel.token
108
        });
109
        return result as unknown as TNode;
1,830✔
110
    }
111

112
    /**
113
     * Find a list of all children first child where the matcher evaluates to true.
114
     * @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,
115
     * that node is returned. all other return values exclude that value and continue the loop
116
     */
117
    public findChildren<TNode extends AstNode = AstNode>(matcher: (node: AstNode, cancellationSource: CancellationTokenSource) => boolean | AstNode | undefined | void, options?: WalkOptions): Array<TNode> {
118
        const cancel = new CancellationTokenSource();
27✔
119
        let result: Array<AstNode> = [];
27✔
120
        this.walk((node) => {
27✔
121
            const matcherValue = matcher(node, cancel);
306✔
122
            if (matcherValue) {
306✔
123
                result.push(matcherValue === true ? node : matcherValue);
52!
124
            }
125
        }, {
126
            walkMode: WalkMode.visitAllRecursive,
127
            ...options ?? {},
81✔
128
            cancel: cancel.token
129
        });
130
        return result as TNode[];
27✔
131
    }
132

133
    /**
134
     * FInd the deepest child that includes the given position
135
     */
136
    public findChildAtPosition<TNodeType extends AstNode = AstNode>(position: Position, options?: WalkOptions): TNodeType | undefined {
137
        return this.findChild<TNodeType>((node) => {
1,456✔
138
            //if the current node includes this range, keep that node
139
            if (util.rangeContains(node?.location.range, position)) {
3,034!
140
                return node.findChildAtPosition(position, options) ?? node;
1,272✔
141
            }
142
        }, options);
143
    }
144

145
    /**
146
     * Get the BscType of this node.
147
     */
148
    public getType(options: GetTypeOptions): BscType {
149
        return DynamicType.instance;
494✔
150
    }
151

152
    /**
153
     * 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
154
     */
155
    public link() {
156
        //the act of walking causes the nodes to be linked
157
        this.walk(() => { }, {
3,573✔
158
            // eslint-disable-next-line no-bitwise
159
            walkMode: WalkMode.visitAllRecursive | InternalWalkMode.visitFalseConditionalCompilationBlocks
160
        });
161
    }
162

163
    /**
164
     * Walk upward and return the root node
165
     */
166
    public getRoot() {
167
        let node = this as AstNode;
11,158✔
168

169
        while (node) {
11,158✔
170
            if (!node.parent) {
64,085✔
171
                return node;
11,158✔
172
            }
173
            node = node.parent;
52,927✔
174
        }
175
    }
176

177

178
    /**
179
     * Gets all the trivia (comments, whitespace) that is directly before the start of this node
180
     * Note: this includes all trivia that might start on the line of the previous node
181
     */
182
    public get leadingTrivia(): Token[] {
183
        return [];
6,177✔
184
    }
185

186
    /**
187
     * Gets any trivia that is directly before the end of the node
188
     * For example, this would return all trivia before a `end function` token of a FunctionExpression
189
     */
190
    public get endTrivia(): Token[] {
191
        return [];
35✔
192
    }
193

194
    public getBsConsts() {
195
        return this.bsConsts ?? this.parent?.getBsConsts();
84,404✔
196
    }
197

198
    /**
199
     * Clone this node and all of its children. This creates a completely detached and identical copy of the AST.
200
     * All tokens, statements, expressions, range, and location are cloned.
201
     */
202
    public abstract clone();
203

204
    /**
205
     * Helper function for creating a clone. This will clone any attached annotations, as well as reparent the cloned node's children to the clone
206
     */
207
    protected finalizeClone<T extends AstNode>(
208
        clone: T,
209
        propsToReparent?: Array<{ [K in keyof T]: T[K] extends AstNode | AstNode[] ? K : never }[keyof T]>
210
    ) {
211
        //clone the annotations if they exist
212
        if (Array.isArray((this as unknown as Statement).annotations)) {
927✔
213
            (clone as unknown as Statement).annotations = (this as unknown as Statement).annotations?.map(x => x.clone());
7!
214
        }
215
        //reparent all of the supplied props
216
        for (let key of propsToReparent ?? []) {
927✔
217
            const children = (Array.isArray(clone?.[key]) ? clone[key] : [clone?.[key]]) as any[];
991!
218
            for (let child of children ?? []) {
991!
219
                if (child) {
1,013✔
220
                    (clone[key as any] as AstNode).parent = clone;
770✔
221
                }
222
            }
223
        }
224

225
        //reapply the location if we have one but the clone doesn't
226
        if (!clone.location && this.location) {
927✔
227
            clone.location = util.cloneLocation(this.location);
25✔
228
        }
229
        return clone;
927✔
230
    }
231
}
232

233
export abstract class Statement extends AstNode {
1✔
234
    /**
235
     * When being considered by the walk visitor, this describes what type of element the current class is.
236
     */
237
    public visitMode = InternalWalkMode.visitStatements;
31,374✔
238
    /**
239
     * Annotations for this statement
240
     */
241
    public annotations?: AnnotationExpression[] | undefined;
242
}
243

244

245
/** A BrightScript expression */
246
export abstract class Expression extends AstNode {
1✔
247
    /**
248
     * When being considered by the walk visitor, this describes what type of element the current class is.
249
     */
250
    public visitMode = InternalWalkMode.visitExpressions;
36,696✔
251
}
252

253
export enum AstNodeKind {
1✔
254
    Body = 'Body',
1✔
255
    BinaryExpression = 'BinaryExpression',
1✔
256
    CallExpression = 'CallExpression',
1✔
257
    FunctionExpression = 'FunctionExpression',
1✔
258
    FunctionParameterExpression = 'FunctionParameterExpression',
1✔
259
    NamespacedVariableNameExpression = 'NamespacedVariableNameExpression',
1✔
260
    DottedGetExpression = 'DottedGetExpression',
1✔
261
    XmlAttributeGetExpression = 'XmlAttributeGetExpression',
1✔
262
    IndexedGetExpression = 'IndexedGetExpression',
1✔
263
    GroupingExpression = 'GroupingExpression',
1✔
264
    LiteralExpression = 'LiteralExpression',
1✔
265
    EscapedCharCodeLiteralExpression = 'EscapedCharCodeLiteralExpression',
1✔
266
    ArrayLiteralExpression = 'ArrayLiteralExpression',
1✔
267
    AAMemberExpression = 'AAMemberExpression',
1✔
268
    AALiteralExpression = 'AALiteralExpression',
1✔
269
    UnaryExpression = 'UnaryExpression',
1✔
270
    VariableExpression = 'VariableExpression',
1✔
271
    SourceLiteralExpression = 'SourceLiteralExpression',
1✔
272
    NewExpression = 'NewExpression',
1✔
273
    CallfuncExpression = 'CallfuncExpression',
1✔
274
    TemplateStringQuasiExpression = 'TemplateStringQuasiExpression',
1✔
275
    TemplateStringExpression = 'TemplateStringExpression',
1✔
276
    TaggedTemplateStringExpression = 'TaggedTemplateStringExpression',
1✔
277
    AnnotationExpression = 'AnnotationExpression',
1✔
278
    TernaryExpression = 'TernaryExpression',
1✔
279
    NullCoalescingExpression = 'NullCoalescingExpression',
1✔
280
    RegexLiteralExpression = 'RegexLiteralExpression',
1✔
281
    EmptyStatement = 'EmptyStatement',
1✔
282
    AssignmentStatement = 'AssignmentStatement',
1✔
283
    ExpressionStatement = 'ExpressionStatement',
1✔
284
    ExitStatement = 'ExitStatement',
1✔
285
    FunctionStatement = 'FunctionStatement',
1✔
286
    IfStatement = 'IfStatement',
1✔
287
    IncrementStatement = 'IncrementStatement',
1✔
288
    PrintStatement = 'PrintStatement',
1✔
289
    DimStatement = 'DimStatement',
1✔
290
    GotoStatement = 'GotoStatement',
1✔
291
    LabelStatement = 'LabelStatement',
1✔
292
    ReturnStatement = 'ReturnStatement',
1✔
293
    EndStatement = 'EndStatement',
1✔
294
    StopStatement = 'StopStatement',
1✔
295
    ForStatement = 'ForStatement',
1✔
296
    ForEachStatement = 'ForEachStatement',
1✔
297
    WhileStatement = 'WhileStatement',
1✔
298
    DottedSetStatement = 'DottedSetStatement',
1✔
299
    IndexedSetStatement = 'IndexedSetStatement',
1✔
300
    LibraryStatement = 'LibraryStatement',
1✔
301
    NamespaceStatement = 'NamespaceStatement',
1✔
302
    ImportStatement = 'ImportStatement',
1✔
303
    InterfaceStatement = 'InterfaceStatement',
1✔
304
    InterfaceFieldStatement = 'InterfaceFieldStatement',
1✔
305
    InterfaceMethodStatement = 'InterfaceMethodStatement',
1✔
306
    ClassStatement = 'ClassStatement',
1✔
307
    MethodStatement = 'MethodStatement',
1✔
308
    ClassMethodStatement = 'ClassMethodStatement',
1✔
309
    FieldStatement = 'FieldStatement',
1✔
310
    ClassFieldStatement = 'ClassFieldStatement',
1✔
311
    TryCatchStatement = 'TryCatchStatement',
1✔
312
    CatchStatement = 'CatchStatement',
1✔
313
    ThrowStatement = 'ThrowStatement',
1✔
314
    EnumStatement = 'EnumStatement',
1✔
315
    EnumMemberStatement = 'EnumMemberStatement',
1✔
316
    ConstStatement = 'ConstStatement',
1✔
317
    ContinueStatement = 'ContinueStatement',
1✔
318
    Block = 'Block',
1✔
319
    TypeExpression = 'TypeExpression',
1✔
320
    TypecastExpression = 'TypecastExpression',
1✔
321
    TypedArrayExpression = 'TypedArrayExpression',
1✔
322
    TypecastStatement = 'TypecastStatement',
1✔
323
    AliasStatement = 'AliasStatement',
1✔
324
    ConditionalCompileStatement = 'ConditionalCompileStatement',
1✔
325
    ConditionalCompileConstStatement = 'ConditionalCompileConstStatement',
1✔
326
    ConditionalCompileErrorStatement = 'ConditionalCompileErrorStatement',
1✔
327
    AugmentedAssignmentStatement = 'AugmentedAssignmentStatement'
1✔
328
}
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