File: Microsoft.NetCore.Analyzers\Performance\UseAsSpanInsteadOfRangeIndexerTests.cs
Web Access
Project: ..\..\..\src\Microsoft.CodeAnalysis.NetAnalyzers\tests\Microsoft.CodeAnalysis.NetAnalyzers.UnitTests\Microsoft.CodeAnalysis.NetAnalyzers.UnitTests.csproj (Microsoft.CodeAnalysis.NetAnalyzers.UnitTests)
// Copyright (c) Microsoft.  All Rights Reserved.  Licensed under the MIT license.  See License.txt in the project root for license information.
 
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Testing;
using Xunit;
using VerifyCS = Test.Utilities.CSharpCodeFixVerifier<
    Microsoft.NetCore.Analyzers.Performance.UseAsSpanInsteadOfRangeIndexerAnalyzer,
    Microsoft.NetCore.CSharp.Analyzers.Performance.CSharpUseAsSpanInsteadOfRangeIndexerFixer>;
 
namespace Microsoft.NetCore.Analyzers.Performance.UnitTests
{
    public static partial class UseAsSpanInsteadOfRangeIndexerTests
    {
#pragma warning disable CA1819
        public static object[][] ArrayElementTypes { get; } =
            new[]
            {
                new object[] { "int" },
                new object[] { "byte" },
                new object[] { "object" },
                new object[] { "string" },
            };
#pragma warning restore CA1819
 
        [Fact]
        public static async Task StringToStringLocalAsync()
        {
            await TestCSAsync(@"
using System;
 
public class TestClass
{
    public string TestMethod(string input)
    {
        string tmp = input[3..5];
        return tmp;
    }
}");
        }
 
        [Fact]
        public static async Task StringToStringReturnAsync()
        {
            await TestCSAsync(@"
using System;
 
public class TestClass
{
    public string TestMethod(string input)
    {
        return input[3..5];
    }
}");
        }
 
        [Fact]
        public static async Task StringToStringParameterAsync()
        {
            await TestCSAsync(@"
using System;
 
public class TestClass
{
    private static int TestMethod2(string input) => input.Length;
 
    public int TestMethod(string input)
    {
        return TestMethod2(input[3..5]);
    }
}");
        }
 
        [Fact]
        public static async Task StringToSpanLocalAsync()
        {
            // This test is responsible for verifying the placeholders for string to ReadOnlySpan<char>.
            await TestCSAsync(@"
using System;
 
public class TestClass
{
    public int TestMethod(string input)
    {
        ReadOnlySpan<char> tmp = input[3..5];
        return tmp.Length;
    }
}",
                @"
using System;
 
public class TestClass
{
    public int TestMethod(string input)
    {
        ReadOnlySpan<char> tmp = input.AsSpan()[3..5];
        return tmp.Length;
    }
}",
                VerifyCS.Diagnostic(UseAsSpanInsteadOfRangeIndexerAnalyzer.StringRule).
                    WithSpan(8, 34, 8, 34 + 11).
                    WithArguments("AsSpan", "System.Range", "string"));
        }
 
        [Fact]
        public static async Task StringToSpanReturnAsync()
        {
            await TestCSAsync(@"
using System;
 
public class TestClass
{
    public ReadOnlySpan<char> TestMethod(string input)
    {
        return {|CA1831:input[3..5]|};
    }
}",
                @"
using System;
 
public class TestClass
{
    public ReadOnlySpan<char> TestMethod(string input)
    {
        return input.AsSpan()[3..5];
    }
}");
        }
 
        [Fact]
        public static async Task StringToSpanParameterAsync()
        {
            await TestCSAsync(@"
using System;
 
public class TestClass
{
    private static int TestMethod2(ReadOnlySpan<char> input) => input.Length;
 
    public int TestMethod(string input)
    {
        return TestMethod2({|CA1831:input[3..5]|});
    }
}",
                @"
using System;
 
public class TestClass
{
    private static int TestMethod2(ReadOnlySpan<char> input) => input.Length;
 
    public int TestMethod(string input)
    {
        return TestMethod2(input.AsSpan()[3..5]);
    }
}");
        }
 
        [Fact]
        public static async Task StringToSpanParameterMultipleTimesAsync()
        {
            await TestCSAsync(@"
using System;
 
public class TestClass
{
    private static int TestMethod2(ReadOnlySpan<char> input) => input.Length;
 
    public int TestMethod(string input)
    {
        return TestMethod2({|CA1831:input[3..5]|}) + TestMethod2({|CA1831:input[1..^2]|});
    }
}",
                @"
using System;
 
public class TestClass
{
    private static int TestMethod2(ReadOnlySpan<char> input) => input.Length;
 
    public int TestMethod(string input)
    {
        return TestMethod2(input.AsSpan()[3..5]) + TestMethod2(input.AsSpan()[1..^2]);
    }
}");
        }
 
        [Fact]
        public static async Task StringToSpanCastLocalAsync()
        {
            await TestCSAsync(@"
using System;
 
public class TestClass
{
    public int TestMethod(string input)
    {
        ReadOnlySpan<char> tmp = (ReadOnlySpan<char>)input[3..5];
        return tmp.Length;
    }
}");
        }
 
        [Fact]
        public static async Task StringToSpanCastReturnAsync()
        {
            await TestCSAsync(@"
using System;
 
public class TestClass
{
    public ReadOnlySpan<char> TestMethod(string input)
    {
        return (ReadOnlySpan<char>)input[3..5];
    }
}");
        }
 
        [Fact]
        public static async Task StringToSpanCastParameterAsync()
        {
            await TestCSAsync(@"
using System;
 
public class TestClass
{
    private static int TestMethod2(ReadOnlySpan<char> input) => input.Length;
 
    public int TestMethod(string input)
    {
        return TestMethod2((ReadOnlySpan<char>)input[3..5]);
    }
}");
        }
 
        [Theory]
        [MemberData(nameof(ArrayElementTypes))]
        public static async Task ArrayToArrayLocalAsync(string typeName)
        {
            await TestCSAsync(@"
using System;
 
public class TestClass
{
    public " + typeName + @"[] TestMethod(" + typeName + @"[] input)
    {
        " + typeName + @"[] tmp = input[3..5];
        return tmp;
    }
}");
        }
 
        [Theory]
        [MemberData(nameof(ArrayElementTypes))]
        public static async Task ArrayToArrayReturnAsync(string typeName)
        {
            await TestCSAsync(@"
using System;
 
public class TestClass
{
    public " + typeName + @"[] TestMethod(" + typeName + @"[] input)
    {
        return input[3..5];
    }
}");
        }
 
        [Theory]
        [MemberData(nameof(ArrayElementTypes))]
        public static async Task ArrayToArrayParameterAsync(string typeName)
        {
            await TestCSAsync(@"
using System;
 
public class TestClass
{
    private static int TestMethod2(" + typeName + @"[] input) => input.Length;
 
    public int TestMethod(" + typeName + @"[] input)
    {
        return TestMethod2(input[3..5]);
    }
}");
        }
 
        [Theory]
        [MemberData(nameof(ArrayElementTypes))]
        public static async Task ArrayToReadOnlySpanLocalAsync(string typeName)
        {
            // This test is responsible for verifying the placeholders for T[] to ReadOnlySpan<T>.
            await TestCSAsync(@"
using System;
 
public class TestClass
{
    public int TestMethod(" + typeName + @"[] input)
    {
        ReadOnlySpan<" + typeName + @"> tmp = input[3..5];
        return tmp.Length;
    }
}",
                @"
using System;
 
public class TestClass
{
    public int TestMethod(" + typeName + @"[] input)
    {
        ReadOnlySpan<" + typeName + @"> tmp = input.AsSpan()[3..5];
        return tmp.Length;
    }
}",
                VerifyCS.Diagnostic(UseAsSpanInsteadOfRangeIndexerAnalyzer.ArrayReadOnlyRule).
                    WithSpan(8, 30 + typeName.Length, 8, 30 + 11 + typeName.Length).
                    WithArguments("AsSpan", "System.Range", typeName + "[]"));
        }
 
        [Theory]
        [MemberData(nameof(ArrayElementTypes))]
        public static async Task ArrayToReadOnlySpanReturnAsync(string typeName)
        {
            await TestCSAsync(@"
using System;
 
public class TestClass
{
    public ReadOnlySpan<" + typeName + @"> TestMethod(" + typeName + @"[] input)
    {
        return {|CA1832:input[3..5]|};
    }
}",
                @"
using System;
 
public class TestClass
{
    public ReadOnlySpan<" + typeName + @"> TestMethod(" + typeName + @"[] input)
    {
        return input.AsSpan()[3..5];
    }
}");
        }
 
        [Theory]
        [MemberData(nameof(ArrayElementTypes))]
        public static async Task ArrayToReadOnlySpanParameterAsync(string typeName)
        {
            await TestCSAsync(@"
using System;
 
public class TestClass
{
    private static int TestMethod2(ReadOnlySpan<" + typeName + @"> input) => input.Length;
 
    public int TestMethod(" + typeName + @"[] input)
    {
        return TestMethod2({|CA1832:input[3..5]|});
    }
}",
                @"
using System;
 
public class TestClass
{
    private static int TestMethod2(ReadOnlySpan<" + typeName + @"> input) => input.Length;
 
    public int TestMethod(" + typeName + @"[] input)
    {
        return TestMethod2(input.AsSpan()[3..5]);
    }
}");
        }
 
        [Theory]
        [MemberData(nameof(ArrayElementTypes))]
        public static async Task ArrayToReadOnlySpanCastLocalAsync(string typeName)
        {
            await TestCSAsync(@"
using System;
 
public class TestClass
{
    public int TestMethod(" + typeName + @"[] input)
    {
        ReadOnlySpan<" + typeName + @"> tmp = (ReadOnlySpan<" + typeName + @">)input[3..5];
        return tmp.Length;
    }
}");
        }
 
        [Theory]
        [MemberData(nameof(ArrayElementTypes))]
        public static async Task ArrayToReadOnlySpanCastReturnAsync(string typeName)
        {
            await TestCSAsync(@"
using System;
 
public class TestClass
{
    public ReadOnlySpan<" + typeName + @"> TestMethod(" + typeName + @"[] input)
    {
        return (ReadOnlySpan<" + typeName + @">)input[3..5];
    }
}");
        }
 
        [Theory]
        [MemberData(nameof(ArrayElementTypes))]
        public static async Task ArrayToReadOnlySpanCastParameterAsync(string typeName)
        {
            await TestCSAsync(@"
using System;
 
public class TestClass
{
    private static int TestMethod2<T>(ReadOnlySpan<T> input) => input.Length;
 
    public int TestMethod(" + typeName + @"[] input)
    {
        return TestMethod2((ReadOnlySpan<" + typeName + @">)input[3..5]);
    }
}");
        }
 
        [Theory]
        [MemberData(nameof(ArrayElementTypes))]
        public static async Task ArrayToSpanLocalAsync(string typeName)
        {
            // This test is responsible for verifying the placeholders for T[] to Span<T>.
            await TestCSAsync(@"
using System;
 
public class TestClass
{
    public int TestMethod(" + typeName + @"[] input)
    {
        Span<" + typeName + @"> tmp = input[3..5];
        return tmp.Length;
    }
}",
                @"
using System;
 
public class TestClass
{
    public int TestMethod(" + typeName + @"[] input)
    {
        Span<" + typeName + @"> tmp = input.AsSpan()[3..5];
        return tmp.Length;
    }
}",
                VerifyCS.Diagnostic(UseAsSpanInsteadOfRangeIndexerAnalyzer.ArrayReadWriteRule).
                    WithSpan(8, 22 + typeName.Length, 8, 22 + 11 + typeName.Length).
                    WithArguments("AsSpan", "System.Range", typeName + "[]"));
        }
 
        [Theory]
        [MemberData(nameof(ArrayElementTypes))]
        public static async Task ArrayToSpanReturnAsync(string typeName)
        {
            await TestCSAsync(@"
using System;
 
public class TestClass
{
    public Span<" + typeName + @"> TestMethod(" + typeName + @"[] input)
    {
        return {|CA1833:input[3..5]|};
    }
}",
                @"
using System;
 
public class TestClass
{
    public Span<" + typeName + @"> TestMethod(" + typeName + @"[] input)
    {
        return input.AsSpan()[3..5];
    }
}");
        }
 
        [Theory]
        [MemberData(nameof(ArrayElementTypes))]
        public static async Task ArrayToSpanParameterAsync(string typeName)
        {
            await TestCSAsync(@"
using System;
 
public class TestClass
{
    private static int TestMethod2(Span<" + typeName + @"> input) => input.Length;
 
    public int TestMethod(" + typeName + @"[] input)
    {
        return TestMethod2({|CA1833:input[3..5]|});
    }
}",
                @"
using System;
 
public class TestClass
{
    private static int TestMethod2(Span<" + typeName + @"> input) => input.Length;
 
    public int TestMethod(" + typeName + @"[] input)
    {
        return TestMethod2(input.AsSpan()[3..5]);
    }
}");
        }
 
        [Theory]
        [MemberData(nameof(ArrayElementTypes))]
        public static async Task ArrayToSpanCastLocalAsync(string typeName)
        {
            await TestCSAsync(@"
using System;
 
public class TestClass
{
    public int TestMethod(" + typeName + @"[] input)
    {
        Span<" + typeName + @"> tmp = (Span<" + typeName + @">)input[3..5];
        return tmp.Length;
    }
}");
        }
 
        [Theory]
        [MemberData(nameof(ArrayElementTypes))]
        public static async Task ArrayToSpanCastReturnAsync(string typeName)
        {
            await TestCSAsync(@"
using System;
 
public class TestClass
{
    public Span<" + typeName + @"> TestMethod(" + typeName + @"[] input)
    {
        return (Span<" + typeName + @">)input[3..5];
    }
}");
        }
 
        [Theory]
        [MemberData(nameof(ArrayElementTypes))]
        public static async Task ArrayToSpanCastParameterAsync(string typeName)
        {
            await TestCSAsync(@"
using System;
 
public class TestClass
{
    private static int TestMethod2<T>(Span<T> input) => input.Length;
 
    public int TestMethod(" + typeName + @"[] input)
    {
        return TestMethod2((Span<" + typeName + @">)input[3..5]);
    }
}");
        }
 
        [Theory]
        [MemberData(nameof(ArrayElementTypes))]
        public static async Task ArrayToReadOnlyMemoryLocalAsync(string typeName)
        {
            // This test is responsible for verifying the placeholders for T[] to ReadOnlyMemory<T>.
            await TestCSAsync(@"
using System;
 
public class TestClass
{
    public int TestMethod(" + typeName + @"[] input)
    {
        ReadOnlyMemory<" + typeName + @"> tmp = input[3..5];
        return tmp.Length;
    }
}",
                @"
using System;
 
public class TestClass
{
    public int TestMethod(" + typeName + @"[] input)
    {
        ReadOnlyMemory<" + typeName + @"> tmp = input.AsMemory()[3..5];
        return tmp.Length;
    }
}",
                VerifyCS.Diagnostic(UseAsSpanInsteadOfRangeIndexerAnalyzer.ArrayReadOnlyRule).
                    WithSpan(8, 32 + typeName.Length, 8, 32 + 11 + typeName.Length).
                    WithArguments("AsMemory", "System.Range", typeName + "[]"));
        }
 
        [Theory]
        [MemberData(nameof(ArrayElementTypes))]
        public static async Task ArrayToReadOnlyMemoryReturnAsync(string typeName)
        {
            await TestCSAsync(@"
using System;
 
public class TestClass
{
    public ReadOnlyMemory<" + typeName + @"> TestMethod(" + typeName + @"[] input)
    {
        return {|CA1832:input[3..5]|};
    }
}",
                @"
using System;
 
public class TestClass
{
    public ReadOnlyMemory<" + typeName + @"> TestMethod(" + typeName + @"[] input)
    {
        return input.AsMemory()[3..5];
    }
}");
        }
 
        [Theory]
        [MemberData(nameof(ArrayElementTypes))]
        public static async Task ArrayToReadOnlyMemoryParameterAsync(string typeName)
        {
            await TestCSAsync(@"
using System;
 
public class TestClass
{
    private static int TestMethod2(ReadOnlyMemory<" + typeName + @"> input) => input.Length;
 
    public int TestMethod(" + typeName + @"[] input)
    {
        return TestMethod2({|CA1832:input[3..5]|});
    }
}",
                @"
using System;
 
public class TestClass
{
    private static int TestMethod2(ReadOnlyMemory<" + typeName + @"> input) => input.Length;
 
    public int TestMethod(" + typeName + @"[] input)
    {
        return TestMethod2(input.AsMemory()[3..5]);
    }
}"
                );
        }
 
        [Theory]
        [MemberData(nameof(ArrayElementTypes))]
        public static async Task ArrayToReadOnlyMemoryCastLocalAsync(string typeName)
        {
            await TestCSAsync(@"
using System;
 
public class TestClass
{
    public int TestMethod(" + typeName + @"[] input)
    {
        ReadOnlyMemory<" + typeName + @"> tmp = (ReadOnlyMemory<" + typeName + @">)input[3..5];
        return tmp.Length;
    }
}");
        }
 
        [Theory]
        [MemberData(nameof(ArrayElementTypes))]
        public static async Task ArrayToReadOnlyMemoryCastReturnAsync(string typeName)
        {
            await TestCSAsync(@"
using System;
 
public class TestClass
{
    public ReadOnlyMemory<" + typeName + @"> TestMethod(" + typeName + @"[] input)
    {
        return (ReadOnlyMemory<" + typeName + @">)input[3..5];
    }
}");
        }
 
        [Theory]
        [MemberData(nameof(ArrayElementTypes))]
        public static async Task ArrayToReadOnlyMemoryCastParameterAsync(string typeName)
        {
            await TestCSAsync(@"
using System;
 
public class TestClass
{
    private static int TestMethod2<T>(ReadOnlyMemory<T> input) => input.Length;
 
    public int TestMethod(" + typeName + @"[] input)
    {
        return TestMethod2((ReadOnlyMemory<" + typeName + @">)input[3..5]);
    }
}");
        }
 
        [Theory]
        [MemberData(nameof(ArrayElementTypes))]
        public static async Task ArrayToMemoryLocalAsync(string typeName)
        {
            // This test is responsible for verifying the placeholders for T[] to Memory<T>.
            await TestCSAsync(@"
using System;
 
public class TestClass
{
    public int TestMethod(" + typeName + @"[] input)
    {
        Memory<" + typeName + @"> tmp = input[3..5];
        return tmp.Length;
    }
}",
                @"
using System;
 
public class TestClass
{
    public int TestMethod(" + typeName + @"[] input)
    {
        Memory<" + typeName + @"> tmp = input.AsMemory()[3..5];
        return tmp.Length;
    }
}",
                VerifyCS.Diagnostic(UseAsSpanInsteadOfRangeIndexerAnalyzer.ArrayReadWriteRule).
                    WithSpan(8, 24 + typeName.Length, 8, 24 + 11 + typeName.Length).
                    WithArguments("AsMemory", "System.Range", typeName + "[]"));
        }
 
        [Theory]
        [MemberData(nameof(ArrayElementTypes))]
        public static async Task ArrayToMemoryReturnAsync(string typeName)
        {
            await TestCSAsync(@"
using System;
 
public class TestClass
{
    public Memory<" + typeName + @"> TestMethod(" + typeName + @"[] input)
    {
        return {|CA1833:input[3..5]|};
    }
}",
                @"
using System;
 
public class TestClass
{
    public Memory<" + typeName + @"> TestMethod(" + typeName + @"[] input)
    {
        return input.AsMemory()[3..5];
    }
}");
        }
 
        [Theory]
        [MemberData(nameof(ArrayElementTypes))]
        public static async Task ArrayToMemoryParameterAsync(string typeName)
        {
            await TestCSAsync(@"
using System;
 
public class TestClass
{
    private static int TestMethod2(Memory<" + typeName + @"> input) => input.Length;
 
    public int TestMethod(" + typeName + @"[] input)
    {
        return TestMethod2({|CA1833:input[3..5]|});
    }
}",
                @"
using System;
 
public class TestClass
{
    private static int TestMethod2(Memory<" + typeName + @"> input) => input.Length;
 
    public int TestMethod(" + typeName + @"[] input)
    {
        return TestMethod2(input.AsMemory()[3..5]);
    }
}");
        }
 
        [Theory]
        [MemberData(nameof(ArrayElementTypes))]
        public static async Task ArrayToMemoryCastLocalAsync(string typeName)
        {
            await TestCSAsync(@"
using System;
 
public class TestClass
{
    public int TestMethod(" + typeName + @"[] input)
    {
        Memory<" + typeName + @"> tmp = (Memory<" + typeName + @">)input[3..5];
        return tmp.Length;
    }
}");
        }
 
        [Theory]
        [MemberData(nameof(ArrayElementTypes))]
        public static async Task ArrayToMemoryCastReturnAsync(string typeName)
        {
            await TestCSAsync(@"
using System;
 
public class TestClass
{
    public Memory<" + typeName + @"> TestMethod(" + typeName + @"[] input)
    {
        return (Memory<" + typeName + @">)input[3..5];
    }
}");
        }
 
        [Theory]
        [MemberData(nameof(ArrayElementTypes))]
        public static async Task ArrayToMemoryCastParameterAsync(string typeName)
        {
            await TestCSAsync(@"
using System;
 
public class TestClass
{
    private static int TestMethod2<T>(Memory<T> input) => input.Length;
 
    public int TestMethod(" + typeName + @"[] input)
    {
        return TestMethod2((Memory<" + typeName + @">)input[3..5]);
    }
}");
        }
 
        private static Task TestCSAsync(string source, string corrected, params DiagnosticResult[] expected)
        {
            var test = new VerifyCS.Test
            {
                TestCode = source,
                ReferenceAssemblies = ReferenceAssemblies.Net.Net50,
                LanguageVersion = LanguageVersion.CSharp9,
                FixedCode = corrected,
            };
 
            test.ExpectedDiagnostics.AddRange(expected);
            return test.RunAsync();
        }
 
        private static Task TestCSAsync(string source, params DiagnosticResult[] expected)
        {
            var test = new VerifyCS.Test
            {
                TestCode = source,
                ReferenceAssemblies = ReferenceAssemblies.Net.Net50,
                LanguageVersion = LanguageVersion.CSharp9,
            };
 
            test.ExpectedDiagnostics.AddRange(expected);
            return test.RunAsync();
        }
    }
}