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

rokucommunity / brighterscript / #15048

01 Jan 2026 11:17PM UTC coverage: 87.048% (-0.9%) from 87.907%
#15048

push

web-flow
Merge 02ba2bb57 into 2ea4d2108

14498 of 17595 branches covered (82.4%)

Branch coverage included in aggregate %.

192 of 261 new or added lines in 12 files covered. (73.56%)

897 existing lines in 48 files now uncovered.

15248 of 16577 relevant lines covered (91.98%)

24112.76 hits per line

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

93.38
/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, isDynamicType, 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>
26✔
16
    ) {
17

18
    }
19

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

27
        this.event.file.ast.walk(createVisitor({
26✔
28
            VariableExpression: (node) => {
29
                this.tryAddToken(node, node.tokens.name);
70✔
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);
28✔
54
            },
55
            FunctionParameterExpression: (node) => {
56
                this.addToken(node.tokens.name, SemanticTokenTypes.parameter);
12✔
57
            },
58
            TypeStatement: (node) => {
UNCOV
59
                this.tryAddToken(node, node.tokens.name);
×
60
            }
61
        }), {
62
            walkMode: WalkMode.visitAllRecursive
63
        });
64

65
        if (scope) {
26!
66
            scope.unlinkSymbolTable();
26✔
67
        }
68

69
        //add all tokens to the event
70
        this.event.semanticTokens.push(
26✔
71
            ...this.result.values()
72
        );
73
    }
74

75
    private result = new Map<string, SemanticToken>();
26✔
76

77

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

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

108
    private getSemanticTokenInfo(node: AstNode, type: BscType, extraData: ExtraSymbolData): { type: SemanticTokenTypes; modifiers?: SemanticTokenModifiers[] } {
109
        const isInTypeExpression = util.isInTypeExpression(node);
152✔
110

111
        if (isConstStatement(extraData?.definingNode)) {
152!
112
            return { type: SemanticTokenTypes.variable, modifiers: [SemanticTokenModifiers.readonly, SemanticTokenModifiers.static] };
4✔
113
            // non-instances of classes should be colored like classes
114
        } else if (isClassType(type) && extraData.isInstance !== true) {
148✔
115
            return { type: SemanticTokenTypes.class };
10✔
116
            //function statements and expressions
117
        } else if (!isInTypeExpression && isCallableType(type)) {
138✔
118
            //if the typetime type is a class, then color this like a class
119
            const typetimeType = node.getType({ flags: SymbolTypeFlag.typetime });
44✔
120
            if (isClassType(typetimeType)) {
44✔
121
                return { type: SemanticTokenTypes.class };
1✔
122
            }
123
            //if this is a function statement or expression, treat it as a function
124
            if (isFunctionExpression(node) || isFunctionStatement(node)) {
43✔
125
                return { type: SemanticTokenTypes.function };
28✔
126
            }
127
            if (
15✔
128
                //if this is a standalone function
129
                isVariableExpression(node) ||
33✔
130
                //if this is a dottedGet, and the LHS is a namespace, treat it as a function.
131
                (isDottedGetExpression(node) && isNamespaceType(node.obj.getType({ flags: SymbolTypeFlag.typetime })))
132
            ) {
133
                if (!isDynamicType(type)) {
12✔
134
                    return { type: SemanticTokenTypes.function };
10✔
135
                }
136

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