diff --git a/.gitignore b/.gitignore index 20879b3..0eba03e 100644 --- a/.gitignore +++ b/.gitignore @@ -26,9 +26,6 @@ # Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets !packages/*/build/ -# Debug artifacts -launchSettings.json - # Prevent accidental re-checkin of NuGet.exe NuGet.exe diff --git a/Aspid.MVVM.Generators.sln b/Aspid.MVVM.Generators.sln index 3c82bd3..a882cb7 100644 --- a/Aspid.MVVM.Generators.sln +++ b/Aspid.MVVM.Generators.sln @@ -17,9 +17,7 @@ Global {8396B53C-70A1-422E-9E40-9AEBB223F6B3}.Release|Any CPU.ActiveCfg = Release|Any CPU {8396B53C-70A1-422E-9E40-9AEBB223F6B3}.Release|Any CPU.Build.0 = Release|Any CPU {A1DE8BA3-CAEC-4823-99D1-720059565792}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A1DE8BA3-CAEC-4823-99D1-720059565792}.Debug|Any CPU.Build.0 = Debug|Any CPU {A1DE8BA3-CAEC-4823-99D1-720059565792}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A1DE8BA3-CAEC-4823-99D1-720059565792}.Release|Any CPU.Build.0 = Release|Any CPU {837E3102-786A-44C6-9490-1C77BE8D26C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {837E3102-786A-44C6-9490-1C77BE8D26C3}.Debug|Any CPU.Build.0 = Debug|Any CPU {837E3102-786A-44C6-9490-1C77BE8D26C3}.Release|Any CPU.ActiveCfg = Release|Any CPU diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators.Sample/Aspid.MVVM.Generators.Sample.csproj b/Aspid.MVVM.Generators/Aspid.MVVM.Generators.Sample/Aspid.MVVM.Generators.Sample.csproj index 38d009e..6ee56e6 100644 --- a/Aspid.MVVM.Generators/Aspid.MVVM.Generators.Sample/Aspid.MVVM.Generators.Sample.csproj +++ b/Aspid.MVVM.Generators/Aspid.MVVM.Generators.Sample/Aspid.MVVM.Generators.Sample.csproj @@ -1,4 +1,4 @@ - + net9.0 @@ -14,4 +14,15 @@ + + + + Source\%(RecursiveDir)%(Filename)%(Extension) + + + diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators.Sample/ExProperty1Vm.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators.Sample/ExProperty1Vm.cs new file mode 100644 index 0000000..5a76410 --- /dev/null +++ b/Aspid.MVVM.Generators/Aspid.MVVM.Generators.Sample/ExProperty1Vm.cs @@ -0,0 +1,36 @@ +namespace Aspid.MVVM.Generators.Sample; + +[ViewModel] +public partial class ExProperty1Vm +{ + private string _firstName = string.Empty; + private string _lastName = string.Empty; + + [Bind] + public string FirstName + { + get => _firstName; + private set + { + if (SetFirstNameField(ref _firstName, value)) + OnFullNamePropertyChanged(); + } + } + + [Bind] + [BindAlso(nameof(FullName))] + public string LastName + { + get => _lastName; + private set + { + if (_lastName == value) return; + + _lastName = value; + OnLastNamePropertyChanged(); + } + } + + [Bind] + public string FullName => $"{FirstName} {LastName}"; +} diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators.Sample/ExProperty2Vm.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators.Sample/ExProperty2Vm.cs new file mode 100644 index 0000000..2d080d6 --- /dev/null +++ b/Aspid.MVVM.Generators/Aspid.MVVM.Generators.Sample/ExProperty2Vm.cs @@ -0,0 +1,36 @@ +namespace Aspid.MVVM.Generators.Sample; + +[ViewModel] +public partial class ExProperty2Vm +{ + private string _firstName = string.Empty; + private string _lastName = string.Empty; + + [Bind] + public string FirstName + { + get => _firstName; + private set + { + if (SetField(ref _firstName, value)) + OnPropertyChanged(nameof(FullName)); + } + } + + [Bind] + [BindAlso(nameof(FullName))] + public string LastName + { + get => _lastName; + private set + { + if (_lastName == value) return; + + _lastName = value; + OnPropertyChanged(); + } + } + + [Bind] + public string FullName => $"{_firstName} {_lastName}"; +} diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Aspid.MVVM.Generators.csproj b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Aspid.MVVM.Generators.csproj index 19aae24..0441d35 100644 --- a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Aspid.MVVM.Generators.csproj +++ b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Aspid.MVVM.Generators.csproj @@ -4,7 +4,7 @@ netstandard2.0 false enable - 13 + 14 true true @@ -12,12 +12,15 @@ + + all runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Binders/BinderGenerator.Find.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Binders/BinderGenerator.Find.cs index 255c714..b1518ce 100644 --- a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Binders/BinderGenerator.Find.cs +++ b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Binders/BinderGenerator.Find.cs @@ -2,27 +2,27 @@ using System.Threading; using System.Diagnostics; using Microsoft.CodeAnalysis; -using Aspid.Generator.Helpers; +using Aspid.Generators.Helper; using System.Collections.Generic; using Microsoft.CodeAnalysis.CSharp; using System.Runtime.CompilerServices; -using Aspid.MVVM.Generators.Binders.Data; -using Aspid.MVVM.Generators.Descriptions; using Microsoft.CodeAnalysis.CSharp.Syntax; +using Aspid.MVVM.Generators.Generators.Binders.Data; +using Classes = Aspid.MVVM.Generators.Generators.Descriptions.Classes; -namespace Aspid.MVVM.Generators.Binders; +namespace Aspid.MVVM.Generators.Generators.Binders; public partial class BinderGenerator { - private static FoundForGenerator FindBinders(GeneratorSyntaxContext context, + private static BinderData? FindBinders(GeneratorSyntaxContext context, CancellationToken cancellationToken) { Debug.Assert(context.Node is TypeDeclarationSyntax); var candidate = Unsafe.As(context.Node); var symbol = context.SemanticModel.GetDeclaredSymbol(candidate, cancellationToken); - if (symbol is null) return default; - if (!symbol.HasInterfaceInSelfOrBases(Classes.IBinder, out var binderInterface)) return default; + if (symbol is null) return null; + if (!symbol.TryGetAnyInterfaceInSelfAndBases(out var binderInterface, Classes.IBinder)) return null; var hasBinderLogInBaseType = false; const string setValueName = "SetValue"; @@ -35,16 +35,22 @@ private static FoundForGenerator FindBinders(GeneratorSyntaxContext .Any(binderMethod => { if (binderMethod.Name is not setValueName) return false; - + return binderMethod.EqualsSignature(method) && method.ExplicitInterfaceImplementations.Length != 0; }); - - if (methodsExplicitImplemented) return default; - if (!hasBinderLogInBaseType + if (methodsExplicitImplemented) return null; + + // Check if IAnyBinder.SetValue is already explicitly implemented + if (method.IsGenericMethod && method.Name == setValueName + && method.ExplicitInterfaceImplementations.Any(m => + m.ContainingType.Name is "IAnyBinder")) + return null; + + if (!hasBinderLogInBaseType && !SymbolEqualityComparer.Default.Equals(type, symbol) - && method.HasAnyAttribute(Classes.BinderLogAttribute)) + && method.HasAnyAttributeInSelf(Classes.BinderLogAttribute)) { hasBinderLogInBaseType = true; } @@ -55,17 +61,25 @@ private static FoundForGenerator FindBinders(GeneratorSyntaxContext foreach (var method in symbol.GetMembers().OfType()) { - if (method.Parameters.Length != 1) continue; + if (method.Parameters.Length is not 1) continue; if (method.NameFromExplicitImplementation() != setValueName) continue; - if (!symbol.HasInterfaceInSelfOrBases($"{Classes.IBinder.FullName}<{method.Parameters[0].Type.ToDisplayString()}>")) continue; - - if (method.HasAnyAttribute(Classes.BinderLogAttribute) && + + var isIBinderMethod = !method.IsGenericMethod + && symbol.HasAnyInterfaceInSelfAndBases( + $"{Classes.IBinder.FullName}<{method.Parameters[0].Type.ToDisplayString()}>"); + + var isIAnyBinderMethod = method is { IsGenericMethod: true, TypeParameters.Length: 1 } + && symbol.HasAnyInterfaceInSelfAndBases(Classes.IAnyBinder.FullName); + + if (!isIBinderMethod && !isIAnyBinderMethod) continue; + + if (method.HasAnyAttributeInSelf(Classes.BinderLogAttribute) && !method.ExplicitInterfaceImplementations.Any()) binderLogMethods.Add(method); } - - if (binderLogMethods.Count == 0) return default; - - return new FoundForGenerator(new BinderData(symbol, candidate, hasBinderLogInBaseType, binderLogMethods)); + + return binderLogMethods.Count is 0 + ? null + : new BinderData(symbol, candidate, hasBinderLogInBaseType, binderLogMethods); } } \ No newline at end of file diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Binders/BinderGenerator.Generate.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Binders/BinderGenerator.Generate.cs index c99b203..17d6520 100644 --- a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Binders/BinderGenerator.Generate.cs +++ b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Binders/BinderGenerator.Generate.cs @@ -1,10 +1,10 @@ using Microsoft.CodeAnalysis; -using Aspid.Generator.Helpers; -using Aspid.MVVM.Generators.Binders.Body; -using Aspid.MVVM.Generators.Binders.Data; -using Aspid.MVVM.Generators.Descriptions; +using Aspid.Generators.Helper; +using Aspid.MVVM.Generators.Generators.Binders.Body; +using Aspid.MVVM.Generators.Generators.Binders.Data; +using Aspid.MVVM.Generators.Generators.Descriptions; -namespace Aspid.MVVM.Generators.Binders; +namespace Aspid.MVVM.Generators.Generators.Binders; public partial class BinderGenerator { @@ -14,7 +14,7 @@ private static void GenerateCode(SourceProductionContext context, BinderData bin { var declaration = binderData.Declaration; var @namespace = declaration.GetNamespaceName(); - var declarationText = declaration.GetDeclarationText(); + var declarationText = new DeclarationText(declaration); GenerateBinderLog(@namespace, new BinderDataSpan(binderData), context, declarationText); } @@ -31,15 +31,15 @@ private static void GenerateBinderLog( #if DEBUG code.AppendLine($"#if !{Defines.ASPID_MVVM_BINDER_LOG_DISABLED}") - .AppendClassBegin(@namespace, declarationText) + .BeginClass(@namespace, declarationText) .AppendBinderLogBody(data) - .AppendClassEnd(@namespace) + .EndClass(@namespace) .AppendLine("#endif"); #else - code.AppendLine($"#if {Defines.UNITY_EDITOR} && !{Defines.ASPID_MVVM_BINDER_LOG_DISABLED}") - .AppendClassBegin(@namespace, declarationText) + code.AppendLine($"#if {Aspid.Generators.Helper.Unity.Defines.UNITY_EDITOR} && !{Defines.ASPID_MVVM_BINDER_LOG_DISABLED}") + .BeginClass(@namespace, declarationText) .AppendBinderLogBody(data) - .AppendClassEnd(@namespace) + .EndClass(@namespace) .Append("#endif"); #endif diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Binders/BinderGenerator.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Binders/BinderGenerator.cs index 196ad03..f0b64e8 100644 --- a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Binders/BinderGenerator.cs +++ b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Binders/BinderGenerator.cs @@ -3,16 +3,17 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; -namespace Aspid.MVVM.Generators.Binders; +namespace Aspid.MVVM.Generators.Generators.Binders; [Generator] public partial class BinderGenerator : IIncrementalGenerator { public void Initialize(IncrementalGeneratorInitializationContext context) { + // ReSharper disable once NullableWarningSuppressionIsUsed var provider = context.SyntaxProvider.CreateSyntaxProvider(SyntacticPredicate, FindBinders) - .Where(foundForSourceGenerator => foundForSourceGenerator.IsNeed) - .Select((foundForSourceGenerator, _) => foundForSourceGenerator.Container); + .Where(data => data.HasValue) + .Select((data, _) => data!.Value); context.RegisterSourceOutput( source: provider, diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Binders/Body/BinderLogBody.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Binders/Body/BinderLogBody.cs index 51fec3b..06bba5e 100644 --- a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Binders/Body/BinderLogBody.cs +++ b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Binders/Body/BinderLogBody.cs @@ -1,127 +1,178 @@ using System; using Microsoft.CodeAnalysis; -using Aspid.Generator.Helpers; -using Aspid.MVVM.Generators.Binders.Data; -using Aspid.MVVM.Generators.Descriptions; +using Aspid.Generators.Helper; +using Aspid.MVVM.Generators.Generators.Binders.Data; +using static Aspid.Generators.Helper.Classes; +using static Aspid.Generators.Helper.Unity.UnityClasses; +using static Aspid.MVVM.Generators.Generators.Descriptions.Constants; +using Classes = Aspid.MVVM.Generators.Generators.Descriptions.Classes; -namespace Aspid.MVVM.Generators.Binders.Body; +namespace Aspid.MVVM.Generators.Generators.Binders.Body; // ReSharper disable InconsistentNaming public static class BinderLogBody { - private const string GeneratedAttribute = General.GeneratedCodeLogBinderAttribute; - - private static readonly string List = Classes.List.Global; - private static readonly string IBinder = Classes.IBinder.Global; - private static readonly string Exception = Classes.Exception.Global; - private static readonly string SerializeFieldAttribute = Classes.SerializeField.Global; - - public static CodeWriter AppendBinderLogBody(this CodeWriter code, in BinderDataSpan data) - { - var hasBinderLogInBaseType = data.HasBinderLogInBaseType; + private static readonly string IBinder = Classes.IBinder; + private static readonly string IAnyBinder = Classes.IAnyBinder; + private static readonly string Exception = Aspid.Generators.Helper.Classes.Exception; - if (!hasBinderLogInBaseType) + extension(CodeWriter code) + { + public CodeWriter AppendBinderLogBody(in BinderDataSpan data) { - code.AppendProfilerMarkers(data) - .AppendProperties(data); - } + var hasBinderLogInBaseType = data.HasBinderLogInBaseType; + + if (!hasBinderLogInBaseType) + { + code.AppendProfilerMarkers(data) + .AppendProperties(data); + } - code.AppendSetValueMethods(data.Methods); + code.AppendSetValueMethods(data.Methods); - if (!hasBinderLogInBaseType) - code.AppendAddLogMethod(data); + if (!hasBinderLogInBaseType) + code.AppendAddLogMethod(data); - return code; - } + return code; + } - private static CodeWriter AppendProfilerMarkers(this CodeWriter code, in BinderDataSpan data) - { - var modifier = data.Symbol.IsSealed ? "private" : "protected"; - var className = data.Declaration.Identifier.Text; + private CodeWriter AppendProfilerMarkers(in BinderDataSpan data) + { + var modifier = data.Symbol.IsSealed ? "private" : "protected"; + var className = data.Declaration.Identifier.Text; - code.AppendLine($"{modifier} static readonly {Classes.ProfilerMarker.Global} SetValueMarker = new(\"{className}.SetValue\");"); - return code.AppendLine(); - } + code.AppendLine($"{modifier} static readonly {ProfilerMarker} SetValueMarker = new(\"{className}.SetValue\");"); + return code.AppendLine(); + } - private static CodeWriter AppendProperties(this CodeWriter code, in BinderDataSpan data) - { - var modifier = data.Symbol.IsSealed ? "private" : "protected"; + private CodeWriter AppendProperties(in BinderDataSpan data) + { + var modifier = data.Symbol.IsSealed ? "private" : "protected"; - code.AppendMultiline( - $""" - {GeneratedAttribute} - [{SerializeFieldAttribute}] private bool _isDebug; - - // TODO Add Custom Property - {GeneratedAttribute} - [{SerializeFieldAttribute}] private {Classes.List.Global} _log; - - {GeneratedAttribute} - {modifier} bool IsDebug => _isDebug; - """) - .AppendLine(); + code.AppendMultiline( + $""" + {GeneratedCodeLogBinderAttribute} + [{SerializeField}] private bool _isDebug; + + // TODO Add Custom Property + {GeneratedCodeLogBinderAttribute} + [{SerializeField}] private {List_1} _log; + + {GeneratedCodeLogBinderAttribute} + {modifier} bool IsDebug => _isDebug; + """) + .AppendLine(); - return code; - } + return code; + } - private static CodeWriter AppendSetValueMethods(this CodeWriter code, in ReadOnlySpan methods) - { - code.AppendLoop(methods, method => + private CodeWriter AppendSetValueMethods(in ReadOnlySpan methods) + { + foreach (var method in methods) + { + if (method.IsGenericMethod) + code.AppendIAnyBinderSetValueMethod(method); + else + code.AppendIBinderSetValueMethod(method); + + code.AppendLine(); + } + + return code; + } + + private void AppendIBinderSetValueMethod(IMethodSymbol method) { var parameterName = method.Parameters[0].Name; var parameterType = method.Parameters[0].Type.ToDisplayStringGlobal(); - + code.AppendMultiline( $$""" - {{GeneratedAttribute}} - void {{IBinder}}<{{parameterType}}>.{{method.Name}}({{parameterType}} {{parameterName}}) - { - if (IsDebug) - { - try - { - using (SetValueMarker.Auto()) - { - SetValue({{parameterName}}); - } - - AddLog($"SetValue: {{{parameterName}}}"); - } - catch ({{Exception}} e) - { - AddLog($"Exception: {e}. {nameof({{parameterName}})}: {{parameterName}}"); - throw; - } - } - else - { - using (SetValueMarker.Auto()) - { - SetValue({{parameterName}}); - } - } - } - """) - .AppendLine(); - }); + {{GeneratedCodeLogBinderAttribute}} + void {{IBinder}}<{{parameterType}}>.{{method.Name}}({{parameterType}} {{parameterName}}) + { + if (IsDebug) + { + try + { + using (SetValueMarker.Auto()) + { + SetValue({{parameterName}}); + } - return code; - } + AddLog($"SetValue: {{{parameterName}}}"); + } + catch ({{Exception}} e) + { + AddLog($"Exception: {e}. {nameof({{parameterName}})}: {{parameterName}}"); + throw; + } + } + else + { + using (SetValueMarker.Auto()) + { + SetValue({{parameterName}}); + } + } + } + """); + } - private static CodeWriter AppendAddLogMethod(this CodeWriter code, in BinderDataSpan data) - { - var modifier = data.Symbol.IsSealed ? "private" : "protected"; + private void AppendIAnyBinderSetValueMethod(IMethodSymbol method) + { + var typeParamName = method.TypeParameters[0].Name; + var parameterName = method.Parameters[0].Name; + + code.AppendMultiline( + $$""" + {{GeneratedCodeLogBinderAttribute}} + void {{IAnyBinder}}.{{method.Name}}<{{typeParamName}}>({{typeParamName}} {{parameterName}}) + { + if (IsDebug) + { + try + { + using (SetValueMarker.Auto()) + { + SetValue({{parameterName}}); + } + + AddLog($"SetValue: {{{parameterName}}}"); + } + catch ({{Exception}} e) + { + AddLog($"Exception: {e}. {nameof({{parameterName}})}: {{parameterName}}"); + throw; + } + } + else + { + using (SetValueMarker.Auto()) + { + SetValue({{parameterName}}); + } + } + } + """); + } + + private CodeWriter AppendAddLogMethod(in BinderDataSpan data) + { + var modifier = data.Symbol.IsSealed ? "private" : "protected"; - code.AppendMultiline( - $$""" - {{GeneratedAttribute}} - {{modifier}} void AddLog(string log) - { - _log ??= new {{List}}(); - _log.Add(log); - } - """); + code.AppendMultiline( + $$""" + {{GeneratedCodeLogBinderAttribute}} + {{modifier}} void AddLog(string log) + { + _log ??= new {{List_1}}(); + _log.Add(log); + } + """); - return code; + return code; + } } + } \ No newline at end of file diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Binders/Data/BinderData.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Binders/Data/BinderData.cs index a870d34..f2aebe0 100644 --- a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Binders/Data/BinderData.cs +++ b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Binders/Data/BinderData.cs @@ -3,7 +3,7 @@ using System.Collections.Immutable; using Microsoft.CodeAnalysis.CSharp.Syntax; -namespace Aspid.MVVM.Generators.Binders.Data; +namespace Aspid.MVVM.Generators.Generators.Binders.Data; public readonly struct BinderData( INamedTypeSymbol symbol, diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Binders/Data/BinderDataSpan.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Binders/Data/BinderDataSpan.cs index e3a607b..f2a5ddd 100644 --- a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Binders/Data/BinderDataSpan.cs +++ b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Binders/Data/BinderDataSpan.cs @@ -2,7 +2,7 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; -namespace Aspid.MVVM.Generators.Binders.Data; +namespace Aspid.MVVM.Generators.Generators.Binders.Data; public readonly ref struct BinderDataSpan(BinderData data) { diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/CreateFrom/Body/CreateFromBody.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/CreateFrom/Body/CreateFromBody.cs deleted file mode 100644 index 4bead8e..0000000 --- a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/CreateFrom/Body/CreateFromBody.cs +++ /dev/null @@ -1,313 +0,0 @@ -using System; -using System.Text; -using Microsoft.CodeAnalysis; -using Aspid.Generator.Helpers; -using Aspid.MVVM.Generators.Descriptions; -using Aspid.MVVM.Generators.CreateFrom.Data; - -namespace Aspid.MVVM.Generators.CreateFrom.Body; - -// ReSharper disable InconsistentNaming -public static class CreateFromBody -{ - private const string GeneratedAttribute = General.GeneratedCodeCreateFromAttribute; - - private static readonly string List = Classes.List.Global; - private static readonly string Func = Classes.Func.Global; - private static readonly string Span = Classes.Span.Global; - private static readonly string IList = Classes.IList.Global; - private static readonly string IEnumerable = Classes.IEnumerable.Global; - private static readonly string ReadOnlySpan = Classes.ReadOnlySpan.Global; - private static readonly SymbolEqualityComparer Comparer = SymbolEqualityComparer.Default; - - public static CodeWriter AppendCreateFromBody(this CodeWriter code, in CreateFromDataSpan data) - { - if (data.Constructor.Parameters.Length == 0) return code; - var parameters = GetParameters(data.Constructor, data.FromType); - - code.AppendLine($"[{Classes.MethodImplAttribute.Global}({Classes.MethodImplOptions.Global}.AggressiveInlining)]") - .AppendMethodDeclaration(data, parameters, ParameterType.None, ParameterType.None) - .AppendMultiline( - $$""" - { - return new({{parameters.Constructor(parameters.From)}}); - } - """) - .AppendLine(); - - code.AppendArrayMethodBody(data, ParameterType.Array, parameters) - .AppendArrayMethodBody(data, ParameterType.Span, parameters) - .AppendArrayMethodBody(data, ParameterType.ReadOnlySpan, parameters) - .AppendArrayMethodBody(data, ParameterType.List, parameters); - - code.AppendListMethodBody(data, ParameterType.Array, parameters) - .AppendListMethodBody(data, ParameterType.List, parameters) - .AppendListMethodBody(data, ParameterType.Span, parameters) - .AppendListMethodBody(data, ParameterType.ReadOnlySpan, parameters) - .AppendListMethodBody(data ,ParameterType.Enumerable, parameters); - - code.AppendIListMethodBody(data, ParameterType.Array, parameters) - .AppendIListMethodBody(data, ParameterType.List, parameters) - .AppendIListMethodBody(data, ParameterType.Span, parameters) - .AppendIListMethodBody(data, ParameterType.ReadOnlySpan, parameters) - .AppendIListMethodBody(data, ParameterType.Enumerable, parameters); - - code.AppendEnumerableMethodBody(data, ParameterType.Array, ParameterType.Enumerable, parameters) - .AppendEnumerableMethodBody(data, ParameterType.List, ParameterType.Enumerable, parameters) - .AppendEnumerableMethodBody(data, ParameterType.Enumerable, ParameterType.Enumerable, parameters); - - return code; - } - - private static CodeWriter AppendArrayMethodBody( - this CodeWriter code, - in CreateFromDataSpan data, - ParameterType fromParameterType, - in Parameters parameters) - { - var fromName = parameters.From; - - var capacity = GetLengthType(fromParameterType) switch - { - LengthType.None => "0", - LengthType.Count => $"{fromName}.Count", - LengthType.Length => $"{fromName}.Length", - _ => throw new ArgumentOutOfRangeException() - }; - - return code - .AppendMethodDeclaration(data, parameters, fromParameterType, ParameterType.Array) - .AppendMultiline( - $$""" - { - var __to = new {{data.ToTypeName}}[{{capacity}}]; - - for (var __i = 0; __i < __to.Length; __i++) - __to[__i] = {{fromName}}[__i].{{data.MethodName}}({{parameters.EnumParameters}}); - - return __to; - } - """) - .AppendLine(); - } - - private static CodeWriter AppendListMethodBody( - this CodeWriter code, - in CreateFromDataSpan data, - ParameterType fromParameterType, - in Parameters parameters) - { - var capacity = GetLengthType(fromParameterType) switch - { - LengthType.None => string.Empty, - LengthType.Count => $"{parameters.From}.Count", - LengthType.Length => $"{parameters.From}.Length", - _ => throw new ArgumentOutOfRangeException() - }; - - return code - .AppendMethodDeclaration(data, parameters, fromParameterType, ParameterType.List) - .AppendMultiline( - $$""" - { - var __to = new {{List}}<{{data.ToTypeName}}>({{capacity}}); - - foreach(var __from in {{parameters.From}}) - __to.Add(__from.{{data.MethodName}}({{parameters.EnumParameters}})); - - return __to; - } - """) - .AppendLine(); - } - - private static CodeWriter AppendIListMethodBody( - this CodeWriter code, - in CreateFromDataSpan data, - ParameterType fromParameterType, - in Parameters parameters) - { - return code - .AppendMethodDeclaration(data, parameters, fromParameterType, ParameterType.IList) - .AppendMultiline( - $$""" - { - var __to = __createList(); - - foreach(var __from in {{parameters.From}}) - __to.Add(__from.{{data.MethodName}}({{parameters.EnumParameters}})); - - return __to; - } - """) - .AppendLine(); - } - - private static CodeWriter AppendEnumerableMethodBody( - this CodeWriter code, - in CreateFromDataSpan data, - ParameterType fromParameterType, - ParameterType toParameterType, - in Parameters parameters) - { - return code - .AppendMethodDeclaration(data, parameters, fromParameterType, toParameterType) - .AppendMultiline( - $$""" - { - foreach (var __from in {{parameters.From}}) - yield return __from.{{data.MethodName}}({{parameters.EnumParameters}}); - } - """) - .AppendLine(); - } - - private static CodeWriter AppendMethodDeclaration( - this CodeWriter code, - in CreateFromDataSpan data, - in Parameters parameters, - ParameterType fromParameterType, - ParameterType toParameterType) - { - var methodName = data.MethodName; - var returnType = data.ToTypeName; - var additionalParameter = string.Empty; - - switch (toParameterType) - { - case ParameterType.None: break; - case ParameterType.List: - methodName += "AsList"; - returnType = $"{List}<{returnType}>"; - break; - - case ParameterType.Span: - case ParameterType.Array: - case ParameterType.ReadOnlySpan: - methodName += "AsArray"; - returnType += "[]"; - break; - - case ParameterType.Enumerable: - methodName += "AsEnumerable"; - returnType = $"{IEnumerable}<{returnType}>"; - break; - - case ParameterType.IList: - methodName += "AsList"; - returnType = $"{IList}<{returnType}>"; - additionalParameter = $", {Func}<{IList}<{data.ToTypeName}>> __createList"; - break; - - default: throw new ArgumentOutOfRangeException(nameof(toParameterType), toParameterType, null); - } - - if (!data.CanBeInherited) - { - return code.AppendMultiline( - $""" - {GeneratedAttribute} - public static {returnType} {methodName}({parameters.Method(fromParameterType, data.FromTypeName)}{additionalParameter}) - """); - } - - return code.AppendMultiline( - $""" - {GeneratedAttribute} - public static {returnType} {methodName}({parameters.Method(fromParameterType, "T")}{additionalParameter}) - where T : {data.FromTypeName} - """); - } - - private static LengthType GetLengthType(ParameterType type) => type switch - { - ParameterType.None => LengthType.None, - ParameterType.Array => LengthType.Length, - ParameterType.List => LengthType.Count, - ParameterType.IList => LengthType.Count, - ParameterType.Span => LengthType.Length, - ParameterType.ReadOnlySpan => LengthType.Length, - ParameterType.Enumerable => LengthType.None, - _ => throw new ArgumentOutOfRangeException(nameof(type), type, null) - }; - - private static Parameters GetParameters(IMethodSymbol constructor, ITypeSymbol fromType) - { - string? fromName = null; - StringBuilder parameters = new(); - StringBuilder methodParameters = new(); - StringBuilder constructorParameters = new(); - - foreach (var parameter in constructor.Parameters) - { - if (constructorParameters.Length is not 0) - constructorParameters.Append(", "); - - if (fromName is null && Comparer.Equals(parameter.Type, fromType)) - { - fromName = parameter.Name; - constructorParameters.Append("{0}"); - } - else - { - if (parameters.Length is not 0) - parameters.Append(", "); - - parameters.Append($"{parameter.Name}"); - methodParameters.Append($", {parameter.Type.ToDisplayStringGlobal()} {parameter.Name}"); - constructorParameters.Append($"{parameter.Name}"); - } - } - - return new Parameters(fromName!, methodParameters, parameters, constructorParameters); - } - - private readonly ref struct Parameters( - string from, - StringBuilder method, - StringBuilder parameters, - StringBuilder constructor) - { - public readonly string From = from; - public readonly string EnumParameters = parameters.ToString(); - - private readonly string _methods = from + method; - private readonly string _constructor = constructor.ToString(); - - - public string Method(ParameterType type, string fromType) - { - return type switch - { - ParameterType.None => $"this {fromType} {_methods}", - ParameterType.Array => $"this {fromType}[] {_methods}", - ParameterType.Span => $"this {Span}<{fromType}> {_methods}", - ParameterType.List => $"this {List}<{fromType}> {_methods}", - ParameterType.IList => $"this {IList}<{fromType}> {_methods}", - ParameterType.ReadOnlySpan => $"this {ReadOnlySpan}<{fromType}> {_methods}", - ParameterType.Enumerable => $"this {IEnumerable}<{fromType}> {_methods}", - _ => throw new ArgumentOutOfRangeException(nameof(type), type, null) - }; - } - - public string Constructor(string fromName) => string.Format(_constructor, fromName); - } - - private enum LengthType - { - None, - Count, - Length, - } - - private enum ParameterType - { - None, - List, - IList, - Array, - Span, - ReadOnlySpan, - Enumerable, - } -} \ No newline at end of file diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/CreateFrom/CreateFromGenerator.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/CreateFrom/CreateFromGenerator.cs deleted file mode 100644 index 3b77551..0000000 --- a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/CreateFrom/CreateFromGenerator.cs +++ /dev/null @@ -1,91 +0,0 @@ -using System.Linq; -using System.Threading; -using Microsoft.CodeAnalysis; -using Aspid.Generator.Helpers; -using Microsoft.CodeAnalysis.CSharp; -using System.Runtime.CompilerServices; -using Aspid.MVVM.Generators.Descriptions; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Aspid.MVVM.Generators.CreateFrom.Body; -using Aspid.MVVM.Generators.CreateFrom.Data; - -namespace Aspid.MVVM.Generators.CreateFrom; - -[Generator(LanguageNames.CSharp)] -public class CreateFromGenerator : IIncrementalGenerator -{ - public void Initialize(IncrementalGeneratorInitializationContext context) - { - var provider = context.SyntaxProvider.ForAttributeWithMetadataName(Classes.CreateFromAttribute.FullName, SyntacticPredicate, FindCreateFrom). - Where(foundForSourceGenerator => foundForSourceGenerator.IsNeed). - Select((foundForSourceGenerator, _) => foundForSourceGenerator.Container); - - context.RegisterSourceOutput( - source: provider, - action: GenerateCode); - } - - private static bool SyntacticPredicate(SyntaxNode node, CancellationToken cancellationToken) - { - var candidate = node switch - { - ConstructorDeclarationSyntax syntax => syntax, - _ => null - }; - - return candidate is not null && !candidate.Modifiers.Any(SyntaxKind.StaticKeyword); - } - - private static FoundForGenerator FindCreateFrom( - GeneratorAttributeSyntaxContext context, - CancellationToken cancellationToken) - { - if (context.TargetSymbol is not IMethodSymbol constructor) return default; - - var attribute = context.Attributes.First(attribute => - attribute.AttributeClass?.ToDisplayString() == Classes.CreateFromAttribute.FullName); - - if (attribute.ConstructorArguments.First().Value is not ITypeSymbol fromType) return default; - - if (constructor.Parameters.Length == 0) return default; - var candidate = Unsafe.As(context.TargetNode); - return new FoundForGenerator(new CreateFromData(candidate, constructor, fromType)); - } - - private static void GenerateCode(SourceProductionContext context, CreateFromData data) - { - var @namespace = data.Declaration.GetNamespaceName(); - var dataSpan = new CreateFromDataSpan(data); - - if (data.Declaration.Parent is not TypeDeclarationSyntax typeDeclaration) return; - - var i = 0; - var index = -1; - - foreach (var constructor in typeDeclaration.Members.OfType()) - { - if (constructor == data.Declaration) - { - index = i; - break; - } - - i++; - } - - if (index == -1) return; - - var declaration = new DeclarationText( - "public static", - "class", - $"{dataSpan.FromType.ToDisplayString().Replace(".", "_")}To{dataSpan.ToType.ToDisplayString().Replace(".", "_")}_{index}", - null); - - var code = new CodeWriter(); - code.AppendClassBegin(@namespace, declaration) - .AppendCreateFromBody(dataSpan) - .AppendClassEnd(@namespace); - - context.AddSource(declaration.GetFileName(@namespace, "IViewModel"), code.GetSourceText()); - } -} \ No newline at end of file diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/CreateFrom/Data/CreateFromData.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/CreateFrom/Data/CreateFromData.cs deleted file mode 100644 index b02b9ab..0000000 --- a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/CreateFrom/Data/CreateFromData.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp.Syntax; - -namespace Aspid.MVVM.Generators.CreateFrom.Data; - -public readonly struct CreateFromData( - ConstructorDeclarationSyntax declaration, - IMethodSymbol constructor, - ITypeSymbol fromType) -{ - public readonly ITypeSymbol FromType = fromType; - public readonly IMethodSymbol Constructor = constructor; - public readonly ConstructorDeclarationSyntax Declaration = declaration; -} \ No newline at end of file diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/CreateFrom/Data/CreateFromDataSpan.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/CreateFrom/Data/CreateFromDataSpan.cs deleted file mode 100644 index 82c5741..0000000 --- a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/CreateFrom/Data/CreateFromDataSpan.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Microsoft.CodeAnalysis; -using Aspid.Generator.Helpers; - -namespace Aspid.MVVM.Generators.CreateFrom.Data; - -public readonly ref struct CreateFromDataSpan(CreateFromData data) -{ - public readonly CreateFromData Data = data; - public readonly IMethodSymbol Constructor = data.Constructor; - - public readonly ITypeSymbol ToType = data.Constructor.ContainingType; - public readonly string ToName = data.Declaration.Identifier.Text; - public readonly string ToTypeName = data.Constructor.ContainingType.ToDisplayStringGlobal(); - - public readonly ITypeSymbol FromType = data.FromType; - public readonly string FromTypeName = data.FromType.ToDisplayStringGlobal(); - - public readonly string MethodName = $"To{data.Declaration.Identifier.Text}"; - public readonly bool CanBeInherited = data.FromType.TypeKind != TypeKind.Struct && !data.FromType.IsSealed; -} \ No newline at end of file diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Descriptions/Classes.Aspid.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Descriptions/Classes.Aspid.cs index 4535b0e..b8c58d6 100644 --- a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Descriptions/Classes.Aspid.cs +++ b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Descriptions/Classes.Aspid.cs @@ -1,23 +1,37 @@ -using Aspid.Generator.Helpers; +using Aspid.Generators.Helper; -namespace Aspid.MVVM.Generators.Descriptions; +namespace Aspid.MVVM.Generators.Generators.Descriptions; // ReSharper disable InconsistentNaming -public static partial class Classes +public static class Classes { #region Views public static readonly TypeText IView = new(nameof(IView), Namespaces.Aspid_MVVM); public static readonly AttributeText ViewAttribute = - new("View", Namespaces.Aspid_MVVM); + new("ViewAttribute", Namespaces.Aspid_MVVM); public static readonly AttributeText AsBinderAttribute = - new("AsBinder", Namespaces.Aspid_MVVM); + new("AsBinderAttribute", Namespaces.Aspid_MVVM); public static readonly TypeText ViewBinder = new (nameof(ViewBinder), Namespaces.Aspid_MVVM); #endregion + + #region Headers + public static readonly AttributeText HeaderAttribute = + new(nameof(HeaderAttribute), "UnityEngine"); + + public static readonly AttributeText HeaderGroupAttribute = + new (nameof(HeaderGroupAttribute), Namespaces.Aspid_MVVM); + + public static readonly AttributeText HeaderGroupStartAttribute = + new (nameof(HeaderGroupStartAttribute), Namespaces.Aspid_MVVM); + + public static readonly AttributeText HeaderGroupEndAttribute = + new (nameof(HeaderGroupEndAttribute), Namespaces.Aspid_MVVM); + #endregion #region Binders public static readonly TypeText BindMode = @@ -25,18 +39,21 @@ public static partial class Classes public static readonly TypeText IBinder = new(nameof(IBinder), Namespaces.Aspid_MVVM); - + + public static readonly TypeText IAnyBinder = + new(nameof(IAnyBinder), Namespaces.Aspid_MVVM); + public static readonly TypeText IReverseBinder = new(nameof(IReverseBinder), Namespaces.Aspid_MVVM); public static readonly TypeText MonoBinder = - new(nameof(MonoBinder), Namespaces.Aspid_MVVM_UNITY); + new(nameof(MonoBinder), Namespaces.Aspid_MVVM); public static readonly AttributeText BinderLogAttribute = - new("BinderLog", Namespaces.Aspid_MVVM); + new("BinderLogAttribute", Namespaces.Aspid_MVVM); public static readonly AttributeText RequireBinderAttribute = - new("RequireBinder", Namespaces.Aspid_MVVM); + new("RequireBinderAttribute", Namespaces.Aspid_MVVM); #endregion #region View Models @@ -44,7 +61,7 @@ public static partial class Classes new(nameof(IViewModel), Namespaces.Aspid_MVVM); public static readonly AttributeText ViewModelAttribute = - new("ViewModel", Namespaces.Aspid_MVVM); + new("ViewModelAttribute", Namespaces.Aspid_MVVM); public static readonly TypeText FindBindableMemberResult = new(nameof(FindBindableMemberResult), Namespaces.Aspid_MVVM); @@ -55,31 +72,31 @@ public static partial class Classes #region Bind Attributes public static readonly AttributeText BindAttribute = - new("Bind", Namespaces.Aspid_MVVM); + new("BindAttribute", Namespaces.Aspid_MVVM); public static readonly AttributeText IdAttribute = - new("BindId", Namespaces.Aspid_MVVM); + new("BindIdAttribute", Namespaces.Aspid_MVVM); public static readonly AttributeText AccessAttribute = - new("Access", Namespaces.Aspid_MVVM); + new("AccessAttribute", Namespaces.Aspid_MVVM); public static readonly AttributeText BindAlsoAttribute = - new("BindAlso", Namespaces.Aspid_MVVM); + new("BindAlsoAttribute", Namespaces.Aspid_MVVM); public static readonly AttributeText IgnoreAttribute = - new("IgnoreBind", Namespaces.Aspid_MVVM); + new("IgnoreBindAttribute", Namespaces.Aspid_MVVM); public static readonly AttributeText OneWayBindAttribute = - new("OneWayBind", Namespaces.Aspid_MVVM); + new("OneWayBindAttribute", Namespaces.Aspid_MVVM); public static readonly AttributeText TwoWayBindAttribute = - new("TwoWayBind", Namespaces.Aspid_MVVM); + new("TwoWayBindAttribute", Namespaces.Aspid_MVVM); public static readonly AttributeText OneTimeBindAttribute = - new("OneTimeBind", Namespaces.Aspid_MVVM); + new("OneTimeBindAttribute", Namespaces.Aspid_MVVM); public static readonly AttributeText OneWayToSourceBindAttribute = - new("OneWayToSourceBind", Namespaces.Aspid_MVVM); + new("OneWayToSourceBindAttribute", Namespaces.Aspid_MVVM); #endregion #region Bindable Member Events @@ -98,40 +115,40 @@ public static partial class Classes public static readonly TypeText IBinderRemover = new(nameof(IBinderRemover), Namespaces.Aspid_MVVM); - public static readonly TypeText? OneWayBindableMember = + public static readonly TypeText OneWayBindableMember = new(nameof(OneWayBindableMember), Namespaces.Aspid_MVVM); - public static readonly TypeText? OneWayStructBindableMember = + public static readonly TypeText OneWayStructBindableMember = new(nameof(OneWayStructBindableMember), Namespaces.Aspid_MVVM); - public static readonly TypeText? OneWayEnumBindableMember = + public static readonly TypeText OneWayEnumBindableMember = new(nameof(OneWayEnumBindableMember), Namespaces.Aspid_MVVM); - public static readonly TypeText? TwoWayBindableMember = + public static readonly TypeText TwoWayBindableMember = new(nameof(TwoWayBindableMember), Namespaces.Aspid_MVVM); - public static readonly TypeText? TwoWayStructBindableMember = + public static readonly TypeText TwoWayStructBindableMember = new(nameof(TwoWayStructBindableMember), Namespaces.Aspid_MVVM); - public static readonly TypeText? TwoWayEnumBindableMember = + public static readonly TypeText TwoWayEnumBindableMember = new(nameof(TwoWayEnumBindableMember), Namespaces.Aspid_MVVM); - public static readonly TypeText? OneTimeBindableMember = + public static readonly TypeText OneTimeBindableMember = new(nameof(OneTimeBindableMember), Namespaces.Aspid_MVVM); - public static readonly TypeText? OneTimeStructBindableMember = + public static readonly TypeText OneTimeStructBindableMember = new(nameof(OneTimeStructBindableMember), Namespaces.Aspid_MVVM); - public static readonly TypeText? OneTimeEnumBindableMember = + public static readonly TypeText OneTimeEnumBindableMember = new(nameof(OneTimeEnumBindableMember), Namespaces.Aspid_MVVM); - public static readonly TypeText? OneWayToSourceBindableMember = + public static readonly TypeText OneWayToSourceBindableMember = new(nameof(OneWayToSourceBindableMember), Namespaces.Aspid_MVVM); - public static readonly TypeText? OneWayToSourceStructBindableMember = + public static readonly TypeText OneWayToSourceStructBindableMember = new(nameof(OneWayToSourceStructBindableMember), Namespaces.Aspid_MVVM); - public static readonly TypeText? OneWayToSourceEnumBindableMember = + public static readonly TypeText OneWayToSourceEnumBindableMember = new(nameof(OneWayToSourceEnumBindableMember), Namespaces.Aspid_MVVM); #endregion @@ -143,7 +160,7 @@ public static partial class Classes new(nameof(IRelayCommand), Namespaces.Aspid_MVVM); public static readonly AttributeText RelayCommandAttribute = - new("RelayCommand", Namespaces.Aspid_MVVM); + new("RelayCommandAttribute", Namespaces.Aspid_MVVM); #endregion public static readonly TypeText Unsafe = @@ -153,8 +170,8 @@ public static partial class Classes new(nameof(Ids), Namespaces.Aspid_MVVM_Generated); public static readonly AttributeText CreateFromAttribute = - new("CreateFrom", Namespaces.Aspid_MVVM); + new("CreateFromAttribute", Namespaces.Aspid_MVVM); public static readonly AttributeText AddComponentContextMenuAttribute = - new ("AddComponentContextMenu", Namespaces.Aspid_MVVM_UNITY); + new ("AddComponentContextMenuAttribute", Namespaces.Aspid_MVVM); } \ No newline at end of file diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Descriptions/Classes.System.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Descriptions/Classes.System.cs deleted file mode 100644 index 3a8d0f2..0000000 --- a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Descriptions/Classes.System.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Aspid.Generator.Helpers; - -namespace Aspid.MVVM.Generators.Descriptions; - -public static partial class Classes -{ - public static readonly TypeText Span = new("Span", Namespaces.System); - public static readonly TypeText ReadOnlySpan = new("ReadOnlySpan", Namespaces.System); - public static readonly TypeText Func = new("Func", Namespaces.System); - public static readonly TypeText Action = new("Action", Namespaces.System); - public static readonly TypeText Delegate = new("Delegate", Namespaces.System); - - public static readonly TypeText Exception = new("Exception", Namespaces.System); - public static readonly TypeText ArgumentNullException = new("ArgumentNullException", Namespaces.System); - public static readonly TypeText InvalidOperationException = new("InvalidOperationException", Namespaces.System); - - public static readonly TypeText List = new("List", Namespaces.System_Collections_Generic); - public static readonly TypeText IList = new("IList", Namespaces.System_Collections_Generic); - public static readonly TypeText Dictionary = new("Dictionary", Namespaces.System_Collections_Generic); - public static readonly TypeText IEnumerable = new("IEnumerable", Namespaces.System_Collections_Generic); - public static readonly TypeText EqualityComparer = new("EqualityComparer", Namespaces.System_Collections_Generic); - - public static readonly TypeText EditorBrowsableState = new(nameof(EditorBrowsableState), Namespaces.System_ComponentModel); - public static readonly AttributeText EditorBrowsableAttribute = new("EditorBrowsable", Namespaces.System_ComponentModel); - - public static readonly TypeText MethodImplOptions = new("MethodImplOptions", Namespaces.System_Runtime_CompilerServices); - public static readonly AttributeText MethodImplAttribute = new("MethodImpl", Namespaces.System_Runtime_CompilerServices); -} \ No newline at end of file diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Descriptions/Classes.UnityEngine.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Descriptions/Classes.UnityEngine.cs deleted file mode 100644 index aa04347..0000000 --- a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Descriptions/Classes.UnityEngine.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Aspid.Generator.Helpers; - -namespace Aspid.MVVM.Generators.Descriptions; - -public static partial class Classes -{ - public static readonly TypeText ProfilerMarker = new("ProfilerMarker", Namespaces.Unity_Profiling); - - public static readonly TypeText Object = new("Object", Namespaces.UnityEngine); - public static readonly TypeText Component = new("Component", Namespaces.UnityEngine); - public static readonly TypeText MonoBehaviour = new("MonoBehaviour", Namespaces.UnityEngine); - public static readonly TypeText SerializeField = new("SerializeField", Namespaces.UnityEngine); - - public static readonly TypeText MenuItem = new("MenuItem", Namespaces.UnityEditor); - public static readonly TypeText MenuCommand = new("MenuCommand", Namespaces.UnityEditor); - - public static readonly TypeText Button = new("Button", Namespaces.UnityEngine_UI); - public static readonly TypeText Toggle = new("Toggle", Namespaces.UnityEngine_UI); - public static readonly TypeText Slider = new("Slider", Namespaces.UnityEngine_UI); - public static readonly TypeText ScrollRect = new("ScrollRect", Namespaces.UnityEngine_UI); -} \ No newline at end of file diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Descriptions/General.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Descriptions/Constants.cs similarity index 52% rename from Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Descriptions/General.cs rename to Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Descriptions/Constants.cs index 7d1aaf3..37b90bf 100644 --- a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Descriptions/General.cs +++ b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Descriptions/Constants.cs @@ -1,19 +1,20 @@ -namespace Aspid.MVVM.Generators.Descriptions; +using static Aspid.Generators.Helper.Classes; -public class General +namespace Aspid.MVVM.Generators.Generators.Descriptions; + +public static class Constants { public const string GeneratedCodeIdAttribute = - "[global::System.CodeDom.Compiler.GeneratedCode(\"Aspid.MVVM.Generators.IdGenerator\", \"1.0.0\")]"; + "[global::System.CodeDom.Compiler.GeneratedCode(\"Aspid.MVVM.Generators.IdGenerator\", \"1.1.0\")]"; public const string GeneratedCodeViewAttribute = - "[global::System.CodeDom.Compiler.GeneratedCode(\"Aspid.MVVM.Generators.ViewGenerator\", \"1.0.0\")]"; + "[global::System.CodeDom.Compiler.GeneratedCode(\"Aspid.MVVM.Generators.ViewGenerator\", \"1.1.0\")]"; public const string GeneratedCodeLogBinderAttribute = - "[global::System.CodeDom.Compiler.GeneratedCode(\"Aspid.MVVM.Generators.LogBinderGenerator\", \"1.0.0\")]"; - - public const string GeneratedCodeCreateFromAttribute = - "[global::System.CodeDom.Compiler.GeneratedCode(\"Aspid.MVVM.Generators.CreateFromGenerator\", \"1.0.0\")]"; + "[global::System.CodeDom.Compiler.GeneratedCode(\"Aspid.MVVM.Generators.LogBinderGenerator\", \"1.1.0\")]"; public const string GeneratedCodeViewModelAttribute = - "[global::System.CodeDom.Compiler.GeneratedCode(\"Aspid.MVVM.Generators.ViewModelGenerator\", \"1.0.0\")]"; + "[global::System.CodeDom.Compiler.GeneratedCode(\"Aspid.MVVM.Generators.ViewModelGenerator\", \"1.1.0\")]"; + + public static readonly string EditorBrowsableAttributeNever = $"[{EditorBrowsableAttribute}({EditorBrowsableState}.Never)]"; } \ No newline at end of file diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Descriptions/Defines.Aspid.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Descriptions/Defines.Aspid.cs index a15c35a..69ee7e4 100644 --- a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Descriptions/Defines.Aspid.cs +++ b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Descriptions/Defines.Aspid.cs @@ -1,7 +1,7 @@ -namespace Aspid.MVVM.Generators.Descriptions; +namespace Aspid.MVVM.Generators.Generators.Descriptions; // ReSharper disable InconsistentNaming -public static partial class Defines +public static class Defines { public const string ASPID_MVVM_BINDER_LOG_DISABLED = nameof(ASPID_MVVM_BINDER_LOG_DISABLED); diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Descriptions/Defines.UnityEngine.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Descriptions/Defines.UnityEngine.cs deleted file mode 100644 index c5601dc..0000000 --- a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Descriptions/Defines.UnityEngine.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Aspid.MVVM.Generators.Descriptions; - -// ReSharper disable InconsistentNaming -public static partial class Defines -{ - public const string UNITY_EDITOR = nameof(UNITY_EDITOR); - - public const string UNITY_IOS = nameof(UNITY_IOS); - public const string UNITY_ANDROID = nameof(UNITY_ANDROID); -} \ No newline at end of file diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Descriptions/Namespaces.Aspid.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Descriptions/Namespaces.Aspid.cs index 345c9f3..44cf406 100644 --- a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Descriptions/Namespaces.Aspid.cs +++ b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Descriptions/Namespaces.Aspid.cs @@ -1,13 +1,12 @@ -using Aspid.Generator.Helpers; +using Aspid.Generators.Helper; -namespace Aspid.MVVM.Generators.Descriptions; +namespace Aspid.MVVM.Generators.Generators.Descriptions; // ReSharper disable InconsistentNaming -public static partial class Namespaces +public static class Namespaces { public static readonly NamespaceText Aspid = new(nameof(Aspid)); public static readonly NamespaceText Aspid_MVVM = new("MVVM", Aspid); - public static readonly NamespaceText Aspid_MVVM_UNITY = new("Unity", Aspid_MVVM); public static readonly NamespaceText Aspid_MVVM_Generated = new("Generated", Aspid_MVVM); } \ No newline at end of file diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Descriptions/Namespaces.System.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Descriptions/Namespaces.System.cs deleted file mode 100644 index 44f3571..0000000 --- a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Descriptions/Namespaces.System.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Aspid.Generator.Helpers; - -namespace Aspid.MVVM.Generators.Descriptions; - -// ReSharper disable InconsistentNaming -public static partial class Namespaces -{ - public static readonly NamespaceText System = new(nameof(System)); - public static readonly NamespaceText System_Runtime = new("Runtime", System); - public static readonly NamespaceText System_Collections = new("Collections", System); - public static readonly NamespaceText System_ComponentModel = new("ComponentModel", System); - public static readonly NamespaceText System_Collections_Generic = new("Generic", System_Collections); - public static readonly NamespaceText System_Runtime_CompilerServices = new("CompilerServices", System_Runtime); -} \ No newline at end of file diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Descriptions/Namespaces.UnityEngine.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Descriptions/Namespaces.UnityEngine.cs deleted file mode 100644 index cfd9f33..0000000 --- a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Descriptions/Namespaces.UnityEngine.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Aspid.Generator.Helpers; - -namespace Aspid.MVVM.Generators.Descriptions; - -// ReSharper disable InconsistentNaming -public static partial class Namespaces -{ - public static readonly NamespaceText Unity = new("Unity"); - public static readonly NamespaceText Unity_Profiling = new("Profiling", Unity); - - public static readonly NamespaceText UnityEngine = new("UnityEngine"); - public static readonly NamespaceText UnityEngine_UI = new("UI", UnityEngine); - - public static readonly NamespaceText UnityEditor = new("UnityEditor"); -} \ No newline at end of file diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Ids/Data/IdData.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Ids/Data/IdData.cs index 764e4b7..fd45829 100644 --- a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Ids/Data/IdData.cs +++ b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Ids/Data/IdData.cs @@ -1,9 +1,9 @@ using System; using Microsoft.CodeAnalysis; -using Aspid.MVVM.Generators.Descriptions; -using Aspid.MVVM.Generators.Ids.Extensions; +using Aspid.MVVM.Generators.Generators.Descriptions; +using Aspid.MVVM.Generators.Generators.Ids.Extensions; -namespace Aspid.MVVM.Generators.Ids.Data; +namespace Aspid.MVVM.Generators.Generators.Ids.Data; public readonly struct IdData : IEquatable { diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Ids/Extensions/IdGeneratorExtensions.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Ids/Extensions/IdGeneratorExtensions.cs index 06eb234..624595e 100644 --- a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Ids/Extensions/IdGeneratorExtensions.cs +++ b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Ids/Extensions/IdGeneratorExtensions.cs @@ -1,14 +1,15 @@ using Microsoft.CodeAnalysis; -using Aspid.Generator.Helpers; -using Aspid.MVVM.Generators.Descriptions; +using Aspid.Generators.Helper; +using Classes = Aspid.MVVM.Generators.Generators.Descriptions.Classes; +using SymbolExtensions = Aspid.MVVM.Generators.Helpers.SymbolExtensions; -namespace Aspid.MVVM.Generators.Ids.Extensions; +namespace Aspid.MVVM.Generators.Generators.Ids.Extensions; public static class IdGeneratorExtensions { public static string GetId(this ISymbol member, string prefixName = "") { - if (!member.HasAnyAttribute(out var attribute, Classes.IdAttribute)) + if (!member.TryGetAnyAttributeInSelf(out var attribute, Classes.IdAttribute)) return member.GetName(prefixName); var value = attribute!.ConstructorArguments[0].Value as string; diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Ids/IdGenerator.Find.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Ids/IdGenerator.Find.cs index 7fd4fb5..55c6621 100644 --- a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Ids/IdGenerator.Find.cs +++ b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Ids/IdGenerator.Find.cs @@ -1,26 +1,26 @@ using System.Threading; using Microsoft.CodeAnalysis; -using Aspid.Generator.Helpers; +using Aspid.Generators.Helper; using System.Collections.Generic; using Microsoft.CodeAnalysis.CSharp; -using Aspid.MVVM.Generators.Descriptions; using Microsoft.CodeAnalysis.CSharp.Syntax; -using Aspid.MVVM.Generators.Views.Factories; -using Aspid.MVVM.Generators.ViewModels.Factories; +using Aspid.MVVM.Generators.Generators.Views.Factories; +using Aspid.MVVM.Generators.Generators.ViewModels.Factories; +using Classes = Aspid.MVVM.Generators.Generators.Descriptions.Classes; -namespace Aspid.MVVM.Generators.Ids; +namespace Aspid.MVVM.Generators.Generators.Ids; public partial class IdGenerator { - private static FoundForGenerator> GetIdsForSourceGeneration( + private static HashSet? GetIdsForSourceGeneration( GeneratorSyntaxContext context, CancellationToken cancellationToken) { var syntax = (TypeDeclarationSyntax)context.Node; - if (context.SemanticModel.GetDeclaredSymbol(syntax) is not { } symbol) return default; + if (context.SemanticModel.GetDeclaredSymbol(syntax) is not { } symbol) return null; var ids = new HashSet(); - if (symbol.HasAnyAttribute(out var attribute, Classes.ViewAttribute, Classes.ViewModelAttribute)) + if (symbol.TryGetAnyAttributeInSelf(out var attribute, Classes.ViewAttribute, Classes.ViewModelAttribute)) { var attributeName = attribute!.AttributeClass!.ToDisplayString(); @@ -33,15 +33,13 @@ private static FoundForGenerator> GetIdsForSourceGeneration( } else { - var members = BindableMembersFactory.Create(symbol); + var members = BindableMembersFactory.Create(symbol, syntax, out _); foreach (var member in members) ids.Add(member.Id.SourceValue); } } - return ids.Count is 0 - ? default - : new FoundForGenerator>(ids); + return ids.Count is 0 ? null : ids; } } \ No newline at end of file diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Ids/IdGenerator.Generate.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Ids/IdGenerator.Generate.cs index 7daf40f..ed41e0d 100644 --- a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Ids/IdGenerator.Generate.cs +++ b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Ids/IdGenerator.Generate.cs @@ -1,10 +1,10 @@ using Microsoft.CodeAnalysis; -using Aspid.Generator.Helpers; +using Aspid.Generators.Helper; using System.Collections.Generic; using System.Collections.Immutable; -using static Aspid.MVVM.Generators.Descriptions.General; +using static Aspid.MVVM.Generators.Generators.Descriptions.Constants; -namespace Aspid.MVVM.Generators.Ids; +namespace Aspid.MVVM.Generators.Generators.Ids; public partial class IdGenerator { diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Ids/IdGenerator.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Ids/IdGenerator.cs index 4a768c4..2f71648 100644 --- a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Ids/IdGenerator.cs +++ b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Ids/IdGenerator.cs @@ -3,16 +3,17 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; -namespace Aspid.MVVM.Generators.Ids; +namespace Aspid.MVVM.Generators.Generators.Ids; [Generator(LanguageNames.CSharp)] public partial class IdGenerator : IIncrementalGenerator { public void Initialize(IncrementalGeneratorInitializationContext context) { + // ReSharper disable once NullableWarningSuppressionIsUsed var provider = context.SyntaxProvider.CreateSyntaxProvider(SyntacticPredicate, GetIdsForSourceGeneration) - .Where(foundFor => foundFor.IsNeed) - .Select((foundFor, _) => foundFor.Container); + .Where(foundFor => foundFor is not null) + .Select((foundFor, _) => foundFor!); context.RegisterSourceOutput(provider.Collect(), GenerateCode); } @@ -21,7 +22,7 @@ private static bool SyntacticPredicate(SyntaxNode node, CancellationToken cancel { var candidate = node switch { - ClassDeclarationSyntax or StructDeclarationSyntax => node as TypeDeclarationSyntax, + ClassDeclarationSyntax => node as TypeDeclarationSyntax, _ => null }; diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Body/BindableMembersBody.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Body/BindableInterfaceMembersBody.cs similarity index 65% rename from Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Body/BindableMembersBody.cs rename to Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Body/BindableInterfaceMembersBody.cs index 3e7af2e..32b4766 100644 --- a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Body/BindableMembersBody.cs +++ b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Body/BindableInterfaceMembersBody.cs @@ -1,26 +1,26 @@ using Microsoft.CodeAnalysis; -using Aspid.Generator.Helpers; -using Aspid.MVVM.Generators.ViewModels.Data; -using static Aspid.MVVM.Generators.Descriptions.Classes; -using static Aspid.MVVM.Generators.Descriptions.General; -using BindMode = Aspid.MVVM.Generators.ViewModels.Data.BindMode; +using Aspid.Generators.Helper; +using Aspid.MVVM.Generators.Generators.ViewModels.Data; +using static Aspid.MVVM.Generators.Generators.Descriptions.Classes; +using static Aspid.MVVM.Generators.Generators.Descriptions.Constants; +using BindMode = Aspid.MVVM.Generators.Generators.ViewModels.Data.BindMode; -namespace Aspid.MVVM.Generators.ViewModels.Body; +namespace Aspid.MVVM.Generators.Generators.ViewModels.Body; -public static class BindableMembersBody +public static class BindableInterfaceMembersBody { public static void Generate( string @namespace, in ViewModelData data, - in DeclarationText declaration, + DeclarationText declaration, in SourceProductionContext context) { var code = new CodeWriter(); - code.AppendClassBegin(@namespace, declaration) + code.BeginClass(@namespace, declaration) .AppendProperties(data) - .AppendClassEnd(@namespace); + .EndClass(@namespace); - context.AddSource(declaration.GetFileName(@namespace, "BindableMembers"), code.GetSourceText()); + context.AddSource(declaration.GetFileName(@namespace, "BindableInterfaceMembers"), code.GetSourceText()); } private static CodeWriter AppendProperties(this CodeWriter code, in ViewModelData data) @@ -39,7 +39,7 @@ private static CodeWriter AppendProperties(this CodeWriter code, in ViewModelDat if (!propertyType.Contains(IReadOnlyValueBindableMember)) { - if (!member.BindableMemberPropertyType.Contains(IReadOnlyBindableMember)) + if (!member.Bindable.PropertyType.Contains(IReadOnlyBindableMember)) continue; } } @@ -47,7 +47,7 @@ private static CodeWriter AppendProperties(this CodeWriter code, in ViewModelDat var interfaceType = customInterface.Interface.ToDisplayStringGlobal(); code.AppendLine(GeneratedCodeViewModelAttribute) - .AppendLine($"{propertyType} {interfaceType}.{property.Name} => {member.GeneratedName}Bindable;") + .AppendLine($"{propertyType} {interfaceType}.{property.Name} => {member.Bindable.PropertyName};") .AppendLine(); } diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Body/BindableMembers.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Body/BindableMembers.cs new file mode 100644 index 0000000..4f59f5c --- /dev/null +++ b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Body/BindableMembers.cs @@ -0,0 +1,157 @@ +using System.Linq; +using Microsoft.CodeAnalysis; +using Aspid.Generators.Helper; +using System.Collections.Generic; +using Aspid.MVVM.Generators.Helpers; +using Aspid.MVVM.Generators.Generators.ViewModels.Data; +using Aspid.MVVM.Generators.Generators.ViewModels.Data.Infos; +using static Aspid.MVVM.Generators.Generators.Descriptions.Classes; +using static Aspid.MVVM.Generators.Generators.Descriptions.Constants; +using SymbolExtensions = Aspid.MVVM.Generators.Helpers.SymbolExtensions; +using BindMode = Aspid.MVVM.Generators.Generators.ViewModels.Data.BindMode; + +namespace Aspid.MVVM.Generators.Generators.ViewModels.Body; + +public static class BindableMembers +{ + public static void Generate( + string @namespace, + in ViewModelData data, + DeclarationText declaration, + in SourceProductionContext context) + { + var code = new CodeWriter(); + + code.BeginClass(@namespace, declaration) + .AppendBody(data) + .EndClass(@namespace); + + context.AddSource(declaration.GetFileName(@namespace, "BindableMembers"), code.GetSourceText()); + } + + private static IEnumerable GetBindableBindAlso(ISymbol member, in ViewModelData data) + { + var set = new HashSet(); + + foreach (var attribute in member.GetAttributes()) + { + if (attribute.AttributeClass is not null && + attribute.AttributeClass.ToDisplayStringGlobal() == BindAlsoAttribute) + { + var value = attribute.ConstructorArguments[0].Value; + if (value is null) continue; + + set.Add(value.ToString()); + } + } + + return data.Members.Where(bindAlso => + set.Contains(bindAlso.Name) && bindAlso.Mode is not BindMode.OneTime and not BindMode.None); + } + + extension(CodeWriter code) + { + private CodeWriter AppendBody(in ViewModelData data) + { + if (!data.Members.IsEmpty) + { + code.AppendBindableMembers(data); + } + + return code + .AppendNotifyAll(data) + .AppendLine() + .AppendNotifyCanExecuteChangedAll(data); + } + + private CodeWriter AppendBindableMembers(in ViewModelData data) + { + foreach (var member in data.Members) + { + var capitalizeName = member.Name.CapitalizeFirstLetter(); + + code.AppendLine($"#region {capitalizeName}") + .AppendMultiline(member.Bindable.Declaration) + .AppendLine(); + + if (!string.IsNullOrWhiteSpace(member.Bindable.OnPropertyChangedName)) + { + code.AppendLine($"{GeneratedCodeViewModelAttribute}") + .AppendLine($"private void On{capitalizeName}PropertyChanged()") + .BeginBlock() + .AppendLineIf(!string.IsNullOrWhiteSpace(member.Bindable.Invoke), member.Bindable.Invoke); + + foreach (var bindAlso in GetBindableBindAlso(member.Member, data)) + { + code.AppendLineIf(!string.IsNullOrWhiteSpace(member.Bindable.OnPropertyChangedName), $"{bindAlso.Bindable.OnPropertyChangedName}();"); + } + + code.EndBlock(); + } + + code.AppendLine("#endregion") + .AppendLine(); + } + + return code; + } + + private CodeWriter AppendNotifyAll(in ViewModelData data) + { + var modifiers = "public"; + if (data.Inheritor is not Inheritor.None) modifiers += " override"; + else if (!data.Symbol.IsSealed) modifiers += " virtual"; + + code.AppendLine(GeneratedCodeViewModelAttribute) + .AppendLine($"{modifiers} void NotifyAll()") + .BeginBlock() + .AppendLineIf(data.Inheritor is Inheritor.Inheritor, "base.NotifyAll();"); + + foreach (var member in data.Members) + { + var invoke = member.Bindable.Invoke; + code.AppendLineIf(!string.IsNullOrWhiteSpace(invoke), invoke); + } + + return code.EndBlock(); + } + + private CodeWriter AppendNotifyCanExecuteChangedAll(in ViewModelData data) + { + var modifiers = "public"; + if (data.Inheritor is not Inheritor.None) modifiers += " override"; + else if (!data.Symbol.IsSealed) modifiers += " virtual"; + + code.AppendLine(GeneratedCodeViewModelAttribute) + .AppendLine($"{modifiers} void NotifyCanExecuteChangedAll()") + .BeginBlock() + .AppendLineIf(data.Inheritor is Inheritor.Inheritor, "base.NotifyCanExecuteChangedAll();"); + + foreach (var member in data.Members) + { + var name = member.Name; + + if (member is BindableCommandInfo command) + { + if (string.IsNullOrWhiteSpace(command.CanExecute)) continue; + name = $"{SymbolExtensions.GetFieldName(name, prefix: "__")}"; + } + else + { + var memberType = member.Member.GetSymbolType(); + if (memberType is null) continue; + + if (!memberType.ToDisplayStringGlobal().Contains(IRelayCommand)) + { + if (!memberType.AllInterfaces.Any(i => i.ToDisplayStringGlobal().Contains(IRelayCommand))) + continue; + } + } + + code.AppendLine($"{name}?.NotifyCanExecuteChanged();"); + } + + return code.EndBlock(); + } + } +} \ No newline at end of file diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Body/FindBindableMembersBody.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Body/FindBindableMembersBody.cs index 0e73bbd..0705266 100644 --- a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Body/FindBindableMembersBody.cs +++ b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Body/FindBindableMembersBody.cs @@ -1,29 +1,30 @@ using Microsoft.CodeAnalysis; -using Aspid.Generator.Helpers; +using Aspid.Generators.Helper; using System.Collections.Generic; using System.Collections.Immutable; -using Aspid.MVVM.Generators.ViewModels.Data; -using Aspid.MVVM.Generators.ViewModels.Data.Members; -using static Aspid.MVVM.Generators.Descriptions.Classes; -using static Aspid.MVVM.Generators.Descriptions.Defines; -using static Aspid.MVVM.Generators.Descriptions.General; +using Aspid.MVVM.Generators.Generators.ViewModels.Data; +using Aspid.MVVM.Generators.Generators.ViewModels.Data.Infos; +using static Aspid.Generators.Helper.Unity.UnityClasses; +using static Aspid.MVVM.Generators.Generators.Descriptions.Classes; +using static Aspid.MVVM.Generators.Generators.Descriptions.Defines; +using static Aspid.MVVM.Generators.Generators.Descriptions.Constants; -namespace Aspid.MVVM.Generators.ViewModels.Body; +namespace Aspid.MVVM.Generators.Generators.ViewModels.Body; public static class FindBindableMembersBody { public static void Generate( string @namespace, in ViewModelData data, - in DeclarationText declaration, + DeclarationText declaration, in SourceProductionContext context) { string[] baseTypes = [IViewModel]; var code = new CodeWriter(); - code.AppendClassBegin(@namespace, declaration, baseTypes) + code.BeginClass(@namespace, declaration, baseTypes) .AppendBody(data) - .AppendClassEnd(@namespace); + .EndClass(@namespace); context.AddSource(declaration.GetFileName(@namespace, "FindBindableMembers"), code.GetSourceText()); } @@ -43,8 +44,8 @@ private static CodeWriter AppendMarkers(this CodeWriter code, in ViewModelData d return code.AppendMultiline( $""" #if !{ASPID_MVVM_UNITY_PROFILER_DISABLED} + {EditorBrowsableAttributeNever} {GeneratedCodeViewModelAttribute} - [{EditorBrowsableAttribute}({EditorBrowsableState}.Never)] private static readonly {ProfilerMarker} __findBindableMemberMarker = new("{className}.FindBindableMember"); #endif """); @@ -52,7 +53,7 @@ private static CodeWriter AppendMarkers(this CodeWriter code, in ViewModelData d private static CodeWriter AppendFindBindableMember(this CodeWriter code, in ViewModelData data) { - var addedMembers = new HashSet(); + var addedMembers = new HashSet(); var modifiers = "public"; if (data.Inheritor is not Inheritor.None) modifiers = "public override"; @@ -117,7 +118,7 @@ private static CodeWriter AppendFindBindableMember(this CodeWriter code, in View return code; - void AppendIdBlock(ImmutableArray members) + void AppendIdBlock(ImmutableArray members) { foreach (var member in members) { @@ -127,7 +128,7 @@ void AppendIdBlock(ImmutableArray members) $$""" case {{member.Id}}: { - return new({{member.GeneratedName}}Bindable); + return new({{member.Bindable.PropertyName}}); } """); diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Body/GeneratedPropertiesBody.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Body/GeneratedPropertiesBody.cs new file mode 100644 index 0000000..a31b510 --- /dev/null +++ b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Body/GeneratedPropertiesBody.cs @@ -0,0 +1,41 @@ +using Microsoft.CodeAnalysis; +using Aspid.Generators.Helper; +using System.Collections.Immutable; +using Aspid.MVVM.Generators.Generators.ViewModels.Data; +using Aspid.MVVM.Generators.Generators.ViewModels.Data.Infos; + +namespace Aspid.MVVM.Generators.Generators.ViewModels.Body; + +public static class GeneratedPropertiesBody +{ + public static void Generate( + string namespaceName, + in ViewModelData data, + DeclarationText declaration, + in SourceProductionContext context) + { + var fields = data.Members.OfType().ToImmutableArray(); + if (fields.Length is 0) return; + + var code = new CodeWriter(); + + code.BeginClass(namespaceName, declaration) + .AppendBody(fields) + .EndClass(namespaceName); + + context.AddSource(declaration.GetFileName(namespaceName, "GeneratedProperties"), code.GetSourceText()); + } + + extension(CodeWriter code) + { + private CodeWriter AppendBody(in ImmutableArray fields) + { + foreach (var field in fields) + { + code.AppendMultiline(field.Declaration); + } + + return code; + } + } +} \ No newline at end of file diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Body/GeneratedPropertyMethodsBody.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Body/GeneratedPropertyMethodsBody.cs new file mode 100644 index 0000000..f41063e --- /dev/null +++ b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Body/GeneratedPropertyMethodsBody.cs @@ -0,0 +1,41 @@ +using Microsoft.CodeAnalysis; +using Aspid.Generators.Helper; +using System.Collections.Immutable; +using Aspid.MVVM.Generators.Generators.ViewModels.Data; +using Aspid.MVVM.Generators.Generators.ViewModels.Data.Infos; + +namespace Aspid.MVVM.Generators.Generators.ViewModels.Body; + +public static class GeneratedPropertyMethodsBody +{ + public static void Generate( + string namespaceName, + in ViewModelData data, + DeclarationText declaration, + in SourceProductionContext context) + { + var properties = data.Members.OfType().ToImmutableArray(); + if (properties.Length is 0) return; + + var code = new CodeWriter(); + + code.BeginClass(namespaceName, declaration) + .AppendBody(properties) + .EndClass(namespaceName); + + context.AddSource(declaration.GetFileName(namespaceName, "GeneratedPropertyMethods"), code.GetSourceText()); + } + + extension(CodeWriter code) + { + private CodeWriter AppendBody(in ImmutableArray properties) + { + foreach (var property in properties) + { + code.AppendMultiline(property.Declaration); + } + + return code; + } + } +} \ No newline at end of file diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Body/PropertiesBody.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Body/PropertiesBody.cs deleted file mode 100644 index 4187432..0000000 --- a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Body/PropertiesBody.cs +++ /dev/null @@ -1,109 +0,0 @@ -using Microsoft.CodeAnalysis; -using Aspid.Generator.Helpers; -using Aspid.MVVM.Generators.ViewModels.Data; -using Aspid.MVVM.Generators.ViewModels.Data.Members; -using static Aspid.MVVM.Generators.Descriptions.General; - -namespace Aspid.MVVM.Generators.ViewModels.Body; - -public static class PropertiesBody -{ - public static void Generate( - string @namespace, - in ViewModelData data, - in DeclarationText declaration, - in SourceProductionContext context) - { - var code = new CodeWriter(); - - code.AppendClassBegin(@namespace, declaration) - .AppendBody(data) - .AppendClassEnd(@namespace); - - context.AddSource(declaration.GetFileName(@namespace, "Properties"), code.GetSourceText()); - } - - private static CodeWriter AppendBody(this CodeWriter code, in ViewModelData data) - { - if (!data.Members.IsEmpty) - { - code.AppendFieldEvents(data) - .AppendProperties(data) - .AppendBindableMembers(data) - .AppendSetMethods(data); - } - - return code.AppendNotifyAll(data); - } - - private static CodeWriter AppendBindableMembers(this CodeWriter code, in ViewModelData data) - { - foreach (var member in data.Members) - { - code.AppendMultiline(member.ToBindableMemberPropertyDeclarationString()) - .AppendLine(); - } - - return code; - } - - private static CodeWriter AppendFieldEvents(this CodeWriter code, in ViewModelData data) - { - foreach (var member in data.Members) - { - var fieldDeclaration = member.ToBindableMemberFieldDeclarationString(); - if (fieldDeclaration is null) continue; - - code.AppendMultiline(fieldDeclaration) - .AppendLine(); - } - - return code; - } - - private static CodeWriter AppendProperties(this CodeWriter code, in ViewModelData data) - { - foreach (var field in data.Members.OfType()) - { - if (field.Member.IsConst) continue; - - code.AppendMultiline(field.ToDeclarationPropertyString()) - .AppendLine(); - } - - return code; - } - - private static CodeWriter AppendSetMethods(this CodeWriter code, in ViewModelData data) - { - foreach (var field in data.Members.OfType()) - { - if (field.Mode is BindMode.OneTime) continue; - - code.AppendMultiline(field.ToSetMethodString()) - .AppendLine(); - } - - return code; - } - - private static CodeWriter AppendNotifyAll(this CodeWriter code, in ViewModelData data) - { - var modifiers = "private"; - if (data.Inheritor is not Inheritor.None) modifiers = "protected override"; - else if (!data.Symbol.IsSealed) modifiers = "protected virtual"; - - code.AppendLine(GeneratedCodeViewModelAttribute) - .AppendLine($"{modifiers} void NotifyAll()") - .BeginBlock() - .AppendLineIf(data.Inheritor is Inheritor.Inheritor, "base.NotifyAll();"); - - foreach (var member in data.Members) - { - var invoke = member.ToInvokeBindableMemberString(); - code.AppendLineIf(!string.IsNullOrWhiteSpace(invoke), invoke); - } - - return code.EndBlock(); - } -} \ No newline at end of file diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Body/PropertyNotificationBody.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Body/PropertyNotificationBody.cs new file mode 100644 index 0000000..a80c4f7 --- /dev/null +++ b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Body/PropertyNotificationBody.cs @@ -0,0 +1,138 @@ +using System.Linq; +using Microsoft.CodeAnalysis; +using Aspid.Generators.Helper; +using Aspid.MVVM.Generators.Helpers; +using Aspid.MVVM.Generators.Generators.ViewModels.Data; +using static Aspid.Generators.Helper.Classes; +using static Aspid.MVVM.Generators.Generators.Descriptions.Constants; + +namespace Aspid.MVVM.Generators.Generators.ViewModels.Body; + +public static class PropertyNotificationBody +{ + public static void Generate( + string namespaceName, + in ViewModelData data, + DeclarationText declaration, + in SourceProductionContext context) + { + var code = new CodeWriter(); + + code.BeginClass(namespaceName, declaration) + .AppendBody(data) + .EndClass(namespaceName); + + context.AddSource(declaration.GetFileName(namespaceName, postfix: "PropertyNotification"), code.GetSourceText()); + } + + extension(CodeWriter code) + { + private CodeWriter AppendBody(in ViewModelData data) + { + return code + .AppendOnPropertyChanged(data) + .AppendLine() + .AppendSetField(data); + } + + private CodeWriter AppendOnPropertyChanged(in ViewModelData data) + { + var notificationData = data.PropertyNotificationData; + + code.AppendLine("#region OnPropertyChanged"); + + code.AppendLine(GeneratedCodeViewModelAttribute) + .AppendLine($"private void OnPropertyChanged([{CallerLineNumberAttribute}] int line = -1)") + .BeginBlock(); + + if (notificationData.HasOnPropertyChangedCalls) + { + var first = true; + + foreach (var kvp in notificationData.OnPropertyChangedCalls) + { + var propertyName = kvp.Key; + var lines = kvp.Value; + var linesPattern = string.Join(" or ", lines.Select(line => line.ToString())); + var keyword = first ? "if" : "else if"; + + first = false; + code.AppendLine($"{keyword} (line is {linesPattern}) On{propertyName.CapitalizeFirstLetter()}PropertyChanged();"); + } + + code.AppendLine($"else throw new {NotImplementedException}($\"OnPropertyChanged: No property found for line {{line}}\");"); + } + else + { + code.AppendLine($"throw new {NotImplementedException}($\"OnPropertyChanged: No property found for line {{line}}\");"); + } + + code.EndBlock() + .AppendLine(); + + code.AppendMultiline( + $""" + {GeneratedCodeViewModelAttribute} + private void OnPropertyChanged(string propertyName, [{CallerLineNumberAttribute}] int line = -1) => + OnPropertyChanged(line); + """) + .AppendLine("#endregion"); + + return code; + } + + private CodeWriter AppendSetField(in ViewModelData data) + { + var notificationData = data.PropertyNotificationData; + + code.AppendLine("#region SetField") + .AppendMultiline( + $$""" + {{GeneratedCodeViewModelAttribute}} + private bool SetField(ref T field, T newValue, [{{CallerLineNumberAttribute}}] int line = -1) => + throw new {{NotImplementedException}}("SetField: No property found for line {line}. Use typed overload."); + + {{GeneratedCodeViewModelAttribute}} + private bool SetField(ref T field, T newValue, string propertyName, [{{CallerLineNumberAttribute}}] int line = -1) => + throw new {{NotImplementedException}}($"SetField: No property {propertyName} found for line {line}. Use typed overload."); + """); + + if (notificationData.HasSetFieldCalls) + { + foreach (var typeGroup in notificationData.SetFieldCallsByType) + { + var type = typeGroup.Key; + var propertyCalls = typeGroup.Value; + + code.AppendLine(GeneratedCodeViewModelAttribute) + .AppendLine($"private bool SetField(ref {type} field, {type} newValue, [{CallerLineNumberAttribute}] int line = -1)") + .BeginBlock(); + + var first = true; + foreach (var kvp in propertyCalls) + { + var propertyName = kvp.Key; + var lines = kvp.Value; + var linesPattern = string.Join(" or ", lines.Select(l => l.ToString())); + var keyword = first ? "if" : "else if"; + first = false; + + code.AppendLine($"{keyword} (line is {linesPattern}) return Set{propertyName.CapitalizeFirstLetter()}Field(ref field, newValue);"); + } + + code.AppendLine($"else throw new {NotImplementedException}($\"SetField: No property found for line {{line}}\");") + .EndBlock() + .AppendLine() + .AppendMultiline( + $""" + {GeneratedCodeViewModelAttribute} + private bool SetField(ref {type} field, {type} newValue, string propertyName, [{CallerLineNumberAttribute}] int line = -1) => + SetField(ref field, newValue, line); + """); + } + } + + return code.AppendLine("#endregion"); + } + } +} \ No newline at end of file diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Body/RelayCommandBody.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Body/RelayCommandBody.cs index a5b134d..02400e8 100644 --- a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Body/RelayCommandBody.cs +++ b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Body/RelayCommandBody.cs @@ -1,35 +1,35 @@ using System.Linq; using Microsoft.CodeAnalysis; -using Aspid.Generator.Helpers; -using Aspid.MVVM.Generators.ViewModels.Data; -using Aspid.MVVM.Generators.ViewModels.Data.Members; +using Aspid.Generators.Helper; +using Aspid.MVVM.Generators.Generators.ViewModels.Data; +using Aspid.MVVM.Generators.Generators.ViewModels.Data.Infos; -namespace Aspid.MVVM.Generators.ViewModels.Body; +namespace Aspid.MVVM.Generators.Generators.ViewModels.Body; public static class RelayCommandBody { public static void Generate( string @namespace, in ViewModelData data, - in DeclarationText declaration, + DeclarationText declaration, in SourceProductionContext context) { - if (!data.Members.OfType().Any()) return; + if (!data.Members.OfType().Any()) return; var code = new CodeWriter(); - code.AppendClassBegin(@namespace, declaration) + code.BeginClass(@namespace, declaration) .AppendBody(data) - .AppendClassEnd(@namespace); + .EndClass(@namespace); context.AddSource(declaration.GetFileName(@namespace, "Commands"), code.GetSourceText()); } private static CodeWriter AppendBody(this CodeWriter code, in ViewModelData data) { - foreach (var command in data.Members.OfType()) + foreach (var command in data.Members.OfType()) { - code.AppendMultiline(command.ToDeclarationCommandString()) + code.AppendMultiline(command.CommandDeclaration) .AppendLine(); } diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Data/BindMode.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Data/BindMode.cs index 344db09..af1f4e0 100644 --- a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Data/BindMode.cs +++ b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Data/BindMode.cs @@ -1,4 +1,4 @@ -namespace Aspid.MVVM.Generators.ViewModels.Data; +namespace Aspid.MVVM.Generators.Generators.ViewModels.Data; public enum BindMode { diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Data/Infos/BindableBindAlsoInfo.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Data/Infos/BindableBindAlsoInfo.cs new file mode 100644 index 0000000..e1ee6d9 --- /dev/null +++ b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Data/Infos/BindableBindAlsoInfo.cs @@ -0,0 +1,40 @@ +using System; +using Microsoft.CodeAnalysis; +using Aspid.Generators.Helper; +using Aspid.MVVM.Generators.Generators.Ids.Data; +using static Aspid.MVVM.Generators.Generators.Descriptions.Classes; + +namespace Aspid.MVVM.Generators.Generators.ViewModels.Data.Infos; + +public sealed class BindableBindAlsoInfo(IPropertySymbol propertySymbol) : IBindableMemberInfo, IEquatable +{ + private readonly IPropertySymbol _propertySymbol = propertySymbol; + + public ISymbol Member { get; } = propertySymbol; + + public string Type { get; } = propertySymbol.Type.ToDisplayStringGlobal(); + + public string Name { get; } = propertySymbol.Name; + + public IdData Id { get; } = new(propertySymbol); + + public BindMode Mode => BindMode.OneWay; + + public bool HasBindAttribute { get; } = propertySymbol.HasAnyAttributeInSelf( + BindAttribute, + OneWayBindAttribute, + TwoWayBindAttribute, + OneTimeBindAttribute, + OneWayToSourceBindAttribute); + + public GeneratedBindableMembers Bindable { get; } = GeneratedBindableMembers.CreateForBindAlso(propertySymbol); + + public override bool Equals(object? obj) => + obj is BindableBindAlsoInfo other && Equals(other); + + public bool Equals(BindableBindAlsoInfo other) => + SymbolEqualityComparer.Default.Equals(_propertySymbol, other._propertySymbol); + + public override int GetHashCode() => + SymbolEqualityComparer.Default.GetHashCode(_propertySymbol); +} \ No newline at end of file diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Data/Infos/BindableCommandInfo.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Data/Infos/BindableCommandInfo.cs new file mode 100644 index 0000000..ad02fed --- /dev/null +++ b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Data/Infos/BindableCommandInfo.cs @@ -0,0 +1,92 @@ +using System.Linq; +using System.Text; +using Microsoft.CodeAnalysis; +using Aspid.Generators.Helper; +using Aspid.MVVM.Generators.Helpers; +using Aspid.MVVM.Generators.Generators.Ids.Data; +using static Aspid.MVVM.Generators.Generators.Descriptions.Classes; +using static Aspid.MVVM.Generators.Generators.Descriptions.Constants; + +namespace Aspid.MVVM.Generators.Generators.ViewModels.Data.Infos; + +public sealed class BindableCommandInfo : IBindableMemberInfo +{ + public ISymbol Member { get; } + + public string Type { get; } + + public string Name { get; } + + public string CanExecute { get; } + + public IdData Id { get; } + + public BindMode Mode => BindMode.OneTime; + + public GeneratedBindableMembers Bindable { get; } + + public string CommandDeclaration { get; } + + public BindableCommandInfo(IMethodSymbol methodSymbol, string? canExecute, bool isLambda, bool isMethod) + { + Member = methodSymbol; + Type = GetTypeName(methodSymbol); + Id = new IdData(methodSymbol, "Command"); + Name = $"{methodSymbol.GetPropertyName()}Command"; + Bindable = GeneratedBindableMembers.CreateForRelayCommand(Type, Name); + + var fieldName = $"{methodSymbol.GetFieldName("__")}Command"; + CanExecute = GetCanExecuteAction(methodSymbol, isLambda, isMethod, canExecute); + + CommandDeclaration = + $""" + #region {Name} + {EditorBrowsableAttributeNever} + {GeneratedCodeViewModelAttribute} + private {Type} {fieldName}; + + {GeneratedCodeViewModelAttribute} + private {Type} {Name} => {fieldName} ??= new {Type}({methodSymbol.Name}{CanExecute}); + #endregion + """; + } + + private static string GetTypeName(IMethodSymbol command) + { + var type = new StringBuilder(RelayCommand); + var parameters = command.Parameters; + if (parameters.Length <= 0) return type.ToString(); + + type.Append("<"); + + foreach (var parameter in parameters) + type.Append($"{parameter.Type.ToDisplayStringGlobal()},"); + + type.Length--; + type.Append(">"); + + return type.ToString(); + } + + private static string GetCanExecuteAction(IMethodSymbol command, bool isLambda, bool isMethod, string? canExecute) + { + if (canExecute is null || string.IsNullOrWhiteSpace(canExecute)) return string.Empty; + + var canExecuteName = new StringBuilder(canExecute); + + if (!isLambda) + { + canExecuteName.Insert(0, ", "); + } + else + { + var parameters = command.Parameters; + var missingParameters = string.Join(", ", Enumerable.Repeat("_", parameters.Length)); + + canExecuteName.Insert(0, $", ({missingParameters}) => "); + if (isLambda && isMethod) canExecuteName.Append("()"); + } + + return canExecuteName.ToString(); + } +} \ No newline at end of file diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Data/Infos/BindableFieldInfo.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Data/Infos/BindableFieldInfo.cs new file mode 100644 index 0000000..35c4add --- /dev/null +++ b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Data/Infos/BindableFieldInfo.cs @@ -0,0 +1,202 @@ +using System.Text; +using Microsoft.CodeAnalysis; +using Aspid.Generators.Helper; +using Microsoft.CodeAnalysis.CSharp; +using Aspid.MVVM.Generators.Helpers; +using Aspid.MVVM.Generators.Generators.Ids.Data; +using static Aspid.Generators.Helper.Classes; +using static Aspid.MVVM.Generators.Generators.Descriptions.Classes; +using static Aspid.MVVM.Generators.Generators.Descriptions.Constants; + +namespace Aspid.MVVM.Generators.Generators.ViewModels.Data.Infos; + +public sealed class BindableFieldInfo : IBindableMemberInfo +{ + public ISymbol Member { get; } + + public string Type { get; } + + public string Name { get; } + + public IdData Id { get; } + + public BindMode Mode { get; } + + public GeneratedBindableMembers Bindable { get; } + + public string Declaration { get; } + + public BindableFieldInfo(IFieldSymbol fieldSymbol, BindMode mode) + { + Mode = mode; + Member = fieldSymbol; + Id = new IdData(fieldSymbol); + Type = fieldSymbol.Type.ToDisplayStringGlobal(); + Name = fieldSymbol.IsConst ? fieldSymbol.Name : fieldSymbol.GetPropertyName(); + Bindable = GeneratedBindableMembers.CreateForField(fieldSymbol); + var accessors = Accessors.GetAccessors(fieldSymbol); + + var setMethodName = $"Set{Name}"; + StringBuilder declaration = new(); + + if (!fieldSymbol.IsConst) + { + declaration.AppendLine($"#region {Name}"); + + var keywordThis = !fieldSymbol.IsStatic ? "this." : string.Empty; + var fieldInvoke = $"{keywordThis}{fieldSymbol.Name}"; + + declaration.AppendLine(Mode is BindMode.OneTime + ? $""" + {GeneratedCodeViewModelAttribute} + {accessors.General}{Type} {Name} => {fieldInvoke}; + """ + : $$""" + {{GeneratedCodeViewModelAttribute}} + {{accessors.General}}{{Type}} {{Name}} + { + {{accessors.Get}}get => {{fieldInvoke}}; + {{accessors.Set}}set => {{setMethodName}}(value); + } + """); + + if (Mode is not BindMode.OneTime && Mode is not BindMode.None) + { + var onChangedMethod = $"On{Name}Changed"; + var onChangingMethod = $"On{Name}Changing"; + var methodModifier = Accessors.ConvertAccessorToString(accessors.SetKind); + + var invoke = !string.IsNullOrWhiteSpace(Bindable.OnPropertyChangedName) + ? $"{Bindable.OnPropertyChangedName}();" + : string.Empty; + + declaration.AppendLine(); + declaration.AppendLine( + $$""" + {{GeneratedCodeViewModelAttribute}} + {{methodModifier}}void {{setMethodName}}({{Type}} value) + { + if ({{EqualityComparer_1}}<{{Type}}>.Default.Equals({{fieldInvoke}}, value)) return; + + {{Type}} oldValue = {{fieldInvoke}}; + + {{onChangingMethod}}(value); + {{onChangingMethod}}(oldValue, value); + { + {{fieldInvoke}} = value; + {{invoke}} + } + {{onChangedMethod}}(value); + {{onChangedMethod}}(oldValue, value); + } + + {{EditorBrowsableAttributeNever}} + {{GeneratedCodeViewModelAttribute}} + partial void {{onChangingMethod}}({{Type}} newValue); + + {{EditorBrowsableAttributeNever}} + {{GeneratedCodeViewModelAttribute}} + partial void {{onChangingMethod}}({{Type}} oldValue, {{Type}} newValue); + + {{EditorBrowsableAttributeNever}} + {{GeneratedCodeViewModelAttribute}} + partial void {{onChangedMethod}}({{Type}} newValue); + + {{EditorBrowsableAttributeNever}} + {{GeneratedCodeViewModelAttribute}} + partial void {{onChangedMethod}}({{Type}} oldValue, {{Type}} newValue); + """); + } + + declaration.AppendLine("#endregion"); + } + + Declaration = declaration.ToString(); + } + + private readonly ref struct Accessors + { + public readonly SyntaxKind GetKind; + public readonly SyntaxKind SetKind; + + public readonly string Get; + public readonly string Set; + public readonly string General; + + private Accessors(SyntaxKind get, SyntaxKind set) + { + GetKind = get; + SetKind = set; + + switch (Compare(get, set)) + { + case 0: + Get = string.Empty; + Set = string.Empty; + General = ConvertAccessorToString(get); + break; + + case > 0: + Get = string.Empty; + Set = ConvertAccessorToString(set); + General = ConvertAccessorToString(get); + break; + + case < 0: + Get = ConvertAccessorToString(get); + Set = string.Empty; + General = ConvertAccessorToString(set); + break; + } + } + + public static Accessors GetAccessors(IFieldSymbol field) + { + var get = SyntaxKind.PrivateKeyword; + var set = SyntaxKind.PrivateKeyword; + + if (field.TryGetAnyAttributeInSelf(out var accessAttribute, AccessAttribute)) + { + if (accessAttribute.ConstructorArguments.Length == 1) + { + var value = (SyntaxKind)(int)(accessAttribute.ConstructorArguments[0].Value ?? SyntaxKind.PrivateKeyword); + get = value; + set = value; + } + + foreach (var argument in accessAttribute!.NamedArguments) + { + switch (argument.Key) + { + case "Get": get = (SyntaxKind)(int)(argument.Value.Value ?? SyntaxKind.PrivateKeyword); break; + case "Set": set = (SyntaxKind)(int)(argument.Value.Value ?? SyntaxKind.PrivateKeyword); break; + } + } + } + + return new Accessors(get, set); + } + + public static string ConvertAccessorToString(SyntaxKind syntaxKind) => syntaxKind switch + { + SyntaxKind.PublicKeyword => "public ", + SyntaxKind.PrivateKeyword => "private ", + SyntaxKind.ProtectedKeyword => "protected ", + _ => string.Empty + }; + + private static int Compare(in SyntaxKind get, in SyntaxKind set) + { + if (get == set) return 0; + + if (set is SyntaxKind.PrivateKeyword) return 1; + if (get is SyntaxKind.PrivateKeyword) return -1; + + if (set is SyntaxKind.ProtectedKeyword) return 1; + if (get is SyntaxKind.ProtectedKeyword) return -1; + + // This is not impossible. + return 0; + } + } +} \ No newline at end of file diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Data/Infos/BindablePropertyInfo.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Data/Infos/BindablePropertyInfo.cs new file mode 100644 index 0000000..c26e0ef --- /dev/null +++ b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Data/Infos/BindablePropertyInfo.cs @@ -0,0 +1,106 @@ +using System.Text; +using Microsoft.CodeAnalysis; +using Aspid.Generators.Helper; +using Aspid.MVVM.Generators.Helpers; +using Aspid.MVVM.Generators.Generators.Ids.Data; +using static Aspid.Generators.Helper.Classes; +using static Aspid.MVVM.Generators.Generators.Descriptions.Constants; + +namespace Aspid.MVVM.Generators.Generators.ViewModels.Data.Infos; + +public sealed class BindablePropertyInfo : IBindableMemberInfo +{ + public ISymbol Member { get; } + + public string Type { get; } + + public string Name { get; } + + public IdData Id { get; } + + public BindMode Mode { get; } + + public GeneratedBindableMembers Bindable { get; } + + public string Declaration { get; } + + public BindablePropertyInfo(IPropertySymbol propertySymbol, BindMode mode, bool isSetMethodUsed) + { + Mode = mode; + Member = propertySymbol; + Id = new IdData(propertySymbol); + Name = propertySymbol.Name.CapitalizeFirstLetter(); + Type = propertySymbol.Type.ToDisplayStringGlobal(); + Bindable = GeneratedBindableMembers.CreateForProperty(propertySymbol); + + var setMethodName = $"Set{Name}Field"; + StringBuilder declaration = new(); + + if (Mode is not BindMode.OneTime && Mode is not BindMode.None) + { + var onPropertyChangedMethod = $"On{Name}PropertyChanged"; + + if (propertySymbol is { IsReadOnly: false, IsWriteOnly: false }) + { + declaration.AppendLine($"#region {Name}"); + + if (isSetMethodUsed) + { + var onChangedMethod = $"On{Name}Changed"; + var onChangingMethod = $"On{Name}Changing"; + + declaration.AppendLine( + $$""" + {{GeneratedCodeViewModelAttribute}} + private bool {{setMethodName}}(ref {{Type}} field, {{Type}} value) + { + if ({{EqualityComparer_1}}<{{Type}}>.Default.Equals(field, value)) return false; + + {{Type}} oldValue = field; + + {{onChangingMethod}}(value); + {{onChangingMethod}}(oldValue, value); + { + field = value; + {{onPropertyChangedMethod}}(); + } + {{onChangedMethod}}(value); + {{onChangedMethod}}(oldValue, value); + + return true; + } + + {{EditorBrowsableAttributeNever}} + {{GeneratedCodeViewModelAttribute}} + partial void {{onChangingMethod}}({{Type}} newValue); + + {{EditorBrowsableAttributeNever}} + {{GeneratedCodeViewModelAttribute}} + partial void {{onChangingMethod}}({{Type}} oldValue, {{Type}} newValue); + + {{EditorBrowsableAttributeNever}} + {{GeneratedCodeViewModelAttribute}} + partial void {{onChangedMethod}}({{Type}} newValue); + + {{EditorBrowsableAttributeNever}} + {{GeneratedCodeViewModelAttribute}} + partial void {{onChangedMethod}}({{Type}} oldValue, {{Type}} newValue); + """); + } + else + { + declaration.AppendLine( + $""" + {GeneratedCodeViewModelAttribute} + private bool {setMethodName}(ref {Type} field, {Type} value) => + throw new {NotImplementedException}("Generator Error: SetMethod is not used."); + """); + } + + declaration.AppendLine("#endregion"); + } + } + + Declaration = declaration.ToString(); + } +} \ No newline at end of file diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Data/Infos/GeneratedBindableMembers.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Data/Infos/GeneratedBindableMembers.cs new file mode 100644 index 0000000..d3c8624 --- /dev/null +++ b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Data/Infos/GeneratedBindableMembers.cs @@ -0,0 +1,239 @@ +using Microsoft.CodeAnalysis; +using Aspid.Generators.Helper; +using Aspid.MVVM.Generators.Helpers; +using Aspid.MVVM.Generators.Generators.ViewModels.Extensions; +using static Aspid.MVVM.Generators.Generators.Descriptions.Classes; +using static Aspid.MVVM.Generators.Generators.Descriptions.Constants; +using SymbolExtensions = Aspid.MVVM.Generators.Helpers.SymbolExtensions; + +namespace Aspid.MVVM.Generators.Generators.ViewModels.Data.Infos; + +public readonly struct GeneratedBindableMembers +{ + // TODO Aspid.MVVM.Generators – Write summary + public readonly string? Invoke; + public readonly string Declaration; + public readonly string PropertyName; + public readonly string PropertyType; + public readonly string? OnPropertyChangedName; + + private GeneratedBindableMembers( + string? invoke, + string declaration, + string propertyName, + string propertyType, + string? onPropertyChangedName) + { + Invoke = invoke; + Declaration = declaration; + PropertyName = propertyName; + PropertyType = propertyType; + OnPropertyChangedName = onPropertyChangedName; + } + + public static GeneratedBindableMembers CreateForRelayCommand( + string type, + string memberName) + { + const BindMode mode = BindMode.OneTime; + + var fieldType = $"{OneTimeBindableMember}<{type}>"; + var propertyType = $"{IReadOnlyValueBindableMember}<{type}>"; + var propertyName = $"{memberName}Bindable"; + + var declaration = RecognizeDeclaration(mode, memberName, null, fieldType, propertyName, propertyType); + + return new GeneratedBindableMembers(null, declaration, propertyName, propertyType, null); + } + + public static GeneratedBindableMembers CreateForField(IFieldSymbol fieldSymbol) + { + var mode = fieldSymbol.GetBindMode(); + var memberName = fieldSymbol.Name; + + // __fieldName; + var fieldName = $"__{fieldSymbol.RemoveFieldPrefix()}Bindable"; + var fieldType = RecognizeFieldType(fieldSymbol.Type, mode); + + // _fieldName -> FieldNameBindable + var propertyName = $"{fieldSymbol.GetPropertyName()}Bindable"; + var propertyType = RecognizePropertyType(fieldSymbol.Type, mode); + + var invoke = RecognizeInvoke(mode, memberName, fieldName); + var declaration = RecognizeDeclaration(mode, memberName, fieldName, fieldType, propertyName, propertyType, $"Set{SymbolExtensions.GetPropertyName(memberName)}"); + + var onPropertyChangedName = mode is not BindMode.OneTime and not BindMode.OneWayToSource and not BindMode.None + ? $"On{fieldSymbol.GetPropertyName()}PropertyChanged" + : null; + + return new GeneratedBindableMembers(invoke, declaration, propertyName, propertyType, onPropertyChangedName); + } + + public static GeneratedBindableMembers CreateForProperty(IPropertySymbol propertySymbol) + { + var mode = propertySymbol.GetBindMode(); + var memberName = propertySymbol.Name; + + // PropertyName -> __propertyNameBindable + var fieldName = $"{propertySymbol.GetFieldName(prefix: "__")}Bindable"; + var fieldType = RecognizeFieldType(propertySymbol.Type, mode); + + // PropertyName -> PropertyNameBindable + var capitalizedName = propertySymbol.Name.CapitalizeFirstLetter(); + var propertyName = $"{capitalizedName}Bindable"; + var propertyType = RecognizePropertyType(propertySymbol.Type, mode); + + var invoke = RecognizeInvoke(mode, memberName, fieldName); + var declaration = RecognizeDeclaration(mode, memberName, fieldName, fieldType, propertyName, propertyType); + + var onPropertyChangedName = mode is not BindMode.OneTime and not BindMode.OneWayToSource and not BindMode.None + ? $"On{capitalizedName}PropertyChanged" + : null; + + return new GeneratedBindableMembers(invoke, declaration, propertyName, propertyType, onPropertyChangedName); + } + + public static GeneratedBindableMembers CreateForBindAlso(IPropertySymbol propertySymbol) + { + const BindMode mode = BindMode.OneWay; + var memberName = propertySymbol.Name; + + // PropertyName -> __propertyNameBindable + var fieldName = $"{propertySymbol.GetFieldName(prefix: "__")}Bindable"; + var fieldType = RecognizeFieldType(propertySymbol.Type, mode); + + // PropertyName -> PropertyNameBindable + var capitalizedName = propertySymbol.Name.CapitalizeFirstLetter(); + var propertyName = $"{capitalizedName}Bindable"; + var propertyType = RecognizePropertyType(propertySymbol.Type, mode); + + var invoke = RecognizeInvoke(mode, memberName, fieldName); + var declaration = RecognizeDeclaration(mode, memberName, fieldName, fieldType, propertyName, propertyType); + + return new GeneratedBindableMembers(invoke, declaration, propertyName, propertyType, $"On{capitalizedName}PropertyChanged"); + } + + private static string RecognizeFieldType(ITypeSymbol type, BindMode mode) + { + var typeKind = type.TypeKind; + + if (typeKind is TypeKind.TypeParameter) + { + if (type is ITypeParameterSymbol typeParameter) + { + typeKind = GetEffectiveTypeKind(typeParameter); + } + } + + string result = mode switch + { + BindMode.OneWay => typeKind switch + { + TypeKind.Enum => OneWayEnumBindableMember, + TypeKind.Struct => OneWayStructBindableMember, + _ => OneWayBindableMember + }, + BindMode.TwoWay => typeKind switch + { + TypeKind.Enum => TwoWayEnumBindableMember, + TypeKind.Struct => TwoWayStructBindableMember, + _ => TwoWayBindableMember + }, + BindMode.OneTime => typeKind switch + { + TypeKind.Enum => OneTimeEnumBindableMember, + TypeKind.Struct => OneTimeStructBindableMember, + _ => OneTimeBindableMember + }, + BindMode.OneWayToSource => typeKind switch + { + TypeKind.Enum => OneWayToSourceEnumBindableMember, + TypeKind.Struct => OneWayToSourceStructBindableMember, + _ => OneWayToSourceBindableMember + }, + _ => string.Empty + }; + + return string.IsNullOrWhiteSpace(result) + ? string.Empty + : $"{result}<{type.ToDisplayStringGlobal()}>"; + + TypeKind GetEffectiveTypeKind(ITypeParameterSymbol typeParameter) + { + foreach (var constraint in typeParameter.ConstraintTypes) + { + if (constraint.TypeKind == TypeKind.Enum + || constraint.SpecialType == SpecialType.System_Enum) + { + return TypeKind.Enum; + } + } + + return typeParameter.HasValueTypeConstraint ? TypeKind.Struct : TypeKind.Class; + } + } + + private static string RecognizePropertyType(ITypeSymbol type, BindMode mode) + { + string result = mode switch + { + BindMode.TwoWay => IBindableMember, + BindMode.OneWay => IReadOnlyBindableMember, + BindMode.OneTime or BindMode.OneWayToSource => IReadOnlyValueBindableMember, + _ => string.Empty + }; + + return string.IsNullOrWhiteSpace(result) + ? string.Empty + : $"{result}<{type.ToDisplayStringGlobal()}>"; + } + + private static string? RecognizeInvoke(BindMode mode, string memberName, string fieldName) + { + return mode is not (BindMode.OneWayToSource or BindMode.OneTime) + ? $"{fieldName}?.Invoke({memberName});" + : null; + } + + private static string RecognizeDeclaration( + BindMode mode, + string memberName, + string? fieldName, + string fieldType, + string propertyName, + string propertyType, + string? setMethod = null) + { + if (mode is BindMode.OneTime) + { + return + $""" + {GeneratedCodeViewModelAttribute} + public {propertyType} {propertyName} => + {fieldType}.Get({memberName}); + """; + } + + setMethod ??= $"value => {memberName} = value"; + + // For properties, we use a property setter directly instead of a method reference + var instantiate = mode switch + { + BindMode.OneWay => $"{fieldName} ??= new({memberName})", + BindMode.TwoWay => $"{fieldName} ??= new({memberName}, {setMethod})", + BindMode.OneWayToSource => $"{fieldName} ??= new({setMethod})", + _ => string.Empty + }; + + return + $""" + {EditorBrowsableAttributeNever} + {GeneratedCodeViewModelAttribute} + private {fieldType} {fieldName}; + + {GeneratedCodeViewModelAttribute} + public {propertyType} {propertyName} => + {instantiate}; + """; + } +} \ No newline at end of file diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Data/Infos/IBindableMemberInfo.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Data/Infos/IBindableMemberInfo.cs new file mode 100644 index 0000000..20ec3ef --- /dev/null +++ b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Data/Infos/IBindableMemberInfo.cs @@ -0,0 +1,19 @@ +using Microsoft.CodeAnalysis; +using Aspid.MVVM.Generators.Generators.Ids.Data; + +namespace Aspid.MVVM.Generators.Generators.ViewModels.Data.Infos; + +public interface IBindableMemberInfo +{ + public ISymbol Member { get; } + + public string Type { get; } + + public string Name { get; } + + public IdData Id { get; } + + public BindMode Mode { get; } + + public GeneratedBindableMembers Bindable { get; } +} \ No newline at end of file diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Data/Inheritor.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Data/Inheritor.cs index 9ea8296..122bb98 100644 --- a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Data/Inheritor.cs +++ b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Data/Inheritor.cs @@ -1,4 +1,4 @@ -namespace Aspid.MVVM.Generators.ViewModels.Data; +namespace Aspid.MVVM.Generators.Generators.ViewModels.Data; public enum Inheritor { diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Data/Members/BindableBindAlso.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Data/Members/BindableBindAlso.cs deleted file mode 100644 index 966ce75..0000000 --- a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Data/Members/BindableBindAlso.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using Microsoft.CodeAnalysis; -using Aspid.Generator.Helpers; - -namespace Aspid.MVVM.Generators.ViewModels.Data.Members; - -public sealed class BindableBindAlso : BindableMember, IEquatable -{ - public BindableBindAlso(ISymbol member) - : base(member, BindMode.OneWay, member.GetSymbolType()?.ToDisplayStringGlobal() ?? string.Empty, member.Name, member.Name, string.Empty, member.GetSymbolType()?.TypeKind ?? TypeKind.Class) { } - - public override bool Equals(object? obj) => - obj is BindableBindAlso other && Equals(other); - - public bool Equals(BindableBindAlso other) => - SymbolEqualityComparer.Default.Equals(Member, other.Member); - - public override int GetHashCode() => - SymbolEqualityComparer.Default.GetHashCode(Member); -} \ No newline at end of file diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Data/Members/BindableCommand.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Data/Members/BindableCommand.cs deleted file mode 100644 index 89e99fc..0000000 --- a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Data/Members/BindableCommand.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System.Linq; -using System.Text; -using Microsoft.CodeAnalysis; -using Aspid.Generator.Helpers; -using static Aspid.MVVM.Generators.Descriptions.Classes; -using static Aspid.MVVM.Generators.Descriptions.General; - -namespace Aspid.MVVM.Generators.ViewModels.Data.Members; - -public sealed class BindableCommand : BindableMember -{ - public readonly string CanExecute; - - public BindableCommand(IMethodSymbol command, string? canExecute, bool isLambda, bool isMethod) - : base(command, BindMode.OneTime, GetTypeName(command), $"{command.GetFieldName("__")}Command", $"{command.GetPropertyName()}Command", "Command") - { - CanExecute = GetCanExecuteAction(command, isLambda, isMethod, canExecute); - } - - public string ToDeclarationCommandString() - { - return - $""" - {GeneratedCodeViewModelAttribute} - [{EditorBrowsableAttribute}({EditorBrowsableState}.Never)] - private {Type} {SourceName}; - - {GeneratedCodeViewModelAttribute} - private {Type} {GeneratedName} => {SourceName} ??= new {Type}({Member.Name}{CanExecute}); - """; - } - - private static string GetTypeName(IMethodSymbol command) - { - var type = new StringBuilder(RelayCommand); - var parameters = command.Parameters; - if (parameters.Length <= 0) return type.ToString(); - - type.Append("<"); - - foreach (var parameter in parameters) - type.Append($"{parameter.Type.ToDisplayStringGlobal()},"); - - type.Length--; - type.Append(">"); - - return type.ToString(); - } - - private static string GetCanExecuteAction(IMethodSymbol command, bool isLambda, bool isMethod, string? canExecute) - { - var canExecuteName = new StringBuilder(canExecute ?? ""); - - if (canExecuteName.Length != 0) - { - if (!isLambda) - { - canExecuteName.Insert(0, ", "); - } - else - { - var parameters = command.Parameters; - var missingParameters = string.Join(", ", Enumerable.Repeat("_", parameters.Length)); - - canExecuteName.Insert(0, $", ({missingParameters}) => "); - if (isLambda && isMethod) canExecuteName.Append("()"); - } - } - - return canExecuteName.ToString(); - } -} \ No newline at end of file diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Data/Members/BindableField.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Data/Members/BindableField.cs deleted file mode 100644 index d751fc6..0000000 --- a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Data/Members/BindableField.cs +++ /dev/null @@ -1,146 +0,0 @@ -using Microsoft.CodeAnalysis; -using Aspid.Generator.Helpers; -using System.Collections.Immutable; -using Microsoft.CodeAnalysis.CSharp; -using static Aspid.MVVM.Generators.Descriptions.Classes; -using static Aspid.MVVM.Generators.Descriptions.General; - -namespace Aspid.MVVM.Generators.ViewModels.Data.Members; - -public class BindableField : BindableMember -{ - public readonly bool IsReadOnly; - public readonly string GetAccessAsText; - public readonly string SetAccessAsText; - public readonly string GeneralAccessAsText; - public readonly ImmutableArray BindAlso; - - public BindableField(IFieldSymbol field, BindMode mode, ImmutableArray bindAlso) - : base(field, - mode, - field.Type.ToDisplayStringGlobal(), - field.Name, - field.IsConst ? field.Name : field.GetPropertyName(), - string.Empty, - field.Type.TypeKind) - { - BindAlso = bindAlso; - IsReadOnly = mode is BindMode.OneTime; - - var accessors = GetAccessors(field); - - GetAccessAsText = accessors.Get == accessors.General - ? string.Empty - : ConvertAccessToText(accessors.Get); - - SetAccessAsText = accessors.Set == accessors.General - ? string.Empty - : ConvertAccessToText(accessors.Set); - - GeneralAccessAsText = ConvertAccessToText(accessors.General); - } - - public string ToDeclarationPropertyString() - { - return IsReadOnly - ? $""" - {GeneratedCodeViewModelAttribute} - {GeneralAccessAsText}{Type} {GeneratedName} => {SourceName}; - """ - : $$""" - {{GeneratedCodeViewModelAttribute}} - {{GeneralAccessAsText}}{{Type}} {{GeneratedName}} - { - {{GetAccessAsText}}get => {{SourceName}}; - {{SetAccessAsText}}set => Set{{GeneratedName}}(value); - } - """; - } - - // TODO Nullable? - public string ToSetMethodString() - { - if (Mode is BindMode.OneTime) return string.Empty; - - var setMethod = $"Set{GeneratedName}"; - var onMethodChanged = $"On{GeneratedName}Changed"; - var onMethodChanging = $"On{GeneratedName}Changing"; - - var eventInvoke = ToInvokeBindableMemberString(); - var keyWordThis = !Member.IsStatic ? "this." : string.Empty; - - foreach (var property in BindAlso) - eventInvoke += $"\n\t{property.ToInvokeBindableMemberString()}"; - - return - $$""" - {{GeneratedCodeViewModelAttribute}} - private void {{setMethod}}({{Type}} value) - { - if ({{EqualityComparer}}<{{Type}}>.Default.Equals({{SourceName}}, value)) return; - - {{onMethodChanging}}({{SourceName}}, value); - {{keyWordThis}}{{SourceName}} = value; - {{eventInvoke}} - {{onMethodChanged}}(value); - } - - {{GeneratedCodeViewModelAttribute}} - partial void {{onMethodChanging}}({{Type}} oldValue, {{Type}} newValue); - - {{GeneratedCodeViewModelAttribute}} - partial void {{onMethodChanged}}({{Type}} newValue); - """; - } - - private static string ConvertAccessToText(SyntaxKind syntaxKind) => syntaxKind switch - { - SyntaxKind.PrivateKeyword => "private ", - SyntaxKind.ProtectedKeyword => "protected ", - SyntaxKind.PublicKeyword => "public ", - _ => "" - }; - - private static Accessors GetAccessors(IFieldSymbol field) - { - var accessors = new Accessors(SyntaxKind.PrivateKeyword, SyntaxKind.PrivateKeyword); - if (!field.HasAnyAttribute(out var accessAttribute, AccessAttribute)) return accessors; - - if (accessAttribute!.ConstructorArguments.Length == 1) - { - var value = (SyntaxKind)(int)(accessAttribute.ConstructorArguments[0].Value ?? SyntaxKind.PrivateKeyword); - accessors.Get = value; - accessors.Set = value; - } - - foreach (var argument in accessAttribute!.NamedArguments) - { - switch (argument.Key) - { - case "Get": accessors.Get = (SyntaxKind)(int)(argument.Value.Value ?? SyntaxKind.PrivateKeyword); break; - case "Set": accessors.Set = (SyntaxKind)(int)(argument.Value.Value ?? SyntaxKind.PrivateKeyword); break; - } - } - - return accessors; - } - - private ref struct Accessors(SyntaxKind get, SyntaxKind set) - { - public SyntaxKind Get = get; - public SyntaxKind Set = set; - - public SyntaxKind General => GetGeneralAccessor(Get, Set); - - private static SyntaxKind GetGeneralAccessor(SyntaxKind getAccessor, SyntaxKind setAccessor) - { - if (setAccessor == getAccessor) return getAccessor; - if (getAccessor == SyntaxKind.PublicKeyword) return getAccessor; - if (setAccessor == SyntaxKind.PublicKeyword) return setAccessor; - if (getAccessor == SyntaxKind.ProtectedKeyword) return getAccessor; - if (setAccessor == SyntaxKind.ProtectedKeyword) return setAccessor; - - return SyntaxKind.PrivateKeyword; - } - } -} \ No newline at end of file diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Data/Members/BindableMember.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Data/Members/BindableMember.cs deleted file mode 100644 index d680117..0000000 --- a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Data/Members/BindableMember.cs +++ /dev/null @@ -1,133 +0,0 @@ -using Microsoft.CodeAnalysis; -using Aspid.MVVM.Generators.Ids.Data; -using static Aspid.Generator.Helpers.SymbolExtensions; -using static Aspid.MVVM.Generators.Descriptions.General; -using static Aspid.MVVM.Generators.Descriptions.Classes; - -namespace Aspid.MVVM.Generators.ViewModels.Data.Members; - -public abstract class BindableMember : BindableMember - where T : ISymbol -{ - public readonly T Member; - - protected BindableMember(T member, BindMode mode, string type, string sourceName, string generatedName, string idPostfix, TypeKind typeKind = TypeKind.Class) - : base(member, mode, type, sourceName, generatedName, idPostfix, typeKind) - { - Member = member; - } -} - -public abstract class BindableMember -{ - public readonly string Type; - public readonly string SourceName; - public readonly string GeneratedName; - public readonly string BindableMemberPropertyType; - - public readonly IdData Id; - public readonly BindMode Mode; - - private readonly string? _bindableType; - private readonly string _bindableFieldName; - - protected BindableMember(ISymbol member, BindMode mode, string type, string name, string idPostfix, TypeKind typeKind = TypeKind.Class) - : this(member, mode, type, name, name, idPostfix, typeKind) { } - - protected BindableMember( - ISymbol member, - BindMode mode, - string type, - string sourceName, - string generatedName, - string idPostfix, - TypeKind typeKind = TypeKind.Class) - { - Type = type; - Mode = mode; - SourceName = sourceName; - GeneratedName = generatedName; - Id = new IdData(member, idPostfix); - - switch (mode) - { - case BindMode.OneWay: - _bindableType = typeKind switch - { - TypeKind.Enum => OneWayEnumBindableMember, - TypeKind.Struct => OneWayStructBindableMember, - _ => OneWayBindableMember - }; - break; - - case BindMode.TwoWay: - _bindableType = typeKind switch - { - TypeKind.Enum => TwoWayEnumBindableMember, - TypeKind.Struct => TwoWayStructBindableMember, - _ => TwoWayBindableMember - }; - break; - - case BindMode.OneTime: - _bindableType = typeKind switch - { - TypeKind.Enum => OneTimeEnumBindableMember, - TypeKind.Struct => OneTimeStructBindableMember, - _ => OneTimeBindableMember - }; - break; - - case BindMode.OneWayToSource: - _bindableType = typeKind switch - { - TypeKind.Enum => OneWayToSourceEnumBindableMember, - TypeKind.Struct => OneWayToSourceStructBindableMember, - _ => OneWayToSourceBindableMember - }; - break; - } - - BindableMemberPropertyType = Mode is BindMode.OneTime - ? IReadOnlyValueBindableMember - : IReadOnlyBindableMember; - - _bindableFieldName = $"__{RemoveFieldPrefix(GetFieldName(generatedName, null))}Bindable"; - } - - public string? ToBindableMemberFieldDeclarationString() - { - if (Mode is BindMode.OneTime) return null; - - return _bindableType is null - ? null - : $""" - [{EditorBrowsableAttribute}({EditorBrowsableState}.Never)] - {GeneratedCodeViewModelAttribute} - private {_bindableType}<{Type}> {_bindableFieldName}; - """; - } - - public string ToBindableMemberPropertyDeclarationString() - { - var instantiate = Mode switch - { - BindMode.OneWay => $"{_bindableFieldName} ??= new({GeneratedName})", - BindMode.TwoWay => $"{_bindableFieldName} ??= new({GeneratedName}, Set{GeneratedName})", - BindMode.OneTime => $"{_bindableType}<{Type}>.Get({GeneratedName})", - BindMode.OneWayToSource => $"{_bindableFieldName} ??= new(Set{GeneratedName})", - _ => string.Empty - }; - - return $""" - {GeneratedCodeViewModelAttribute} - public {BindableMemberPropertyType}<{Type}> {GeneratedName}Bindable => - {instantiate}; - """; - } - - // TODO Nullable? - public string ToInvokeBindableMemberString() => Mode is not (BindMode.OneWayToSource or BindMode.OneTime) - ? $"this.{_bindableFieldName}?.Invoke({SourceName});" - : string.Empty; -} \ No newline at end of file diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Data/Members/Collections/IdLengthMemberGroup.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Data/Members/Collections/IdLengthMemberGroup.cs index d686f96..047566e 100644 --- a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Data/Members/Collections/IdLengthMemberGroup.cs +++ b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Data/Members/Collections/IdLengthMemberGroup.cs @@ -1,15 +1,16 @@ using System.Linq; using System.Collections.Generic; using System.Collections.Immutable; +using Aspid.MVVM.Generators.Generators.ViewModels.Data.Infos; -namespace Aspid.MVVM.Generators.ViewModels.Data.Members.Collections; +namespace Aspid.MVVM.Generators.Generators.ViewModels.Data.Members.Collections; -public readonly struct IdLengthMemberGroup(int length, ImmutableArray members) +public readonly struct IdLengthMemberGroup(int length, ImmutableArray members) { public readonly int Length = length; - public readonly ImmutableArray Members = members; + public readonly ImmutableArray Members = members; - public static ImmutableArray Create(ImmutableArray bindableMembers) + public static ImmutableArray Create(ImmutableArray bindableMembers) { var bindableMembersCountByLength = new Dictionary(); @@ -22,7 +23,7 @@ public static ImmutableArray Create(ImmutableArray>(); + var idGroups = new Dictionary>(); foreach (var bindableMember in bindableMembers) { diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Data/Members/CustomViewModelInterface.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Data/Members/CustomViewModelInterface.cs index 5edb3ef..8c2ac44 100644 --- a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Data/Members/CustomViewModelInterface.cs +++ b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Data/Members/CustomViewModelInterface.cs @@ -1,6 +1,6 @@ using Microsoft.CodeAnalysis; -namespace Aspid.MVVM.Generators.ViewModels.Data.Members; +namespace Aspid.MVVM.Generators.Generators.ViewModels.Data.Members; public readonly struct CustomViewModelInterface(string id, IPropertySymbol property, ITypeSymbol @interface) { diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Data/PropertyNotificationData.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Data/PropertyNotificationData.cs new file mode 100644 index 0000000..d0c6ac8 --- /dev/null +++ b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Data/PropertyNotificationData.cs @@ -0,0 +1,47 @@ +using System.Collections.Generic; + +namespace Aspid.MVVM.Generators.Generators.ViewModels.Data; + +public sealed class PropertyNotificationData +{ + // Key - propertyName, Value - List of line numbers + public Dictionary> OnPropertyChangedCalls { get; } = []; + + // Key - type, Value - Dictionary (propertyName -> List of line numbers) + public Dictionary>> SetFieldCallsByType { get; } = []; + + public HashSet PropertiesRequiringSetFieldBody { get; } = []; + + public bool HasOnPropertyChangedCalls => OnPropertyChangedCalls.Count > 0; + + public bool HasSetFieldCalls => SetFieldCallsByType.Count > 0; + + public void AddOnPropertyChangedCall(string propertyName, int line) + { + if (!OnPropertyChangedCalls.TryGetValue(propertyName, out var lines)) + { + lines = []; + OnPropertyChangedCalls[propertyName] = lines; + } + + lines.Add(line); + } + + public void AddSetFieldCall(string propertyName, int line, string type) + { + if (!SetFieldCallsByType.TryGetValue(type, out var propertyCalls)) + { + propertyCalls = []; + SetFieldCallsByType[type] = propertyCalls; + } + + if (!propertyCalls.TryGetValue(propertyName, out var lines)) + { + lines = []; + propertyCalls[propertyName] = lines; + } + + lines.Add(line); + PropertiesRequiringSetFieldBody.Add(propertyName); + } +} \ No newline at end of file diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Data/ViewModelData.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Data/ViewModelData.cs index d09ff45..32385ae 100644 --- a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Data/ViewModelData.cs +++ b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Data/ViewModelData.cs @@ -2,18 +2,20 @@ using System.Collections.Generic; using System.Collections.Immutable; using Microsoft.CodeAnalysis.CSharp.Syntax; -using Aspid.MVVM.Generators.ViewModels.Data.Members; -using Aspid.MVVM.Generators.ViewModels.Data.Members.Collections; +using Aspid.MVVM.Generators.Generators.ViewModels.Data.Infos; +using Aspid.MVVM.Generators.Generators.ViewModels.Data.Members; +using Aspid.MVVM.Generators.Generators.ViewModels.Data.Members.Collections; -namespace Aspid.MVVM.Generators.ViewModels.Data; +namespace Aspid.MVVM.Generators.Generators.ViewModels.Data; public readonly struct ViewModelData( Inheritor inheritor, INamedTypeSymbol symbol, ClassDeclarationSyntax declaration, - ImmutableArray members, + ImmutableArray members, ImmutableArray idGroups, - Dictionary customViewModelInterfaces) + Dictionary customViewModelInterfaces, + PropertyNotificationData propertyNotificationData) { public readonly string Name = symbol.Name; @@ -21,7 +23,9 @@ public readonly struct ViewModelData( public readonly INamedTypeSymbol Symbol = symbol; public readonly ClassDeclarationSyntax Declaration = declaration; - public readonly ImmutableArray Members = members; + public readonly ImmutableArray Members = members; public readonly ImmutableArray IdGroups = idGroups; + public readonly Dictionary CustomViewModelInterfaces = customViewModelInterfaces; + public readonly PropertyNotificationData PropertyNotificationData = propertyNotificationData; } \ No newline at end of file diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Extensions/InvocationExpressionSyntaxExtensions.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Extensions/InvocationExpressionSyntaxExtensions.cs new file mode 100644 index 0000000..5fdb9e6 --- /dev/null +++ b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Extensions/InvocationExpressionSyntaxExtensions.cs @@ -0,0 +1,13 @@ +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Aspid.MVVM.Generators.Generators.ViewModels.Extensions; + +public static class InvocationExpressionSyntaxExtensions +{ + public static string? GetMethodName(this InvocationExpressionSyntax invocation) => invocation.Expression switch + { + IdentifierNameSyntax identifier => identifier.Identifier.Text, + MemberAccessExpressionSyntax memberAccess => memberAccess.Name.Identifier.Text, + _ => null + }; +} \ No newline at end of file diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Extensions/MethodUsageAnalyzer.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Extensions/MethodUsageAnalyzer.cs new file mode 100644 index 0000000..7e01898 --- /dev/null +++ b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Extensions/MethodUsageAnalyzer.cs @@ -0,0 +1,23 @@ +using System.Linq; +using System.Collections.Generic; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Aspid.MVVM.Generators.Generators.ViewModels.Extensions; + +public static class MethodUsageAnalyzer +{ + public static HashSet GetUsedMethods(TypeDeclarationSyntax declaration, HashSet methodNames) + { + var usedMethods = new HashSet(); + + foreach (var invocation in declaration.DescendantNodes().OfType()) + { + var invokedMethodName = invocation.GetMethodName(); + + if (invokedMethodName is not null && methodNames.Contains(invokedMethodName)) + usedMethods.Add(invokedMethodName); + } + + return usedMethods; + } +} \ No newline at end of file diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Extensions/PropertyNotificationAnalyzer.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Extensions/PropertyNotificationAnalyzer.cs new file mode 100644 index 0000000..78df8b1 --- /dev/null +++ b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Extensions/PropertyNotificationAnalyzer.cs @@ -0,0 +1,136 @@ +using System.Linq; +using Microsoft.CodeAnalysis; +using System.Collections.Generic; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Aspid.MVVM.Generators.Generators.ViewModels.Extensions; + +public static class PropertyNotificationAnalyzer +{ + /// + /// Анализирует класс и возвращает данные о вызовах OnPropertyChanged и SetField. + /// + public static Data.PropertyNotificationData Analyze( + TypeDeclarationSyntax classDeclaration, + IReadOnlyDictionary bindableProperties) // propertyName -> type + { + var data = new Data.PropertyNotificationData(); + + foreach (var invocation in classDeclaration.DescendantNodes().OfType()) + { + var methodName = invocation.GetMethodName(); + + switch (methodName) + { + case "OnPropertyChanged": + AnalyzeOnPropertyChanged(invocation, bindableProperties, data); + break; + + case "SetField": + AnalyzeSetField(invocation, bindableProperties, data); + break; + } + } + + return data; + } + + private static void AnalyzeOnPropertyChanged( + InvocationExpressionSyntax invocation, + IReadOnlyDictionary bindableProperties, + Data.PropertyNotificationData data) + { + var line = invocation.GetLocation().GetLineSpan().StartLinePosition.Line + 1; + var arguments = invocation.ArgumentList.Arguments; + + if (arguments.Count == 0) + { + // OnPropertyChanged() - определяем свойство по контексту (ищем свойство в котором вызов) + var propertyName = FindContainingPropertyName(invocation); + if (propertyName is not null && bindableProperties.ContainsKey(propertyName)) + { + data.AddOnPropertyChangedCall(propertyName, line); + } + } + else + { + // OnPropertyChanged(nameof(FirstName)) или OnPropertyChanged("FirstName") + var propertyName = ExtractPropertyName(arguments[0].Expression); + if (propertyName is not null && bindableProperties.ContainsKey(propertyName)) + { + data.AddOnPropertyChangedCall(propertyName, line); + } + } + } + + private static void AnalyzeSetField( + InvocationExpressionSyntax invocation, + IReadOnlyDictionary bindableProperties, + Data.PropertyNotificationData data) + { + var line = invocation.GetLocation().GetLineSpan().StartLinePosition.Line + 1; + var arguments = invocation.ArgumentList.Arguments; + + // SetField(ref _field, value) - минимум 2 аргумента + // SetField(ref _field, value, nameof(Property)) - 3 аргумента + if (arguments.Count < 2) return; + + string? propertyName; + + if (arguments.Count >= 3) + { + // Третий аргумент - имя свойства + propertyName = ExtractPropertyName(arguments[2].Expression); + } + else + { + // Определяем по контексту свойства (вызов внутри свойства) + propertyName = FindContainingPropertyName(invocation); + } + + if (propertyName is not null && bindableProperties.TryGetValue(propertyName, out var type)) + { + data.AddSetFieldCall(propertyName, line, type); + } + } + + private static string? FindContainingPropertyName(SyntaxNode node) + { + var current = node.Parent; + while (current is not null) + { + if (current is PropertyDeclarationSyntax property) + { + return property.Identifier.Text; + } + current = current.Parent; + } + return null; + } + + private static string? ExtractPropertyName(ExpressionSyntax expression) + { + return expression switch + { + // nameof(FirstName) + InvocationExpressionSyntax { Expression: IdentifierNameSyntax { Identifier.Text: "nameof" } } nameofExpr + when nameofExpr.ArgumentList.Arguments.Count > 0 + => GetIdentifierFromExpression(nameofExpr.ArgumentList.Arguments[0].Expression), + + // "FirstName" + LiteralExpressionSyntax literal => literal.Token.ValueText, + + _ => null + }; + } + + private static string? GetIdentifierFromExpression(ExpressionSyntax expression) + { + return expression switch + { + IdentifierNameSyntax identifier => identifier.Identifier.Text, + MemberAccessExpressionSyntax memberAccess => memberAccess.Name.Identifier.Text, + _ => null + }; + } +} \ No newline at end of file diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Extensions/SymbolExtensions.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Extensions/SymbolExtensions.cs index 8ed9654..18ea793 100644 --- a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Extensions/SymbolExtensions.cs +++ b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Extensions/SymbolExtensions.cs @@ -1,30 +1,39 @@ using Microsoft.CodeAnalysis; -using Aspid.Generator.Helpers; -using Aspid.MVVM.Generators.Descriptions; -using Aspid.MVVM.Generators.ViewModels.Data; +using Aspid.Generators.Helper; +using Aspid.MVVM.Generators.Generators.ViewModels.Data; +using Classes = Aspid.MVVM.Generators.Generators.Descriptions.Classes; -namespace Aspid.MVVM.Generators.ViewModels.Extensions; +namespace Aspid.MVVM.Generators.Generators.ViewModels.Extensions; public static class SymbolExtensions { public static BindMode GetBindMode(this ISymbol member) { - if (member.HasAnyAttribute(out var attribute, Classes.BindAttribute, Classes.OneWayBindAttribute, + if (member.TryGetAnyAttributeInSelf(out var attribute, Classes.BindAttribute, Classes.OneWayBindAttribute, Classes.TwoWayBindAttribute, Classes.OneTimeBindAttribute, Classes.OneWayToSourceBindAttribute)) { - var attributeName = attribute!.AttributeClass!.ToDisplayString(); + var attributeName = attribute!.AttributeClass.ToDisplayString(); if (attributeName == Classes.BindAttribute.FullName) { - if (attribute!.ConstructorArguments.Length is 0) + if (attribute.ConstructorArguments.Length is 0) { - if (member is not IFieldSymbol field) return BindMode.TwoWay; - if (field.IsReadOnly || field.IsConst) return BindMode.OneTime; - + if (member is IFieldSymbol field) + { + if (field.IsReadOnly || field.IsConst) return BindMode.OneTime; + return BindMode.TwoWay; + } + + if (member is IPropertySymbol property) + { + if (property.IsReadOnly) return Determine(BindMode.OneWay); + if (property.IsWriteOnly) return Determine(BindMode.OneWayToSource); + } + return BindMode.TwoWay; } - return Determine((BindMode)(int)attribute!.ConstructorArguments[0].Value!); + return Determine((BindMode)(int)attribute.ConstructorArguments[0].Value!); } if (attributeName == Classes.OneWayBindAttribute.FullName) @@ -51,6 +60,13 @@ BindMode Determine(BindMode current) if (field.IsReadOnly && current is not BindMode.OneTime) return BindMode.None; break; } + + case IPropertySymbol property: + { + if (property.IsReadOnly && current is not (BindMode.OneTime or BindMode.OneWay)) return BindMode.None; + if (property.IsWriteOnly && current is not BindMode.OneWayToSource) return BindMode.None; + break; + } } return current; diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Factories/BindableBindAlsoFactory.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Factories/BindableBindAlsoFactory.cs index 19ae3c9..7518abe 100644 --- a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Factories/BindableBindAlsoFactory.cs +++ b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Factories/BindableBindAlsoFactory.cs @@ -1,23 +1,23 @@ using Microsoft.CodeAnalysis; -using Aspid.Generator.Helpers; +using Aspid.Generators.Helper; using System.Collections.Generic; using System.Collections.Immutable; -using Aspid.MVVM.Generators.ViewModels.Data.Members; -using static Aspid.MVVM.Generators.Descriptions.Classes; +using Aspid.MVVM.Generators.Generators.ViewModels.Data.Infos; +using static Aspid.MVVM.Generators.Generators.Descriptions.Classes; -namespace Aspid.MVVM.Generators.ViewModels.Factories; +namespace Aspid.MVVM.Generators.Generators.ViewModels.Factories; public static class BindableBindAlsoFactory { - public static IReadOnlyCollection Create(ImmutableArray members) + public static IReadOnlyCollection Create(ImmutableArray members) { var set = new HashSet(); - var bindableBindAlso = new List(); + var bindableBindAlso = new List(); foreach (var member in members) { - if (!member.HasAnyAttribute(BindAttribute, OneWayBindAttribute, TwoWayBindAttribute)) continue; - if (!member.HasAnyAttribute(out var attribute, BindAlsoAttribute)) continue; + if (!member.HasAnyAttributeInSelf(BindAttribute, OneWayBindAttribute, TwoWayBindAttribute)) continue; + if (!member.TryGetAnyAttributeInSelf(out var attribute, BindAlsoAttribute)) continue; var value = attribute!.ConstructorArguments[0].Value; if (value is null) continue; @@ -27,8 +27,10 @@ public static IReadOnlyCollection Create(ImmutableArray Create( + public static IReadOnlyCollection Create( ImmutableArray methods, ImmutableArray properties, ImmutableArray generatedBoolProperties) { - var bindableCommands = new List(); + var bindableCommands = new List(); var boolMethods = methods.Where(boolMethods => boolMethods.ReturnType.ToString() is CanExecuteReturnType).ToImmutableArray(); @@ -27,7 +27,7 @@ public static IReadOnlyCollection Create( foreach (var method in methods) { - if (!method.HasAnyAttribute(out var attribute, Classes.RelayCommandAttribute)) continue; + if (!method.TryGetAnyAttributeInSelf(out var attribute, Classes.RelayCommandAttribute)) continue; var canExecuteArgument = attribute!.NamedArguments .Where(pair => pair.Key == "CanExecute") @@ -37,8 +37,8 @@ public static IReadOnlyCollection Create( var canExecute = GetCanExecute(canExecuteArgument, method, boolMethods, boolProperties, generatedBoolProperties); bindableCommands.Add(canExecute.isEixst ? - new BindableCommand(method, canExecuteArgument, canExecute.isLamda, canExecute.isMethod) : - new BindableCommand(method, null, false, false)); + new BindableCommandInfo(method, canExecuteArgument, canExecute.isLamda, canExecute.isMethod) : + new BindableCommandInfo(method, null, false, false)); } return bindableCommands; diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Factories/BindableFieldFactory.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Factories/BindableFieldFactory.cs index df4b51a..ae6cdb3 100644 --- a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Factories/BindableFieldFactory.cs +++ b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Factories/BindableFieldFactory.cs @@ -1,20 +1,17 @@ -using System.Linq; using Microsoft.CodeAnalysis; -using Aspid.Generator.Helpers; using System.Collections.Generic; using System.Collections.Immutable; -using Aspid.MVVM.Generators.ViewModels.Extensions; -using Aspid.MVVM.Generators.ViewModels.Data.Members; -using static Aspid.MVVM.Generators.Descriptions.Classes; -using BindMode = Aspid.MVVM.Generators.ViewModels.Data.BindMode; +using Aspid.MVVM.Generators.Generators.ViewModels.Data.Infos; +using Aspid.MVVM.Generators.Generators.ViewModels.Extensions; +using BindMode = Aspid.MVVM.Generators.Generators.ViewModels.Data.BindMode; -namespace Aspid.MVVM.Generators.ViewModels.Factories; +namespace Aspid.MVVM.Generators.Generators.ViewModels.Factories; public static class BindableFieldFactory { - public static IReadOnlyCollection Create(ImmutableArray fields, IReadOnlyCollection bindableBindAlsos) + public static IReadOnlyCollection Create(ImmutableArray fields) { - var bindableFields = new List(); + var bindableFields = new List(); foreach (var field in fields) { @@ -28,36 +25,18 @@ public static IReadOnlyCollection Create(ImmutableArray GetBindableBindAlso(IFieldSymbol field, IReadOnlyCollection allBindableBindAlsos) - { - var set = new HashSet(); - - foreach (var attribute in field.GetAttributes()) - { - if (attribute.AttributeClass != null && - attribute.AttributeClass.ToDisplayStringGlobal() == BindAlsoAttribute.Global) - { - var value = attribute.ConstructorArguments[0].Value; - if (value is null) continue; - - set.Add(value.ToString()); - } - } - - return allBindableBindAlsos.Where(bindableBindAlso => - set.Contains(bindableBindAlso.SourceName) || set.Contains(bindableBindAlso.GeneratedName)).ToImmutableArray(); - } } \ No newline at end of file diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Factories/BindableMembersFactory.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Factories/BindableMembersFactory.cs index 4c94839..8eb7fb0 100644 --- a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Factories/BindableMembersFactory.cs +++ b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Factories/BindableMembersFactory.cs @@ -1,33 +1,68 @@ using System.Linq; using Microsoft.CodeAnalysis; -using Aspid.Generator.Helpers; +using Aspid.Generators.Helper; using System.Collections.Generic; using System.Collections.Immutable; -using Aspid.MVVM.Generators.ViewModels.Data.Members; +using Aspid.MVVM.Generators.Helpers; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Aspid.MVVM.Generators.Generators.ViewModels.Data; +using Aspid.MVVM.Generators.Generators.ViewModels.Data.Infos; +using Aspid.MVVM.Generators.Generators.ViewModels.Extensions; -namespace Aspid.MVVM.Generators.ViewModels.Factories; +namespace Aspid.MVVM.Generators.Generators.ViewModels.Factories; public static class BindableMembersFactory { - public static ImmutableArray Create(ITypeSymbol symbol) + public static ImmutableArray Create( + ITypeSymbol symbol, + TypeDeclarationSyntax declaration, + out PropertyNotificationData propertyNotificationData) { var members = new MembersByGroup(symbol); + propertyNotificationData = CreatePropertyNotificationData(symbol, declaration); + var bindableFields = BindableFieldFactory.Create(members.Fields); var bindableBindAlso = BindableBindAlsoFactory.Create(members.All); - var bindableFields = BindableFieldFactory.Create(members.Fields, bindableBindAlso); + var bindableProperties = BindablePropertyFactory.Create(declaration, members.Properties, propertyNotificationData); var generatedProperties = bindableFields .Where(field => field.Type.ToString() == "bool") - .Select(field => field.GeneratedName) + .Select(field => field.Name) + .Concat(bindableProperties + .Where(property => property.Type.ToString() == "bool") + .Select(property => property.Name)) .ToImmutableArray(); var bindableCommands = BindableCommandFactory.Create(members.Methods, members.Properties, generatedProperties); - var bindableMembers = new List(bindableBindAlso.Count + bindableFields.Count + bindableCommands.Count); + + var filteredBindableBindAlso = bindableBindAlso.Where(b => !b.HasBindAttribute).ToList(); + var bindableMembers = new List(filteredBindableBindAlso.Count + bindableFields.Count + bindableProperties.Count + bindableCommands.Count); bindableMembers.AddRange(bindableFields); + bindableMembers.AddRange(bindableProperties); bindableMembers.AddRange(bindableCommands); - bindableMembers.AddRange(bindableBindAlso); + bindableMembers.AddRange(filteredBindableBindAlso); return bindableMembers.ToImmutableArray(); } + + private static PropertyNotificationData CreatePropertyNotificationData(ITypeSymbol symbol, TypeDeclarationSyntax candidate) + { + var members = new MembersByGroup(symbol); + + var bindablePropertiesDict = members.Properties + .Where(property => property.GetBindMode() is not BindMode.None) + .ToDictionary(property => property.Name, property => property.Type.ToDisplayStringGlobal()); + + var bindableFieldsDict = members.Fields + .Where(field => field.GetBindMode() is not BindMode.None) + .ToDictionary(field => field.GetPropertyName(), field => field.Type.ToDisplayStringGlobal()); + + foreach (var pair in bindableFieldsDict) + { + bindablePropertiesDict[pair.Key] = pair.Value; + } + + return PropertyNotificationAnalyzer.Analyze(candidate, bindablePropertiesDict); + } } \ No newline at end of file diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Factories/BindablePropertyFactory.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Factories/BindablePropertyFactory.cs new file mode 100644 index 0000000..03b70e6 --- /dev/null +++ b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Factories/BindablePropertyFactory.cs @@ -0,0 +1,64 @@ +using System.Linq; +using Microsoft.CodeAnalysis; +using System.Collections.Generic; +using System.Collections.Immutable; +using Aspid.MVVM.Generators.Helpers; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Aspid.MVVM.Generators.Generators.ViewModels.Data; +using Aspid.MVVM.Generators.Generators.ViewModels.Data.Infos; +using Aspid.MVVM.Generators.Generators.ViewModels.Extensions; +using BindMode = Aspid.MVVM.Generators.Generators.ViewModels.Data.BindMode; + +namespace Aspid.MVVM.Generators.Generators.ViewModels.Factories; + +public static class BindablePropertyFactory +{ + public static IReadOnlyCollection Create( + TypeDeclarationSyntax declaration, + ImmutableArray properties, + PropertyNotificationData propertyNotificationData) + { + var bindableProperties = new List(); + + var setMethodNames = new HashSet(properties + .Select(p => $"Set{p.Name.CapitalizeFirstLetter()}Field")); + + var usedSetMethods = MethodUsageAnalyzer.GetUsedMethods(declaration, setMethodNames); + + foreach (var property in properties) + { + var mode = property.GetBindMode(); + + switch (mode) + { + case BindMode.OneTime: + case BindMode.OneWay: + { + if (property.IsWriteOnly) continue; + break; + } + case BindMode.TwoWay: + { + if (property.IsReadOnly || property.IsWriteOnly) continue; + break; + } + case BindMode.OneWayToSource: + { + if (property.IsReadOnly) continue; + break; + } + + case BindMode.None: + default: continue; + } + + var setMethodName = $"Set{property.Name.CapitalizeFirstLetter()}Field"; + var isSetMethodUsed = usedSetMethods.Contains(setMethodName) + || propertyNotificationData.PropertiesRequiringSetFieldBody.Contains(property.Name); + + bindableProperties.Add(new BindablePropertyInfo(property, mode, isSetMethodUsed)); + } + + return bindableProperties; + } +} \ No newline at end of file diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Factories/CustomViewModelInterfacesFactory.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Factories/CustomViewModelInterfacesFactory.cs index 8218543..b21e919 100644 --- a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Factories/CustomViewModelInterfacesFactory.cs +++ b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/Factories/CustomViewModelInterfacesFactory.cs @@ -1,12 +1,12 @@ using System.Linq; using Microsoft.CodeAnalysis; -using Aspid.Generator.Helpers; +using Aspid.Generators.Helper; using System.Collections.Generic; -using Aspid.MVVM.Generators.Ids.Extensions; -using Aspid.MVVM.Generators.ViewModels.Data.Members; -using static Aspid.MVVM.Generators.Descriptions.Classes; +using Aspid.MVVM.Generators.Generators.Ids.Extensions; +using Aspid.MVVM.Generators.Generators.ViewModels.Data.Members; +using static Aspid.MVVM.Generators.Generators.Descriptions.Classes; -namespace Aspid.MVVM.Generators.ViewModels.Factories; +namespace Aspid.MVVM.Generators.Generators.ViewModels.Factories; public static class CustomViewModelInterfacesFactory { @@ -22,7 +22,7 @@ public static Dictionary Create(ITypeSymbol sy void AddMembers(ITypeSymbol @interface) { - if (!@interface.HasInterfaceInSelfOrBases(IViewModel)) return; + if (!@interface.HasAnyInterfaceInSelfAndBases(IViewModel)) return; foreach (var property in @interface.GetMembers() .OfType() @@ -34,7 +34,7 @@ void AddMembers(ITypeSymbol @interface) || type.Contains(IReadOnlyValueBindableMember); })) { - if (property.HasAnyAttribute(IgnoreAttribute)) continue; + if (property.HasAnyAttributeInSelf(IgnoreAttribute)) continue; var id = property.GetId(); dictionary[id] = new CustomViewModelInterface(id, property, @interface); diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/ViewModelGenerator.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/ViewModelGenerator.cs index 62e26d3..0118c96 100644 --- a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/ViewModelGenerator.cs +++ b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/ViewModels/ViewModelGenerator.cs @@ -1,26 +1,28 @@ +using System; using System.Threading; using System.Diagnostics; using Microsoft.CodeAnalysis; -using Aspid.Generator.Helpers; +using Aspid.Generators.Helper; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; -using Aspid.MVVM.Generators.ViewModels.Body; -using Aspid.MVVM.Generators.ViewModels.Data; -using Aspid.MVVM.Generators.ViewModels.Factories; -using Aspid.MVVM.Generators.ViewModels.Data.Members.Collections; +using Aspid.MVVM.Generators.Generators.ViewModels.Body; +using Aspid.MVVM.Generators.Generators.ViewModels.Data; +using Aspid.MVVM.Generators.Generators.ViewModels.Factories; +using Aspid.MVVM.Generators.Generators.ViewModels.Data.Members.Collections; using Unsafe = System.Runtime.CompilerServices.Unsafe; -using static Aspid.MVVM.Generators.Descriptions.Classes; +using static Aspid.MVVM.Generators.Generators.Descriptions.Classes; -namespace Aspid.MVVM.Generators.ViewModels; +namespace Aspid.MVVM.Generators.Generators.ViewModels; [Generator(LanguageNames.CSharp)] public sealed class ViewModelGenerator : IIncrementalGenerator { public void Initialize(IncrementalGeneratorInitializationContext context) { + Console.WriteLine(ViewModelAttribute.FullName); var provider = context.SyntaxProvider.ForAttributeWithMetadataName(ViewModelAttribute.FullName, SyntacticPredicate, FindViewModels) - .Where(static foundForSourceGenerator => foundForSourceGenerator.IsNeed) - .Select(static (foundForSourceGenerator, _) => foundForSourceGenerator.Container); + .Where(static foundForSourceGenerator => foundForSourceGenerator.HasValue) + .Select(static (foundForSourceGenerator, _) => foundForSourceGenerator!.Value); context.RegisterSourceOutput( source: provider, @@ -35,35 +37,37 @@ private static bool SyntacticPredicate(SyntaxNode node, CancellationToken cancel && !candidate.Modifiers.Any(SyntaxKind.StaticKeyword); } - private static FoundForGenerator FindViewModels(GeneratorAttributeSyntaxContext context, + private static ViewModelData? FindViewModels(GeneratorAttributeSyntaxContext context, CancellationToken cancellationToken) { - if (context.TargetSymbol is not INamedTypeSymbol symbol) return default; + if (context.TargetSymbol is not INamedTypeSymbol symbol) return null; Debug.Assert(context.TargetNode is ClassDeclarationSyntax); var candidate = Unsafe.As(context.TargetNode); - var inheritor = symbol.HasAttributeInBases(ViewModelAttribute) + var inheritor = symbol.HasAnyAttributeInBases(ViewModelAttribute) ? Inheritor.Inheritor : Inheritor.None; - - var bindableMembers = BindableMembersFactory.Create(symbol); + + var bindableMembers = BindableMembersFactory.Create(symbol, candidate, out var propertyNotificationData); var memberByGroups = IdLengthMemberGroup.Create(bindableMembers); var customViewModelInterfaces = CustomViewModelInterfacesFactory.Create(symbol); - var data = new ViewModelData(inheritor, symbol, candidate, bindableMembers, memberByGroups, customViewModelInterfaces); - return new FoundForGenerator(data); + return new ViewModelData(inheritor, symbol, candidate, bindableMembers, memberByGroups, customViewModelInterfaces, propertyNotificationData); } private static void GenerateCode(SourceProductionContext context, ViewModelData data) { var declaration = data.Declaration; var @namespace = declaration.GetNamespaceName(); - var declarationText = declaration.GetDeclarationText(); + var declarationText = new DeclarationText(declaration); - PropertiesBody.Generate(@namespace, data, declarationText, context); + BindableMembers.Generate(@namespace, data, declarationText, context); RelayCommandBody.Generate(@namespace, data, declarationText, context); - BindableMembersBody.Generate(@namespace, data, declarationText, context); FindBindableMembersBody.Generate(@namespace, data, declarationText, context); + GeneratedPropertiesBody.Generate(@namespace, data, declarationText, context); + PropertyNotificationBody.Generate(@namespace, data, declarationText, context); + GeneratedPropertyMethodsBody.Generate(@namespace, data, declarationText, context); + BindableInterfaceMembersBody.Generate(@namespace, data, declarationText, context); } } \ No newline at end of file diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Views/Body/BinderCachedBody.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Views/Body/BinderCachedBody.cs index 1533b13..9a3bf71 100644 --- a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Views/Body/BinderCachedBody.cs +++ b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Views/Body/BinderCachedBody.cs @@ -1,28 +1,25 @@ using Microsoft.CodeAnalysis; -using Aspid.Generator.Helpers; -using Aspid.MVVM.Generators.Descriptions; -using Aspid.MVVM.Generators.Views.Data; -using Aspid.MVVM.Generators.Views.Data.Members; +using Aspid.Generators.Helper; +using Aspid.MVVM.Generators.Generators.Views.Data; +using Aspid.MVVM.Generators.Generators.Views.Data.Members; +using static Aspid.MVVM.Generators.Generators.Descriptions.Constants; -namespace Aspid.MVVM.Generators.Views.Body; +namespace Aspid.MVVM.Generators.Generators.Views.Body; public static class BinderCachedBody { - private const string GeneratedAttribute = General.GeneratedCodeViewAttribute; - private static readonly string EditorBrowsableAttribute = $"[{Classes.EditorBrowsableAttribute.Global}({Classes.EditorBrowsableState.Global}.Never)]"; - public static void Generate( string @namespace, in ViewDataSpan data, - in DeclarationText declaration, + DeclarationText declaration, in SourceProductionContext context) { if (data.MembersByType.PropertyBinders.Length + data.MembersByType.AsBinders.Length == 0) return; var code = new CodeWriter(); - code.AppendClassBegin(@namespace, declaration) + code.BeginClass(@namespace, declaration) .AppendCachedBinders(data) - .AppendClassEnd(@namespace); + .EndClass(@namespace); context.AddSource(declaration.GetFileName(@namespace, "CachedBinders"), code.GetSourceText()); } @@ -43,8 +40,8 @@ private static CodeWriter AppendCachedBinders(this CodeWriter code, in ViewDataS private static CodeWriter AppendCachedBinderMember(this CodeWriter code, in CachedBinderMember cashedBinderMember) { - code.AppendLine(EditorBrowsableAttribute) - .AppendLine(GeneratedAttribute) + code.AppendLine(GeneratedCodeViewAttribute) + .AppendLine(EditorBrowsableAttributeNever) .AppendLine($"private {cashedBinderMember.Type?.ToDisplayStringGlobal()} {cashedBinderMember.CachedName};") .AppendLine(); @@ -53,8 +50,8 @@ private static CodeWriter AppendCachedBinderMember(this CodeWriter code, in Cach private static CodeWriter AppendAsBinderMember(this CodeWriter code, AsBinderMember asBinderMember) { - code.AppendLine(EditorBrowsableAttribute) - .AppendLine(GeneratedAttribute) + code.AppendLine(GeneratedCodeViewAttribute) + .AppendLine(EditorBrowsableAttributeNever) .AppendLine(asBinderMember.Type is IArrayTypeSymbol ? $"private {asBinderMember.AsBinderType}[] {asBinderMember.CachedName};" : $"private {asBinderMember.AsBinderType} {asBinderMember.CachedName};") diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Views/Body/BinderFieldsBody.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Views/Body/BinderFieldsBody.cs new file mode 100644 index 0000000..9b03155 --- /dev/null +++ b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Views/Body/BinderFieldsBody.cs @@ -0,0 +1,49 @@ +using System.Linq; +using Microsoft.CodeAnalysis; +using Aspid.Generators.Helper; +using Aspid.Generators.Helper.Unity; +using Aspid.MVVM.Generators.Generators.Views.Data; +using Aspid.MVVM.Generators.Generators.Views.Helpers; +using static Aspid.MVVM.Generators.Generators.Descriptions.Constants; +using Classes = Aspid.MVVM.Generators.Generators.Descriptions.Classes; + +namespace Aspid.MVVM.Generators.Generators.Views.Body; + +public static class BinderFieldsBody +{ + public static void Generate( + string @namespace, + in ViewDataSpan data, + DeclarationText declaration, + in SourceProductionContext context) + { + var virtualFields = VirtualBinderFields.Collect(data); + if (virtualFields.Count is 0) return; + + var code = new CodeWriter(); + code.BeginClass(@namespace, declaration); + + foreach (var info in virtualFields.Values) + { + var requireBinderArgs = string.Join(", ", info.RequireBinderTypes.Select(type => $"typeof({type})")); + + code.AppendLine(GeneratedCodeViewAttribute); + + if (info.HeaderGroup is not null) + code.AppendLine($"[{Classes.HeaderGroupAttribute}({EscapeStringLiteral(info.HeaderGroup)})]"); + + if (info.Header is not null) + code.AppendLine($"[{Classes.HeaderAttribute}({EscapeStringLiteral(info.Header)})]"); + + code.AppendLine($"[{Classes.RequireBinderAttribute}({requireBinderArgs})]") + .AppendLine($"[{UnityClasses.SerializeField}] private {Classes.MonoBinder}[] {info.FieldName};") + .AppendLine(); + } + + code.EndClass(@namespace); + context.AddSource(declaration.GetFileName(@namespace, "BinderFields"), code.GetSourceText()); + } + + private static string EscapeStringLiteral(string value) => + $"\"{value.Replace("\\", "\\\\").Replace("\"", "\\\"")}\""; +} diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Views/Body/Extensions/BindSafelyExtensions.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Views/Body/Extensions/BindSafelyExtensions.cs index 2ea5ade..a6d8819 100644 --- a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Views/Body/Extensions/BindSafelyExtensions.cs +++ b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Views/Body/Extensions/BindSafelyExtensions.cs @@ -1,33 +1,21 @@ -using Microsoft.CodeAnalysis; -using Aspid.Generator.Helpers; -using Aspid.MVVM.Generators.ViewModels.Data.Members; -using Aspid.MVVM.Generators.Views.Data.Members; +using Aspid.Generators.Helper; +using Aspid.MVVM.Generators.Generators.Views.Data.Members; +using Aspid.MVVM.Generators.Generators.ViewModels.Data.Infos; -namespace Aspid.MVVM.Generators.Views.Body.Extensions; +namespace Aspid.MVVM.Generators.Generators.Views.Body.Extensions; public static class BindSafelyExtensions { - public static CodeWriter AppendBindSafely(this CodeWriter code, BinderMember member, BindableMember bindableMember) => - code.AppendBindSafely(member, bindableMember?.GeneratedName + "Bindable"); - + public static CodeWriter AppendBindSafely(this CodeWriter code, BinderMember member, IBindableMemberInfo bindableMember) => + code.AppendBindSafely(member, bindableMember.Bindable.PropertyName); + public static CodeWriter AppendBindSafely(this CodeWriter code, BinderMember member, string? bindableMemberName = null) { var parameters = $"new({member.Id})"; var name = member is CachedBinderMember cachedMember ? cachedMember.CachedName : member.Name; - return code.AppendLine(bindableMemberName is not null - ? $"{name}.BindSafely(viewModel.{bindableMemberName});" - : $"{name}.BindSafely(viewModel.FindBindableMember({parameters}));"); - - } - - private static string GetBinderMemberType(this BinderMember member) - { - if (member is AsBinderMember asBinderMember) - return asBinderMember.AsBinderType; - - return member.Type is IArrayTypeSymbol arrayType - ? arrayType.ElementType.ToDisplayStringGlobal() - : member.Type.ToDisplayStringGlobal(); + return code.AppendLine(bindableMemberName is not null + ? $"{name}.BindSafely(viewModel.{bindableMemberName}, this, {member.Id});" + : $"{name}.BindSafely(viewModel.FindBindableMember({parameters}), this, {member.Id});"); } -} \ No newline at end of file +} diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Views/Body/GenericInitializeView.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Views/Body/GenericInitializeView.cs index b86e7dc..43e9b38 100644 --- a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Views/Body/GenericInitializeView.cs +++ b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Views/Body/GenericInitializeView.cs @@ -1,33 +1,35 @@ using System.Linq; using Microsoft.CodeAnalysis; -using Aspid.Generator.Helpers; +using Aspid.Generators.Helper; using System.Collections.Generic; -using Aspid.MVVM.Generators.Views.Data; -using Aspid.MVVM.Generators.Descriptions; -using Aspid.MVVM.Generators.ViewModels.Factories; -using Aspid.MVVM.Generators.Views.Body.Extensions; -using Aspid.MVVM.Generators.ViewModels.Data.Members; -using static Aspid.MVVM.Generators.Descriptions.Classes; -using static Aspid.MVVM.Generators.Descriptions.Defines; -using static Aspid.MVVM.Generators.Descriptions.General; +using Aspid.MVVM.Generators.Generators.Views.Data; +using Aspid.MVVM.Generators.Generators.Descriptions; +using Aspid.MVVM.Generators.Generators.Views.Helpers; +using Aspid.MVVM.Generators.Generators.ViewModels.Factories; +using Aspid.MVVM.Generators.Generators.ViewModels.Data.Infos; +using Aspid.MVVM.Generators.Generators.Views.Body.Extensions; +using static Aspid.Generators.Helper.Classes; +using static Aspid.Generators.Helper.Unity.UnityClasses; +using static Aspid.MVVM.Generators.Generators.Descriptions.Defines; +using static Aspid.MVVM.Generators.Generators.Descriptions.Constants; -namespace Aspid.MVVM.Generators.Views.Body; +namespace Aspid.MVVM.Generators.Generators.Views.Body; public static class GenericInitializeView { public static void Generate( string @namespace, in ViewDataSpan data, - in DeclarationText declaration, + DeclarationText declaration, in SourceProductionContext context) { foreach (var genericView in data.GenericViews) { var code = new CodeWriter(); - code.AppendClassBegin([Namespaces.Aspid_MVVM], @namespace, declaration, null) + code.BeginClass([Namespaces.Aspid_MVVM], @namespace, declaration, null) .AppendGenericViews(data, genericView) - .AppendClassEnd(@namespace); + .EndClass(@namespace); context.AddSource(declaration.GetFileName(@namespace, genericView.Type.ToDisplayString()), code.GetSourceText()); } @@ -97,12 +99,12 @@ public void Initialize({{typeName}} viewModel) if (genericView.Type.TypeKind is not TypeKind.Interface) { - var bindableMembers = new Dictionary(); + var bindableMembers = new Dictionary(); for (var viewModelType = genericView.Type; viewModelType is not null; viewModelType = viewModelType.BaseType) { foreach (var memberPair in - BindableMembersFactory.Create(viewModelType) + BindableMembersFactory.Create(viewModelType, data.Declaration, out _) .ToDictionary(bindable => bindable.Id.SourceValue, bindable => bindable)) { bindableMembers.Add(memberPair.Key, memberPair.Value); @@ -120,6 +122,13 @@ public void Initialize({{typeName}} viewModel) code.AppendBindSafely(member); } } + + var virtualFields = VirtualBinderFields.Collect(data); + foreach (var info in virtualFields.Values) + { + if (bindableMembers.TryGetValue(info.Id.SourceValue, out var bindableMember)) + code.AppendLine($"{info.FieldName}.BindSafely(viewModel.{bindableMember.Bindable.PropertyName}, this, {info.Id.Value});"); + } } else { @@ -156,7 +165,7 @@ private static CodeWriter AppendProfilerMarker(this CodeWriter code, in ViewData $""" #if !{ASPID_MVVM_UNITY_PROFILER_DISABLED} {GeneratedCodeViewAttribute} - [{EditorBrowsableAttribute}({EditorBrowsableState}.Never)] + {EditorBrowsableAttributeNever} private readonly static {ProfilerMarker} __initialize{viewModelTypeName.Replace(".", "_")}Marker = new("{data.Declaration.Identifier.Text}.{viewModelTypeName}.Initialize"); #endif """); diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Views/Body/InitializeBody.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Views/Body/InitializeBody.cs index 00440c9..b9d5c9f 100644 --- a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Views/Body/InitializeBody.cs +++ b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Views/Body/InitializeBody.cs @@ -1,35 +1,33 @@ -using System; using Microsoft.CodeAnalysis; -using Aspid.Generator.Helpers; -using Aspid.MVVM.Generators.Views.Data; -using Aspid.MVVM.Generators.Descriptions; -using Aspid.MVVM.Generators.Views.Data.Members; -using Aspid.MVVM.Generators.Views.Body.Extensions; +using Aspid.Generators.Helper; +using Aspid.MVVM.Generators.Generators.Views.Data; +using Aspid.MVVM.Generators.Generators.Descriptions; +using Aspid.MVVM.Generators.Generators.Views.Helpers; +using Aspid.MVVM.Generators.Generators.Views.Data.Members; +using Aspid.MVVM.Generators.Generators.Views.Body.Extensions; +using static Aspid.Generators.Helper.Classes; +using static Aspid.Generators.Helper.Unity.UnityClasses; +using static Aspid.MVVM.Generators.Generators.Descriptions.Classes; +using static Aspid.MVVM.Generators.Generators.Descriptions.Constants; -namespace Aspid.MVVM.Generators.Views.Body; +namespace Aspid.MVVM.Generators.Generators.Views.Body; // ReSharper disable InconsistentNaming // ReSharper disable once InconsistentNaming public static class InitializeBody { - private const string GeneratedAttribute = General.GeneratedCodeViewAttribute; - - private static readonly string IViewModel = Classes.IViewModel.Global; - private static readonly string ProfilerMarker = Classes.ProfilerMarker.Global; - private static readonly string EditorBrowsableAttribute = $"[{Classes.EditorBrowsableAttribute.Global}({Classes.EditorBrowsableState.Global}.Never)]"; - public static void Generate( string @namespace, in ViewDataSpan data, - in DeclarationText declaration, + DeclarationText declaration, in SourceProductionContext context) { var code = new CodeWriter(); var baseTypes = GetBaseTypes(data); - code.AppendClassBegin([Namespaces.Aspid_MVVM], @namespace, declaration, baseTypes) + code.BeginClass([Namespaces.Aspid_MVVM], @namespace, declaration, baseTypes) .AppendIView(data) - .AppendClassEnd(@namespace); + .EndClass(@namespace); context.AddSource(declaration.GetFileName(@namespace, "Initialize"), code.GetSourceText()); } @@ -40,7 +38,7 @@ private static CodeWriter AppendIView(this CodeWriter code, in ViewDataSpan data { Inheritor.None => code.AppendNone(data), Inheritor.InheritorViewAttribute => code.AppendHasInterfaceOrInheritor(data), - _ => throw new ArgumentOutOfRangeException() + _ => code }; if (!data.IsInstantiateBinders) return code; @@ -61,35 +59,35 @@ private static CodeWriter AppendNone(this CodeWriter code, in ViewDataSpan data) .AppendMultiline( $""" [global::System.NonSerialized] - {GeneratedAttribute} - {EditorBrowsableAttribute} + {GeneratedCodeViewAttribute} + {EditorBrowsableAttributeNever} private bool __isInitializing; """) .AppendMultilineIf(data.IsInstantiateBinders, $""" [global::System.NonSerialized] - {GeneratedAttribute} - {EditorBrowsableAttribute} + {GeneratedCodeViewAttribute} + {EditorBrowsableAttributeNever} private bool __isBindersCached; """) .AppendMultiline( $$""" - {{GeneratedAttribute}} + {{GeneratedCodeViewAttribute}} public {{IViewModel}} ViewModel { get; protected set; } - {{GeneratedAttribute}} + {{GeneratedCodeViewAttribute}} public void Initialize({{IViewModel}} viewModel) { - if (viewModel is null) throw new {{Classes.ArgumentNullException.Global}}(nameof(viewModel)); - if (ViewModel is not null) throw new {{Classes.InvalidOperationException.Global}}("View is already initialized."); + if (viewModel is null) throw new {{ArgumentNullException}}(nameof(viewModel)); + if (ViewModel is not null) throw new {{InvalidOperationException}}("View is already initialized."); ViewModel = viewModel; InitializeInternal(viewModel); } - {{GeneratedAttribute}} + {{GeneratedCodeViewAttribute}} {{modifiers}} void InitializeInternal({{IViewModel}} viewModel) """) .AppendInitializeBody(data) @@ -97,7 +95,7 @@ public void Initialize({{IViewModel}} viewModel) .AppendLine() .AppendMultiline( $$""" - {{GeneratedAttribute}} + {{GeneratedCodeViewAttribute}} public void Deinitialize() { if (ViewModel is null) return; @@ -106,7 +104,7 @@ public void Deinitialize() ViewModel = null; } - {{GeneratedAttribute}} + {{GeneratedCodeViewAttribute}} {{modifiers}} void DeinitializeInternal() """) .AppendDeinitializeBody(data) @@ -125,16 +123,16 @@ private static CodeWriter AppendHasInterfaceOrInheritor(this CodeWriter code, in .AppendMultiline( $""" [global::System.NonSerialized] - {GeneratedAttribute} - {EditorBrowsableAttribute} + {GeneratedCodeViewAttribute} + {EditorBrowsableAttributeNever} private bool __isInitializing; """) .AppendMultilineIf(data.IsInstantiateBinders, $""" [global::System.NonSerialized] - {GeneratedAttribute} - {EditorBrowsableAttribute} + {GeneratedCodeViewAttribute} + {EditorBrowsableAttributeNever} private bool __isBindersCached; """); @@ -156,12 +154,12 @@ private static CodeWriter AppendProfilerMarkers(this CodeWriter code, string cla return code.AppendMultiline( $""" #if !{Defines.ASPID_MVVM_UNITY_PROFILER_DISABLED} - {EditorBrowsableAttribute} - {GeneratedAttribute} + {GeneratedCodeViewAttribute} + {EditorBrowsableAttributeNever} private static readonly {ProfilerMarker} __initializeMarker = new("{className}.Initialize"); - {EditorBrowsableAttribute} - {GeneratedAttribute} + {GeneratedCodeViewAttribute} + {EditorBrowsableAttributeNever} private static readonly {ProfilerMarker} __deinitializeMarker = new("{className}.Deinitialize"); #endif """); @@ -175,7 +173,7 @@ private static CodeWriter AppendInitializeInternalDeclaration(this CodeWriter co code.AppendMultiline( $""" - {GeneratedAttribute} + {GeneratedCodeViewAttribute} {modifiers} void InitializeInternal({IViewModel} viewModel) """); @@ -190,7 +188,7 @@ private static CodeWriter AppendDeinitializeInternalDeclaration(this CodeWriter code.AppendMultiline( $""" - {GeneratedAttribute} + {GeneratedCodeViewAttribute} {modifiers} void DeinitializeInternal() """); @@ -236,7 +234,11 @@ private static CodeWriter AppendInitializeBody(this CodeWriter code, in ViewData foreach (var member in data.Members) code.AppendBindSafely(member); - + + var virtualFields = VirtualBinderFields.Collect(data); + foreach (var info in virtualFields.Values) + code.AppendLine($"{info.FieldName}.BindSafely(viewModel.FindBindableMember(new({info.Id.Value})), this, {info.Id.Value});"); + return code.AppendLine() .AppendLine("OnInitializedInternal(viewModel);") .AppendLine("__isInitializing = false;") @@ -263,14 +265,18 @@ private static CodeWriter AppendDeinitializeBody(this CodeWriter code, in ViewDa { if (member is CachedBinderMember cachedBinderMember) { - code.AppendLine($"{cachedBinderMember.CachedName}.UnbindSafely();"); + code.AppendLine($"{cachedBinderMember.CachedName}.UnbindSafely(this, {member.Id});"); } else { - code.AppendLine($"{member.Name}.UnbindSafely();"); + code.AppendLine($"{member.Name}.UnbindSafely(this, {member.Id});"); } } + var virtualFields = VirtualBinderFields.Collect(data); + foreach (var info in virtualFields.Values) + code.AppendLine($"{info.FieldName}.UnbindSafely(this, {info.Id.Value});"); + code.AppendLine() .AppendLine("OnDeinitializedInternal();") .EndBlock() @@ -283,7 +289,7 @@ private static CodeWriter AppendInstantiateBindersMethods(this CodeWriter code, { code.AppendMultiline( $""" - {GeneratedAttribute} + {GeneratedCodeViewAttribute} private void InstantiateBinders() """) .BeginBlock() @@ -298,10 +304,10 @@ private void InstantiateBinders() .AppendMultiline( $""" - {GeneratedAttribute} + {GeneratedCodeViewAttribute} partial void OnInstantiatingBinders(); - {GeneratedAttribute} + {GeneratedCodeViewAttribute} partial void OnInstantiatedBinders(); """); @@ -341,12 +347,12 @@ private static CodeWriter AppendCreateBinders(this CodeWriter code, in ViewDataS code.AppendLineIf(isAppend) .AppendMultiline( - $$""" - var {{localName}} = {{name}}; - {{binderName}} = new {{binderType}}[{{localName}}.Length]; + $""" + var {localName} = {name}; + {binderName} = new {binderType}[{localName}.Length]; - for (var i = 0; i < {{localName}}.Length; i++) - {{binderName}}[i] = new {{member.AsBinderType}}({{localName}}[i]{{arguments}}); + for (var i = 0; i < {localName}.Length; i++) + {binderName}[i] = new {member.AsBinderType}({localName}[i]{arguments}); """) .AppendLineIf(i + 1 < membersCount); @@ -368,10 +374,10 @@ private static CodeWriter AppendInitializeInternalEvents(this CodeWriter code) return code.AppendMultiline( $""" - {GeneratedAttribute} + {GeneratedCodeViewAttribute} partial void OnInitializingInternal({IViewModel} viewModel); - {GeneratedAttribute} + {GeneratedCodeViewAttribute} partial void OnInitializedInternal({IViewModel} viewModel); """); } @@ -381,14 +387,14 @@ private static CodeWriter AppendDeinitializeInternalEvents(this CodeWriter code) return code.AppendMultiline( $""" - {GeneratedAttribute} + {GeneratedCodeViewAttribute} partial void OnDeinitializingInternal(); - {GeneratedAttribute} + {GeneratedCodeViewAttribute} partial void OnDeinitializedInternal(); """); } private static string[]? GetBaseTypes(in ViewDataSpan data) => - data.Inheritor is Inheritor.None ? [Classes.IView.ToString()] : null; + data.Inheritor is Inheritor.None ? [IView.ToString()] : null; } \ No newline at end of file diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Views/Data/GenericViewData.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Views/Data/GenericViewData.cs index 66625af..386bc02 100644 --- a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Views/Data/GenericViewData.cs +++ b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Views/Data/GenericViewData.cs @@ -1,7 +1,7 @@ using System; using Microsoft.CodeAnalysis; -namespace Aspid.MVVM.Generators.Views.Data; +namespace Aspid.MVVM.Generators.Generators.Views.Data; public readonly struct GenericViewData(bool isSelf, ITypeSymbol type) : IEquatable { diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Views/Data/Inheritor.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Views/Data/Inheritor.cs index f34b095..9a4d9bd 100644 --- a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Views/Data/Inheritor.cs +++ b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Views/Data/Inheritor.cs @@ -1,7 +1,7 @@ -namespace Aspid.MVVM.Generators.Views.Data; +namespace Aspid.MVVM.Generators.Generators.Views.Data; public enum Inheritor { None, InheritorViewAttribute, -} +} \ No newline at end of file diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Views/Data/Members/AsBinderMember.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Views/Data/Members/AsBinderMember.cs index 4960011..9a3d2c4 100644 --- a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Views/Data/Members/AsBinderMember.cs +++ b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Views/Data/Members/AsBinderMember.cs @@ -1,7 +1,7 @@ using Microsoft.CodeAnalysis; using System.Collections.Generic; -namespace Aspid.MVVM.Generators.Views.Data.Members; +namespace Aspid.MVVM.Generators.Generators.Views.Data.Members; public class AsBinderMember(ISymbol member, string asBinderType, IReadOnlyList? arguments) : CachedBinderMember(member) { diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Views/Data/Members/BinderMember.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Views/Data/Members/BinderMember.cs index ff9ff22..a1d676c 100644 --- a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Views/Data/Members/BinderMember.cs +++ b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Views/Data/Members/BinderMember.cs @@ -1,8 +1,8 @@ using Microsoft.CodeAnalysis; -using Aspid.Generator.Helpers; -using Aspid.MVVM.Generators.Ids.Data; +using Aspid.Generators.Helper; +using Aspid.MVVM.Generators.Generators.Ids.Data; -namespace Aspid.MVVM.Generators.Views.Data.Members; +namespace Aspid.MVVM.Generators.Generators.Views.Data.Members; public class BinderMember { diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Views/Data/Members/CachedBinderMember.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Views/Data/Members/CachedBinderMember.cs index 1bfa836..9c65a86 100644 --- a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Views/Data/Members/CachedBinderMember.cs +++ b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Views/Data/Members/CachedBinderMember.cs @@ -1,7 +1,7 @@ using Microsoft.CodeAnalysis; -using Aspid.Generator.Helpers; +using Aspid.MVVM.Generators.Helpers; -namespace Aspid.MVVM.Generators.Views.Data.Members; +namespace Aspid.MVVM.Generators.Generators.Views.Data.Members; public class CachedBinderMember(ISymbol member) : BinderMember(member) { diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Views/Data/Members/Collections/BinderMembersCollectionSpanByType.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Views/Data/Members/Collections/BinderMembersCollectionSpanByType.cs index e6cfbbe..26b65fb 100644 --- a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Views/Data/Members/Collections/BinderMembersCollectionSpanByType.cs +++ b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Views/Data/Members/Collections/BinderMembersCollectionSpanByType.cs @@ -1,9 +1,9 @@ using System; using System.Linq; -using Aspid.Generator.Helpers; using System.Collections.Immutable; +using Aspid.MVVM.Generators.Helpers; -namespace Aspid.MVVM.Generators.Views.Data.Members.Collections; +namespace Aspid.MVVM.Generators.Generators.Views.Data.Members.Collections; public readonly ref struct BinderMembersCollectionSpanByType { diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Views/Data/ViewData.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Views/Data/ViewData.cs index cccc8d3..4186338 100644 --- a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Views/Data/ViewData.cs +++ b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Views/Data/ViewData.cs @@ -1,20 +1,22 @@ using Microsoft.CodeAnalysis; using System.Collections.Immutable; using Microsoft.CodeAnalysis.CSharp.Syntax; -using Aspid.MVVM.Generators.Views.Data.Members; +using Aspid.MVVM.Generators.Generators.Views.Data.Members; -namespace Aspid.MVVM.Generators.Views.Data; +namespace Aspid.MVVM.Generators.Generators.Views.Data; public readonly struct ViewData( INamedTypeSymbol symbol, - Inheritor inheritor, + Inheritor inheritor, TypeDeclarationSyntax declaration, ImmutableArray members, - ImmutableArray genericViews) + ImmutableArray genericViews, + ImmutableArray inheritedDeclaredIds) { - public readonly INamedTypeSymbol Symbol = symbol; + public readonly INamedTypeSymbol Symbol = symbol; public readonly Inheritor Inheritor = inheritor; public readonly ImmutableArray Members = members; public readonly TypeDeclarationSyntax Declaration = declaration; public readonly ImmutableArray GenericViews = genericViews; + public readonly ImmutableArray InheritedDeclaredIds = inheritedDeclaredIds; } \ No newline at end of file diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Views/Data/ViewDataSpan.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Views/Data/ViewDataSpan.cs index 0eb1720..0c567ea 100644 --- a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Views/Data/ViewDataSpan.cs +++ b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Views/Data/ViewDataSpan.cs @@ -1,10 +1,10 @@ using System; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; -using Aspid.MVVM.Generators.Views.Data.Members; -using Aspid.MVVM.Generators.Views.Data.Members.Collections; +using Aspid.MVVM.Generators.Generators.Views.Data.Members; +using Aspid.MVVM.Generators.Generators.Views.Data.Members.Collections; -namespace Aspid.MVVM.Generators.Views.Data; +namespace Aspid.MVVM.Generators.Generators.Views.Data; public readonly ref struct ViewDataSpan(ViewData viewData) { @@ -14,6 +14,7 @@ public readonly ref struct ViewDataSpan(ViewData viewData) public readonly ReadOnlySpan Members = viewData.Members.AsSpan(); public readonly BinderMembersCollectionSpanByType MembersByType = new(viewData.Members); public readonly ReadOnlySpan GenericViews = viewData.GenericViews.AsSpan(); + public readonly ReadOnlySpan InheritedDeclaredIds = viewData.InheritedDeclaredIds.AsSpan(); public bool IsInstantiateBinders => MembersByType.AsBinders.Length + MembersByType.PropertyBinders.Length > 0; } \ No newline at end of file diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Views/Factories/BinderMembersFactory.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Views/Factories/BinderMembersFactory.cs index 7e4bdca..fa3c1a3 100644 --- a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Views/Factories/BinderMembersFactory.cs +++ b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Views/Factories/BinderMembersFactory.cs @@ -1,34 +1,49 @@ using System.Linq; using Microsoft.CodeAnalysis; -using Aspid.Generator.Helpers; +using Aspid.Generators.Helper; using System.Collections.Generic; using System.Collections.Immutable; using Microsoft.CodeAnalysis.CSharp; -using Aspid.MVVM.Generators.Descriptions; using Microsoft.CodeAnalysis.CSharp.Syntax; -using Aspid.MVVM.Generators.Views.Data.Members; +using Aspid.MVVM.Generators.Generators.Views.Data.Members; +using Classes = Aspid.MVVM.Generators.Generators.Descriptions.Classes; -namespace Aspid.MVVM.Generators.Views.Factories; +namespace Aspid.MVVM.Generators.Generators.Views.Factories; public static class BinderMembersFactory { + public static ImmutableArray CollectInheritedIds(INamedTypeSymbol symbol, SemanticModel semanticModel) + { + var ids = ImmutableArray.CreateBuilder(); + + for (var baseType = symbol.BaseType; baseType is not null; baseType = baseType.BaseType) + { + if (!baseType.HasAnyAttributeInSelf(Classes.ViewAttribute)) continue; + + foreach (var member in Create(baseType, semanticModel)) + ids.Add(member.Id.SourceValue); + } + + return ids.ToImmutable(); + } + public static ImmutableArray Create(INamedTypeSymbol symbolClass, SemanticModel semanticModel) { var binderMembers = new List(); foreach (var member in symbolClass.GetMembers()) { - if (member.HasAnyAttribute(Classes.IgnoreAttribute)) continue; + if (member.HasAnyAttributeInSelf()) continue; var type = GetType(member); if (type is null) continue; - if (member.HasAnyAttribute(out var asBinderAttribute, Classes.AsBinderAttribute)) + if (member.TryGetAnyAttributeInSelf(out var asBinderAttribute, Classes.AsBinderAttribute)) { if (asBinderAttribute!.ConstructorArguments[0].Value is not INamedTypeSymbol argumentType) continue; if (argumentType.IsAbstract) continue; - if (!argumentType.HasInterfaceInSelfOrBases(Classes.IBinder)) continue; + if (!argumentType.HasAnyInterfaceInSelfAndBases(Classes.IBinder)) continue; var arguments = new List(); @@ -93,11 +108,11 @@ invocationExpression.Expression is IdentifierNameSyntax identifier && binderMembers.Add(new AsBinderMember(member, argumentType.ToDisplayStringGlobal(), arguments)); } - else if (type.HasAnyAttribute(Classes.ViewAttribute) || type.HasInterfaceInSelfOrBases(Classes.IView)) + else if (type.HasAnyAttributeInSelf(Classes.ViewAttribute) || type.HasAnyInterfaceInSelfAndBases(Classes.IView)) { - binderMembers.Add(new AsBinderMember(member, Classes.ViewBinder.Global, null)); + binderMembers.Add(new AsBinderMember(member, Classes.ViewBinder, null)); } - else if (type.HasInterfaceInSelfOrBases(Classes.IBinder)) + else if (type.HasAnyInterfaceInSelfAndBases(Classes.IBinder)) { switch (member) { @@ -110,7 +125,7 @@ invocationExpression.Expression is IdentifierNameSyntax identifier && var symbols = GetPropertyReturnSymbols(property, semanticModel); if (symbols is null) continue; - if (symbols.Any(symbol => symbol is IFieldSymbol or IPropertySymbol && !symbol.HasAnyAttribute(Classes.IgnoreAttribute))) continue; + if (symbols.Any(symbol => symbol is IFieldSymbol or IPropertySymbol && !symbol.HasAnyAttributeInSelf(Classes.IgnoreAttribute))) continue; binderMembers.Add(new CachedBinderMember(property)); break; diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Views/Helpers/VirtualBinderFields.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Views/Helpers/VirtualBinderFields.cs new file mode 100644 index 0000000..0c212b4 --- /dev/null +++ b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Views/Helpers/VirtualBinderFields.cs @@ -0,0 +1,177 @@ +using Microsoft.CodeAnalysis; +using System.Collections.Generic; +using Aspid.Generators.Helper; +using Aspid.Generators.Helper.Unity; +using Aspid.MVVM.Generators.Generators.Ids.Data; +using Aspid.MVVM.Generators.Generators.Views.Data; +using Aspid.MVVM.Generators.Generators.ViewModels.Factories; +using Aspid.MVVM.Generators.Generators.ViewModels.Data.Infos; +using Classes = Aspid.MVVM.Generators.Generators.Descriptions.Classes; +using SymbolExtensions = Aspid.MVVM.Generators.Helpers.SymbolExtensions; + +namespace Aspid.MVVM.Generators.Generators.Views.Helpers; + +public static class VirtualBinderFields +{ + public static Dictionary Collect(in ViewDataSpan data) + { + var result = new Dictionary(); + if (data.GenericViews.Length == 0) return result; + if (!IsAutoBinderFieldsEnabled(data.Symbol)) return result; + if (InheritsFromScriptableObject(data.Symbol)) return result; + + var declared = new HashSet(); + foreach (var member in data.Members) + declared.Add(member.Id.SourceValue); + foreach (var id in data.InheritedDeclaredIds) + declared.Add(id); + + foreach (var genericView in data.GenericViews) + { + if (genericView.Type.TypeKind is TypeKind.Interface) continue; + + for (var viewModelType = genericView.Type; viewModelType is not null; viewModelType = viewModelType.BaseType) + { + CollectFromType(viewModelType, data, declared, result); + } + } + + return result; + } + + private static void CollectFromType( + ITypeSymbol viewModelType, + in ViewDataSpan data, + HashSet declared, + Dictionary result) + { + var bindableMembers = BindableMembersFactory.Create(viewModelType, data.Declaration, out _); + if (bindableMembers.Length == 0) return; + + var membersBySymbol = new Dictionary>(SymbolEqualityComparer.Default); + foreach (var bindable in bindableMembers) + { + if (!membersBySymbol.TryGetValue(bindable.Member, out var list)) + { + list = []; + membersBySymbol[bindable.Member] = list; + } + + list.Add(bindable); + } + + string? activeGroup = null; + + foreach (var symbol in viewModelType.GetMembers()) + { + var groupStart = GetAttributeStringArgument(symbol, Classes.HeaderGroupStartAttribute); + var groupName = GetAttributeStringArgument(symbol, Classes.HeaderGroupAttribute); + var groupEnd = HasAttribute(symbol, Classes.HeaderGroupEndAttribute); + + if (groupStart is not null) activeGroup = groupStart; + else if (groupName is not null) activeGroup = groupName; + + if (membersBySymbol.TryGetValue(symbol, out var bindablesForSymbol)) + { + foreach (var bindableMember in bindablesForSymbol) + { + var sourceValue = bindableMember.Id.SourceValue; + if (declared.Contains(sourceValue)) continue; + + if (!result.TryGetValue(sourceValue, out var info)) + { + info = new Info(bindableMember.Id, SymbolExtensions.GetFieldName(sourceValue, "_")); + result[sourceValue] = info; + } + + var requireType = StripNullable(bindableMember.Type); + if (!info.RequireBinderTypes.Contains(requireType)) + info.RequireBinderTypes.Add(requireType); + + if (bindableMember is not BindableCommandInfo) + info.Header ??= GetAttributeStringArgument(symbol, Classes.HeaderAttribute); + + if (activeGroup is not null) + info.HeaderGroup ??= activeGroup; + + if (groupEnd && !info.HeaderGroupEnd) + info.HeaderGroupEnd = true; + } + } + + if (groupEnd) activeGroup = null; + } + } + + private static bool IsAutoBinderFieldsEnabled(INamedTypeSymbol viewSymbol) + { + foreach (var attribute in viewSymbol.GetAttributes()) + { + if (attribute.AttributeClass?.ToDisplayString() != Classes.ViewAttribute.FullName) continue; + + foreach (var named in attribute.NamedArguments) + { + if (named.Key != "AutoBinderFields") continue; + if (named.Value.Value is bool enabled) return enabled; + } + + return true; + } + + return true; + } + + private static bool InheritsFromScriptableObject(INamedTypeSymbol viewSymbol) + { + var scriptableObjectFullName = UnityClasses.ScriptableObject.FullName; + + for (var type = viewSymbol.BaseType; type is not null; type = type.BaseType) + { + if (type.ToDisplayString() == scriptableObjectFullName) return true; + } + + return false; + } + + private static string? GetAttributeStringArgument(ISymbol symbol, AttributeText attributeText) + { + if (symbol.TryGetAnyAttributeInSelf(out var attribute, attributeText)) + { + if (attribute.ConstructorArguments.Length is 0) return null; + + var value = attribute.ConstructorArguments[0].Value as string; + if (!string.IsNullOrWhiteSpace(value)) return value; + } + + return null; + } + + private static bool HasAttribute(ISymbol symbol, string attributeFullName) + { + foreach (var attribute in symbol.GetAttributes()) + { + if (attribute.AttributeClass?.ToDisplayString() == attributeFullName) + return true; + } + + return false; + } + + private static string StripNullable(string type) => + type.EndsWith("?") ? type.Substring(0, type.Length - 1) : type; + + public sealed class Info(IdData id, string fieldName) + { + public IdData Id { get; } = id; + + public string FieldName { get; } = fieldName; + + public List RequireBinderTypes { get; } = []; + + public string? Header { get; set; } + + public string? HeaderGroup { get; set; } + + public bool HeaderGroupEnd { get; set; } + } +} diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Views/ViewGenerator.Find.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Views/ViewGenerator.Find.cs index 10c18a9..846765f 100644 --- a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Views/ViewGenerator.Find.cs +++ b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Views/ViewGenerator.Find.cs @@ -1,36 +1,36 @@ using System.Threading; using System.Diagnostics; using Microsoft.CodeAnalysis; -using Aspid.Generator.Helpers; +using Aspid.Generators.Helper; using System.Collections.Generic; using System.Collections.Immutable; using System.Runtime.CompilerServices; -using Aspid.MVVM.Generators.Views.Data; -using Aspid.MVVM.Generators.Descriptions; using Microsoft.CodeAnalysis.CSharp.Syntax; -using Aspid.MVVM.Generators.Views.Factories; +using Aspid.MVVM.Generators.Generators.Views.Data; +using Aspid.MVVM.Generators.Generators.Views.Factories; +using Classes = Aspid.MVVM.Generators.Generators.Descriptions.Classes; -namespace Aspid.MVVM.Generators.Views; +namespace Aspid.MVVM.Generators.Generators.Views; public partial class ViewGenerator { - private static FoundForGenerator FindView( + private static ViewData? FindView( GeneratorAttributeSyntaxContext context, CancellationToken cancellationToken) { - if (context.TargetSymbol is not INamedTypeSymbol symbol) return default; + if (context.TargetSymbol is not INamedTypeSymbol symbol) return null; - var inheritor = symbol.HasAttributeInBases(Classes.ViewAttribute) + var inheritor = symbol.HasAnyAttributeInBases(Classes.ViewAttribute) ? Inheritor.InheritorViewAttribute : Inheritor.None; var members = BinderMembersFactory.Create(symbol, context.SemanticModel); + var inheritedDeclaredIds = BinderMembersFactory.CollectInheritedIds(symbol, context.SemanticModel); Debug.Assert(context.TargetNode is TypeDeclarationSyntax); var candidate = Unsafe.As(context.TargetNode); - var viewData = new ViewData(symbol, inheritor, candidate, members, GetGenericViews(symbol)); - return new FoundForGenerator(viewData); + return new ViewData(symbol, inheritor, candidate, members, GetGenericViews(symbol), inheritedDeclaredIds); } private static ImmutableArray GetGenericViews(INamedTypeSymbol symbol) diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Views/ViewGenerator.Generate.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Views/ViewGenerator.Generate.cs index 4c78aec..79e266c 100644 --- a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Views/ViewGenerator.Generate.cs +++ b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Views/ViewGenerator.Generate.cs @@ -1,9 +1,9 @@ using Microsoft.CodeAnalysis; -using Aspid.Generator.Helpers; -using Aspid.MVVM.Generators.Views.Body; -using Aspid.MVVM.Generators.Views.Data; +using Aspid.Generators.Helper; +using Aspid.MVVM.Generators.Generators.Views.Body; +using Aspid.MVVM.Generators.Generators.Views.Data; -namespace Aspid.MVVM.Generators.Views; +namespace Aspid.MVVM.Generators.Generators.Views; public partial class ViewGenerator { @@ -13,10 +13,11 @@ private static void GenerateCode(SourceProductionContext context, ViewData data) var declaration = dataSpan.Declaration; var @namespace = declaration.GetNamespaceName(); - var declarationText = declaration.GetDeclarationText(); + var declarationText = new DeclarationText(declaration); InitializeBody.Generate(@namespace, dataSpan, declarationText, context); BinderCachedBody.Generate(@namespace, dataSpan, declarationText, context); + BinderFieldsBody.Generate(@namespace, dataSpan, declarationText, context); GenericInitializeView.Generate(@namespace, dataSpan, declarationText, context); } } \ No newline at end of file diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Views/ViewGenerator.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Views/ViewGenerator.cs index 54b8a13..72fe30e 100644 --- a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Views/ViewGenerator.cs +++ b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Generators/Views/ViewGenerator.cs @@ -1,10 +1,10 @@ using System.Threading; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; -using Aspid.MVVM.Generators.Descriptions; using Microsoft.CodeAnalysis.CSharp.Syntax; +using Aspid.MVVM.Generators.Generators.Descriptions; -namespace Aspid.MVVM.Generators.Views; +namespace Aspid.MVVM.Generators.Generators.Views; [Generator(LanguageNames.CSharp)] public partial class ViewGenerator : IIncrementalGenerator @@ -12,8 +12,8 @@ public partial class ViewGenerator : IIncrementalGenerator public void Initialize(IncrementalGeneratorInitializationContext context) { var provider = context.SyntaxProvider.ForAttributeWithMetadataName(Classes.ViewAttribute.FullName, SyntacticPredicate, FindView) - .Where(foundForSourceGenerator => foundForSourceGenerator.IsNeed) - .Select((foundForSourceGenerator, _) => foundForSourceGenerator.Container); + .Where(foundForSourceGenerator => foundForSourceGenerator.HasValue) + .Select((foundForSourceGenerator, _) => foundForSourceGenerator!.Value); context.RegisterSourceOutput( source: provider, diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Helpers/AttributeText.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Helpers/AttributeText.cs deleted file mode 100644 index 183c698..0000000 --- a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Helpers/AttributeText.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace Aspid.Generator.Helpers; - -public class AttributeText(string name, NamespaceText? @namespace = null) : - TypeText(name + "Attribute", @namespace) -{ - public string AttributeName => name; - - public string AttributeFullName => (Namespace != null ? $"{Namespace}." : "") + AttributeName; - - public string AttributeGlobal => $"global::{AttributeFullName}"; - - public override string ToString() => - AttributeGlobal; - - public static implicit operator string(AttributeText type) => - type.ToString(); -} \ No newline at end of file diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Helpers/Data/CastedSpan.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Helpers/CastedSpan.cs similarity index 95% rename from Aspid.MVVM.Generators/Aspid.MVVM.Generators/Helpers/Data/CastedSpan.cs rename to Aspid.MVVM.Generators/Aspid.MVVM.Generators/Helpers/CastedSpan.cs index 520dd1b..450eeb7 100644 --- a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Helpers/Data/CastedSpan.cs +++ b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Helpers/CastedSpan.cs @@ -1,7 +1,7 @@ using System; using System.Runtime.CompilerServices; -namespace Aspid.Generator.Helpers; +namespace Aspid.MVVM.Generators.Helpers; public ref struct CastedSpan(ReadOnlySpan span) { diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Helpers/CodeWriter.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Helpers/CodeWriter.cs deleted file mode 100644 index 72c76c8..0000000 --- a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Helpers/CodeWriter.cs +++ /dev/null @@ -1,120 +0,0 @@ -using System; -using System.IO; -using System.Text; -using System.CodeDom.Compiler; -using Microsoft.CodeAnalysis.Text; - -namespace Aspid.Generator.Helpers; - -public sealed class CodeWriter -{ - private readonly MemoryStream _sourceStream; - private readonly IndentedTextWriter _textWriter; - - public int Indent - { - get => _textWriter.Indent; - set => _textWriter.Indent = value; - } - - public CodeWriter() - { - _sourceStream = new MemoryStream(); - var sourceStreamWriter = new StreamWriter(_sourceStream, Encoding.UTF8); - _textWriter = new IndentedTextWriter(sourceStreamWriter); - } - - public CodeWriter Append(string value = "") - { - _textWriter.Write(value); - return this; - } - - public CodeWriter AppendLine(string value = "") - { - _textWriter.WriteLine(value); - return this; - } - - public CodeWriter AppendMultiline(string value) - { - var indent = Indent; - Indent = 0; - - var tab = new string('\t', indent); - value = $"{tab}{value}"; - value = value.Replace("\n", $"\n{tab}"); - AppendLine(value); - - Indent = indent; - return this; - } - - public IDisposable BeginIndentScope() => - new IndentScope(this); - - public IDisposable BeginBlockScope() => - new BlockScope(this); - - public CodeWriter BeginBlock() - { - AppendLine("{"); - IncreaseIndent(); - - return this; - } - - public CodeWriter EndBlock() - { - DecreaseIndent(); - AppendLine("}"); - - return this; - } - - public CodeWriter IncreaseIndent() - { - _textWriter.Indent++; - return this; - } - - public CodeWriter DecreaseIndent() - { - _textWriter.Indent--; - return this; - } - - public SourceText GetSourceText() - { - _textWriter.Flush(); - return SourceText.From(_sourceStream, Encoding.UTF8, canBeEmbedded: true); - } - - private readonly struct IndentScope : IDisposable - { - private readonly CodeWriter _source; - - public IndentScope(CodeWriter source) - { - _source = source; - source.IncreaseIndent(); - } - - public void Dispose() => - _source.DecreaseIndent(); - } - - private readonly struct BlockScope : IDisposable - { - private readonly CodeWriter _source; - - public BlockScope(CodeWriter source) - { - _source = source; - source.BeginBlock(); - } - - public void Dispose() => - _source.EndBlock(); - } -} \ No newline at end of file diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Helpers/DeclarationText.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Helpers/DeclarationText.cs deleted file mode 100644 index 751ad04..0000000 --- a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Helpers/DeclarationText.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System.Linq; - -namespace Aspid.Generator.Helpers; - -public readonly struct DeclarationText(string? modifiers, string typeType, string name, string? genericArguments) -{ - public string? Modifiers { get; } = modifiers; - - public string TypeType { get; } = typeType; - - public string Name { get; } = name; - - public string? GenericArguments { get; } = genericArguments; - - public string GetFileName(string? namespaceName, string? postfix) - { - postfix ??= ""; - if (postfix.Length > 0 && postfix[0] != '.') postfix = $".{postfix}"; - - namespaceName = string.IsNullOrEmpty(namespaceName) ? "" : $"{namespaceName}."; - - return namespaceName + (string.IsNullOrEmpty(GenericArguments) - ? $"{Name}{postfix}.g.cs" - : $"{Name}`{GenericArguments!.Count(arg => arg == ',') + 1}{postfix}.g.cs"); - } - - public override string ToString() - { - var modifiers = !string.IsNullOrEmpty(Modifiers) ? $"{Modifiers} " : ""; - var arguments = !string.IsNullOrEmpty(GenericArguments) ? $"<{GenericArguments}>" : ""; - - return $"{modifiers}{TypeType} {Name}{arguments}"; - } - - public static implicit operator string(DeclarationText declaration) => - declaration.ToString(); -} \ No newline at end of file diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Helpers/Extensions/Declarations/CSharpSyntaxNodeExtensions.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Helpers/Extensions/Declarations/CSharpSyntaxNodeExtensions.cs deleted file mode 100644 index 762e70b..0000000 --- a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Helpers/Extensions/Declarations/CSharpSyntaxNodeExtensions.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; - -namespace Aspid.Generator.Helpers; - -public static class CSharpSyntaxNodeExtensions -{ - public static string GetNamespaceName(this CSharpSyntaxNode node) - { - for (var parent = node.Parent; parent != null; parent = parent.Parent) - { - if (parent is BaseNamespaceDeclarationSyntax namespaceDeclaration) - return namespaceDeclaration.Name.ToString(); - } - - return ""; - } -} \ No newline at end of file diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Helpers/Extensions/Declarations/MemberDeclarationSyntaxExtensions.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Helpers/Extensions/Declarations/MemberDeclarationSyntaxExtensions.cs deleted file mode 100644 index 0869f7e..0000000 --- a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Helpers/Extensions/Declarations/MemberDeclarationSyntaxExtensions.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Linq; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp.Syntax; - -namespace Aspid.Generator.Helpers; - -public static class MemberDeclarationSyntaxExtensions -{ - public static bool HasAttribute(this MemberDeclarationSyntax declaration, SemanticModel semanticModel, string name) - { - foreach (var attribute in declaration.AttributeLists.SelectMany(attributeList => attributeList.Attributes)) - { - if (semanticModel.GetSymbolInfo(attribute).Symbol is not IMethodSymbol attributeSymbol) continue; - if (attributeSymbol.ContainingType?.ToDisplayString() == name) return true; - } - - return false; - } -} \ No newline at end of file diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Helpers/Extensions/Declarations/PropertyDeclarationSyntaxExtensions.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Helpers/Extensions/Declarations/PropertyDeclarationSyntaxExtensions.cs deleted file mode 100644 index fabb772..0000000 --- a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Helpers/Extensions/Declarations/PropertyDeclarationSyntaxExtensions.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.Linq; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; - -namespace Aspid.Generator.Helpers; - -public static class PropertyDeclarationSyntaxExtensions -{ - public static bool HasGetAccessor(this PropertyDeclarationSyntax property) => - property.HasAccessor(SyntaxKind.GetAccessorDeclaration); - - public static bool HasSetAccessor(this PropertyDeclarationSyntax property) => - property.HasAccessor(SyntaxKind.SetAccessorDeclaration); - - public static bool HasInitAccessor(this PropertyDeclarationSyntax property) => - property.HasAccessor(SyntaxKind.InitAccessorDeclaration); - - private static bool HasAccessor(this PropertyDeclarationSyntax propertyDeclaration, SyntaxKind accessorKind) - { - var accessorList = propertyDeclaration.AccessorList; - return accessorList != null && accessorList.Accessors.Any(accessor => accessor.Kind() == accessorKind); - } -} \ No newline at end of file diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Helpers/Extensions/Declarations/TypeDeclarationSyntaxExtensions.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Helpers/Extensions/Declarations/TypeDeclarationSyntaxExtensions.cs deleted file mode 100644 index 884392c..0000000 --- a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Helpers/Extensions/Declarations/TypeDeclarationSyntaxExtensions.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System.Text; -using Microsoft.CodeAnalysis.CSharp.Syntax; - -namespace Aspid.Generator.Helpers; - -public static class TypeDeclarationSyntaxExtensions -{ - public static DeclarationText GetDeclarationText(this TypeDeclarationSyntax declaration) - { - var modifiers = declaration.GetModifiersAsText(); - var typeType = declaration is ClassDeclarationSyntax ? "class" : "struct"; - var typeName = declaration.Identifier.Text; - var genericArguments = declaration.GetGenericArgumentsAsText(); - - return new DeclarationText(modifiers, typeType, typeName, genericArguments); - } - - private static string GetModifiersAsText(this TypeDeclarationSyntax declaration) - { - var modifiers = new StringBuilder(); - foreach (var modifier in declaration.Modifiers) - modifiers.Append(modifier.ToString() + " "); - - modifiers.Length--; - return modifiers.ToString(); - } - - private static string GetGenericArgumentsAsText(this TypeDeclarationSyntax declaration) - { - var types = declaration.TypeParameterList; - if (types == null || types.Parameters.Count == 0) return ""; - - var genericTypes = new StringBuilder(); - foreach (var type in types.Parameters) - { - if (genericTypes.Length > 0) - genericTypes.Append(", "); - - genericTypes.Append(type.Identifier.Text); - } - - return genericTypes.ToString(); - } -} \ No newline at end of file diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Helpers/Extensions/Symbols/MethodSymbolExtensions.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Helpers/Extensions/Symbols/MethodSymbolExtensions.cs deleted file mode 100644 index 3ed60a4..0000000 --- a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Helpers/Extensions/Symbols/MethodSymbolExtensions.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System.Linq; -using Microsoft.CodeAnalysis; - -namespace Aspid.Generator.Helpers; - -public static class MethodSymbolExtensions -{ - public static bool EqualsSignature(this IMethodSymbol method1, IMethodSymbol method2) - { - if (method1.Parameters.Length != method2.Parameters.Length) return false; - - var method1Name = method1.NameFromExplicitImplementation(); - var method2Name = method2.NameFromExplicitImplementation(); - if (method1Name != method2Name) return false; - - if (!SymbolEqualityComparer.Default.Equals(method1.ReturnType, method2.ReturnType)) return false; - - var areParametersEqual = method1.Parameters - .Where((parameter, i) => SymbolEqualityComparer.Default.Equals(parameter.Type, method2.Parameters[i].Type)) - .Any(); - - return areParametersEqual; - } - - public static string NameFromExplicitImplementation(this IMethodSymbol method) => - method.Name.Substring(method.Name.LastIndexOf('.') + 1); -} \ No newline at end of file diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Helpers/Extensions/Symbols/SymbolExtensions.Attribute.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Helpers/Extensions/Symbols/SymbolExtensions.Attribute.cs deleted file mode 100644 index 4eed3fd..0000000 --- a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Helpers/Extensions/Symbols/SymbolExtensions.Attribute.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System.Linq; -using Microsoft.CodeAnalysis; -using System.Collections.Generic; - -namespace Aspid.Generator.Helpers; - -public static partial class SymbolExtensions -{ - public static bool HasAnyAttribute(this ISymbol symbol, params IReadOnlyCollection attributeText) => - symbol.HasAnyAttribute(attributeText.Select(attribute => attribute.FullName).ToArray()); - - public static bool HasAnyAttribute(this ISymbol symbol, params IReadOnlyCollection attributeFullName) - { - foreach (var attribute in symbol.GetAttributes()) - { - if (attribute.AttributeClass != null && attributeFullName.Any(name => name == attribute.AttributeClass.ToDisplayString())) - return true; - } - - return false; - } - - public static bool HasAnyAttribute(this ISymbol symbol, out AttributeData? foundAttribute, params IReadOnlyCollection attributeText) => - symbol.HasAnyAttribute(out foundAttribute, attributeText.Select(attribute => attribute.FullName).ToArray()); - - public static bool HasAnyAttribute(this ISymbol symbol, out AttributeData? foundAttribute, params IReadOnlyCollection attributeFullName) - { - foundAttribute = null; - - foreach (var attribute in symbol.GetAttributes()) - { - if (attribute.AttributeClass != null && attributeFullName.Any(name => name == attribute.AttributeClass.ToDisplayString())) - { - foundAttribute = attribute; - return true; - } - } - - return false; - } -} \ No newline at end of file diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Helpers/Extensions/Symbols/TypeSymbolExtensions.Attribute.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Helpers/Extensions/Symbols/TypeSymbolExtensions.Attribute.cs deleted file mode 100644 index eb6d80f..0000000 --- a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Helpers/Extensions/Symbols/TypeSymbolExtensions.Attribute.cs +++ /dev/null @@ -1,64 +0,0 @@ -using Microsoft.CodeAnalysis; - -namespace Aspid.Generator.Helpers; - -public static partial class TypeSymbolExtensions -{ - public static bool HasAttributeInBases(this ITypeSymbol typeSymbol, AttributeText attributeText) => - HasAttributeInBases(typeSymbol, attributeText.FullName); - - public static bool HasAttributeInBases(this ITypeSymbol symbol, string fullName) - { - for (var type = symbol.BaseType; type is not null; type = type.BaseType) - { - if (type.HasAnyAttribute(fullName)) - return true; - } - - return false; - } - - public static bool HasAttributeInBases(this ITypeSymbol typeSymbol, AttributeText attributeText, out AttributeData? attribute) => - HasAttributeInBases(typeSymbol, attributeText.FullName, out attribute); - - public static bool HasAttributeInBases(this ITypeSymbol symbol, string fullName, out AttributeData? attribute) - { - for (var type = symbol.BaseType; type is not null; type = type.BaseType) - { - if (type.HasAnyAttribute(out attribute, fullName)) - return true; - } - - attribute = null; - return false; - } - - public static bool HasAttributeInSelfOrBases(this ITypeSymbol typeSymbol, AttributeText attributeText) => - HasAttributeInSelfOrBases(typeSymbol, attributeText.FullName); - - public static bool HasAttributeInSelfOrBases(this ITypeSymbol symbol, string fullName) - { - for (var type = symbol; type is not null; type = type.BaseType) - { - if (type.HasAnyAttribute(fullName)) - return true; - } - - return false; - } - - public static bool HasAttributeInSelfOrBases(this ITypeSymbol typeSymbol, AttributeText attributeText, out AttributeData? attribute) => - HasAttributeInSelfOrBases(typeSymbol, attributeText.FullName, out attribute); - - public static bool HasAttributeInSelfOrBases(this ITypeSymbol symbol, string fullName, out AttributeData? attribute) - { - for (var type = symbol; type is not null; type = type.BaseType) - { - if (type.HasAnyAttribute(out attribute, fullName)) - return true; - } - - attribute = null; - return false; - } -} \ No newline at end of file diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Helpers/Extensions/Symbols/TypeSymbolExtensions.BaseType.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Helpers/Extensions/Symbols/TypeSymbolExtensions.BaseType.cs deleted file mode 100644 index f2a18e9..0000000 --- a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Helpers/Extensions/Symbols/TypeSymbolExtensions.BaseType.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System.Linq; -using Microsoft.CodeAnalysis; - -namespace Aspid.Generator.Helpers; - -public static partial class TypeSymbolExtensions -{ - public static bool HasBaseType(this ITypeSymbol symbol, TypeText typeText) => - HasBaseType(symbol, typeText.FullName); - - public static bool HasBaseType(this ITypeSymbol symbol, string baseTypeName) => - HasBaseType(symbol, baseTypeName, out _); - - public static bool HasBaseType(this ITypeSymbol symbol, TypeText typeText, out ITypeSymbol? foundBaseType) => - HasBaseType(symbol, typeText.FullName, out foundBaseType); - - public static bool HasBaseType(this ITypeSymbol symbol, string baseTypeName, out ITypeSymbol? foundBaseType) - { - foundBaseType = null; - - for (var type = symbol; type != null; type = type.BaseType) - { - if (type.ToDisplayString() != baseTypeName) continue; - - foundBaseType = type; - return true; - } - - return false; - } - - public static bool HasBaseType(this ITypeSymbol symbol, params TypeText[] baseTypeNames) => - HasBaseType(symbol, baseTypeNames.Select(baseTypeName => baseTypeName.FullName).ToArray()); - - public static bool HasBaseType(this ITypeSymbol symbol, params string[] baseTypeNames) - { - for (var type = symbol; type != null; type = type.BaseType) - { - if (baseTypeNames.Any(baseTypeName => type.ToDisplayString() == baseTypeName)) - { - return true; - } - } - - return false; - } -} \ No newline at end of file diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Helpers/Extensions/Symbols/TypeSymbolExtensions.Interface.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Helpers/Extensions/Symbols/TypeSymbolExtensions.Interface.cs deleted file mode 100644 index 5bbbf87..0000000 --- a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Helpers/Extensions/Symbols/TypeSymbolExtensions.Interface.cs +++ /dev/null @@ -1,82 +0,0 @@ -using System.Linq; -using Microsoft.CodeAnalysis; - -namespace Aspid.Generator.Helpers; - -public static partial class TypeSymbolExtensions -{ - public static bool HasInterface(this ITypeSymbol type, TypeText typeText) => - type.HasInterface(typeText.FullName); - - public static bool HasInterface(this ITypeSymbol type, string name) => - type.HasInterface(name, out _); - - public static bool HasInterface(this ITypeSymbol type, TypeText typeText, out INamedTypeSymbol? foundInterface) => - type.HasInterface(typeText.FullName, out foundInterface); - - public static bool HasInterface(this ITypeSymbol type, string name, out INamedTypeSymbol? foundInterface) - { - foundInterface = null; - - foreach (var @interface in type.Interfaces) - { - if (@interface.ToDisplayString() != name) continue; - - foundInterface = @interface; - return true; - } - - return false; - } - - public static bool HasInterfaceInBases(this ITypeSymbol type, TypeText typeText) => - type.HasInterfaceInBases(typeText.FullName); - - public static bool HasInterfaceInBases(this ITypeSymbol type, string name) => - type.HasInterfaceInBases(name, out _); - - public static bool HasInterfaceInBases(this ITypeSymbol type, TypeText typeText, out INamedTypeSymbol? foundInterface) => - type.HasInterfaceInBases(typeText.FullName, out foundInterface); - - public static bool HasInterfaceInBases(this ITypeSymbol type, string name, out INamedTypeSymbol? foundInterface) - { - foundInterface = null; - - var baseType = type.BaseType; - if (baseType is null) return false; - - foreach (var @interface in baseType.AllInterfaces) - { - if (@interface.ToDisplayString() != name) continue; - - foundInterface = @interface; - return true; - } - - return false; - } - - public static bool HasInterfaceInSelfOrBases(this ITypeSymbol type, TypeText typeText) => - type.HasInterfaceInSelfOrBases(typeText.FullName); - - public static bool HasInterfaceInSelfOrBases(this ITypeSymbol type, string name) => - type.AllInterfaces.Any(@interface => @interface.ToDisplayString() == name); - - public static bool HasInterfaceInSelfOrBases(this ITypeSymbol type, TypeText typeText, out INamedTypeSymbol? foundInterface) => - type.HasInterfaceInSelfOrBases(typeText.FullName, out foundInterface); - - public static bool HasInterfaceInSelfOrBases(this ITypeSymbol type, string name, out INamedTypeSymbol? foundInterface) - { - foundInterface = null; - - foreach (var @interface in type.AllInterfaces) - { - if (@interface.ToDisplayString() != name) continue; - - foundInterface = @interface; - return true; - } - - return false; - } -} \ No newline at end of file diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Helpers/Extensions/Writer/CodeWriteClassExtension.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Helpers/Extensions/Writer/CodeWriteClassExtension.cs deleted file mode 100644 index 24f5e36..0000000 --- a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Helpers/Extensions/Writer/CodeWriteClassExtension.cs +++ /dev/null @@ -1,81 +0,0 @@ -using System; - -namespace Aspid.Generator.Helpers; - -public static class CodeWriteClassExtension -{ - public static CodeWriter AppendClass( - this CodeWriter code, - string @namespace, - DeclarationText declaration, - Action? body, params string[]? baseTypes) - { - code.AppendClassBegin(@namespace, declaration, baseTypes); - body?.Invoke(); - code.AppendClassEnd(@namespace); - - return code; - } - - public static CodeWriter AppendClassBegin( - this CodeWriter code, - string? @namespace, - DeclarationText declaration, - params string[]? baseTypes) - { - var hasNamespace = !string.IsNullOrEmpty(@namespace); - - var baseTypesText = ""; - if (baseTypes is { Length: > 0 }) baseTypesText = $" : {string.Join(",", baseTypes!)}"; - - code.AppendLine("// ") - .AppendChildIf(hasNamespace, () => code - .AppendLine($"namespace {@namespace}") - .BeginBlock()) - .AppendLine($"{declaration}{baseTypesText}") - .BeginBlock(); - - return code; - } - - public static CodeWriter AppendClassBegin( - this CodeWriter code, - string[] imports, - string? @namespace, - DeclarationText declaration, - params string[]? baseTypes) - { - var hasNamespace = !string.IsNullOrEmpty(@namespace); - - var baseTypesText = ""; - if (baseTypes is { Length: > 0 }) baseTypesText = $" : {string.Join(", ", baseTypes!)}"; - - code.AppendLine("// ") - .AppendLine() - .AppendLoop(imports, import => - { - code.AppendLine($"using {import};"); - }) - .AppendLine() - .AppendChildIf(hasNamespace, () => code - .AppendLine($"namespace {@namespace}") - .BeginBlock()) - .AppendLine($"{declaration}{baseTypesText}") - .BeginBlock(); - - return code; - } - - public static CodeWriter AppendClassEnd(this CodeWriter code, string? @namespace) - { - code.AppendClassEnd(!string.IsNullOrEmpty(@namespace)); - return code; - } - - private static CodeWriter AppendClassEnd(this CodeWriter code, bool hasNamespace) - { - code.EndBlock() - .EndBlockIf(hasNamespace); - return code; - } -} \ No newline at end of file diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Helpers/Extensions/Writer/CodeWriteLoopExtensions.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Helpers/Extensions/Writer/CodeWriteLoopExtensions.cs deleted file mode 100644 index f3390bb..0000000 --- a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Helpers/Extensions/Writer/CodeWriteLoopExtensions.cs +++ /dev/null @@ -1,91 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Aspid.Generator.Helpers; - -public static class CodeWriteLoopExtensions -{ - public static CodeWriter AppendLoop(this CodeWriter code, IEnumerable enumerable, Action setValue) - { - foreach (var value in enumerable) - setValue(value); - - return code; - } - - public static CodeWriter AppendLoop(this CodeWriter code, IEnumerable enumerable, Action setValue) - { - var i = 0; - - foreach (var value in enumerable) - { - setValue(i, value); - i++; - } - - return code; - } - - public static CodeWriter AppendLoop(this CodeWriter code, IEnumerable enumerable, Action setValue) - { - foreach (var value in enumerable) - setValue(code, value); - - return code; - } - - public static CodeWriter AppendLoop(this CodeWriter code, IEnumerable enumerable, Action setValue) - { - var i = 0; - - foreach (var value in enumerable) - { - setValue(code, i, value); - i++; - } - - return code; - } - - public static CodeWriter AppendLoop(this CodeWriter code, ReadOnlySpan span, Action setValue) - { - foreach (var value in span) - setValue(value); - - return code; - } - - public static CodeWriter AppendLoop(this CodeWriter code, ReadOnlySpan span, Action setValue) - { - var i = 0; - - foreach (var value in span) - { - setValue(i, value); - i++; - } - - return code; - } - - public static CodeWriter AppendLoop(this CodeWriter code, ReadOnlySpan span, Action setValue) - { - foreach (var value in span) - setValue(code, value); - - return code; - } - - public static CodeWriter AppendLoop(this CodeWriter code, ReadOnlySpan span, Action setValue) - { - var i = 0; - - foreach (var value in span) - { - setValue(code, i, value); - i++; - } - - return code; - } -} \ No newline at end of file diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Helpers/Extensions/Writer/CodeWriterIfExtensions.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Helpers/Extensions/Writer/CodeWriterIfExtensions.cs deleted file mode 100644 index 54346be..0000000 --- a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Helpers/Extensions/Writer/CodeWriterIfExtensions.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System; - -namespace Aspid.Generator.Helpers; - -public static class CodeWriterIfExtensions -{ - public static CodeWriter AppendIf(this CodeWriter code, Func condition, string value = "") => - code.AppendIf(condition.Invoke(), value); - - public static CodeWriter AppendIf(this CodeWriter code, bool flag, string value = "") => - !flag ? code : code.Append(value); - - public static CodeWriter AppendLineIf(this CodeWriter code, Func condition, string value = "") => - code.AppendLineIf(condition.Invoke(), value); - - public static CodeWriter AppendLineIf(this CodeWriter code, bool flag, string value = "") => - !flag ? code : code.AppendLine(value); - - public static CodeWriter AppendMultilineIf(this CodeWriter code, Func condition, string value = "") => - code.AppendMultilineIf(condition.Invoke(), value); - - public static CodeWriter AppendMultilineIf(this CodeWriter code, bool flag, string value = "") => - !flag ? code : code.AppendMultiline(value); - - public static CodeWriter AppendChildIf(this CodeWriter code, Func condition, Func childFunctions) => - code.AppendChildIf(condition.Invoke(), childFunctions); - - public static CodeWriter AppendChildIf(this CodeWriter code, bool flag, Func childFunctions) => - !flag ? code : childFunctions.Invoke(); - - public static CodeWriter BeginBlockIf(this CodeWriter code, Func condition) => - code.BeginBlockIf(condition.Invoke()); - - public static CodeWriter BeginBlockIf(this CodeWriter code, bool flag) => - !flag ? code : code.BeginBlock(); - - public static CodeWriter EndBlockIf(this CodeWriter code, Func condition) => - code.EndBlockIf(condition.Invoke()); - - public static CodeWriter EndBlockIf(this CodeWriter code, bool flag) => - !flag ? code : code.EndBlock(); -} \ No newline at end of file diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Helpers/FoundForGenerator.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Helpers/FoundForGenerator.cs deleted file mode 100644 index 5a8bfa0..0000000 --- a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Helpers/FoundForGenerator.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Aspid.Generator.Helpers; - -public readonly struct FoundForGenerator -{ - public readonly bool IsNeed; - public readonly T Container; - - public FoundForGenerator(T container) - { - IsNeed = true; - Container = container; - } -} \ No newline at end of file diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Helpers/Data/MembersByGroup.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Helpers/MembersByGroup.cs similarity index 97% rename from Aspid.MVVM.Generators/Aspid.MVVM.Generators/Helpers/Data/MembersByGroup.cs rename to Aspid.MVVM.Generators/Aspid.MVVM.Generators/Helpers/MembersByGroup.cs index ffff928..f28c590 100644 --- a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Helpers/Data/MembersByGroup.cs +++ b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Helpers/MembersByGroup.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Collections.Immutable; -namespace Aspid.Generator.Helpers; +namespace Aspid.MVVM.Generators.Helpers; public readonly struct MembersByGroup { diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Helpers/NamespaceText.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Helpers/NamespaceText.cs deleted file mode 100644 index b9f79a9..0000000 --- a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Helpers/NamespaceText.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Aspid.Generator.Helpers; - -public sealed class NamespaceText(string name, NamespaceText? parent = null) -{ - public string Name { get; } = (parent != null ? $"{parent}." : "") + name; - - public override string ToString() => Name; - - public static implicit operator string(NamespaceText @namespace) => - @namespace.ToString(); -} \ No newline at end of file diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Helpers/Extensions/Symbols/SymbolExtensions.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Helpers/SymbolExtensions.cs similarity index 55% rename from Aspid.MVVM.Generators/Aspid.MVVM.Generators/Helpers/Extensions/Symbols/SymbolExtensions.cs rename to Aspid.MVVM.Generators/Aspid.MVVM.Generators/Helpers/SymbolExtensions.cs index beabc6a..21ec8e4 100644 --- a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Helpers/Extensions/Symbols/SymbolExtensions.cs +++ b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Helpers/SymbolExtensions.cs @@ -1,28 +1,9 @@ using Microsoft.CodeAnalysis; -namespace Aspid.Generator.Helpers; +namespace Aspid.MVVM.Generators.Helpers; -public static partial class SymbolExtensions +public static class SymbolExtensions { - public static string ToDisplayStringGlobal(this ISymbol symbol) => - symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); - - public static ITypeSymbol? GetSymbolType(this ISymbol symbol) => symbol switch - { - ITypeSymbol type => type, - IFieldSymbol field => field.Type, - ILocalSymbol local => local.Type, - IEventSymbol @event => @event.Type, - IDiscardSymbol discard => discard.Type, - - // TODO Delete - IMethodSymbol method => method.ReturnType, - - IPropertySymbol property => property.Type, - IParameterSymbol parameter => parameter.Type, - _ => null - }; - public static string GetFieldName(this ISymbol member, in string? prefix = "_") => GetFieldName(member.Name, prefix); @@ -59,14 +40,17 @@ public static string RemoveFieldPrefix(this ISymbol member) => public static string RemoveFieldPrefix(in string name) { - var prefixCount = name.StartsWith("_") + var prefixCount = name.StartsWith("_") ? 1 - : name.StartsWith("m_") || name.StartsWith("s_") - ? 2 + : name.StartsWith("m_") || name.StartsWith("s_") + ? 2 : 0; - - return prefixCount > 0 - ? name.Remove(0, prefixCount) + + return prefixCount > 0 + ? name.Remove(0, prefixCount) : name; } + + public static string CapitalizeFirstLetter(this string name) => + name.Length == 0 ? name : char.ToUpper(name[0]) + name.Substring(1); } \ No newline at end of file diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Helpers/TypeText.cs b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Helpers/TypeText.cs deleted file mode 100644 index e4f0dfe..0000000 --- a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Helpers/TypeText.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace Aspid.Generator.Helpers; - -public class TypeText(string name, NamespaceText? @namespace = null) -{ - public string Name { get; } = name; - - public NamespaceText? Namespace { get; } = @namespace; - - public string FullName => (Namespace != null ? $"{Namespace}." : "") + Name; - - public string Global => $"global::{FullName}"; - - public override string ToString() => - Global; - - public static implicit operator string(TypeText? type) => - type.ToString(); -} \ No newline at end of file diff --git a/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Properties/launchSettings.json b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Properties/launchSettings.json new file mode 100644 index 0000000..dc7f06f --- /dev/null +++ b/Aspid.MVVM.Generators/Aspid.MVVM.Generators/Properties/launchSettings.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "DebugRoslynSourceGenerator": { + "commandName": "DebugRoslynComponent", + "targetProject": "../Aspid.MVVM.Generators.Sample/Aspid.MVVM.Generators.Sample.csproj" + } + } +} \ No newline at end of file diff --git a/Directory.Build.targets b/Directory.Build.targets new file mode 100644 index 0000000..93f892a --- /dev/null +++ b/Directory.Build.targets @@ -0,0 +1,11 @@ + + + + + <_UnityDestination>$(MSBuildThisFileDirectory)../Aspid.MVVM/Assets/Aspid/MVVM/ + + + + + +