|
// 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.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.MakeStructMemberReadOnly;
using Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions;
using Microsoft.CodeAnalysis.Test.Utilities;
using Microsoft.CodeAnalysis.Testing;
using Xunit;
namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.MakeStructMemberReadOnly;
using VerifyCS = CSharpCodeFixVerifier<
CSharpMakeStructMemberReadOnlyDiagnosticAnalyzer,
CSharpMakeStructMemberReadOnlyCodeFixProvider>;
[Trait(Traits.Feature, Traits.Features.CodeActionsMakeStructMemberReadOnly)]
public sealed class MakeStructMemberReadOnlyTests
{
[Fact]
public async Task TestEmptyMethod()
{
await new VerifyCS.Test
{
TestCode = """
struct S
{
void [|M|]() { }
}
""",
FixedCode = """
struct S
{
readonly void M() { }
}
"""
}.RunAsync();
}
[Fact]
public async Task TestNotInClass()
{
var test = """
class S
{
void M() { }
}
""";
await new VerifyCS.Test
{
TestCode = test,
FixedCode = test,
}.RunAsync();
}
[Fact]
public async Task TestNotInReadOnlyStruct()
{
var test = """
readonly struct S
{
void M() { }
}
""";
await new VerifyCS.Test
{
TestCode = test,
FixedCode = test,
}.RunAsync();
}
[Fact]
public async Task TestNotInReadOnlyMember()
{
var test = """
struct S
{
readonly void M() { }
}
""";
await new VerifyCS.Test
{
TestCode = test,
FixedCode = test,
}.RunAsync();
}
[Fact]
public async Task TestNotWithAssignmentToThis()
{
var test = """
struct S
{
void M()
{
this = default;
}
}
""";
await new VerifyCS.Test
{
TestCode = test,
FixedCode = test,
}.RunAsync();
}
[Fact]
public async Task TestNotWithThisPassedByRef1()
{
var test = """
struct S
{
void M()
{
G(ref this);
}
static void G(ref S s) { }
}
""";
await new VerifyCS.Test
{
TestCode = test,
FixedCode = test,
}.RunAsync();
}
[Fact]
public async Task TestNotWithThisPassedByRef2()
{
var test = """
struct S
{
void M()
{
this.G();
}
}
static class X
{
public static void G(ref this S s) { }
}
""";
await new VerifyCS.Test
{
TestCode = test,
FixedCode = test,
}.RunAsync();
}
[Fact]
public async Task TestWithThisPassedByIn1_A()
{
await new VerifyCS.Test
{
TestCode = """
struct S
{
void [|M|]()
{
G(in this);
}
static void G(in S s) { }
}
""",
FixedCode = """
struct S
{
readonly void M()
{
G(in this);
}
static void G(in S s) { }
}
""",
}.RunAsync();
}
[Fact]
public async Task TestWithThisPassedByIn1_B()
{
await new VerifyCS.Test
{
TestCode = """
struct S
{
void [|M|]()
{
G(this);
}
static void G(in S s) { }
}
""",
FixedCode = """
struct S
{
readonly void M()
{
G(this);
}
static void G(in S s) { }
}
""",
}.RunAsync();
}
[Fact]
public async Task TestWithThisPassedByIn2()
{
await new VerifyCS.Test
{
TestCode = """
struct S
{
void [|M|]()
{
this.G();
}
}
static class X
{
public static void G(in this S s) { }
}
""",
FixedCode = """
struct S
{
readonly void M()
{
this.G();
}
}
static class X
{
public static void G(in this S s) { }
}
""",
}.RunAsync();
}
[Fact]
public async Task TestWithThisPassedByIn3()
{
await new VerifyCS.Test
{
TestCode = """
struct S
{
void [|M|]()
{
var v = this + this;
}
public static S operator+(in S s1, in S s2) => default;
}
""",
FixedCode = """
struct S
{
readonly void M()
{
var v = this + this;
}
public static S operator+(in S s1, in S s2) => default;
}
""",
}.RunAsync();
}
[Fact]
public async Task TestNotWithWriteToField1()
{
var test = """
struct S
{
int x;
void M()
{
x = 0;
}
}
""";
await new VerifyCS.Test
{
TestCode = test,
FixedCode = test,
}.RunAsync();
}
[Fact]
public async Task TestNotWithWriteToField2()
{
var test = """
struct S
{
int x;
void M()
{
x++;
}
}
""";
await new VerifyCS.Test
{
TestCode = test,
FixedCode = test,
}.RunAsync();
}
[Fact]
public async Task TestNotWithWriteToField3()
{
var test = """
struct S
{
int x;
void M()
{
G(ref x);
}
static void G(ref int x) { }
}
""";
await new VerifyCS.Test
{
TestCode = test,
FixedCode = test,
}.RunAsync();
}
[Fact]
public async Task TestNotWithWriteToField4()
{
var test = """
struct S
{
int x;
void M()
{
G(out x);
}
static void G(out int x) { x = 0; }
}
""";
await new VerifyCS.Test
{
TestCode = test,
FixedCode = test,
}.RunAsync();
}
[Fact]
public async Task TestNotWithWriteToField5()
{
var test = """
struct S
{
int x;
void M()
{
(x, x) = (0, 0);
}
}
""";
await new VerifyCS.Test
{
TestCode = test,
FixedCode = test,
}.RunAsync();
}
[Fact]
public async Task TestNotWithWriteToField6()
{
var test = """
struct D
{
public int i;
}
struct S
{
D d;
void M()
{
d.i = 0;
}
}
""";
await new VerifyCS.Test
{
TestCode = test,
FixedCode = test,
}.RunAsync();
}
[Fact]
public async Task TestNotWithWriteToField7()
{
var test = """
struct BitVector
{
int x;
public bool this[int index] { get => x++ > 0; set => x++; }
}
struct S
{
BitVector bits;
void M()
{
bits[0] = true;
}
}
""";
await new VerifyCS.Test
{
TestCode = test,
FixedCode = test,
}.RunAsync();
}
[Fact]
public async Task TestNotWithWriteToField8()
{
var test = """
struct BitVector
{
int x;
public bool this[int index] { get => x++ > 0; set => x++; }
}
struct S
{
BitVector bits;
void M()
{
(bits[0], bits[1]) = (true, false);
}
}
""";
await new VerifyCS.Test
{
TestCode = test,
FixedCode = test,
}.RunAsync();
}
[Fact]
public async Task TestNotInCSharp7()
{
var test = """
struct S
{
void M() { }
}
""";
await new VerifyCS.Test
{
TestCode = test,
FixedCode = test,
LanguageVersion = LanguageVersion.CSharp7,
}.RunAsync();
}
[Fact]
public async Task TestPropertyExpressionBody()
{
await new VerifyCS.Test
{
TestCode = """
struct S
{
int P [|=>|] 0;
}
""",
FixedCode = """
struct S
{
readonly int P => 0;
}
"""
}.RunAsync();
}
[Fact]
public async Task TestPropertyAccessor1()
{
var test = """
struct S
{
int P { get; }
}
""";
await new VerifyCS.Test
{
TestCode = test,
FixedCode = test,
}.RunAsync();
}
[Fact]
public async Task TestPropertyAccessor2()
{
await new VerifyCS.Test
{
TestCode = """
struct S
{
int P { [|get|] => 0; }
}
""",
FixedCode = """
struct S
{
readonly int P { get => 0; }
}
"""
}.RunAsync();
}
[Fact]
public async Task TestIndexerAccessor2()
{
await new VerifyCS.Test
{
TestCode = """
struct S
{
int this[int i] { [|get|] => 0; }
}
""",
FixedCode = """
struct S
{
readonly int this[int i] { get => 0; }
}
"""
}.RunAsync();
}
[Fact]
public async Task TestPropertyAccessor3()
{
await new VerifyCS.Test
{
TestCode = """
struct S
{
int P { [|get|] { return 0; } }
}
""",
FixedCode = """
struct S
{
readonly int P { get { return 0; } }
}
"""
}.RunAsync();
}
[Fact]
public async Task TestIndexerAccessor3()
{
await new VerifyCS.Test
{
TestCode = """
struct S
{
int this[int i] { [|get|] { return 0; } }
}
""",
FixedCode = """
struct S
{
readonly int this[int i] { get { return 0; } }
}
"""
}.RunAsync();
}
[Fact]
public async Task TestWriteToFieldNotThroughThis()
{
await new VerifyCS.Test
{
TestCode = """
struct S
{
int i;
void [|M|]()
{
S s;
s.i = 1;
}
}
""",
FixedCode = """
struct S
{
int i;
readonly void M()
{
S s;
s.i = 1;
}
}
"""
}.RunAsync();
}
[Fact]
public async Task TestCallToStaticMethod()
{
await new VerifyCS.Test
{
TestCode = """
struct S
{
int i;
void [|M|]()
{
G();
}
static void G() { }
}
""",
FixedCode = """
struct S
{
int i;
readonly void M()
{
G();
}
static void G() { }
}
"""
}.RunAsync();
}
[Fact]
public async Task TestRecursiveCall()
{
await new VerifyCS.Test
{
TestCode = """
struct S
{
int i;
void [|M|]()
{
M();
}
}
""",
FixedCode = """
struct S
{
int i;
readonly void M()
{
M();
}
}
"""
}.RunAsync();
}
[Fact]
public async Task TestMultipleAccessor()
{
await new VerifyCS.Test
{
TestCode = """
struct S
{
int i;
int X { [|get|] => 0; [|set|] { } }
}
""",
FixedCode = """
struct S
{
int i;
readonly int X { get => 0; set { } }
}
""",
}.RunAsync();
}
[Fact]
public async Task TestMultipleIndexerAccessor()
{
await new VerifyCS.Test
{
TestCode = """
struct S
{
int i;
int this[int x] { [|get|] => 0; [|set|] { } }
}
""",
FixedCode = """
struct S
{
int i;
readonly int this[int x] { get => 0; set { } }
}
""",
}.RunAsync();
}
[Fact]
public async Task TestMultipleAccessor_FixOne1()
{
await new VerifyCS.Test
{
TestCode = """
struct S
{
int i;
int X { [|get|] => 0; [|set|] { } }
}
""",
FixedState =
{
Sources =
{
"""
struct S
{
int i;
int X { readonly get => 0; set { } }
}
""",
},
ExpectedDiagnostics =
{
// /0/Test0.cs(5,32): info IDE0251:
VerifyCS.Diagnostic("IDE0251").WithSeverity(DiagnosticSeverity.Info).WithSpan(5, 32, 5, 35).WithOptions(DiagnosticOptions.IgnoreAdditionalLocations),
},
},
BatchFixedCode = """
struct S
{
int i;
readonly int X { get => 0; set { } }
}
""",
CodeFixTestBehaviors = CodeFixTestBehaviors.FixOne,
}.RunAsync();
}
[Fact]
public async Task TestMultipleIndexerAccessor_FixOne1()
{
await new VerifyCS.Test
{
TestCode = """
struct S
{
int i;
int this[int x] { [|get|] => 0; [|set|] { } }
}
""",
FixedState =
{
Sources =
{
"""
struct S
{
int i;
int this[int x] { readonly get => 0; set { } }
}
""",
},
ExpectedDiagnostics =
{
// /0/Test0.cs(5,32): info IDE0251:
VerifyCS.Diagnostic("IDE0251").WithSeverity(DiagnosticSeverity.Info).WithSpan(5, 42, 5, 45).WithOptions(DiagnosticOptions.IgnoreAdditionalLocations),
},
},
BatchFixedCode = """
struct S
{
int i;
readonly int this[int x] { get => 0; set { } }
}
""",
CodeFixTestBehaviors = CodeFixTestBehaviors.FixOne,
}.RunAsync();
}
[Fact]
public async Task TestMultipleAccessor2()
{
await new VerifyCS.Test
{
TestCode = """
struct S
{
int i;
int X { [|get|] => 0; readonly set { } }
}
""",
FixedCode = """
struct S
{
int i;
readonly int X { get => 0; set { } }
}
"""
}.RunAsync();
}
[Fact]
public async Task TestMultipleIndexerAccessor2()
{
await new VerifyCS.Test
{
TestCode = """
struct S
{
int i;
int this[int x] { [|get|] => 0; readonly set { } }
}
""",
FixedCode = """
struct S
{
int i;
readonly int this[int x] { get => 0; set { } }
}
"""
}.RunAsync();
}
[Fact]
public async Task TestTakeRefReadOnlyToField()
{
await new VerifyCS.Test
{
TestCode = """
struct S
{
int i;
void [|M|]()
{
ref readonly int x = ref i;
}
}
""",
FixedCode = """
struct S
{
int i;
readonly void M()
{
ref readonly int x = ref i;
}
}
"""
}.RunAsync();
}
[Fact]
public async Task TestNotWithAddressOfFieldTaken()
{
var test = """
struct S
{
int x;
unsafe void M()
{
fixed (int* y = &x)
{
}
}
}
""";
await new VerifyCS.Test
{
TestCode = test,
FixedCode = test,
}.RunAsync();
}
[Fact]
public async Task TestNotWithCallToNonReadOnlyMethod()
{
var test = """
struct S
{
int x;
void M()
{
this.X();
}
void X()
{
x = 1;
}
}
""";
await new VerifyCS.Test
{
TestCode = test,
FixedCode = test,
}.RunAsync();
}
[Fact]
public async Task TestNotWithCallToNonReadOnlyIndexer()
{
var test = """
struct S
{
int x;
int this[int y] { get { return x++; } set { x++; } }
void M()
{
var v = this[0];
}
}
""";
await new VerifyCS.Test
{
TestCode = test,
FixedCode = test,
}.RunAsync();
}
[Fact]
public async Task TestNotWithCaptureOfNonReadOnlyMethod1()
{
var test = """
struct S
{
int x;
void M()
{
System.Action v = this.X;
}
void X()
{
x = 1;
}
}
""";
await new VerifyCS.Test
{
TestCode = test,
FixedCode = test,
}.RunAsync();
}
[Fact]
public async Task TestNotWithCaptureOfNonReadOnlyMethod2()
{
var test = """
struct S
{
int x;
void M()
{
var v = this.X;
}
void X()
{
x = 1;
}
}
""";
await new VerifyCS.Test
{
TestCode = test,
FixedCode = test,
LanguageVersion = LanguageVersion.CSharp10,
}.RunAsync();
}
[Fact]
public async Task TestCallToObjectMethod()
{
await new VerifyCS.Test
{
TestCode = """
struct S
{
void [|M|]() { this.ToString(); }
}
""",
FixedCode = """
struct S
{
readonly void M() { this.ToString(); }
}
"""
}.RunAsync();
}
[Fact]
public async Task TestCallToReadOnlyMethod()
{
await new VerifyCS.Test
{
TestCode = """
struct S
{
void [|M|]() { this.X(); }
readonly void X() { }
}
""",
FixedCode = """
struct S
{
readonly void M() { this.X(); }
readonly void X() { }
}
"""
}.RunAsync();
}
[Fact]
public async Task TestCallToReadOnlyIndexer1()
{
await new VerifyCS.Test
{
TestCode = """
struct S
{
int i;
int this[int x] { readonly get => 0; set { i++; } }
void [|M|]()
{
var v = this[0];
}
}
""",
FixedCode = """
struct S
{
int i;
int this[int x] { readonly get => 0; set { i++; } }
readonly void M()
{
var v = this[0];
}
}
"""
}.RunAsync();
}
[Fact]
public async Task TestCallToReadOnlyIndexer2()
{
await new VerifyCS.Test
{
TestCode = """
struct S
{
int i;
readonly int this[int x] { get => 0; }
void [|M|]()
{
var v = this[0];
}
}
""",
FixedCode = """
struct S
{
int i;
readonly int this[int x] { get => 0; }
readonly void M()
{
var v = this[0];
}
}
"""
}.RunAsync();
}
[Fact]
public async Task TestExplicitInterfaceImpl()
{
await new VerifyCS.Test
{
TestCode = """
using System;
struct S : IEquatable<S>
{
bool IEquatable<S>.[|Equals|](S s) => true;
}
""",
FixedCode = """
using System;
struct S : IEquatable<S>
{
readonly bool IEquatable<S>.Equals(S s) => true;
}
"""
}.RunAsync();
}
[Fact]
public async Task TestEventMutation()
{
var testCode = """
using System;
struct S
{
event Action E;
void M()
{
this.E += () => { };
}
}
""";
await new VerifyCS.Test
{
TestCode = testCode,
FixedCode = testCode,
}.RunAsync();
}
[Fact]
public async Task TestNotWithNonReadOnlyMethodCallOnField()
{
var testCode = """
struct T
{
int i;
public void Dispose() { i++; }
}
struct S
{
T t;
void Dispose()
{
t.Dispose();
}
}
""";
await new VerifyCS.Test
{
TestCode = testCode,
FixedCode = testCode,
}.RunAsync();
}
[Fact]
public async Task TestWithReadOnlyMethodCallOnField()
{
await new VerifyCS.Test
{
TestCode = """
struct T
{
public readonly void Dispose() { }
}
struct S
{
T t;
void [|Dispose|]()
{
t.Dispose();
}
}
""",
FixedCode = """
struct T
{
public readonly void Dispose() { }
}
struct S
{
T t;
readonly void Dispose()
{
t.Dispose();
}
}
""",
}.RunAsync();
}
[Fact]
public async Task TestNotWithNonReadOnlyMethodOnUnconstrainedField()
{
var testCode = """
using System;
struct T<X> where X : IComparable
{
X x;
public void M() { x.CompareTo(null); }
}
""";
await new VerifyCS.Test
{
TestCode = testCode,
FixedCode = testCode,
}.RunAsync();
}
[Fact]
public async Task TestNotWithNonReadOnlyMethodOnStructConstrainedField()
{
var testCode = """
using System;
struct T<X> where X : struct, IComparable
{
X x;
public void M() { x.CompareTo(null); }
}
""";
await new VerifyCS.Test
{
TestCode = testCode,
FixedCode = testCode,
}.RunAsync();
}
[Fact]
public async Task TestWithNonReadOnlyMethodOnClassConstrainedField()
{
await new VerifyCS.Test
{
TestCode = """
using System;
struct T<X> where X : class, IComparable
{
X x;
public void [|M|]() { x.CompareTo(null); }
}
""",
FixedCode = """
using System;
struct T<X> where X : class, IComparable
{
X x;
public readonly void M() { x.CompareTo(null); }
}
""",
}.RunAsync();
}
[Fact]
public async Task TestNotWithMethodThatOnlyThrows1()
{
var test = """
struct S
{
void M() => throw new System.Exception();
}
""";
await new VerifyCS.Test
{
TestCode = test,
FixedCode = test,
}.RunAsync();
}
[Fact]
public async Task TestNotWithMethodThatOnlyThrows2()
{
var test = """
struct S
{
void M()
{
throw new System.Exception();
}
}
""";
await new VerifyCS.Test
{
TestCode = test,
FixedCode = test,
}.RunAsync();
}
[Fact]
public async Task TestNotWithBadOperation()
{
var test = """
struct S
{
void M()
{
{|CS0103:Goo|}();
}
}
""";
await new VerifyCS.Test
{
TestCode = test,
FixedCode = test,
}.RunAsync();
}
[Fact]
public async Task TestWithLinqRewrite()
{
await new VerifyCS.Test
{
TestCode = """
using System.Collections.Generic;
using System.Linq;
struct S
{
void [|M|](IEnumerable<int> x)
{
var v = from y in x
select y;
}
}
""",
FixedCode = """
using System.Collections.Generic;
using System.Linq;
struct S
{
readonly void M(IEnumerable<int> x)
{
var v = from y in x
select y;
}
}
""",
}.RunAsync();
}
}
|