Refactoring of the Source Code Generator (#1141)

* Refactoring of the Source Code Generator

- Moved the Svg_Model.cs file as SvgModel.cs into the main source.
- Restore the standard naming of the generated code files with "*.g.cs" suffix.
- Set the output directory of the generated codes to "Generated".
- Added the Generated directory to the main source codes.
- Moved the Svg.Custom project to Tests directory.

* Update runtests.yml

- NuGet/setup-nuget@v1 to NuGet/setup-nuget@v2
- Needed to eliminate the warning: Node.js 16 actions are deprecated.

* Update AvailableElementsGenerator.cs

Removed the comment out portions.
This commit is contained in:
Paul Selormey 2024-02-04 03:23:06 +09:00 коммит произвёл GitHub
Родитель 02518c43c1
Коммит a2c69b70a1
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
14 изменённых файлов: 264 добавлений и 238 удалений

4
.github/workflows/runtests.yml поставляемый
Просмотреть файл

@ -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
.gitignore поставляемый
Просмотреть файл

@ -361,3 +361,5 @@ Tests/W3CTestSuite/images
Tests/W3CTestSuite/png
Tests/W3CTestSuite/resources
Tests/W3CTestSuite/svg
Source/Generated/**/*.cs

Просмотреть файл

@ -5,7 +5,6 @@ using System.Linq;
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 @@ namespace Svg.Generators
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 @@ namespace Svg
/// <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 @@ namespace Svg
}}
}}
");
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 @@ namespace {namespaceElement}
}}
}}
");
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 @@ namespace Svg
}
}
");
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 @@ namespace {namespaceElementFactory}
}}
}}");
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 @@ namespace {namespaceElementFactory}
{
"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 @@ namespace {namespaceElementFactory}
}
}
}
/// <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
Generators/Element.cs Normal file
Просмотреть файл

@ -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
Generators/MemberType.cs Normal file
Просмотреть файл

@ -0,0 +1,17 @@
namespace Svg.Generators
{
/// <summary>
/// Symbol member type.
/// </summary>
enum MemberType
{
/// <summary>
/// Property symbol.
/// </summary>
Property,
/// <summary>
/// Event symbol.
/// </summary>
Event
}
}

52
Generators/Property.cs Normal file
Просмотреть файл

@ -0,0 +1,52 @@
using Microsoft.CodeAnalysis;
namespace Svg.Generators
{
/// <summary>
/// The SvgElement object property/event.
/// </summary>
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;
}
}
}

Просмотреть файл

@ -0,0 +1,25 @@
using System.Collections.Generic;
using Microsoft.CodeAnalysis;
namespace Svg.Generators
{
/// <summary>
/// Custom <see cref="Property"/> equality comparer using <see cref="ISymbol"/> for cmparison.
/// </summary>
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
}
}
}

Просмотреть файл

@ -19,7 +19,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.4.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" PrivateAssets="all" />
</ItemGroup>

Просмотреть файл

@ -0,0 +1,26 @@
using System.Collections.Generic;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace Svg.Generators
{
/// <summary>
/// The SyntaxReceiver is used to filter compiled code. This enable quick and easy way to filter compiled code.
/// </summary>
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);
}
}
}
}

Просмотреть файл

@ -0,0 +1,5 @@
### Geneated
A directory of the generated source codes.
- The generated files are excluded from the repository.
- These files will not be directly included in the project.
- To view the generated files, turn "Show All Files" on in the VS.

Просмотреть файл

@ -37,7 +37,7 @@
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)\$(Configuration)\$(TargetFramework)\GeneratedFiles</CompilerGeneratedFilesOutputPath>
<CompilerGeneratedFilesOutputPath>Generated</CompilerGeneratedFilesOutputPath>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">

Просмотреть файл

@ -35,10 +35,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Svg.Benchmark", "..\Tests\S
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Generators", "Generators", "{FAFD6DC7-5203-4CED-A151-66379DD43695}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Svg.Custom", "..\Svg.Custom\Svg.Custom.csproj", "{6FAA2B79-FE5C-456B-A743-E9290665B223}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Svg.Tests.Common", "..\Tests\Svg.Tests.Common\Svg.Tests.Common.csproj", "{D7C625E8-79EA-4859-A457-2C4A90786790}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Svg.Custom", "..\Tests\Svg.Custom\Svg.Custom.csproj", "{AD112647-446B-4DAC-8D90-17B0F5FA7188}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -85,14 +85,14 @@ Global
{8DEB3EA7-5915-4EB9-8052-85A7AC4379EC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8DEB3EA7-5915-4EB9-8052-85A7AC4379EC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8DEB3EA7-5915-4EB9-8052-85A7AC4379EC}.Release|Any CPU.Build.0 = Release|Any CPU
{6FAA2B79-FE5C-456B-A743-E9290665B223}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6FAA2B79-FE5C-456B-A743-E9290665B223}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6FAA2B79-FE5C-456B-A743-E9290665B223}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6FAA2B79-FE5C-456B-A743-E9290665B223}.Release|Any CPU.Build.0 = Release|Any CPU
{D7C625E8-79EA-4859-A457-2C4A90786790}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D7C625E8-79EA-4859-A457-2C4A90786790}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D7C625E8-79EA-4859-A457-2C4A90786790}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D7C625E8-79EA-4859-A457-2C4A90786790}.Release|Any CPU.Build.0 = Release|Any CPU
{AD112647-446B-4DAC-8D90-17B0F5FA7188}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AD112647-446B-4DAC-8D90-17B0F5FA7188}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AD112647-446B-4DAC-8D90-17B0F5FA7188}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AD112647-446B-4DAC-8D90-17B0F5FA7188}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -108,6 +108,7 @@ Global
{BACDD1F4-B97D-4E27-BD09-6957633FF484} = {FAFD6DC7-5203-4CED-A151-66379DD43695}
{8DEB3EA7-5915-4EB9-8052-85A7AC4379EC} = {2EC3F3A0-F097-43EF-A603-9B82E3061469}
{D7C625E8-79EA-4859-A457-2C4A90786790} = {2EC3F3A0-F097-43EF-A603-9B82E3061469}
{AD112647-446B-4DAC-8D90-17B0F5FA7188} = {2EC3F3A0-F097-43EF-A603-9B82E3061469}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {5096EEB3-8F41-44B5-BCF9-58743A59AB21}

64
Source/SvgModel.cs Normal file
Просмотреть файл

@ -0,0 +1,64 @@
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));
}
}
}
}

Просмотреть файл

@ -11,7 +11,7 @@
<IsPackable>True</IsPackable>
<Nullable>disable</Nullable>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)\$(Configuration)\$(TargetFramework)\GeneratedFiles</CompilerGeneratedFilesOutputPath>
<CompilerGeneratedFilesOutputPath>$(SvgSourcesBasePath)\Generated</CompilerGeneratedFilesOutputPath>
<DefineConstants>$(DefineConstants);NO_SDC</DefineConstants>
<LangVersion>10.0</LangVersion>
</PropertyGroup>
@ -24,7 +24,7 @@
</PropertyGroup>
<PropertyGroup>
<SvgSourcesBasePath>..</SvgSourcesBasePath>
<SvgSourcesBasePath>..\..</SvgSourcesBasePath>
</PropertyGroup>
<ItemGroup>
@ -42,6 +42,11 @@
<Compile Remove="$(SvgSourcesBasePath)\Source\Resources\svg11.dtd" />
</ItemGroup>
<ItemGroup>
<Compile Remove="$(CompilerGeneratedFilesOutputPath)/**/*.cs" />
<Compile Remove="$(SvgSourcesBasePath)\Source\Generated\**\*.cs" />
</ItemGroup>
<PropertyGroup Condition="'$(TargetFramework)'=='netstandard2.0'">
<DefineConstants>$(DefineConstants);NETSTANDARD;NETSTANDARD20</DefineConstants>
</PropertyGroup>