diff --git a/src/Maui.BindableProperty.Generator/CodeWriter/CodeWriter.cs b/src/Maui.BindableProperty.Generator/CodeWriter/CodeWriter.cs index ddab2f0..d80d21e 100644 --- a/src/Maui.BindableProperty.Generator/CodeWriter/CodeWriter.cs +++ b/src/Maui.BindableProperty.Generator/CodeWriter/CodeWriter.cs @@ -5,191 +5,190 @@ using System.Text; // Based on https://github.com/SaladLab/CodeWriter -namespace Maui.BindableProperty.Generator +namespace Maui.BindableProperty.Generator; + +public class CodeWriter { - public class CodeWriter - { - private readonly CodeWriterSettings _settings; - private readonly StringBuilder _sb; - private int _indent; - private bool _newLineOnBlockEnd; + private readonly CodeWriterSettings _settings; + private readonly StringBuilder _sb; + private int _indent; + private bool _newLineOnBlockEnd; - public CodeWriterSettings Settings => _settings; - public int Indent => _indent; - public List HeadLines { get; set; } + public CodeWriterSettings Settings => _settings; + public int Indent => _indent; + public List HeadLines { get; set; } - public CodeWriter(CodeWriterSettings settings) - { - _settings = settings; - _sb = new StringBuilder(); - _indent = 0; - } + public CodeWriter(CodeWriterSettings settings) + { + _settings = settings; + _sb = new StringBuilder(); + _indent = 0; + } - public void WriteRaw(string str = null) + public void WriteRaw(string str = null) + { + if (str != null) + _sb.Append(str); + } + + public void Write(string str = null) + { + if (_newLineOnBlockEnd) { if (str != null) - _sb.Append(str); + _sb.Append(_settings.NewLine); + + _newLineOnBlockEnd = false; } - public void Write(string str = null) - { - if (_newLineOnBlockEnd) - { - if (str != null) - _sb.Append(_settings.NewLine); + WriteInternal(str); + } - _newLineOnBlockEnd = false; - } + public void Write(string str, params string[] strs) + { + Write(str); + foreach (var s in strs) + Write(s); + } - WriteInternal(str); + private void WriteInternal(string str = null) + { + if (str != null) + { + _sb.Append(GetIndentString()); + _sb.Append(str); + _sb.Append(_settings.NewLine); } - - public void Write(string str, params string[] strs) + else { - Write(str); - foreach (var s in strs) - Write(s); + _sb.Append(_settings.NewLine); } + } - private void WriteInternal(string str = null) + public UsingHandle OpenBlock(string[] strs, bool newLineAfterBlockEnd = false) + { + if (_newLineOnBlockEnd) { - if (str != null) - { - _sb.Append(GetIndentString()); - _sb.Append(str); - _sb.Append(_settings.NewLine); - } - else - { - _sb.Append(_settings.NewLine); - } + _sb.Append(_settings.NewLine); + _newLineOnBlockEnd = false; } - public UsingHandle OpenBlock(string[] strs, bool newLineAfterBlockEnd = false) + if (strs.Any()) { - if (_newLineOnBlockEnd) + for (var i = 0; i < strs.Length; i++) { - _sb.Append(_settings.NewLine); - _newLineOnBlockEnd = false; + _sb.Append(GetIndentString(i == 0 ? 0 : 1)); + _sb.Append(strs[i]); + if (i < strs.Length - 1) + _sb.Append(_settings.NewLine); } - if (strs.Any()) + if (_settings.NewLineBeforeBlockBegin) { - for (var i = 0; i < strs.Length; i++) - { - _sb.Append(GetIndentString(i == 0 ? 0 : 1)); - _sb.Append(strs[i]); - if (i < strs.Length - 1) - _sb.Append(_settings.NewLine); - } - - if (_settings.NewLineBeforeBlockBegin) - { - _sb.Append(_settings.NewLineBeforeBlockBegin ? _settings.NewLine : " "); - Write(_settings.BlockBegin); - } - else - { - _sb.Append(" "); - _sb.Append(_settings.BlockBegin); - _sb.Append(_settings.NewLine); - } + _sb.Append(_settings.NewLineBeforeBlockBegin ? _settings.NewLine : " "); + Write(_settings.BlockBegin); } else { - Write(_settings.BlockBegin); + _sb.Append(" "); + _sb.Append(_settings.BlockBegin); + _sb.Append(_settings.NewLine); } - - IncIndent(); - return new UsingHandle(() => - { - DecIndent(); - WriteInternal(_settings.BlockEnd); - - _newLineOnBlockEnd = newLineAfterBlockEnd; - }); } - - public UsingHandle OpenIndent(string begin, string end, bool newLineAfterBlockEnd = false) + else { - if (_newLineOnBlockEnd) - { - _sb.Append(_settings.NewLine); - _newLineOnBlockEnd = false; - } + Write(_settings.BlockBegin); + } - if (begin != null) - { - _sb.Append(GetIndentString()); - _sb.Append(begin); - _sb.Append(_settings.NewLine); - } + IncIndent(); + return new UsingHandle(() => + { + DecIndent(); + WriteInternal(_settings.BlockEnd); - IncIndent(); - return new UsingHandle(() => - { - DecIndent(); - if (end != null) - WriteInternal(end); + _newLineOnBlockEnd = newLineAfterBlockEnd; + }); + } - _newLineOnBlockEnd = newLineAfterBlockEnd; - }); + public UsingHandle OpenIndent(string begin, string end, bool newLineAfterBlockEnd = false) + { + if (_newLineOnBlockEnd) + { + _sb.Append(_settings.NewLine); + _newLineOnBlockEnd = false; } - public void IncIndent() + if (begin != null) { - _indent += 1; + _sb.Append(GetIndentString()); + _sb.Append(begin); + _sb.Append(_settings.NewLine); } - public void DecIndent() + IncIndent(); + return new UsingHandle(() => { - if (_indent == 0) - throw new InvalidOperationException("Cannot decrease indent."); + DecIndent(); + if (end != null) + WriteInternal(end); - _indent -= 1; - } + _newLineOnBlockEnd = newLineAfterBlockEnd; + }); + } - public string GetIndentString(int additional = 0) - { - return string.Concat(Enumerable.Repeat(_settings.Indent, _indent + additional)); - } + public void IncIndent() + { + _indent += 1; + } - public override string ToString() - { - var headComment = HeadLines != null - ? string.Join(_settings.NewLine, HeadLines) + _settings.NewLine - : ""; + public void DecIndent() + { + if (_indent == 0) + throw new InvalidOperationException("Cannot decrease indent."); + + _indent -= 1; + } + + public string GetIndentString(int additional = 0) + { + return string.Concat(Enumerable.Repeat(_settings.Indent, _indent + additional)); + } + + public override string ToString() + { + var headComment = HeadLines != null + ? string.Join(_settings.NewLine, HeadLines) + _settings.NewLine + : ""; - var text = headComment + _sb.ToString(); - if (_settings.TranslationMapping != null) + var text = headComment + _sb.ToString(); + if (_settings.TranslationMapping != null) + { + foreach (var i in _settings.TranslationMapping) { - foreach (var i in _settings.TranslationMapping) - { - text = text.Replace(i.Key, i.Value); - } + text = text.Replace(i.Key, i.Value); } - return text; } + return text; + } - public bool WriteAllText(string path, bool skipNotChanged = true) - { - return WriteAllText(path, skipNotChanged, Encoding.UTF8); - } + public bool WriteAllText(string path, bool skipNotChanged = true) + { + return WriteAllText(path, skipNotChanged, Encoding.UTF8); + } - public bool WriteAllText(string path, bool skipNotChanged, Encoding encoding) - { - var text = ToString(); + public bool WriteAllText(string path, bool skipNotChanged, Encoding encoding) + { + var text = ToString(); - if (skipNotChanged && File.Exists(path)) + if (skipNotChanged && File.Exists(path)) + { + var existingText = File.ReadAllText(path); + if (existingText == text) { - var existingText = File.ReadAllText(path); - if (existingText == text) - { - return false; - } + return false; } - File.WriteAllText(path, text, encoding); - return true; } + File.WriteAllText(path, text, encoding); + return true; } } diff --git a/src/Maui.BindableProperty.Generator/CodeWriter/CodeWriterExtensions.cs b/src/Maui.BindableProperty.Generator/CodeWriter/CodeWriterExtensions.cs index c0bc239..75c5d0c 100644 --- a/src/Maui.BindableProperty.Generator/CodeWriter/CodeWriterExtensions.cs +++ b/src/Maui.BindableProperty.Generator/CodeWriter/CodeWriterExtensions.cs @@ -1,38 +1,37 @@ #pragma warning disable SA1300 // Element must begin with upper-case letter // Based on https://github.com/SaladLab/CodeWriter -namespace Maui.BindableProperty.Generator +namespace Maui.BindableProperty.Generator; + +public static class CodeWriterExtensions { - public static class CodeWriterExtensions + public static void _(this CodeWriter w, string str = null) { - public static void _(this CodeWriter w, string str = null) - { - w.Write(str); - } + w.Write(str); + } - public static void _(this CodeWriter w, string str, params string[] strs) - { - w.Write(str, strs); - } + public static void _(this CodeWriter w, string str, params string[] strs) + { + w.Write(str, strs); + } - public static UsingHandle b(this CodeWriter w, params string[] strs) - { - return w.OpenBlock(strs, newLineAfterBlockEnd: false); - } + public static UsingHandle b(this CodeWriter w, params string[] strs) + { + return w.OpenBlock(strs, newLineAfterBlockEnd: false); + } - public static UsingHandle B(this CodeWriter w, params string[] strs) - { - return w.OpenBlock(strs, newLineAfterBlockEnd: true); - } + public static UsingHandle B(this CodeWriter w, params string[] strs) + { + return w.OpenBlock(strs, newLineAfterBlockEnd: true); + } - public static UsingHandle i(this CodeWriter w, string begin = null, string end = null) - { - return w.OpenIndent(begin, end, newLineAfterBlockEnd: false); - } + public static UsingHandle i(this CodeWriter w, string begin = null, string end = null) + { + return w.OpenIndent(begin, end, newLineAfterBlockEnd: false); + } - public static UsingHandle I(this CodeWriter w, string begin = null, string end = null) - { - return w.OpenIndent(begin, end, newLineAfterBlockEnd: true); - } + public static UsingHandle I(this CodeWriter w, string begin = null, string end = null) + { + return w.OpenIndent(begin, end, newLineAfterBlockEnd: true); } } diff --git a/src/Maui.BindableProperty.Generator/CodeWriter/CodeWriterSettings.cs b/src/Maui.BindableProperty.Generator/CodeWriter/CodeWriterSettings.cs index eb8eeef..0556d53 100644 --- a/src/Maui.BindableProperty.Generator/CodeWriter/CodeWriterSettings.cs +++ b/src/Maui.BindableProperty.Generator/CodeWriter/CodeWriterSettings.cs @@ -2,75 +2,74 @@ using System.Collections.Generic; // Based on https://github.com/SaladLab/CodeWriter -namespace Maui.BindableProperty.Generator -{ - public class CodeWriterSettings - { - /// - /// Indentation string. For Tab, "\t". For 4 spaces, " " - /// - public string Indent; +namespace Maui.BindableProperty.Generator; - /// - /// String for begin of block. For C#, "{". - /// - public string BlockBegin; +public class CodeWriterSettings +{ + /// + /// Indentation string. For Tab, "\t". For 4 spaces, " " + /// + public string Indent; - /// - /// String for end of block. For C#, "}". - /// - public string BlockEnd; + /// + /// String for begin of block. For C#, "{". + /// + public string BlockBegin; - /// - /// String for newline. For Windows, "\r\n". - /// - public string NewLine; + /// + /// String for end of block. For C#, "}". + /// + public string BlockEnd; - /// - /// NewLine is inserted with new block. - /// - /// BlockNewLine: true - /// - /// Block - /// { - /// } - /// - /// - /// BlockNewLine: false - /// - /// Block { - /// } - /// - /// - public bool NewLineBeforeBlockBegin; + /// + /// String for newline. For Windows, "\r\n". + /// + public string NewLine; - /// - /// After ToString() called, TranslationMapping replace string. - /// For example, when TranslationMapping = {"A": "a"} will translate text "ABC" to "aBC". - /// - public Dictionary TranslationMapping = new Dictionary(); + /// + /// NewLine is inserted with new block. + /// + /// BlockNewLine: true + /// + /// Block + /// { + /// } + /// + /// + /// BlockNewLine: false + /// + /// Block { + /// } + /// + /// + public bool NewLineBeforeBlockBegin; - public CodeWriterSettings() - { - } + /// + /// After ToString() called, TranslationMapping replace string. + /// For example, when TranslationMapping = {"A": "a"} will translate text "ABC" to "aBC". + /// + public Dictionary TranslationMapping = new Dictionary(); - public CodeWriterSettings(CodeWriterSettings o) - { - Indent = o.Indent; - BlockBegin = o.BlockBegin; - BlockEnd = o.BlockEnd; - NewLine = o.NewLine; - NewLineBeforeBlockBegin = o.NewLineBeforeBlockBegin; - TranslationMapping = new Dictionary(o.TranslationMapping); - } + public CodeWriterSettings() + { + } - public static CodeWriterSettings CSharpDefault = new CodeWriterSettings - { - Indent = " ", - BlockBegin = "{", - BlockEnd = "}", - NewLine = Environment.NewLine, - NewLineBeforeBlockBegin = true, - }; + public CodeWriterSettings(CodeWriterSettings o) + { + Indent = o.Indent; + BlockBegin = o.BlockBegin; + BlockEnd = o.BlockEnd; + NewLine = o.NewLine; + NewLineBeforeBlockBegin = o.NewLineBeforeBlockBegin; + TranslationMapping = new Dictionary(o.TranslationMapping); } + + public static CodeWriterSettings CSharpDefault = new CodeWriterSettings + { + Indent = " ", + BlockBegin = "{", + BlockEnd = "}", + NewLine = Environment.NewLine, + NewLineBeforeBlockBegin = true, + }; } diff --git a/src/Maui.BindableProperty.Generator/CodeWriter/UsingHandle.cs b/src/Maui.BindableProperty.Generator/CodeWriter/UsingHandle.cs index 021f4ec..ee97792 100644 --- a/src/Maui.BindableProperty.Generator/CodeWriter/UsingHandle.cs +++ b/src/Maui.BindableProperty.Generator/CodeWriter/UsingHandle.cs @@ -1,24 +1,23 @@ using System; // Based on https://github.com/SaladLab/CodeWriter -namespace Maui.BindableProperty.Generator +namespace Maui.BindableProperty.Generator; + +public sealed class UsingHandle : IDisposable { - public sealed class UsingHandle : IDisposable - { - private Action _disposed; + private Action _disposed; - public UsingHandle(Action disposed) - { - _disposed = disposed; - } + public UsingHandle(Action disposed) + { + _disposed = disposed; + } - public void Dispose() + public void Dispose() + { + if (_disposed != null) { - if (_disposed != null) - { - _disposed(); - _disposed = null; - } + _disposed(); + _disposed = null; } } } diff --git a/src/Maui.BindableProperty.Generator/Core/BindableProperty/AutoBindableConstants.cs b/src/Maui.BindableProperty.Generator/Core/BindableProperty/AutoBindableConstants.cs index 432ac5a..4f84205 100644 --- a/src/Maui.BindableProperty.Generator/Core/BindableProperty/AutoBindableConstants.cs +++ b/src/Maui.BindableProperty.Generator/Core/BindableProperty/AutoBindableConstants.cs @@ -1,29 +1,46 @@ -namespace Maui.BindableProperty.Generator.Core.BindableProperty +namespace Maui.BindableProperty.Generator.Core.BindableProperty; + +public class AutoBindableConstants { - public class AutoBindableConstants - { - public const string FullNameMauiControls = "Microsoft.Maui.Controls"; + public const string FullNameMauiControls = "Microsoft.Maui.Controls"; + + public const string ProjectName = "Maui.BindableProperty.Generator"; + + public const string ExceptionTitle = $"{ProjectName} exception"; + + public const string ExceptionMBPG001Id = "MBPG001"; + + public const string ExceptionMBPG001Message = "An exception was thrown trying to generate the partial class '{0}'.\nException: {1}"; + + public const string ExceptionMBPG002Id = "MBPG002"; + + public const string ExceptionMBPG002Message = $"{AutoBindableConstants.ProjectName} does not currently support nested classes. Class '{{0}}'"; + + public const string ExceptionMBPG003Id = "MBPG003"; + + public const string ExceptionMBPG003Message = "An exception was thrown trying to generate the bindable property of '{0}' in the class '{1}'. Exception: {2}"; + + public const string ExceptionMBPG004Id = "MBPG004"; - public const string ProjectName = "Maui.BindableProperty.Generator"; + public const string ExceptionMBPG004Message = "An exception was thrown trying to process this field '{0}'"; - public const string AttrName = "AutoBindableAttribute"; + public const string AttrName = "AutoBindableAttribute"; - public const string AttrClassDisplayString = @$"{ProjectName}.Core.{AttrName}"; + public const string AttrClassDisplayString = @$"{ProjectName}.Core.{AttrName}"; - public const string AttrGeneratedCodeString = @$"[global::System.CodeDom.Compiler.GeneratedCode(""{{GeneratorFullName}}"", ""{{Version}}"")]"; + public const string AttrGeneratedCodeString = @$"[global::System.CodeDom.Compiler.GeneratedCode(""{{GeneratorFullName}}"", ""{{Version}}"")]"; - public const string AttrExcludeFromCodeCoverageString = "[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]"; + public const string AttrExcludeFromCodeCoverageString = "[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]"; - public const string AttrPropertyName = "PropertyName"; + public const string AttrPropertyName = "PropertyName"; - public const string AttrOnChanged = "OnChanged"; + public const string AttrOnChanged = "OnChanged"; - public const string AttrDefaultValue = "DefaultValue"; + public const string AttrDefaultValue = "DefaultValue"; - public const string AttrValidateValue = "ValidateValue"; + public const string AttrValidateValue = "ValidateValue"; - public const string AttrDefaultBindingMode = "DefaultBindingMode"; + public const string AttrDefaultBindingMode = "DefaultBindingMode"; - public const string AttrHidesUnderlyingProperty = "HidesUnderlyingProperty"; - } + public const string AttrHidesUnderlyingProperty = "HidesUnderlyingProperty"; } diff --git a/src/Maui.BindableProperty.Generator/Core/BindableProperty/AutoBindablePropertyGenerator.cs b/src/Maui.BindableProperty.Generator/Core/BindableProperty/AutoBindablePropertyGenerator.cs index 44405ce..690851d 100644 --- a/src/Maui.BindableProperty.Generator/Core/BindableProperty/AutoBindablePropertyGenerator.cs +++ b/src/Maui.BindableProperty.Generator/Core/BindableProperty/AutoBindablePropertyGenerator.cs @@ -5,21 +5,21 @@ using Maui.BindableProperty.Generator.Core.BindableProperty.Implementation; using Maui.BindableProperty.Generator.Core.BindableProperty.Implementation.Interfaces; -namespace Maui.BindableProperty.Generator.Core.BindableProperty -{ +namespace Maui.BindableProperty.Generator.Core.BindableProperty; - [Generator] - public class AutoBindablePropertyGenerator : ISourceGenerator - { - private readonly List TypeImplementations = new() { - typeof(DefaultValue), - typeof(PropertyChanged), - typeof(DefaultBindingMode), - typeof(ValidateValue) - }; - private readonly List Implementations = new(); - - private const string attributeText = @" + +[Generator] +public class AutoBindablePropertyGenerator : ISourceGenerator +{ + private readonly List TypeImplementations = new() { + typeof(DefaultValue), + typeof(PropertyChanged), + typeof(DefaultBindingMode), + typeof(ValidateValue) + }; + private readonly List Implementations = new(); + + private const string attributeText = @" #pragma warning disable #nullable enable using System; @@ -45,160 +45,239 @@ public AutoBindableAttribute(){} } }"; - public void Execute(GeneratorExecutionContext context) - { - context.EachField(AutoBindableConstants.AttrClassDisplayString, (attributeSymbol, group) => { - var classSource = this.ProcessClass(group.Key, group.ToList(), attributeSymbol, context); - context.AddSource($"{group.Key.Name}.generated.cs", SourceText.From(classSource, Encoding.UTF8)); - }); - } + public void Execute(GeneratorExecutionContext context) + { + context.EachClass(AutoBindableConstants.AttrClassDisplayString, (attributeSymbol, group) => { + var classNamedTypeSymbol = group.Key; + try + { + var classSource = this.ProcessClass(classNamedTypeSymbol, group.ToList(), attributeSymbol, context); + if (string.IsNullOrEmpty(classSource)) + return; - private string ProcessClass(INamedTypeSymbol classSymbol, List fields, ISymbol attributeSymbol, GeneratorExecutionContext context) - { - if (!classSymbol.ContainingSymbol.Equals(classSymbol.ContainingNamespace, SymbolEqualityComparer.Default)) + context.AddSource($"{classNamedTypeSymbol.Name}.generated.cs", SourceText.From(classSource, Encoding.UTF8)); + } + catch (Exception e) { - return null; // TODO: issue a diagnostic that it must be top level + context.ReportDiagnostic( + Diagnostic.Create( + new DiagnosticDescriptor( + AutoBindableConstants.ExceptionMBPG001Id, + AutoBindableConstants.ExceptionTitle, + AutoBindableConstants.ExceptionMBPG001Message, + AutoBindableConstants.ProjectName, + DiagnosticSeverity.Error, + isEnabledByDefault: true + ), + classNamedTypeSymbol.Locations.FirstOrDefault(), + classNamedTypeSymbol.ToDisplayString(), + e.ToString() + ) + ); } + }); + } - var namespaceName = classSymbol.ContainingNamespace.ToDisplayString(); - var w = new CodeWriter(CodeWriterSettings.CSharpDefault); - w._("// "); - w._("#pragma warning disable"); - w._("#nullable enable"); - using (w.B(@$"namespace {namespaceName}")) + private string ProcessClass(INamedTypeSymbol classSymbol, List fields, ISymbol attributeSymbol, GeneratorExecutionContext context) + { + if (!classSymbol.ContainingSymbol.Equals(classSymbol.ContainingNamespace, SymbolEqualityComparer.Default)) + { + context.ReportDiagnostic( + Diagnostic.Create( + new DiagnosticDescriptor( + AutoBindableConstants.ExceptionMBPG002Id, + AutoBindableConstants.ExceptionTitle, + AutoBindableConstants.ExceptionMBPG002Message, + AutoBindableConstants.ProjectName, + DiagnosticSeverity.Error, + isEnabledByDefault: true + ), + classSymbol.Locations.FirstOrDefault(), + classSymbol.ToDisplayString() + ) + ); + + return null; + } + + var namespaceName = classSymbol.ContainingNamespace.ToDisplayString(); + var w = new CodeWriter(CodeWriterSettings.CSharpDefault); + w._("// "); + w._("#pragma warning disable"); + w._("#nullable enable"); + using (w.B(@$"namespace {namespaceName}")) + { + using (w.B(@$"public partial class {classSymbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat)}")) { - using (w.B(@$"public partial class {classSymbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat)}")) + // Create properties for each field + foreach (IFieldSymbol fieldSymbol in fields) { - // Create properties for each field - foreach (IFieldSymbol fieldSymbol in fields) + try + { + this.ProcessBindableProperty(w, fieldSymbol, attributeSymbol, classSymbol, context); + } + catch (Exception e) { - this.ProcessBindableProperty(w, fieldSymbol, attributeSymbol, classSymbol); + context.ReportDiagnostic( + Diagnostic.Create( + new DiagnosticDescriptor( + AutoBindableConstants.ExceptionMBPG003Id, + AutoBindableConstants.ExceptionTitle, + AutoBindableConstants.ExceptionMBPG003Message, + AutoBindableConstants.ProjectName, + DiagnosticSeverity.Error, + isEnabledByDefault: true + ), + classSymbol.Locations.FirstOrDefault(), + fieldSymbol.ToDisplayString(), + classSymbol.ToDisplayString(), + e.ToString() + ) + ); } } } - - return w.ToString(); } - private void ProcessBindableProperty(CodeWriter w, IFieldSymbol fieldSymbol, ISymbol attributeSymbol, INamedTypeSymbol classSymbol) - { - // Get the name and type of the field - var fieldName = fieldSymbol.Name; - var fieldType = fieldSymbol.Type; + return w.ToString(); + } - var nameProperty = fieldSymbol.GetTypedConstant(attributeSymbol, AutoBindableConstants.AttrPropertyName); - this.InitializeImplementations(fieldSymbol, attributeSymbol, classSymbol); + private void ProcessBindableProperty( + CodeWriter w, + IFieldSymbol fieldSymbol, + ISymbol attributeSymbol, + INamedTypeSymbol classSymbol, + GeneratorExecutionContext context) + { + // Get the name and type of the field + var fieldName = fieldSymbol.Name; + var fieldType = fieldSymbol.Type; - var propertyName = this.ChooseName(fieldName, nameProperty); - if (propertyName?.Length == 0 || propertyName == fieldName) - { - // TODO: issue a diagnostic that we can't process this field - return; - } + var nameProperty = fieldSymbol.GetTypedConstant(attributeSymbol, AutoBindableConstants.AttrPropertyName); + this.InitializeImplementations(fieldSymbol, attributeSymbol, classSymbol); - var bindablePropertyName = $@"{propertyName}Property"; - var customParameters = this.ProcessBindableParameters(); - var applyHidesUnderlying = fieldSymbol.GetValue(attributeSymbol, AutoBindableConstants.AttrHidesUnderlyingProperty); - var hidesUnderlying = applyHidesUnderlying ? " new" : string.Empty; - var declaringType = fieldType.WithNullableAnnotation(NullableAnnotation.None); - var parameters = $"nameof({propertyName}),typeof({declaringType.ToDisplayString(CommonSymbolDisplayFormat.DefaultFormat)}),typeof({classSymbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat)}){customParameters}".Split(','); + var propertyName = this.ChooseName(fieldName, nameProperty); + if (propertyName?.Length == 0 || propertyName == fieldName) + { + context.ReportDiagnostic( + Diagnostic.Create( + new DiagnosticDescriptor( + AutoBindableConstants.ExceptionMBPG004Id, + AutoBindableConstants.ExceptionTitle, + AutoBindableConstants.ExceptionMBPG004Message, + AutoBindableConstants.ProjectName, + DiagnosticSeverity.Error, + isEnabledByDefault: true + ), + classSymbol.Locations.FirstOrDefault(), + fieldName + ) + ); + return; + } - w._(AttributeBuilder.GetAttrGeneratedCodeString()); - w._($@"public static{hidesUnderlying} readonly {AutoBindableConstants.FullNameMauiControls}.BindableProperty {bindablePropertyName} ="); - w._($"{w.GetIndentString(6)}{AutoBindableConstants.FullNameMauiControls}.BindableProperty.Create("); + var bindablePropertyName = $@"{propertyName}Property"; + var customParameters = this.ProcessBindableParameters(); + var applyHidesUnderlying = fieldSymbol.GetValue(attributeSymbol, AutoBindableConstants.AttrHidesUnderlyingProperty); + var hidesUnderlying = applyHidesUnderlying ? " new" : string.Empty; + var declaringType = fieldType.WithNullableAnnotation(NullableAnnotation.None); + var parameters = $"nameof({propertyName}),typeof({declaringType.ToDisplayString(CommonSymbolDisplayFormat.DefaultFormat)}),typeof({classSymbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat)}){customParameters}".Split(','); - for (int i = 0; i < parameters.Length; i++) - { - var parameter = parameters[i]; - var ends = i < parameters.Length - 1 ? "," : ");"; - w._($"{w.GetIndentString(12)}{parameter}{ends}"); - } + w._(AttributeBuilder.GetAttrGeneratedCodeString()); + w._($@"public static{hidesUnderlying} readonly {AutoBindableConstants.FullNameMauiControls}.BindableProperty {bindablePropertyName} ="); + w._($"{w.GetIndentString(6)}{AutoBindableConstants.FullNameMauiControls}.BindableProperty.Create("); + + for (int i = 0; i < parameters.Length; i++) + { + var parameter = parameters[i]; + var ends = i < parameters.Length - 1 ? "," : ");"; + w._($"{w.GetIndentString(12)}{parameter}{ends}"); + } - w._(); - AttributeBuilder.WriteAllAttrGeneratedCodeStrings(w); - using (w.B(@$"public{hidesUnderlying} {fieldType.ToDisplayString(CommonSymbolDisplayFormat.DefaultFormat)} {propertyName}")) + w._(); + AttributeBuilder.WriteAllAttrGeneratedCodeStrings(w); + using (w.B(@$"public{hidesUnderlying} {fieldType.ToDisplayString(CommonSymbolDisplayFormat.DefaultFormat)} {propertyName}")) + { + w._($@"get => ({fieldType.ToDisplayString(CommonSymbolDisplayFormat.DefaultFormat)})GetValue({bindablePropertyName});"); + if (this.ExistsBodySetter()) { - w._($@"get => ({fieldType.ToDisplayString(CommonSymbolDisplayFormat.DefaultFormat)})GetValue({bindablePropertyName});"); - if (this.ExistsBodySetter()) - { - using (w.B(@$"set")) - { - w._($@"SetValue({bindablePropertyName}, value);"); - this.ProcessBodySetter(w); - } - } - else + using (w.B(@$"set")) { - w._($@"set => SetValue({bindablePropertyName}, value);"); + w._($@"SetValue({bindablePropertyName}, value);"); + this.ProcessBodySetter(w); } } - - this.ProcessImplementationLogic(w); + else + { + w._($@"set => SetValue({bindablePropertyName}, value);"); + } } - private void InitializeImplementations(IFieldSymbol fieldSymbol, ISymbol attributeSymbol, INamedTypeSymbol classSymbol) - { - this.Implementations.Clear(); - var args = new object[] { fieldSymbol, attributeSymbol, classSymbol }; - this.TypeImplementations.ForEach(t => { - var ctor = t.GetConstructors().FirstOrDefault(); - var paramLength = ctor.GetParameters().Length; - var paramsCtor = args.Take(paramLength).ToArray(); - var instantiatedType = Activator.CreateInstance(t, paramsCtor) as IImplementation; - this.Implementations.Add(instantiatedType); - }); - } + this.ProcessImplementationLogic(w); + } - private string ProcessBindableParameters() - { - var parameters = this.Implementations - .Select(i => i.ProcessBindableParameters()) - .Where(x => !string.IsNullOrEmpty(x)).ToArray(); - - return parameters.Length > 0 ? $@",{ string.Join(",", parameters) }" : string.Empty; - } + private void InitializeImplementations(IFieldSymbol fieldSymbol, ISymbol attributeSymbol, INamedTypeSymbol classSymbol) + { + this.Implementations.Clear(); + var args = new object[] { fieldSymbol, attributeSymbol, classSymbol }; + this.TypeImplementations.ForEach(t => { + var ctor = t.GetConstructors().FirstOrDefault(); + var paramLength = ctor.GetParameters().Length; + var paramsCtor = args.Take(paramLength).ToArray(); + var instantiatedType = Activator.CreateInstance(t, paramsCtor) as IImplementation; + this.Implementations.Add(instantiatedType); + }); + } - private void ProcessBodySetter(CodeWriter w) - { - this.Implementations - .ForEach(i => i.ProcessBodySetter(w)); - } + private string ProcessBindableParameters() + { + var parameters = this.Implementations + .Select(i => i.ProcessBindableParameters()) + .Where(x => !string.IsNullOrEmpty(x)).ToArray(); + + return parameters.Length > 0 ? $@",{ string.Join(",", parameters) }" : string.Empty; + } - private void ProcessImplementationLogic(CodeWriter w) - { - this.Implementations - .ForEach(i => i.ProcessImplementationLogic(w)); - } + private void ProcessBodySetter(CodeWriter w) + { + this.Implementations + .ForEach(i => i.ProcessBodySetter(w)); + } - private bool ExistsBodySetter() - { - return this.Implementations.Any(i => i.SetterImplemented()); - } + private void ProcessImplementationLogic(CodeWriter w) + { + this.Implementations + .ForEach(i => i.ProcessImplementationLogic(w)); + } - private string ChooseName(string fieldName, TypedConstant overridenNameOpt) + private bool ExistsBodySetter() + { + return this.Implementations.Any(i => i.SetterImplemented()); + } + + private string ChooseName(string fieldName, TypedConstant overridenNameOpt) + { + if (!overridenNameOpt.IsNull) { - if (!overridenNameOpt.IsNull) - { - return overridenNameOpt.Value?.ToString(); - } + return overridenNameOpt.Value?.ToString(); + } - fieldName = fieldName.TrimStart('_'); - if (fieldName.Length == 0) - return string.Empty; + fieldName = fieldName.TrimStart('_'); + if (fieldName.Length == 0) + return string.Empty; - if (fieldName.Length == 1) - return fieldName.ToUpper(); + if (fieldName.Length == 1) + return fieldName.ToUpper(); - return fieldName.Substring(0, 1).ToUpper() + fieldName.Substring(1); - } + return fieldName.Substring(0, 1).ToUpper() + fieldName.Substring(1); + } - public void Initialize(GeneratorInitializationContext context) - { - // Register the attribute source - context.RegisterForPostInitialization((i) => i.AddSource(AutoBindableConstants.AttrName, attributeText)); + public void Initialize(GeneratorInitializationContext context) + { + // Register the attribute source + context.RegisterForPostInitialization((i) => i.AddSource(AutoBindableConstants.AttrName, attributeText)); - // Register a syntax receiver that will be created for each generation pass - context.RegisterForSyntaxNotifications(() => new AutoBindableSyntaxReceiver()); - } + // Register a syntax receiver that will be created for each generation pass + context.RegisterForSyntaxNotifications(() => new AutoBindableSyntaxReceiver()); } } diff --git a/src/Maui.BindableProperty.Generator/Core/BindableProperty/AutoBindableSyntaxReceiver.cs b/src/Maui.BindableProperty.Generator/Core/BindableProperty/AutoBindableSyntaxReceiver.cs index 8b9b59d..fd06262 100644 --- a/src/Maui.BindableProperty.Generator/Core/BindableProperty/AutoBindableSyntaxReceiver.cs +++ b/src/Maui.BindableProperty.Generator/Core/BindableProperty/AutoBindableSyntaxReceiver.cs @@ -1,29 +1,28 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; -namespace Maui.BindableProperty.Generator.Core.BindableProperty +namespace Maui.BindableProperty.Generator.Core.BindableProperty; + +public class AutoBindableSyntaxReceiver : ISyntaxContextReceiver, IFieldSyntaxContextReceiver { - public class AutoBindableSyntaxReceiver : ISyntaxContextReceiver, IFieldSyntaxContextReceiver - { - public List Fields { get; } = new List(); + public List Fields { get; } = new List(); - /// - /// Called for every syntax node in the compilation, we can inspect the nodes and save any information useful for generation - /// - public void OnVisitSyntaxNode(GeneratorSyntaxContext context) + /// + /// Called for every syntax node in the compilation, we can inspect the nodes and save any information useful for generation + /// + public void OnVisitSyntaxNode(GeneratorSyntaxContext context) + { + // any field with at least one attribute is a candidate for property generation + if (context.Node is FieldDeclarationSyntax fieldDeclarationSyntax + && fieldDeclarationSyntax.AttributeLists.Count > 0) { - // any field with at least one attribute is a candidate for property generation - if (context.Node is FieldDeclarationSyntax fieldDeclarationSyntax - && fieldDeclarationSyntax.AttributeLists.Count > 0) + foreach (VariableDeclaratorSyntax variable in fieldDeclarationSyntax.Declaration.Variables) { - foreach (VariableDeclaratorSyntax variable in fieldDeclarationSyntax.Declaration.Variables) + // Get the symbol being declared by the field, and keep it if its annotated + if (context.SemanticModel.GetDeclaredSymbol(variable) is IFieldSymbol fieldSymbol && + fieldSymbol.GetAttributes().Any(ad => ad?.AttributeClass?.ToDisplayString() == AutoBindableConstants.AttrClassDisplayString)) { - // Get the symbol being declared by the field, and keep it if its annotated - if (context.SemanticModel.GetDeclaredSymbol(variable) is IFieldSymbol fieldSymbol && - fieldSymbol.GetAttributes().Any(ad => ad?.AttributeClass?.ToDisplayString() == AutoBindableConstants.AttrClassDisplayString)) - { - Fields.Add(fieldSymbol); - } + Fields.Add(fieldSymbol); } } } diff --git a/src/Maui.BindableProperty.Generator/Core/BindableProperty/Implementation/DefaultBindingMode.cs b/src/Maui.BindableProperty.Generator/Core/BindableProperty/Implementation/DefaultBindingMode.cs index 1449918..8f15bf8 100644 --- a/src/Maui.BindableProperty.Generator/Core/BindableProperty/Implementation/DefaultBindingMode.cs +++ b/src/Maui.BindableProperty.Generator/Core/BindableProperty/Implementation/DefaultBindingMode.cs @@ -2,49 +2,48 @@ using Maui.BindableProperty.Generator.Helpers; using Microsoft.CodeAnalysis; -namespace Maui.BindableProperty.Generator.Core.BindableProperty.Implementation +namespace Maui.BindableProperty.Generator.Core.BindableProperty.Implementation; + +public class DefaultBindingMode : IImplementation { - public class DefaultBindingMode : IImplementation - { - private const string FullNameSpaceBindingMode = $"{AutoBindableConstants.FullNameMauiControls}.BindingMode"; - private TypedConstant DefaultBindingModeValueProperty { get; set; } - private IFieldSymbol FieldSymbol { get; set; } + private const string FullNameSpaceBindingMode = $"{AutoBindableConstants.FullNameMauiControls}.BindingMode"; + private TypedConstant DefaultBindingModeValueProperty { get; set; } + private IFieldSymbol FieldSymbol { get; set; } - public DefaultBindingMode(IFieldSymbol fieldSymbol, ISymbol attributeSymbol, INamedTypeSymbol classSymbol) - { - this.DefaultBindingModeValueProperty = fieldSymbol.GetTypedConstant(attributeSymbol, AutoBindableConstants.AttrDefaultBindingMode); - this.FieldSymbol = fieldSymbol; - } + public DefaultBindingMode(IFieldSymbol fieldSymbol, ISymbol attributeSymbol, INamedTypeSymbol classSymbol) + { + this.DefaultBindingModeValueProperty = fieldSymbol.GetTypedConstant(attributeSymbol, AutoBindableConstants.AttrDefaultBindingMode); + this.FieldSymbol = fieldSymbol; + } - public bool SetterImplemented() - { - return false; - } + public bool SetterImplemented() + { + return false; + } - public string ProcessBindableParameters() + public string ProcessBindableParameters() + { + var fieldType = this.FieldSymbol.Type; + var defaultValue = this.DefaultBindingModeValueProperty.GetValue(value => { - var fieldType = this.FieldSymbol.Type; - var defaultValue = this.DefaultBindingModeValueProperty.GetValue(value => + if (!value.Contains(FullNameSpaceBindingMode)) { - if (!value.Contains(FullNameSpaceBindingMode)) - { - return $"{FullNameSpaceBindingMode}.{value}"; - } + return $"{FullNameSpaceBindingMode}.{value}"; + } - return value; - }); + return value; + }); - return defaultValue != default ? $"defaultBindingMode: {defaultValue}" : default; - } + return defaultValue != default ? $"defaultBindingMode: {defaultValue}" : default; + } - public void ProcessBodySetter(CodeWriter w) - { - // Not implemented - } + public void ProcessBodySetter(CodeWriter w) + { + // Not implemented + } - public void ProcessImplementationLogic(CodeWriter w) - { - // Not implemented - } + public void ProcessImplementationLogic(CodeWriter w) + { + // Not implemented } } diff --git a/src/Maui.BindableProperty.Generator/Core/BindableProperty/Implementation/DefaultValue.cs b/src/Maui.BindableProperty.Generator/Core/BindableProperty/Implementation/DefaultValue.cs index 780a1f8..9179466 100644 --- a/src/Maui.BindableProperty.Generator/Core/BindableProperty/Implementation/DefaultValue.cs +++ b/src/Maui.BindableProperty.Generator/Core/BindableProperty/Implementation/DefaultValue.cs @@ -2,58 +2,57 @@ using Maui.BindableProperty.Generator.Helpers; using Microsoft.CodeAnalysis; -namespace Maui.BindableProperty.Generator.Core.BindableProperty.Implementation +namespace Maui.BindableProperty.Generator.Core.BindableProperty.Implementation; + +public class DefaultValue : IImplementation { - public class DefaultValue : IImplementation - { - private TypedConstant DefaultValueProperty { get; set; } - private IFieldSymbol FieldSymbol { get; set; } + private TypedConstant DefaultValueProperty { get; set; } + private IFieldSymbol FieldSymbol { get; set; } - public DefaultValue(IFieldSymbol fieldSymbol, ISymbol attributeSymbol) - { - this.DefaultValueProperty = fieldSymbol.GetTypedConstant(attributeSymbol, AutoBindableConstants.AttrDefaultValue); - this.FieldSymbol = fieldSymbol; - } + public DefaultValue(IFieldSymbol fieldSymbol, ISymbol attributeSymbol) + { + this.DefaultValueProperty = fieldSymbol.GetTypedConstant(attributeSymbol, AutoBindableConstants.AttrDefaultValue); + this.FieldSymbol = fieldSymbol; + } - public bool SetterImplemented() - { - return false; - } + public bool SetterImplemented() + { + return false; + } - public string ProcessBindableParameters() + public string ProcessBindableParameters() + { + var fieldType = this.FieldSymbol.Type; + var defaultValue = this.DefaultValueProperty.GetValue(value => { - var fieldType = this.FieldSymbol.Type; - var defaultValue = this.DefaultValueProperty.GetValue(value => + if (value != null) { - if (value != null) + if (fieldType.IsStringType()) { - if (fieldType.IsStringType()) + if (value == string.Empty) { - if (value == string.Empty) - { - return "string.Empty"; - } - - return $"\"{value}\""; + return "string.Empty"; } - return value; + return $"\"{value}\""; } - return default; - }); + return value; + } - return defaultValue != null ? $"defaultValue: {defaultValue}" : $"defaultValue: default({fieldType.ToDisplayString(CommonSymbolDisplayFormat.DefaultFormat)})"; - } + return default; + }); - public void ProcessBodySetter(CodeWriter w) - { - // Not implemented - } + return defaultValue != null ? $"defaultValue: {defaultValue}" : $"defaultValue: default({fieldType.ToDisplayString(CommonSymbolDisplayFormat.DefaultFormat)})"; + } - public void ProcessImplementationLogic(CodeWriter w) - { - // Not implemented - } + public void ProcessBodySetter(CodeWriter w) + { + // Not implemented + } + + public void ProcessImplementationLogic(CodeWriter w) + { + // Not implemented } } diff --git a/src/Maui.BindableProperty.Generator/Core/BindableProperty/Implementation/Interfaces/IImplementation.cs b/src/Maui.BindableProperty.Generator/Core/BindableProperty/Implementation/Interfaces/IImplementation.cs index 2eb9362..764b13f 100644 --- a/src/Maui.BindableProperty.Generator/Core/BindableProperty/Implementation/Interfaces/IImplementation.cs +++ b/src/Maui.BindableProperty.Generator/Core/BindableProperty/Implementation/Interfaces/IImplementation.cs @@ -1,12 +1,11 @@ using Microsoft.CodeAnalysis; -namespace Maui.BindableProperty.Generator.Core.BindableProperty.Implementation.Interfaces +namespace Maui.BindableProperty.Generator.Core.BindableProperty.Implementation.Interfaces; + +public interface IImplementation { - public interface IImplementation - { - bool SetterImplemented(); - string ProcessBindableParameters(); - void ProcessBodySetter(CodeWriter w); - void ProcessImplementationLogic(CodeWriter w); - } + bool SetterImplemented(); + string ProcessBindableParameters(); + void ProcessBodySetter(CodeWriter w); + void ProcessImplementationLogic(CodeWriter w); } diff --git a/src/Maui.BindableProperty.Generator/Core/BindableProperty/Implementation/PropertyChanged.cs b/src/Maui.BindableProperty.Generator/Core/BindableProperty/Implementation/PropertyChanged.cs index 2082f1e..8cd8e14 100644 --- a/src/Maui.BindableProperty.Generator/Core/BindableProperty/Implementation/PropertyChanged.cs +++ b/src/Maui.BindableProperty.Generator/Core/BindableProperty/Implementation/PropertyChanged.cs @@ -2,77 +2,76 @@ using Maui.BindableProperty.Generator.Helpers; using Microsoft.CodeAnalysis; -namespace Maui.BindableProperty.Generator.Core.BindableProperty.Implementation +namespace Maui.BindableProperty.Generator.Core.BindableProperty.Implementation; + +public class PropertyChanged : IImplementation { - public class PropertyChanged : IImplementation - { - private TypedConstant OnChangedProperty { get; set; } - private IFieldSymbol FieldSymbol { get; set; } - private INamedTypeSymbol ClassSymbol { get; set; } + private TypedConstant OnChangedProperty { get; set; } + private IFieldSymbol FieldSymbol { get; set; } + private INamedTypeSymbol ClassSymbol { get; set; } - public PropertyChanged(IFieldSymbol fieldSymbol, ISymbol attributeSymbol, INamedTypeSymbol classSymbol) - { - this.OnChangedProperty = fieldSymbol.GetTypedConstant(attributeSymbol, AutoBindableConstants.AttrOnChanged); - this.FieldSymbol = fieldSymbol; - this.ClassSymbol = classSymbol; - } + public PropertyChanged(IFieldSymbol fieldSymbol, ISymbol attributeSymbol, INamedTypeSymbol classSymbol) + { + this.OnChangedProperty = fieldSymbol.GetTypedConstant(attributeSymbol, AutoBindableConstants.AttrOnChanged); + this.FieldSymbol = fieldSymbol; + this.ClassSymbol = classSymbol; + } - public bool SetterImplemented() - { - return false; - } + public bool SetterImplemented() + { + return false; + } - public string ProcessBindableParameters() - { - return this.OnChangedProperty.GetValue(methodName => { - return $@"propertyChanged: __{methodName}"; - }); - } + public string ProcessBindableParameters() + { + return this.OnChangedProperty.GetValue(methodName => { + return $@"propertyChanged: __{methodName}"; + }); + } - public void ProcessBodySetter(CodeWriter w) - { - // Not implemented - } + public void ProcessBodySetter(CodeWriter w) + { + // Not implemented + } - public void ProcessImplementationLogic(CodeWriter w) - { - this.OnChangedProperty.GetValue(methodName => { - var methodDefinition = @$"private static void __{methodName}({AutoBindableConstants.FullNameMauiControls}.BindableObject bindable, object oldValue, object newValue)"; + public void ProcessImplementationLogic(CodeWriter w) + { + this.OnChangedProperty.GetValue(methodName => { + var methodDefinition = @$"private static void __{methodName}({AutoBindableConstants.FullNameMauiControls}.BindableObject bindable, object oldValue, object newValue)"; - if (w.ToString().Contains(methodDefinition)) - return default; + if (w.ToString().Contains(methodDefinition)) + return default; - AttributeBuilder.WriteAllAttrGeneratedCodeStrings(w); - using (w.B(methodDefinition)) + AttributeBuilder.WriteAllAttrGeneratedCodeStrings(w); + using (w.B(methodDefinition)) + { + var methods = this.GetMethodsToCall(methodName); + if (methods.Any()) { - var methods = this.GetMethodsToCall(methodName); - if (methods.Any()) - { - w._($@"var ctrl = ({this.ClassSymbol.ToDisplayString(CommonSymbolDisplayFormat.DefaultFormat)})bindable;"); - methods.ToList().ForEach(m => { - var count = m.Parameters.Count(); - if (count == 0) - w._($@"ctrl.{methodName}();"); - else if (count == 1) - w._($@"ctrl.{methodName}(({this.FieldSymbol.Type})newValue);"); - else if (count == 2) - w._($@"ctrl.{methodName}(({this.FieldSymbol.Type})oldValue, ({this.FieldSymbol.Type})newValue);"); - }); - } + w._($@"var ctrl = ({this.ClassSymbol.ToDisplayString(CommonSymbolDisplayFormat.DefaultFormat)})bindable;"); + methods.ToList().ForEach(m => { + var count = m.Parameters.Count(); + if (count == 0) + w._($@"ctrl.{methodName}();"); + else if (count == 1) + w._($@"ctrl.{methodName}(({this.FieldSymbol.Type})newValue);"); + else if (count == 2) + w._($@"ctrl.{methodName}(({this.FieldSymbol.Type})oldValue, ({this.FieldSymbol.Type})newValue);"); + }); } + } - return default; - }); - } + return default; + }); + } - private IEnumerable GetMethodsToCall(string methodName) - { - var typeSymbol = this.FieldSymbol.Type; - var methods = this.ClassSymbol.GetMembers(methodName) - .OfType() - .Where(m => m != null && (m.Parameters.Count() == 0 || (m.Parameters.Count() <= 2 && m.Parameters.All(p => p.Type.Equals(typeSymbol, SymbolEqualityComparer.Default))))); + private IEnumerable GetMethodsToCall(string methodName) + { + var typeSymbol = this.FieldSymbol.Type; + var methods = this.ClassSymbol.GetMembers(methodName) + .OfType() + .Where(m => m != null && (m.Parameters.Count() == 0 || (m.Parameters.Count() <= 2 && m.Parameters.All(p => p.Type.Equals(typeSymbol, SymbolEqualityComparer.Default))))); - return methods.OrderBy(m => m.Parameters.Count()); - } + return methods.OrderBy(m => m.Parameters.Count()); } } diff --git a/src/Maui.BindableProperty.Generator/Core/BindableProperty/Implementation/ValidateValue.cs b/src/Maui.BindableProperty.Generator/Core/BindableProperty/Implementation/ValidateValue.cs index 7d5c9ee..3e9bb6c 100644 --- a/src/Maui.BindableProperty.Generator/Core/BindableProperty/Implementation/ValidateValue.cs +++ b/src/Maui.BindableProperty.Generator/Core/BindableProperty/Implementation/ValidateValue.cs @@ -2,85 +2,84 @@ using Maui.BindableProperty.Generator.Helpers; using Microsoft.CodeAnalysis; -namespace Maui.BindableProperty.Generator.Core.BindableProperty.Implementation +namespace Maui.BindableProperty.Generator.Core.BindableProperty.Implementation; + +public class ValidateValue : IImplementation { - public class ValidateValue : IImplementation + private TypedConstant ValidateValueProperty { get; set; } + private IFieldSymbol FieldSymbol { get; set; } + private INamedTypeSymbol ClassSymbol { get; set; } + + public ValidateValue(IFieldSymbol fieldSymbol, ISymbol attributeSymbol, INamedTypeSymbol classSymbol) + { + this.ValidateValueProperty = fieldSymbol.GetTypedConstant(attributeSymbol, AutoBindableConstants.AttrValidateValue); + this.FieldSymbol = fieldSymbol; + this.ClassSymbol = classSymbol; + } + + public bool SetterImplemented() + { + return false; + } + + public string ProcessBindableParameters() + { + return this.ValidateValueProperty.GetValue(methodName => { + var method = this.GetMethodsToCall(methodName).FirstOrDefault(); + if (!this.IsValidMethod(method)) + return default; + + if (method.IsStatic) + { + return $@"validateValue: {methodName}"; + } + + return $@"validateValue: __{methodName}"; + }); + } + + public void ProcessBodySetter(CodeWriter w) + { + // Not implemented + } + + public void ProcessImplementationLogic(CodeWriter w) { - private TypedConstant ValidateValueProperty { get; set; } - private IFieldSymbol FieldSymbol { get; set; } - private INamedTypeSymbol ClassSymbol { get; set; } - - public ValidateValue(IFieldSymbol fieldSymbol, ISymbol attributeSymbol, INamedTypeSymbol classSymbol) - { - this.ValidateValueProperty = fieldSymbol.GetTypedConstant(attributeSymbol, AutoBindableConstants.AttrValidateValue); - this.FieldSymbol = fieldSymbol; - this.ClassSymbol = classSymbol; - } - - public bool SetterImplemented() - { - return false; - } - - public string ProcessBindableParameters() - { - return this.ValidateValueProperty.GetValue(methodName => { - var method = this.GetMethodsToCall(methodName).FirstOrDefault(); - if (!this.IsValidMethod(method)) - return default; - - if (method.IsStatic) - { - return $@"validateValue: {methodName}"; - } - - return $@"validateValue: __{methodName}"; - }); - } - - public void ProcessBodySetter(CodeWriter w) - { - // Not implemented - } - - public void ProcessImplementationLogic(CodeWriter w) - { - this.ValidateValueProperty.GetValue(methodName => { - var method = this.GetMethodsToCall(methodName).FirstOrDefault(); - if (!this.IsValidMethod(method) || method.IsStatic) - { - return default; - } - - var methodDefinition = @$"private static bool __{methodName}({AutoBindableConstants.FullNameMauiControls}.BindableObject bindable, object value)"; - if (w.ToString().Contains(methodDefinition)) - return default; - - AttributeBuilder.WriteAllAttrGeneratedCodeStrings(w); - using (w.B(methodDefinition)) - { - w._($@"var ctrl = ({this.ClassSymbol.ToDisplayString(CommonSymbolDisplayFormat.DefaultFormat)})bindable;"); - w._($@"return ctrl.{methodName}(ctrl, ({this.FieldSymbol.Type})value);"); - } + this.ValidateValueProperty.GetValue(methodName => { + var method = this.GetMethodsToCall(methodName).FirstOrDefault(); + if (!this.IsValidMethod(method) || method.IsStatic) + { + return default; + } + var methodDefinition = @$"private static bool __{methodName}({AutoBindableConstants.FullNameMauiControls}.BindableObject bindable, object value)"; + if (w.ToString().Contains(methodDefinition)) return default; - }); - } + AttributeBuilder.WriteAllAttrGeneratedCodeStrings(w); + using (w.B(methodDefinition)) + { + w._($@"var ctrl = ({this.ClassSymbol.ToDisplayString(CommonSymbolDisplayFormat.DefaultFormat)})bindable;"); + w._($@"return ctrl.{methodName}(ctrl, ({this.FieldSymbol.Type})value);"); + } - private bool IsValidMethod(IMethodSymbol method) - { - return method is not null && method.Parameters.Count() == 2; - } + return default; + }); + } + + + private bool IsValidMethod(IMethodSymbol method) + { + return method is not null && method.Parameters.Count() == 2; + } - private IEnumerable GetMethodsToCall(string methodName) - { - var typeSymbol = this.FieldSymbol.Type; - var methods = this.ClassSymbol.GetMembers(methodName) - .OfType() - .Where(m => m != null && m.Parameters.Count() == 2); + private IEnumerable GetMethodsToCall(string methodName) + { + var typeSymbol = this.FieldSymbol.Type; + var methods = this.ClassSymbol.GetMembers(methodName) + .OfType() + .Where(m => m != null && m.Parameters.Count() == 2); - return methods.OrderBy(m => m.Parameters.Count()); - } + return methods.OrderBy(m => m.Parameters.Count()); } } diff --git a/src/Maui.BindableProperty.Generator/Core/IFieldSyntaxContextReceiver.cs b/src/Maui.BindableProperty.Generator/Core/IFieldSyntaxContextReceiver.cs index 94991bc..79c4a2e 100644 --- a/src/Maui.BindableProperty.Generator/Core/IFieldSyntaxContextReceiver.cs +++ b/src/Maui.BindableProperty.Generator/Core/IFieldSyntaxContextReceiver.cs @@ -1,9 +1,8 @@ using Microsoft.CodeAnalysis; -namespace Maui.BindableProperty.Generator.Core +namespace Maui.BindableProperty.Generator.Core; + +internal interface IFieldSyntaxContextReceiver { - internal interface IFieldSyntaxContextReceiver - { - List Fields { get; } - } + List Fields { get; } } diff --git a/src/Maui.BindableProperty.Generator/Helpers/ExecutionContextExtensions.cs b/src/Maui.BindableProperty.Generator/Helpers/ExecutionContextExtensions.cs index f6770ff..a34ed28 100644 --- a/src/Maui.BindableProperty.Generator/Helpers/ExecutionContextExtensions.cs +++ b/src/Maui.BindableProperty.Generator/Helpers/ExecutionContextExtensions.cs @@ -1,32 +1,31 @@ using Microsoft.CodeAnalysis; using Maui.BindableProperty.Generator.Core; -namespace Maui.BindableProperty.Generator.Helpers +namespace Maui.BindableProperty.Generator.Helpers; + +internal static class ExecutionContextExtensions { - internal static class ExecutionContextExtensions + public static void EachClass( + this GeneratorExecutionContext context, + string metadataName, + Action> action) where T : IFieldSyntaxContextReceiver { - public static void EachField( - this GeneratorExecutionContext context, - string metadataName, - Action> action) where T : IFieldSyntaxContextReceiver - { - // Retrieve the populated receiver - if (context.SyntaxContextReceiver is not T receiver) - return; + // Retrieve the populated receiver + if (context.SyntaxContextReceiver is not T receiver) + return; - if (receiver is null) - return; + if (receiver is null) + return; - // Get the added attribute - var attributeSymbol = context.Compilation?.GetTypeByMetadataName(metadataName); + // Get the added attribute + var attributeSymbol = context.Compilation?.GetTypeByMetadataName(metadataName); - // Group the fields by class, and generate the source + // Group the fields by class, and generate the source #pragma warning disable RS1024 // Symbols should be compared for equality - foreach (IGrouping group in receiver.Fields?.GroupBy(f => f.ContainingType) ?? Enumerable.Empty>()) - { - action?.Invoke(attributeSymbol, group); - } -#pragma warning restore RS1024 // Symbols should be compared for equality + foreach (var group in receiver.Fields?.GroupBy(f => f.ContainingType) ?? Enumerable.Empty>()) + { + action?.Invoke(attributeSymbol, group); } +#pragma warning restore RS1024 // Symbols should be compared for equality } } diff --git a/src/Maui.BindableProperty.Generator/Helpers/SymbolExtensions.cs b/src/Maui.BindableProperty.Generator/Helpers/SymbolExtensions.cs index 549ff30..40cc85a 100644 --- a/src/Maui.BindableProperty.Generator/Helpers/SymbolExtensions.cs +++ b/src/Maui.BindableProperty.Generator/Helpers/SymbolExtensions.cs @@ -1,55 +1,54 @@ using Microsoft.CodeAnalysis; -namespace Maui.BindableProperty.Generator.Helpers +namespace Maui.BindableProperty.Generator.Helpers; + +internal static class SymbolExtensions { - internal static class SymbolExtensions + public static TypedConstant GetTypedConstant( + this IFieldSymbol fieldSymbol, + ISymbol attributeSymbol, + string key) { - public static TypedConstant GetTypedConstant( - this IFieldSymbol fieldSymbol, - ISymbol attributeSymbol, - string key) - { - // Get the AutoNotify attribute from the field, and any associated data - var attributeData = fieldSymbol.GetAttributes().Single(ad => attributeSymbol.Equals(ad.AttributeClass, SymbolEqualityComparer.Default)); - return attributeData.NamedArguments.SingleOrDefault(kvp => kvp.Key == key).Value; - } + // Get the AutoNotify attribute from the field, and any associated data + var attributeData = fieldSymbol.GetAttributes().Single(ad => attributeSymbol.Equals(ad.AttributeClass, SymbolEqualityComparer.Default)); + return attributeData.NamedArguments.SingleOrDefault(kvp => kvp.Key == key).Value; + } - // Based on https://github.com/dotnet/roslyn/blob/main/src/Compilers/CSharp/Portable/Symbols/TypeSymbolExtensions.cs - public static bool IsStringType(this ITypeSymbol type) - { - return type.SpecialType == SpecialType.System_String; - } + // Based on https://github.com/dotnet/roslyn/blob/main/src/Compilers/CSharp/Portable/Symbols/TypeSymbolExtensions.cs + public static bool IsStringType(this ITypeSymbol type) + { + return type.SpecialType == SpecialType.System_String; + } - public static T GetValue( - this IFieldSymbol fieldSymbol, - ISymbol attributeSymbol, - string key, - Func onSuccess = null) - { - var typeConstant = fieldSymbol.GetTypedConstant(attributeSymbol, key); - return typeConstant.GetValue(onSuccess); - } + public static T GetValue( + this IFieldSymbol fieldSymbol, + ISymbol attributeSymbol, + string key, + Func onSuccess = null) + { + var typeConstant = fieldSymbol.GetTypedConstant(attributeSymbol, key); + return typeConstant.GetValue(onSuccess); + } - public static T GetValue( - this TypedConstant TypedConstant, - Func onSuccess = null) + public static T GetValue( + this TypedConstant TypedConstant, + Func onSuccess = null) + { + if (!TypedConstant.IsNull) { - if (!TypedConstant.IsNull) - { - var value = TypedConstant.Value; - if (value?.GetType() == typeof(T)) + var value = TypedConstant.Value; + if (value?.GetType() == typeof(T)) + { + if (onSuccess != null) { - if (onSuccess != null) - { - return onSuccess.Invoke((T)value); - } - - return (T)value; + return onSuccess.Invoke((T)value); } - } - return default; + return (T)value; + } } + + return default; } } diff --git a/src/Maui.BindableProperty.Generator/Maui.BindableProperty.Generator.csproj b/src/Maui.BindableProperty.Generator/Maui.BindableProperty.Generator.csproj index b89b103..501efa6 100644 --- a/src/Maui.BindableProperty.Generator/Maui.BindableProperty.Generator.csproj +++ b/src/Maui.BindableProperty.Generator/Maui.BindableProperty.Generator.csproj @@ -10,7 +10,7 @@ Source generator that automatically transforms fields into BindableProperties that can be used in MAUI MAUI;BindableProperty;Source Generator README.md - 0.9.3 + 0.10.0 https://github.com/rrmanzano/maui-bindableproperty-generator https://github.com/rrmanzano/maui-bindableproperty-generator M.BindableProperty.Generator