Skip to content

Commit d0b2734

Browse files
committed
fix(DependencyObjectGenerator): Handle nested classes properly
1 parent 1e9a811 commit d0b2734

File tree

4 files changed

+164
-187
lines changed

4 files changed

+164
-187
lines changed

src/SourceGenerators/SourceGeneratorHelpers/Helpers/SymbolExtensions.cs

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Collections.Generic;
77
using System.Linq;
88
using System.Text;
9+
using Uno;
910

1011
namespace Microsoft.CodeAnalysis
1112
{
@@ -44,24 +45,24 @@ internal static class SymbolExtensions
4445
/// Given an <see cref="INamedTypeSymbol"/>, add the symbol declaration (including parent classes/namespaces) to the given <see cref="IIndentedStringBuilder"/>.
4546
/// </summary>
4647
/// <remarks>
47-
/// <para>IMPORTANT: The returned stack must be disposed after putting everything for the given <see cref="INamedTypeSymbol"/>.</para>
4848
/// <para>Example usage:</para>
4949
/// <code><![CDATA[
50-
/// var stack = myClass.AddToIndentedStringBuilder(builder);
51-
/// using (builder.BlockInvariant("public static void M()"))
50+
/// using (myClass.AddToIndentedStringBuilder(builder))
5251
/// {
53-
/// builder.AppendLineInvariant("Console.WriteLine(\"Hello world\")");
54-
/// }
55-
///
56-
/// while (disposables.Count > 0)
57-
/// {
58-
/// disposables.Pop().Dispose();
52+
/// using (builder.BlockInvariant("public static void M()"))
53+
/// {
54+
/// builder.AppendLineInvariant("Console.WriteLine(\"Hello world\")");
55+
/// }
5956
/// }
6057
/// ]]></code>
6158
/// <para>NOTE: Another possible implementation is to accept an <see cref="Action"/> as a parameter to generate the type members, execute the action here, and also dispose
6259
/// the stack here. The advantage is that callers don't need to worry about disposing the stack.</para>
6360
/// </remarks>
64-
public static Stack<IDisposable> AddToIndentedStringBuilder(this INamedTypeSymbol namedTypeSymbol, IIndentedStringBuilder builder, Action<IIndentedStringBuilder>? beforeClassHeaderAction = null)
61+
public static IDisposable AddToIndentedStringBuilder(
62+
this INamedTypeSymbol namedTypeSymbol,
63+
IIndentedStringBuilder builder,
64+
Action<IIndentedStringBuilder>? beforeClassHeaderAction = null,
65+
string afterClassHeader = "")
6566
{
6667
var stack = new Stack<string>();
6768
ISymbol symbol = namedTypeSymbol;
@@ -78,7 +79,9 @@ public static Stack<IDisposable> AddToIndentedStringBuilder(this INamedTypeSymbo
7879
}
7980
else if (symbol is INamedTypeSymbol namedSymbol)
8081
{
81-
stack.Push(GetDeclarationHeaderFromNamedTypeSymbol(namedSymbol));
82+
// When generating the class header for the originally given namedTypeSymbol, invoke afterClassHeaderAction.
83+
// This is usually used to append the base types or interfaces.
84+
stack.Push(GetDeclarationHeaderFromNamedTypeSymbol(namedSymbol, ReferenceEquals(namedSymbol, namedTypeSymbol) ? afterClassHeader : null));
8285
}
8386
else
8487
{
@@ -100,10 +103,10 @@ public static Stack<IDisposable> AddToIndentedStringBuilder(this INamedTypeSymbo
100103
outputDisposableStack.Push(builder.BlockInvariant(stack.Pop()));
101104
}
102105

103-
return outputDisposableStack;
106+
return new DisposableAction(() => outputDisposableStack.ForEach(d => d.Dispose()));
104107
}
105108

106-
public static string GetDeclarationHeaderFromNamedTypeSymbol(this INamedTypeSymbol namedTypeSymbol)
109+
public static string GetDeclarationHeaderFromNamedTypeSymbol(this INamedTypeSymbol namedTypeSymbol, string? afterClassHeader)
107110
{
108111
// Interfaces are implicitly abstract, but they can't explicitly have the abstract modifier.
109112
var abstractKeyword = namedTypeSymbol.IsAbstract && !namedTypeSymbol.IsAbstract ? "abstract " : string.Empty;
@@ -120,7 +123,7 @@ public static string GetDeclarationHeaderFromNamedTypeSymbol(this INamedTypeSymb
120123

121124
var declarationIdentifier = namedTypeSymbol.ToDisplayString(s_format);
122125

123-
return $"{abstractKeyword}{staticKeyword}partial {typeKeyword}{declarationIdentifier}";
126+
return $"{abstractKeyword}{staticKeyword}partial {typeKeyword}{declarationIdentifier}{afterClassHeader}";
124127
}
125128

126129
public static IEnumerable<IPropertySymbol> GetProperties(this INamedTypeSymbol symbol) => symbol.GetMembers().OfType<IPropertySymbol>();

src/SourceGenerators/Uno.UI.SourceGenerators.Tests.Shared/Given_SymbolExtension.cs

Lines changed: 92 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,7 @@
11
using Microsoft.CodeAnalysis;
22
using Microsoft.CodeAnalysis.CSharp;
3-
using Microsoft.VisualStudio.TestTools.UnitTesting;
4-
using System;
5-
using System.Collections.Generic;
6-
using System.Linq;
73
using System.Reflection;
84
using Uno.Extensions;
9-
using Uno.UI.SourceGenerators.XamlGenerator.Utils;
105

116
namespace Uno.UI.SourceGenerators.Tests
127
{
@@ -48,141 +43,137 @@ public void When_GetFullyQualifiedString(string input, string expected)
4843
[TestMethod]
4944
public void When_Generating_Nested_Class()
5045
{
51-
var compilation = CreateCompilationWithProgramText(@"
52-
namespace A.B
53-
{
54-
namespace C
55-
{
56-
partial class D
57-
{
58-
partial class E
59-
{
60-
}
61-
}
62-
}
63-
}");
46+
var compilation = CreateCompilationWithProgramText("""
47+
namespace A.B
48+
{
49+
namespace C
50+
{
51+
partial class D
52+
{
53+
partial class E
54+
{
55+
}
56+
}
57+
}
58+
}
59+
""");
6460
var type = compilation.GetTypeByMetadataName("A.B.C.D+E");
6561
Assert.IsNotNull(type);
6662
var builder = new IndentedStringBuilder();
67-
var disposables = type.AddToIndentedStringBuilder(builder);
68-
Assert.AreEqual(@"namespace A.B.C
69-
{
70-
partial class D
71-
{
72-
partial class E
73-
{
74-
", builder.ToString());
75-
76-
while (disposables.Count > 0)
63+
using (type.AddToIndentedStringBuilder(builder))
7764
{
78-
disposables.Pop().Dispose();
65+
builder.AppendLineIndented("// Comment");
7966
}
8067

81-
Assert.AreEqual(@"namespace A.B.C
82-
{
83-
partial class D
84-
{
85-
partial class E
86-
{
87-
}
88-
}
89-
}
90-
", builder.ToString());
68+
Assert.AreEqual("""
69+
namespace A.B.C
70+
{
71+
partial class D
72+
{
73+
partial class E
74+
{
75+
// Comment
76+
}
77+
}
78+
}
79+
80+
""", builder.ToString());
9181
}
9282

9383
[TestMethod]
9484
public void When_Generating_Nested_Class_With_Action()
9585
{
96-
var compilation = CreateCompilationWithProgramText(@"
97-
namespace A.B
98-
{
99-
namespace C
100-
{
101-
partial class D
102-
{
103-
partial struct E
104-
{
105-
}
106-
}
107-
}
108-
}");
86+
var compilation = CreateCompilationWithProgramText("""
87+
namespace A.B
88+
{
89+
namespace C
90+
{
91+
partial class D
92+
{
93+
partial struct E
94+
{
95+
}
96+
}
97+
}
98+
}
99+
""");
109100
var type = compilation.GetTypeByMetadataName("A.B.C.D+E");
110101
Assert.IsNotNull(type);
111102
var builder = new IndentedStringBuilder();
112-
var disposables = type.AddToIndentedStringBuilder(builder, builder => builder.AppendLineIndented("[MyAttribute]"));
113-
Assert.AreEqual(@"namespace A.B.C
114-
{
115-
partial class D
116-
{
117-
[MyAttribute]
118-
partial struct E
119-
{
120-
", builder.ToString());
121-
122-
while (disposables.Count > 0)
103+
using (type.AddToIndentedStringBuilder(builder, builder => builder.AppendLineIndented("[MyAttribute]"), " : IMyInterface"))
123104
{
124-
disposables.Pop().Dispose();
105+
builder.AppendLineIndented("// Comment");
125106
}
126107

127-
Assert.AreEqual(@"namespace A.B.C
128-
{
129-
partial class D
130-
{
131-
[MyAttribute]
132-
partial struct E
133-
{
134-
}
135-
}
136-
}
137-
", builder.ToString());
108+
109+
Assert.AreEqual("""
110+
namespace A.B.C
111+
{
112+
partial class D
113+
{
114+
[MyAttribute]
115+
partial struct E : IMyInterface
116+
{
117+
// Comment
118+
}
119+
}
120+
}
121+
122+
""", builder.ToString());
138123
}
139124

140125
[TestMethod]
141126
public void When_Generating_Generic_Type()
142127
{
143-
var compilation = CreateCompilationWithProgramText(@"
144-
class C<T1, T2>
145-
{
146-
}");
128+
var compilation = CreateCompilationWithProgramText("""
129+
class C<T1, T2>
130+
{
131+
}
132+
""");
147133
var type = compilation.GetTypeByMetadataName("C`2");
148134
Assert.IsNotNull(type);
149135
var builder = new IndentedStringBuilder();
150-
var disposables = type.AddToIndentedStringBuilder(builder);
151-
while (disposables.Count > 0)
136+
using (type.AddToIndentedStringBuilder(builder))
152137
{
153-
disposables.Pop().Dispose();
138+
builder.AppendLineIndented("// Comment");
154139
}
155140

156-
Assert.AreEqual(@"partial class C<T1, T2>
157-
{
158-
}
159-
", builder.ToString());
141+
Assert.AreEqual("""
142+
partial class C<T1, T2>
143+
{
144+
// Comment
145+
}
146+
147+
""", builder.ToString());
160148
}
161149

162150
[TestMethod]
163151
public void When_Generating_Generic_Type_With_Variance()
164152
{
165-
var compilation = CreateCompilationWithProgramText(@"
166-
interface I<in T1, out T2, T3>
167-
{
168-
class C { }
169-
}");
153+
var compilation = CreateCompilationWithProgramText("""
154+
interface I<in T1, out T2, T3>
155+
{
156+
class C { }
157+
}
158+
""");
170159
var type = compilation.GetTypeByMetadataName("I`3+C");
171160
Assert.IsNotNull(type);
172161
var builder = new IndentedStringBuilder();
173-
var disposables = type.AddToIndentedStringBuilder(builder);
174-
while (disposables.Count > 0)
162+
using (type.AddToIndentedStringBuilder(builder))
175163
{
176-
disposables.Pop().Dispose();
164+
builder.AppendLineIndented("// Comment");
177165
}
166+
167+
Assert.AreEqual("""
168+
partial interface I<in T1, out T2, T3>
169+
{
170+
partial class C
171+
{
172+
// Comment
173+
}
174+
}
178175
179-
Assert.AreEqual(@"partial interface I<in T1, out T2, T3>
180-
{
181-
partial class C
182-
{
183-
}
184-
}
185-
", builder.ToString());
176+
""", builder.ToString());
186177
}
187178

188179
private static Compilation CreateTestCompilation(string type)

src/SourceGenerators/Uno.UI.SourceGenerators/DependencyObject/DependencyObjectGenerator.cs

Lines changed: 12 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -164,46 +164,29 @@ private void ProcessType(INamedTypeSymbol typeSymbol)
164164
using AppKit;
165165
#endif
166166
");
167-
168-
using (typeSymbol.ContainingNamespace.IsGlobalNamespace ? NullDisposable.Instance : builder.BlockInvariant($"namespace {typeSymbol.ContainingNamespace}"))
167+
Action<IIndentedStringBuilder> beforeClassHeaderAction = builder =>
169168
{
170-
using (GenerateNestingContainers(builder, typeSymbol))
169+
if (_bindableAttributeSymbol != null && typeSymbol.FindAttribute(_bindableAttributeSymbol) == null)
171170
{
172-
if (_bindableAttributeSymbol != null && typeSymbol.FindAttribute(_bindableAttributeSymbol) == null)
173-
{
174-
builder.AppendLineIndented(@"[global::Windows.UI.Xaml.Data.Bindable]");
175-
}
171+
builder.AppendLineIndented(@"[global::Windows.UI.Xaml.Data.Bindable]");
172+
}
176173

177-
AnalyzerSuppressionsGenerator.Generate(builder, _analyzerSuppressions);
174+
AnalyzerSuppressionsGenerator.Generate(builder, _analyzerSuppressions);
175+
};
178176

179-
var internalDependencyObject = _isUnoSolution && !typeSymbol.IsSealed ? ", IDependencyObjectInternal" : "";
177+
var internalDependencyObject = _isUnoSolution && !typeSymbol.IsSealed ? ", IDependencyObjectInternal" : "";
178+
using (typeSymbol.AddToIndentedStringBuilder(builder, beforeClassHeaderAction, afterClassHeader: $" : IDependencyObjectStoreProvider, IWeakReferenceProvider{internalDependencyObject}"))
179+
{
180+
AnalyzerSuppressionsGenerator.Generate(builder, _analyzerSuppressions);
180181

181-
using (builder.BlockInvariant($"partial class {typeSymbol.Name} : IDependencyObjectStoreProvider, IWeakReferenceProvider{internalDependencyObject}"))
182-
{
183-
GenerateDependencyObjectImplementation(typeSymbol, builder, hasDispatcherQueue: _dependencyObjectSymbol!.GetMembers("DispatcherQueue").Any());
184-
GenerateIBinderImplementation(typeSymbol, builder);
185-
}
186-
}
182+
GenerateDependencyObjectImplementation(typeSymbol, builder, hasDispatcherQueue: _dependencyObjectSymbol!.GetMembers("DispatcherQueue").Any());
183+
GenerateIBinderImplementation(typeSymbol, builder);
187184
}
188185

189186
_context.AddSource(HashBuilder.BuildIDFromSymbol(typeSymbol), builder.ToString());
190187
}
191188
}
192189

193-
private IDisposable GenerateNestingContainers(IndentedStringBuilder builder, INamedTypeSymbol? typeSymbol)
194-
{
195-
var disposables = new List<IDisposable>();
196-
197-
while (typeSymbol?.ContainingType != null)
198-
{
199-
disposables.Add(builder.BlockInvariant($"partial class {typeSymbol?.ContainingType.Name}"));
200-
201-
typeSymbol = typeSymbol?.ContainingType;
202-
}
203-
204-
return new DisposableAction(() => disposables.ForEach(d => d.Dispose()));
205-
}
206-
207190
private void GenerateIBinderImplementation(INamedTypeSymbol typeSymbol, IndentedStringBuilder builder)
208191
{
209192
WriteInitializer(typeSymbol, builder);

0 commit comments

Comments
 (0)