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

rokucommunity / brighterscript / #15409

24 Mar 2026 03:24PM UTC coverage: 88.897% (-0.1%) from 88.993%
#15409

push

web-flow
Merge 8b8790702 into 0c894b16d

7928 of 9404 branches covered (84.3%)

Branch coverage included in aggregate %.

43 of 58 new or added lines in 7 files covered. (74.14%)

1 existing line in 1 file now uncovered.

10182 of 10968 relevant lines covered (92.83%)

1921.2 hits per line

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

89.71
/src/bscPlugin/selectionRanges/SelectionRangesProcessor.ts
1
import { SelectionRange } from 'vscode-languageserver-types';
1✔
2
import type { Range, Position } from 'vscode-languageserver-protocol';
3
import { isBrsFile } from '../../astUtils/reflection';
1✔
4
import type { ProvideSelectionRangesEvent } from '../../interfaces';
5
import type { AstNode } from '../../parser/AstNode';
6

7
export class SelectionRangesProcessor {
1✔
8
    public constructor(
9
        public event: ProvideSelectionRangesEvent
14✔
10
    ) { }
11

12
    public process() {
13
        if (!isBrsFile(this.event.file)) {
14!
NEW
14
            return;
×
15
        }
16

17
        for (const position of this.event.positions) {
14✔
18
            const selectionRange = this.buildSelectionRange(position);
15✔
19
            if (selectionRange) {
15✔
20
                this.event.selectionRanges.push(selectionRange);
14✔
21
            }
22
        }
23
    }
24

25
    private buildSelectionRange(position: Position): SelectionRange | undefined {
26
        const file = this.event.file;
15✔
27
        if (!isBrsFile(file)) {
15!
NEW
28
            return undefined;
×
29
        }
30

31
        // Find the deepest AST node containing this position
32
        const innerNode = file.ast.findChildAtPosition(position);
15✔
33
        if (!innerNode?.range) {
15✔
34
            return undefined;
1✔
35
        }
36

37
        const ranges: Range[] = [];
14✔
38

39
        // Many BrightScript AST nodes store identifier names as raw Tokens (not child
40
        // AstNodes), so findChildAtPosition stops at the enclosing statement/expression.
41
        // To give the first expansion step a tight "identifier only" range — matching
42
        // the JS/TS smart-select behaviour — we look up the exact lexer token at the
43
        // cursor position and use its range as the initial step.
44
        const token = file.getTokenAt(position);
14✔
45
        if (token?.range && !rangesEqual(token.range, innerNode.range)) {
14!
46
            ranges.push(token.range);
10✔
47
        }
48

49
        // Walk up the AST parent chain, collecting each node's range.
50
        // Skip duplicate consecutive ranges (e.g. a Block whose range equals its
51
        // only child statement, or an ExpressionStatement === its expression).
52
        let node: AstNode | undefined = innerNode;
14✔
53
        while (node) {
14✔
54
            const nodeRange = node.range;
82✔
55
            if (nodeRange && !rangesEqual(nodeRange, ranges[ranges.length - 1])) {
82✔
56
                ranges.push(nodeRange);
40✔
57
            }
58
            node = node.parent;
82✔
59
        }
60

61
        if (ranges.length === 0) {
14!
NEW
62
            return undefined;
×
63
        }
64

65
        // Build the SelectionRange linked list from outermost → innermost.
66
        // LSP: the innermost range has `.parent` pointing outward.
67
        let selectionRange: SelectionRange | undefined;
68
        for (let i = ranges.length - 1; i >= 0; i--) {
14✔
69
            selectionRange = SelectionRange.create(ranges[i], selectionRange);
50✔
70
        }
71
        return selectionRange;
14✔
72
    }
73
}
74

75
function rangesEqual(a: Range | undefined, b: Range | undefined): boolean {
76
    if (!a || !b) {
96✔
77
        return false;
4✔
78
    }
79
    return a.start.line === b.start.line &&
92✔
80
        a.start.character === b.start.character &&
81
        a.end.line === b.end.line &&
82
        a.end.character === b.end.character;
83
}
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