|
// 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.
#nullable disable
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeStyle;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.CodeStyle;
using Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions;
using Microsoft.CodeAnalysis.Test.Utilities;
using Roslyn.Test.Utilities;
using Xunit;
using Xunit.Abstractions;
namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.RemoveUnusedParametersAndValues;
[Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedValues)]
public partial class RemoveUnusedValueExpressionStatementTests : RemoveUnusedValuesTestsBase
{
public RemoveUnusedValueExpressionStatementTests(ITestOutputHelper logger)
: base(logger)
{
}
private protected override OptionsCollection PreferNone
=> Option(CSharpCodeStyleOptions.UnusedValueExpressionStatement,
new CodeStyleOption2<UnusedValuePreference>(UnusedValuePreference.DiscardVariable, NotificationOption2.None));
private protected override OptionsCollection PreferDiscard
=> Option(CSharpCodeStyleOptions.UnusedValueExpressionStatement,
new CodeStyleOption2<UnusedValuePreference>(UnusedValuePreference.DiscardVariable, NotificationOption2.Silent));
private protected override OptionsCollection PreferUnusedLocal
=> Option(CSharpCodeStyleOptions.UnusedValueExpressionStatement,
new CodeStyleOption2<UnusedValuePreference>(UnusedValuePreference.UnusedLocalVariable, NotificationOption2.Silent));
[Fact]
public async Task ExpressionStatement_Suppressed()
{
await TestMissingInRegularAndScriptAsync(
"""
class C
{
void M()
{
[|M2()|];
}
int M2() => 0;
}
""", options: PreferNone);
}
[Fact]
public async Task ExpressionStatement_PreferDiscard_CSharp6()
{
// Discard not supported in C# 6.0, so we fallback to unused local variable.
await TestInRegularAndScriptAsync(
"""
class C
{
void M()
{
[|M2()|];
}
int M2() => 0;
}
""",
"""
class C
{
void M()
{
var unused = M2();
}
int M2() => 0;
}
""", options: PreferDiscard,
parseOptions: CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp6));
}
[Theory]
[InlineData(nameof(PreferDiscard))]
[InlineData(nameof(PreferUnusedLocal))]
public async Task ExpressionStatement_VariableInitialization(string optionName)
{
await TestMissingInRegularAndScriptAsync(
"""
class C
{
void M()
{
int x = [|M2()|];
}
int M2() => 0;
}
""", optionName);
}
[Theory]
[InlineData(nameof(PreferDiscard), "_")]
[InlineData(nameof(PreferUnusedLocal), "var unused")]
public async Task ExpressionStatement_NonConstantPrimitiveTypeValue(string optionName, string fix)
{
await TestInRegularAndScriptAsync(
"""
class C
{
void M()
{
[|M2()|];
}
int M2() => 0;
}
""",
$$"""
class C
{
void M()
{
{{fix}} = M2();
}
int M2() => 0;
}
""", optionName);
}
[Theory]
[InlineData(nameof(PreferDiscard), "_")]
[InlineData(nameof(PreferUnusedLocal), "var unused")]
public async Task ExpressionStatement_UserDefinedType(string optionName, string fix)
{
await TestInRegularAndScriptAsync(
"""
class C
{
void M()
{
[|M2()|];
}
C M2() => new C();
}
""",
$$"""
class C
{
void M()
{
{{fix}} = M2();
}
C M2() => new C();
}
""", optionName);
}
[Theory]
[InlineData(nameof(PreferDiscard))]
[InlineData(nameof(PreferUnusedLocal))]
public async Task ExpressionStatement_ConstantValue(string optionName)
{
await TestMissingInRegularAndScriptAsync(
"""
class C
{
void M()
{
[|1|];
}
}
""", optionName);
}
[Theory]
[InlineData(nameof(PreferDiscard))]
[InlineData(nameof(PreferUnusedLocal))]
public async Task ExpressionStatement_SyntaxError(string optionName)
{
await TestMissingInRegularAndScriptAsync(
"""
class C
{
void M()
{
[|M2(,)|];
}
int M2() => 0;
}
""", optionName);
}
[Theory]
[InlineData(nameof(PreferDiscard))]
[InlineData(nameof(PreferUnusedLocal))]
public async Task ExpressionStatement_SemanticError(string optionName)
{
await TestMissingInRegularAndScriptAsync(
"""
class C
{
void M()
{
[|M2()|];
}
}
""", optionName);
}
[Theory, WorkItem("https://github.com/dotnet/roslyn/issues/33073")]
[InlineData(nameof(PreferDiscard))]
[InlineData(nameof(PreferUnusedLocal))]
public async Task ExpressionStatement_SemanticError_02(string optionName)
{
await TestMissingInRegularAndScriptAsync(
"""
class C
{
void M()
{
[|M2()|];
}
UndefinedType M2() => null;
}
""", optionName);
}
[Theory, WorkItem("https://github.com/dotnet/roslyn/issues/33073")]
[InlineData(nameof(PreferDiscard))]
[InlineData(nameof(PreferUnusedLocal))]
public async Task ExpressionStatement_SemanticError_03(string optionName)
{
await TestMissingInRegularAndScriptAsync(
"""
using System.Threading.Tasks;
class C
{
private async Task M()
{
// error CS0103: The name 'CancellationToken' does not exist in the current context
[|await Task.Delay(0, CancellationToken.None).ConfigureAwait(false)|];
}
}
""", optionName);
}
[Theory, WorkItem("https://github.com/dotnet/roslyn/issues/33073")]
[InlineData(nameof(PreferDiscard))]
[InlineData(nameof(PreferUnusedLocal))]
public async Task ExpressionStatement_SemanticError_04(string optionName)
{
await TestMissingInRegularAndScriptAsync(
"""
class C
{
private async Task M()
{
// error CS0103: The name 'Task' does not exist in the current context
// error CS0103: The name 'CancellationToken' does not exist in the current context
// error CS1983: The return type of an async method must be void, Task or Task<T>
[|await Task.Delay(0, CancellationToken.None).ConfigureAwait(false)|];
}
}
""", optionName);
}
[Theory]
[InlineData(nameof(PreferDiscard))]
[InlineData(nameof(PreferUnusedLocal))]
public async Task ExpressionStatement_VoidReturningMethodCall(string optionName)
{
await TestMissingInRegularAndScriptAsync(
"""
class C
{
void M()
{
[|M2()|];
}
void M2() { }
}
""", optionName);
}
[Theory]
[InlineData("=")]
[InlineData("+=")]
public async Task ExpressionStatement_AssignmentExpression(string op)
{
await TestMissingInRegularAndScriptWithAllOptionsAsync(
$$"""
class C
{
void M(int x)
{
x {{op}} [|M2()|];
}
int M2() => 0;
}
""");
}
[Theory]
[InlineData("x++")]
[InlineData("x--")]
[InlineData("++x")]
[InlineData("--x")]
public async Task ExpressionStatement_IncrementOrDecrement(string incrementOrDecrement)
{
await TestMissingInRegularAndScriptWithAllOptionsAsync(
$$"""
class C
{
int M(int x)
{
[|{{incrementOrDecrement}}|];
return x;
}
}
""");
}
[Fact]
public async Task ExpressionStatement_UnusedLocal_NameAlreadyUsed()
{
await TestInRegularAndScriptAsync(
"""
class C
{
void M()
{
var unused = M2();
[|M2()|];
}
int M2() => 0;
}
""",
"""
class C
{
void M()
{
var unused = M2();
var unused1 = M2();
}
int M2() => 0;
}
""", options: PreferUnusedLocal);
}
[Fact]
public async Task ExpressionStatement_UnusedLocal_NameAlreadyUsed_02()
{
await TestInRegularAndScriptAsync(
"""
class C
{
void M()
{
[|M2()|];
var unused = M2();
}
int M2() => 0;
}
""",
"""
class C
{
void M()
{
var unused1 = M2();
var unused = M2();
}
int M2() => 0;
}
""", options: PreferUnusedLocal);
}
[Fact]
public async Task ExpressionStatement_UnusedLocal_NameAlreadyUsed_03()
{
await TestInRegularAndScriptAsync(
"""
class C
{
void M(int p)
{
[|M2()|];
if (p > 0)
{
var unused = M2();
}
}
int M2() => 0;
}
""",
"""
class C
{
void M(int p)
{
var unused1 = M2();
if (p > 0)
{
var unused = M2();
}
}
int M2() => 0;
}
""", options: PreferUnusedLocal);
}
[Fact]
public async Task ExpressionStatement_UnusedLocal_NameAlreadyUsed_04()
{
await TestInRegularAndScriptAsync(
"""
class C
{
void M(int p)
{
if (p > 0)
{
[|M2()|];
}
else
{
var unused = M2();
}
}
int M2() => 0;
}
""",
"""
class C
{
void M(int p)
{
if (p > 0)
{
var unused1 = M2();
}
else
{
var unused = M2();
}
}
int M2() => 0;
}
""", options: PreferUnusedLocal);
}
[Theory]
[InlineData(nameof(PreferDiscard), "_", "_", "_")]
[InlineData(nameof(PreferUnusedLocal), "var unused", "var unused", "var unused3")]
public async Task ExpressionStatement_FixAll(string optionName, string fix1, string fix2, string fix3)
{
await TestInRegularAndScriptAsync(
"""
class C
{
public C()
{
M2(); // Separate code block
}
void M(int unused1, int unused2)
{
{|FixAllInDocument:M2()|};
M2(); // Another instance in same code block
_ = M2(); // Already fixed
var x = M2(); // Different unused value diagnostic
}
int M2() => 0;
}
""",
$$"""
class C
{
public C()
{
{{fix1}} = M2(); // Separate code block
}
void M(int unused1, int unused2)
{
{{fix3}} = M2();
{{fix2}} = M2(); // Another instance in same code block
_ = M2(); // Already fixed
var x = M2(); // Different unused value diagnostic
}
int M2() => 0;
}
""", optionName);
}
[Fact]
public async Task ExpressionStatement_Trivia_PreferDiscard_01()
{
await TestInRegularAndScriptAsync(
"""
class C
{
void M()
{
// C1
[|M2()|]; // C2
// C3
}
int M2() => 0;
}
""",
"""
class C
{
void M()
{
// C1
_ = M2(); // C2
// C3
}
int M2() => 0;
}
""", options: PreferDiscard);
}
[Fact]
public async Task ExpressionStatement_Trivia_PreferDiscard_02()
{
await TestInRegularAndScriptAsync(
"""
class C
{
void M()
{/*C0*/
/*C1*/[|M2()|]/*C2*/;/*C3*/
/*C4*/
}
int M2() => 0;
}
""",
"""
class C
{
void M()
{/*C0*/
/*C1*/
_ = M2()/*C2*/;/*C3*/
/*C4*/
}
int M2() => 0;
}
""", options: PreferDiscard);
}
[Fact]
public async Task ExpressionStatement_Trivia_PreferUnusedLocal_01()
{
await TestInRegularAndScriptAsync(
"""
class C
{
void M()
{
// C1
[|M2()|]; // C2
// C3
}
int M2() => 0;
}
""",
"""
class C
{
void M()
{
// C1
var unused = M2(); // C2
// C3
}
int M2() => 0;
}
""", options: PreferUnusedLocal);
}
[Fact]
public async Task ExpressionStatement_Trivia_PreferUnusedLocal_02()
{
await TestInRegularAndScriptAsync(
"""
class C
{
void M()
{/*C0*/
/*C1*/[|M2()|]/*C2*/;/*C3*/
/*C4*/
}
int M2() => 0;
}
""",
"""
class C
{
void M()
{/*C0*/
/*C1*/
var unused = M2()/*C2*/;/*C3*/
/*C4*/
}
int M2() => 0;
}
""", options: PreferUnusedLocal);
}
[Theory, WorkItem("https://github.com/dotnet/roslyn/issues/32942")]
[InlineData(nameof(PreferDiscard))]
[InlineData(nameof(PreferUnusedLocal))]
public async Task ExpressionBodiedMember_01(string optionName)
{
await TestMissingInRegularAndScriptAsync(
"""
class C
{
void M() => [|M2()|];
int M2() => 0;
}
""", optionName);
}
[Theory, WorkItem("https://github.com/dotnet/roslyn/issues/32942")]
[InlineData(nameof(PreferDiscard))]
[InlineData(nameof(PreferUnusedLocal))]
public async Task ExpressionBodiedMember_02(string optionName)
{
await TestMissingInRegularAndScriptAsync(
"""
class C
{
void M()
{
System.Action a = () => [|M2()|];
}
int M2() => 0;
}
""", optionName);
}
[Theory, WorkItem("https://github.com/dotnet/roslyn/issues/32942")]
[InlineData(nameof(PreferDiscard))]
[InlineData(nameof(PreferUnusedLocal))]
public async Task ExpressionBodiedMember_03(string optionName)
{
await TestMissingInRegularAndScriptAsync(
"""
class C
{
void M()
{
LocalFunction();
return;
void LocalFunction() => [|M2()|];
}
int M2() => 0;
}
""", optionName);
}
[Theory, WorkItem("https://github.com/dotnet/roslyn/issues/43648")]
[InlineData(nameof(PreferDiscard))]
[InlineData(nameof(PreferUnusedLocal))]
public async Task ExpressionStatement_Dynamic(string optionName)
{
await TestMissingInRegularAndScriptAsync(
"""
using System.Collections.Generic;
class C
{
void M()
{
List<dynamic> returnValue = new List<dynamic>();
dynamic dynamicValue = new object();
[|returnValue.Add(dynamicValue)|];
}
}
""", optionName);
}
}
|