/// <inheritdoc /> public void Execute(GeneratorExecutionContext context) { try { var options = SourceGenerationOptions.From(context); var compilation = context.Compilation .AddSource(ref context, MapFromAttributeSource.Generate(options)) .AddSource(ref context, IgnorePropertyAttributeSource.Generate(options)) .AddSource(ref context, ITypeConverterSource.Generate(options)) .AddSource(ref context, MapTypeConverterAttributeSource.Generate(options)) .AddSource(ref context, MapPropertyAttributeSource.Generate(options)) .AddSource(ref context, MappingContextSource.Generate(options)); if (context.SyntaxReceiver is MapToSyntaxReceiver receiver && receiver.CandidateTypes.Any()) { AddGeneratedMappingsClasses(context, compilation, receiver.CandidateTypes, options); } } catch (Exception ex) { Console.WriteLine(ex); throw; } }
internal static SourceCode Generate(SourceGenerationOptions options) { using var builder = new SourceBuilder() .WriteLine(GeneratedFilesHeader) .WriteLine("using System;") .WriteLine() .WriteLine($"namespace {RootNamespace}") .WriteOpeningBracket(); if (options.GenerateXmlDocument) { builder .WriteLine("/// <summary>") .WriteLine("/// Specifies that the annotated class can be mapped from the provided <see cref=\"SourceType\"/>.") .WriteLine("/// </summary>"); } builder .WriteLine("[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]") .WriteLine($"public sealed class {AttributeName}Attribute : Attribute") .WriteOpeningBracket(); if (options.GenerateXmlDocument) { builder .WriteLine("/// <summary>") .WriteLine($"/// Initializes a new instance of the <see cref=\"{AttributeName}Attribute\"/> class with the specified <paramref name=\"sourceType\"/>.") .WriteLine("/// </summary>") .WriteLine("/// <param name=\"sourceType\">The type of to map from.</param>"); } builder .WriteLine($"public {AttributeName}Attribute(Type sourceType)") .WriteOpeningBracket() .WriteLine("SourceType = sourceType;") .WriteClosingBracket() .WriteLine(); if (options.GenerateXmlDocument) { builder .WriteLine("/// <summary>") .WriteLine("/// Gets the type to map from.") .WriteLine("/// </summary>"); } builder .WriteLine("public Type SourceType { get; }") .WriteClosingBracket() // class .WriteClosingBracket(); // namespace return(new(builder.ToString(), $"{AttributeName}Attribute.g.cs")); }
public static MappingContext Create(Compilation compilation, SourceGenerationOptions sourceGenerationOptions, TypeDeclarationSyntax typeSyntax) { MappingContext context = typeSyntax switch { ClassDeclarationSyntax => new ClassMappingContext(compilation, sourceGenerationOptions, typeSyntax), RecordDeclarationSyntax => new RecordMappingContext(compilation, sourceGenerationOptions, typeSyntax), _ => throw new ArgumentOutOfRangeException() }; context.Model = context.CreateMappingModel(); return(context); }
public override string[] GenerateSource(SourceGenerationOptions options) { var generatedClassName = options.Type.Name + "_" + options.FactoryOptions.ClassName; var text = this.CSharpTemplate() .Replace("<codeGenerationTool>", $"{nameof(ConsoleTelemeter)} {Assembly.GetExecutingAssembly().GetName().Version} {DateTimeOffset.UtcNow:yyyy-MM-dd HH:mm:ss zzz}") .Replace("<generatedNamespace>", options.FactoryOptions.Namespace) .Replace("<generatedClassName>", generatedClassName) .Replace("<interfaceImplemented>", options.Type.FullName) .Replace("//<privateFields>", "private const string Null = \"<null>\";") .Replace("//<publicMethodImplementations>", Methods(options.Methods)) .Replace("\t", " ") ; return(new[] { text, $"{options.FactoryOptions.Namespace}.{generatedClassName}" }); }
internal static SourceCode Generate(SourceGenerationOptions options) { using var builder = new SourceBuilder() .WriteLine(GeneratedFilesHeader) .WriteNullableContextOptionIf(options.SupportNullableReferenceTypes) .WriteLine() .WriteLine("using System;") .WriteLine() .WriteLine($"namespace {RootNamespace}") .WriteOpeningBracket(); if (options.GenerateXmlDocument) { builder .WriteLine("/// <summary>") .WriteLine("/// Specifies the mapping behavior of the annotated property.") .WriteLine("/// </summary>") .WriteLine("/// <remarks>") .WriteLine($"/// {AttributeClassName} has a number of uses:") .WriteLine("/// <list type=\"bullet\">") .WriteLine("/// <item><description>By default properties with same name will get mapped. This attribute allows the names to be different.</description></item>") .WriteLine("/// <item><description>Indicates that a property should be mapped when member serialization is set to opt-in.</description></item>") .WriteLine("/// </list>") .WriteLine("/// </remarks>"); } builder .WriteLine("[AttributeUsage(AttributeTargets.Property | AttributeTargets.Parameter, AllowMultiple = true)]") .WriteLine($"public sealed class {AttributeClassName} : Attribute") .WriteOpeningBracket(); if (options.GenerateXmlDocument) { builder .WriteLine("/// <summary>") .WriteLine("/// Gets or sets the property name of the object to mapping from.") .WriteLine("/// </summary>"); } builder .WriteLine($"public string{options.NullableReferenceSyntax} {SourcePropertyNamePropertyName} {{ get; set; }}") .WriteClosingBracket() // class .WriteClosingBracket(); // namespace return(new(builder.ToString(), $"{AttributeClassName}.g.cs")); }
protected MappingContext(Compilation compilation, SourceGenerationOptions sourceGenerationOptions, TypeDeclarationSyntax typeSyntax) { _ignoredNamespaces = new(); Diagnostics = ImmutableArray <Diagnostic> .Empty; Usings = ImmutableArray.Create("System", Constants.RootNamespace); SourceGenerationOptions = sourceGenerationOptions; TypeSyntax = typeSyntax; Compilation = compilation; IgnorePropertyAttributeTypeSymbol = compilation.GetTypeByMetadataNameOrThrow(IgnorePropertyAttributeSource.FullyQualifiedName); MapTypeConverterAttributeTypeSymbol = compilation.GetTypeByMetadataNameOrThrow(MapTypeConverterAttributeSource.FullyQualifiedName); TypeConverterInterfaceTypeSymbol = compilation.GetTypeByMetadataNameOrThrow(ITypeConverterSource.FullyQualifiedName); MapPropertyAttributeTypeSymbol = compilation.GetTypeByMetadataNameOrThrow(MapPropertyAttributeSource.FullyQualifiedName); MapFromAttributeTypeSymbol = compilation.GetTypeByMetadataNameOrThrow(MapFromAttributeSource.FullyQualifiedName); MappingContextTypeSymbol = compilation.GetTypeByMetadataNameOrThrow(MappingContextSource.FullyQualifiedName); AddUsingIfRequired(sourceGenerationOptions.SupportNullableStaticAnalysis, "System.Diagnostics.CodeAnalysis"); }
internal static SourceCode Generate(SourceGenerationOptions options) { using var builder = new SourceBuilder() .WriteLine(GeneratedFilesHeader) .WriteNullableContextOptionIf(options.SupportNullableReferenceTypes) .WriteLine() .WriteLine($"namespace {RootNamespace}") .WriteOpeningBracket(); if (options.GenerateXmlDocument) { builder .WriteLine("/// <summary>") .WriteLine("/// Converts the value of <typeparamref name=\"TSource\"/> to <typeparamref name=\"TDestination\"/>.") .WriteLine("/// </summary>") .WriteLine("/// <typeparam name=\"TSource\">The type to convert from.</typeparam>") .WriteLine("/// <typeparam name=\"TDestination\">The type to convert to.</typeparam>"); } builder .WriteLine($"public interface {InterfaceName}<in TSource, out TDestination>") .WriteOpeningBracket(); if (options.GenerateXmlDocument) { builder .WriteLine("/// <summary>") .WriteLine("/// Converts the value of <paramref name=\"source\"/> object to <typeparamref name=\"TDestination\"/>.") .WriteLine("/// </summary>") .WriteLine("/// <param name=\"source\">The <see cref=\"TSource\"/> to convert.</param>") .WriteLine($"/// <param name=\"converterParameters\">The parameter list passed to the <see cref=\"{MapTypeConverterAttributeSource.AttributeClassName}\"/></param>") .WriteLine("/// <returns><typeparamref name=\"TDestination\"/> object.</returns>"); } builder .WriteLine($"TDestination Convert(TSource source, object[]{options.NullableReferenceSyntax} converterParameters);") .WriteClosingBracket() .WriteClosingBracket(); return(new(builder.ToString(), $"{InterfaceName}.g.cs")); }
internal static SourceCode Generate(SourceGenerationOptions options) { var builder = new SourceBuilder() .WriteLine(GeneratedFilesHeader) .WriteLine("using System;") .WriteLine() .WriteLine($"namespace {RootNamespace}") .WriteOpeningBracket(); if (options.GenerateXmlDocument) { builder .WriteLine("/// <summary>") .WriteLine("/// Specifies that the annotated property should be excluded.") .WriteLine("/// </summary>"); } builder .WriteLine("[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)]") .WriteLine($"public sealed class {AttributeClassName} : Attribute {{ }}") .WriteClosingBracket(); return(new(builder.ToString(), $"{AttributeClassName}.g.cs")); }
internal static SourceCode Generate(SourceGenerationOptions options) { var usings = new List <string> { "System", "System.Collections.Generic", "System.Reflection" }; using var builder = new SourceBuilder() .WriteLine(GeneratedFilesHeader) .WriteLine() .WriteUsings(usings) .WriteLine() // Namespace declaration .WriteLine($"namespace {RootNamespace}") .WriteOpeningBracket() // Class declaration .WriteLine($"internal sealed class {ClassName}") .WriteOpeningBracket() .WriteLine("private readonly Dictionary<object, object> _cache;") .WriteLine() // Constructor .WriteLine($"internal {ClassName}()") .WriteOpeningBracket() .WriteLine("_cache = new Dictionary<object, object>(1);") .WriteClosingBracket() .WriteLine() // Factory .WriteLine($"internal static TMapped {FactoryMethodName}<TOriginal, TMapped>(TOriginal original)") .WriteOpeningBracket() .WriteLine("if (original == null) throw new ArgumentNullException(nameof(original));") .WriteLine() .WriteLine("var context = new MappingContext();") .WriteLine("var mapped = context.MapFromWithContext<TOriginal, TMapped>(original);") .WriteLine() .WriteLine("if (mapped == null)") .WriteOpeningBracket() .WriteLine("throw new InvalidOperationException();") .WriteClosingBracket() .WriteLine() .WriteLine("return mapped;") .WriteClosingBracket() .WriteLine() // MapFromWithContext method .WriteLine($"internal TMapped MapFromWithContext<TOriginal, TMapped>(TOriginal original)") .WriteOpeningBracket() .WriteLine("if (original == null)") .WriteOpeningBracket() .WriteLine("return default(TMapped);") .WriteClosingBracket() .WriteLine() .WriteLine("if (!TryGetValue<TOriginal, TMapped>(original, out var mapped))") .WriteOpeningBracket() .WriteLine("var instance = Activator.CreateInstance(typeof(TMapped), BindingFlags.Instance | BindingFlags.NonPublic, null, new object[] { this, original }, null);") .WriteLine("if (instance != null)") .WriteOpeningBracket() .WriteLine("mapped = (TMapped)instance;") .WriteClosingBracket() .WriteClosingBracket() .WriteLine() .WriteLine("return mapped;") .WriteClosingBracket() .WriteLine() // Register method .WriteLine("internal void Register<TOriginal, TMapped>(TOriginal original, TMapped mapped)") .WriteOpeningBracket() .WriteLine("if (original == null) throw new ArgumentNullException(nameof(original));") .WriteLine("if (mapped == null) throw new ArgumentNullException(nameof(mapped));") .WriteLine() .WriteLine("if (!_cache.ContainsKey(original))") .WriteOpeningBracket() .WriteLine("_cache.Add(original, mapped);") .WriteClosingBracket() .WriteClosingBracket() .WriteLine() // TryGetValue method .WriteLine("private bool TryGetValue<TOriginal, TMapped>(TOriginal original, out TMapped mapped)") .WriteOpeningBracket() .WriteLine("if (original != null && _cache.TryGetValue(original, out var value))") .WriteOpeningBracket() .WriteLine("mapped = (TMapped)value;") .WriteLine("return true;") .WriteClosingBracket() .WriteLine() .WriteLine("mapped = default(TMapped);") .WriteLine("return false;") .WriteClosingBracket() // End class declaration .WriteClosingBracket() // End namespace declaration .WriteClosingBracket(); return(new(builder.ToString(), $"{ClassName}.g.cs")); }
internal ClassMappingContext(Compilation compilation, SourceGenerationOptions sourceGenerationOptions, TypeDeclarationSyntax typeSyntax) : base(compilation, sourceGenerationOptions, typeSyntax) { }
internal static SourceCode Generate(SourceGenerationOptions options) { using var builder = new SourceBuilder() .WriteLine(GeneratedFilesHeader) .WriteNullableContextOptionIf(options.SupportNullableReferenceTypes) .WriteLine() .WriteLine("using System;") .WriteLine() .WriteLine($"namespace {RootNamespace}") .WriteOpeningBracket(); if (options.GenerateXmlDocument) { builder .WriteLine("/// <summary>") .WriteLine("/// Specifies what type to use as a converter for the property this attribute is bound to.") .WriteLine("/// </summary>"); } builder .WriteLine("[AttributeUsage(AttributeTargets.Property | AttributeTargets.Parameter, AllowMultiple = false)]") .WriteLine($"public sealed class {AttributeClassName} : Attribute") .WriteOpeningBracket(); if (options.GenerateXmlDocument) { builder .WriteLine("/// <summary>") .WriteLine($"/// Initializes a new instance of <see cref=\"{AttributeClassName}\"/>.") .WriteLine("/// </summary>") .WriteLine($"/// <param name=\"converter\">The <see cref=\"{ITypeConverterSource.InterfaceName}{{TSource,TDestination}}\" /> to be used to convert the source type.</param>") .WriteLine("/// <param name=\"converterParameters\">The list of parameters to pass to the <paramref name=\"converter\"/> during the type conversion.</param>"); } builder .WriteLine($"public {AttributeClassName}(Type converter, object[]{options.NullableReferenceSyntax} converterParameters = null)") .WriteOpeningBracket() .WriteLine($"{ConverterPropertyName} = converter;") .WriteLine($"{ConverterParametersPropertyName} = converterParameters;") .WriteClosingBracket() .WriteLine(); if (options.GenerateXmlDocument) { builder .WriteLine("/// <summary>") .WriteLine($"/// Gets or sets the <see cref=\"{ITypeConverterSource.InterfaceName}{{TSource,TDestination}}\" /> to be used to convert the source type.") .WriteLine("/// </summary>"); } builder .WriteLine($"public Type {ConverterPropertyName} {{ get; }}") .WriteLine(); if (options.GenerateXmlDocument) { builder .WriteLine("/// <summary>") .WriteLine($"/// Gets the list of parameters to pass to the <see cref=\"{ConverterPropertyName}\"/> during the type conversion.") .WriteLine("/// </summary>"); } builder .WriteLine($"public object[]{options.NullableReferenceSyntax} {ConverterParametersPropertyName} {{ get; }}") .WriteClosingBracket() .WriteClosingBracket(); return(new(builder.ToString(), $"{AttributeClassName}.g.cs")); }
private static void AddGeneratedMappingsClasses(GeneratorExecutionContext context, Compilation compilation, IEnumerable <TypeDeclarationSyntax> candidateTypes, SourceGenerationOptions options) { foreach (var typeDeclarationSyntax in candidateTypes) { var mappingContext = MappingContext.Create(compilation, options, typeDeclarationSyntax); mappingContext.Diagnostics.ForEach(context.ReportDiagnostic); if (mappingContext.Model is null) { continue; } var(source, hintName) = typeDeclarationSyntax switch { ClassDeclarationSyntax => MapClassSource.Generate(mappingContext.Model), RecordDeclarationSyntax => MapRecordSource.Generate(mappingContext.Model), _ => throw new ArgumentOutOfRangeException() }; context.AddSource(hintName, source); } } }