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

rokucommunity / brighterscript / #12930

13 Aug 2024 05:02PM UTC coverage: 86.193% (-1.7%) from 87.933%
#12930

push

web-flow
Merge 58ad447a2 into 0e968f1c3

10630 of 13125 branches covered (80.99%)

Branch coverage included in aggregate %.

6675 of 7284 new or added lines in 99 files covered. (91.64%)

84 existing lines in 18 files now uncovered.

12312 of 13492 relevant lines covered (91.25%)

26865.48 hits per line

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

80.12
/src/types/ReferenceType.ts
1
import type { GetTypeOptions, TypeChainEntry, TypeCompatibilityData } from '../interfaces';
2
import type { GetSymbolTypeOptions, SymbolTypeGetterProvider } from '../SymbolTable';
3
import type { SymbolTypeFlag } from '../SymbolTypeFlag';
4
import { isAnyReferenceType, isArrayDefaultTypeReferenceType, isArrayType, isBinaryOperatorReferenceType, isComponentType, isDynamicType, isReferenceType, isTypePropertyReferenceType } from '../astUtils/reflection';
1✔
5
import { BscType } from './BscType';
1✔
6
import { DynamicType } from './DynamicType';
1✔
7
import { BscTypeKind } from './BscTypeKind';
1✔
8
import type { Token } from '../lexer/Token';
9

10
export type AnyReferenceType = ReferenceType | TypePropertyReferenceType | BinaryOperatorReferenceType | ArrayDefaultTypeReferenceType;
11

12
export function referenceTypeFactory(memberKey: string, fullName, flags: SymbolTypeFlag, tableProvider: SymbolTypeGetterProvider) {
1✔
13
    return new ReferenceType(memberKey, fullName, flags, tableProvider);
157,613✔
14
}
15

16
export class ReferenceType extends BscType {
1✔
17

18
    /**
19
     * ReferenceTypes are used when the actual type may be resolved later from a Symbol table
20
     * @param memberKey which key do we use to look up this type in the given table?
21
     * @param fullName the full/display name for this type
22
     * @param flags is this type available at typetime, runtime, etc.
23
     * @param tableProvider function that returns a SymbolTable that we use for the lookup.
24
     */
25
    constructor(public memberKey: string, public fullName, public flags: SymbolTypeFlag, private tableProvider: SymbolTypeGetterProvider) {
158,546✔
26
        super(memberKey);
158,546✔
27
        // eslint-disable-next-line no-constructor-return
28
        return new Proxy(this, {
158,546✔
29
            get: (target, propName, receiver) => {
30

31
                if (propName === '__reflection') {
350,903✔
32
                    // Cheeky way to get `isReferenceType` reflection to work
33
                    return this.__reflection;
321,460✔
34
                }
35
                if (propName === '__identifier') {
29,443✔
36
                    // Cheeky way to get `isReferenceType` reflection to work
37
                    return this.__identifier;
37✔
38
                }
39
                if (propName === 'fullName') {
29,406✔
40
                    return this.fullName;
717✔
41
                }
42
                if (propName === 'isResolvable') {
28,689✔
43
                    return () => {
3,417✔
44
                        let resultSoFar = this.resolve();
3,417✔
45
                        while (resultSoFar && isReferenceType(resultSoFar)) {
3,417✔
NEW
46
                            resultSoFar = (resultSoFar as any).getTarget?.();
×
47
                        }
48
                        return !!resultSoFar;
3,417✔
49
                    };
50
                }
51
                if (propName === 'getTarget') {
25,272✔
52
                    return () => {
688✔
53
                        return this.resolve();
688✔
54
                    };
55
                }
56
                if (propName === 'tableProvider') {
24,584✔
57
                    return this.tableProvider;
18✔
58
                }
59
                if (propName === 'isEqual') {
24,566✔
60
                    //Need to be able to check equality without resolution, because resolution need to check equality
61
                    //To see if you need to make a UnionType
62
                    return (targetType: BscType, data?: TypeCompatibilityData) => {
63✔
63
                        if (!targetType) {
63!
NEW
64
                            return false;
×
65
                        }
66
                        const resolvedType = this.resolve();
63✔
67
                        let equal = false;
63✔
68
                        if (resolvedType && !isReferenceType(resolvedType)) {
63✔
69
                            equal = resolvedType.isEqual(targetType, data);
31✔
70
                        } else if (isReferenceType(targetType)) {
32✔
71
                            equal = this.fullName.toLowerCase() === targetType.fullName.toLowerCase() &&
28!
72
                                (this.tableProvider === targetType.tableProvider ||
73
                                    this.tableProvider().name === targetType.tableProvider().name);
74
                        } else {
75
                            equal = targetType.isEqual(this, data);
4✔
76
                        }
77
                        return equal;
63✔
78
                    };
79
                }
80
                if (propName === 'isTypeCompatible') {
24,503✔
81
                    //Need to be able to check equality without resolution, because resolution need to check equality
82
                    //To see if you need to make a UnionType
83
                    return (targetType: BscType, data?: TypeCompatibilityData) => {
75✔
84
                        if (!targetType) {
75!
NEW
85
                            return false;
×
86
                        }
87
                        if (isDynamicType(targetType)) {
75✔
88
                            return true;
1✔
89
                        }
90
                        const resolvedType = this.resolve();
74✔
91
                        if (resolvedType && !isReferenceType(resolvedType)) {
74✔
92
                            return resolvedType.isTypeCompatible(targetType, data);
71✔
93
                        } else if (isReferenceType(targetType)) {
3!
NEW
94
                            return this.fullName.toLowerCase() === targetType.fullName.toLowerCase() &&
×
95
                                this.tableProvider === targetType.tableProvider;
96
                        }
97
                        return targetType.isTypeCompatible(this, data);
3✔
98
                    };
99
                }
100

101
                //There may be some need to specifically get members on ReferenceType in the future
102
                // eg: if (Reflect.has(target, name)) {
103
                //   return Reflect.get(target, propName, receiver);
104

105
                let innerType = this.resolve();
24,428✔
106

107
                if (!innerType) {
24,428✔
108
                    // No real BscType found - we may need to handle some specific cases
109

110
                    if (propName === 'getMemberType') {
16,980✔
111
                        // We're looking for a member of a reference type
112
                        // Since we don't know what type this is, yet, return ReferenceType
113
                        return (memberName: string, options: GetTypeOptions) => {
913✔
114
                            const resolvedType = this.resolve();
913✔
115
                            if (resolvedType) {
913!
NEW
116
                                return resolvedType.getMemberType(memberName, options);
×
117
                            }
118
                            const refLookUp = `${memberName.toLowerCase()}-${options?.flags}`;
913!
119
                            let memberTypeReference = this.memberTypeReferences.get(refLookUp);
913✔
120
                            if (memberTypeReference) {
913✔
121
                                return memberTypeReference;
21✔
122

123
                            }
124
                            memberTypeReference = new ReferenceType(memberName, this.makeMemberFullName(memberName), options.flags, this.futureMemberTableProvider);
892✔
125
                            this.memberTypeReferences.set(refLookUp, memberTypeReference);
892✔
126
                            return memberTypeReference;
892✔
127
                        };
128
                    } else if (propName === 'getCallFuncType') {
16,067✔
129
                        // We're looking for a callfunc member of a reference type
130
                        // Since we don't know what type this is, yet, return ReferenceType
131
                        return (memberName: string, options: GetTypeOptions) => {
1✔
132
                            const resolvedType = this.resolve();
1✔
133
                            if (isComponentType(resolvedType)) {
1!
NEW
134
                                return resolvedType.getCallFuncType(memberName, options);
×
135
                            }
136
                            const refLookUp = `${memberName.toLowerCase()}-${options.flags}-callfunc`;
1✔
137
                            let callFuncMemberTypeReference = this.callFuncMemberTypeReferences.get(refLookUp);
1✔
138
                            if (callFuncMemberTypeReference) {
1!
NEW
139
                                return callFuncMemberTypeReference;
×
140

141
                            }
142
                            callFuncMemberTypeReference = new ReferenceType(memberName, this.makeMemberFullName(memberName), options.flags, this.futureCallFuncMemberTableProvider);
1✔
143
                            this.callFuncMemberTypeReferences.set(refLookUp, callFuncMemberTypeReference);
1✔
144
                            return callFuncMemberTypeReference;
1✔
145
                        };
146
                    } else if (propName === 'toString') {
16,066✔
147
                        // This type was never found
148
                        // For diagnostics, we should return the expected name of of the type
149
                        return () => this.fullName;
5,029✔
150
                    } else if (propName === 'toTypeString') {
11,037!
151
                        // For transpilation, we should 'dynamic'
NEW
152
                        return () => 'dynamic';
×
153
                    } else if (propName === 'returnType') {
11,037✔
154
                        let propRefType = this.propertyTypeReference.get(propName);
37✔
155
                        if (!propRefType) {
37✔
156
                            propRefType = new TypePropertyReferenceType(this, propName);
20✔
157
                            this.propertyTypeReference.set(propName, propRefType);
20✔
158
                        }
159
                        return propRefType;
37✔
160
                    } else if (propName === 'memberTable') {
11,000✔
161
                        return this.memberTable;
10✔
162
                    } else if (propName === 'getMemberTable') {
10,990✔
163
                        return () => {
5✔
164
                            return this.memberTable;
5✔
165
                        };
166
                    } else if (propName === 'callFuncTable') {
10,985!
NEW
167
                        return (this as any).callFuncMemberTable;
×
168
                    } else if (propName === 'getCallFuncTable') {
10,985!
NEW
169
                        return () => {
×
NEW
170
                            return (this as any).callFuncMemberTable;
×
171
                        };
172
                    } else if (propName === 'isTypeCompatible') {
10,985!
NEW
173
                        return (targetType: BscType) => {
×
NEW
174
                            return isDynamicType(targetType);
×
175
                        };
176
                    } else if (propName === 'isEqual') {
10,985!
NEW
177
                        return (targetType: BscType) => {
×
NEW
178
                            if (isReferenceType(targetType)) {
×
NEW
179
                                return this.fullName.toLowerCase() === targetType.toString().toLowerCase() &&
×
180
                                    this.tableProvider() === targetType.tableProvider();
181
                            }
NEW
182
                            return false;
×
183
                        };
184
                    } else if (propName === 'addBuiltInInterfaces') {
10,985✔
185
                        // this is an unknown type. There is no use in adding built in interfaces
186
                        // return no-op function
187
                        return () => { };
21✔
188
                    } else if (propName === 'hasAddedBuiltInInterfaces') {
10,964!
189
                        // this is an unknown type. There is no use in adding built in interfaces
NEW
190
                        return true;
×
191
                    }
192
                }
193

194
                if (!innerType) {
18,412✔
195
                    innerType = DynamicType.instance;
10,964✔
196
                }
197
                const result = Reflect.get(innerType, propName, innerType);
18,412✔
198
                return result;
18,412✔
199
            },
200
            set: (target, name, value, receiver) => {
201
                //There may be some need to specifically set members on ReferenceType in the future
202
                // eg: if (Reflect.has(target, name)) {
203
                //   return Reflect.set(target, name, value, receiver);
204

205
                let innerType = this.resolve();
397✔
206

207
                // Look for circular references
208
                if (!innerType || this.referenceChain.has(innerType)) {
397!
NEW
209
                    return false;
×
210
                }
211
                const result = Reflect.set(innerType, name, value, innerType);
397✔
212
                this.referenceChain.clear();
397✔
213
                return result;
397✔
214
            }
215
        });
216
    }
217

218
    public readonly kind = BscTypeKind.ReferenceType;
158,546✔
219

220
    /**
221
     * Resolves the type based on the original name and the table provider
222
     */
223
    private resolve(): BscType {
224
        const symbolTable = this.tableProvider();
40,594✔
225
        if (!symbolTable) {
40,594✔
226
            return;
10✔
227
        }
228
        // Look for circular references
229
        let resolvedType = symbolTable.getSymbolType(this.memberKey, { flags: this.flags, onlyCacheResolvedTypes: true });
40,584✔
230
        if (!resolvedType) {
40,584✔
231
            // could not find this member
232
            return;
23,616✔
233
        }
234
        if (isAnyReferenceType(resolvedType)) {
16,968✔
235
            // If this is a referenceType, keep digging down until we have a non reference Type.
236
            while (resolvedType && isAnyReferenceType(resolvedType)) {
723✔
237
                if (this.referenceChain.has(resolvedType)) {
723✔
238
                    // this is a circular reference
239
                    this.circRefCount++;
39✔
240
                }
241
                if (this.circRefCount > 1) {
723✔
242
                    //It is possible that we could properly resolve the case that one reference points to itself
243
                    //see test: '[Scope][symbolTable lookups with enhanced typing][finds correct class field type with default value enums are used]
244
                    return;
33✔
245
                }
246
                this.referenceChain.add(resolvedType);
690✔
247
                resolvedType = (resolvedType as any).getTarget?.();
690!
248
            }
249
            this.tableProvider().setCachedType(this.memberKey, { type: resolvedType }, { flags: this.flags });
690✔
250
        }
251

252
        if (resolvedType && !isAnyReferenceType(resolvedType)) {
16,935✔
253
            this.circRefCount = 0;
16,248✔
254
            this.referenceChain.clear();
16,248✔
255
        }
256
        return resolvedType;
16,935✔
257
    }
258

259
    get __reflection() {
260
        return { name: 'ReferenceType' };
321,497✔
261
    }
262

263
    makeMemberFullName(memberName: string) {
264
        return this.fullName + '.' + memberName;
893✔
265
    }
266
    private circRefCount = 0;
158,546✔
267

268
    private referenceChain = new Set<BscType>();
158,546✔
269

270
    private propertyTypeReference = new Map<string, TypePropertyReferenceType>();
158,546✔
271

272
    private memberTypeReferences = new Map<string, ReferenceType>();
158,546✔
273
    private callFuncMemberTypeReferences = new Map<string, ReferenceType>();
158,546✔
274

275
    private futureMemberTableProvider = () => {
158,546✔
276
        return {
10,613✔
277
            name: `FutureMemberTableProvider: '${this.__identifier}'`,
278
            getSymbolType: (innerName: string, innerOptions: GetTypeOptions) => {
279
                const resolvedType = this.resolve();
9,935✔
280
                if (resolvedType) {
9,935✔
281
                    return resolvedType.getMemberType(innerName, innerOptions);
6,736✔
282
                }
283
            },
284
            setCachedType: (innerName: string, innerResolvedTypeCacheEntry: TypeChainEntry, options: GetSymbolTypeOptions) => {
285
                const resolvedType = this.resolve();
678✔
286
                if (resolvedType) {
678!
287
                    resolvedType.memberTable.setCachedType(innerName, innerResolvedTypeCacheEntry, options);
678✔
288
                }
289
            }
290
        };
291
    };
292

293
    private futureCallFuncMemberTableProvider = () => {
158,546✔
NEW
294
        return {
×
295
            name: `FutureCallFuncMemberTableProvider: '${this.__identifier}'`,
296
            getSymbolType: (innerName: string, innerOptions: GetTypeOptions) => {
NEW
297
                const resolvedType = this.resolve();
×
NEW
298
                if (isComponentType(resolvedType)) {
×
NEW
299
                    return resolvedType.getCallFuncType(innerName, innerOptions);
×
300
                }
301
            },
302
            setCachedType: (innerName: string, innerResolvedTypeCacheEntry: TypeChainEntry, options: GetSymbolTypeOptions) => {
NEW
303
                const resolvedType = this.resolve();
×
NEW
304
                if (isComponentType(resolvedType)) {
×
NEW
305
                    resolvedType.getCallFuncTable().setCachedType(innerName, innerResolvedTypeCacheEntry, options);
×
306
                }
307
            }
308
        };
309
    };
310
}
311

312
/**
313
 * Use this class for when you need to reference a property of a BscType, and that property is also a BscType,
314
 * Especially when the instance with the property may not have been instantiated/discovered yet
315
 *
316
 * For Example, FunctionType.returnType --- if the FunctionExpression has not been walked yet, it will be a ReferenceType
317
 * if we just access .returnType on a ReferenceType that doesn't have access to an actual FunctionType yet, .returnType will be undefined
318
 * So when we use this class, it maintains that reference to a potential ReferenceType, and this class will change from
319
 * returning undefined to the ACTUAL .returnType when the ReferenceType can be resolved.
320
 *
321
 * This is really cool. It's like programming with time-travel.
322
 */
323
export class TypePropertyReferenceType extends BscType {
1✔
324
    constructor(public outerType: BscType, public propertyName: string) {
67✔
325
        super(propertyName);
67✔
326
        // eslint-disable-next-line no-constructor-return
327
        return new Proxy(this, {
67✔
328
            get: (target, propName, receiver) => {
329

330
                if (propName === '__reflection') {
301✔
331
                    // Cheeky way to get `isTypePropertyReferenceType` reflection to work
332
                    return { name: 'TypePropertyReferenceType' };
53✔
333
                }
334

335
                if (propName === 'outerType') {
248!
NEW
336
                    return outerType;
×
337
                }
338

339
                if (isAnyReferenceType(this.outerType) && !this.outerType.isResolvable()) {
248✔
340
                    if (propName === 'getMemberType') {
34✔
341
                        //If we're calling `getMemberType()`, we need it to proxy to using the actual symbol table
342
                        //So if that symbol is ever populated, the correct type is passed through
343
                        return (memberName: string, options: GetSymbolTypeOptions) => {
10✔
344
                            const fullMemberName = this.outerType.toString() + '.' + memberName;
10✔
345
                            return new ReferenceType(memberName, fullMemberName, options.flags, () => {
10✔
346
                                return {
18✔
347
                                    name: `TypePropertyReferenceType : '${fullMemberName}'`,
348
                                    getSymbolType: (innerName: string, innerOptions: GetTypeOptions) => {
349
                                        return this.outerType?.[this.propertyName]?.getMemberType(innerName, innerOptions);
18!
350
                                    },
351
                                    setCachedType: (innerName: string, innerTypeCacheEntry: TypeChainEntry, innerOptions: GetTypeOptions) => {
NEW
352
                                        return this.outerType?.[this.propertyName]?.memberTable.setCachedType(innerName, innerTypeCacheEntry, innerOptions);
×
353
                                    }
354
                                };
355
                            });
356
                        };
357
                    }
358
                    if (propName === 'isResolvable') {
24✔
359
                        return () => false;
3✔
360
                    }
361
                }
362
                let inner = this.outerType?.[this.propertyName];
235✔
363

364
                if (!inner) {
235✔
365
                    inner = DynamicType.instance;
38✔
366
                }
367

368
                if (inner) {
235!
369
                    const result = Reflect.get(inner, propName, inner);
235✔
370
                    return result;
235✔
371
                }
372
            },
373
            set: (target, name, value, receiver) => {
374
                //There may be some need to specifically set members on ReferenceType in the future
375
                // eg: if (Reflect.has(target, name)) {
376
                //   return Reflect.set(target, name, value, receiver);
377

378
                let inner = this.outerType[this.propertyName];
23✔
379

380
                if (inner) {
23!
381
                    const result = Reflect.set(inner, name, value, inner);
23✔
382
                    return result;
23✔
383
                }
384
            }
385
        });
386
    }
387
}
388

389

390
/**
391
 * Use this class for when there is a binary operator and either the left hand side and/or the right hand side
392
 * are ReferenceTypes
393
 */
394
export class BinaryOperatorReferenceType extends BscType {
1✔
395
    cachedType: BscType;
396

397
    constructor(public leftType: BscType, public operator: Token, public rightType: BscType, binaryOpResolver: (lType: BscType, operator: Token, rType: BscType) => BscType) {
31✔
398
        super(operator.text);
31✔
399
        // eslint-disable-next-line no-constructor-return
400
        return new Proxy(this, {
31✔
401
            get: (target, propName, receiver) => {
402

403
                if (propName === '__reflection') {
130✔
404
                    // Cheeky way to get `BinaryOperatorReferenceType` reflection to work
405
                    return { name: 'BinaryOperatorReferenceType' };
78✔
406
                }
407

408
                if (propName === 'leftType') {
52✔
409
                    return leftType;
6✔
410
                }
411

412
                if (propName === 'rightType') {
46✔
413
                    return rightType;
6✔
414
                }
415

416
                let resultType: BscType = this.cachedType ?? DynamicType.instance;
40✔
417
                if (!this.cachedType) {
40✔
418
                    if ((isAnyReferenceType(this.leftType) && !this.leftType.isResolvable()) ||
26✔
419
                        (isAnyReferenceType(this.rightType) && !this.rightType.isResolvable())
420
                    ) {
421
                        if (propName === 'isResolvable') {
24✔
422
                            return () => false;
13✔
423
                        }
424
                        if (propName === 'getTarget') {
11✔
425
                            return () => undefined;
2✔
426
                        }
427
                    } else {
428
                        resultType = binaryOpResolver(this.leftType, this.operator, this.rightType);
2✔
429
                        this.cachedType = resultType;
2✔
430
                    }
431

432
                }
433
                if (resultType) {
25!
434
                    const result = Reflect.get(resultType, propName, resultType);
25✔
435
                    return result;
25✔
436
                }
437
            }
438
        });
439
    }
440
}
441

442

443
/**
444
 * Use this class for when there is a presumable array type that is a referenceType
445
 */
446
export class ArrayDefaultTypeReferenceType extends BscType {
1✔
447
    cachedType: BscType;
448

449
    constructor(public objType: BscType) {
2✔
450
        super('ArrayDefaultType');
2✔
451
        // eslint-disable-next-line no-constructor-return
452
        return new Proxy(this, {
2✔
453
            get: (target, propName, receiver) => {
454

455
                if (propName === '__reflection') {
25✔
456
                    // Cheeky way to get `ArrayDefaultTypeReferenceType` reflection to work
457
                    return { name: 'ArrayDefaultTypeReferenceType' };
14✔
458
                }
459

460
                if (propName === 'objType') {
11!
NEW
461
                    return objType;
×
462
                }
463

464
                let resultType: BscType = this.cachedType ?? DynamicType.instance;
11✔
465
                if (!this.cachedType) {
11✔
466
                    if ((isAnyReferenceType(this.objType) && !this.objType.isResolvable())
4✔
467
                    ) {
468
                        if (propName === 'isResolvable') {
2✔
469
                            return () => false;
1✔
470
                        }
471
                        if (propName === 'getTarget') {
1!
NEW
472
                            return () => undefined;
×
473
                        }
474
                    } else {
475
                        if (isArrayType(this.objType)) {
2!
476
                            resultType = this.objType.defaultType;
2✔
477
                        } else {
NEW
478
                            resultType = DynamicType.instance;
×
479
                        }
480
                        this.cachedType = resultType;
2✔
481
                    }
482

483
                }
484
                if (resultType) {
10!
485
                    const result = Reflect.get(resultType, propName, resultType);
10✔
486
                    return result;
10✔
487
                }
488
            }
489
        });
490
    }
491
}
492

493
/**
494
 * Gives an array of all the symbol names that need to be resolved to make the given reference type be resolved
495
 */
496
export function getAllRequiredSymbolNames(refType: BscType, namespaceLower?: string): Array<{ name: string; namespacedName?: string }> {
1✔
497
    if (refType.isResolvable()) {
75✔
498
        return [];
8✔
499
    }
500
    if (isReferenceType(refType)) {
67✔
501
        return [{ name: refType.fullName, namespacedName: namespaceLower ? `${namespaceLower}.${refType.fullName}` : null }];
61✔
502
    }
503
    if (isTypePropertyReferenceType(refType)) {
6!
NEW
504
        const outer = refType.outerType;
×
NEW
505
        if (isAnyReferenceType(outer)) {
×
NEW
506
            return getAllRequiredSymbolNames(outer);
×
507
        } else {
NEW
508
            return [];
×
509
        }
510

511
    }
512
    if (isArrayDefaultTypeReferenceType(refType)) {
6!
NEW
513
        const objType = refType.objType;
×
NEW
514
        if (isAnyReferenceType(objType)) {
×
NEW
515
            return getAllRequiredSymbolNames(objType);
×
516
        } else {
NEW
517
            return [];
×
518
        }
519
    }
520
    if (isBinaryOperatorReferenceType(refType)) {
6!
521
        const left = refType.leftType;
6✔
522
        const right = refType.rightType;
6✔
523
        const result = [];
6✔
524
        if (isAnyReferenceType(left)) {
6✔
525
            result.push(...getAllRequiredSymbolNames(left, namespaceLower));
4✔
526
        }
527
        if (isAnyReferenceType(right)) {
6!
528
            result.push(...getAllRequiredSymbolNames(right, namespaceLower));
6✔
529

530
        }
531
        return result;
6✔
532
    }
NEW
533
    return [];
×
534
}
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

© 2025 Coveralls, Inc