Skip to content

Commit 4d73d5e

Browse files
committed
DependencyPropertyGenerator
1 parent 4abe63d commit 4d73d5e

File tree

8 files changed

+248
-0
lines changed

8 files changed

+248
-0
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<Solution>
2+
<Project Path="ClassLibrary1/ClassLibrary1.csproj" />
3+
<Project Path="Generator.UnitTests/Generator.UnitTests.csproj" />
4+
<Project Path="Generator/Generator.csproj" />
5+
</Solution>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
using System.Windows;
2+
3+
namespace ClassLibrary1;
4+
5+
public partial class Class1 : DependencyObject
6+
{
7+
[DependencyPropertyGenerator.DependencyProperty]
8+
public partial int P { get; set; }
9+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net10.0-windows</TargetFramework>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
<Nullable>enable</Nullable>
7+
<UseWPF>true</UseWPF>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<ProjectReference Include="..\Generator\Generator.csproj">
12+
<OutputItemType>Analyzer</OutputItemType>
13+
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
14+
</ProjectReference>
15+
</ItemGroup>
16+
17+
</Project>
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
using Microsoft.CodeAnalysis;
2+
using Microsoft.CodeAnalysis.CSharp;
3+
using System.Diagnostics.CodeAnalysis;
4+
using System.Runtime.InteropServices;
5+
using Xunit;
6+
7+
namespace Generator.UnitTests;
8+
9+
public class DependencyPropertyGeneratorTests
10+
{
11+
[Fact]
12+
public void X()
13+
{
14+
RunGenerator("""
15+
class C
16+
{
17+
[DependencyPropertyGenerator.DependencyProperty]
18+
public partial int P { get; set; }
19+
}
20+
""",
21+
"C.P",
22+
"""
23+
using System.Windows;
24+
25+
partial class C
26+
{
27+
public static readonly DependencyProperty PProperty =
28+
DependencyProperty.Register(
29+
nameof(P),
30+
typeof(System.Int32),
31+
typeof(C),
32+
new PropertyMetadata(default(System.Int32)));
33+
34+
public partial System.Int32 P
35+
{
36+
get => (System.Int32)GetValue(PProperty);
37+
set => SetValue(PProperty, value);
38+
}
39+
}
40+
""");
41+
}
42+
43+
private static void RunGenerator(
44+
[StringSyntax("C#")] string targetSource,
45+
string fileName,
46+
[StringSyntax("C#")] string generatedSource)
47+
{
48+
var parseOptions = new CSharpParseOptions(LanguageVersion.Latest);
49+
var driver = CSharpGeneratorDriver.Create(new DependencyPropertyGenerator()).WithUpdatedParseOptions(parseOptions);
50+
51+
var compilation = CSharpCompilation.Create("GeneratorTest",
52+
references: [MetadataReference.CreateFromFile(typeof(object).Assembly.Location)],
53+
options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
54+
55+
compilation = compilation.AddSyntaxTrees(CSharpSyntaxTree.ParseText(targetSource, parseOptions));
56+
57+
driver.RunGeneratorsAndUpdateCompilation(compilation, out var newCompilation, out var diagnostics);
58+
59+
foreach (var t in newCompilation.SyntaxTrees)
60+
{
61+
if (t.FilePath.EndsWith(fileName + ".g.cs"))
62+
{
63+
var s = t.GetText().ToString();
64+
Assert.Equal(generatedSource, s);
65+
}
66+
}
67+
}
68+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<ImplicitUsings>enable</ImplicitUsings>
5+
<Nullable>enable</Nullable>
6+
<TargetFramework>net9.0</TargetFramework>
7+
<IsTestProject>true</IsTestProject>
8+
<IsPackable>false</IsPackable>
9+
<LangVersion>latest</LangVersion>
10+
<DisableTransitiveProjectReferences>false</DisableTransitiveProjectReferences>
11+
</PropertyGroup>
12+
13+
<ItemGroup>
14+
<ProjectReference Include="..\Generator\Generator.csproj" />
15+
</ItemGroup>
16+
17+
<ItemGroup>
18+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
19+
<PackageReference Include="xunit" Version="2.9.3" />
20+
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5">
21+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
22+
<PrivateAssets>all</PrivateAssets>
23+
</PackageReference>
24+
</ItemGroup>
25+
26+
</Project>
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
using Microsoft.CodeAnalysis;
2+
using Microsoft.CodeAnalysis.CSharp;
3+
using Microsoft.CodeAnalysis.CSharp.Syntax;
4+
5+
namespace Generator;
6+
7+
[Generator]
8+
public class DependencyPropertyGenerator : IIncrementalGenerator
9+
{
10+
public void Initialize(IncrementalGeneratorInitializationContext context)
11+
{
12+
context.RegisterPostInitializationOutput(ctx => ctx.AddSource(
13+
"DependencyPropertyAttribute.g.cs",
14+
/* lang=C# */
15+
"""
16+
using System;
17+
18+
namespace DependencyPropertyGenerator;
19+
20+
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
21+
public class DependencyPropertyAttribute : Attribute
22+
{
23+
}
24+
"""));
25+
26+
var propertyDeclarations = context.SyntaxProvider.ForAttributeWithMetadataName(
27+
"DependencyPropertyGenerator.DependencyPropertyAttribute",
28+
(node, _) => IsPartialProperty(node),
29+
(context, _) => GetSemanticTargetForGeneration(context));
30+
31+
context.RegisterSourceOutput(propertyDeclarations, static (spc, source) => Execute(source, spc));
32+
}
33+
34+
private static bool IsPartialProperty(SyntaxNode node)
35+
{
36+
return node is PropertyDeclarationSyntax propertyDeclaration &&
37+
propertyDeclaration.Modifiers.Any(SyntaxKind.PartialKeyword);
38+
}
39+
40+
private static PropertyInfo GetSemanticTargetForGeneration(GeneratorAttributeSyntaxContext context)
41+
{
42+
var p = (PropertyDeclarationSyntax)context.TargetNode;
43+
var t = context.SemanticModel.GetTypeInfo(p.Type).Type!;
44+
var ct = context.SemanticModel.GetDeclaredSymbol(p)!.ContainingType;
45+
46+
var pi = new PropertyInfo(
47+
p.Identifier.Text,
48+
t.ContainingNamespace + "." + t.Name,
49+
ct.Name,
50+
ct.ContainingNamespace.Name);
51+
52+
return pi;
53+
}
54+
55+
private static void Execute(PropertyInfo? propertyInfo, SourceProductionContext context)
56+
{
57+
if (propertyInfo is null)
58+
return;
59+
60+
var source = GenerateSource(propertyInfo);
61+
context.AddSource($"{propertyInfo.ClassName}.{propertyInfo.PropertyName}.g.cs", source);
62+
}
63+
64+
private static string GenerateSource(PropertyInfo propertyInfo)
65+
{
66+
var (propertyName, propertyType, className, namespaceName) = propertyInfo;
67+
68+
var namespaceDeclaration = string.IsNullOrEmpty(namespaceName)
69+
? string.Empty
70+
: $"""
71+
namespace {namespaceName};
72+
73+
74+
""";
75+
76+
return $$"""
77+
using System.Windows;
78+
79+
{{namespaceDeclaration}}partial class {{className}}
80+
{
81+
public static readonly DependencyProperty {{propertyName}}Property =
82+
DependencyProperty.Register(
83+
nameof({{propertyName}}),
84+
typeof({{propertyType}}),
85+
typeof({{className}}),
86+
new PropertyMetadata(default({{propertyType}})));
87+
88+
public partial {{propertyType}} {{propertyName}}
89+
{
90+
get => ({{propertyType}})GetValue({{propertyName}}Property);
91+
set => SetValue({{propertyName}}Property, value);
92+
}
93+
}
94+
""";
95+
}
96+
97+
private record PropertyInfo(
98+
string PropertyName,
99+
string PropertyType,
100+
string ClassName,
101+
string Namespace);
102+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>netstandard2.0</TargetFramework>
5+
<Nullable>enable</Nullable>
6+
<LangVersion>14</LangVersion>
7+
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="5.0.0" />
12+
</ItemGroup>
13+
14+
</Project>
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
namespace System.Runtime.CompilerServices
2+
{
3+
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Interface | AttributeTargets.Enum | AttributeTargets.Delegate, Inherited = false)]
4+
internal sealed class IsExternalInit : Attribute
5+
{
6+
}
7+
}

0 commit comments

Comments
 (0)