csharpc-sharpdotnetxamlavaloniauicross-platformcross-platform-xamlavaloniaguimulti-platformuser-interfacedotnetcore
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
488 lines
15 KiB
488 lines
15 KiB
using System.Diagnostics.CodeAnalysis;
|
|
using System.Threading.Tasks;
|
|
using Avalonia.Analyzers.GeneratedProperties;
|
|
using Microsoft.CodeAnalysis;
|
|
using Microsoft.CodeAnalysis.CSharp;
|
|
using Microsoft.CodeAnalysis.CSharp.Testing;
|
|
using Microsoft.CodeAnalysis.Testing;
|
|
using Xunit;
|
|
|
|
namespace Avalonia.Analyzers.Tests.GeneratedProperties;
|
|
|
|
public class GeneratedPropertyAnalyzerTests
|
|
{
|
|
private static Task Verify([StringSyntax("csharp")] string source, LanguageVersion? languageVersion = null)
|
|
{
|
|
var test = new GeneratedPropertyAnalyzerTest
|
|
{
|
|
TestCode = """
|
|
using Avalonia;
|
|
using Avalonia.Data;
|
|
|
|
""" + source
|
|
};
|
|
if (languageVersion is { } version)
|
|
{
|
|
test.LanguageVersion = version;
|
|
}
|
|
|
|
return test.RunAsync(TestContext.Current.CancellationToken);
|
|
}
|
|
|
|
[Fact]
|
|
public Task Valid_Properties_Report_Nothing() => Verify(
|
|
"""
|
|
public partial class MyControl : AvaloniaObject
|
|
{
|
|
[GeneratedStyledProperty(DefaultValue = 100)]
|
|
public partial int Width { get; set; }
|
|
|
|
[GeneratedDirectProperty]
|
|
public partial string Text { get; set; } = "";
|
|
|
|
[GeneratedAttachedProperty]
|
|
public static partial int GetRow(Visual element);
|
|
}
|
|
""");
|
|
|
|
[Fact]
|
|
public Task Valid_Callback_Reports_Nothing() => Verify(
|
|
"""
|
|
public partial class MyControl : AvaloniaObject
|
|
{
|
|
[GeneratedStyledProperty(ValidateMethodName = nameof(ValidateValue), CoerceMethodName = nameof(CoerceValue))]
|
|
public partial int Value { get; set; }
|
|
|
|
private static bool ValidateValue(int value) => value >= 0;
|
|
|
|
private static int CoerceValue(AvaloniaObject sender, int value) => value;
|
|
}
|
|
""");
|
|
|
|
[Fact]
|
|
public Task Missing_Base_Class_Reports_AVP2001() => Verify(
|
|
"""
|
|
public partial class MyControl
|
|
{
|
|
[GeneratedStyledProperty]
|
|
public partial string? {|AVP2001:Header|} { get; set; }
|
|
}
|
|
""");
|
|
|
|
[Fact]
|
|
public Task Attached_AddOwner_On_Static_Class_Reports_AVP2001() => Verify(
|
|
"""
|
|
public class BasePanel : AvaloniaObject
|
|
{
|
|
public static readonly AttachedProperty<int> RowProperty =
|
|
AvaloniaProperty.RegisterAttached<BasePanel, Visual, int>("Row");
|
|
}
|
|
|
|
public static partial class Helper
|
|
{
|
|
// AddOwner<T> requires generic host parameter, which is not possible in this context (static class)
|
|
[GeneratedAttachedProperty(AddOwnerFrom = typeof(BasePanel))]
|
|
public static partial int {|AVP2001:GetRow|}(Visual element);
|
|
}
|
|
""");
|
|
|
|
[Fact]
|
|
public Task Attached_On_Static_Class_Reports_Nothing() => Verify(
|
|
"""
|
|
public static partial class Helper
|
|
{
|
|
[GeneratedAttachedProperty]
|
|
public static partial int GetRow(Visual element);
|
|
}
|
|
""");
|
|
|
|
[Fact]
|
|
public Task Inherits_With_Add_Owner_Reports_AVP2002() => Verify(
|
|
"""
|
|
public class RangeBase : AvaloniaObject
|
|
{
|
|
public static readonly StyledProperty<double> ValueProperty =
|
|
AvaloniaProperty.Register<RangeBase, double>("Value");
|
|
}
|
|
|
|
public partial class MyControl : RangeBase
|
|
{
|
|
// AddOwner() doesn't have inherits argument.
|
|
[GeneratedStyledProperty(AddOwnerFrom = typeof(RangeBase), {|AVP2002:Inherits = true|})]
|
|
public partial double Value { get; set; }
|
|
}
|
|
""");
|
|
|
|
[Fact]
|
|
public Task Multiple_Generator_Attributes_Report_AVP2002() => Verify(
|
|
"""
|
|
public partial class MyControl : AvaloniaObject
|
|
{
|
|
[GeneratedStyledProperty]
|
|
[GeneratedDirectProperty]
|
|
public partial string? {|AVP2002:Header|} { get; set; }
|
|
}
|
|
""");
|
|
|
|
[Fact]
|
|
public Task Incompatible_Default_Value_Reports_AVP2003() => Verify(
|
|
"""
|
|
public partial class MyControl : AvaloniaObject
|
|
{
|
|
[GeneratedStyledProperty({|AVP2003:DefaultValue = "text"|})]
|
|
public partial int Value { get; set; }
|
|
}
|
|
""");
|
|
|
|
[Fact]
|
|
public Task Incompatible_Unset_Value_Reports_AVP2003() => Verify(
|
|
"""
|
|
public partial class MyControl : AvaloniaObject
|
|
{
|
|
[GeneratedDirectProperty({|AVP2003:UnsetValue = "text"|})]
|
|
public partial int Value { get; set; }
|
|
}
|
|
""");
|
|
|
|
[Fact]
|
|
public Task Missing_Add_Owner_Source_Reports_AVP2004() => Verify(
|
|
"""
|
|
public class EmptyBase : AvaloniaObject
|
|
{
|
|
// ValueProperty expected
|
|
}
|
|
|
|
public partial class MyControl : EmptyBase
|
|
{
|
|
[GeneratedStyledProperty({|AVP2004:AddOwnerFrom = typeof(EmptyBase)|})]
|
|
public partial double Value { get; set; }
|
|
}
|
|
""");
|
|
|
|
[Fact]
|
|
public Task AddOwner_From_Generated_Styled_Property_Reports_Nothing() => Verify(
|
|
"""
|
|
public partial class RangeBase : AvaloniaObject
|
|
{
|
|
// Source property itself is source-generated.
|
|
[GeneratedStyledProperty]
|
|
public partial double Value { get; set; }
|
|
}
|
|
|
|
public partial class MyControl : RangeBase
|
|
{
|
|
[GeneratedStyledProperty(AddOwnerFrom = typeof(RangeBase))]
|
|
public new partial double Value { get; set; }
|
|
}
|
|
""");
|
|
|
|
[Fact]
|
|
public Task AddOwner_From_Generated_Direct_Property_Reports_Nothing() => Verify(
|
|
"""
|
|
public partial class TextBase : AvaloniaObject
|
|
{
|
|
[GeneratedDirectProperty]
|
|
public partial string? Text { get; set; }
|
|
}
|
|
|
|
public partial class MyControl : TextBase
|
|
{
|
|
[GeneratedDirectProperty(AddOwnerFrom = typeof(TextBase))]
|
|
public new partial string? Text { get; set; }
|
|
}
|
|
""");
|
|
|
|
[Fact]
|
|
public Task AddOwner_From_Generated_Attached_Property_Reports_Nothing() => Verify(
|
|
"""
|
|
public partial class BasePanel : AvaloniaObject
|
|
{
|
|
[GeneratedAttachedProperty]
|
|
public static partial int GetRow(Visual element);
|
|
}
|
|
|
|
public partial class MyPanel : BasePanel
|
|
{
|
|
[GeneratedAttachedProperty(AddOwnerFrom = typeof(BasePanel))]
|
|
public static partial int GetRow(Visual element);
|
|
}
|
|
""");
|
|
|
|
[Fact]
|
|
public Task AddOwner_From_Generated_Property_Type_Mismatch_Reports_AVP2004() => Verify(
|
|
"""
|
|
public partial class RangeBase : AvaloniaObject
|
|
{
|
|
[GeneratedStyledProperty]
|
|
public partial double Value { get; set; }
|
|
}
|
|
|
|
public partial class MyControl : RangeBase
|
|
{
|
|
// Source generated property exists but its value type is 'double', not 'int'.
|
|
[GeneratedStyledProperty({|AVP2004:AddOwnerFrom = typeof(RangeBase)|})]
|
|
public new partial int Value { get; set; }
|
|
}
|
|
""");
|
|
|
|
[Fact]
|
|
public Task Attached_Without_Get_Prefix_Reports_AVP2005() => Verify(
|
|
"""
|
|
public partial class Grid : AvaloniaObject
|
|
{
|
|
[GeneratedAttachedProperty]
|
|
public static partial int {|AVP2005:Row|}(Visual element);
|
|
}
|
|
""");
|
|
|
|
[Fact]
|
|
public Task Attached_Not_Static_Reports_AVP2005() => Verify(
|
|
"""
|
|
public partial class Grid : AvaloniaObject
|
|
{
|
|
// GetRow expected to be static
|
|
[GeneratedAttachedProperty]
|
|
public partial int {|AVP2005:GetRow|}(Visual element);
|
|
}
|
|
""");
|
|
|
|
[Fact]
|
|
public Task Missing_Callback_Reports_AVP2006() => Verify(
|
|
"""
|
|
public partial class MyControl : AvaloniaObject
|
|
{
|
|
[GeneratedStyledProperty({|AVP2006:CoerceMethodName = "CoerceValue"|})]
|
|
public partial int Value { get; set; }
|
|
}
|
|
""");
|
|
|
|
[Fact]
|
|
public Task Wrong_Signature_Callback_Reports_AVP2006() => Verify(
|
|
"""
|
|
public partial class MyControl : AvaloniaObject
|
|
{
|
|
[GeneratedStyledProperty({|AVP2006:ValidateMethodName = nameof(ValidateValue)|})]
|
|
public partial int Value { get; set; }
|
|
|
|
// 'string' instead of 'int'
|
|
private static bool ValidateValue(string value) => true;
|
|
}
|
|
""");
|
|
|
|
[Fact]
|
|
public Task Unimplemented_Partial_Callback_reports_AVP2006() => Verify(
|
|
"""
|
|
public partial class MyControl : AvaloniaObject
|
|
{
|
|
[GeneratedStyledProperty({|AVP2006:ValidateMethodName = nameof(ValidateValue)|})]
|
|
public partial int Value { get; set; }
|
|
|
|
private static partial bool ValidateValue(int value);
|
|
}
|
|
""");
|
|
|
|
[Fact]
|
|
public Task Invalid_Callback_Name_Reports_AVP2006() => Verify(
|
|
"""
|
|
public partial class MyControl : AvaloniaObject
|
|
{
|
|
[GeneratedStyledProperty({|AVP2006:CoerceMethodName = "not a name"|})]
|
|
public partial int Value { get; set; }
|
|
}
|
|
""");
|
|
|
|
[Fact]
|
|
public Task Inherited_Accessible_Callback_Reports_Nothing() => Verify(
|
|
"""
|
|
public class BaseClass : AvaloniaObject
|
|
{
|
|
protected static bool ValidateAnswer(int value) => value == 42;
|
|
|
|
internal static int CoerceAnswer(AvaloniaObject sender, int value) => value;
|
|
}
|
|
|
|
public partial class DerivedClass : BaseClass
|
|
{
|
|
[GeneratedStyledProperty(ValidateMethodName = nameof(ValidateAnswer), CoerceMethodName = nameof(CoerceAnswer))]
|
|
public partial int Answer { get; set; }
|
|
}
|
|
""");
|
|
|
|
[Fact]
|
|
public Task Hidden_Base_Callback_Reports_AVP2006() => Verify(
|
|
"""
|
|
public class BaseClass : AvaloniaObject
|
|
{
|
|
protected static bool ValidateAnswer(int value) => value == 42;
|
|
}
|
|
|
|
public partial class DerivedClass : BaseClass
|
|
{
|
|
[GeneratedStyledProperty({|AVP2006:ValidateMethodName = nameof(ValidateAnswer)|})]
|
|
public partial int Answer { get; set; }
|
|
|
|
// Wrong return type, and it hides BaseClass.ValidateAnswer(int).
|
|
private new static string ValidateAnswer(int value) => "";
|
|
}
|
|
""");
|
|
|
|
[Fact]
|
|
public Task Nullable_Mismatched_Callback_Reports_AVP2006() => Verify(
|
|
"""
|
|
#nullable enable
|
|
|
|
public partial class MyControl : AvaloniaObject
|
|
{
|
|
[GeneratedStyledProperty({|AVP2006:ValidateMethodName = nameof(ValidateHeader)|})]
|
|
public partial string? Header { get; set; }
|
|
|
|
// 'string' instead of 'string?'
|
|
private static bool ValidateHeader(string value) => true;
|
|
}
|
|
""");
|
|
|
|
[Fact]
|
|
public Task Nullable_Disable_Callback_Reports_Nothing() => Verify(
|
|
"""
|
|
#nullable enable
|
|
|
|
public partial class MyControl : AvaloniaObject
|
|
{
|
|
[GeneratedStyledProperty(ValidateMethodName = nameof(ValidateHeader))]
|
|
public partial string? Header { get; set; }
|
|
|
|
#nullable disable
|
|
private static bool ValidateHeader(string value) => true;
|
|
#nullable restore
|
|
}
|
|
""");
|
|
|
|
[Fact]
|
|
public Task Non_Partial_Member_Reports_AVP2007() => Verify(
|
|
"""
|
|
public partial class MyControl : AvaloniaObject
|
|
{
|
|
[GeneratedStyledProperty]
|
|
public string? {|AVP2007:Header|} { get; set; }
|
|
}
|
|
""");
|
|
|
|
[Fact]
|
|
public Task Non_Partial_Containing_Type_Reports_AVP2007() => Verify(
|
|
"""
|
|
public class MyControl : AvaloniaObject
|
|
{
|
|
[GeneratedStyledProperty]
|
|
public partial string? {|AVP2007:Header|} { get; set; }
|
|
}
|
|
""");
|
|
|
|
[Fact]
|
|
public Task Language_Below_13_Reports_AVP2007() => Verify(
|
|
"""
|
|
public partial class MyControl : AvaloniaObject
|
|
{
|
|
// Partial properties require C# 13; below that the generator can't emit.
|
|
[GeneratedStyledProperty]
|
|
public partial string? {|AVP2007:Header|} { get; set; }
|
|
}
|
|
""",
|
|
LanguageVersion.CSharp12);
|
|
|
|
[Fact]
|
|
public Task CSharp13_Valid_Property_Reports_Nothing() => Verify(
|
|
"""
|
|
public partial class MyControl : AvaloniaObject
|
|
{
|
|
[GeneratedStyledProperty]
|
|
public partial string? Header { get; set; }
|
|
}
|
|
""",
|
|
LanguageVersion.CSharp13);
|
|
|
|
[Fact]
|
|
public Task Static_Property_Reports_AVP2008() => Verify(
|
|
"""
|
|
public partial class MyControl : AvaloniaObject
|
|
{
|
|
[GeneratedStyledProperty]
|
|
public static partial string? {|AVP2008:Header|} { get; set; }
|
|
}
|
|
""");
|
|
|
|
[Fact]
|
|
public Task Getter_Only_Property_Reports_AVP2008() => Verify(
|
|
"""
|
|
public partial class MyControl : AvaloniaObject
|
|
{
|
|
// Styled properties are never truly readonly.
|
|
[GeneratedStyledProperty]
|
|
public partial string? {|AVP2008:Header|} { get; }
|
|
}
|
|
""");
|
|
|
|
[Fact]
|
|
public Task Styled_Non_Public_Setter_Reports_AVP2101() => Verify(
|
|
"""
|
|
public partial class MyControl : AvaloniaObject
|
|
{
|
|
// Styled properties are never truly readonly.
|
|
[GeneratedStyledProperty]
|
|
public partial bool {|AVP2101:IsPressed|} { get; private set; }
|
|
}
|
|
""");
|
|
|
|
[Fact]
|
|
public Task Direct_Non_Public_Setter_Reports_Nothing() => Verify(
|
|
"""
|
|
public partial class MyControl : AvaloniaObject
|
|
{
|
|
// Direct properties can actually be read-only.
|
|
[GeneratedDirectProperty]
|
|
public partial int SelectedIndex { get; private set; } = -1;
|
|
}
|
|
""");
|
|
|
|
[Fact]
|
|
public Task Doubled_Suffix_Reports_AVP2100() => Verify(
|
|
"""
|
|
public partial class MyControl : AvaloniaObject
|
|
{
|
|
// This code will generate HeaderPropertyProperty definition.
|
|
// if user really needs it, they can suppress the warning.
|
|
[GeneratedStyledProperty]
|
|
public partial string? {|AVP2100:HeaderProperty|} { get; set; }
|
|
}
|
|
""");
|
|
|
|
[Fact]
|
|
public Task Doubled_Suffix_On_Attached_Reports_AVP2100() => Verify(
|
|
"""
|
|
public partial class Grid : AvaloniaObject
|
|
{
|
|
[GeneratedAttachedProperty]
|
|
public static partial int {|AVP2100:GetRowProperty|}(Visual element);
|
|
}
|
|
""");
|
|
|
|
public class GeneratedPropertyAnalyzerTest : CSharpAnalyzerTest<GeneratedPropertyAnalyzer, DefaultVerifier>
|
|
{
|
|
public LanguageVersion LanguageVersion { get; set; } = LanguageVersion.CSharp14;
|
|
|
|
public GeneratedPropertyAnalyzerTest()
|
|
{
|
|
ReferenceAssemblies = new ReferenceAssemblies(TestReferences.DefaultTargetFramework);
|
|
foreach (var reference in TestReferences.All.Value)
|
|
{
|
|
TestState.AdditionalReferences.Add(reference);
|
|
}
|
|
|
|
// Disable compiled diagnostics - it will fail anyway, because of missing partial implementations.
|
|
// We only test out analyzers.
|
|
CompilerDiagnostics = CompilerDiagnostics.None;
|
|
}
|
|
|
|
protected override ParseOptions CreateParseOptions()
|
|
=> ((CSharpParseOptions)base.CreateParseOptions()).WithLanguageVersion(LanguageVersion);
|
|
}
|
|
}
|
|
|