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
Show file tree
Hide file tree
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
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Up @@ -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
Expand All @@ -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)
{
Expand All @@ -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)
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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();
}

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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();
}

Expand Down Expand Up @@ -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
};
Expand Down Expand Up @@ -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