2026-06-08 01:20:30 +07:00
|
|
|
|
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) =>
|
|
|
|
|
|
{
|
2026-06-08 12:00:10 +07:00
|
|
|
|
if (compilation.AssemblyName == null || !compilation.AssemblyName.EndsWith(".Tests"))
|
2026-06-08 01:20:30 +07:00
|
|
|
|
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<INamedTypeSymbol>();
|
|
|
|
|
|
CollectTypesWithAttribute(mensuraAssembly.GlobalNamespace, targetTypes, UnitAttributeFullName);
|
|
|
|
|
|
CollectTypesWithAttribute(mensuraAssembly.GlobalNamespace, targetTypes, ComplexAttributeFullName);
|
|
|
|
|
|
|
|
|
|
|
|
if (targetTypes.Count == 0)
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var type in targetTypes)
|
|
|
|
|
|
{
|
|
|
|
|
|
var unitProperties = GetStaticProperties(type);
|
2026-06-08 12:00:10 +07:00
|
|
|
|
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");
|
2026-06-08 01:20:30 +07:00
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-08 12:00:10 +07:00
|
|
|
|
private static void GenerateSerializationTest(SourceProductionContext spc, string typeName, string unitName, string framework, string namespaceName)
|
|
|
|
|
|
{
|
|
|
|
|
|
var sb = new StringBuilder();
|
|
|
|
|
|
sb.AppendLine("// <auto-generated/>");
|
|
|
|
|
|
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));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-08 01:20:30 +07:00
|
|
|
|
private static void CollectTypesWithAttribute(INamespaceSymbol ns, List<INamedTypeSymbol> 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<UnitProperty> GetStaticProperties(INamedTypeSymbol type)
|
|
|
|
|
|
{
|
|
|
|
|
|
var props = new List<UnitProperty>();
|
|
|
|
|
|
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()));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-06-08 12:00:10 +07:00
|
|
|
|
return props.ToImmutableArray();
|
2026-06-08 01:20:30 +07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private readonly struct UnitProperty
|
|
|
|
|
|
{
|
|
|
|
|
|
public string Name { get; }
|
|
|
|
|
|
public string Type { get; }
|
|
|
|
|
|
public UnitProperty(string name, string type) => (Name, Type) = (name, type);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|