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

orion-ecs / keen-eye / 20395524465

20 Dec 2025 02:11PM UTC coverage: 62.297% (-0.5%) from 62.793%
20395524465

push

github

tyevco
Add voxel game sample with biome-based terrain generation

This sample demonstrates how to build a voxel-based game using KeenEyes ECS:

- Chunk-based world storage with 16x16x16 voxel chunks
- Simplex noise terrain generation with multiple biomes
  (Plains, Forest, Desert, Mountains, Tundra, Ocean)
- Temperature and moisture-based biome selection
- Feature placement (trees, cacti, plants) using deterministic noise
- Player movement with AABB collision against voxel terrain
- Chunk loading/unloading based on player view distance
- ASCII side-view visualization for terminal output

Key architecture patterns shown:
- Complex component types (VoxelData, HeightMap) using IComponent directly
- System dependencies via property injection
- Seeded procedural generation for reproducible worlds
- Chunk coordinate to world coordinate transformations

2719 of 3811 branches covered (71.35%)

Branch coverage included in aggregate %.

15597 of 25590 relevant lines covered (60.95%)

0.85 hits per line

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

95.73
/src/KeenEyes.Generators/SerializationGenerator.cs
1
using System.Collections.Generic;
2
using System.Collections.Immutable;
3
using System.Linq;
4
using System.Text;
5
using KeenEyes.Generators.Utilities;
6
using Microsoft.CodeAnalysis;
7
using Microsoft.CodeAnalysis.CSharp.Syntax;
8
using Microsoft.CodeAnalysis.Text;
9

10
namespace KeenEyes.Generators;
11

12
/// <summary>
13
/// Generates AOT-compatible serialization code for components marked with [Component(Serializable = true)].
14
/// Eliminates runtime reflection by generating strongly-typed serialization methods.
15
/// </summary>
16
[Generator]
17
public sealed class SerializationGenerator : IIncrementalGenerator
18
{
19
    private const string ComponentAttribute = "KeenEyes.ComponentAttribute";
20

21
    /// <inheritdoc />
22
    public void Initialize(IncrementalGeneratorInitializationContext context)
23
    {
24
        // Find all structs with [Component(Serializable = true)]
25
        var serializableComponents = context.SyntaxProvider
1✔
26
            .ForAttributeWithMetadataName(
1✔
27
                ComponentAttribute,
1✔
28
                predicate: static (node, _) => node is StructDeclarationSyntax,
1✔
29
                transform: static (ctx, _) => GetSerializableComponentInfo(ctx))
1✔
30
            .Where(static info => info is not null);
1✔
31

32
        var collected = serializableComponents.Collect();
1✔
33

34
        // Generate the serialization registry only if there are serializable components
35
        context.RegisterSourceOutput(collected, static (ctx, components) =>
1✔
36
        {
1✔
37
            var validComponents = components
1✔
38
                .Where(c => c is not null)
1✔
39
                .Select(c => c!)
1✔
40
                .ToImmutableArray();
1✔
41

1✔
42
            // Don't generate anything if there are no serializable components
1✔
43
            // This avoids conflicts when multiple projects reference KeenEyes.Core
1✔
44
            if (validComponents.Length == 0)
1✔
45
            {
1✔
46
                return;
1✔
47
            }
1✔
48

1✔
49
            var source = GenerateSerializationRegistry(validComponents);
1✔
50
            ctx.AddSource("ComponentSerializer.g.cs", SourceText.From(source, Encoding.UTF8));
1✔
51
        });
1✔
52
    }
1✔
53

54
    private static SerializableComponentInfo? GetSerializableComponentInfo(GeneratorAttributeSyntaxContext context)
55
    {
56
        // TargetSymbol is guaranteed to be INamedTypeSymbol because we filter for StructDeclarationSyntax
57
        var typeSymbol = (INamedTypeSymbol)context.TargetSymbol;
1✔
58

59
        // Check for Serializable = true in attribute
60
        // Attributes is guaranteed non-empty because we use ForAttributeWithMetadataName
61
        var attr = context.Attributes.First();
1✔
62

63
        var serializableArg = attr.NamedArguments
1✔
64
            .FirstOrDefault(a => a.Key == "Serializable");
1✔
65

66
        if (serializableArg.Value.Value is not true)
1✔
67
        {
68
            return null;
1✔
69
        }
70

71
        // Collect fields for serialization
72
        var fields = new List<SerializableFieldInfo>();
1✔
73
        foreach (var member in typeSymbol.GetMembers())
1✔
74
        {
75
            if (member is not IFieldSymbol field)
1✔
76
            {
77
                continue;
78
            }
79

80
            if (field.IsStatic || field.IsConst)
1✔
81
            {
82
                continue;
83
            }
84

85
            var isNullable = field.Type.NullableAnnotation == NullableAnnotation.Annotated;
1✔
86
            fields.Add(new SerializableFieldInfo(
1✔
87
                field.Name,
1✔
88
                field.Type.ToDisplayString(),
1✔
89
                GetJsonTypeName(field.Type),
1✔
90
                isNullable));
1✔
91
        }
92

93
        return new SerializableComponentInfo(
1✔
94
            typeSymbol.Name,
1✔
95
            typeSymbol.ContainingNamespace.ToDisplayString(),
1✔
96
            typeSymbol.ToDisplayString(),
1✔
97
            fields.ToImmutableArray());
1✔
98
    }
99

100
    private static string GetJsonTypeName(ITypeSymbol type)
101
    {
102
        // Map CLR types to JSON reader method names
103
        var displayString = type.ToDisplayString();
1✔
104
        return displayString switch
1✔
105
        {
1✔
106
            "int" or "System.Int32" => "Int32",
1✔
107
            "long" or "System.Int64" => "Int64",
1✔
108
            "short" or "System.Int16" => "Int16",
×
109
            "byte" or "System.Byte" => "Byte",
×
110
            "uint" or "System.UInt32" => "UInt32",
×
111
            "ulong" or "System.UInt64" => "UInt64",
×
112
            "ushort" or "System.UInt16" => "UInt16",
×
113
            "sbyte" or "System.SByte" => "SByte",
×
114
            "float" or "System.Single" => "Single",
1✔
115
            "double" or "System.Double" => "Double",
1✔
116
            "decimal" or "System.Decimal" => "Decimal",
1✔
117
            "bool" or "System.Boolean" => "Boolean",
1✔
118
            "string" or "System.String" => "String",
1✔
119
            _ => "Object" // For complex types, use generic deserialization
1✔
120
        };
1✔
121
    }
122

123
    private static string GenerateSerializationRegistry(ImmutableArray<SerializableComponentInfo> components)
124
    {
125
        var sb = new StringBuilder();
1✔
126

127
        sb.AppendLine("// <auto-generated />");
1✔
128
        sb.AppendLine("#nullable enable");
1✔
129
        sb.AppendLine();
1✔
130
        sb.AppendLine("using System;");
1✔
131
        sb.AppendLine("using System.Collections.Generic;");
1✔
132
        sb.AppendLine("using System.IO;");
1✔
133
        sb.AppendLine("using System.Text.Json;");
1✔
134
        sb.AppendLine("using KeenEyes.Capabilities;");
1✔
135
        sb.AppendLine("using KeenEyes.Serialization;");
1✔
136
        sb.AppendLine();
1✔
137
        sb.AppendLine("namespace KeenEyes.Generated;");
1✔
138
        sb.AppendLine();
1✔
139
        sb.AppendLine("/// <summary>");
1✔
140
        sb.AppendLine("/// Generated registry for AOT-compatible component serialization.");
1✔
141
        sb.AppendLine("/// Contains serialization methods for components marked with [Component(Serializable = true)].");
1✔
142
        sb.AppendLine("/// </summary>");
1✔
143
        sb.AppendLine("public sealed class ComponentSerializer : IComponentSerializer, IBinaryComponentSerializer");
1✔
144
        sb.AppendLine("{");
1✔
145
        sb.AppendLine("    /// <summary>");
1✔
146
        sb.AppendLine("    /// Shared instance for convenience.");
1✔
147
        sb.AppendLine("    /// </summary>");
1✔
148
        sb.AppendLine("    public static readonly ComponentSerializer Instance = new();");
1✔
149
        sb.AppendLine();
1✔
150
        sb.AppendLine("    private sealed record ComponentSerializationInfo(");
1✔
151
        sb.AppendLine("        Type Type,");
1✔
152
        sb.AppendLine("        Func<JsonElement, object> JsonDeserializer,");
1✔
153
        sb.AppendLine("        Func<object, JsonElement> JsonSerializer,");
1✔
154
        sb.AppendLine("        Func<BinaryReader, object> BinaryDeserializer,");
1✔
155
        sb.AppendLine("        Action<object, BinaryWriter> BinarySerializer,");
1✔
156
        sb.AppendLine("        Func<ISerializationCapability, bool, ComponentInfo> Registrar,");
1✔
157
        sb.AppendLine("        Action<ISerializationCapability, object> SingletonSetter);");
1✔
158
        sb.AppendLine();
1✔
159
        sb.AppendLine("    private static readonly Dictionary<string, ComponentSerializationInfo> ComponentsByName;");
1✔
160
        sb.AppendLine("    private static readonly Dictionary<Type, ComponentSerializationInfo> ComponentsByType;");
1✔
161
        sb.AppendLine();
1✔
162

163
        // Static constructor to initialize dictionaries
164
        sb.AppendLine("    static ComponentSerializer()");
1✔
165
        sb.AppendLine("    {");
1✔
166
        sb.AppendLine("        ComponentsByName = new Dictionary<string, ComponentSerializationInfo>();");
1✔
167
        sb.AppendLine("        ComponentsByType = new Dictionary<Type, ComponentSerializationInfo>();");
1✔
168
        sb.AppendLine();
1✔
169

170
        foreach (var component in components)
1✔
171
        {
172
            sb.AppendLine($"        var info_{component.Name} = new ComponentSerializationInfo(");
1✔
173
            sb.AppendLine($"            typeof({component.FullName}),");
1✔
174
            sb.AppendLine($"            Deserialize_{component.Name},");
1✔
175
            sb.AppendLine($"            value => Serialize_{component.Name}(({component.FullName})value),");
1✔
176
            sb.AppendLine($"            DeserializeBinary_{component.Name},");
1✔
177
            sb.AppendLine($"            (value, writer) => SerializeBinary_{component.Name}(({component.FullName})value, writer),");
1✔
178
            sb.AppendLine($"            (serialization, isTag) => serialization.Components.Register<{component.FullName}>(isTag),");
1✔
179
            sb.AppendLine($"            (serialization, value) => serialization.SetSingleton(({component.FullName})value));");
1✔
180
            sb.AppendLine();
1✔
181
            sb.AppendLine($"        ComponentsByName[typeof({component.FullName}).AssemblyQualifiedName!] = info_{component.Name};");
1✔
182
            sb.AppendLine($"        ComponentsByName[\"{component.FullName}\"] = info_{component.Name};");
1✔
183
            sb.AppendLine($"        ComponentsByType[typeof({component.FullName})] = info_{component.Name};");
1✔
184
            sb.AppendLine();
1✔
185
        }
186

187
        sb.AppendLine("    }");
1✔
188
        sb.AppendLine();
1✔
189

190
        // Public API methods implementing IComponentSerializer
191
        sb.AppendLine("    /// <inheritdoc />");
1✔
192
        sb.AppendLine("    public bool IsSerializable(Type type) => ComponentsByType.ContainsKey(type);");
1✔
193
        sb.AppendLine();
1✔
194
        sb.AppendLine("    /// <inheritdoc />");
1✔
195
        sb.AppendLine("    public bool IsSerializable(string typeName) => ComponentsByName.ContainsKey(typeName);");
1✔
196
        sb.AppendLine();
1✔
197
        sb.AppendLine("    /// <inheritdoc />");
1✔
198
        sb.AppendLine("    public object? Deserialize(string typeName, JsonElement json)");
1✔
199
        sb.AppendLine("    {");
1✔
200
        sb.AppendLine("        return ComponentsByName.TryGetValue(typeName, out var info)");
1✔
201
        sb.AppendLine("            ? info.JsonDeserializer(json)");
1✔
202
        sb.AppendLine("            : null;");
1✔
203
        sb.AppendLine("    }");
1✔
204
        sb.AppendLine();
1✔
205
        sb.AppendLine("    /// <inheritdoc />");
1✔
206
        sb.AppendLine("    public JsonElement? Serialize(Type type, object value)");
1✔
207
        sb.AppendLine("    {");
1✔
208
        sb.AppendLine("        if (!ComponentsByType.TryGetValue(type, out var info))");
1✔
209
        sb.AppendLine("        {");
1✔
210
        sb.AppendLine("            return null;");
1✔
211
        sb.AppendLine("        }");
1✔
212
        sb.AppendLine("        return info.JsonSerializer(value);");
1✔
213
        sb.AppendLine("    }");
1✔
214
        sb.AppendLine();
1✔
215
        sb.AppendLine("    /// <inheritdoc />");
1✔
216
        sb.AppendLine("    Type? IComponentSerializer.GetType(string typeName)");
1✔
217
        sb.AppendLine("    {");
1✔
218
        sb.AppendLine("        return ComponentsByName.TryGetValue(typeName, out var info) ? info.Type : null;");
1✔
219
        sb.AppendLine("    }");
1✔
220
        sb.AppendLine();
1✔
221
        sb.AppendLine("    /// <inheritdoc />");
1✔
222
        sb.AppendLine("    public ComponentInfo? RegisterComponent(ISerializationCapability serialization, string typeName, bool isTag)");
1✔
223
        sb.AppendLine("    {");
1✔
224
        sb.AppendLine("        return ComponentsByName.TryGetValue(typeName, out var info) ? info.Registrar(serialization, isTag) : null;");
1✔
225
        sb.AppendLine("    }");
1✔
226
        sb.AppendLine();
1✔
227
        sb.AppendLine("    /// <inheritdoc />");
1✔
228
        sb.AppendLine("    public bool SetSingleton(ISerializationCapability serialization, string typeName, object value)");
1✔
229
        sb.AppendLine("    {");
1✔
230
        sb.AppendLine("        if (ComponentsByName.TryGetValue(typeName, out var info))");
1✔
231
        sb.AppendLine("        {");
1✔
232
        sb.AppendLine("            info.SingletonSetter(serialization, value);");
1✔
233
        sb.AppendLine("            return true;");
1✔
234
        sb.AppendLine("        }");
1✔
235
        sb.AppendLine("        return false;");
1✔
236
        sb.AppendLine("    }");
1✔
237
        sb.AppendLine();
1✔
238
        sb.AppendLine("    /// <inheritdoc />");
1✔
239
        sb.AppendLine("    public object? CreateDefault(string typeName)");
1✔
240
        sb.AppendLine("    {");
1✔
241
        sb.AppendLine("        return ComponentsByName.TryGetValue(typeName, out var info) ? Activator.CreateInstance(info.Type) : null;");
1✔
242
        sb.AppendLine("    }");
1✔
243
        sb.AppendLine();
1✔
244
        sb.AppendLine("    /// <summary>");
1✔
245
        sb.AppendLine("    /// Gets all registered serializable type names.");
1✔
246
        sb.AppendLine("    /// </summary>");
1✔
247
        sb.AppendLine("    public IEnumerable<string> GetSerializableTypeNames() => ComponentsByName.Keys;");
1✔
248
        sb.AppendLine();
1✔
249
        sb.AppendLine("    /// <summary>");
1✔
250
        sb.AppendLine("    /// Gets all registered serializable types.");
1✔
251
        sb.AppendLine("    /// </summary>");
1✔
252
        sb.AppendLine("    public IEnumerable<Type> GetSerializableTypes() => ComponentsByType.Keys;");
1✔
253
        sb.AppendLine();
1✔
254

255
        // IBinaryComponentSerializer implementation
256
        sb.AppendLine("    /// <inheritdoc />");
1✔
257
        sb.AppendLine("    public bool WriteTo(Type type, object value, BinaryWriter writer)");
1✔
258
        sb.AppendLine("    {");
1✔
259
        sb.AppendLine("        if (!ComponentsByType.TryGetValue(type, out var info))");
1✔
260
        sb.AppendLine("        {");
1✔
261
        sb.AppendLine("            return false;");
1✔
262
        sb.AppendLine("        }");
1✔
263
        sb.AppendLine("        info.BinarySerializer(value, writer);");
1✔
264
        sb.AppendLine("        return true;");
1✔
265
        sb.AppendLine("    }");
1✔
266
        sb.AppendLine();
1✔
267
        sb.AppendLine("    /// <inheritdoc />");
1✔
268
        sb.AppendLine("    public object? ReadFrom(string typeName, BinaryReader reader)");
1✔
269
        sb.AppendLine("    {");
1✔
270
        sb.AppendLine("        return ComponentsByName.TryGetValue(typeName, out var info)");
1✔
271
        sb.AppendLine("            ? info.BinaryDeserializer(reader)");
1✔
272
        sb.AppendLine("            : null;");
1✔
273
        sb.AppendLine("    }");
1✔
274
        sb.AppendLine();
1✔
275

276
        // Generate individual serialization/deserialization methods
277
        foreach (var component in components)
1✔
278
        {
279
            GenerateComponentMethods(sb, component);
1✔
280
        }
281

282
        sb.AppendLine("}");
1✔
283

284
        return sb.ToString();
1✔
285
    }
286

287
    private static void GenerateComponentMethods(StringBuilder sb, SerializableComponentInfo component)
288
    {
289
        // Deserialize method
290
        sb.AppendLine($"    private static object Deserialize_{component.Name}(JsonElement json)");
1✔
291
        sb.AppendLine("    {");
1✔
292
        sb.AppendLine($"        var result = new {component.FullName}();");
1✔
293

294
        foreach (var field in component.Fields)
1✔
295
        {
296
            var camelFieldName = StringHelpers.ToCamelCase(field.Name);
1✔
297
            sb.AppendLine($"        if (json.TryGetProperty(\"{camelFieldName}\", out var {camelFieldName}Elem))");
1✔
298
            sb.AppendLine("        {");
1✔
299

300
            if (field.JsonTypeName == "Object")
1✔
301
            {
302
                // Complex type - use generic deserialization
303
                if (field.IsNullable)
1✔
304
                {
305
                    sb.AppendLine($"            result.{field.Name} = JsonSerializer.Deserialize<{field.Type}>({camelFieldName}Elem.GetRawText());");
1✔
306
                }
307
                else
308
                {
309
                    sb.AppendLine($"            result.{field.Name} = JsonSerializer.Deserialize<{field.Type}>({camelFieldName}Elem.GetRawText()) ?? throw new JsonException(\"Non-nullable field '{field.Name}' was null in JSON\");");
1✔
310
                }
311
            }
312
            else if (field.JsonTypeName == "String")
1✔
313
            {
314
                if (field.IsNullable)
1✔
315
                {
316
                    sb.AppendLine($"            result.{field.Name} = {camelFieldName}Elem.GetString();");
×
317
                }
318
                else
319
                {
320
                    sb.AppendLine($"            result.{field.Name} = {camelFieldName}Elem.GetString() ?? throw new JsonException(\"Non-nullable field '{field.Name}' was null in JSON\");");
1✔
321
                }
322
            }
323
            else
324
            {
325
                // Value types like int, bool, etc. - GetInt32(), GetBoolean(), etc. never return null
326
                sb.AppendLine($"            result.{field.Name} = {camelFieldName}Elem.Get{field.JsonTypeName}();");
1✔
327
            }
328

329
            sb.AppendLine("        }");
1✔
330
        }
331

332
        sb.AppendLine("        return result;");
1✔
333
        sb.AppendLine("    }");
1✔
334
        sb.AppendLine();
1✔
335

336
        // Serialize method
337
        sb.AppendLine($"    private static JsonElement Serialize_{component.Name}({component.FullName} value)");
1✔
338
        sb.AppendLine("    {");
1✔
339
        sb.AppendLine("        using var stream = new System.IO.MemoryStream();");
1✔
340
        sb.AppendLine("        using var writer = new Utf8JsonWriter(stream);");
1✔
341
        sb.AppendLine("        writer.WriteStartObject();");
1✔
342

343
        foreach (var field in component.Fields)
1✔
344
        {
345
            var camelFieldName = StringHelpers.ToCamelCase(field.Name);
1✔
346

347
            if (field.JsonTypeName == "Object")
1✔
348
            {
349
                sb.AppendLine($"        writer.WritePropertyName(\"{camelFieldName}\");");
1✔
350
                sb.AppendLine($"        JsonSerializer.Serialize(writer, value.{field.Name});");
1✔
351
            }
352
            else if (field.JsonTypeName == "String")
1✔
353
            {
354
                sb.AppendLine($"        writer.WriteString(\"{camelFieldName}\", value.{field.Name});");
1✔
355
            }
356
            else if (field.JsonTypeName == "Boolean")
1✔
357
            {
358
                sb.AppendLine($"        writer.WriteBoolean(\"{camelFieldName}\", value.{field.Name});");
1✔
359
            }
360
            else
361
            {
362
                sb.AppendLine($"        writer.WriteNumber(\"{camelFieldName}\", value.{field.Name});");
1✔
363
            }
364
        }
365

366
        sb.AppendLine("        writer.WriteEndObject();");
1✔
367
        sb.AppendLine("        writer.Flush();");
1✔
368
        sb.AppendLine("        stream.Position = 0;");
1✔
369
        sb.AppendLine("        return JsonDocument.Parse(stream).RootElement.Clone();");
1✔
370
        sb.AppendLine("    }");
1✔
371
        sb.AppendLine();
1✔
372

373
        // Binary deserialize method
374
        sb.AppendLine($"    private static object DeserializeBinary_{component.Name}(BinaryReader reader)");
1✔
375
        sb.AppendLine("    {");
1✔
376
        sb.AppendLine($"        var result = new {component.FullName}();");
1✔
377

378
        foreach (var field in component.Fields)
1✔
379
        {
380
            var binaryRead = GetBinaryReadMethod(field.JsonTypeName, field.Type, field.IsNullable);
1✔
381
            sb.AppendLine($"        result.{field.Name} = {binaryRead};");
1✔
382
        }
383

384
        sb.AppendLine("        return result;");
1✔
385
        sb.AppendLine("    }");
1✔
386
        sb.AppendLine();
1✔
387

388
        // Binary serialize method
389
        sb.AppendLine($"    private static void SerializeBinary_{component.Name}({component.FullName} value, BinaryWriter writer)");
1✔
390
        sb.AppendLine("    {");
1✔
391

392
        foreach (var field in component.Fields)
1✔
393
        {
394
            var binaryWrite = GetBinaryWriteCode(field.JsonTypeName, field.Type, $"value.{field.Name}");
1✔
395
            sb.AppendLine($"        {binaryWrite}");
1✔
396
        }
397

398
        sb.AppendLine("    }");
1✔
399
        sb.AppendLine();
1✔
400
    }
1✔
401

402
    private static string GetBinaryReadMethod(string jsonTypeName, string type, bool isNullable)
403
    {
404
        return jsonTypeName switch
1✔
405
        {
1✔
406
            "Int32" => "reader.ReadInt32()",
1✔
407
            "Int64" => "reader.ReadInt64()",
1✔
408
            "Int16" => "reader.ReadInt16()",
×
409
            "Byte" => "reader.ReadByte()",
×
410
            "UInt32" => "reader.ReadUInt32()",
×
411
            "UInt64" => "reader.ReadUInt64()",
×
412
            "UInt16" => "reader.ReadUInt16()",
×
413
            "SByte" => "reader.ReadSByte()",
×
414
            "Single" => "reader.ReadSingle()",
1✔
415
            "Double" => "reader.ReadDouble()",
1✔
416
            "Decimal" => "reader.ReadDecimal()",
1✔
417
            "Boolean" => "reader.ReadBoolean()",
1✔
418
            "String" => "reader.ReadString()", // BinaryReader.ReadString() never returns null
1✔
419
            _ => isNullable
1✔
420
                ? $"JsonSerializer.Deserialize<{type}>(reader.ReadString())"
1✔
421
                : $"JsonSerializer.Deserialize<{type}>(reader.ReadString()) ?? throw new InvalidDataException(\"Non-nullable field was null in binary data\")"
1✔
422
        };
1✔
423
    }
424

425
    private static string GetBinaryWriteCode(string jsonTypeName, string type, string valueExpr)
426
    {
427
        return jsonTypeName switch
1✔
428
        {
1✔
429
            "Int32" or "Int64" or "Int16" or "Byte" or
1✔
430
            "UInt32" or "UInt64" or "UInt16" or "SByte" or
1✔
431
            "Single" or "Double" or "Decimal" or "Boolean" or "String"
1✔
432
                => $"writer.Write({valueExpr});",
1✔
433
            _ => $"writer.Write(JsonSerializer.Serialize({valueExpr}));" // Complex types as JSON
1✔
434
        };
1✔
435
    }
436

437
    private sealed record SerializableComponentInfo(
1✔
438
        string Name,
1✔
439
        string Namespace,
×
440
        string FullName,
1✔
441
        ImmutableArray<SerializableFieldInfo> Fields);
1✔
442

443
    private sealed record SerializableFieldInfo(
1✔
444
        string Name,
1✔
445
        string Type,
1✔
446
        string JsonTypeName,
1✔
447
        bool IsNullable);
1✔
448
}
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