├── .github └── FUNDING.yml ├── Directory.Build.targets ├── src ├── Avalonia.PropertyGenerator.CSharp │ ├── IsExternalInit.cs │ ├── AnalyzerReleases.Shipped.md │ ├── AnalyzerReleases.Unshipped.md │ ├── DeclaringType.cs │ ├── Avalonia.PropertyGenerator.CSharp.csproj │ ├── StyledProperty.cs │ ├── Visitors │ │ ├── AvaloniaPropertyRootVisitor.cs │ │ └── AvaloniaPropertyDeclaringTypeVisitor.cs │ ├── Property.cs │ ├── AttachedProperty.cs │ ├── Types.cs │ ├── DirectProperty.cs │ └── AvaloniaToClrPropertyGenerator.cs └── Avalonia.PropertyGenerator.CSharp.Demo │ ├── MainWindow.axaml.cs │ ├── App.axaml │ ├── MainWindow.axaml │ ├── Avalonia.PropertyGenerator.CSharp.Demo.csproj │ ├── App.axaml.cs │ ├── DemoControl.axaml │ └── DemoControl.axaml.cs ├── .gitignore ├── LICENSE.md ├── appveyor.yml ├── Directory.Build.props ├── Avalonia.PropertyGenerator.sln ├── README.md └── .editorconfig /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: jp2masa 2 | -------------------------------------------------------------------------------- /Directory.Build.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/Avalonia.PropertyGenerator.CSharp/IsExternalInit.cs: -------------------------------------------------------------------------------- 1 | namespace System.Runtime.CompilerServices 2 | { 3 | internal static class IsExternalInit { } 4 | } 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # User-specific files 2 | 3 | .vs/ 4 | *.suo 5 | *.user 6 | *.userosscache 7 | *.sln.docstates 8 | 9 | # Build results 10 | 11 | [Aa]rtifacts/ 12 | [Bb]in/ 13 | [Oo]bj/ 14 | -------------------------------------------------------------------------------- /src/Avalonia.PropertyGenerator.CSharp/AnalyzerReleases.Shipped.md: -------------------------------------------------------------------------------- 1 | ; Shipped analyzer releases 2 | ; https://github.com/dotnet/roslyn-analyzers/blob/master/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md 3 | 4 | -------------------------------------------------------------------------------- /src/Avalonia.PropertyGenerator.CSharp.Demo/MainWindow.axaml.cs: -------------------------------------------------------------------------------- 1 | using Avalonia.Controls; 2 | 3 | namespace Avalonia.PropertyGenerator.CSharp.Demo 4 | { 5 | internal sealed partial class MainWindow : Window 6 | { 7 | public MainWindow() 8 | { 9 | InitializeComponent(); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/Avalonia.PropertyGenerator.CSharp/AnalyzerReleases.Unshipped.md: -------------------------------------------------------------------------------- 1 | ; Unshipped analyzer release 2 | ; https://github.com/dotnet/roslyn-analyzers/blob/master/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md 3 | 4 | ### New Rules 5 | Rule ID | Category | Severity | Notes 6 | --------|----------|----------|------- 7 | AVPROP01 | Avalonia.PropertyGenerator.CSharp | Warning | Types 8 | -------------------------------------------------------------------------------- /src/Avalonia.PropertyGenerator.CSharp.Demo/App.axaml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/Avalonia.PropertyGenerator.CSharp/DeclaringType.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | using Microsoft.CodeAnalysis; 3 | 4 | namespace Avalonia.PropertyGenerator.CSharp 5 | { 6 | internal sealed record DeclaringType( 7 | INamedTypeSymbol Type, 8 | ImmutableArray StyledProperties, 9 | ImmutableArray DirectProperties, 10 | ImmutableArray AttachedProperties); 11 | } 12 | -------------------------------------------------------------------------------- /src/Avalonia.PropertyGenerator.CSharp.Demo/MainWindow.axaml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/Avalonia.PropertyGenerator.CSharp.Demo/Avalonia.PropertyGenerator.CSharp.Demo.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | WinExe 6 | False 7 | CA1812 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/Avalonia.PropertyGenerator.CSharp.Demo/App.axaml.cs: -------------------------------------------------------------------------------- 1 | using Avalonia.Controls.ApplicationLifetimes; 2 | using Avalonia.Markup.Xaml; 3 | 4 | namespace Avalonia.PropertyGenerator.CSharp.Demo 5 | { 6 | public sealed class App : Application 7 | { 8 | public override void Initialize() => AvaloniaXamlLoader.Load(this); 9 | 10 | public override void OnFrameworkInitializationCompleted() 11 | { 12 | if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) 13 | { 14 | desktop.MainWindow = new MainWindow(); 15 | } 16 | 17 | base.OnFrameworkInitializationCompleted(); 18 | } 19 | 20 | private static void Main(string[] args) => 21 | BuildAvaloniaApp().StartWithClassicDesktopLifetime(args); 22 | 23 | private static AppBuilder BuildAvaloniaApp() => 24 | AppBuilder.Configure() 25 | .UsePlatformDetect() 26 | .LogToTrace(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Avalonia.PropertyGenerator.CSharp/Avalonia.PropertyGenerator.CSharp.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | True 6 | False 7 | True 8 | True 9 | true 10 | 11 | 12 | 13 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | ===================== 3 | 4 | Copyright © jp2masa 5 | 6 | Permission is hereby granted, free of charge, to any person 7 | obtaining a copy of this software and associated documentation 8 | files (the “Software”), to deal in the Software without 9 | restriction, including without limitation the rights to use, 10 | copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the 12 | Software is furnished to do so, subject to the following 13 | conditions: 14 | 15 | The above copyright notice and this permission notice shall be 16 | included in all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 20 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 22 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 23 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 25 | OTHER DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: 0.11.0-build{build} 2 | image: Visual Studio 2022 3 | 4 | shallow_clone: true 5 | clone_folder: c:\Avalonia.PropertyGenerator 6 | 7 | configuration: 8 | - Debug 9 | - Release 10 | platform: Any CPU 11 | 12 | matrix: 13 | fast_finish: true 14 | 15 | nuget: 16 | account_feed: false 17 | project_feed: true 18 | disable_publish_on_pr: true 19 | 20 | build_script: 21 | - cmd: dotnet msbuild /t:Restore;Build;Pack Avalonia.PropertyGenerator.sln 22 | 23 | test: off 24 | 25 | artifacts: 26 | - path: 'artifacts\Debug\nupkg\*.nupkg' 27 | name: DebugNupkg 28 | - path: 'artifacts\Release\nupkg\*.nupkg' 29 | name: ReleaseNupkg 30 | 31 | deploy: 32 | - provider: NuGet 33 | api_key: 34 | secure: km9pZkPsOEo0CU5QdUy4q6pLM4mEgI/ib1MiHfV1KBBNSZ2Ja5pf3yGRTMNuTQiA 35 | artifact: ReleaseNupkg 36 | on: 37 | appveyor_repo_tag: true 38 | configuration: Release 39 | - provider: NuGet 40 | server: https://www.myget.org/F/jp2masa/api/v2/package 41 | artifact: DebugNupkg 42 | api_key: 43 | secure: puOcEbngEmaVMEnUL20u4mzATgvoyaTPRWGGwE98as1+8KGY3ypOKzt5OV63duwI 44 | on: 45 | branch: master 46 | configuration: Debug 47 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $(MSBuildThisFileDirectory) 5 | 6 | 7 | 8 | $(RepoRoot)\Icon.ico 9 | 10 | 11 | 12 | Latest 13 | Enable 14 | 15 | 16 | 17 | All 18 | True 19 | 20 | 21 | 22 | 0.11.0 23 | build.$(APPVEYOR_BUILD_NUMBER)+$(APPVEYOR_REPO_COMMIT.Substring(0, 7)) 24 | localbuild$([System.DateTime]::Now.ToString("yyyyMMddHHmmss")) 25 | beta2 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/Avalonia.PropertyGenerator.CSharp.Demo/DemoControl.axaml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/Avalonia.PropertyGenerator.CSharp/StyledProperty.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using Microsoft.CodeAnalysis; 4 | 5 | namespace Avalonia.PropertyGenerator.CSharp 6 | { 7 | internal sealed class StyledProperty : Property 8 | { 9 | private StyledProperty(IFieldSymbol field, string name, bool clrPropertyExists, Accessibility clrPropertyAccessibility) 10 | : base(field, name) 11 | { 12 | ClrPropertyExists = clrPropertyExists; 13 | ClrPropertyAccessibility = clrPropertyAccessibility; 14 | } 15 | 16 | public bool ClrPropertyExists { get; } 17 | 18 | public Accessibility ClrPropertyAccessibility { get; } 19 | 20 | public static new StyledProperty? TryCreate(Types types, IFieldSymbol field) 21 | { 22 | var name = GetPropertyName(field); 23 | 24 | if (name is null) 25 | { 26 | return null; 27 | } 28 | 29 | var clrPropertyExists = false; 30 | 31 | if (field.ContainingType is { } declaringType) 32 | { 33 | clrPropertyExists = declaringType.GetMembers().Any(x => String.Equals(x.Name, name, StringComparison.Ordinal)); 34 | } 35 | 36 | return new StyledProperty(field, name, clrPropertyExists, field.DeclaredAccessibility); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Avalonia.PropertyGenerator.CSharp/Visitors/AvaloniaPropertyRootVisitor.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | using Microsoft.CodeAnalysis; 3 | 4 | namespace Avalonia.PropertyGenerator.CSharp.Visitors 5 | { 6 | internal sealed class AvaloniaPropertyRootVisitor(Types types) 7 | : SymbolVisitor?> 8 | { 9 | private readonly AvaloniaPropertyDeclaringTypeVisitor _visitor = 10 | new AvaloniaPropertyDeclaringTypeVisitor(types); 11 | 12 | public override ImmutableArray? VisitNamespace(INamespaceSymbol symbol) 13 | { 14 | ImmutableArray.Builder? builder = null; 15 | 16 | foreach (var member in symbol.GetMembers()) 17 | { 18 | var ns = member.Accept(this); 19 | 20 | if (ns is not null) 21 | { 22 | (builder ??= ImmutableArray.CreateBuilder()) 23 | .AddRange(ns); 24 | } 25 | 26 | var type = member.Accept(_visitor); 27 | 28 | if (type is not null) 29 | { 30 | (builder ??= ImmutableArray.CreateBuilder()) 31 | .Add(type); 32 | } 33 | } 34 | 35 | return builder?.ToImmutable(); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Avalonia.PropertyGenerator.CSharp/Property.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.CodeAnalysis; 3 | 4 | namespace Avalonia.PropertyGenerator.CSharp 5 | { 6 | internal abstract class Property(IFieldSymbol field, string name) 7 | { 8 | public IFieldSymbol Field { get; } = field; 9 | 10 | public string Name { get; } = name; 11 | 12 | public static Property? TryCreate(Types types, IFieldSymbol field) 13 | { 14 | if (!field.IsStatic) 15 | { 16 | return null; 17 | } 18 | 19 | if (field.Type is INamedTypeSymbol type && type.IsGenericType) 20 | { 21 | type = type.ConstructUnboundGenericType(); 22 | 23 | if (SymbolEqualityComparer.Default.Equals(type, types.StyledProperty)) 24 | { 25 | return StyledProperty.TryCreate(types, field); 26 | } 27 | 28 | if (SymbolEqualityComparer.Default.Equals(type, types.DirectProperty)) 29 | { 30 | return DirectProperty.TryCreate(types, field); 31 | } 32 | 33 | if (SymbolEqualityComparer.Default.Equals(type, types.AttachedProperty)) 34 | { 35 | return AttachedProperty.TryCreate(types, field); 36 | } 37 | } 38 | 39 | return null; 40 | } 41 | 42 | public static string? GetPropertyName(IFieldSymbol field) 43 | { 44 | var i = field.Name.LastIndexOf("Property", StringComparison.Ordinal); 45 | 46 | if (i == -1 || i == 0) 47 | { 48 | return null; 49 | } 50 | 51 | return field.Name.Substring(0, i); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Avalonia.PropertyGenerator.CSharp/AttachedProperty.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using Microsoft.CodeAnalysis; 4 | 5 | namespace Avalonia.PropertyGenerator.CSharp 6 | { 7 | internal sealed class AttachedProperty : Property 8 | { 9 | private AttachedProperty( 10 | IFieldSymbol field, 11 | string name, 12 | bool getterExists, 13 | Accessibility getterAccessibility, 14 | bool setterExists, 15 | Accessibility setterAccessibility) 16 | : base(field, name) 17 | { 18 | GetterExists = getterExists; 19 | GetterAccessibility = getterAccessibility; 20 | 21 | SetterExists = setterExists; 22 | SetterAccessibility = setterAccessibility; 23 | } 24 | 25 | public bool GetterExists { get; } 26 | 27 | public Accessibility GetterAccessibility { get; } 28 | 29 | public bool SetterExists { get; } 30 | 31 | public Accessibility SetterAccessibility { get; } 32 | 33 | public static new AttachedProperty? TryCreate(Types types, IFieldSymbol field) 34 | { 35 | var name = GetPropertyName(field); 36 | 37 | if (name is null) 38 | { 39 | return null; 40 | } 41 | 42 | var getterName = "Get" + name; 43 | var setterName = "Set" + name; 44 | 45 | var getterExists = false; 46 | var setterExists = false; 47 | 48 | if (field.ContainingType is { } declaringType) 49 | { 50 | getterExists = declaringType.GetMembers().Any(x => String.Equals(x.Name, getterName, StringComparison.Ordinal)); 51 | setterExists = declaringType.GetMembers().Any(x => String.Equals(x.Name, setterName, StringComparison.Ordinal)); 52 | } 53 | 54 | return new AttachedProperty(field, name, getterExists, field.DeclaredAccessibility, setterExists, field.DeclaredAccessibility); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Avalonia.PropertyGenerator.CSharp.Demo/DemoControl.axaml.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | 3 | using Avalonia.Controls; 4 | using Avalonia.Controls.Primitives; 5 | 6 | namespace Avalonia.PropertyGenerator.CSharp.Demo 7 | { 8 | internal sealed partial class DemoControl : TemplatedControl 9 | { 10 | public static readonly StyledProperty NumberProperty = 11 | AvaloniaProperty.Register(nameof(Number)); 12 | 13 | public static readonly StyledProperty NullableNumberProperty = 14 | AvaloniaProperty.Register(nameof(NullableNumber)); 15 | 16 | [BackingField(Name = "m_text", Accessibility = BackingFieldAccessibility.Internal)] 17 | public static readonly DirectProperty TextProperty = 18 | AvaloniaProperty.RegisterDirect(nameof(Text), o => o.Text, (o, v) => o.Text = v); 19 | 20 | [Readonly] 21 | public static readonly DirectProperty ReadonlyTextProperty = 22 | AvaloniaProperty.RegisterDirect(nameof(ReadonlyText), o => o.ReadonlyText); 23 | 24 | public static readonly AttachedProperty BoolProperty = 25 | AvaloniaProperty.RegisterAttached("Bool"); 26 | 27 | public static readonly StyledProperty ExistingStyledProperty = 28 | AvaloniaProperty.Register(nameof(ExistingStyled)); 29 | 30 | public string ExistingStyled => GetValue(ExistingStyledProperty); 31 | 32 | static DemoControl() 33 | { 34 | NumberProperty.Changed.AddClassHandler( 35 | (sender, e) => 36 | { 37 | var str = e.NewValue.Value.ToString(CultureInfo.CurrentCulture); 38 | 39 | sender.SetAndRaise(ReadonlyTextProperty, ref sender._readonlyText, str); 40 | sender.SetValue(ExistingStyledProperty, str); 41 | } 42 | ); 43 | } 44 | 45 | public DemoControl() 46 | { 47 | m_text = "Hello World!"; 48 | _readonlyText = "0"; 49 | SetValue(ExistingStyledProperty, "0"); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Avalonia.PropertyGenerator.CSharp/Types.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.CodeAnalysis; 3 | 4 | namespace Avalonia.PropertyGenerator.CSharp 5 | { 6 | internal sealed record Types( 7 | INamedTypeSymbol AvaloniaObject, 8 | INamedTypeSymbol StyledProperty, 9 | INamedTypeSymbol DirectProperty, 10 | INamedTypeSymbol AttachedProperty) 11 | { 12 | private const string GetTypeWarningId = "AVPROP01"; 13 | private const string GetTypeWarningTitle = "AVPROP01"; 14 | private const string GetTypeWarningMessage = "Failed to load '{0}'"; 15 | private const string GetTypeWarningCategory = "Avalonia.PropertyGenerator.CSharp"; 16 | private const string GetTypeWarningDescription = "The Avalonia property generator was unable to find a property type from the Avalonia assemblies. Make sure Avalonia is correctly referenced. If you believe this is a bug, please report it: https://github.com/jp2masa/Avalonia.PropertyGenerator/issues/new."; 17 | 18 | private static readonly DiagnosticDescriptor GetTypeWarning = 19 | new DiagnosticDescriptor( 20 | GetTypeWarningId, 21 | GetTypeWarningTitle, 22 | GetTypeWarningMessage, 23 | GetTypeWarningCategory, 24 | DiagnosticSeverity.Warning, 25 | isEnabledByDefault: true, 26 | GetTypeWarningDescription); 27 | 28 | public static Types? FromCompilation(Compilation compilation, Action? reportDiagnostic) 29 | { 30 | INamedTypeSymbol? GetType(string name) 31 | { 32 | var type = compilation.GetTypeByMetadataName(name); 33 | 34 | if (type is null) 35 | { 36 | reportDiagnostic?.Invoke(Diagnostic.Create(GetTypeWarning, null, name)); 37 | return null; 38 | } 39 | 40 | if (type.IsGenericType && !type.IsUnboundGenericType) 41 | { 42 | type = type.ConstructUnboundGenericType(); 43 | } 44 | 45 | return type; 46 | } 47 | 48 | return GetType("Avalonia.AvaloniaObject") is { } avaloniaObject 49 | && GetType("Avalonia.StyledProperty`1") is { } styledProperty 50 | && GetType("Avalonia.DirectProperty`2") is { } directProperty 51 | && GetType("Avalonia.AttachedProperty`1") is { } attachedProperty 52 | ? new Types(avaloniaObject, styledProperty, directProperty, attachedProperty) 53 | : null; 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Avalonia.PropertyGenerator.CSharp/Visitors/AvaloniaPropertyDeclaringTypeVisitor.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | using Microsoft.CodeAnalysis; 3 | 4 | namespace Avalonia.PropertyGenerator.CSharp.Visitors 5 | { 6 | internal sealed class AvaloniaPropertyDeclaringTypeVisitor(Types types) 7 | : SymbolVisitor 8 | { 9 | private readonly Types _types = types; 10 | 11 | public override DeclaringType? VisitNamedType(INamedTypeSymbol symbol) 12 | { 13 | if (!symbol.IsStatic && !IsAvaloniaObject(symbol)) 14 | { 15 | return null; 16 | } 17 | 18 | ImmutableArray.Builder? styledBuilder = null; 19 | ImmutableArray.Builder? directBuilder = null; 20 | ImmutableArray.Builder? attachedBuilder = null; 21 | 22 | foreach (var member in symbol.GetMembers()) 23 | { 24 | if (member.Kind != SymbolKind.Field) 25 | { 26 | continue; 27 | } 28 | 29 | var property = Property.TryCreate(_types, (IFieldSymbol)member); 30 | 31 | switch (property) 32 | { 33 | case StyledProperty x when !symbol.IsStatic: 34 | (styledBuilder ??= ImmutableArray.CreateBuilder()).Add(x); 35 | break; 36 | case DirectProperty x when !symbol.IsStatic: 37 | (directBuilder ??= ImmutableArray.CreateBuilder()).Add(x); 38 | break; 39 | case AttachedProperty x: 40 | (attachedBuilder ??= ImmutableArray.CreateBuilder()).Add(x); 41 | break; 42 | } 43 | } 44 | 45 | var styled = styledBuilder?.ToImmutable() ?? ImmutableArray.Empty; 46 | var direct = directBuilder?.ToImmutable() ?? ImmutableArray.Empty; 47 | var attached = attachedBuilder?.ToImmutable() ?? ImmutableArray.Empty; 48 | 49 | return (styled.Length > 0 || direct.Length > 0 || attached.Length > 0) 50 | ? new DeclaringType(symbol, styled, direct, attached) 51 | : null; 52 | } 53 | 54 | private bool IsAvaloniaObject(INamedTypeSymbol? type) 55 | { 56 | while (type is not null) 57 | { 58 | if (SymbolEqualityComparer.Default.Equals(type, _types.AvaloniaObject)) 59 | { 60 | return true; 61 | } 62 | 63 | type = type.BaseType; 64 | } 65 | 66 | return false; 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Avalonia.PropertyGenerator.sln: -------------------------------------------------------------------------------- 1 | Microsoft Visual Studio Solution File, Format Version 12.00 2 | # Visual Studio Version 17 3 | VisualStudioVersion = 17.2.32630.192 4 | MinimumVisualStudioVersion = 10.0.40219.1 5 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{F1CA42E2-1B03-4AD0-9CF8-3835DD0CC728}" 6 | ProjectSection(SolutionItems) = preProject 7 | .editorconfig = .editorconfig 8 | appveyor.yml = appveyor.yml 9 | Directory.Build.props = Directory.Build.props 10 | Directory.Build.targets = Directory.Build.targets 11 | README.md = README.md 12 | EndProjectSection 13 | EndProject 14 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{A416D0AA-C885-4CD3-B89A-A33360D231E7}" 15 | EndProject 16 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Targets", "Targets", "{189398A8-47D7-4752-9AAF-6E9632AD9F98}" 17 | ProjectSection(SolutionItems) = preProject 18 | build\Targets\Import.props = build\Targets\Import.props 19 | build\Targets\Import.targets = build\Targets\Import.targets 20 | build\Targets\PackageProperties.props = build\Targets\PackageProperties.props 21 | build\Targets\PackageProperties.targets = build\Targets\PackageProperties.targets 22 | build\Targets\PackageVersions.targets = build\Targets\PackageVersions.targets 23 | build\Targets\RepoLayout.props = build\Targets\RepoLayout.props 24 | EndProjectSection 25 | EndProject 26 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.PropertyGenerator.CSharp", "src\Avalonia.PropertyGenerator.CSharp\Avalonia.PropertyGenerator.CSharp.csproj", "{5B1DE05B-3878-402D-B629-22FA03CDD588}" 27 | EndProject 28 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.PropertyGenerator.CSharp.Demo", "src\Avalonia.PropertyGenerator.CSharp.Demo\Avalonia.PropertyGenerator.CSharp.Demo.csproj", "{B9BBDBE5-50A0-4265-982F-36DB2AA12E12}" 29 | EndProject 30 | Global 31 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 32 | Debug|Any CPU = Debug|Any CPU 33 | Release|Any CPU = Release|Any CPU 34 | EndGlobalSection 35 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 36 | {5B1DE05B-3878-402D-B629-22FA03CDD588}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 37 | {5B1DE05B-3878-402D-B629-22FA03CDD588}.Debug|Any CPU.Build.0 = Debug|Any CPU 38 | {5B1DE05B-3878-402D-B629-22FA03CDD588}.Release|Any CPU.ActiveCfg = Release|Any CPU 39 | {5B1DE05B-3878-402D-B629-22FA03CDD588}.Release|Any CPU.Build.0 = Release|Any CPU 40 | {B9BBDBE5-50A0-4265-982F-36DB2AA12E12}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 41 | {B9BBDBE5-50A0-4265-982F-36DB2AA12E12}.Debug|Any CPU.Build.0 = Debug|Any CPU 42 | {B9BBDBE5-50A0-4265-982F-36DB2AA12E12}.Release|Any CPU.ActiveCfg = Release|Any CPU 43 | {B9BBDBE5-50A0-4265-982F-36DB2AA12E12}.Release|Any CPU.Build.0 = Release|Any CPU 44 | EndGlobalSection 45 | GlobalSection(SolutionProperties) = preSolution 46 | HideSolutionNode = FALSE 47 | EndGlobalSection 48 | GlobalSection(NestedProjects) = preSolution 49 | {A416D0AA-C885-4CD3-B89A-A33360D231E7} = {F1CA42E2-1B03-4AD0-9CF8-3835DD0CC728} 50 | {189398A8-47D7-4752-9AAF-6E9632AD9F98} = {A416D0AA-C885-4CD3-B89A-A33360D231E7} 51 | EndGlobalSection 52 | GlobalSection(ExtensibilityGlobals) = postSolution 53 | SolutionGuid = {3A9EB4DD-8527-4171-AA20-45BF527D9737} 54 | EndGlobalSection 55 | EndGlobal 56 | -------------------------------------------------------------------------------- /src/Avalonia.PropertyGenerator.CSharp/DirectProperty.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using Microsoft.CodeAnalysis; 4 | 5 | namespace Avalonia.PropertyGenerator.CSharp 6 | { 7 | internal sealed class DirectProperty : Property 8 | { 9 | private DirectProperty( 10 | IFieldSymbol field, 11 | string name, 12 | bool clrPropertyExists, 13 | Accessibility clrPropertyAccessibility, 14 | bool backingFieldExists, 15 | Accessibility backingFieldAccessibility, 16 | string backingFieldName, 17 | bool isReadonly) 18 | : base(field, name) 19 | { 20 | ClrPropertyExists = clrPropertyExists; 21 | ClrPropertyAccessibility = clrPropertyAccessibility; 22 | 23 | BackingFieldExists = backingFieldExists; 24 | BackingFieldAccessibility = backingFieldAccessibility; 25 | BackingFieldName = backingFieldName; 26 | 27 | IsReadonly = isReadonly; 28 | } 29 | 30 | public bool ClrPropertyExists { get; } 31 | 32 | public Accessibility ClrPropertyAccessibility { get; } 33 | 34 | public bool BackingFieldExists { get; } 35 | 36 | public Accessibility BackingFieldAccessibility { get; } 37 | 38 | public string BackingFieldName { get; } 39 | 40 | public bool IsReadonly { get; } 41 | 42 | public static new DirectProperty? TryCreate(Types types, IFieldSymbol field) 43 | { 44 | var name = GetPropertyName(field); 45 | 46 | if (name is null) 47 | { 48 | return null; 49 | } 50 | 51 | var backingFieldAccessibility = Accessibility.NotApplicable; 52 | var backingFieldName = default(string); 53 | var isReadonly = false; 54 | 55 | foreach (var attribute in field.GetAttributes()) 56 | { 57 | if (IsAttributeFullNameEqualTo(attribute, "Avalonia.PropertyGenerator.ReadonlyAttribute")) 58 | { 59 | isReadonly = true; 60 | } 61 | else if (IsAttributeFullNameEqualTo(attribute, "Avalonia.PropertyGenerator.BackingFieldAttribute")) 62 | { 63 | foreach (var arg in attribute.NamedArguments) 64 | { 65 | if (arg.Key.Equals("Name", StringComparison.Ordinal) 66 | && arg.Value.Value is string fieldName) 67 | { 68 | backingFieldName = fieldName; 69 | } 70 | else if (arg.Key.Equals("Accessibility", StringComparison.Ordinal) 71 | && arg.Value.Value is { } fieldAccessibility) 72 | { 73 | backingFieldAccessibility = (Accessibility)fieldAccessibility; 74 | } 75 | } 76 | } 77 | } 78 | 79 | if (backingFieldAccessibility == Accessibility.NotApplicable) 80 | { 81 | backingFieldAccessibility = Accessibility.Private; 82 | } 83 | 84 | backingFieldName ??= GetDefaultBackingFieldName(name); 85 | 86 | var clrPropertyExists = false; 87 | var backingFieldExists = false; 88 | 89 | if (field.ContainingType is { } declaringType) 90 | { 91 | clrPropertyExists = declaringType.GetMembers().Any(x => String.Equals(x.Name, name, StringComparison.Ordinal)); 92 | backingFieldExists = declaringType.GetMembers().Any(x => String.Equals(x.Name, backingFieldName, StringComparison.Ordinal)); 93 | } 94 | 95 | return new DirectProperty(field, name, clrPropertyExists, field.DeclaredAccessibility, backingFieldExists, backingFieldAccessibility, backingFieldName, isReadonly); 96 | } 97 | 98 | private static string GetDefaultBackingFieldName(string name) => 99 | String.IsNullOrWhiteSpace(name) 100 | ? throw new InvalidOperationException() 101 | : $"_{Char.ToLowerInvariant(name[0])}{name.Substring(1)}"; 102 | 103 | private static bool IsAttributeFullNameEqualTo(AttributeData attribute, string fullName) => 104 | String.Equals(attribute.AttributeClass?.ToDisplayString(), fullName, StringComparison.Ordinal); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build status](https://ci.appveyor.com/api/projects/status/1ow4vfhp9t92k3bh/branch/master?svg=true)](https://ci.appveyor.com/project/jp2masa/avalonia-propertygenerator/branch/master) 2 | [![NuGet](https://img.shields.io/nuget/v/jp2masa.Avalonia.PropertyGenerator.CSharp.svg)](https://www.nuget.org/packages/jp2masa.Avalonia.PropertyGenerator.CSharp/) 3 | [![MyGet](https://img.shields.io/myget/jp2masa/vpre/jp2masa.Avalonia.PropertyGenerator.CSharp.svg?label=myget)](https://www.myget.org/feed/jp2masa/package/nuget/jp2masa.Avalonia.PropertyGenerator.CSharp) 4 | 5 | # Avalonia.PropertyGenerator 6 | 7 | Avalonia.PropertyGenerator generates the appropriate CLR members for Avalonia property definitions. 8 | 9 | ## Usage 10 | 11 | 1. Add reference to `jp2masa.Avalonia.PropertyGenerator.CSharp` package: 12 | 13 | ```xml 14 | 15 | ``` 16 | 17 | 2. Declare Avalonia properties as usual, except the CLR members, which are now automatically generated! 18 | 19 | ## Example 20 | 21 | ### Source 22 | 23 | ```cs 24 | using System.Globalization; 25 | 26 | using Avalonia.Controls; 27 | using Avalonia.Controls.Primitives; 28 | 29 | namespace Avalonia.PropertyGenerator.CSharp.Demo 30 | { 31 | internal sealed partial class DemoControl : TemplatedControl 32 | { 33 | public static readonly StyledProperty NumberProperty = 34 | AvaloniaProperty.Register(nameof(Number)); 35 | 36 | public static readonly StyledProperty NullableNumberProperty = 37 | AvaloniaProperty.Register(nameof(NullableNumber)); 38 | 39 | [BackingField(Name = "m_text", Accessibility = BackingFieldAccessibility.Internal)] 40 | public static readonly DirectProperty TextProperty = 41 | AvaloniaProperty.RegisterDirect(nameof(Text), o => o.Text, (o, v) => o.Text = v); 42 | 43 | [Readonly] 44 | public static readonly DirectProperty ReadonlyTextProperty = 45 | AvaloniaProperty.RegisterDirect(nameof(ReadonlyText), o => o.ReadonlyText); 46 | 47 | public static readonly AttachedProperty BoolProperty = 48 | AvaloniaProperty.RegisterAttached("Bool"); 49 | 50 | public static readonly StyledProperty ExistingStyledProperty = 51 | AvaloniaProperty.Register(nameof(ExistingStyled)); 52 | 53 | public string ExistingStyled => GetValue(ExistingStyledProperty); 54 | 55 | static DemoControl() 56 | { 57 | NumberProperty.Changed.AddClassHandler( 58 | (sender, e) => 59 | { 60 | var str = e.NewValue.Value.ToString(CultureInfo.CurrentCulture); 61 | 62 | sender.SetAndRaise(ReadonlyTextProperty, ref sender._readonlyText, str); 63 | sender.SetValue(ExistingStyledProperty, str); 64 | } 65 | ); 66 | } 67 | 68 | public DemoControl() 69 | { 70 | m_text = "Hello World!"; 71 | _readonlyText = "0"; 72 | SetValue(ExistingStyledProperty, "0"); 73 | } 74 | } 75 | } 76 | ``` 77 | 78 | ### Generated code 79 | 80 | (To make the code more readable, generated attributes such as `GeneratedCode` and `ExcludeFromCodeCoverage` were removed.) 81 | 82 | ```cs 83 | namespace Avalonia.PropertyGenerator.CSharp.Demo 84 | { 85 | partial class DemoControl 86 | { 87 | public decimal Number 88 | { 89 | get => GetValue(NumberProperty); 90 | set => SetValue(NumberProperty, value); 91 | } 92 | 93 | public double? NullableNumber 94 | { 95 | get => GetValue(NullableNumberProperty); 96 | set => SetValue(NullableNumberProperty, value); 97 | } 98 | 99 | internal string? m_text; 100 | 101 | public string? Text 102 | { 103 | get => m_text; 104 | set => SetAndRaise(TextProperty, ref m_text, value); 105 | } 106 | 107 | private string _readonlyText; 108 | 109 | public string ReadonlyText => _readonlyText; 110 | 111 | public static bool GetBool(global::Avalonia.AvaloniaObject obj) => 112 | (obj ?? throw new global::System.ArgumentNullException(nameof(obj))).GetValue(BoolProperty); 113 | 114 | public static void SetBool(global::Avalonia.AvaloniaObject obj, bool value) => 115 | (obj ?? throw new global::System.ArgumentNullException(nameof(obj))).SetValue(BoolProperty, value); 116 | } 117 | } 118 | 119 | ``` 120 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = crlf 6 | indent_size = 4 7 | indent_style = space 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.cs] 12 | csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true:silent 13 | csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = true:silent 14 | csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true:silent 15 | csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true:silent 16 | csharp_style_allow_embedded_statements_on_same_line_experimental = true:silent 17 | csharp_style_conditional_delegate_call = true:warning 18 | csharp_style_deconstructed_variable_declaration = true:silent 19 | csharp_style_expression_bodied_accessors = true:suggestion 20 | csharp_style_expression_bodied_constructors = false:warning 21 | csharp_style_expression_bodied_indexers = true:suggestion 22 | csharp_style_expression_bodied_lambdas = true:suggestion 23 | csharp_style_expression_bodied_local_functions = true:suggestion 24 | csharp_style_expression_bodied_methods = true:suggestion 25 | csharp_style_expression_bodied_operators = true:suggestion 26 | csharp_style_expression_bodied_properties = true:suggestion 27 | csharp_style_implicit_object_creation_when_type_is_apparent = false:suggestion 28 | csharp_style_inlined_variable_declaration = true:warning 29 | csharp_style_namespace_declarations = block_scoped:warning 30 | csharp_style_pattern_matching_over_as_with_null_check = true:warning 31 | csharp_style_pattern_matching_over_is_with_cast_check = true:warning 32 | csharp_style_prefer_extended_property_pattern = true:silent 33 | csharp_style_prefer_implicitly_typed_lambda_expression = true:suggestion 34 | csharp_style_prefer_index_operator = true:suggestion 35 | csharp_style_prefer_local_over_anonymous_function = true:suggestion 36 | csharp_style_prefer_method_group_conversion = true:suggestion 37 | csharp_style_prefer_not_pattern = true:suggestion 38 | csharp_style_prefer_null_check_over_type_check = true:suggestion 39 | csharp_style_prefer_pattern_matching = true:silent 40 | csharp_style_prefer_primary_constructors = true:suggestion 41 | csharp_style_prefer_range_operator = true:suggestion 42 | csharp_style_prefer_readonly_struct_member = true:suggestion 43 | csharp_style_prefer_switch_expression = true:suggestion 44 | csharp_style_prefer_top_level_statements = false:suggestion 45 | csharp_style_prefer_tuple_swap = true:suggestion 46 | csharp_style_prefer_unbound_generic_type_in_nameof = true:suggestion 47 | csharp_style_prefer_utf8_string_literals = true:suggestion 48 | csharp_style_throw_expression = true:warning 49 | csharp_style_var_elsewhere = true:suggestion 50 | csharp_style_var_for_built_in_types = false:none 51 | csharp_style_var_when_type_is_apparent = true:suggestion 52 | 53 | csharp_new_line_before_catch = true:warning 54 | csharp_new_line_before_else = true:warning 55 | csharp_new_line_before_finally = true:warning 56 | csharp_new_line_before_members_in_anonymous_types = true:warning 57 | csharp_new_line_before_members_in_object_initializers = true:warning 58 | csharp_new_line_before_open_brace = all 59 | csharp_new_line_between_query_expression_clauses = true:warning 60 | 61 | csharp_indent_case_contents = true:warning 62 | csharp_indent_case_contents_when_block = false:warning 63 | csharp_indent_labels = one_less_than_current:suggestion 64 | csharp_indent_switch_labels = true:warning 65 | 66 | csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:warning 67 | 68 | csharp_preserve_single_line_blocks = true:suggestion 69 | csharp_preserve_single_line_statements = false:warning 70 | 71 | csharp_space_after_cast = false:warning 72 | csharp_space_after_colon_in_inheritance_clause = true:warning 73 | csharp_space_after_keywords_in_control_flow_statements = true:warning 74 | csharp_space_around_binary_operators = before_and_after:warning 75 | csharp_space_before_colon_in_inheritance_clause = true:warning 76 | csharp_space_between_method_call_empty_parameter_list_parentheses = false:warning 77 | csharp_space_between_method_call_name_and_opening_parenthesis = false:warning 78 | csharp_space_between_method_call_parameter_list_parentheses = false:warning 79 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false:warning 80 | csharp_space_between_method_declaration_parameter_list_parentheses = false:warning 81 | csharp_space_between_parentheses = false:warning 82 | 83 | csharp_prefer_braces = true:warning 84 | csharp_prefer_simple_default_expression = false:none 85 | csharp_prefer_simple_using_statement = true:suggestion 86 | csharp_prefer_system_threading_lock = true:suggestion 87 | 88 | csharp_using_directive_placement = outside_namespace:warning 89 | 90 | dotnet_code_quality.ca1801.api_surface = private, internal 91 | dotnet_code_quality_unused_parameters = all:suggestion 92 | 93 | dotnet_separate_import_directive_groups = true 94 | 95 | dotnet_sort_system_directives_first = true:suggestion 96 | 97 | dotnet_style_allow_multiple_blank_lines_experimental = false:suggestion 98 | dotnet_style_allow_statement_immediately_after_block_experimental = false:suggestion 99 | dotnet_style_coalesce_expression = true:warning 100 | dotnet_style_collection_initializer = true:suggestion 101 | dotnet_style_explicit_tuple_names = true:suggestion 102 | dotnet_style_namespace_match_folder = true:suggestion 103 | dotnet_style_null_propagation = true:warning 104 | dotnet_style_object_initializer = true:suggestion 105 | dotnet_style_operator_placement_when_wrapping = beginning_of_line 106 | dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:suggestion 107 | dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:suggestion 108 | dotnet_style_parentheses_in_other_operators = never_if_unnecessary:suggestion 109 | dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:suggestion 110 | dotnet_style_predefined_type_for_locals_parameters_members = true:warning 111 | dotnet_style_predefined_type_for_member_access = false:warning 112 | dotnet_style_prefer_auto_properties = true:warning 113 | dotnet_style_prefer_collection_expression = when_types_loosely_match:suggestion 114 | dotnet_style_prefer_compound_assignment = true:suggestion 115 | dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion 116 | dotnet_style_prefer_conditional_expression_over_return = true:suggestion 117 | dotnet_style_prefer_inferred_tuple_names = true:suggestion 118 | dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion 119 | dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning 120 | dotnet_style_prefer_simplified_boolean_expressions = true:suggestion 121 | dotnet_style_prefer_simplified_interpolation = true:suggestion 122 | dotnet_style_qualification_for_event = false:warning 123 | dotnet_style_qualification_for_field = false:warning 124 | dotnet_style_qualification_for_method = false:warning 125 | dotnet_style_qualification_for_property = false:warning 126 | dotnet_style_readonly_field = true:warning 127 | dotnet_style_require_accessibility_modifiers = for_non_interface_members:warning 128 | 129 | dotnet_naming_rule.camel_case_for_private_fields.severity = suggestion 130 | dotnet_naming_rule.camel_case_for_private_fields.symbols = private_fields 131 | dotnet_naming_rule.camel_case_for_private_fields.style = camel_case_underscore_style 132 | 133 | dotnet_naming_rule.camel_case_for_private_fields.severity = suggestion 134 | dotnet_naming_rule.camel_case_for_private_fields.symbols = private_static_fields 135 | dotnet_naming_rule.camel_case_for_private_fields.style = camel_case_s_underscore_style 136 | 137 | dotnet_naming_symbols.private_fields.applicable_kinds = field 138 | dotnet_naming_symbols.private_fields.applicable_accessibilities = private 139 | 140 | dotnet_naming_symbols.private_static_fields.applicable_kinds = field 141 | dotnet_naming_symbols.private_static_fields.applicable_accessibilities = private 142 | dotnet_naming_symbols.private_static_fields.required_modifiers = static 143 | 144 | dotnet_naming_style.camel_case_underscore_style.required_prefix = _ 145 | dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case 146 | 147 | dotnet_naming_style.camel_case_s_underscore_style.required_prefix = s_ 148 | dotnet_naming_style.camel_case_s_underscore_style.capitalization = camel_case 149 | -------------------------------------------------------------------------------- /src/Avalonia.PropertyGenerator.CSharp/AvaloniaToClrPropertyGenerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using System.Text; 4 | using Microsoft.CodeAnalysis; 5 | using Microsoft.CodeAnalysis.CSharp; 6 | using Microsoft.CodeAnalysis.Text; 7 | 8 | using Avalonia.PropertyGenerator.CSharp.Visitors; 9 | 10 | namespace Avalonia.PropertyGenerator.CSharp 11 | { 12 | [Generator] 13 | internal sealed class AvaloniaToClrPropertyGenerator : IIncrementalGenerator 14 | { 15 | private static readonly string s_generatorName = 16 | typeof(AvaloniaToClrPropertyGenerator).FullName!; 17 | 18 | private static readonly string s_generatorVersion = 19 | Assembly.GetExecutingAssembly().GetCustomAttribute().InformationalVersion; 20 | 21 | public void Initialize(IncrementalGeneratorInitializationContext context) => 22 | context.RegisterSourceOutput( 23 | context.CompilationProvider.Combine(context.ParseOptionsProvider), 24 | static (ctx, args) => Execute(ctx, args.Left, args.Right) 25 | ); 26 | 27 | private static void Execute(SourceProductionContext context, Compilation compilation, ParseOptions parseOptions) 28 | { 29 | if (Types.FromCompilation(compilation, context.ReportDiagnostic) is not { } wellKnownTypes) 30 | { 31 | return; 32 | } 33 | 34 | var attributes = 35 | $@"{AutoGeneratedFileHeader} 36 | namespace Avalonia.PropertyGenerator 37 | {{ 38 | /// 39 | /// Copied from roslyn: https://github.com/dotnet/roslyn/blob/9007fbc80c19cef52defe1cfb981c838b995c74d/src/Compilers/Core/Portable/Symbols/Accessibility.cs 40 | /// 41 | {GeneratedCodeAttribute} 42 | internal enum BackingFieldAccessibility 43 | {{ 44 | NotApplicable = 0, 45 | Private = 1, 46 | ProtectedAndInternal = 2, 47 | ProtectedAndFriend = ProtectedAndInternal, 48 | Protected = 3, 49 | Internal = 4, 50 | Friend = Internal, 51 | ProtectedOrInternal = 5, 52 | ProtectedOrFriend = ProtectedOrInternal, 53 | Public = 6, 54 | }} 55 | 56 | {GeneratedCodeAttribute} 57 | {ExcludeFromCodeCoverageAttribute} 58 | [global::System.AttributeUsage(global::System.AttributeTargets.Field)] 59 | internal sealed class BackingFieldAttribute : global::System.Attribute 60 | {{ 61 | public string? Name {{ get; set; }} 62 | 63 | public global::Avalonia.PropertyGenerator.BackingFieldAccessibility Accessibility {{ get; set; }} 64 | }} 65 | 66 | {GeneratedCodeAttribute} 67 | {ExcludeFromCodeCoverageAttribute} 68 | [global::System.AttributeUsage(global::System.AttributeTargets.Field)] 69 | internal sealed class ReadonlyAttribute : global::System.Attribute 70 | {{ 71 | }} 72 | }} 73 | "; 74 | 75 | var attributesSource = SourceText.From(attributes, Encoding.UTF8); 76 | 77 | if (parseOptions is not CSharpParseOptions csParseOptions) 78 | { 79 | throw new InvalidOperationException("Only C# is currently supported!"); 80 | } 81 | 82 | compilation = compilation.AddSyntaxTrees(CSharpSyntaxTree.ParseText(attributesSource, csParseOptions)); 83 | 84 | var visitor = new AvaloniaPropertyRootVisitor(wellKnownTypes); 85 | var types = visitor.Visit(compilation.Assembly.GlobalNamespace); 86 | 87 | if (!types.HasValue || types.Value.Length == 0) 88 | { 89 | return; 90 | } 91 | 92 | context.AddSource("Attributes.cs", attributesSource); 93 | 94 | foreach (var type in types) 95 | { 96 | var sourceBuilder = new StringBuilder( 97 | $@"{AutoGeneratedFileHeader} 98 | namespace {type.Type.ContainingNamespace.ToDisplayString()} 99 | {{"); 100 | 101 | sourceBuilder.Append($@" 102 | partial class {type.Type.Name} 103 | {{"); 104 | 105 | foreach (var property in type.StyledProperties) 106 | { 107 | 108 | if (!property.ClrPropertyExists) 109 | { 110 | var typeFullName = TypeToFullDisplayString(((INamedTypeSymbol)property.Field.Type).TypeArguments[0]); 111 | var accessibility = GetAccessibilityText(property.ClrPropertyAccessibility); 112 | 113 | sourceBuilder.Append($@" 114 | {GeneratedCodeAttribute} 115 | {ExcludeFromCodeCoverageAttribute} 116 | {accessibility}{typeFullName} {property.Name} 117 | {{ 118 | get => GetValue({property.Field.Name}); 119 | set => SetValue({property.Field.Name}, value); 120 | }} 121 | "); 122 | } 123 | } 124 | 125 | foreach (var property in type.DirectProperties) 126 | { 127 | var typeFullName = TypeToFullDisplayString(((INamedTypeSymbol)property.Field.Type).TypeArguments[1]); 128 | 129 | var propertyAccessibility = GetAccessibilityText(property.ClrPropertyAccessibility); 130 | var fieldAccessibility = GetAccessibilityText(property.BackingFieldAccessibility); 131 | 132 | var isReadonly = property.IsReadonly; 133 | 134 | if (!property.BackingFieldExists) 135 | { 136 | sourceBuilder.Append($@" 137 | {GeneratedCodeAttribute} 138 | {fieldAccessibility}{typeFullName} {property.BackingFieldName}; 139 | "); 140 | } 141 | 142 | if (!property.ClrPropertyExists) 143 | { 144 | if (isReadonly) 145 | { 146 | sourceBuilder.Append($@" 147 | {GeneratedCodeAttribute} 148 | {ExcludeFromCodeCoverageAttribute} 149 | {propertyAccessibility}{typeFullName} {property.Name} => {property.BackingFieldName}; 150 | "); 151 | } 152 | else 153 | { 154 | sourceBuilder.Append($@" 155 | {GeneratedCodeAttribute} 156 | {ExcludeFromCodeCoverageAttribute} 157 | {propertyAccessibility}{typeFullName} {property.Name} 158 | {{ 159 | get => {property.BackingFieldName}; 160 | set => SetAndRaise({property.Field.Name}, ref {property.BackingFieldName}, value); 161 | }} 162 | "); 163 | } 164 | } 165 | } 166 | 167 | foreach (var property in type.AttachedProperties) 168 | { 169 | var typeFullName = TypeToFullDisplayString(((INamedTypeSymbol)property.Field.Type).TypeArguments[0]); 170 | 171 | var getterAccessibility = GetAccessibilityText(property.GetterAccessibility); 172 | var setterAccessibility = GetAccessibilityText(property.SetterAccessibility); 173 | 174 | if (!property.GetterExists) 175 | { 176 | sourceBuilder.Append($@" 177 | {GeneratedCodeAttribute} 178 | {ExcludeFromCodeCoverageAttribute} 179 | {getterAccessibility}static {typeFullName} Get{property.Name}({TypeToFullDisplayString(wellKnownTypes.AvaloniaObject)} obj) => 180 | (obj ?? throw new global::System.ArgumentNullException(nameof(obj))).GetValue({property.Field.Name}); 181 | "); 182 | } 183 | 184 | if (!property.SetterExists) 185 | { 186 | sourceBuilder.Append($@" 187 | {GeneratedCodeAttribute} 188 | {ExcludeFromCodeCoverageAttribute} 189 | {setterAccessibility}static void Set{property.Name}({TypeToFullDisplayString(wellKnownTypes.AvaloniaObject)} obj, {typeFullName} value) => 190 | (obj ?? throw new global::System.ArgumentNullException(nameof(obj))).SetValue({property.Field.Name}, value); 191 | "); 192 | } 193 | } 194 | 195 | sourceBuilder.Append( 196 | @" } 197 | } 198 | "); 199 | 200 | var fileName = FormattableString.Invariant($"{type.Type.ToDisplayString()}.g.cs"); 201 | var sourceText = SourceText.From(sourceBuilder.ToString(), Encoding.UTF8); 202 | 203 | context.AddSource(fileName, sourceText); 204 | } 205 | } 206 | 207 | private static string GetAccessibilityText(Accessibility accessibility) 208 | { 209 | var result = SyntaxFacts.GetText(accessibility); 210 | 211 | if (!String.IsNullOrWhiteSpace(result)) 212 | { 213 | result += ' '; 214 | } 215 | 216 | return result; 217 | } 218 | 219 | private static string TypeToFullDisplayString(ITypeSymbol type) => 220 | type.ToDisplayString( 221 | SymbolDisplayFormat.FullyQualifiedFormat.WithMiscellaneousOptions( 222 | SymbolDisplayMiscellaneousOptions.EscapeKeywordIdentifiers 223 | | SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier 224 | | SymbolDisplayMiscellaneousOptions.UseSpecialTypes 225 | ) 226 | ); 227 | 228 | private static string AutoGeneratedFileHeader => 229 | @"// 230 | #pragma warning disable 231 | "; 232 | 233 | private static string GeneratedCodeAttribute => 234 | $@"[global::System.CodeDom.Compiler.GeneratedCode(""{s_generatorName}"", ""{s_generatorVersion}"")]"; 235 | 236 | private static string ExcludeFromCodeCoverageAttribute => 237 | "[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]"; 238 | } 239 | } 240 | --------------------------------------------------------------------------------