Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ private static EquatableArray<CustomPropertyInfo> GetCustomPropertyInfo(INamedTy
FullyQualifiedTypeName: propertySymbol.Type.GetFullyQualifiedNameWithNullabilityAnnotations(),
FullyQualifiedIndexerTypeName: indexerType?.GetFullyQualifiedNameWithNullabilityAnnotations(),
CanRead: propertySymbol.GetMethod is { DeclaredAccessibility: Accessibility.Public },
CanWrite: propertySymbol.SetMethod is { DeclaredAccessibility: Accessibility.Public },
CanWrite: propertySymbol.SetMethod is { DeclaredAccessibility: Accessibility.Public, IsInitOnly: false },
IsStatic: propertySymbol.IsStatic));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Reflection;
using Basic.Reference.Assemblies;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Emit;
using Microsoft.UI.Xaml.Controls;
using Windows.ApplicationModel.Core;

Expand Down Expand Up @@ -42,6 +44,27 @@ public static void VerifySources(string source, (string Filename, string Source)
Assert.AreEqual(expectedText, actualText);
}

/// <summary>
/// Compiles the generated sources and loads the resulting assembly.
/// </summary>
/// <param name="source">The input source to process.</param>
/// <param name="languageVersion">The language version to use to run the test.</param>
/// <returns>The compiled assembly.</returns>
public static Assembly Compile(string source, LanguageVersion languageVersion = LanguageVersion.CSharp14)
{
RunGenerator(source, languageVersion, out Compilation compilation, out ImmutableArray<Diagnostic> diagnostics);

CollectionAssert.AreEquivalent((Diagnostic[])[], diagnostics);

using MemoryStream stream = new();

EmitResult result = compilation.Emit(stream);

Assert.IsTrue(result.Success, string.Join("\n", result.Diagnostics));

return Assembly.Load(stream.ToArray());
}

/// <summary>
/// Creates a compilation from a given source.
/// </summary>
Expand Down
207 changes: 207 additions & 0 deletions src/Tests/SourceGenerator2Test/Test_CustomPropertyProviderGenerator.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,221 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.UI.Xaml.Data;
using WindowsRuntime.SourceGenerator.Tests.Helpers;

namespace WindowsRuntime.SourceGenerator.Tests;

[TestClass]
public class Test_CustomPropertyProviderGenerator
{
[TestMethod]
[DataRow("sealed partial class", false)]
[DataRow("sealed partial class", true)]
[DataRow("readonly partial struct", false)]
[DataRow("readonly partial struct", true)]
public void InitOnlyProperties_AreReadOnly(string typeDeclaration, bool isRequired)
{
string source = $$"""
using WindowsRuntime.Xaml;

namespace MyNamespace;

[GeneratedCustomPropertyProvider]
public {{typeDeclaration}} MyType
{
public {{(isRequired ? "required " : "")}}double Width { get; init; }

public {{(isRequired ? "required " : "")}}string Text { get; init; }

public static MyType Create() => new MyType { Width = 42, Text = "Initialized" };
}
""";

ICustomPropertyProvider provider = CreateProvider(source);

AssertReadOnlyProperty(provider, "Width", 42.0);
AssertReadOnlyProperty(provider, "Text", "Initialized");
}

[TestMethod]
public void InitOnlyProperties_MixedAccessors_PreserveWritability()
{
const string source = """
using WindowsRuntime.Xaml;

namespace MyNamespace;

[GeneratedCustomPropertyProvider]
public partial class MyType
{
private int indexedValue = 5;

public required string InitOnly { get; init; }

public string ReadOnly => "Read only";

public string Writable { get; set; } = "Before";

public string PrivateSetter { get; private set; } = "Private setter";

public string ProtectedSetter { get; protected set; } = "Protected setter";

public string InternalSetter { get; internal set; } = "Internal setter";

public string PrivateInit { get; private init; } = "Private init";

public int this[int index]
{
get => indexedValue + index;
set => indexedValue = value - index;
}

public static MyType Create() => new MyType { InitOnly = "Initialized" };
}
""";

ICustomPropertyProvider provider = CreateProvider(source);

AssertReadOnlyProperty(provider, "InitOnly", "Initialized");
AssertReadOnlyProperty(provider, "ReadOnly", "Read only");
AssertReadOnlyProperty(provider, "PrivateSetter", "Private setter");
AssertReadOnlyProperty(provider, "ProtectedSetter", "Protected setter");
AssertReadOnlyProperty(provider, "InternalSetter", "Internal setter");
AssertReadOnlyProperty(provider, "PrivateInit", "Private init");

ICustomProperty writable = provider.GetCustomProperty("Writable");

Assert.IsNotNull(writable);
Assert.IsTrue(writable.CanRead);
Assert.IsTrue(writable.CanWrite);
Assert.AreEqual("Before", writable.GetValue(provider));

writable.SetValue(provider, "After");

Assert.AreEqual("After", writable.GetValue(provider));

ICustomProperty indexer = provider.GetIndexedProperty("Item", typeof(int));

Assert.IsNotNull(indexer);
Assert.IsTrue(indexer.CanRead);
Assert.IsTrue(indexer.CanWrite);
Assert.AreEqual(7, indexer.GetIndexedValue(provider, 2));

indexer.SetIndexedValue(provider, 10, 2);

Assert.AreEqual(10, indexer.GetIndexedValue(provider, 2));
}

[TestMethod]
[DataRow(false)]
[DataRow(true)]
public void InitOnlyProperties_InheritedProperties_RespectSelection(bool explicitSelection)
{
string source = $$"""
using WindowsRuntime.Xaml;

namespace MyNamespace;

public class Base
{
public required string Inherited { get; init; }

public string Excluded { get; init; }
}

[GeneratedCustomPropertyProvider{{(explicitSelection ? "([\"Inherited\", \"Declared\"], [])" : "")}}]
public partial class MyType : Base
{
public string Declared { get; init; }

public static MyType Create() => new MyType
{
Inherited = "Base value",
Declared = "Derived value",
Excluded = "Excluded value"
};
}
""";

ICustomPropertyProvider provider = CreateProvider(source);

AssertReadOnlyProperty(provider, "Inherited", "Base value");
AssertReadOnlyProperty(provider, "Declared", "Derived value");

if (explicitSelection)
{
Assert.IsNull(provider.GetCustomProperty("Excluded"));
}
else
{
AssertReadOnlyProperty(provider, "Excluded", "Excluded value");
}
}

[TestMethod]
[DataRow(false)]
[DataRow(true)]
public void InitOnlyIndexer_IsReadOnly(bool explicitSelection)
{
string source = $$"""
using WindowsRuntime.Xaml;

namespace MyNamespace;

[GeneratedCustomPropertyProvider{{(explicitSelection ? "([], [typeof(int)])" : "")}}]
public partial class MyType
{
private int indexedValue;

public int this[int index]
{
get => indexedValue + index;
init => indexedValue = value - index;
}

public static MyType Create() => new MyType { [2] = 42 };
}
""";

ICustomPropertyProvider provider = CreateProvider(source);
ICustomProperty indexer = provider.GetIndexedProperty("Item", typeof(int));

Assert.IsNotNull(indexer);
Assert.IsTrue(indexer.CanRead);
Assert.IsFalse(indexer.CanWrite);
Assert.AreEqual(42, indexer.GetIndexedValue(provider, 2));
Assert.ThrowsExactly<NotSupportedException>(() => indexer.SetIndexedValue(provider, 100, 2));
Assert.AreEqual(42, indexer.GetIndexedValue(provider, 2));
}

private static ICustomPropertyProvider CreateProvider(string source)
{
Assembly assembly = CSharpGeneratorTest<CustomPropertyProviderGenerator>.Compile(source);
MethodInfo factory = assembly.GetType("MyNamespace.MyType", throwOnError: true).GetMethod("Create");

Assert.IsNotNull(factory);

return (ICustomPropertyProvider)factory.Invoke(null, null);
}

private static void AssertReadOnlyProperty(ICustomPropertyProvider provider, string name, object expectedValue)
{
ICustomProperty property = provider.GetCustomProperty(name);

Assert.IsNotNull(property);
Assert.IsTrue(property.CanRead);
Assert.IsFalse(property.CanWrite);
Assert.AreEqual(name, property.Name);
Assert.AreEqual(expectedValue.GetType(), property.Type);
Assert.AreEqual(expectedValue, property.GetValue(provider));
Assert.ThrowsExactly<NotSupportedException>(() => property.SetValue(provider, expectedValue));
Assert.AreEqual(expectedValue, property.GetValue(provider));
}

[TestMethod]
public async Task ValidClass_MixedProperties()
{
Expand Down