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

rokucommunity / brighterscript / #13186

15 Oct 2024 11:38AM UTC coverage: 89.043% (+2.2%) from 86.831%
#13186

push

web-flow
Merge 2b9d8bd39 into 1519a87aa

7212 of 8538 branches covered (84.47%)

Branch coverage included in aggregate %.

10 of 10 new or added lines in 1 file covered. (100.0%)

539 existing lines in 53 files now uncovered.

9619 of 10364 relevant lines covered (92.81%)

1781.3 hits per line

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

87.88
/src/bscPlugin/hover/HoverProcessor.ts
1
import { isBrsFile, isFunctionType, isXmlFile } from '../../astUtils/reflection';
1✔
2
import type { BrsFile } from '../../files/BrsFile';
3
import type { XmlFile } from '../../files/XmlFile';
4
import type { Hover, ProvideHoverEvent } from '../../interfaces';
5
import type { Token } from '../../lexer/Token';
6
import { TokenKind } from '../../lexer/TokenKind';
1✔
7
import { BrsTranspileState } from '../../parser/BrsTranspileState';
1✔
8
import { ParseMode } from '../../parser/Parser';
1✔
9
import util from '../../util';
1✔
10

11
export class HoverProcessor {
1✔
12
    public constructor(
13
        public event: ProvideHoverEvent
30✔
14
    ) {
15

16
    }
17

18
    public process() {
19
        let hover: Hover | undefined;
20
        if (isBrsFile(this.event.file)) {
30!
21
            hover = this.getBrsFileHover(this.event.file);
30✔
UNCOV
22
        } else if (isXmlFile(this.event.file)) {
×
UNCOV
23
            hover = this.getXmlFileHover(this.event.file);
×
24
        }
25

26
        //if we got a result, "return" it
27
        if (hover) {
30✔
28
            //assign the hover to the event
29
            this.event.hovers.push(hover);
26✔
30
        }
31
    }
32

33
    private buildContentsWithDocs(text: string, startingToken: Token) {
34
        const parts = [text];
20✔
35
        const docs = this.getTokenDocumentation((this.event.file as BrsFile).parser.tokens, startingToken);
20✔
36
        if (docs) {
20✔
37
            parts.push('***', docs);
2✔
38
        }
39
        return parts.join('\n');
20✔
40
    }
41

42
    private getBrsFileHover(file: BrsFile): Hover | undefined {
43
        const scope = this.event.scopes[0];
30✔
44
        const fence = (code: string) => util.mdFence(code, 'brightscript');
30✔
45
        //get the token at the position
46
        let token = file.getTokenAt(this.event.position);
30✔
47

48
        let hoverTokenTypes = [
30✔
49
            TokenKind.Identifier,
50
            TokenKind.Function,
51
            TokenKind.EndFunction,
52
            TokenKind.Sub,
53
            TokenKind.EndSub
54
        ];
55

56
        //throw out invalid tokens and the wrong kind of tokens
57
        if (!token || !hoverTokenTypes.includes(token.kind)) {
30✔
58
            return undefined;
4✔
59
        }
60

61
        const expression = file.getClosestExpression(this.event.position);
26✔
62
        if (expression?.range) {
26!
63
            let containingNamespace = file.getNamespaceStatementForPosition(expression.range.start)?.getName(ParseMode.BrighterScript);
26✔
64
            const fullName = util.getAllDottedGetParts(expression)?.map(x => x.text).join('.');
30✔
65

66
            //find a constant with this name
67
            const constant = scope?.getConstFileLink(fullName, containingNamespace);
26!
68
            if (constant) {
26✔
69
                const constantValue = util.sourceNodeFromTranspileResult(null, null, null, constant.item.value.transpile(new BrsTranspileState(file))).toString();
5✔
70
                return {
5✔
71
                    contents: this.buildContentsWithDocs(fence(`const ${constant.item.fullName} = ${constantValue}`), constant.item.tokens.const),
72
                    range: token.range
73
                };
74
            }
75
        }
76

77
        let lowerTokenText = token.text.toLowerCase();
21✔
78

79
        //look through local variables first
80
        {
81
            //get the function scope for this position (if exists)
82
            let functionScope = file.getFunctionScopeAtPosition(this.event.position);
21✔
83
            if (functionScope) {
21!
84
                //find any variable with this name
85
                for (const varDeclaration of functionScope.variableDeclarations) {
21✔
86
                    //we found a variable declaration with this token text!
87
                    if (varDeclaration.name.toLowerCase() === lowerTokenText) {
21✔
88
                        let typeText: string;
89
                        if (isFunctionType(varDeclaration.type)) {
6✔
90
                            typeText = varDeclaration.type.toString();
2✔
91
                        } else {
92
                            typeText = `${varDeclaration.name} as ${varDeclaration.type.toString()}`;
4✔
93
                        }
94
                        return {
6✔
95
                            range: token.range,
96
                            //append the variable name to the front for scope
97
                            contents: fence(typeText)
98
                        };
99
                    }
100
                }
101
                for (const labelStatement of functionScope.labelStatements) {
15✔
UNCOV
102
                    if (labelStatement.name.toLocaleLowerCase() === lowerTokenText) {
×
103
                        return {
×
104
                            range: token.range,
105
                            contents: fence(`${labelStatement.name}: label`)
106
                        };
107
                    }
108
                }
109
            }
110
        }
111

112
        //look through all callables in relevant scopes
113
        for (let scope of this.event.scopes) {
15✔
114
            let callable = scope.getCallableByName(lowerTokenText);
15✔
115
            if (callable) {
15!
116
                return {
15✔
117
                    range: token.range,
118
                    contents: this.buildContentsWithDocs(fence(callable.type.toString()), callable.functionStatement?.func?.functionType)
90✔
119
                };
120
            }
121
        }
122
    }
123

124
    /**
125
     * Combine all the documentation found before a token (i.e. comment tokens)
126
     */
127
    private getTokenDocumentation(tokens: Token[], token?: Token) {
128
        const comments = [] as Token[];
20✔
129
        if (!token) {
20✔
130
            return undefined;
3✔
131
        }
132
        const idx = tokens?.indexOf(token);
17!
133
        if (!idx || idx === -1) {
17✔
134
            return undefined;
3✔
135
        }
136
        for (let i = idx - 1; i >= 0; i--) {
14✔
137
            const token = tokens[i];
42✔
138
            //skip whitespace and newline chars
139
            if (token.kind === TokenKind.Comment) {
42✔
140
                comments.push(token);
7✔
141
            } else if (token.kind === TokenKind.Newline || token.kind === TokenKind.Whitespace) {
35✔
142
                //skip these tokens
143
                continue;
25✔
144

145
                //any other token means there are no more comments
146
            } else {
147
                break;
10✔
148
            }
149
        }
150
        if (comments.length > 0) {
14✔
151
            return comments.reverse().map(x => x.text.replace(/^('|rem)/i, '')).join('\n');
7✔
152
        }
153
    }
154

155
    private getXmlFileHover(file: XmlFile): Hover | undefined {
156
        //TODO add xml hovers
UNCOV
157
        return undefined;
×
158
    }
159
}
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