• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In
Build has been canceled!

rokucommunity / brighterscript / #13117

01 Oct 2024 08:24AM UTC coverage: 86.842% (-1.4%) from 88.193%
#13117

push

web-flow
Merge abd960cd5 into 3a2dc7282

11537 of 14048 branches covered (82.13%)

Branch coverage included in aggregate %.

6991 of 7582 new or added lines in 100 files covered. (92.21%)

83 existing lines in 18 files now uncovered.

12692 of 13852 relevant lines covered (91.63%)

29478.96 hits per line

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

83.62
/src/bscPlugin/hover/HoverProcessor.ts
1
import { isAssignmentStatement, isBrsFile, isCallfuncExpression, isClassStatement, isDottedGetExpression, isEnumMemberStatement, isEnumStatement, isEnumType, isInheritableType, isInterfaceStatement, isMemberField, isNamespaceStatement, isNamespaceType, isNewExpression, isTypedFunctionType, isXmlFile } from '../../astUtils/reflection';
1✔
2
import type { BrsFile } from '../../files/BrsFile';
3
import type { XmlFile } from '../../files/XmlFile';
4
import type { ExtraSymbolData, Hover, ProvideHoverEvent, TypeChainEntry } 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
import { SymbolTypeFlag } from '../../SymbolTypeFlag';
1✔
11
import type { AstNode, Expression } from '../../parser/AstNode';
12
import type { Scope } from '../../Scope';
13
import type { FunctionScope } from '../../FunctionScope';
14
import type { BscType } from '../../types/BscType';
15
import type { ClassStatement, EnumStatement, FieldStatement, InterfaceFieldStatement, InterfaceStatement } from '../../parser/Statement';
16

17
const fence = (code: string) => util.mdFence(code, 'brightscript');
62✔
18

19

20
export class HoverProcessor {
1✔
21
    public constructor(
22
        public event: ProvideHoverEvent
68✔
23
    ) {
24

25
    }
26

27
    public process() {
28
        let hover: Hover | undefined;
29
        if (isBrsFile(this.event.file)) {
67!
30
            hover = this.getBrsFileHover(this.event.file);
67✔
31
        } else if (isXmlFile(this.event.file)) {
×
32
            hover = this.getXmlFileHover(this.event.file);
×
33
        }
34

35
        //if we got a result, "return" it
36
        if (hover) {
67✔
37
            //assign the hover to the event
38
            this.event.hovers.push(hover);
62✔
39
        }
40
    }
41

42
    private buildContentsWithDocsFromDescription(text: string, docs: string) {
43
        const parts = [text];
2✔
44
        if (docs) {
2!
45
            parts.push('***', docs);
2✔
46
        }
47
        return parts.join('\n');
2✔
48
    }
49

50
    private buildContentsWithDocsFromExpression(text: string, expression: AstNode) {
51
        const parts = [text];
49✔
52
        const docs = util.getNodeDocumentation(expression);
49✔
53
        if (docs) {
49✔
54
            parts.push('***', docs);
7✔
55
        }
56
        return parts.join('\n');
49✔
57
    }
58

59
    private isValidTokenForHover(token: Token) {
60
        let hoverTokenTypes = [
67✔
61
            TokenKind.Identifier,
62
            TokenKind.Function,
63
            TokenKind.EndFunction,
64
            TokenKind.Sub,
65
            TokenKind.EndSub
66
        ];
67

68
        //throw out invalid tokens and the wrong kind of tokens
69
        return (token && hoverTokenTypes.includes(token.kind));
67✔
70
    }
71

72
    private getConstHover(token: Token, file: BrsFile, scope: Scope, expression: Expression) {
73
        let containingNamespace = file.getNamespaceStatementForPosition(expression?.location?.range?.start)?.getName(ParseMode.BrighterScript);
63!
74
        const fullName = util.getAllDottedGetParts(expression)?.map(x => x.text).join('.');
63✔
75

76
        //find a constant with this name
77
        const constant = scope?.getConstFileLink(fullName, containingNamespace);
63!
78
        if (constant) {
63✔
79
            const constantValue = util.sourceNodeFromTranspileResult(null, null, null, constant.item.value.transpile(new BrsTranspileState(file))).toString();
6✔
80
            const constantType = constant.item.getType({ flags: SymbolTypeFlag.runtime });
6✔
81
            return this.buildContentsWithDocsFromExpression(fence(`const ${constant.item.fullName} = ${constantValue} as ${constantType.toString()}`), constant.item);
6✔
82
        }
83
    }
84

85
    private getLabelHover(token: Token, functionScope: FunctionScope) {
86
        let lowerTokenText = token.text.toLowerCase();
47✔
87
        for (const labelStatement of functionScope.labelStatements) {
47✔
NEW
88
            if (labelStatement.name.toLocaleLowerCase() === lowerTokenText) {
×
NEW
89
                return fence(`${labelStatement.name}: label`);
×
90
            }
91
        }
92
    }
93

94
    private getCustomTypeHover(expressionType: BscType, extraData: ExtraSymbolData) {
95
        let declarationText = '';
5✔
96
        let exprTypeString = expressionType.toString();
5✔
97
        let firstToken: Token;
98
        if (extraData?.definingNode) {
5!
99
            if (isClassStatement(extraData.definingNode)) {
5✔
100
                firstToken = extraData.definingNode.tokens.class;
3✔
101
                declarationText = firstToken?.text ?? TokenKind.Class;
3!
102
            } else if (isInterfaceStatement(extraData.definingNode)) {
2!
NEW
103
                firstToken = extraData.definingNode.tokens.interface;
×
NEW
104
                declarationText = firstToken?.text ?? TokenKind.Interface;
×
105
            } else if (isNamespaceStatement(extraData.definingNode)) {
2✔
106
                firstToken = extraData.definingNode.tokens.namespace;
1✔
107
                exprTypeString = extraData.definingNode.getName(ParseMode.BrighterScript);
1✔
108
                declarationText = firstToken?.text ?? TokenKind.Namespace;
1!
109
            } else if (isEnumStatement(extraData.definingNode)) {
1!
110
                firstToken = extraData.definingNode.tokens.enum;
1✔
111
                exprTypeString = extraData.definingNode.fullName;
1✔
112
                declarationText = firstToken?.text ?? TokenKind.Enum;
1!
113
            }
114
        }
115
        const innerText = `${declarationText} ${exprTypeString}`.trim();
5✔
116
        let result = fence(innerText);
5✔
117
        return result;
5✔
118
    }
119

120
    private getMemberHover(memberExpression: FieldStatement | InterfaceFieldStatement, expressionType: BscType) {
121
        let nameText = `${(memberExpression.parent as ClassStatement | InterfaceStatement)?.getName(ParseMode.BrighterScript)}.${memberExpression.tokens.name.text}`;
7!
122
        let exprTypeString = expressionType.isResolvable() ? expressionType.toString() : 'invalid';
7!
123
        const innerText = `${nameText} as ${exprTypeString}`.trim();
7✔
124
        let result = fence(innerText);
7✔
125
        return result;
7✔
126
    }
127

128
    private getBrsFileHover(file: BrsFile): Hover {
129
        //get the token at the position
130
        let token = file.getTokenAt(this.event.position);
67✔
131

132
        if (!this.isValidTokenForHover(token)) {
67✔
133
            return null;
5✔
134
        }
135
        const expression = file.getClosestExpression(this.event.position);
62✔
136
        const hoverContents: string[] = [];
62✔
137
        for (let scope of this.event.scopes) {
62✔
138
            try {
62✔
139
                scope.linkSymbolTable();
62✔
140
                const constHover = this.getConstHover(token, file, scope, expression);
62✔
141
                if (constHover) {
62✔
142
                    hoverContents.push(constHover);
6✔
143
                    continue;
6✔
144
                }
145
                //get the function scope for this position (if exists)
146
                let functionScope = file.getFunctionScopeAtPosition(this.event.position);
56✔
147
                if (functionScope) {
56✔
148
                    const labelHover = this.getLabelHover(token, functionScope);
47✔
149
                    if (labelHover) {
47!
NEW
150
                        hoverContents.push(labelHover);
×
NEW
151
                        continue;
×
152
                    }
153
                }
154
                const isInTypeExpression = util.isInTypeExpression(expression);
56✔
155
                const typeFlag = isInTypeExpression ? SymbolTypeFlag.typetime : SymbolTypeFlag.runtime;
56✔
156
                const typeChain: TypeChainEntry[] = [];
56✔
157
                const extraData = {} as ExtraSymbolData;
56✔
158
                let exprType = expression.getType({ flags: typeFlag, typeChain: typeChain, data: extraData, ignoreCall: isCallfuncExpression(expression) });
56✔
159

160
                if (isAssignmentStatement(expression) && token === expression.tokens.name) {
56✔
161
                    // if this is an assignment, but we're really interested in the LHS, need to a symbol lookup
162
                    exprType = expression.getSymbolTable().getSymbolType(expression.tokens.name.text, { flags: typeFlag, typeChain: typeChain, data: extraData });
9✔
163
                }
164

165
                const processedTypeChain = util.processTypeChain(typeChain);
56✔
166
                const fullName = processedTypeChain.fullNameOfItem || token.text;
56✔
167
                // if the type chain has dynamic in it, then just say the token text
168
                const exprNameString = !processedTypeChain.containsDynamic ? fullName : token.text;
56✔
169
                const useCustomTypeHover = isInTypeExpression || expression?.findAncestor(isNewExpression);
56!
170
                let hoverContent = '';
56✔
171
                let descriptionNode;
172
                if (useCustomTypeHover && isInheritableType(exprType)) {
56✔
173
                    hoverContent = this.getCustomTypeHover(exprType, extraData);
3✔
174
                } else if (isMemberField(expression)) {
53✔
175
                    hoverContent = this.getMemberHover(expression, exprType);
7✔
176
                    descriptionNode = expression;
7✔
177
                } else if (isEnumMemberStatement(expression)) {
46✔
178
                    hoverContent = fence(`${(expression.parent as EnumStatement)?.name}.${expression.tokens.name.text} as ${exprType.toString()}`.trim());
1!
179
                    descriptionNode = expression;
1✔
180
                } else if (isNamespaceType(exprType) ||
45✔
181
                    isEnumType(expression.getType({ flags: SymbolTypeFlag.typetime }))) {
182
                    hoverContent = this.getCustomTypeHover(exprType, extraData);
2✔
183
                } else {
184
                    const variableName = !isTypedFunctionType(exprType) ? `${exprNameString} as ` : '';
43✔
185
                    if (isTypedFunctionType(exprType)) {
43✔
186
                        exprType.setName(exprNameString);
12✔
187
                    }
188
                    let exprTypeString = exprType.toString();
43✔
189
                    if (!exprType.isResolvable()) {
43✔
190
                        if (isDottedGetExpression(expression)) {
2✔
191
                            exprTypeString = 'invalid';
1✔
192
                        }
193
                    }
194
                    hoverContent = fence(`${variableName}${exprTypeString}`);
43✔
195
                }
196
                const modifiers = [];
56✔
197
                // eslint-disable-next-line no-bitwise
198
                if (extraData?.flags && extraData?.flags & SymbolTypeFlag.optional) {
56!
NEW
199
                    modifiers.push(TokenKind.Optional);
×
200
                }
201
                if (modifiers.length > 0) {
56!
NEW
202
                    hoverContent += `\n * (${modifiers.join(', ').toLowerCase()})* `;
×
203
                }
204

205
                if (descriptionNode) {
56✔
206
                    hoverContent = this.buildContentsWithDocsFromExpression(hoverContent, descriptionNode);
8✔
207
                } else if (extraData.description) {
48✔
208
                    hoverContent = this.buildContentsWithDocsFromDescription(hoverContent, extraData.description);
2✔
209
                } else if (extraData.definingNode) {
46✔
210
                    hoverContent = this.buildContentsWithDocsFromExpression(hoverContent, extraData.definingNode);
35✔
211
                }
212
                hoverContents.push(hoverContent);
56✔
213

214
            } finally {
215
                scope?.unlinkSymbolTable();
62!
216
            }
217
        }
218
        return {
62✔
219
            range: token?.location.range,
186!
220
            contents: hoverContents
221
        };
222
    }
223

224
    /**
225
     * @param file the file
226
     */
227
    private getXmlFileHover(file: XmlFile) {
228
        //TODO add xml hovers
229
        return undefined;
×
230
    }
231
}
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