using System; using System.Collections.Generic; using System.Linq; using MapTo.Extensions; using MapTo.Sources; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Shouldly; namespace MapTo.Tests { internal static class Common { internal const int Indent1 = 4; internal const int Indent2 = Indent1 * 2; internal const int Indent3 = Indent1 * 3; internal static readonly Location IgnoreLocation = Location.None; internal static readonly Dictionary DefaultAnalyzerOptions = new() { [GeneratorExecutionContextExtensions.GetBuildPropertyName(nameof(SourceGenerationOptions.GenerateXmlDocument))] = "false" }; internal static string GetSourceText(SourceGeneratorOptions? options = null) { const string ns = "Test"; options ??= new SourceGeneratorOptions(); var hasDifferentSourceNamespace = options.SourceClassNamespace != ns; var builder = new SourceBuilder(); builder.WriteLine("//"); builder.WriteLine("// Test source code."); builder.WriteLine("//"); builder.WriteLine(); options.Usings?.ForEach(s => builder.WriteLine($"using {s};")); if (options.UseMapToNamespace) { builder.WriteLine($"using {Constants.RootNamespace};"); } builder .WriteLine($"using {options.SourceClassNamespace};") .WriteLine() .WriteLine(); builder .WriteLine($"namespace {ns}") .WriteOpeningBracket(); if (hasDifferentSourceNamespace && options.UseMapToNamespace) { builder .WriteLine($"using {options.SourceClassNamespace};") .WriteLine() .WriteLine(); } builder .WriteLine(options.UseMapToNamespace ? "[MapFrom(typeof(Baz))]" : "[MapTo.MapFrom(typeof(Baz))]") .WriteLine("public partial class Foo") .WriteOpeningBracket(); for (var i = 1; i <= options.ClassPropertiesCount; i++) { builder.WriteLine(i % 2 == 0 ? $"public int Prop{i} {{ get; set; }}" : $"public int Prop{i} {{ get; }}"); } options.PropertyBuilder?.Invoke(builder); builder .WriteClosingBracket() .WriteClosingBracket() .WriteLine() .WriteLine(); builder .WriteLine($"namespace {options.SourceClassNamespace}") .WriteOpeningBracket() .WriteLine("public class Baz") .WriteOpeningBracket(); for (var i = 1; i <= options.SourceClassPropertiesCount; i++) { builder.WriteLine(i % 2 == 0 ? $"public int Prop{i} {{ get; set; }}" : $"public int Prop{i} {{ get; }}"); } options.SourcePropertyBuilder?.Invoke(builder); builder .WriteClosingBracket() .WriteClosingBracket(); return builder.ToString(); } internal static string[] GetEmployeeManagerSourceText( Func? employeeClassSource = null, Func? managerClassSource = null, Func? employeeViewModelSource = null, Func? managerViewModelSource = null, bool useDifferentViewModelNamespace = false) { return new[] { employeeClassSource?.Invoke() ?? DefaultEmployeeClassSource(), managerClassSource?.Invoke() ?? DefaultManagerClassSource(), employeeViewModelSource?.Invoke() ?? DefaultEmployeeViewModelSource(useDifferentViewModelNamespace), managerViewModelSource?.Invoke() ?? DefaultManagerViewModelSource(useDifferentViewModelNamespace) }; static string DefaultEmployeeClassSource() => @" using System; using System.Collections.Generic; using System.Text; namespace Test.Data.Models { public class Employee { public int Id { get; set; } public string EmployeeCode { get; set; } public Manager Manager { get; set; } } }".Trim(); static string DefaultManagerClassSource() => @"using System; using System.Collections.Generic; using System.Text; namespace Test.Data.Models { public class Manager: Employee { public int Level { get; set; } public IEnumerable Employees { get; set; } = Array.Empty(); } } ".Trim(); static string DefaultEmployeeViewModelSource(bool useDifferentNamespace) => useDifferentNamespace ? @" using MapTo; using Test.Data.Models; using Test.ViewModels2; namespace Test.ViewModels { [MapFrom(typeof(Employee))] public partial class EmployeeViewModel { public int Id { get; set; } public string EmployeeCode { get; set; } public ManagerViewModel Manager { get; set; } } } ".Trim() : @" using MapTo; using Test.Data.Models; namespace Test.ViewModels { [MapFrom(typeof(Employee))] public partial class EmployeeViewModel { public int Id { get; set; } public string EmployeeCode { get; set; } public ManagerViewModel Manager { get; set; } } } ".Trim(); static string DefaultManagerViewModelSource(bool useDifferentNamespace) => useDifferentNamespace ? @" using System; using System.Collections.Generic; using MapTo; using Test.Data.Models; using Test.ViewModels; namespace Test.ViewModels2 { [MapFrom(typeof(Manager))] public partial class ManagerViewModel : EmployeeViewModel { public int Level { get; set; } public IEnumerable Employees { get; set; } = Array.Empty(); } } ".Trim() : @" using System; using System.Collections.Generic; using MapTo; using Test.Data.Models; namespace Test.ViewModels { [MapFrom(typeof(Manager))] public partial class ManagerViewModel : EmployeeViewModel { public int Level { get; set; } public IEnumerable Employees { get; set; } = Array.Empty(); } }".Trim(); } internal static PropertyDeclarationSyntax GetPropertyDeclarationSyntax(SyntaxTree syntaxTree, string targetPropertyName, string targetClass = "Foo") { return syntaxTree.GetRoot() .DescendantNodes() .OfType() .Single(c => c.Identifier.ValueText == targetClass) .DescendantNodes() .OfType() .Single(p => p.Identifier.ValueText == targetPropertyName); } internal static IPropertySymbol GetSourcePropertySymbol(string propertyName, Compilation compilation, string targetClass = "Foo") { var syntaxTree = compilation.SyntaxTrees.First(); var propSyntax = GetPropertyDeclarationSyntax(syntaxTree, propertyName, targetClass); var semanticModel = compilation.GetSemanticModel(syntaxTree); return semanticModel.GetDeclaredSymbol(propSyntax).ShouldNotBeNull(); } internal record SourceGeneratorOptions( bool UseMapToNamespace = false, string SourceClassNamespace = "Test.Models", int ClassPropertiesCount = 3, int SourceClassPropertiesCount = 3, Action? PropertyBuilder = null, Action? SourcePropertyBuilder = null, IEnumerable? Usings = null); } }