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

rokucommunity / brighterscript / #14310

01 May 2025 07:04PM UTC coverage: 87.105% (-1.9%) from 89.017%
#14310

push

web-flow
Merge 9f25468b6 into a742ceae5

13490 of 16372 branches covered (82.4%)

Branch coverage included in aggregate %.

8031 of 8709 new or added lines in 103 files covered. (92.21%)

84 existing lines in 22 files now uncovered.

14463 of 15719 relevant lines covered (92.01%)

19992.96 hits per line

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

77.15
/src/types/BuiltInInterfaceAdder.ts
1
import type { BRSInterfaceData, BRSInterfaceMethodData, SGNodeData } from '../roku-types';
2
import { components, events, interfaces, nodes } from '../roku-types';
1✔
3
import { Cache } from '../Cache';
1✔
4
import type { TypedFunctionType } from './TypedFunctionType';
5
import type { SymbolTable } from '../SymbolTable';
6
import { SymbolTypeFlag } from '../SymbolTypeFlag';
1✔
7
import type { BscType } from './BscType';
8
import { isArrayType, isAssociativeArrayType, isBooleanType, isCallableType, isComponentType, isDoubleType, isEnumMemberType, isFloatType, isIntegerType, isInterfaceType, isInvalidType, isLongIntegerType, isStringType } from '../astUtils/reflection';
1✔
9
import type { ComponentType } from './ComponentType';
10
import { util } from '../util';
1✔
11
import type { UnionType } from './UnionType';
12
import type { ExtraSymbolData } from '../interfaces';
13

14

15
export interface BuiltInInterfaceOverride {
16
    type?: BscType;
17
    parameterTypes?: BscType[];
18
    returnType?: BscType;
19
}
20

21
const builtInSymbolData: ExtraSymbolData = {
1✔
22
    completionPriority: 1,
23
    isBuiltIn: true
24
};
25

26
// eslint-disable-next-line @typescript-eslint/no-extraneous-class
27
export class BuiltInInterfaceAdder {
1✔
28

29
    static readonly primitiveTypeInstanceCache = new Cache<string, BscType>();
1✔
30

31
    static typedFunctionFactory: (type: BscType) => TypedFunctionType;
32
    static unionTypeFactory: (types: BscType[]) => UnionType;
33

34
    static getLookupTable: () => SymbolTable;
35

36
    static addBuiltInInterfacesToType(thisType: BscType, overrides?: Map<string, BuiltInInterfaceOverride>) {
37
        const builtInMemberTable = thisType.getBuiltInMemberTable();
534,362✔
38
        if (!builtInMemberTable) {
534,362✔
39
            // no memberTable to add to
40
            // this could be because it's a class that has a parent
41
            // the original ancestor should get the built ins
42
            return;
178,977✔
43
        }
44
        //const realMemberTable = thisType.getMemberTable();
45
        //const checkForExistingMembers = realMemberTable && realMemberTable !== builtInMemberTable;
46
        const builtInComponent = this.getMatchingRokuComponent(thisType);
355,385✔
47
        if (!builtInComponent) {
355,385✔
48
            // TODO: Perhaps have error here, but docs have some references to unknown types
49
            //throw new Error(`Unknown Roku component '${this.getMatchingRokuComponentName(thisType)}' for type '${thisType.toString()}'`);
50
            return;
3,974✔
51
        }
52
        if (!this.typedFunctionFactory) {
351,411!
NEW
53
            throw new Error(`Unable to build typed functions - no typed function factory`);
×
54
        }
55
        const interfacesToLoop = builtInComponent.interfaces ?? [builtInComponent];
351,411✔
56

57
        //add any direct methods from this component to the member table
58
        if (this.isBrightScriptComponent(thisType)) {
351,411✔
59
            for (const method of builtInComponent.methods ?? []) {
143,301✔
60
                const methodFuncType = this.buildMethodFromDocData(method, overrides, thisType);
1,982✔
61
                let flags = SymbolTypeFlag.runtime;
1,982✔
62
                //set the deprecated flag if applicable
63
                if ((method as any).isDeprecated) {
1,982!
64
                    flags |= SymbolTypeFlag.deprecated; // eslint-disable-line no-bitwise
1,982✔
65
                }
66
                builtInMemberTable.addSymbol(method.name, { ...builtInSymbolData, description: method.description }, methodFuncType, flags);
1,982✔
67
            }
68
        }
69

70
        for (const iface of interfacesToLoop) {
351,411✔
71
            const lowerIfaceName = iface.name.toLowerCase();
586,447✔
72
            const ifaceData = (interfaces[lowerIfaceName] ?? events[lowerIfaceName]) as BRSInterfaceData;
586,447✔
73

74
            if (builtInComponent.interfaces) {
586,447✔
75
                // this type has interfaces - add them directly as members
76
                const ifaceType = this.getLookupTable()?.getSymbolType(iface.name, { flags: SymbolTypeFlag.typetime });
378,337!
77
                if (ifaceType) {
378,337!
78
                    builtInMemberTable.addSymbol(iface.name, { ...builtInSymbolData }, ifaceType, SymbolTypeFlag.runtime);
378,337✔
79
                }
80
            }
81

82
            for (const method of ifaceData.methods ?? []) {
586,447!
83
                if (ifaceData.name.toLowerCase() === 'ifintops' && method.name.toLowerCase() === 'tostr') {
4,321,973✔
84
                    // handle special case - this messed up the .toStr() method on integers
85
                    continue;
4,025✔
86
                }
87
                const methodFuncType = this.buildMethodFromDocData(method, overrides, thisType);
4,317,948✔
88
                builtInMemberTable.addSymbol(method.name, { ...builtInSymbolData, description: method.description }, methodFuncType, SymbolTypeFlag.runtime);
4,317,948✔
89
            }
90
            for (const property of ifaceData.properties ?? []) {
586,447!
NEW
91
                const override = overrides?.get(property.name.toLowerCase());
×
NEW
92
                builtInMemberTable.addSymbol(property.name, { ...builtInSymbolData, description: property.description }, override?.type ??
×
93
                    this.getPrimitiveType(property.type) ?? this.getPrimitiveType('dynamic'), SymbolTypeFlag.runtime);
×
94
            }
95
        }
96
    }
97

98
    private static builtInMethodCache = new Cache<BRSInterfaceMethodData, TypedFunctionType>();
1✔
99

100
    private static buildMethodFromDocData(method: BRSInterfaceMethodData, overrides?: Map<string, BuiltInInterfaceOverride>, thisType?: BscType): TypedFunctionType {
101
        const override = overrides?.get(method.name.toLowerCase());
4,319,930✔
102
        const getMethodType = () => {
4,319,930✔
103

104
            let returnType = override?.returnType ?? this.getPrimitiveType(method.returnType);
1,106✔
105
            if (!returnType && method.returnType.toLowerCase() === (thisType as any)?.name?.toLowerCase()) {
1,106!
NEW
106
                returnType = thisType;
×
107
            }
108
            const methodFuncType = this.typedFunctionFactory(returnType);
1,106✔
109
            methodFuncType.name = method.name;
1,106✔
110
            methodFuncType.isVariadic = method.isVariadic ?? false;
1,106✔
111
            // eslint-disable-next-line @typescript-eslint/prefer-for-of
112
            for (let i = 0; i < method.params.length; i++) {
1,106✔
113
                const param = method.params[i];
651✔
114
                let paramType = override?.parameterTypes?.[i] ?? this.getPrimitiveType(param.type);
651✔
115
                if (!paramType && param.type.toLowerCase() === (thisType as any)?.name?.toLowerCase()) {
651!
NEW
116
                    paramType = thisType;
×
117
                }
118
                paramType ??= this.primitiveTypeInstanceCache.get('dynamic');
651!
119
                methodFuncType.addParameter(param.name, paramType, !param.isRequired);
651✔
120
            }
121
            return methodFuncType;
1,106✔
122
        };
123

124
        if (override) {
4,319,930✔
125
            return getMethodType();
343✔
126
        }
127

128
        return this.builtInMethodCache.getOrAdd(method, getMethodType);
4,319,587✔
129
    }
130

131
    private static getPrimitiveType(typeName: string): BscType {
132
        if (typeName.includes(' or ')) {
1,430✔
133
            if (!this.unionTypeFactory) {
8!
NEW
134
                throw new Error(`Unable to build union types - no union type factory`);
×
135
            }
136
            // union types!
137
            const unionOfTypeNames = typeName.split(' or ');
8✔
138
            return this.unionTypeFactory(unionOfTypeNames.map(name => this.getPrimitiveType(name)));
16✔
139
        }
140
        const returnType = this.primitiveTypeInstanceCache.get(typeName.toLowerCase());
1,422✔
141
        if (!returnType) {
1,422✔
142
            if (!this.getLookupTable) {
38!
NEW
143
                throw new Error(`Unable to find type instance '${typeName}'`);
×
144
            }
145
            return this.getLookupTable()?.getSymbolType(typeName, { flags: SymbolTypeFlag.typetime, fullName: typeName, tableProvider: this.getLookupTable });
38!
146
        }
147

148
        return returnType;
1,384✔
149
    }
150

151
    static getMatchingRokuComponentName(theType: BscType) {
152
        if (isStringType(theType)) {
707,260✔
153
            return 'roString';
214✔
154
        } else if (isIntegerType(theType)) {
707,046✔
155
            return 'roInt';
120✔
156
        } else if (isBooleanType(theType)) {
706,926✔
157
            return 'roBoolean';
10✔
158
        } else if (isFloatType(theType)) {
706,916✔
159
            return 'roFloat';
12✔
160
        } else if (isDoubleType(theType)) {
706,904✔
161
            return 'roDouble';
6✔
162
        } else if (isLongIntegerType(theType)) {
706,898✔
163
            return 'roLongInteger';
2✔
164
        } else if (isInvalidType(theType)) {
706,896!
NEW
165
            return 'roInvalid';
×
166
        } else if (isCallableType(theType)) {
706,896✔
167
            return 'roFunction';
22✔
168
        } else if (isAssociativeArrayType(theType)) {
706,874✔
169
            return 'roAssociativeArray';
644✔
170
        } else if (isArrayType(theType)) {
706,230✔
171
            return 'roArray';
100✔
172
        } else if (isEnumMemberType(theType)) {
706,130✔
173
            return this.getMatchingRokuComponentName(theType.underlyingType);
464✔
174
        } else if (isInterfaceType(theType)) {
705,666✔
175
            return theType.name;
676,063✔
176
        } else if (isComponentType(theType)) {
29,603✔
177
            return 'roSGNode';
27,804✔
178
        }
179
    }
180

181
    static isBrightScriptComponent(theType: BscType) {
182
        const componentName = this.getMatchingRokuComponentName(theType);
351,411✔
183
        if (!componentName) {
351,411!
184
            // No component matches the given type
NEW
185
            return;
×
186
        }
187
        const lowerComponentName = componentName.toLowerCase();
351,411✔
188
        return !!components[lowerComponentName];
351,411✔
189
    }
190

191
    //the return type is a union of the three data types. Just pick the first item from each collection, as every item in the collection should have the same shape
192
    static getMatchingRokuComponent(theType: BscType): typeof components['roappinfo'] & typeof interfaces['ifappinfo'] & typeof events['rourlevent'] {
193
        const componentName = this.getMatchingRokuComponentName(theType);
355,385✔
194
        if (!componentName) {
355,385✔
195
            // No component matches the given type
196
            return;
1,799✔
197
        }
198
        const lowerComponentName = componentName.toLowerCase();
353,586✔
199
        return components[lowerComponentName] ?? interfaces[lowerComponentName] ?? events[lowerComponentName];
353,586✔
200
    }
201

202
    static addBuiltInFieldsToNodeType(thisType: ComponentType) {
203
        const nodeName = thisType.name;
192,777✔
204
        const memberTable = thisType.memberTable;
192,777✔
205
        if (!memberTable) {
192,777!
206
            // no memberTable to add to
NEW
207
            return;
×
208
        }
209
        const builtInNode = nodes[nodeName.toLowerCase()] as SGNodeData;
192,777✔
210
        if (!builtInNode) {
192,777✔
211
            return;
522✔
212
        }
213
        if (!this.typedFunctionFactory) {
192,255!
NEW
214
            throw new Error(`Unable to build typed functions - no typed function factory`);
×
215
        }
216
        const lookupTable = this.getLookupTable();
192,255✔
217
        for (const field of builtInNode.fields) {
192,255✔
218
            memberTable.addSymbol(field.name, { ...builtInSymbolData, description: field.description }, util.getNodeFieldType(field.type, lookupTable), SymbolTypeFlag.runtime);
1,781,832✔
219
        }
220
        for (const method of builtInNode.methods ?? []) {
192,255!
NEW
221
            const methodFuncType = this.buildMethodFromDocData(method, null, thisType);
×
NEW
222
            let flags = SymbolTypeFlag.runtime;
×
223
            //set the deprecated flag if applicable
NEW
224
            if (method.isDeprecated) {
×
NEW
225
                flags |= SymbolTypeFlag.deprecated; // eslint-disable-line no-bitwise
×
226
            }
NEW
227
            memberTable.addSymbol(method.name, { ...builtInSymbolData, description: method.description }, methodFuncType, flags);
×
228
        }
229
    }
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