diff --git a/ModernUO.sln b/ModernUO.sln index adf6fb240..238c627b9 100644 --- a/ModernUO.sln +++ b/ModernUO.sln @@ -16,6 +16,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SerializationGenerator", "P EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SerializationGenerator.Tests", "Projects\SerializationGenerator.Tests\SerializationGenerator.Tests.csproj", "{2BC92375-BB66-4CA5-B39C-36215749FE64}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SerializationSchemaGenerator", "Projects\SerializationSchemaGenerator\SerializationSchemaGenerator.csproj", "{A30150A3-796C-4C6D-B3E4-B7BEB0021701}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Analyze|x64 = Analyze|x64 @@ -65,6 +67,12 @@ Global {2BC92375-BB66-4CA5-B39C-36215749FE64}.Debug|x64.Build.0 = Debug|x64 {2BC92375-BB66-4CA5-B39C-36215749FE64}.Release|x64.ActiveCfg = Release|x64 {2BC92375-BB66-4CA5-B39C-36215749FE64}.Release|x64.Build.0 = Release|x64 + {A30150A3-796C-4C6D-B3E4-B7BEB0021701}.Analyze|x64.ActiveCfg = Analyze|x64 + {A30150A3-796C-4C6D-B3E4-B7BEB0021701}.Analyze|x64.Build.0 = Analyze|x64 + {A30150A3-796C-4C6D-B3E4-B7BEB0021701}.Debug|x64.ActiveCfg = Debug|x64 + {A30150A3-796C-4C6D-B3E4-B7BEB0021701}.Debug|x64.Build.0 = Debug|x64 + {A30150A3-796C-4C6D-B3E4-B7BEB0021701}.Release|x64.ActiveCfg = Release|x64 + {A30150A3-796C-4C6D-B3E4-B7BEB0021701}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Projects/SerializationGenerator.Tests/Tests/GenerateClassTests.cs b/Projects/SerializationGenerator.Tests/Tests/GenerateClassTests.cs index 03f8db598..f90df7315 100644 --- a/Projects/SerializationGenerator.Tests/Tests/GenerateClassTests.cs +++ b/Projects/SerializationGenerator.Tests/Tests/GenerateClassTests.cs @@ -1,7 +1,7 @@ using System.Collections.Immutable; using System.Text; using Microsoft.CodeAnalysis; -using SerializationGenerator; +using SourceGeneration; using Xunit; namespace SerializationGeneratorTests diff --git a/Projects/SerializationGenerator/EntitySerializationGenerator.cs b/Projects/SerializationGenerator/EntitySerializationGenerator.cs index 79db7143a..3f243844c 100755 --- a/Projects/SerializationGenerator/EntitySerializationGenerator.cs +++ b/Projects/SerializationGenerator/EntitySerializationGenerator.cs @@ -14,11 +14,10 @@ *************************************************************************/ using System.Collections.Immutable; -using System.Diagnostics; -using System.Linq; using System.Text; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Text; +using SerializableMigration; namespace SerializationGenerator { @@ -27,12 +26,6 @@ namespace SerializationGenerator { public void Initialize(GeneratorInitializationContext context) { - context.RegisterForPostInitialization(i => - { - SerializerSyntaxReceiver.AttributeTypes.Add("Server.SerializableAttribute"); - SerializerSyntaxReceiver.AttributeTypes.Add("Server.SerializableFieldAttribute"); - }); - context.RegisterForSyntaxNotifications(() => new SerializerSyntaxReceiver()); } @@ -43,29 +36,28 @@ namespace SerializationGenerator return; } - var migrationPath = SerializableMigration.GetMigrationPath(context); - var jsonOptions = SerializableMigration.GetJsonSerializerOptions(context.Compilation); + var jsonOptions = SerializableMigrationSchema.GetJsonSerializerOptions(); // List of types that _will_ become ISerializable - var serializableList = receiver - .ClassAndFields - .Select(g => g.Key) - .Where(t => t.WillBeSerializable(context)) - .ToImmutableArray(); + var serializableList = receiver.SerializableList; - foreach (var kvp in receiver.ClassAndFields) + foreach (var (classSymbol, (serializableAttr, fieldsList)) in receiver.ClassAndFields) { - string classSource = SerializableEntityGeneration.GenerateSerializationPartialClass( - kvp.Key, - kvp.Value.ToImmutableArray(), - context, - migrationPath, + if (serializableAttr == null) + { + continue; + } + + string classSource = context.GenerateSerializationPartialClass( + classSymbol, + serializableAttr, + fieldsList.ToImmutableArray(), jsonOptions, serializableList ); if (classSource != null) { - context.AddSource($"{kvp.Key.ToDisplayString()}.Serialization.cs", SourceText.From(classSource, Encoding.UTF8)); + context.AddSource($"{classSymbol.ToDisplayString()}.Serialization.cs", SourceText.From(classSource, Encoding.UTF8)); } } } diff --git a/Projects/SerializationGenerator/SerializableEntityGeneration/SerializableEntityGeneration.Class.cs b/Projects/SerializationGenerator/SerializableEntityGeneration/SerializableEntityGeneration.Class.cs index 879543c19..5d0f4c445 100644 --- a/Projects/SerializationGenerator/SerializableEntityGeneration/SerializableEntityGeneration.Class.cs +++ b/Projects/SerializationGenerator/SerializableEntityGeneration/SerializableEntityGeneration.Class.cs @@ -20,61 +20,29 @@ using System.Linq; using System.Text; using System.Text.Json; using Microsoft.CodeAnalysis; +using SerializableMigration; +using SourceGeneration; namespace SerializationGenerator { public static partial class SerializableEntityGeneration { - public static bool WillBeSerializable(this INamedTypeSymbol classSymbol, GeneratorExecutionContext context) - { - var compilation = context.Compilation; - - var serializableEntityAttribute = - compilation.GetTypeByMetadataName(SERIALIZABLE_ATTRIBUTE); - var serializableInterface = compilation.GetTypeByMetadataName(SERIALIZABLE_INTERFACE); - - if (!classSymbol.ContainingSymbol.Equals(classSymbol.ContainingNamespace, SymbolEqualityComparer.Default)) - { - return false; - } - - if (!classSymbol.ContainsInterface(serializableInterface)) - { - return false; - } - - var versionValue = classSymbol.GetAttributes() - .FirstOrDefault( - attr => SymbolEqualityComparer.Default.Equals(attr.AttributeClass, serializableEntityAttribute) - )?.ConstructorArguments.FirstOrDefault().Value; - - return versionValue != null; - } - public static string GenerateSerializationPartialClass( + this GeneratorExecutionContext context, INamedTypeSymbol classSymbol, + AttributeData serializableAttr, ImmutableArray fieldsAndProperties, - GeneratorExecutionContext context, - string migrationPath, JsonSerializerOptions jsonSerializerOptions, ImmutableArray serializableTypes ) { var compilation = context.Compilation; - var serializableEntityAttribute = - compilation.GetTypeByMetadataName(SERIALIZABLE_ATTRIBUTE); var serializableFieldAttribute = - compilation.GetTypeByMetadataName(SERIALIZABLE_FIELD_ATTRIBUTE); + compilation.GetTypeByMetadataName(SymbolMetadata.SERIALIZABLE_FIELD_ATTRIBUTE); var serializableFieldAttrAttribute = - compilation.GetTypeByMetadataName(SERIALIZABLE_FIELD_ATTR_ATTRIBUTE); - var serializableInterface = compilation.GetTypeByMetadataName(SERIALIZABLE_INTERFACE); - - // This is a class symbol if the containing symbol is the namespace - if (!classSymbol.ContainingSymbol.Equals(classSymbol.ContainingNamespace, SymbolEqualityComparer.Default)) - { - return null; - } + compilation.GetTypeByMetadataName(SymbolMetadata.SERIALIZABLE_FIELD_ATTR_ATTRIBUTE); + var serializableInterface = compilation.GetTypeByMetadataName(SymbolMetadata.SERIALIZABLE_INTERFACE); // If we have a parent that is or derives from ISerializable, then we are in override var isOverride = classSymbol.BaseType.ContainsInterface(serializableInterface); @@ -84,13 +52,8 @@ namespace SerializationGenerator return null; } - var serializableAttribute = classSymbol.GetAttributes() - .FirstOrDefault( - attr => SymbolEqualityComparer.Default.Equals(attr.AttributeClass, serializableEntityAttribute) - ); - - var version = (int)serializableAttribute?.ConstructorArguments[0].Value!; - var encodedVersion = (bool)serializableAttribute.ConstructorArguments[1].Value!; + var version = (int)serializableAttr.ConstructorArguments[0].Value!; + var encodedVersion = (bool)serializableAttr.ConstructorArguments[1].Value!; var namespaceName = classSymbol.ContainingNamespace.ToDisplayString(); var className = classSymbol.Name; @@ -161,31 +124,15 @@ namespace SerializationGenerator } } - string propertyName; - ITypeSymbol propertyType; - if (fieldOrPropertySymbol is IFieldSymbol fieldSymbol) { source.GenerateSerializableProperty(fieldSymbol, compilation); source.AppendLine(); - - propertyName = fieldSymbol.GetPropertyName(); - propertyType = fieldSymbol.Type; - } - else if (fieldOrPropertySymbol is IPropertySymbol propertySymbol) - { - propertyName = fieldOrPropertySymbol.Name; - propertyType = propertySymbol.Type; - } - else - { - throw new Exception($"Invalid node {fieldOrPropertySymbol.Name}. Expecting a field or property node."); } var serializableProperty = SerializableMigrationRulesEngine.GenerateSerializableProperty( compilation, - propertyName, - propertyType, + fieldOrPropertySymbol, order, allAttributes, serializableTypes @@ -219,46 +166,17 @@ namespace SerializationGenerator AccessModifier.None, indent ); - - // bool ISerializable.UseDirtyChecking { get; } = true; - // source.GenerateAutoProperty( - // AccessModifier.None, - // "bool", - // "ISerializable.UseDirtyChecking", - // AccessModifier.None, - // null, - // indent, - // defaultValue: "true" - // ); - // source.AppendLine(); } - // else - // { - // If this type does not *directly* inherit `ISerializable`, then we assume it has an overridable `UseDirtyChecking` - // public override bool ISerializable.UseDirtyChecking { get; } = true; - // source.GenerateAutoProperty( - // AccessModifier.Public, - // "bool", - // "UseDirtyChecking", - // AccessModifier.None, - // null, - // indent, - // defaultValue: "true", - // isOverride: true - // ); - // source.AppendLine(); - // } // Serial constructor - source.GenerateSerialCtor(context, className, isOverride); + source.GenerateSerialCtor(compilation, className, isOverride); source.AppendLine(); List migrations = new List(); if (version > 0) { - migrations = SerializableMigration.GetMigrations( - migrationPath, + migrations = context.GetMigrationsByAnalyzerConfig( classSymbol, version, jsonSerializerOptions @@ -298,15 +216,6 @@ namespace SerializationGenerator source.GenerateClassEnd(); source.GenerateNamespaceEnd(); - // Write the migration file - var newMigration = new SerializableMetadata - { - Version = version, - Type = classSymbol.ToDisplayString(), - Properties = serializableProperties - }; - SerializableMigration.WriteMigration(migrationPath, newMigration, jsonSerializerOptions); - return source.ToString(); } } diff --git a/Projects/SerializationGenerator/SerializableEntityGeneration/SerializableEntityGeneration.DeserializeMethod.cs b/Projects/SerializationGenerator/SerializableEntityGeneration/SerializableEntityGeneration.DeserializeMethod.cs index 5a86c757b..8ffd6a21f 100644 --- a/Projects/SerializationGenerator/SerializableEntityGeneration/SerializableEntityGeneration.DeserializeMethod.cs +++ b/Projects/SerializationGenerator/SerializableEntityGeneration/SerializableEntityGeneration.DeserializeMethod.cs @@ -18,6 +18,8 @@ using System.Collections.Immutable; using System.Linq; using System.Text; using Microsoft.CodeAnalysis; +using SerializableMigration; +using SourceGeneration; namespace SerializationGenerator { @@ -34,7 +36,7 @@ namespace SerializationGenerator ImmutableArray properties ) { - var genericReaderInterface = compilation.GetTypeByMetadataName(GENERIC_READER_INTERFACE); + var genericReaderInterface = compilation.GetTypeByMetadataName(SymbolMetadata.GENERIC_READER_INTERFACE); source.GenerateMethodStart( "Deserialize", @@ -109,7 +111,7 @@ namespace SerializationGenerator .Any( attr => SymbolEqualityComparer.Default.Equals( attr.AttributeClass, - compilation.GetTypeByMetadataName(AFTERDESERIALIZATION_ATTRIBUTE) + compilation.GetTypeByMetadataName(SymbolMetadata.AFTERDESERIALIZATION_ATTRIBUTE) ) ) ); diff --git a/Projects/SerializationGenerator/SerializableEntityGeneration/SerializableEntityGeneration.Property.cs b/Projects/SerializationGenerator/SerializableEntityGeneration/SerializableEntityGeneration.Property.cs index 24d4f4cef..79cad68e2 100644 --- a/Projects/SerializationGenerator/SerializableEntityGeneration/SerializableEntityGeneration.Property.cs +++ b/Projects/SerializationGenerator/SerializableEntityGeneration/SerializableEntityGeneration.Property.cs @@ -16,6 +16,7 @@ using System.Linq; using System.Text; using Microsoft.CodeAnalysis; +using SourceGeneration; namespace SerializationGenerator { @@ -34,7 +35,7 @@ namespace SerializationGenerator .OfType() .FirstOrDefault( attr => attr.AttributeClass?.Equals( - compilation.GetTypeByMetadataName(INVALIDATEPROPERTIES_ATTRIBUTE), + compilation.GetTypeByMetadataName(SymbolMetadata.INVALIDATEPROPERTIES_ATTRIBUTE), SymbolEqualityComparer.Default ) ?? false ); diff --git a/Projects/SerializationGenerator/SerializableEntityGeneration/SerializableEntityGeneration.SerialCtor.cs b/Projects/SerializationGenerator/SerializableEntityGeneration/SerializableEntityGeneration.SerialCtor.cs index 75b91d75c..171882283 100644 --- a/Projects/SerializationGenerator/SerializableEntityGeneration/SerializableEntityGeneration.SerialCtor.cs +++ b/Projects/SerializationGenerator/SerializableEntityGeneration/SerializableEntityGeneration.SerialCtor.cs @@ -16,6 +16,7 @@ using System.Collections.Immutable; using System.Text; using Microsoft.CodeAnalysis; +using SourceGeneration; namespace SerializationGenerator { @@ -24,12 +25,12 @@ namespace SerializationGenerator private static readonly ImmutableArray _baseParameters = new[] { "serial" }.ToImmutableArray(); public static void GenerateSerialCtor( this StringBuilder source, - GeneratorExecutionContext context, + Compilation compilation, string className, bool isOverride ) { - var serialType = (ITypeSymbol)context.Compilation.GetTypeByMetadataName("Server.Serial"); + var serialType = (ITypeSymbol)compilation.GetTypeByMetadataName("Server.Serial"); source.GenerateConstructorStart( className, diff --git a/Projects/SerializationGenerator/SerializableEntityGeneration/SerializableEntityGeneration.SerializeMethod.cs b/Projects/SerializationGenerator/SerializableEntityGeneration/SerializableEntityGeneration.SerializeMethod.cs index bdad39969..2b7d9a4a0 100644 --- a/Projects/SerializationGenerator/SerializableEntityGeneration/SerializableEntityGeneration.SerializeMethod.cs +++ b/Projects/SerializationGenerator/SerializableEntityGeneration/SerializableEntityGeneration.SerializeMethod.cs @@ -16,6 +16,8 @@ using System.Collections.Immutable; using System.Text; using Microsoft.CodeAnalysis; +using SerializableMigration; +using SourceGeneration; namespace SerializationGenerator { @@ -29,7 +31,7 @@ namespace SerializationGenerator ImmutableArray properties ) { - var genericWriterInterface = compilation.GetTypeByMetadataName(GENERIC_WRITER_INTERFACE); + var genericWriterInterface = compilation.GetTypeByMetadataName(SymbolMetadata.GENERIC_WRITER_INTERFACE); source.GenerateMethodStart( "Serialize", diff --git a/Projects/SerializationGenerator/SerializableMigration/SerializableMigration.ContentStruct.cs b/Projects/SerializationGenerator/SerializableEntityGeneration/SerializationGenerator.ContentStruct.cs similarity index 93% rename from Projects/SerializationGenerator/SerializableMigration/SerializableMigration.ContentStruct.cs rename to Projects/SerializationGenerator/SerializableEntityGeneration/SerializationGenerator.ContentStruct.cs index 2e41f0be0..a1579ff77 100644 --- a/Projects/SerializationGenerator/SerializableMigration/SerializableMigration.ContentStruct.cs +++ b/Projects/SerializationGenerator/SerializableEntityGeneration/SerializationGenerator.ContentStruct.cs @@ -2,7 +2,7 @@ * ModernUO * * Copyright 2019-2021 - ModernUO Development Team * * Email: hi@modernuo.com * - * File: SerializableMigration.ContentStruct.cs * + * File: SerializationGenerator.ContentStruct.cs * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * @@ -14,10 +14,11 @@ *************************************************************************/ using System.Text; +using SerializableMigration; namespace SerializationGenerator { - public static partial class SerializableMigration + public static partial class SerializableEntityGeneration { public static void GenerateMigrationContentStruct( this StringBuilder source, diff --git a/Projects/SerializationGenerator/SerializableMigration/ISerializableMigrationRule.cs b/Projects/SerializationGenerator/SerializableMigration/ISerializableMigrationRule.cs index 4c5582331..e7491a594 100644 --- a/Projects/SerializationGenerator/SerializableMigration/ISerializableMigrationRule.cs +++ b/Projects/SerializationGenerator/SerializableMigration/ISerializableMigrationRule.cs @@ -17,7 +17,7 @@ using System.Collections.Immutable; using System.Text; using Microsoft.CodeAnalysis; -namespace SerializationGenerator +namespace SerializableMigration { public interface ISerializableMigrationRule { diff --git a/Projects/SerializationGenerator/SerializableMigration/Rules/ArrayMigrationRule.cs b/Projects/SerializationGenerator/SerializableMigration/Rules/ArrayMigrationRule.cs index 46b64c43c..542b767bf 100644 --- a/Projects/SerializationGenerator/SerializableMigration/Rules/ArrayMigrationRule.cs +++ b/Projects/SerializationGenerator/SerializableMigration/Rules/ArrayMigrationRule.cs @@ -18,7 +18,7 @@ using System.Collections.Immutable; using System.Text; using Microsoft.CodeAnalysis; -namespace SerializationGenerator +namespace SerializableMigration { public class ArrayMigrationRule : ISerializableMigrationRule { diff --git a/Projects/SerializationGenerator/SerializableMigration/Rules/EnumMigrationRule.cs b/Projects/SerializationGenerator/SerializableMigration/Rules/EnumMigrationRule.cs index f3fca519d..42b396f7c 100644 --- a/Projects/SerializationGenerator/SerializableMigration/Rules/EnumMigrationRule.cs +++ b/Projects/SerializationGenerator/SerializableMigration/Rules/EnumMigrationRule.cs @@ -17,8 +17,9 @@ using System; using System.Collections.Immutable; using System.Text; using Microsoft.CodeAnalysis; +using SourceGeneration; -namespace SerializationGenerator +namespace SerializableMigration { public class EnumMigrationRule : ISerializableMigrationRule { diff --git a/Projects/SerializationGenerator/SerializableMigration/Rules/HashSetMigrationRule.cs b/Projects/SerializationGenerator/SerializableMigration/Rules/HashSetMigrationRule.cs index 53639d085..90dc538e0 100644 --- a/Projects/SerializationGenerator/SerializableMigration/Rules/HashSetMigrationRule.cs +++ b/Projects/SerializationGenerator/SerializableMigration/Rules/HashSetMigrationRule.cs @@ -17,8 +17,9 @@ using System; using System.Collections.Immutable; using System.Text; using Microsoft.CodeAnalysis; +using SourceGeneration; -namespace SerializationGenerator +namespace SerializableMigration { public class HashSetMigrationRule : ISerializableMigrationRule { diff --git a/Projects/SerializationGenerator/SerializableMigration/Rules/KeyValuePairMigrationRule.cs b/Projects/SerializationGenerator/SerializableMigration/Rules/KeyValuePairMigrationRule.cs index e91765eeb..017536db2 100644 --- a/Projects/SerializationGenerator/SerializableMigration/Rules/KeyValuePairMigrationRule.cs +++ b/Projects/SerializationGenerator/SerializableMigration/Rules/KeyValuePairMigrationRule.cs @@ -17,8 +17,9 @@ using System; using System.Collections.Immutable; using System.Text; using Microsoft.CodeAnalysis; +using SourceGeneration; -namespace SerializationGenerator +namespace SerializableMigration { public class KeyValuePairMigrationRule : ISerializableMigrationRule { @@ -124,7 +125,7 @@ namespace SerializationGenerator ); source.AppendLine( - $"{indent}{property.Name} = new {SerializableEntityGeneration.KEYVALUEPAIR_STRUCT}<{keyType}, {valueType}>(key, value);" + $"{indent}{property.Name} = new {SymbolMetadata.KEYVALUEPAIR_STRUCT}<{keyType}, {valueType}>(key, value);" ); } diff --git a/Projects/SerializationGenerator/SerializableMigration/Rules/ListMigrationRule.cs b/Projects/SerializationGenerator/SerializableMigration/Rules/ListMigrationRule.cs index 5d069976f..7e6bda2ad 100644 --- a/Projects/SerializationGenerator/SerializableMigration/Rules/ListMigrationRule.cs +++ b/Projects/SerializationGenerator/SerializableMigration/Rules/ListMigrationRule.cs @@ -17,8 +17,9 @@ using System; using System.Collections.Immutable; using System.Text; using Microsoft.CodeAnalysis; +using SourceGeneration; -namespace SerializationGenerator +namespace SerializableMigration { public class ListMigrationRule : ISerializableMigrationRule { diff --git a/Projects/SerializationGenerator/SerializableMigration/Rules/PrimitiveTypeMigrationRule.cs b/Projects/SerializationGenerator/SerializableMigration/Rules/PrimitiveTypeMigrationRule.cs index 3fa6b0ad6..1872fb957 100644 --- a/Projects/SerializationGenerator/SerializableMigration/Rules/PrimitiveTypeMigrationRule.cs +++ b/Projects/SerializationGenerator/SerializableMigration/Rules/PrimitiveTypeMigrationRule.cs @@ -18,8 +18,9 @@ using System.Collections.Immutable; using System.Linq; using System.Text; using Microsoft.CodeAnalysis; +using SourceGeneration; -namespace SerializationGenerator +namespace SerializableMigration { public class PrimitiveTypeMigrationRule : ISerializableMigrationRule { @@ -89,7 +90,7 @@ namespace SerializationGenerator var propertyName = property.Name; var argument = property.RuleArguments.Length >= 1 ? property.RuleArguments[0] : null; - const string ipAddress = SerializableEntityGeneration.IPADDRESS_CLASS; + const string ipAddress = SymbolMetadata.IPADDRESS_CLASS; const string date = "System.DateTime"; var readMethod = property.Type switch diff --git a/Projects/SerializationGenerator/SerializableMigration/Rules/PrimitiveUOTypeMigrationRule.cs b/Projects/SerializationGenerator/SerializableMigration/Rules/PrimitiveUOTypeMigrationRule.cs index 882961962..5699b03e5 100644 --- a/Projects/SerializationGenerator/SerializableMigration/Rules/PrimitiveUOTypeMigrationRule.cs +++ b/Projects/SerializationGenerator/SerializableMigration/Rules/PrimitiveUOTypeMigrationRule.cs @@ -17,8 +17,9 @@ using System; using System.Collections.Immutable; using System.Text; using Microsoft.CodeAnalysis; +using SourceGeneration; -namespace SerializationGenerator +namespace SerializableMigration { public class PrimitiveUOTypeMigrationRule : ISerializableMigrationRule { diff --git a/Projects/SerializationGenerator/SerializableMigration/Rules/SerializableInterfaceMigrationRule.cs b/Projects/SerializationGenerator/SerializableMigration/Rules/SerializableInterfaceMigrationRule.cs index 1b6aa30a4..4412a2ca4 100644 --- a/Projects/SerializationGenerator/SerializableMigration/Rules/SerializableInterfaceMigrationRule.cs +++ b/Projects/SerializationGenerator/SerializableMigration/Rules/SerializableInterfaceMigrationRule.cs @@ -17,8 +17,9 @@ using System; using System.Collections.Immutable; using System.Text; using Microsoft.CodeAnalysis; +using SourceGeneration; -namespace SerializationGenerator +namespace SerializableMigration { public class SerializableInterfaceMigrationRule : ISerializableMigrationRule { diff --git a/Projects/SerializationGenerator/SerializableMigration/Rules/SerializationMethodSignatureMigrationRule.cs b/Projects/SerializationGenerator/SerializableMigration/Rules/SerializationMethodSignatureMigrationRule.cs index 2b6b139fc..656b30182 100644 --- a/Projects/SerializationGenerator/SerializableMigration/Rules/SerializationMethodSignatureMigrationRule.cs +++ b/Projects/SerializationGenerator/SerializableMigration/Rules/SerializationMethodSignatureMigrationRule.cs @@ -17,8 +17,9 @@ using System; using System.Collections.Immutable; using System.Text; using Microsoft.CodeAnalysis; +using SourceGeneration; -namespace SerializationGenerator +namespace SerializableMigration { public class SerializationMethodSignatureMigrationRule : ISerializableMigrationRule { diff --git a/Projects/SerializationGenerator/SerializableMigration/SerializableMetadata.cs b/Projects/SerializationGenerator/SerializableMigration/SerializableMetadata.cs index 289be342e..405e9e647 100644 --- a/Projects/SerializationGenerator/SerializableMigration/SerializableMetadata.cs +++ b/Projects/SerializationGenerator/SerializableMigration/SerializableMetadata.cs @@ -16,7 +16,7 @@ using System.Collections.Immutable; using System.Text.Json.Serialization; -namespace SerializationGenerator +namespace SerializableMigration { public record SerializableMetadata { diff --git a/Projects/SerializationGenerator/SerializableMigration/SerializableMetadataComparer.cs b/Projects/SerializationGenerator/SerializableMigration/SerializableMetadataComparer.cs index 713fd92ba..13fd600b2 100644 --- a/Projects/SerializationGenerator/SerializableMigration/SerializableMetadataComparer.cs +++ b/Projects/SerializationGenerator/SerializableMigration/SerializableMetadataComparer.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; -namespace SerializationGenerator +namespace SerializableMigration { public class SerializableMetadataComparer : IComparer { diff --git a/Projects/SerializationGenerator/SerializableMigration/SerializableMigrationRulesEngine.cs b/Projects/SerializationGenerator/SerializableMigration/SerializableMigrationRulesEngine.cs index df188abc4..46a2cc8f1 100644 --- a/Projects/SerializationGenerator/SerializableMigration/SerializableMigrationRulesEngine.cs +++ b/Projects/SerializationGenerator/SerializableMigration/SerializableMigrationRulesEngine.cs @@ -17,8 +17,9 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; using Microsoft.CodeAnalysis; +using SourceGeneration; -namespace SerializationGenerator +namespace SerializableMigration { public static class SerializableMigrationRulesEngine { @@ -45,6 +46,42 @@ namespace SerializationGenerator } } + public static SerializableProperty? GenerateSerializableProperty( + Compilation compilation, + ISymbol fieldOrPropertySymbol, + int order, + ImmutableArray attributes, + ImmutableArray serializableTypes + ) + { + string propertyName; + ITypeSymbol propertyType; + + if (fieldOrPropertySymbol is IFieldSymbol fieldSymbol) + { + propertyName = fieldSymbol.GetPropertyName(); + propertyType = fieldSymbol.Type; + } + else if (fieldOrPropertySymbol is IPropertySymbol propertySymbol) + { + propertyName = fieldOrPropertySymbol.Name; + propertyType = propertySymbol.Type; + } + else + { + return null; + } + + return GenerateSerializableProperty( + compilation, + propertyName, + propertyType, + order, + attributes, + serializableTypes + ); + } + public static SerializableProperty GenerateSerializableProperty( Compilation compilation, string propertyName, diff --git a/Projects/SerializationGenerator/SerializableMigration/SerializableMigration.cs b/Projects/SerializationGenerator/SerializableMigration/SerializableMigrationSchema.cs similarity index 60% rename from Projects/SerializationGenerator/SerializableMigration/SerializableMigration.cs rename to Projects/SerializationGenerator/SerializableMigration/SerializableMigrationSchema.cs index 5171d654a..deace7413 100644 --- a/Projects/SerializationGenerator/SerializableMigration/SerializableMigration.cs +++ b/Projects/SerializationGenerator/SerializableMigration/SerializableMigrationSchema.cs @@ -2,7 +2,7 @@ * ModernUO * * Copyright 2019-2021 - ModernUO Development Team * * Email: hi@modernuo.com * - * File: SerializableMigration.cs * + * File: SerializableMigrationSchema.cs * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * @@ -16,15 +16,15 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using System.Text; using System.Text.Json; +using System.Text.RegularExpressions; using Microsoft.CodeAnalysis; -namespace SerializationGenerator +namespace SerializableMigration { - public static partial class SerializableMigration + public static class SerializableMigrationSchema { - public static JsonSerializerOptions GetJsonSerializerOptions(Compilation compilation) => + public static JsonSerializerOptions GetJsonSerializerOptions() => new() { WriteIndented = true, @@ -33,32 +33,39 @@ namespace SerializationGenerator ReadCommentHandling = JsonCommentHandling.Skip }; - public static string GetMigrationPath(GeneratorExecutionContext context) - { - context.AnalyzerConfigOptions.GlobalOptions.TryGetValue( - "build_property.SerializableMigrationPath", - out var migrationPath - ); + private static Dictionary _cache = new(); - return migrationPath; - } + private static Regex _fileRegex = new(@"\S+\.v\d+\.json$"); - public static List GetMigrations( - string migrationPath, + public static List GetMigrationsByAnalyzerConfig( + this GeneratorExecutionContext context, INamedTypeSymbol typeSymbol, int version, JsonSerializerOptions options ) { var typeName = typeSymbol.ToDisplayString(); - var migrations = new SortedSet(new SerializableMetadataComparer()); - var migrationFiles = Directory.GetFiles(migrationPath, $"{typeName}.v*.json"); - foreach (var migrationFile in migrationFiles) + foreach (var additionalText in context.AdditionalFiles) { - var text = File.ReadAllText(migrationFile, Encoding.UTF8); - var migration = JsonSerializer.Deserialize(text, options); + var fi = new FileInfo(additionalText.Path); + if (!_fileRegex.IsMatch(fi.Name)) + { + continue; + } + + if (!_cache.TryGetValue(fi.Name, out var migration)) + { + var text = additionalText.GetText(context.CancellationToken)?.ToString(); + if (text == null) + { + continue; + } + + migration = JsonSerializer.Deserialize(text, options); + } + if (typeName == migration!.Type && version > migration.Version) { migrations.Add(migration); @@ -67,12 +74,5 @@ namespace SerializationGenerator return migrations.ToList(); } - - public static void WriteMigration(string migrationPath, SerializableMetadata metadata, JsonSerializerOptions options) - { - Directory.CreateDirectory(migrationPath); - var filePath = Path.Combine(migrationPath, $"{metadata.Type}.v{metadata.Version}.json"); - File.WriteAllText(filePath, JsonSerializer.Serialize(metadata, options)); - } } } diff --git a/Projects/SerializationGenerator/SerializableMigration/SerializableProperty.cs b/Projects/SerializationGenerator/SerializableMigration/SerializableProperty.cs index 72983713a..2255edb46 100644 --- a/Projects/SerializationGenerator/SerializableMigration/SerializableProperty.cs +++ b/Projects/SerializationGenerator/SerializableMigration/SerializableProperty.cs @@ -15,7 +15,7 @@ using System.Text.Json.Serialization; -namespace SerializationGenerator +namespace SerializableMigration { public record SerializableProperty { diff --git a/Projects/SerializationGenerator/SerializableMigration/SerializablePropertyComparer.cs b/Projects/SerializationGenerator/SerializableMigration/SerializablePropertyComparer.cs index 4bad427f6..d4b736978 100644 --- a/Projects/SerializationGenerator/SerializableMigration/SerializablePropertyComparer.cs +++ b/Projects/SerializationGenerator/SerializableMigration/SerializablePropertyComparer.cs @@ -15,7 +15,7 @@ using System.Collections.Generic; -namespace SerializationGenerator +namespace SerializableMigration { public class SerializablePropertyComparer : IComparer { diff --git a/Projects/SerializationGenerator/SerializationGenerator.csproj b/Projects/SerializationGenerator/SerializationGenerator.csproj index 9186dc8dd..2e69e3260 100755 --- a/Projects/SerializationGenerator/SerializationGenerator.csproj +++ b/Projects/SerializationGenerator/SerializationGenerator.csproj @@ -9,7 +9,7 @@ - + diff --git a/Projects/SerializationGenerator/SerializerSyntaxReceiver.cs b/Projects/SerializationGenerator/SerializerSyntaxReceiver.cs index f61357d28..cb471b80d 100755 --- a/Projects/SerializationGenerator/SerializerSyntaxReceiver.cs +++ b/Projects/SerializationGenerator/SerializerSyntaxReceiver.cs @@ -14,69 +14,91 @@ *************************************************************************/ using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; +using SourceGeneration; namespace SerializationGenerator { public class SerializerSyntaxReceiver : ISyntaxContextReceiver { #pragma warning disable RS1024 - public Dictionary> ClassAndFields { get; } = new(SymbolEqualityComparer.Default); + public Dictionary)> ClassAndFields { get; } = new(SymbolEqualityComparer.Default); #pragma warning restore RS1024 - public static HashSet AttributeTypes { get; } = new(); + public ImmutableArray SerializableList => ClassAndFields.Keys.ToImmutableArray(); - public void OnVisitSyntaxNode(GeneratorSyntaxContext context) + public void OnVisitSyntaxNode(SyntaxNode node, SemanticModel semanticModel) { - if (context.Node is ClassDeclarationSyntax { AttributeLists: { Count: > 0 } } classDeclarationSyntax) + var compilation = semanticModel.Compilation; + + if (node is ClassDeclarationSyntax { AttributeLists: { Count: > 0 } } classDeclarationSyntax) { - if (context.SemanticModel.GetDeclaredSymbol(classDeclarationSyntax) is not INamedTypeSymbol classSymbol) + if (semanticModel.GetDeclaredSymbol(classDeclarationSyntax) is not INamedTypeSymbol classSymbol) { return; } - if (classSymbol.GetAttributes().Any(ad => AttributeTypes.Contains(ad.AttributeClass?.ToDisplayString()) && !ClassAndFields.ContainsKey(classSymbol))) + if (classSymbol.WillBeSerializable(compilation, out var attrData)) { - ClassAndFields.Add(classSymbol, new List()); + if (ClassAndFields.TryGetValue(classSymbol, out var value)) + { + var (_, fieldsList) = value; + ClassAndFields[classSymbol] = (attrData, fieldsList); + } + else + { + ClassAndFields.Add(classSymbol, (attrData, new List())); + } } return; } - if (context.Node is FieldDeclarationSyntax { AttributeLists: { Count: > 0 } } fieldDeclarationSyntax) + if (node is FieldDeclarationSyntax { AttributeLists: { Count: > 0 } } fieldDeclarationSyntax) { foreach (var variable in fieldDeclarationSyntax.Declaration.Variables) { - if (context.SemanticModel.GetDeclaredSymbol(variable) is IFieldSymbol fieldSymbol) + if (semanticModel.GetDeclaredSymbol(variable) is IFieldSymbol fieldSymbol) { - AddFieldOrProperty(fieldSymbol); + AddFieldOrProperty(fieldSymbol, compilation); } } } - else if (context.Node is PropertyDeclarationSyntax { AttributeLists: { Count: > 0 } } propertyDeclarationSyntax) + else if (node is PropertyDeclarationSyntax { AttributeLists: { Count: > 0 } } propertyDeclarationSyntax) { - if (context.SemanticModel.GetDeclaredSymbol(propertyDeclarationSyntax) is IPropertySymbol propertySymbol) + if (semanticModel.GetDeclaredSymbol(propertyDeclarationSyntax) is IPropertySymbol propertySymbol) { - AddFieldOrProperty(propertySymbol); + AddFieldOrProperty(propertySymbol, compilation); } } } - private void AddFieldOrProperty(ISymbol symbol) + public void OnVisitSyntaxNode(GeneratorSyntaxContext context) => + OnVisitSyntaxNode(context.Node, context.SemanticModel); + + private void AddFieldOrProperty(ISymbol symbol, Compilation compilation) { - if (symbol.GetAttributes().Any(ad => AttributeTypes.Contains(ad.AttributeClass?.ToDisplayString()))) + var serializableFieldAttr = compilation.GetTypeByMetadataName(SymbolMetadata.SERIALIZABLE_FIELD_ATTRIBUTE); + + if (symbol.GetAttribute(serializableFieldAttr) == null) { - var classSymbol = symbol.ContainingType; - if (ClassAndFields.TryGetValue(classSymbol, out var fieldsList)) - { - fieldsList.Add(symbol); - } - else - { - ClassAndFields.Add(classSymbol, new List { symbol }); - } + return; + } + + var classSymbol = symbol.ContainingType; + if (ClassAndFields.TryGetValue(classSymbol, out var value)) + { + var (_, fieldsList) = value; + fieldsList.Add(symbol); + return; + } + + if (classSymbol.WillBeSerializable(compilation, out var attrData)) + { + ClassAndFields.Add(classSymbol, (attrData, new List { symbol })); } } } diff --git a/Projects/SerializationGenerator/SourceGeneration/Helpers.cs b/Projects/SerializationGenerator/SourceGeneration/Helpers.cs index 8bc0dd4fe..474599f85 100644 --- a/Projects/SerializationGenerator/SourceGeneration/Helpers.cs +++ b/Projects/SerializationGenerator/SourceGeneration/Helpers.cs @@ -18,7 +18,7 @@ using System.Collections.Immutable; using System.Linq; using Microsoft.CodeAnalysis; -namespace SerializationGenerator +namespace SourceGeneration { public static class Helpers { diff --git a/Projects/SerializationGenerator/SourceGeneration/SourceGeneration.AccessModifier.cs b/Projects/SerializationGenerator/SourceGeneration/SourceGeneration.AccessModifier.cs index ed769c7dc..656a1170e 100644 --- a/Projects/SerializationGenerator/SourceGeneration/SourceGeneration.AccessModifier.cs +++ b/Projects/SerializationGenerator/SourceGeneration/SourceGeneration.AccessModifier.cs @@ -13,7 +13,7 @@ * along with this program. If not, see . * *************************************************************************/ -namespace SerializationGenerator +namespace SourceGeneration { public enum AccessModifier { diff --git a/Projects/SerializationGenerator/SourceGeneration/SourceGeneration.Arguments.cs b/Projects/SerializationGenerator/SourceGeneration/SourceGeneration.Arguments.cs index 0d194d65f..87b6103d0 100644 --- a/Projects/SerializationGenerator/SourceGeneration/SourceGeneration.Arguments.cs +++ b/Projects/SerializationGenerator/SourceGeneration/SourceGeneration.Arguments.cs @@ -18,7 +18,7 @@ using System.Collections.Immutable; using System.Text; using Microsoft.CodeAnalysis; -namespace SerializationGenerator +namespace SourceGeneration { public static partial class SourceGeneration { diff --git a/Projects/SerializationGenerator/SourceGeneration/SourceGeneration.Attribute.cs b/Projects/SerializationGenerator/SourceGeneration/SourceGeneration.Attribute.cs index 98e312beb..ff1e24051 100644 --- a/Projects/SerializationGenerator/SourceGeneration/SourceGeneration.Attribute.cs +++ b/Projects/SerializationGenerator/SourceGeneration/SourceGeneration.Attribute.cs @@ -17,7 +17,7 @@ using System.Collections.Immutable; using System.Text; using Microsoft.CodeAnalysis; -namespace SerializationGenerator +namespace SourceGeneration { public static partial class SourceGeneration { diff --git a/Projects/SerializationGenerator/SourceGeneration/SourceGeneration.Class.cs b/Projects/SerializationGenerator/SourceGeneration/SourceGeneration.Class.cs index 109be6529..5dc2e0bb9 100644 --- a/Projects/SerializationGenerator/SourceGeneration/SourceGeneration.Class.cs +++ b/Projects/SerializationGenerator/SourceGeneration/SourceGeneration.Class.cs @@ -17,7 +17,7 @@ using System.Collections.Immutable; using System.Text; using Microsoft.CodeAnalysis; -namespace SerializationGenerator +namespace SourceGeneration { public static partial class SourceGeneration { diff --git a/Projects/SerializationGenerator/SourceGeneration/SourceGeneration.InstanceModifier.cs b/Projects/SerializationGenerator/SourceGeneration/SourceGeneration.InstanceModifier.cs index 2fdfef6da..c61d86bbd 100644 --- a/Projects/SerializationGenerator/SourceGeneration/SourceGeneration.InstanceModifier.cs +++ b/Projects/SerializationGenerator/SourceGeneration/SourceGeneration.InstanceModifier.cs @@ -13,7 +13,7 @@ * along with this program. If not, see . * *************************************************************************/ -namespace SerializationGenerator +namespace SourceGeneration { public enum InstanceModifier { diff --git a/Projects/SerializationGenerator/SourceGeneration/SourceGeneration.Method.cs b/Projects/SerializationGenerator/SourceGeneration/SourceGeneration.Method.cs index f6863fc19..e4ecfd718 100644 --- a/Projects/SerializationGenerator/SourceGeneration/SourceGeneration.Method.cs +++ b/Projects/SerializationGenerator/SourceGeneration/SourceGeneration.Method.cs @@ -17,7 +17,7 @@ using System.Collections.Immutable; using System.Text; using Microsoft.CodeAnalysis; -namespace SerializationGenerator +namespace SourceGeneration { public static partial class SourceGeneration { diff --git a/Projects/SerializationGenerator/SourceGeneration/SourceGeneration.Namespace.cs b/Projects/SerializationGenerator/SourceGeneration/SourceGeneration.Namespace.cs index f2871efde..6767b1db9 100644 --- a/Projects/SerializationGenerator/SourceGeneration/SourceGeneration.Namespace.cs +++ b/Projects/SerializationGenerator/SourceGeneration/SourceGeneration.Namespace.cs @@ -13,13 +13,9 @@ * along with this program. If not, see . * *************************************************************************/ -using System; -using System.Collections.Immutable; -using System.Linq; using System.Text; -using Microsoft.CodeAnalysis; -namespace SerializationGenerator +namespace SourceGeneration { public static partial class SourceGeneration { diff --git a/Projects/SerializationGenerator/SourceGeneration/SourceGeneration.Property.cs b/Projects/SerializationGenerator/SourceGeneration/SourceGeneration.Property.cs index bb360e70b..abbf09b3b 100644 --- a/Projects/SerializationGenerator/SourceGeneration/SourceGeneration.Property.cs +++ b/Projects/SerializationGenerator/SourceGeneration/SourceGeneration.Property.cs @@ -18,7 +18,7 @@ using System.Text; using Humanizer; using Microsoft.CodeAnalysis; -namespace SerializationGenerator +namespace SourceGeneration { public static partial class SourceGeneration { diff --git a/Projects/SerializationGenerator/SourceGeneration/SymbolMetadata/SymbolMetadata.Builtin.cs b/Projects/SerializationGenerator/SourceGeneration/SymbolMetadata/SymbolMetadata.Builtin.cs new file mode 100644 index 000000000..279ef3e42 --- /dev/null +++ b/Projects/SerializationGenerator/SourceGeneration/SymbolMetadata/SymbolMetadata.Builtin.cs @@ -0,0 +1,51 @@ +/************************************************************************* + * ModernUO * + * Copyright 2019-2021 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: SymbolMetadata.Builtin.cs * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using Microsoft.CodeAnalysis; + +namespace SourceGeneration +{ + public static partial class SymbolMetadata + { + public const string LIST_CLASS = "System.Collections.Generic.List`1"; + public const string HASHSET_CLASS = "System.Collections.Generic.HashSet`1"; + public const string IPADDRESS_CLASS = "System.Net.IPAddress"; + public const string KEYVALUEPAIR_STRUCT = "System.Collections.Generic.KeyValuePair"; + + public static bool IsIpAddress(this ISymbol symbol, Compilation compilation) => + symbol.Equals( + compilation.GetTypeByMetadataName(IPADDRESS_CLASS), + SymbolEqualityComparer.Default + ); + + public static bool IsKeyValuePair(this ISymbol symbol, Compilation compilation) => + (symbol as INamedTypeSymbol)?.ConstructedFrom.Equals( + compilation.GetTypeByMetadataName(KEYVALUEPAIR_STRUCT), + SymbolEqualityComparer.Default + ) == true; + + public static bool IsList(this ISymbol symbol, Compilation compilation) => + (symbol as INamedTypeSymbol)?.ConstructedFrom.Equals( + compilation.GetTypeByMetadataName(LIST_CLASS), + SymbolEqualityComparer.Default + ) == true; + + public static bool IsHashSet(this ISymbol symbol, Compilation compilation) => + (symbol as INamedTypeSymbol)?.ConstructedFrom.Equals( + compilation.GetTypeByMetadataName(HASHSET_CLASS), + SymbolEqualityComparer.Default + ) == true; + } +} diff --git a/Projects/SerializationGenerator/SerializableEntityGeneration/SerializableEntityGeneration.MetadataTypes.cs b/Projects/SerializationGenerator/SourceGeneration/SymbolMetadata/SymbolMetadata.UO.cs similarity index 81% rename from Projects/SerializationGenerator/SerializableEntityGeneration/SerializableEntityGeneration.MetadataTypes.cs rename to Projects/SerializationGenerator/SourceGeneration/SymbolMetadata/SymbolMetadata.UO.cs index 0c14aa308..31690092a 100644 --- a/Projects/SerializationGenerator/SerializableEntityGeneration/SerializableEntityGeneration.MetadataTypes.cs +++ b/Projects/SerializationGenerator/SourceGeneration/SymbolMetadata/SymbolMetadata.UO.cs @@ -2,7 +2,7 @@ * ModernUO * * Copyright 2019-2021 - ModernUO Development Team * * Email: hi@modernuo.com * - * File: SerializableEntityGeneration.MetadataTypes.cs * + * File: SymbolMetadata.UO.cs * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * @@ -13,19 +13,15 @@ * along with this program. If not, see . * *************************************************************************/ +using System; using System.Collections.Immutable; using System.Linq; using Microsoft.CodeAnalysis; -namespace SerializationGenerator +namespace SourceGeneration { - public static partial class SerializableEntityGeneration + public static partial class SymbolMetadata { - public const string LIST_CLASS = "System.Collections.Generic.List`1"; - public const string HASHSET_CLASS = "System.Collections.Generic.HashSet`1"; - public const string IPADDRESS_CLASS = "System.Net.IPAddress"; - public const string KEYVALUEPAIR_STRUCT = "System.Collections.Generic.KeyValuePair"; - public const string INVALIDATEPROPERTIES_ATTRIBUTE = "Server.InvalidatePropertiesAttribute"; public const string AFTERDESERIALIZATION_ATTRIBUTE = "Server.AfterDeserializationAttribute"; public const string SERIALIZABLE_ATTRIBUTE = "Server.SerializableAttribute"; @@ -132,12 +128,6 @@ namespace SerializationGenerator SymbolEqualityComparer.Default ); - public static bool IsIpAddress(this ISymbol symbol, Compilation compilation) => - symbol.Equals( - compilation.GetTypeByMetadataName(IPADDRESS_CLASS), - SymbolEqualityComparer.Default - ); - public static bool IsRace(this ISymbol symbol, Compilation compilation) => symbol.Equals( compilation.GetTypeByMetadataName(RACE_CLASS), @@ -150,22 +140,28 @@ namespace SerializationGenerator SymbolEqualityComparer.Default ); - public static bool IsKeyValuePair(this ISymbol symbol, Compilation compilation) => - (symbol as INamedTypeSymbol)?.ConstructedFrom.Equals( - compilation.GetTypeByMetadataName(KEYVALUEPAIR_STRUCT), - SymbolEqualityComparer.Default - ) == true; + public static AttributeData? GetAttribute(this ISymbol symbol, ISymbol attrSymbol) => + symbol + .GetAttributes() + .FirstOrDefault( + ad => ad.AttributeClass != null && SymbolEqualityComparer.Default.Equals(ad.AttributeClass, attrSymbol) + ); - public static bool IsList(this ISymbol symbol, Compilation compilation) => - (symbol as INamedTypeSymbol)?.ConstructedFrom.Equals( - compilation.GetTypeByMetadataName(LIST_CLASS), - SymbolEqualityComparer.Default - ) == true; + public static bool WillBeSerializable(this INamedTypeSymbol classSymbol, Compilation compilation, out AttributeData? attributeData) + { + var serializableInterface = compilation.GetTypeByMetadataName(SERIALIZABLE_INTERFACE); - public static bool IsHashSet(this ISymbol symbol, Compilation compilation) => - (symbol as INamedTypeSymbol)?.ConstructedFrom.Equals( - compilation.GetTypeByMetadataName(HASHSET_CLASS), - SymbolEqualityComparer.Default - ) == true; + if (!classSymbol.ContainsInterface(serializableInterface)) + { + attributeData = null; + return false; + } + + var serializableEntityAttribute = + compilation.GetTypeByMetadataName(SERIALIZABLE_ATTRIBUTE); + + attributeData = classSymbol.GetAttribute(serializableEntityAttribute); + return attributeData != null; + } } } diff --git a/Projects/SerializationGenerator/Utility.cs b/Projects/SerializationGenerator/Utility.cs new file mode 100644 index 000000000..ad3f9c9c8 --- /dev/null +++ b/Projects/SerializationGenerator/Utility.cs @@ -0,0 +1,28 @@ +/************************************************************************* + * ModernUO * + * Copyright 2019-2021 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: Utility.cs * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System.Collections.Generic; + +namespace SerializationGenerator +{ + public static class Utility + { + public static void Deconstruct(this KeyValuePair tuple, out T1 key, out T2 value) + { + key = tuple.Key; + value = tuple.Value; + } + } +} diff --git a/Projects/SerializationSchemaGenerator/.gitignore b/Projects/SerializationSchemaGenerator/.gitignore new file mode 100644 index 000000000..2fe317a80 --- /dev/null +++ b/Projects/SerializationSchemaGenerator/.gitignore @@ -0,0 +1 @@ +Output/ diff --git a/Projects/SerializationSchemaGenerator/Application.cs b/Projects/SerializationSchemaGenerator/Application.cs new file mode 100644 index 000000000..59c3da884 --- /dev/null +++ b/Projects/SerializationSchemaGenerator/Application.cs @@ -0,0 +1,84 @@ +/************************************************************************* + * ModernUO * + * Copyright 2019-2021 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: Application.cs * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System; +using System.Collections.Immutable; +using System.IO; +using System.Text.Json; +using System.Threading.Tasks; +using SerializationGenerator; + +namespace SerializationSchemaGenerator +{ + public static class Application + { + public static void Main(string[] args) + { + if (args.Length < 1) + { + throw new ArgumentException("Usage: dotnet SerializationSchemaGenerator.dll "); + } + + var solutionPath = args[0]; + + Parallel.ForEach( + SourceCodeAnalysis.GetCompilation(solutionPath), + (projectCompilation) => + { + var (project, compilation) = projectCompilation; + if (project.Name.EndsWith(".Tests", StringComparison.Ordinal) || project.Name == "Benchmarks") + { + return; + } + + var projectFile = new FileInfo(project.FilePath!); + var migrationPath = Path.Join(projectFile.Directory?.FullName, "Migrations"); + Directory.CreateDirectory(migrationPath); + + var syntaxReceiver = new SerializerSyntaxReceiver(); + + foreach (var syntaxTree in compilation.SyntaxTrees) + { + var root = syntaxTree.GetRoot(); + var syntaxVisitor = new SyntaxVisitor(compilation.GetSemanticModel(syntaxTree), syntaxReceiver); + syntaxVisitor.Visit(root); + } + + var jsonOptions = new JsonSerializerOptions + { + WriteIndented = true, + AllowTrailingCommas = true, + IgnoreNullValues = true, + ReadCommentHandling = JsonCommentHandling.Skip + }; + + var serializableTypes = syntaxReceiver.SerializableList; + + foreach (var (classSymbol, (attributeData, fieldsList)) in syntaxReceiver.ClassAndFields) + { + compilation.GenerateSchema( + classSymbol, + attributeData, + fieldsList.ToImmutableArray(), + migrationPath, + jsonOptions, + serializableTypes + ); + } + } + ); + } + } +} diff --git a/Projects/SerializationSchemaGenerator/SchemaGenerator.cs b/Projects/SerializationSchemaGenerator/SchemaGenerator.cs new file mode 100644 index 000000000..64fe13e78 --- /dev/null +++ b/Projects/SerializationSchemaGenerator/SchemaGenerator.cs @@ -0,0 +1,98 @@ +/************************************************************************* + * ModernUO * + * Copyright 2019-2021 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: SchemaGenerator.cs * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System.Collections.Generic; +using System.Collections.Immutable; +using System.IO; +using System.Linq; +using System.Text.Json; +using Microsoft.CodeAnalysis; +using SerializableMigration; +using SourceGeneration; + +namespace SerializationSchemaGenerator +{ + public static class SchemaGenerator + { + public static void GenerateSchema( + this Compilation compilation, + INamedTypeSymbol classSymbol, + AttributeData serializableAttr, + ImmutableArray fieldsAndProperties, + string migrationPath, + JsonSerializerOptions jsonSerializerOptions, + ImmutableArray serializableTypes + ) + { + var serializableFieldAttribute = + compilation.GetTypeByMetadataName(SymbolMetadata.SERIALIZABLE_FIELD_ATTRIBUTE); + + var version = (int)serializableAttr.ConstructorArguments[0].Value!; + + var serializablePropertySet = new SortedSet(new SerializablePropertyComparer()); + + foreach (var fieldOrPropertySymbol in fieldsAndProperties) + { + var allAttributes = fieldOrPropertySymbol.GetAttributes(); + + var serializableFieldAttr = allAttributes + .FirstOrDefault( + attr => + SymbolEqualityComparer.Default.Equals(attr.AttributeClass, serializableFieldAttribute) + ); + + if (serializableFieldAttr == null) + { + continue; + } + + var order = (int)serializableFieldAttr.ConstructorArguments[0].Value!; + + var serializableProperty = SerializableMigrationRulesEngine.GenerateSerializableProperty( + compilation, + fieldOrPropertySymbol, + order, + allAttributes, + serializableTypes + ); + + if (serializableProperty == null) + { + continue; + } + + serializablePropertySet.Add(serializableProperty); + } + + var serializableProperties = serializablePropertySet.ToImmutableArray(); + + // Write the migration file + var newMigration = new SerializableMetadata + { + Version = version, + Type = classSymbol.ToDisplayString(), + Properties = serializableProperties + }; + WriteMigration(migrationPath, newMigration, jsonSerializerOptions); + } + + public static void WriteMigration(string migrationPath, SerializableMetadata metadata, JsonSerializerOptions options) + { + Directory.CreateDirectory(migrationPath); + var filePath = Path.Combine(migrationPath, $"{metadata.Type}.v{metadata.Version}.json"); + File.WriteAllText(filePath, JsonSerializer.Serialize(metadata, options)); + } + } +} diff --git a/Projects/SerializationSchemaGenerator/SerializationSchemaGenerator.csproj b/Projects/SerializationSchemaGenerator/SerializationSchemaGenerator.csproj new file mode 100644 index 000000000..a299fd22b --- /dev/null +++ b/Projects/SerializationSchemaGenerator/SerializationSchemaGenerator.csproj @@ -0,0 +1,21 @@ + + + Exe + net5.0 + Output + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + diff --git a/Projects/SerializationSchemaGenerator/SourceCodeAnalysis.cs b/Projects/SerializationSchemaGenerator/SourceCodeAnalysis.cs new file mode 100644 index 000000000..ac578b406 --- /dev/null +++ b/Projects/SerializationSchemaGenerator/SourceCodeAnalysis.cs @@ -0,0 +1,49 @@ +/************************************************************************* + * ModernUO * + * Copyright 2019-2021 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: SourceCodeAnalysis.cs * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Microsoft.Build.Locator; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.MSBuild; + +namespace SerializationSchemaGenerator +{ + public static class SourceCodeAnalysis + { + public static List<(Project, Compilation)> GetCompilation(string solutionPath) + { + if (!File.Exists(solutionPath) || !solutionPath.EndsWith(".sln", StringComparison.Ordinal)) + { + throw new FileNotFoundException($"Could not open a valid solution at location {solutionPath}"); + } + + MSBuildLocator.RegisterDefaults(); + + var workspace = MSBuildWorkspace.Create(); + + var solutionToAnalyze = workspace.OpenSolutionAsync(solutionPath).Result; + + var results = solutionToAnalyze.Projects.AsParallel() + .Select((project) => (project, project?.GetCompilationAsync().Result)) + .Where((value) => value.Result != null) + .ToList(); + + return results; + } + } +} diff --git a/Projects/SerializationSchemaGenerator/SyntaxVisitor.cs b/Projects/SerializationSchemaGenerator/SyntaxVisitor.cs new file mode 100644 index 000000000..9c645d271 --- /dev/null +++ b/Projects/SerializationSchemaGenerator/SyntaxVisitor.cs @@ -0,0 +1,49 @@ +/************************************************************************* + * ModernUO * + * Copyright 2019-2021 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: SyntaxVisitor.cs * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using SerializationGenerator; + +namespace SerializationSchemaGenerator +{ + public class SyntaxVisitor : CSharpSyntaxWalker + { + private readonly SemanticModel _semanticModel; + private readonly SerializerSyntaxReceiver _syntaxReceiver; + + public SyntaxVisitor(SemanticModel semanticModel, SerializerSyntaxReceiver syntaxReceiver) + { + _semanticModel = semanticModel; + _syntaxReceiver = syntaxReceiver; + } + + public override void VisitClassDeclaration(ClassDeclarationSyntax node) + { + _syntaxReceiver.OnVisitSyntaxNode(node, _semanticModel); + } + + public override void VisitFieldDeclaration(FieldDeclarationSyntax node) + { + _syntaxReceiver.OnVisitSyntaxNode(node, _semanticModel); + } + + public override void VisitPropertyDeclaration(PropertyDeclarationSyntax node) + { + _syntaxReceiver.OnVisitSyntaxNode(node, _semanticModel); + } + } +} diff --git a/Projects/Server/Server.csproj b/Projects/Server/Server.csproj index 33988e457..8ce4770b2 100755 --- a/Projects/Server/Server.csproj +++ b/Projects/Server/Server.csproj @@ -41,32 +41,26 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + TargetFramework=netstandard2.0 + Analyzer + false + all + + + + + + + + + + + + + + + + diff --git a/Projects/UOContent/UOContent.csproj b/Projects/UOContent/UOContent.csproj index 48e83e195..ee9ca2158 100644 --- a/Projects/UOContent/UOContent.csproj +++ b/Projects/UOContent/UOContent.csproj @@ -39,24 +39,13 @@ - - - - - - - - - - - - - - - - - - - - + + + + + + + + + diff --git a/publish.cmd b/publish.cmd index 859814292..ffb855fff 100755 --- a/publish.cmd +++ b/publish.cmd @@ -30,10 +30,13 @@ fi echo dotnet restore --force-evaluate --source https://api.nuget.org/v3/index.json dotnet restore --force-evaluate --source https://api.nuget.org/v3/index.json -echo dotnet publish ${config} ${os} --no-restore --self-contained=false -o Distribution Projects/Server/Server.csproj -dotnet publish ${config} ${os} --no-restore --self-contained=false -o Distribution Projects/Server/Server.csproj echo dotnet publish ${config} ${os} --no-restore --self-contained=false -o Distribution/Assemblies Projects/UOContent/UOContent.csproj dotnet publish ${config} ${os} --no-restore --self-contained=false -o Distribution/Assemblies Projects/UOContent/UOContent.csproj +echo dotnet build -c Release Projects/SerializationSchemaGenerator/SerializationSchemaGenerator.csproj +dotnet build -c Release Projects/SerializationSchemaGenerator/SerializationSchemaGenerator.csproj +echo Generating serialization schemas +dotnet Projects/SerializationSchemaGenerator/Output/SerializationSchemaGenerator.dll ModernUO.sln + exit $? :CMDSCRIPT @@ -57,7 +60,9 @@ IF "%~2" == "" ( echo dotnet restore --force-evaluate --source https://api.nuget.org/v3/index.json dotnet restore --force-evaluate --source https://api.nuget.org/v3/index.json -echo dotnet publish %config% %os% --no-restore --self-contained=false -o Distribution Projects\Server\Server.csproj -dotnet publish %config% %os% --no-restore --self-contained=false -o Distribution Projects\Server\Server.csproj echo dotnet publish %config% %os% --no-restore --self-contained=false -o Distribution\Assemblies Projects\UOContent\UOContent.csproj dotnet publish %config% %os% --no-restore --self-contained=false -o Distribution\Assemblies Projects\UOContent\UOContent.csproj +echo dotnet build -c Release Projects/SerializationSchemaGenerator/SerializationSchemaGenerator.csproj +dotnet build -c Release Projects/SerializationSchemaGenerator/SerializationSchemaGenerator.csproj +echo Generating serialization schemas +dotnet Projects/SerializationSchemaGenerator/Output/SerializationSchemaGenerator.dll ModernUO.sln