|
// 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.CodeFixes;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.UsePatternMatching;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Diagnostics;
using Microsoft.CodeAnalysis.Test.Utilities;
using Roslyn.Test.Utilities;
using Xunit;
using Xunit.Abstractions;
namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.UsePatternMatching;
[Trait(Traits.Feature, Traits.Features.CodeActionsInlineTypeCheck)]
public sealed class CSharpIsAndCastCheckWithoutNameDiagnosticAnalyzerTests(ITestOutputHelper logger)
: AbstractCSharpDiagnosticProviderBasedUserDiagnosticTest_NoEditor(logger)
{
internal override (DiagnosticAnalyzer, CodeFixProvider) CreateDiagnosticProviderAndFixer(Workspace workspace)
=> (new CSharpIsAndCastCheckWithoutNameDiagnosticAnalyzer(), new CSharpIsAndCastCheckWithoutNameCodeFixProvider());
[Fact]
public async Task TestBinaryExpression()
{
await TestInRegularAndScript1Async(
"""
class TestFile
{
int i;
bool M(object obj)
{
return [||]obj is TestFile && ((TestFile)obj).i > 0;
}
}
""",
"""
class TestFile
{
int i;
bool M(object obj)
{
return obj is TestFile {|Rename:file|} && file.i > 0;
}
}
""");
}
[Fact]
public async Task TestNotInCSharp6()
{
await TestMissingAsync(
"""
class TestFile
{
int i;
bool M(object obj)
{
return [||]obj is TestFile && ((TestFile)obj).i > 0;
}
}
""", parameters: new TestParameters(parseOptions: CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp6)));
}
[Fact]
public async Task TestExpressionBody()
{
await TestInRegularAndScript1Async(
"""
class TestFile
{
int i;
bool M(object obj)
=> [||]obj is TestFile && ((TestFile)obj).i > 0;
}
""",
"""
class TestFile
{
int i;
bool M(object obj)
=> obj is TestFile {|Rename:file|} && file.i > 0;
}
""");
}
[Fact]
public async Task TestField()
{
await TestInRegularAndScript1Async(
"""
class TestFile
{
int i;
static object obj;
bool M = [||]obj is TestFile && ((TestFile)obj).i > 0;
}
""",
"""
class TestFile
{
int i;
static object obj;
bool M = obj is TestFile {|Rename:file|} && file.i > 0;
}
""");
}
[Fact]
public async Task TestLambdaBody()
{
await TestInRegularAndScript1Async(
"""
using System;
class TestFile
{
int i;
void Goo(Func<bool> f) { }
bool M(object obj)
=> Goo(() => [||]obj is TestFile && ((TestFile)obj).i > 0, () => obj is TestFile && ((TestFile)obj).i > 0);
}
""",
"""
using System;
class TestFile
{
int i;
void Goo(Func<bool> f) { }
bool M(object obj)
=> Goo(() => obj is TestFile {|Rename:file|} && file.i > 0, () => obj is TestFile && ((TestFile)obj).i > 0);
}
""");
}
[Fact]
public async Task TestDefiniteAssignment1()
{
await TestInRegularAndScript1Async(
"""
class TestFile
{
int i;
bool M(object obj)
{
if ([||]obj is TestFile)
{
M(((TestFile)obj).i);
M(((TestFile)obj).i);
}
else
{
M(((TestFile)obj).i);
M(((TestFile)obj).i);
}
}
}
""",
"""
class TestFile
{
int i;
bool M(object obj)
{
if (obj is TestFile {|Rename:file|})
{
M(file.i);
M(file.i);
}
else
{
M(((TestFile)obj).i);
M(((TestFile)obj).i);
}
}
}
""");
}
[Fact]
public async Task TestDefiniteAssignment2()
{
await TestInRegularAndScript1Async(
"""
class TestFile
{
int i;
bool M(object obj)
{
if (!([||]obj is TestFile))
{
M(((TestFile)obj).i);
M(((TestFile)obj).i);
}
else
{
M(((TestFile)obj).i);
M(((TestFile)obj).i);
}
}
}
""",
"""
class TestFile
{
int i;
bool M(object obj)
{
if (!(obj is TestFile {|Rename:file|}))
{
M(((TestFile)obj).i);
M(((TestFile)obj).i);
}
else
{
M(file.i);
M(file.i);
}
}
}
""");
}
[Fact]
public async Task TestNotOnAnalyzerMatch()
{
await TestMissingAsync(
"""
class TestFile
{
bool M(object obj)
{
if ([||]obj is TestFile)
{
var file = (TestFile)obj;
}
}
}
""");
}
[Fact]
public async Task TestNotOnNullable()
{
await TestMissingAsync(
"""
struct TestFile
{
bool M(object obj)
{
if ([||]obj is TestFile?)
{
var i = ((TestFile?)obj).Value;
}
}
}
""");
}
[Fact]
public async Task TestComplexMatch()
{
await TestInRegularAndScript1Async(
"""
class TestFile
{
int i;
bool M(object obj)
{
return [||]M(null) is TestFile && ((TestFile)M(null)).i > 0;
}
}
""",
"""
class TestFile
{
int i;
bool M(object obj)
{
return M(null) is TestFile {|Rename:file|} && file.i > 0;
}
}
""");
}
[Fact]
public async Task TestTrivia()
{
await TestInRegularAndScript1Async(
"""
class TestFile
{
int i;
bool M(object obj)
{
return [||]obj is TestFile && /*before*/ ((TestFile)obj) /*after*/.i > 0;
}
}
""",
"""
class TestFile
{
int i;
bool M(object obj)
{
return obj is TestFile {|Rename:file|} && /*before*/ file /*after*/.i > 0;
}
}
""");
}
[Fact]
public async Task TestFixOnlyAfterIsCheck()
{
await TestInRegularAndScript1Async(
"""
class TestFile
{
int i;
bool M(object obj)
{
return ((TestFile)obj).i > 0 && [||]obj is TestFile && ((TestFile)obj).i > 0;
}
}
""",
"""
class TestFile
{
int i;
bool M(object obj)
{
return ((TestFile)obj).i > 0 && obj is TestFile {|Rename:file|} && file.i > 0;
}
}
""");
}
[Fact]
public async Task TestArrayNaming()
{
await TestInRegularAndScript1Async(
"""
class TestFile
{
int i;
bool M(object obj)
{
return [||]obj is int[] && ((int[])obj) > 0;
}
}
""",
"""
class TestFile
{
int i;
bool M(object obj)
{
return obj is int[] {|Rename:v|} && v > 0;
}
}
""");
}
[Fact]
public async Task TestNamingConflict1()
{
await TestInRegularAndScript1Async(
"""
class TestFile
{
int i;
bool M(object obj)
{
TestFile file = null;
return [||]obj is TestFile && ((TestFile)obj).i > 0;
}
}
""",
"""
class TestFile
{
int i;
bool M(object obj)
{
TestFile file = null;
return obj is TestFile {|Rename:file1|} && file1.i > 0;
}
}
""");
}
[Fact]
public async Task TestNamingConflict2()
{
await TestInRegularAndScript1Async(
"""
class TestFile
{
int i;
bool M(object obj)
{
if ([||]obj is TestFile)
{
TestFile file = null;
M(((TestFile)obj).i);
}
}
}
""",
"""
class TestFile
{
int i;
bool M(object obj)
{
if (obj is TestFile {|Rename:file1|})
{
TestFile file = null;
M(file1.i);
}
}
}
""");
}
[Fact]
public async Task TestNamingNoConflict1()
{
await TestInRegularAndScript1Async(
"""
class TestFile
{
int i;
bool M(object obj)
{
if ([||]obj is TestFile)
{
var v = new { file = 0 };
M(((TestFile)obj).i);
}
}
}
""",
"""
class TestFile
{
int i;
bool M(object obj)
{
if (obj is TestFile {|Rename:file|})
{
var v = new { file = 0 };
M(file.i);
}
}
}
""");
}
[Fact]
public async Task TestNamingNoConflict2()
{
await TestInRegularAndScript1Async(
"""
class TestFile
{
int i;
bool M(object obj)
{
if ([||]obj is TestFile)
{
var v = (file: 0, x: 1);
M(((TestFile)obj).i);
}
}
}
""",
"""
class TestFile
{
int i;
bool M(object obj)
{
if (obj is TestFile {|Rename:file|})
{
var v = (file: 0, x: 1);
M(file.i);
}
}
}
""");
}
[Fact]
public async Task TestNamingNoConflict3()
{
await TestInRegularAndScript1Async(
"""
class X { public int file; }
class TestFile
{
int i;
bool M(object obj, X x)
{
if ([||]obj is TestFile)
{
var v = new { x.file };
M(((TestFile)obj).i);
}
}
}
""",
"""
class X { public int file; }
class TestFile
{
int i;
bool M(object obj, X x)
{
if (obj is TestFile {|Rename:file|})
{
var v = new { x.file };
M(file.i);
}
}
}
""");
}
[Fact]
public async Task TestNamingNoConflict4()
{
await TestInRegularAndScript1Async(
"""
class X { public int file; }
class TestFile
{
int i;
bool M(object obj, X x)
{
if ([||]obj is TestFile)
{
var v = (x.file, 0);
M(((TestFile)obj).i);
}
}
}
""",
"""
class X { public int file; }
class TestFile
{
int i;
bool M(object obj, X x)
{
if (obj is TestFile {|Rename:file|})
{
var v = (x.file, 0);
M(file.i);
}
}
}
""");
}
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/51340")]
public async Task TestNoDiagnosticWhenCS0103Happens()
{
await TestDiagnosticMissingAsync(
"""
using System.Linq;
class Bar
{
private void Foo()
{
var objects = new SpecificThingType[100];
var d = from obj in objects
let aGenericThing = obj.Prop
where aGenericTh[||]ing is SpecificThingType
let specificThing = (SpecificThingType)aGenericThing
select (obj, specificThing);
}
}
class SpecificThingType
{
public SpecificThingType Prop { get; }
}
""");
}
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/58558")]
public async Task TestInExpressionTree1()
{
await TestMissingAsync(
"""
using System.Linq.Expressions;
object? o = null;
Expression<Func<bool>> test = () => [||]o is int && (int)o > 5;
""");
}
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/58558")]
public async Task TestInExpressionTree2()
{
await TestMissingAsync(
"""
using System.Linq.Expressions;
class C
{
void M()
{
object? o = null;
Expression<Func<bool>> test = () => [||]o is int && (int)o > 5;
}
}
""");
}
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/68051")]
public async Task TestNotWhenCrossingStaticLambda()
{
await TestMissingAsync(
"""
using System;
class C
{
void Main(object o)
{
if ([||]o is string)
{
M(static (object o) =>
{
var s = (string)o;
});
}
}
private void M(Action<object> value)
{
}
}
""");
}
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/68051")]
public async Task TestNotWhenCrossingInstanceLambdaThatReferencesDifferentVariable()
{
await TestMissingAsync(
"""
using System;
class C
{
void Main(object o)
{
if ([||]o is string)
{
M((object o) =>
{
var s = (string)o;
});
}
}
private void M(Action<object> value)
{
}
}
""");
}
}
|