using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Text; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Text; namespace G; [Generator] public class TestsGenerator : IIncrementalGenerator { private const string UnitAttributeFullName = "QWERTYkez.Mensura.UnitGeneratorAttribute"; private const string ComplexAttributeFullName = "QWERTYkez.Mensura.ComplexUnitGeneratorAttribute"; public void Initialize(IncrementalGeneratorInitializationContext context) { var compilationProvider = context.CompilationProvider; context.RegisterSourceOutput(compilationProvider, (spc, compilation) => { if (compilation.AssemblyName == null || !compilation.AssemblyName.EndsWith(".Tests")) return; var mensuraAssembly = compilation.References .Select(r => compilation.GetAssemblyOrModuleSymbol(r) as IAssemblySymbol) .FirstOrDefault(a => a?.Name == "QWERTYkez.Mensura" || a?.Name?.EndsWith(".Mensura") == true); if (mensuraAssembly == null) return; var targetTypes = new List(); CollectTypesWithAttribute(mensuraAssembly.GlobalNamespace, targetTypes, UnitAttributeFullName); CollectTypesWithAttribute(mensuraAssembly.GlobalNamespace, targetTypes, ComplexAttributeFullName); if (targetTypes.Count == 0) return; foreach (var type in targetTypes) { var unitProperties = GetStaticProperties(type); if (unitProperties.Length == 0) continue; var firstNonBase = unitProperties.FirstOrDefault(p => !p.Name.StartsWith("_")); if (firstNonBase.Name == null) continue; // Генерируем два файла GenerateSerializationTest(spc, type.Name, firstNonBase.Name, "SystemText", "System.Text.Json"); GenerateSerializationTest(spc, type.Name, firstNonBase.Name, "Newtonsoft", "Newtonsoft.Json"); } }); } private static void GenerateSerializationTest(SourceProductionContext spc, string typeName, string unitName, string framework, string namespaceName) { var sb = new StringBuilder(); sb.AppendLine("// "); sb.AppendLine("#pragma warning disable"); sb.AppendLine(); sb.AppendLine("using Xunit;"); sb.AppendLine($"using {namespaceName};"); sb.AppendLine("using QWERTYkez.Mensura.Units;"); sb.AppendLine(); sb.AppendLine($"namespace QWERTYkez.Mensura.Tests"); sb.AppendLine("{"); sb.AppendLine($" public class {typeName}Serialization_{framework}Tests"); sb.AppendLine(" {"); sb.AppendLine($" private const double Tolerance = 1e-12;"); sb.AppendLine($" private readonly {typeName} _testValue = {typeName}.{unitName};"); sb.AppendLine(); // Тест sb.AppendLine(" [Fact]"); sb.AppendLine($" public void {framework}_SerializeDeserialize_ReturnsEqualValue()"); sb.AppendLine(" {"); if (framework == "SystemText") { sb.AppendLine(" var json = System.Text.Json.JsonSerializer.Serialize(_testValue);"); sb.AppendLine($" var deserialized = System.Text.Json.JsonSerializer.Deserialize<{typeName}>(json);"); } else { sb.AppendLine(" var json = Newtonsoft.Json.JsonConvert.SerializeObject(_testValue);"); sb.AppendLine($" var deserialized = Newtonsoft.Json.JsonConvert.DeserializeObject<{typeName}>(json);"); } sb.AppendLine(" Assert.Equal((double)_testValue, (double)deserialized, Tolerance);"); sb.AppendLine(" }"); sb.AppendLine(); sb.AppendLine(" }"); sb.AppendLine("}"); spc.AddSource($"Serialize.{framework}.{typeName}.g.cs", SourceText.From(sb.ToString(), Encoding.UTF8)); } private static void CollectTypesWithAttribute(INamespaceSymbol ns, List results, string attributeFullName) { foreach (var type in ns.GetTypeMembers()) { if (HasAttribute(type, attributeFullName)) results.Add(type); } foreach (var childNs in ns.GetNamespaceMembers()) { CollectTypesWithAttribute(childNs, results, attributeFullName); } } private static bool HasAttribute(INamedTypeSymbol type, string attributeFullName) { foreach (var attr in type.GetAttributes()) { if (attr.AttributeClass?.ToString() == attributeFullName) return true; } return false; } private static ImmutableArray GetStaticProperties(INamedTypeSymbol type) { var props = new List(); foreach (var member in type.GetMembers()) { if (member is IPropertySymbol prop && prop.IsStatic && prop.DeclaredAccessibility == Accessibility.Public) { var propName = prop.Name; if (propName == "EqualityContract" || propName.StartsWith("<")) continue; props.Add(new UnitProperty(propName, prop.Type.ToString())); } } return props.ToImmutableArray(); } private readonly struct UnitProperty { public string Name { get; } public string Type { get; } public UnitProperty(string name, string type) => (Name, Type) = (name, type); } }