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

rokucommunity / brighterscript / #14386

09 May 2025 11:44AM UTC coverage: 87.026% (-2.0%) from 89.017%
#14386

push

web-flow
Merge a194c3925 into 489231ac7

13731 of 16677 branches covered (82.33%)

Branch coverage included in aggregate %.

8175 of 8874 new or added lines in 103 files covered. (92.12%)

85 existing lines in 22 files now uncovered.

14603 of 15881 relevant lines covered (91.95%)

20322.22 hits per line

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

92.96
/src/bscPlugin/semanticTokens/BrsFileSemanticTokensProcessor.ts
1
import { SemanticTokenModifiers } from 'vscode-languageserver-protocol';
1✔
2
import { SemanticTokenTypes } from 'vscode-languageserver-protocol';
1✔
3
import { isCallableType, isClassType, isComponentType, isConstStatement, isDottedGetExpression, isEnumMemberType, isEnumType, isFunctionExpression, isFunctionStatement, isInterfaceType, isNamespaceType, isPrimitiveType, isVariableExpression } from '../../astUtils/reflection';
1✔
4
import type { BrsFile } from '../../files/BrsFile';
5
import type { ExtraSymbolData, OnGetSemanticTokensEvent, SemanticToken, TypeChainEntry } from '../../interfaces';
6
import type { Locatable, Token } from '../../lexer/Token';
7
import util from '../../util';
1✔
8
import { SymbolTypeFlag } from '../../SymbolTypeFlag';
1✔
9
import type { BscType } from '../../types/BscType';
10
import { WalkMode, createVisitor } from '../../astUtils/visitors';
1✔
11
import type { AstNode } from '../../parser/AstNode';
12

13
export class BrsFileSemanticTokensProcessor {
1✔
14
    public constructor(
15
        public event: OnGetSemanticTokensEvent<BrsFile>
25✔
16
    ) {
17

18
    }
19

20
    public process() {
21
        const scope = this.event.scopes[0];
25✔
22
        this.result.clear();
25✔
23
        if (scope) {
25!
24
            scope.linkSymbolTable();
25✔
25
        }
26

27
        this.event.file.ast.walk(createVisitor({
25✔
28
            VariableExpression: (node) => {
29
                this.tryAddToken(node, node.tokens.name);
66✔
30
            },
31
            AssignmentStatement: (node) => {
32
                this.addToken(node.tokens.name, SemanticTokenTypes.variable);
9✔
33
            },
34
            DottedGetExpression: (node) => {
35
                this.tryAddToken(node, node.tokens.name);
41✔
36
            },
37
            ConstStatement: (node) => {
38
                this.tryAddToken(node, node.tokens.name);
4✔
39
            },
40
            AliasStatement: (node) => {
41
                this.tryAddToken(node, node.tokens.name);
1✔
42
            },
43
            ClassStatement: (node) => {
44
                this.tryAddToken(node, node.tokens.name);
6✔
45
            },
46
            InterfaceStatement: (node) => {
47
                this.tryAddToken(node, node.tokens.name);
3✔
48
            },
49
            EnumStatement: (node) => {
50
                this.tryAddToken(node, node.tokens.name);
3✔
51
            },
52
            FunctionStatement: (node) => {
53
                this.tryAddToken(node, node.tokens.name);
27✔
54
            },
55
            FunctionParameterExpression: (node) => {
56
                this.addToken(node.tokens.name, SemanticTokenTypes.parameter);
10✔
57
            }
58
        }), {
59
            walkMode: WalkMode.visitAllRecursive
60
        });
61

62
        if (scope) {
25!
63
            scope.unlinkSymbolTable();
25✔
64
        }
65

66
        //add all tokens to the event
67
        this.event.semanticTokens.push(
25✔
68
            ...this.result.values()
69
        );
70
    }
71

72
    private result = new Map<string, SemanticToken>();
25✔
73

74

75
    /**
76
     * Add the given token and node IF we have a resolvable type
77
     */
78
    private tryAddToken(node: AstNode, token: Token) {
79
        const extraData = {} as ExtraSymbolData;
151✔
80
        const chain = [] as TypeChainEntry[];
151✔
81
        // eslint-disable-next-line no-bitwise
82
        const symbolType = node.getType({ flags: SymbolTypeFlag.runtime, data: extraData, typeChain: chain });
151✔
83
        if (symbolType?.isResolvable()) {
151!
84
            let info = this.getSemanticTokenInfo(node, symbolType, extraData);
147✔
85
            if (info) {
147✔
86
                //mark as deprecated if applicable
87
                if ((extraData.flags & SymbolTypeFlag.deprecated)) { // eslint-disable-line no-bitwise
136✔
88
                    info.modifiers ??= [];
1!
89
                    info.modifiers.push(SemanticTokenModifiers.deprecated);
1✔
90
                }
91
                this.addToken(token, info.type, info.modifiers);
136✔
92
            }
93
        }
94
    }
95

96
    private addToken(locatable: Locatable, type: SemanticTokenTypes, modifiers: SemanticTokenModifiers[] = []) {
146✔
97
        //only keep a single token per range. Last-in wins
98
        this.result.set(util.rangeToString(locatable.location.range), {
155✔
99
            range: locatable.location.range,
100
            tokenType: type,
101
            tokenModifiers: modifiers
102
        });
103
    }
104

105
    private getSemanticTokenInfo(node: AstNode, type: BscType, extraData: ExtraSymbolData): { type: SemanticTokenTypes; modifiers?: SemanticTokenModifiers[] } {
106
        if (isConstStatement(extraData?.definingNode)) {
147!
107
            return { type: SemanticTokenTypes.variable, modifiers: [SemanticTokenModifiers.readonly, SemanticTokenModifiers.static] };
4✔
108
            // non-instances of classes should be colored like classes
109
        } else if (isClassType(type) && extraData.isInstance !== true) {
143✔
110
            return { type: SemanticTokenTypes.class };
10✔
111
            //function statements and expressions
112
        } else if (isCallableType(type)) {
133✔
113
            //if the typetime type is a class, then color this like a class
114
            const typetimeType = node.getType({ flags: SymbolTypeFlag.typetime });
41✔
115
            if (isClassType(typetimeType)) {
41✔
116
                return { type: SemanticTokenTypes.class };
1✔
117
            }
118

119
            //if this is a function statement or expression, treat it as a function
120
            if (isFunctionExpression(node) || isFunctionStatement(node)) {
40✔
121
                return { type: SemanticTokenTypes.function };
27✔
122
            }
123
            if (
13✔
124
                //if this is a standalone function
125
                isVariableExpression(node) ||
31✔
126
                //if this is a dottedGet, and the LHS is a namespace, treat it as a function.
127
                (isDottedGetExpression(node) && isNamespaceType(node.obj.getType({ flags: SymbolTypeFlag.typetime })))
128
            ) {
129
                return { type: SemanticTokenTypes.function };
10✔
130

131
                //all others should be treated as methods
132
            } else {
133
                return { type: SemanticTokenTypes.method };
3✔
134
            }
135
        } else if (isInterfaceType(type) && extraData.isInstance !== true) {
92✔
136
            return { type: SemanticTokenTypes.interface };
3✔
137
        } else if (isComponentType(type) && extraData.isInstance !== true) {
89!
NEW
138
            return { type: SemanticTokenTypes.class };
×
139
        } else if (isEnumType(type)) {
89✔
140
            return { type: SemanticTokenTypes.enum };
6✔
141
        } else if (isEnumMemberType(type)) {
83✔
142
            return { type: SemanticTokenTypes.enumMember };
2✔
143
        } else if (isNamespaceType(type)) {
81✔
144
            return { type: SemanticTokenTypes.namespace };
57✔
145
            //this is separate from the checks above because we want to resolve alias lookups before turning this variable into a const
146
        } else if (isConstStatement(node)) {
24✔
147
            return { type: SemanticTokenTypes.variable, modifiers: [SemanticTokenModifiers.readonly, SemanticTokenModifiers.static] };
4✔
148
        } else if (util.isInTypeExpression(node) && isPrimitiveType(type)) {
20✔
149
            // primitive types in type expressions should be already properly colored. do not add a semantic token
150
            return undefined;
8✔
151
        } else if (isVariableExpression(node)) {
12!
152
            if (/m/i.test(node.tokens?.name?.text)) {
12!
153
                //don't color `m` variables
154
            } else {
155
                return { type: SemanticTokenTypes.variable };
9✔
156
            }
157
        } else {
158
            //we don't know what it is...return undefined to prevent creating a semantic token
159
        }
160
    }
161
}
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