├── .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 | [](https://ci.appveyor.com/project/jp2masa/avalonia-propertygenerator/branch/master)
2 | [](https://www.nuget.org/packages/jp2masa.Avalonia.PropertyGenerator.CSharp/)
3 | [](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 |
--------------------------------------------------------------------------------