• 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

89.02
/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!
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!
28
            return undefined;
×
29
        }
30

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

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

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

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

63
        if (ranges.length === 0) {
14!
UNCOV
64
            return undefined;
×
65
        }
66

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

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