mirror of
https://github.com/CommunityToolkit/dotnet.git
synced 2026-10-22 23:39:59 +08:00
243 lines
13 KiB
C#
243 lines
13 KiB
C#
// Licensed to the .NET Foundation under one or more agreements.
|
|
// The .NET Foundation licenses this file to you under the MIT license.
|
|
// See the LICENSE file in the project root for more information.
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Collections.Immutable;
|
|
using System.ComponentModel.DataAnnotations;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using CommunityToolkit.Mvvm.ComponentModel;
|
|
using Microsoft.CodeAnalysis;
|
|
using Microsoft.CodeAnalysis.CSharp;
|
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
|
|
|
namespace CommunityToolkit.Mvvm.SourceGenerators.UnitTests;
|
|
|
|
[TestClass]
|
|
public class Test_SourceGeneratorsCodegen
|
|
{
|
|
[TestMethod]
|
|
public void ObservablePropertyWithPartialMethodWithPreviousValuesNotUsed_DoesNotGenerateFieldReadAndMarksOldValueAsNullable()
|
|
{
|
|
string source = """
|
|
using System.ComponentModel;
|
|
using CommunityToolkit.Mvvm.ComponentModel;
|
|
|
|
#nullable enable
|
|
|
|
namespace MyApp;
|
|
|
|
partial class MyViewModel : ObservableObject
|
|
{
|
|
[ObservableProperty]
|
|
private string name = null!;
|
|
}
|
|
""";
|
|
|
|
string result = """
|
|
// <auto-generated/>
|
|
#pragma warning disable
|
|
#nullable enable
|
|
namespace MyApp
|
|
{
|
|
partial class MyViewModel
|
|
{
|
|
/// <inheritdoc cref="name"/>
|
|
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.1.0.0")]
|
|
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
|
|
public string Name
|
|
{
|
|
get => name;
|
|
set
|
|
{
|
|
if (!global::System.Collections.Generic.EqualityComparer<string>.Default.Equals(name, value))
|
|
{
|
|
OnNameChanging(value);
|
|
OnNameChanging(default, value);
|
|
OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Name);
|
|
name = value;
|
|
OnNameChanged(value);
|
|
OnNameChanged(default, value);
|
|
OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Name);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>Executes the logic for when <see cref="Name"/> is changing.</summary>
|
|
/// <param name="value">The new property value being set.</param>
|
|
/// <remarks>This method is invoked right before the value of <see cref="Name"/> is changed.</remarks>
|
|
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.1.0.0")]
|
|
partial void OnNameChanging(string value);
|
|
/// <summary>Executes the logic for when <see cref="Name"/> is changing.</summary>
|
|
/// <param name="oldValue">The previous property value that is being replaced.</param>
|
|
/// <param name="newValue">The new property value being set.</param>
|
|
/// <remarks>This method is invoked right before the value of <see cref="Name"/> is changed.</remarks>
|
|
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.1.0.0")]
|
|
partial void OnNameChanging(string? oldValue, string newValue);
|
|
/// <summary>Executes the logic for when <see cref="Name"/> just changed.</summary>
|
|
/// <param name="value">The new property value that was set.</param>
|
|
/// <remarks>This method is invoked right after the value of <see cref="Name"/> is changed.</remarks>
|
|
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.1.0.0")]
|
|
partial void OnNameChanged(string value);
|
|
/// <summary>Executes the logic for when <see cref="Name"/> just changed.</summary>
|
|
/// <param name="oldValue">The previous property value that was replaced.</param>
|
|
/// <param name="newValue">The new property value that was set.</param>
|
|
/// <remarks>This method is invoked right after the value of <see cref="Name"/> is changed.</remarks>
|
|
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.1.0.0")]
|
|
partial void OnNameChanged(string? oldValue, string newValue);
|
|
}
|
|
}
|
|
""";
|
|
|
|
VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.MyViewModel.g.cs", result));
|
|
}
|
|
|
|
// See https://github.com/CommunityToolkit/dotnet/issues/601
|
|
[TestMethod]
|
|
public void ObservablePropertyWithForwardedAttributesWithNumberLiterals_PreservesType()
|
|
{
|
|
string source = """
|
|
using System.ComponentModel;
|
|
using CommunityToolkit.Mvvm.ComponentModel;
|
|
|
|
#nullable enable
|
|
|
|
namespace MyApp;
|
|
|
|
partial class MyViewModel : ObservableObject
|
|
{
|
|
const double MyDouble = 3.14;
|
|
const float MyFloat = 3.14f;
|
|
|
|
[ObservableProperty]
|
|
[property: DefaultValue(0.0)]
|
|
[property: DefaultValue(1.24)]
|
|
[property: DefaultValue(0.0f)]
|
|
[property: DefaultValue(0.0f)]
|
|
[property: DefaultValue(MyDouble)]
|
|
[property: DefaultValue(MyFloat)]
|
|
private object? a;
|
|
}
|
|
|
|
public class DefaultValueAttribute : Attribute
|
|
{
|
|
public DefaultValueAttribute(object value)
|
|
{
|
|
}
|
|
}
|
|
""";
|
|
|
|
string result = """
|
|
// <auto-generated/>
|
|
#pragma warning disable
|
|
#nullable enable
|
|
namespace MyApp
|
|
{
|
|
partial class MyViewModel
|
|
{
|
|
/// <inheritdoc cref="a"/>
|
|
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.1.0.0")]
|
|
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
|
|
[global::MyApp.DefaultValueAttribute(0D)]
|
|
[global::MyApp.DefaultValueAttribute(1.24D)]
|
|
[global::MyApp.DefaultValueAttribute(0F)]
|
|
[global::MyApp.DefaultValueAttribute(0F)]
|
|
[global::MyApp.DefaultValueAttribute(3.14D)]
|
|
[global::MyApp.DefaultValueAttribute(3.14F)]
|
|
public object? A
|
|
{
|
|
get => a;
|
|
set
|
|
{
|
|
if (!global::System.Collections.Generic.EqualityComparer<object?>.Default.Equals(a, value))
|
|
{
|
|
OnAChanging(value);
|
|
OnAChanging(default, value);
|
|
OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.A);
|
|
a = value;
|
|
OnAChanged(value);
|
|
OnAChanged(default, value);
|
|
OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.A);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>Executes the logic for when <see cref="A"/> is changing.</summary>
|
|
/// <param name="value">The new property value being set.</param>
|
|
/// <remarks>This method is invoked right before the value of <see cref="A"/> is changed.</remarks>
|
|
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.1.0.0")]
|
|
partial void OnAChanging(object? value);
|
|
/// <summary>Executes the logic for when <see cref="A"/> is changing.</summary>
|
|
/// <param name="oldValue">The previous property value that is being replaced.</param>
|
|
/// <param name="newValue">The new property value being set.</param>
|
|
/// <remarks>This method is invoked right before the value of <see cref="A"/> is changed.</remarks>
|
|
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.1.0.0")]
|
|
partial void OnAChanging(object? oldValue, object? newValue);
|
|
/// <summary>Executes the logic for when <see cref="A"/> just changed.</summary>
|
|
/// <param name="value">The new property value that was set.</param>
|
|
/// <remarks>This method is invoked right after the value of <see cref="A"/> is changed.</remarks>
|
|
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.1.0.0")]
|
|
partial void OnAChanged(object? value);
|
|
/// <summary>Executes the logic for when <see cref="A"/> just changed.</summary>
|
|
/// <param name="oldValue">The previous property value that was replaced.</param>
|
|
/// <param name="newValue">The new property value that was set.</param>
|
|
/// <remarks>This method is invoked right after the value of <see cref="A"/> is changed.</remarks>
|
|
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.1.0.0")]
|
|
partial void OnAChanged(object? oldValue, object? newValue);
|
|
}
|
|
}
|
|
""";
|
|
|
|
VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.MyViewModel.g.cs", result));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generates the requested sources
|
|
/// </summary>
|
|
/// <param name="source">The input source to process.</param>
|
|
/// <param name="generators">The generators to apply to the input syntax tree.</param>
|
|
/// <param name="results">The source files to compare.</param>
|
|
private static void VerifyGenerateSources(string source, IIncrementalGenerator[] generators, params (string Filename, string Text)[] results)
|
|
{
|
|
// Ensure CommunityToolkit.Mvvm and System.ComponentModel.DataAnnotations are loaded
|
|
Type observableObjectType = typeof(ObservableObject);
|
|
Type validationAttributeType = typeof(ValidationAttribute);
|
|
|
|
// Get all assembly references for the loaded assemblies (easy way to pull in all necessary dependencies)
|
|
IEnumerable<MetadataReference> references =
|
|
from assembly in AppDomain.CurrentDomain.GetAssemblies()
|
|
where !assembly.IsDynamic
|
|
let reference = MetadataReference.CreateFromFile(assembly.Location)
|
|
select reference;
|
|
|
|
SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(source, CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp11));
|
|
|
|
// Create a syntax tree with the input source
|
|
CSharpCompilation compilation = CSharpCompilation.Create(
|
|
"original",
|
|
new SyntaxTree[] { syntaxTree },
|
|
references,
|
|
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
|
|
|
|
GeneratorDriver driver = CSharpGeneratorDriver.Create(generators).WithUpdatedParseOptions((CSharpParseOptions)syntaxTree.Options);
|
|
|
|
// Run all source generators on the input source code
|
|
_ = driver.RunGeneratorsAndUpdateCompilation(compilation, out Compilation outputCompilation, out ImmutableArray<Diagnostic> diagnostics);
|
|
|
|
// Ensure that no diagnostics were generated
|
|
CollectionAssert.AreEquivalent(Array.Empty<Diagnostic>(), diagnostics);
|
|
|
|
foreach ((string filename, string text) in results)
|
|
{
|
|
SyntaxTree generatedTree = outputCompilation.SyntaxTrees.Single(tree => Path.GetFileName(tree.FilePath) == filename);
|
|
|
|
Assert.AreEqual(text, generatedTree.ToString());
|
|
}
|
|
|
|
GC.KeepAlive(observableObjectType);
|
|
GC.KeepAlive(validationAttributeType);
|
|
}
|
|
}
|