File: src\Analyzers\CSharp\Tests\SimplifyPropertyAccessor\SimplifyPropertyAccessorTests.cs
Web Access
Project: src\src\CodeStyle\CSharp\Tests\Microsoft.CodeAnalysis.CSharp.CodeStyle.UnitTests.csproj (Microsoft.CodeAnalysis.CSharp.CodeStyle.UnitTests)
// 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.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.CodeStyle;
using Microsoft.CodeAnalysis.CSharp.SimplifyPropertyAccessor;
using Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions;
using Microsoft.CodeAnalysis.Test.Utilities;
using Microsoft.CodeAnalysis.Testing;
using Xunit;
 
namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.SimplifyPropertyAccessor;
 
using VerifyCS = CSharpCodeFixVerifier<
    CSharpSimplifyPropertyAccessorDiagnosticAnalyzer,
    CSharpSimplifyPropertyAccessorCodeFixProvider>;
 
[Trait(Traits.Feature, Traits.Features.CodeActionsSimplifyPropertyAccessor)]
public sealed class SimplifyPropertyAccessorTests
{
    private static async Task TestAsync(
        [StringSyntax(PredefinedEmbeddedLanguageNames.CSharpTest)] string markup,
        LanguageVersion languageVersion = LanguageVersion.CSharp14)
    {
        await new VerifyCS.Test
        {
            TestCode = markup,
            FixedCode = markup,
            LanguageVersion = languageVersion,
        }.RunAsync();
    }
 
    private static async Task TestAsync(
        [StringSyntax(PredefinedEmbeddedLanguageNames.CSharpTest)] string initialMarkup,
        [StringSyntax(PredefinedEmbeddedLanguageNames.CSharpTest)] string fixedMarkup)
    {
        await new VerifyCS.Test
        {
            TestCode = initialMarkup,
            FixedCode = fixedMarkup,
            LanguageVersion = LanguageVersion.CSharp14,
            ReferenceAssemblies = ReferenceAssemblies.Net.Net80, // for 'IsExternalInit' type
        }.RunAsync();
    }
 
    public static IEnumerable<string> SimplifiableGetterBodies => ["{ return field; }", "=> field;"];
 
    public static IEnumerable<string> TrailingTrivia => ["", " // useful comment"];
 
    public static IEnumerable<string> SimplifiableSetterBodies => ["{ field = value; }", "=> field = value;"];
 
    public static IEnumerable<string> SetInitKeywords => ["set", "init"];
 
    [Fact]
    public async Task NotInCSharpVersionBefore14()
    {
        // 'field' is not even parsed as a keyword before C# 14, but let's test this anyway
        await TestAsync("""
            class C
            {
                public int Prop
                {
                    get => {|CS0103:field|};
                    set => {|CS0103:field|} = value;
                }
            }
            """, LanguageVersion.CSharp13);
    }
 
    [Fact]
    public async Task NotWhenOptionIsDisabled()
    {
        var code = """
            class C
            {
                public int Prop
                {
                    get => field;
                    set => field = value;
                }
            }
            """;
 
        await new VerifyCS.Test
        {
            TestCode = code,
            FixedCode = code,
            LanguageVersion = LanguageVersion.CSharp14,
            Options =
            {
                { CSharpCodeStyleOptions.PreferSimplePropertyAccessors, false }
            }
        }.RunAsync();
    }
 
    [Fact]
    public async Task NotWhenAccessorHasSyntaxError()
    {
        var code = """
            class C
            {
                public int Prop
                {
                    get => field{|CS1002:|}
                    set { field = value {|CS1002:}|}
                }
            }
            """;
 
        await new VerifyCS.Test
        {
            TestCode = code,
            FixedCode = code,
            LanguageVersion = LanguageVersion.CSharp14
        }.RunAsync();
    }
 
    [Theory]
    [CombinatorialData]
    public async Task SimpleGetter(
        [CombinatorialMemberData(nameof(SimplifiableGetterBodies))] string getterBody,
        [CombinatorialMemberData(nameof(TrailingTrivia))] string trailingTrivia)
    {
        await TestAsync($$"""
            class C
            {
                public int Prop
                {
                    [|get {{getterBody}}|]{{trailingTrivia}}
                    set;
                }
            }
            """, $$"""
            class C
            {
                public int Prop
                {
                    get;{{trailingTrivia}}
                    set;
                }
            }
            """);
    }
 
    [Theory]
    [CombinatorialData]
    public async Task SimpleSetter(
        [CombinatorialMemberData(nameof(SetInitKeywords))] string setterKeyword,
        [CombinatorialMemberData(nameof(SimplifiableSetterBodies))] string setterBody,
        [CombinatorialMemberData(nameof(TrailingTrivia))] string trailingTrivia)
    {
        await TestAsync($$"""
            class C
            {
                public int Prop
                {
                    get;
                    [|{{setterKeyword}} {{setterBody}}|]{{trailingTrivia}}
                }
            }
            """, $$"""
            class C
            {
                public int Prop
                {
                    get;
                    {{setterKeyword}};{{trailingTrivia}}
                }
            }
            """);
    }
 
    [Theory]
    [CombinatorialData]
    public async Task FixAll(
        [CombinatorialMemberData(nameof(SimplifiableGetterBodies))] string getterBody,
        [CombinatorialMemberData(nameof(TrailingTrivia))] string getterTrailingTrivia,
        [CombinatorialMemberData(nameof(SetInitKeywords))] string setterKeyword,
        [CombinatorialMemberData(nameof(SimplifiableSetterBodies))] string setterBody,
        [CombinatorialMemberData(nameof(TrailingTrivia))] string setterTrailingTrivia)
    {
        await TestAsync($$"""
            class C
            {
                public int Prop
                {
                    [|get {{getterBody}}|]{{getterTrailingTrivia}}
                    [|{{setterKeyword}} {{setterBody}}|]{{setterTrailingTrivia}}
                }
            }
            """, $$"""
            class C
            {
                public int Prop
                {
                    get;{{getterTrailingTrivia}}
                    {{setterKeyword}};{{setterTrailingTrivia}}
                }
            }
            """);
    }
 
    [Fact]
    public async Task NotWhenPropertyHasNoAccessors()
    {
        // Just to verify we do not crash etc.
        await TestAsync("""
            class C
            {
                public int {|CS0548:Prop|} { }
            }
            """);
    }
 
    [Fact]
    public async Task EvenWhenPropertyHasTooManyAccessors()
    {
        await new VerifyCS.Test()
        {
            TestCode = """
                class C
                {
                    public int Prop { [|get { return field; }|] [|set => field = value;|] [|init { field = value; }|] }
                }
                """,
            FixedCode = """
                class C
                {
                    public int Prop { get; set; init; }
                }
                """,
            LanguageVersion = LanguageVersion.CSharp14,
            CompilerDiagnostics = CompilerDiagnostics.None,
        }.RunAsync();
    }
 
    [Fact]
    public async Task EvenWhenPropertyHasDuplicateAccessors()
    {
        await new VerifyCS.Test()
        {
            TestCode = """
                class C
                {
                    public int Prop { [|get { return field; }|] [|get => field;|] }
                }
                """,
            FixedCode = """
                class C
                {
                    public int Prop { get; get; }
                }
                """,
            LanguageVersion = LanguageVersion.CSharp14,
            CompilerDiagnostics = CompilerDiagnostics.None,
        }.RunAsync();
    }
 
    [Fact]
    public async Task NotOnIndexerAccessor()
    {
        // Again 'field' is not even parsed as a keyword
        await TestAsync("""
            class C
            {
                public int this[int i]
                {
                    get => {|CS0103:field|};
                    set => {|CS0103:field|} = value;
                }
            }
            """);
    }
 
    [Fact]
    public async Task NotOnPropertyLikeEventAccessor()
    {
        // 'field' keyword? Never heard of that thing...
        await TestAsync("""
            using System;
 
            class C
            {
                public event EventHandler Tested
                {
                    add => {|CS0201:{|CS0103:field|}|};
                    remove => {|CS0103:field|} = value;
                }
            }
            """);
    }
 
    [Fact]
    public async Task NotOnPartialPropertyImplementationWithAnotherAccessorEmpty_Get()
    {
        await TestAsync("""
            partial class C
            {
                public partial int Prop { get; set; }
                public partial int Prop { get => field; set; }
            }
            """);
    }
 
    [Fact]
    public async Task NotOnPartialPropertyImplementationWithAnotherAccessorEmpty_Set()
    {
        await TestAsync("""
            partial class C
            {
                public partial int Prop { get; set; }
                public partial int Prop { get; set { field = value; } }
            }
            """);
    }
 
    [Fact]
    public async Task PartialPropertyImplementation_BothAccessors1()
    {
        await TestAsync("""
            partial class C
            {
                public partial int Prop { get; set; }
 
                public partial int Prop
                {
                    [|get => field;|]
                    [|set { field = value; }|]
                }
            }
            """, """
            partial class C
            {
                public partial int Prop { get; set; }
 
                public partial int Prop
                {
                    get;
                    set { field = value; }
                }
            }
            """);
    }
 
    [Fact]
    public async Task PartialPropertyImplementation_BothAccessors1_DifferentFiles()
    {
        await new VerifyCS.Test
        {
            TestState =
            {
                Sources =
                {
                    """
                    partial class C
                    {
                        public partial int Prop { get; set; }
                    }
                    """, """
                    partial class C
                    {
                        public partial int Prop
                        {
                            [|get => field;|]
                            [|set { field = value; }|]
                        }
                    }
                    """
                }
            },
            FixedState =
            {
                Sources =
                {
                    """
                    partial class C
                    {
                        public partial int Prop { get; set; }
                    }
                    """, """
                    partial class C
                    {
                        public partial int Prop
                        {
                            get;
                            set { field = value; }
                        }
                    }
                    """
                }
            },
            LanguageVersion = LanguageVersion.CSharp14,
        }.RunAsync();
    }
 
    [Fact]
    public async Task PartialPropertyImplementation_BothAccessors2()
    {
        await new VerifyCS.Test
        {
            TestCode = """
                partial class C
                {
                    public partial int Prop { get; set; }
 
                    public partial int Prop
                    {
                        [|get => field;|]
                        [|set { field = value; }|]
                    }
                }
                """,
            FixedCode = """
                partial class C
                {
                    public partial int Prop { get; set; }
            
                    public partial int Prop
                    {
                        get => field;
                        set;
                    }
                }
                """,
            LanguageVersion = LanguageVersion.CSharp14,
            CodeFixTestBehaviors = CodeFixTestBehaviors.SkipFixAllCheck | CodeFixTestBehaviors.FixOne,
            DiagnosticSelector = diagnostics => diagnostics[1],
        }.RunAsync();
    }
 
    [Fact]
    public async Task PartialPropertyImplementation_BothAccessors2_DifferentFiles()
    {
        await new VerifyCS.Test
        {
            TestState =
            {
                Sources =
                {
                    """
                    partial class C
                    {
                        public partial int Prop { get; set; }
                    }
                    """, """
                    partial class C
                    {
                        public partial int Prop
                        {
                            [|get => field;|]
                            [|set { field = value; }|]
                        }
                    }
                    """
                }
            },
            FixedState =
            {
                Sources =
                {
                    """
                    partial class C
                    {
                        public partial int Prop { get; set; }
                    }
                    """, """
                    partial class C
                    {
                        public partial int Prop
                        {
                            get => field;
                            set;
                        }
                    }
                    """
                }
            },
            LanguageVersion = LanguageVersion.CSharp14,
            CodeFixTestBehaviors = CodeFixTestBehaviors.SkipFixAllCheck | CodeFixTestBehaviors.FixOne,
            DiagnosticSelector = diagnostics => diagnostics[1],
        }.RunAsync();
    }
 
    [Fact]
    public async Task MultiplePartialPropertyImplementationsWithBothAccessors()
    {
        await TestAsync("""
            partial class C
            {
                public partial int Prop1 { get; set; }
                public partial string Prop2 { get; set; }
 
                public partial int Prop1
                {
                    [|get => field;|]
                    [|set { field = value; }|]
                }
 
                public partial string Prop2
                {
                    [|get => field;|]
                    [|set { field = value; }|]
                }
            }
            """, """
            partial class C
            {
                public partial int Prop1 { get; set; }
                public partial string Prop2 { get; set; }
            
                public partial int Prop1
                {
                    get;
                    set { field = value; }
                }
            
                public partial string Prop2
                {
                    get;
                    set { field = value; }
                }
            }
            """);
    }
}