Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactoring of the Source Code Generator #1141

Merged
merged 3 commits into from
Feb 3, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/runtests.yml
Original file line number Diff line number Diff line change
@@ -22,7 +22,7 @@ jobs:
with:
fetch-depth: 0 # needed for GitVersioning to work
- name: Setup NuGet
uses: NuGet/setup-nuget@v1
uses: NuGet/setup-nuget@v2
- name: Cache NuGet packages
uses: actions/cache@v4
id: cache
@@ -61,7 +61,7 @@ jobs:
with:
fetch-depth: 0
- name: Setup NuGet
uses: NuGet/setup-nuget@v1
uses: NuGet/setup-nuget@v2
- name: Cache NuGet packages
uses: actions/cache@v4
id: cache
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -361,3 +361,5 @@ Tests/W3CTestSuite/images
Tests/W3CTestSuite/png
Tests/W3CTestSuite/resources
Tests/W3CTestSuite/svg

Source/Generated/**/*.cs
235 changes: 9 additions & 226 deletions Generators/AvailableElementsGenerator.cs
Original file line number Diff line number Diff line change
@@ -5,7 +5,6 @@
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;

namespace Svg.Generators
@@ -26,78 +25,6 @@ public class AvailableElementsGenerator : ISourceGenerator
DiagnosticSeverity.Error,
isEnabledByDefault: true);

#region Model

/// <summary>
/// The object model used to generate SvgElements descriptors.
/// </summary>
private const string ModelText = @"// <auto-generated />
#nullable disable
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;

namespace Svg
{
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
public sealed class ElementFactoryAttribute : Attribute
{
}

internal enum DescriptorType
{
Property,
Event
}

internal interface ISvgPropertyDescriptor
{
DescriptorType DescriptorType { get; }
string AttributeName { get; }
string AttributeNamespace { get; }
TypeConverter Converter { get; }
Type Type { get; }
object GetValue(object component);
void SetValue(object component, ITypeDescriptorContext context, CultureInfo culture, object value);
}

internal class SvgPropertyDescriptor<T, TU> : ISvgPropertyDescriptor
{
public DescriptorType DescriptorType { get; }
public string AttributeName { get; }
public string AttributeNamespace { get; }
public TypeConverter Converter { get; }
public Type Type { get; } = typeof(TU);
private Func<T, TU> Getter { get; }
private Action<T, TU> Setter { get; }

public SvgPropertyDescriptor(DescriptorType descriptorType, string attributeName, string attributeNamespace, TypeConverter converter, Func<T, TU> getter, Action<T, TU> setter)
{
DescriptorType = descriptorType;
AttributeName = attributeName;
AttributeNamespace = attributeNamespace;
Converter = converter;
Getter = getter;
Setter = setter;
}

public object GetValue(object component)
{
return (object)Getter((T)component);
}

public void SetValue(object component, ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (Converter != null)
{
Setter((T)component, (TU)Converter.ConvertFrom(context, culture, value));
}
}
}
}";
#endregion

/// <inheritdoc/>
public void Initialize(GeneratorInitializationContext context)
{
@@ -109,17 +36,13 @@ public void Initialize(GeneratorInitializationContext context)
/// <inheritdoc/>
public void Execute(GeneratorExecutionContext context)
{
// Add the ElementFactory model source to compilation.
context.AddSource("Svg_Model", SourceText.From(ModelText, Encoding.UTF8));

// Check is we have our SyntaxReceiver object used to filter compiled code.
if (!(context.SyntaxReceiver is SyntaxReceiver receiver))
{
return;
}

var options = (context.Compilation as CSharpCompilation)?.SyntaxTrees[0].Options as CSharpParseOptions;
var compilation = context.Compilation.AddSyntaxTrees(CSharpSyntaxTree.ParseText(SourceText.From(ModelText, Encoding.UTF8), options));
var compilation = context.Compilation;

var elementFactoryAttribute = compilation.GetTypeByMetadataName("Svg.ElementFactoryAttribute");
if (elementFactoryAttribute is null)
@@ -367,7 +290,7 @@ internal virtual bool SetValue(string attributeName, ITypeDescriptorContext cont
}}
}}
");
context.AddSource($"Svg_SvgElement_Properties.cs", SourceText.From(source.ToString(), Encoding.UTF8));
context.AddSource($"Svg.SvgElement.Properties.g.cs", SourceText.From(source.ToString(), Encoding.UTF8));
source.Clear();

// Generate SvgElement derived classes with descriptor Properties dictionary.
@@ -456,7 +379,7 @@ internal override bool SetValue(string attributeName, ITypeDescriptorContext con
}}
}}
");
context.AddSource($"{namespaceElement.Replace('.', '_')}_{element.Symbol.Name}_Properties.cs", SourceText.From(source.ToString(), Encoding.UTF8));
context.AddSource($"{namespaceElement}.{element.Symbol.Name}.Properties.g.cs", SourceText.From(source.ToString(), Encoding.UTF8));
source.Clear();
}

@@ -492,7 +415,7 @@ internal static class SvgElements
}
}
");
context.AddSource($"Svg_SvgElements.cs", SourceText.From(source.ToString(), Encoding.UTF8));
context.AddSource($"Svg.SvgElements.g.cs", SourceText.From(source.ToString(), Encoding.UTF8));
source.Clear();

// Generate ElementFactory class.
@@ -586,7 +509,7 @@ internal partial class {classElementFactory}
}}
}}");

context.AddSource($"{namespaceElementFactory.Replace('.', '_')}_{elementFactorySymbol.Name}_ElementFactory.cs", SourceText.From(source.ToString(), Encoding.UTF8));
context.AddSource($"{namespaceElementFactory}.{elementFactorySymbol.Name}.ElementFactory.g.cs", SourceText.From(source.ToString(), Encoding.UTF8));
source.Clear();
}

@@ -755,6 +678,10 @@ private static IEnumerable<INamedTypeSymbol> GetBaseTypes(INamedTypeSymbol named
{
"System.String" => "System.ComponentModel.StringConverter",
"System.Single" => "System.ComponentModel.SingleConverter",
"System.Int16" => "System.ComponentModel.Int16Converter",
"System.Int32" => "System.ComponentModel.Int32Converter",
"System.Int64" => "System.ComponentModel.Int64Converter",
"System.Boolean" => "System.ComponentModel.BooleanConverter",
"System.Uri" => "System.UriTypeConverter",
_ => null
};
@@ -863,149 +790,5 @@ private static IEnumerable<Property> GetElementProperties(Compilation compilatio
}
}
}

/// <summary>
/// Symbol member type.
/// </summary>
private enum MemberType
{
/// <summary>
/// Property symbol.
/// </summary>
Property,
/// <summary>
/// Event symbol.
/// </summary>
Event
}

/// <summary>
/// The SvgElement object property/event.
/// </summary>
private class Property
{
/// <summary>
/// Gets or sets property/event symbol.
/// </summary>
public ISymbol Symbol { get; }

/// <summary>
/// Gets or sets property/event symbol member type.
/// </summary>
public MemberType MemberType { get; }

/// <summary>
/// Gets or sets property/event attribute name.
/// </summary>
public string AttributeName { get; }

/// <summary>
/// Gets or sets property/event attribute namespace.
/// </summary>
public string AttributeNamespace { get; }

/// <summary>
/// Gets or sets property/event type converter type string.
/// </summary>
public string? Converter { get; }

/// <summary>
/// Initializes a new instance of the <see cref="Property"/> class.
/// </summary>
/// <param name="symbol">The property/event symbol.</param>
/// <param name="memberType">The property/event symbol member type.</param>
/// <param name="attributeName">The property/event attribute name.</param>
/// <param name="attributeNamespace">The property/event attribute namespace.</param>
/// <param name="converter">The property/event type converter type string.</param>
public Property(ISymbol symbol, MemberType memberType, string attributeName, string attributeNamespace, string? converter)
{
Symbol = symbol;
MemberType = memberType;
AttributeName = attributeName;
AttributeNamespace = attributeNamespace;
Converter = converter;
}
}

/// <summary>
/// Custom <see cref="Property"/> equality comparer using <see cref="ISymbol"/> for cmparison.
/// </summary>
private class PropertyEqualityComparer : IEqualityComparer<Property>
{
/// <inheritdoc/>
public bool Equals(Property p1, Property p2)
{
return SymbolEqualityComparer.Default.Equals(p1.Symbol, p2.Symbol);
}

/// <inheritdoc/>
public int GetHashCode(Property p)
{
#pragma warning disable RS1024
return p.Symbol.GetHashCode();
#pragma warning restore RS1024
}
}

/// <summary>
/// The SvgElement object.
/// </summary>
private class Element
{
/// <summary>
/// Gets or sets element type symbol.
/// </summary>
public INamedTypeSymbol Symbol { get; }

/// <summary>
/// Gets or sets element name.
/// </summary>
public string? ElementName { get; }

/// <summary>
/// Gets or sets classes that use element name.
/// </summary>
public List<string> ClassNames { get; }

/// <summary>
/// Gets or sets element properties list.
/// </summary>
public List<Property> Properties { get; }

/// <summary>
/// Initializes a new instance of the <see cref="Element"/> class.
/// </summary>
/// <param name="symbol">The element type symbol.</param>
/// <param name="elementName">The element name.</param>
/// <param name="classNames">The classes that use element name.</param>
/// <param name="properties">The element properties list.</param>
public Element(INamedTypeSymbol symbol, string? elementName, List<string> classNames, List<Property> properties)
{
Symbol = symbol;
ElementName = elementName;
ClassNames = classNames;
Properties = properties;
}
}

/// <summary>
/// The SyntaxReceiver is used to filter compiled code. This enable quick and easy way to filter compiled code.
/// </summary>
private class SyntaxReceiver : ISyntaxReceiver
{
/// <summary>
/// Gets the list of all candidate class.
/// </summary>
public List<ClassDeclarationSyntax> CandidateClasses { get; } = new();

/// <inheritdoc/>
public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
{
if (syntaxNode is ClassDeclarationSyntax classDeclarationSyntax)
{
CandidateClasses.Add(classDeclarationSyntax);
}
}
}
}
}
46 changes: 46 additions & 0 deletions Generators/Element.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using System.Collections.Generic;
using Microsoft.CodeAnalysis;

namespace Svg.Generators
{
/// <summary>
/// The SvgElement object.
/// </summary>
class Element
{
/// <summary>
/// Gets or sets element type symbol.
/// </summary>
public INamedTypeSymbol Symbol { get; }

/// <summary>
/// Gets or sets element name.
/// </summary>
public string? ElementName { get; }

/// <summary>
/// Gets or sets classes that use element name.
/// </summary>
public List<string> ClassNames { get; }

/// <summary>
/// Gets or sets element properties list.
/// </summary>
public List<Property> Properties { get; }

/// <summary>
/// Initializes a new instance of the <see cref="Element"/> class.
/// </summary>
/// <param name="symbol">The element type symbol.</param>
/// <param name="elementName">The element name.</param>
/// <param name="classNames">The classes that use element name.</param>
/// <param name="properties">The element properties list.</param>
public Element(INamedTypeSymbol symbol, string? elementName, List<string> classNames, List<Property> properties)
{
Symbol = symbol;
ElementName = elementName;
ClassNames = classNames;
Properties = properties;
}
}
}
17 changes: 17 additions & 0 deletions Generators/MemberType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
namespace Svg.Generators
{
/// <summary>
/// Symbol member type.
/// </summary>
enum MemberType
{
/// <summary>
/// Property symbol.
/// </summary>
Property,
/// <summary>
/// Event symbol.
/// </summary>
Event
}
}
Loading