|
// 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.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.MakeStructReadOnly;
using Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions;
using Microsoft.CodeAnalysis.Test.Utilities;
using Microsoft.CodeAnalysis.Testing;
using Roslyn.Test.Utilities;
using Xunit;
namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.MakeStructReadOnly;
using VerifyCS = CSharpCodeFixVerifier<
CSharpMakeStructReadOnlyDiagnosticAnalyzer,
CSharpMakeStructReadOnlyCodeFixProvider>;
[Trait(Traits.Feature, Traits.Features.CodeActionsMakeStructReadOnly)]
public class MakeStructReadOnlyTests
{
private static Task TestMissingAsync(string testCode, LanguageVersion version = LanguageVersion.CSharp12)
=> TestAsync(testCode, testCode, version);
private static async Task TestAsync(string testCode, string fixedCode, LanguageVersion version = LanguageVersion.CSharp12)
{
await new VerifyCS.Test
{
TestCode = testCode,
FixedCode = fixedCode,
LanguageVersion = version,
ReferenceAssemblies = ReferenceAssemblies.Net.Net50,
}.RunAsync();
}
[Fact]
public async Task ShouldNotTriggerForCSharp7_1()
{
await TestMissingAsync(
"""
struct S
{
readonly int i;
}
""", LanguageVersion.CSharp7_1);
}
[Fact]
public async Task ShouldTriggerFor7_2()
{
await TestAsync(
"""
struct [|S|]
{
readonly int i;
}
""",
"""
readonly struct S
{
readonly int i;
}
""",
LanguageVersion.CSharp7_2);
}
[Fact]
public async Task TestMissingWithAlreadyReadOnlyStruct()
{
await TestMissingAsync(
"""
readonly struct S
{
readonly int i;
}
""");
}
[Fact]
public async Task TestMissingWithAlreadyReadOnlyRecordStruct()
{
await TestMissingAsync(
"""
readonly record struct S
{
readonly int i;
}
""");
}
[Fact]
public async Task TestMissingWithMutableField()
{
await TestMissingAsync(
"""
struct S
{
int i;
}
""");
}
[Fact]
public async Task TestMissingWithMutableFieldRecordStruct()
{
await TestMissingAsync(
"""
record struct S
{
int i;
}
""");
}
[Fact]
public async Task TestMissingWithMutableAndReadOnlyField()
{
await TestMissingAsync(
"""
struct S
{
int i;
readonly int j;
}
""");
}
[Fact]
public async Task TestMissingWithMutableAndReadOnlyFieldRecordStruct1()
{
await TestMissingAsync(
"""
record struct S
{
int i;
readonly int j;
}
""");
}
[Fact]
public async Task TestMissingWithMutableAndReadOnlyFieldRecordStruct2()
{
await TestMissingAsync(
"""
record struct S(int j)
{
int i;
}
""");
}
[Fact]
public async Task TestMissingWithMutableAndReadOnlyFieldStruct2()
{
await TestMissingAsync(
"""
struct S(int j)
{
int i;
}
""");
}
[Fact]
public async Task TestMissingWithMutableProperty()
{
await TestMissingAsync(
"""
struct S
{
int P { get; set; }
}
""");
}
[Fact]
public async Task TestMissingWithMutablePropertyRecordStruct1()
{
await TestMissingAsync(
"""
record struct S
{
int P { get; set; }
}
""");
}
[Fact]
public async Task TestMissingWithMutablePropertyRecordStruct2()
{
await TestMissingAsync(
"""
record struct S(int q)
{
int P { get; set; }
}
""");
}
[Fact]
public async Task TestMissingWithMutablePropertyStruct2()
{
await TestMissingAsync(
"""
struct S(int q)
{
int P { get; set; }
}
""");
}
[Fact]
public async Task TestMissingWithEmptyStruct()
{
await TestMissingAsync(
"""
struct S
{
}
""");
}
[Fact]
public async Task TestMissingWithEmptyRecordStruct()
{
await TestMissingAsync(
"""
record struct S
{
}
""");
}
[Fact]
public async Task TestMissingWithEmptyRecordStructPrimaryConstructor()
{
await TestMissingAsync(
"""
record struct S()
{
}
""");
}
[Fact]
public async Task TestMissingWithEmptyStructPrimaryConstructor()
{
await TestMissingAsync(
"""
struct S()
{
}
""");
}
[Fact]
public async Task TestMissingWithOtherReadonlyPartialPart()
{
await TestMissingAsync(
"""
partial struct S
{
readonly int i;
}
readonly partial struct S
{
}
""");
}
[Fact]
public async Task TestOnStructWithReadOnlyField()
{
await TestAsync(
"""
struct [|S|]
{
readonly int i;
}
""",
"""
readonly struct S
{
readonly int i;
}
""");
}
[Fact]
public async Task TestOnRecordStructWithReadOnlyField()
{
await TestAsync(
"""
record struct [|S|]
{
readonly int i;
}
""",
"""
readonly record struct S
{
readonly int i;
}
""");
}
[Fact]
public async Task TestOnStructWithGetOnlyProperty()
{
await TestAsync(
"""
struct [|S|]
{
int P { get; }
}
""",
"""
readonly struct S
{
int P { get; }
}
""");
}
[Fact]
public async Task TestOnRecordStructWithGetOnlyProperty()
{
await TestAsync(
"""
record struct [|S|]
{
int P { get; }
}
""",
"""
readonly record struct S
{
int P { get; }
}
""");
}
[Fact]
public async Task TestOnStructWithInitOnlyProperty()
{
await TestAsync(
"""
struct [|S|]
{
int P { get; init; }
}
""",
"""
readonly struct S
{
int P { get; init; }
}
""");
}
[Fact]
public async Task TestOnRecordStructWithInitOnlyProperty()
{
await TestAsync(
"""
record struct [|S|]
{
int P { get; init; }
}
""",
"""
readonly record struct S
{
int P { get; init; }
}
""");
}
[Fact]
public async Task TestOnRecordStructWithReadOnlyField2()
{
await TestAsync(
"""
record struct [|S|]
{
readonly int i;
}
""",
"""
readonly record struct S
{
readonly int i;
}
""");
}
[Fact]
public async Task TestMissingRecordStructWithPrimaryConstructorField()
{
await TestMissingAsync(
"""
record struct S(int i)
{
}
""");
}
[Fact]
public async Task TestMissingStructWithPrimaryConstructor()
{
await TestMissingAsync(
"""
struct S(int i)
{
}
""");
}
[Fact]
public async Task TestMissingOnRecordStructWithPrimaryConstructorFieldAndNormalField()
{
await TestMissingAsync(
"""
record struct S(int i)
{
readonly int j;
}
""");
}
[Fact]
public async Task TestOnStructWithPrimaryConstructorAndReadonlyField()
{
await TestAsync(
"""
struct [|S|](int i)
{
readonly int i;
}
""",
"""
readonly struct S(int i)
{
readonly int i;
}
""",
LanguageVersion.CSharp12);
}
[Fact]
public async Task TestNestedStructs1()
{
await TestAsync(
"""
struct [|S|]
{
readonly int i;
struct [|T|]
{
readonly int j;
}
}
""",
"""
readonly struct S
{
readonly int i;
readonly struct T
{
readonly int j;
}
}
""");
}
[Fact]
public async Task TestNestedStructs2()
{
await TestAsync(
"""
struct [|S|]
{
readonly int i;
struct T
{
int j;
}
}
""",
"""
readonly struct S
{
readonly int i;
struct T
{
int j;
}
}
""");
}
[Fact]
public async Task TestNestedStructs3()
{
await TestAsync(
"""
struct S
{
int i;
struct [|T|]
{
readonly int j;
}
}
""",
"""
struct S
{
int i;
readonly struct T
{
readonly int j;
}
}
""");
}
[Fact]
public async Task TestNestedStructs4()
{
await TestAsync(
"""
struct [|S|]
{
readonly int i;
struct T
{
readonly int j;
void M()
{
this = default;
}
}
}
""",
"""
readonly struct S
{
readonly int i;
struct T
{
readonly int j;
void M()
{
this = default;
}
}
}
""");
}
[Fact]
public async Task TestDocComments1()
{
await TestAsync(
"""
/// <summary>docs</summary>
record struct [|S|]
{
readonly int j;
}
""",
"""
/// <summary>docs</summary>
readonly record struct S
{
readonly int j;
}
""");
}
[Fact]
public async Task TestDocComments2()
{
await TestAsync(
"""
namespace N
{
/// <summary>docs</summary>
record struct [|S|]
{
readonly int j;
}
}
""",
"""
namespace N
{
/// <summary>docs</summary>
readonly record struct S
{
readonly int j;
}
}
""");
}
[Fact]
public async Task TestExistingModifier1()
{
await TestAsync(
"""
public record struct [|S|]
{
readonly int j;
}
""",
"""
public readonly record struct S
{
readonly int j;
}
""");
}
[Fact]
public async Task TestExistingModifier2()
{
await TestAsync(
"""
namespace N
{
public record struct [|S|]
{
readonly int j;
}
}
""",
"""
namespace N
{
public readonly record struct S
{
readonly int j;
}
}
""");
}
[Fact]
public async Task TestOnStructWithReadOnlyFieldAndMutableNormalProp()
{
await TestAsync(
"""
struct [|S|]
{
readonly int i;
int P { set { } }
}
""",
"""
readonly struct S
{
readonly int i;
int P { set { } }
}
""");
}
[Fact]
public async Task TestOnStructWithReadOnlyFieldAndMutableAutoProp()
{
await TestMissingAsync(
"""
struct S
{
readonly int i;
int P { get; set; }
}
""");
}
[Fact]
public async Task TestMissingOnStructThatWritesToThis1()
{
await TestMissingAsync(
"""
struct S
{
readonly int i;
void M()
{
this = default;
}
}
""");
}
[Fact]
public async Task TestMissingOnStructThatWritesToThis2()
{
await TestMissingAsync(
"""
struct S
{
readonly int i;
void M()
{
this.ByRef();
}
}
static class Extensions
{
public static void ByRef(ref this S s) { }
}
""");
}
[Fact]
public async Task TestMissingOnStructThatWritesToThis3()
{
await TestMissingAsync(
"""
struct S
{
readonly int i;
void M()
{
Goo(ref this);
}
void Goo(ref S s) { }
}
""");
}
[Fact]
public async Task TestMissingOnStructThatWritesToThis4()
{
await TestMissingAsync(
"""
struct S
{
readonly int i;
void M()
{
Goo(out this);
}
void Goo(out S s) { s = default; }
}
""");
}
[Fact]
public async Task TestMissingOnStructThatWritesToThis5()
{
await TestMissingAsync(
"""
struct S
{
readonly int i;
void M()
{
ref S s = ref this;
}
}
""");
}
[Fact]
public async Task TestMissingOnStructThatWritesToThis6()
{
await TestMissingAsync(
"""
struct S
{
readonly int i;
void M()
{
this++;
}
public static S operator++(S s) => default;
}
""");
}
[Fact]
public async Task TestOnStructThatReadsFromThis1()
{
await TestAsync(
"""
struct [|S|]
{
readonly int i;
void M()
{
Goo(in this);
}
void Goo(in S s) { }
}
""",
"""
readonly struct S
{
readonly int i;
void M()
{
Goo(in this);
}
void Goo(in S s) { }
}
""");
}
[Fact]
public async Task TestOnStructThatReadsFromThis2()
{
await TestAsync(
"""
struct [|S|]
{
readonly int i;
void M()
{
this.Goo();
}
void Goo() { }
}
""",
"""
readonly struct S
{
readonly int i;
void M()
{
this.Goo();
}
void Goo() { }
}
""");
}
[Fact]
public async Task TestOnStructThatReadsFromThis3()
{
await TestAsync(
"""
struct [|S|]
{
readonly int i;
void M()
{
this.Goo();
}
}
static class Extensions
{
public static void Goo(this S s) { }
}
""",
"""
readonly struct S
{
readonly int i;
void M()
{
this.Goo();
}
}
static class Extensions
{
public static void Goo(this S s) { }
}
""");
}
[Fact]
public async Task TestOnStructThatReadsFromThis4()
{
await TestAsync(
"""
struct [|S|]
{
readonly int i;
void M()
{
ref readonly S s = ref this;
}
}
""",
"""
readonly struct S
{
readonly int i;
void M()
{
ref readonly S s = ref this;
}
}
""");
}
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/69994")]
public async Task NotWithFieldLikeEvent()
{
await TestMissingAsync(
"""
using System;
public struct MyStruct
{
public event Action MyEvent;
public readonly int MyInt;
}
""");
}
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/69994")]
public async Task WithPropertyLikeEvent1()
{
await TestAsync(
"""
using System;
public struct [|MyStruct|]
{
public event Action MyEvent { add { } remove { } }
public readonly int MyInt;
}
""",
"""
using System;
public readonly struct MyStruct
{
public event Action MyEvent { add { } remove { } }
public readonly int MyInt;
}
""");
}
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/69994")]
public async Task WithPropertyLikeEvent2()
{
await TestAsync(
"""
using System;
using System.Collections.Generic;
public struct [|MyStruct|]
{
private readonly List<Action> actions = new();
public event Action MyEvent { add => actions.Add(value); remove => actions.Remove(value); }
public MyStruct() { }
}
""",
"""
using System;
using System.Collections.Generic;
public readonly struct MyStruct
{
private readonly List<Action> actions = new();
public event Action MyEvent { add => actions.Add(value); remove => actions.Remove(value); }
public MyStruct() { }
}
""");
}
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/69994")]
public async Task NotWithPropertyLikeEvent1()
{
await TestMissingAsync(
"""
using System;
using System.Collections.Generic;
public struct MyStruct
{
private List<Action> actions = new();
public event Action MyEvent { add => actions.Add(value); remove => actions.Remove(value); }
public MyStruct() { }
}
""");
}
}
|