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

ImmediatePlatform / Immediate.Injections / 27295451037

10 Jun 2026 05:55PM UTC coverage: 95.614% (+3.2%) from 92.448%
27295451037

Pull #35

github

web-flow
Merge 22cd78458 into ba87038e0
Pull Request #35: Add Analyzers for invalid attribute usage

443 of 465 new or added lines in 4 files covered. (95.27%)

1 existing line in 1 file now uncovered.

1199 of 1254 relevant lines covered (95.61%)

3.82 hits per line

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

98.58
/src/Immediate.Injections.Generators/ImmediateInjectionsGenerator.Transform.cs
1
using Microsoft.CodeAnalysis;
2
using Microsoft.CodeAnalysis.CSharp;
3

4
namespace Immediate.Injections.Generators;
5

6
public sealed partial class ImmediateInjectionsGenerator
7
{
8
        private static AssemblyRegistrationDefaults TransformAssemblyDefaults(
9
                GeneratorAttributeSyntaxContext context,
10
                CancellationToken token
11
        )
12
        {
13
                token.ThrowIfCancellationRequested();
4✔
14

15
                var arguments = context.Attributes[0].NamedArguments;
4✔
16

17
                return new()
4✔
18
                {
4✔
19
                        DuplicateStrategy = arguments.GetEnumArgumentValue("DuplicateStrategy"),
4✔
20
                };
4✔
21
        }
22

23
        private static RegisterServicesMethod? TransformRegisterServicesMethod(GeneratorAttributeSyntaxContext context, CancellationToken token)
24
        {
25
                token.ThrowIfCancellationRequested();
4✔
26

27
                return context.TargetSymbol switch
4✔
28
                {
4✔
29
                        IMethodSymbol { IsValidRegisterServicesMethod: true } ims =>
4✔
30
                                new()
4✔
31
                                {
4✔
32
                                        FullName = ims.ToDisplayString(DisplayNameFormatters.MethodFullyQualifiedWithType),
4✔
33
                                        ReceivesTags = ims.Parameters.Length == 2,
4✔
34
                                },
4✔
35

4✔
36
                        _ => null,
4✔
37
                };
4✔
38
        }
39

40
        private static EquatableReadOnlyList<RegisterClass> TransformRegisterClass0(GeneratorAttributeSyntaxContext context, CancellationToken token)
41
        {
42
                token.ThrowIfCancellationRequested();
4✔
43

44
                if (context.TargetSymbol is not INamedTypeSymbol targetSymbol)
4✔
45
                        return new([]);
×
46

47
                var assemblyAttributes = context.SemanticModel.Compilation.Assembly.GetAttributes();
4✔
48
                var defaultsAttribute = assemblyAttributes.FirstOrDefault(a => a.AttributeClass.IsRegistrationDefaultsAttribute);
4✔
49
                var defaultRegistration = defaultsAttribute?.NamedArguments.GetEnumArgumentValue("RegistrationStrategy") ?? "None";
4✔
50
                var defaultUseProxyFactory = defaultsAttribute?.NamedArguments.GetArgumentValue("UseProxyFactory")?.Value is true;
4✔
51

52
                return context.Attributes
4✔
53
                        .SelectMany(attributeData =>
4✔
54
                        {
4✔
55
                                token.ThrowIfCancellationRequested();
4✔
56

4✔
57
                                var arguments = attributeData.NamedArguments;
4✔
58
                                var serviceType = arguments.GetArgumentValue("ServiceType")?.ArgumentType;
4✔
59
                                var tags = arguments.GetArgumentValue("Tags")?.GetStringArray();
4✔
60
                                var serviceKey = arguments.GetArgumentValue("ServiceKey")?.ToCSharpString().NullIf("null");
4✔
61
                                var factory = arguments.GetArgumentValue("Factory")?.Value as string;
4✔
62
                                var duplicateStrategy = arguments.GetEnumArgumentValue("DuplicateStrategy");
4✔
63
                                var registrationStrategy = arguments.GetEnumArgumentValue("RegistrationStrategy") ?? (serviceType is { } ? "None" : defaultRegistration);
4✔
64
                                var useProxyValue = arguments.GetArgumentValue("UseProxyFactory")?.Value;
4✔
65
                                var useProxy = useProxyValue is true;
4✔
66

4✔
67
                                if (targetSymbol.IsGenericType && (factory is { } || useProxy))
4✔
68
                                        return [];
4✔
69

4✔
70
                                if (!targetSymbol.IsValidFactoryMethod(factory, isKeyed: serviceKey is { }))
4✔
71
                                        return [];
4✔
72

4✔
73
                                switch (registrationStrategy)
4✔
74
                                {
4✔
75
                                        case "None":
4✔
76
                                        {
4✔
77
                                                // if self anyway, go there to avoid redundancies here
4✔
78
                                                if (serviceType is null || SymbolEqualityComparer.Default.Equals(serviceType, targetSymbol))
4✔
79
                                                        goto case "DoSelf";
4✔
80

4✔
81
                                                // what does it mean to proxy to the concrete, but also provide a factory when there is no self?
4✔
82
                                                if (useProxy && factory is { })
4✔
83
                                                        return [];
×
84

4✔
85
                                                if (!targetSymbol.IsGenericType)
4✔
86
                                                {
4✔
87
                                                        if (
4✔
88
                                                                context.SemanticModel.Compilation.ClassifyConversion(targetSymbol, serviceType) is not (
4✔
89
                                                                { IsIdentity: true } or { IsImplicit: true, IsReference: true }
4✔
90
                                                                )
4✔
91
                                                        )
4✔
92
                                                        {
4✔
93
                                                                return [];
4✔
94
                                                        }
4✔
95

4✔
96
                                                        return [
4✔
97
                                                                BuildRegistration(
4✔
98
                                                                        serviceType,
4✔
99
                                                                        targetSymbol,
4✔
100
                                                                        useProxy: useProxyValue.GetUseProxyFactoryValue(factory, defaultUseProxyFactory),
4✔
101
                                                                        factory: factory
4✔
102
                                                                ),
4✔
103
                                                        ];
4✔
104
                                                }
4✔
105

4✔
106
                                                if (serviceType.Arity != targetSymbol.Arity)
4✔
107
                                                        return [];
4✔
108

4✔
109
                                                if (!serviceType.IsUnboundGenericType)
4✔
110
                                                {
4✔
111
                                                        var concreteTargetSymbol = targetSymbol.Construct(
4✔
112
                                                                serviceType.TypeArguments,
4✔
113
                                                                serviceType.TypeArgumentNullableAnnotations
4✔
114
                                                        );
4✔
115

4✔
116
                                                        if (context.SemanticModel.Compilation.ClassifyConversion(concreteTargetSymbol, serviceType) is not (
4✔
117
                                                                { IsIdentity: true } or { IsImplicit: true, IsReference: true }
4✔
118
                                                        ))
4✔
119
                                                        {
4✔
120
                                                                return [];
4✔
121
                                                        }
4✔
122

4✔
123
                                                        return [
4✔
124
                                                                BuildRegistration(
4✔
125
                                                                        serviceType,
4✔
126
                                                                        concreteTargetSymbol,
4✔
127
                                                                        useProxy: false,
4✔
128
                                                                        factory: null
4✔
129
                                                                ),
4✔
130
                                                        ];
4✔
131
                                                }
4✔
132

4✔
133
                                                if (
4✔
134
                                                        context.SemanticModel.Compilation.ClassifyConversion(
4✔
135
                                                                targetSymbol,
4✔
136
                                                                serviceType.ConstructedFrom.Construct(targetSymbol.TypeArguments, targetSymbol.TypeArgumentNullableAnnotations)
4✔
137
                                                        ) is not ({ IsIdentity: true } or { IsImplicit: true, IsReference: true })
4✔
138
                                                )
4✔
139
                                                {
4✔
140
                                                        return [];
4✔
141
                                                }
4✔
142

4✔
143
                                                return [
4✔
144
                                                        BuildRegistration(
4✔
145
                                                                serviceType,
4✔
146
                                                                targetSymbol.ConstructUnboundGenericType(),
4✔
147
                                                                useProxy: false,
4✔
148
                                                                factory: null
4✔
149
                                                        ),
4✔
150
                                                ];
4✔
151
                                        }
4✔
152

4✔
153
                                        case "Self":
4✔
154
                                        {
4✔
155
                                                // service type is only valid if we aren't specifying a registration strategy
4✔
156
                                                if (serviceType is { })
4✔
157
                                                        return [];
4✔
158

4✔
159
                                                goto case "DoSelf";
4✔
160
                                        }
4✔
161

4✔
162
                                        case "DoSelf":
4✔
163
                                        {
4✔
164
                                                // what does it mean to proxy to the concrete when targetting self?
4✔
165
                                                if (useProxy)
4✔
166
                                                        return [];
4✔
167

4✔
168
                                                if (targetSymbol.IsGenericType)
4✔
169
                                                {
4✔
170
                                                        var unbound = targetSymbol.ConstructUnboundGenericType();
4✔
171
                                                        return [BuildRegistration(unbound, unbound, useProxy: false, factory: null)];
4✔
172
                                                }
4✔
173

4✔
174
                                                return [BuildRegistration(targetSymbol, targetSymbol, useProxy: false, factory: factory)];
4✔
175
                                        }
4✔
176

4✔
177
                                        case "ImplementedInterfaces":
4✔
178
                                        {
4✔
179
                                                // service type is only valid if we aren't specifying a registration strategy
4✔
180
                                                if (serviceType is { })
4✔
181
                                                        return [];
4✔
182

4✔
183
                                                if (targetSymbol.IsGenericType)
4✔
184
                                                {
4✔
185
                                                        var unbound = targetSymbol.ConstructUnboundGenericType();
4✔
186

4✔
187
                                                        return targetSymbol
4✔
188
                                                                .AllInterfaces
4✔
189
                                                                .Where(i =>
4✔
190
                                                                        i.IsGenericType
4✔
191
                                                                        && i.Arity == targetSymbol.Arity
4✔
192
                                                                        && i.TypeArguments.All(tp => tp is ITypeParameterSymbol)
4✔
193
                                                                        && context.SemanticModel.Compilation.ClassifyConversion(
4✔
194
                                                                                targetSymbol,
4✔
195
                                                                                i.ConstructedFrom.Construct(
4✔
196
                                                                                        targetSymbol.TypeArguments,
4✔
197
                                                                                        targetSymbol.TypeArgumentNullableAnnotations
4✔
198
                                                                                )
4✔
199
                                                                        ) is { IsIdentity: true } or { IsImplicit: true, IsReference: true }
4✔
200
                                                                )
4✔
201
                                                                .Select(i => BuildRegistration(i.ConstructUnboundGenericType(), unbound, useProxy: false, factory: null));
4✔
202
                                                }
4✔
203

4✔
204
                                                // what does it mean to proxy to the concrete, but also provide a factory when there is no self?
4✔
205
                                                if (useProxy && factory is { })
4✔
206
                                                        return [];
4✔
207

4✔
208
                                                return targetSymbol
4✔
209
                                                        .AllInterfaces
4✔
210
                                                        .Select(i =>
4✔
211
                                                                BuildRegistration(
4✔
212
                                                                        i,
4✔
213
                                                                        targetSymbol,
4✔
214
                                                                        useProxy: useProxyValue.GetUseProxyFactoryValue(factory, defaultUseProxyFactory),
4✔
215
                                                                        factory: factory
4✔
216
                                                                )
4✔
217
                                                        );
4✔
218
                                        }
4✔
219

4✔
220
                                        case "SelfAndImplementedInterfaces":
4✔
221
                                        {
4✔
222
                                                // service type is only valid if we aren't specifying a registration strategy
4✔
223
                                                if (serviceType is { })
4✔
224
                                                        return [];
4✔
225

4✔
226
                                                if (targetSymbol.IsGenericType)
4✔
227
                                                {
4✔
228
                                                        var unbound = targetSymbol.ConstructUnboundGenericType();
4✔
229

4✔
230
                                                        return
4✔
231
                                                        [
4✔
232
                                                                BuildRegistration(unbound, unbound, useProxy: false, factory: null),
4✔
233
                                                                ..targetSymbol
4✔
234
                                                                        .AllInterfaces
4✔
235
                                                                        .Where(i =>
4✔
236
                                                                                i.IsGenericType
4✔
237
                                                                                && i.Arity == targetSymbol.Arity
4✔
238
                                                                                && i.TypeArguments.All(tp => tp is ITypeParameterSymbol)
4✔
239
                                                                                && context.SemanticModel.Compilation.ClassifyConversion(
4✔
240
                                                                                        targetSymbol,
4✔
241
                                                                                        i.ConstructedFrom.Construct(
4✔
242
                                                                                                targetSymbol.TypeArguments,
4✔
243
                                                                                                targetSymbol.TypeArgumentNullableAnnotations
4✔
244
                                                                                        )
4✔
245
                                                                                ) is { IsIdentity: true } or { IsImplicit: true, IsReference: true }
4✔
246
                                                                        )
4✔
247
                                                                        .Select(i =>
4✔
248
                                                                                BuildRegistration(
4✔
249
                                                                                        i.ConstructUnboundGenericType(),
4✔
250
                                                                                        unbound,
4✔
251
                                                                                        useProxy: false,
4✔
252
                                                                                        factory: null
4✔
253
                                                                                )
4✔
254
                                                                        ),
4✔
255
                                                        ];
4✔
256
                                                }
4✔
257

4✔
258
                                                return
4✔
259
                                                [
4✔
260
                                                        BuildRegistration(targetSymbol, targetSymbol, useProxy: false, factory: factory),
4✔
261
                                                        ..targetSymbol
4✔
262
                                                                .AllInterfaces
4✔
263
                                                                .Select(i =>
4✔
264
                                                                        BuildRegistration(i,
4✔
265
                                                                                targetSymbol,
4✔
266
                                                                                useProxy: useProxyValue.GetUseProxyFactoryValue(factory, defaultUseProxyFactory),
4✔
267
                                                                                factory: factory
4✔
268
                                                                        )
4✔
269
                                                                ),
4✔
270
                                                ];
4✔
271
                                        }
4✔
272

4✔
273
                                        default:
4✔
274
                                                return [];
×
275
                                }
4✔
276

4✔
277
                                RegisterClass BuildRegistration(
4✔
278
                                        INamedTypeSymbol serviceSymbol,
4✔
279
                                        INamedTypeSymbol targetSymbol,
4✔
280
                                        bool useProxy,
4✔
281
                                        string? factory
4✔
282
                                )
4✔
283
                                {
4✔
284
                                        return new RegisterClass
4✔
285
                                        {
4✔
286
                                                ServiceType = serviceSymbol.ToDisplayString(DisplayNameFormatters.FullyQualifiedWithNullableFormat),
4✔
287
                                                Implementation = targetSymbol.BuildImplementationArgument(
4✔
288
                                                        useProxy: useProxy,
4✔
289
                                                        isKeyed: serviceKey is { },
4✔
290
                                                        factory: factory
4✔
291
                                                ),
4✔
292
                                                Tags = tags,
4✔
293
                                                ServiceKey = serviceKey,
4✔
294
                                                DuplicateStrategy = duplicateStrategy,
4✔
295
                                        };
4✔
296
                                }
4✔
297
                        })
4✔
298
                        .ToEquatableReadOnlyList();
4✔
299
        }
300

301
        private static EquatableReadOnlyList<RegisterClass> TransformRegisterClass1(GeneratorAttributeSyntaxContext context, CancellationToken token)
302
        {
303
                token.ThrowIfCancellationRequested();
4✔
304

305
                if (context.TargetSymbol is not INamedTypeSymbol)
4✔
306
                        return new([]);
×
307

308
                var assemblyAttributes = context.SemanticModel.Compilation.Assembly.GetAttributes();
4✔
309
                var defaultsAttribute = assemblyAttributes.FirstOrDefault(a => a.AttributeClass.IsRegistrationDefaultsAttribute);
4✔
310
                var defaultUseProxyFactory = defaultsAttribute?.NamedArguments.GetArgumentValue("UseProxyFactory")?.Value is true;
4✔
311

312
                return context.Attributes
4✔
313
                        .Select(attributeData =>
4✔
314
                        {
4✔
315
                                token.ThrowIfCancellationRequested();
4✔
316

4✔
317
                                var targetSymbol = (INamedTypeSymbol)context.TargetSymbol;
4✔
318
                                var arguments = attributeData.NamedArguments;
4✔
319

4✔
320
                                if (attributeData.AttributeClass is not
4✔
321
                                        {
4✔
322
                                                TypeArguments:
4✔
323
                                                [
4✔
324
                                                INamedTypeSymbol serviceSymbol,
4✔
325
                                                ],
4✔
326
                                        })
4✔
327
                                {
4✔
328
                                        return null;
×
329
                                }
4✔
330

4✔
331
                                if (targetSymbol.IsGenericType)
4✔
332
                                {
4✔
333
                                        if (!serviceSymbol.IsGenericType)
4✔
334
                                                return null;
4✔
335

4✔
336
                                        if (targetSymbol.Arity != serviceSymbol.Arity)
4✔
337
                                                return null;
4✔
338

4✔
339
                                        targetSymbol = targetSymbol.Construct(
4✔
340
                                                serviceSymbol.TypeArguments,
4✔
341
                                                serviceSymbol.TypeArgumentNullableAnnotations
4✔
342
                                        );
4✔
343
                                }
4✔
344

4✔
345
                                var conversion = context.SemanticModel.Compilation.ClassifyConversion(targetSymbol, serviceSymbol);
4✔
346
                                if (conversion is not ({ IsIdentity: true } or { IsImplicit: true, IsReference: true }))
4✔
347
                                        return null;
4✔
348

4✔
349
                                var tags = arguments.GetArgumentValue("Tags")?.GetStringArray();
4✔
350
                                var serviceKey = arguments.GetArgumentValue("ServiceKey")?.ToCSharpString().NullIf("null");
4✔
351
                                var factory = arguments.GetArgumentValue("Factory")?.Value as string;
4✔
352
                                var duplicateStrategy = arguments.GetEnumArgumentValue("DuplicateStrategy");
4✔
353
                                var useProxyValue = arguments.GetArgumentValue("UseProxyFactory")?.Value;
4✔
354
                                var useProxy = useProxyValue is true;
4✔
355

4✔
356
                                if (useProxy)
4✔
357
                                {
4✔
358
                                        if (factory is { })
4✔
359
                                                return null;
4✔
360

4✔
361
                                        if (conversion.IsIdentity)
4✔
362
                                                return null;
4✔
363
                                }
4✔
364

4✔
365
                                if (!targetSymbol.IsValidFactoryMethod(factory, isKeyed: serviceKey is { }))
4✔
366
                                        return null;
4✔
367

4✔
368
                                return new RegisterClass
4✔
369
                                {
4✔
370
                                        ServiceType = serviceSymbol.ToDisplayString(DisplayNameFormatters.FullyQualifiedWithNullableFormat),
4✔
371
                                        Implementation = targetSymbol.BuildImplementationArgument(
4✔
372
                                                useProxyValue.GetUseProxyFactoryValue(factory, defaultUseProxyFactory),
4✔
373
                                                isKeyed: serviceKey is { },
4✔
374
                                                factory: factory
4✔
375
                                        ),
4✔
376
                                        Tags = tags,
4✔
377
                                        ServiceKey = serviceKey,
4✔
378
                                        DuplicateStrategy = duplicateStrategy,
4✔
379
                                };
4✔
380
                        })
4✔
381
                        .WhereNotNull()
4✔
382
                        .ToEquatableReadOnlyList();
4✔
383
        }
384

385
        private static EquatableReadOnlyList<RegisterClass> TransformRegisterClass2(GeneratorAttributeSyntaxContext context, CancellationToken token)
386
        {
387
                token.ThrowIfCancellationRequested();
4✔
388

389
                if (context.TargetSymbol is not INamedTypeSymbol targetSymbol)
4✔
UNCOV
390
                        return new([]);
×
391

392
                var assemblyAttributes = context.SemanticModel.Compilation.Assembly.GetAttributes();
4✔
393
                var defaultsAttribute = assemblyAttributes.FirstOrDefault(a => a.AttributeClass.IsRegistrationDefaultsAttribute);
4✔
394
                var defaultUseProxyFactory = defaultsAttribute?.NamedArguments.GetArgumentValue("UseProxyFactory")?.Value is true;
4✔
395

396
                return context.Attributes
4✔
397
                        .Select(attributeData =>
4✔
398
                        {
4✔
399
                                token.ThrowIfCancellationRequested();
4✔
400

4✔
401
                                var arguments = attributeData.NamedArguments;
4✔
402

4✔
403
                                if (attributeData.AttributeClass is not
4✔
404
                                        {
4✔
405
                                                TypeArguments:
4✔
406
                                                [
4✔
407
                                                INamedTypeSymbol serviceSymbol,
4✔
408
                                                INamedTypeSymbol implementationSymbol
4✔
409
                                                ],
4✔
410
                                        }
4✔
411
                                        || !SymbolEqualityComparer.Default.Equals(implementationSymbol.OriginalDefinition, targetSymbol))
4✔
412
                                {
4✔
413
                                        return null;
4✔
414
                                }
4✔
415

4✔
416
                                var conversion = context.SemanticModel.Compilation.ClassifyConversion(implementationSymbol, serviceSymbol);
4✔
417
                                if (conversion is not ({ IsIdentity: true } or { IsImplicit: true, IsReference: true }))
4✔
418
                                        return null;
4✔
419

4✔
420
                                var tags = arguments.GetArgumentValue("Tags")?.GetStringArray();
4✔
421
                                var serviceKey = arguments.GetArgumentValue("ServiceKey")?.ToCSharpString().NullIf("null");
4✔
422
                                var factory = arguments.GetArgumentValue("Factory")?.Value as string;
4✔
423
                                var duplicateStrategy = arguments.GetEnumArgumentValue("DuplicateStrategy");
4✔
424
                                var useProxyValue = arguments.GetArgumentValue("UseProxyFactory")?.Value;
4✔
425
                                var useProxy = useProxyValue is true;
4✔
426

4✔
427
                                if (useProxy)
4✔
428
                                {
4✔
429
                                        if (factory is { })
4✔
430
                                                return null;
4✔
431

4✔
432
                                        if (conversion is { IsIdentity: true })
4✔
433
                                                return null;
4✔
434
                                }
4✔
435

4✔
436
                                if (!targetSymbol.IsValidFactoryMethod(factory, isKeyed: serviceKey is { }))
4✔
437
                                        return null;
4✔
438

4✔
439
                                return new RegisterClass
4✔
440
                                {
4✔
441
                                        ServiceType = serviceSymbol.ToDisplayString(DisplayNameFormatters.FullyQualifiedWithNullableFormat),
4✔
442
                                        Implementation = implementationSymbol.BuildImplementationArgument(useProxyValue.GetUseProxyFactoryValue(factory, defaultUseProxyFactory), serviceKey is { }, factory),
4✔
443
                                        Tags = tags,
4✔
444
                                        ServiceKey = serviceKey,
4✔
445
                                        DuplicateStrategy = duplicateStrategy,
4✔
446
                                };
4✔
447
                        })
4✔
448
                        .WhereNotNull()
4✔
449
                        .ToEquatableReadOnlyList();
4✔
450
        }
451
}
452

453
file static class Extensions
454
{
455
        public static string BuildImplementationArgument(
456
                this INamedTypeSymbol typeSymbol,
457
                bool useProxy,
458
                bool isKeyed,
459
                string? factory
460
        )
461
        {
462
                var type = typeSymbol.ToDisplayString(DisplayNameFormatters.FullyQualifiedWithNullableFormat);
4✔
463

464
                return (useProxy, isKeyed, factory) switch
4✔
465
                {
4✔
466
                        (true, true, _) => $"global::Microsoft.Extensions.DependencyInjection.ServiceProviderKeyedServiceExtensions.GetRequiredKeyedService<{type}>",
4✔
467
                        (true, false, _) => $"global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService<{type}>",
4✔
468
                        (false, _, { }) => $"{type}.{factory}",
4✔
469
                        (false, _, null) => $"typeof({type})",
4✔
470
                };
4✔
471
        }
472

473
        public static bool GetUseProxyFactoryValue(
474
                this object? useProxyValue,
475
                string? factory,
476
                bool defaultUseProxyValue
477
        )
478
        {
479
                return useProxyValue switch
4✔
480
                {
4✔
481
                        true => true,
4✔
482
                        false => false,
4✔
483
                        _ => factory is null && defaultUseProxyValue,
4✔
484
                };
4✔
485
        }
486
}
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