File: Microsoft.NetCore.Analyzers\Performance\UseSearchValuesTests.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;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Testing;
using Xunit;
using VerifyCS = Test.Utilities.CSharpCodeFixVerifier<
    Microsoft.NetCore.CSharp.Analyzers.Performance.CSharpUseSearchValuesAnalyzer,
    Microsoft.NetCore.CSharp.Analyzers.Performance.CSharpUseSearchValuesFixer>;
 
namespace Microsoft.NetCore.Analyzers.Performance.UnitTests
{
    public class UseSearchValuesTests
    {
        [Fact]
        public async Task TestIndexOfAnyAnalyzer()
        {
            string source =
                """
                using System;
 
                internal sealed class Test
                {
                    private const string ShortConstStringTypeMember = "foo";
                    private const string LongConstStringTypeMember = "aeiouA";
                    private static string NonConstStringProperty => "aeiouA";
                    private string NonConstStringInstanceField = "aeiouA";
                    private static string NonConstStringStaticField = "aeiouA";
                    private readonly string NonConstStringReadonlyInstanceField = "aeiouA";
                    private static readonly string NonConstStringReadonlyStaticField = "aeiouA";
                    private const char ConstChar = 'A';
                    private static char NonConstChar => 'A';
                    private const byte ConstByte = (byte)'A';
                    private static byte NonConstByte => (byte)'A';
                    private static readonly char[] ShortStaticReadonlyCharArrayField = new[] { 'a', 'e', 'i', 'o', 'u' };
                    private static readonly char[] LongStaticReadonlyCharArrayField = new[] { 'a', 'e', 'i', 'o', 'u', 'A' };
                    private static readonly char[] LongStaticReadonlyCharArrayFieldWithNonInlineLiteral = new[] { 'a', 'e', 'i', 'o', 'u', ConstChar };
                    private readonly char[] LongReadonlyCharArrayFieldWithSimpleFieldModification = new[] { 'a', 'e', 'i', 'o', 'u', 'A' };
                    private readonly char[] LongReadonlyCharArrayFieldWithSimpleElementModification = new[] { 'a', 'e', 'i', 'o', 'u', 'A' };
                    static readonly char[] LongStaticReadonlyCharArrayFieldWithoutAccessibility = new[] { 'a', 'e', 'i', 'o', 'u', 'A' };
                    public static readonly char[] LongStaticReadonlyCharArrayFieldWithPublicAccessibility = new[] { 'a', 'e', 'i', 'o', 'u', 'A' };
                    private static readonly char[] LongStaticReadonlyExplicitCharArrayField = new char[] { 'a', 'e', 'i', 'o', 'u', 'A' };
                    private readonly char[] InstanceReadonlyCharArrayField = new[] { 'a', 'e', 'i', 'o', 'u', 'A' };
                    private char[] InstanceSettableCharArrayField = new[] { 'a', 'e', 'i', 'o', 'u', 'A' };
                    private readonly char[] ShortReadonlyCharArrayFieldFromToCharArray = "aeiou".ToCharArray();
                    private readonly char[] LongReadonlyCharArrayFieldFromToCharArray = "aeiouA".ToCharArray();
                    private readonly char[] ShortReadonlyCharArrayFieldFromConstToCharArray = ShortConstStringTypeMember.ToCharArray();
                    private readonly char[] LongReadonlyCharArrayFieldFromConstToCharArray = LongConstStringTypeMember.ToCharArray();
                    private ReadOnlySpan<char> ShortReadOnlySpanOfCharRVAProperty => new[] { 'a', 'e', 'i', 'o', 'u' };
                    private ReadOnlySpan<char> LongReadOnlySpanOfCharRVAProperty => new[] { 'a', 'e', 'i', 'o', 'u', 'A' };
                    private ReadOnlySpan<byte> ShortReadOnlySpanOfByteRVAProperty => new[] { (byte)'a', (byte)'e', (byte)'i', (byte)'o', (byte)'u' };
                    private ReadOnlySpan<byte> LongReadOnlySpanOfByteRVAProperty => new[] { (byte)'a', (byte)'e', (byte)'i', (byte)'o', (byte)'u', (byte)'A' };
                    private ReadOnlySpan<char> ShortReadOnlySpanOfCharFromToCharArrayProperty => "aeiou".ToCharArray();
                    private ReadOnlySpan<char> LongReadOnlySpanOfCharFromToCharArrayProperty => "aeiouA".ToCharArray();
                    private ReadOnlySpan<char> ShortReadOnlySpanOfCharFromConstToCharArrayProperty => ShortConstStringTypeMember.ToCharArray();
                    private ReadOnlySpan<char> LongReadOnlySpanOfCharFromConstToCharArrayProperty => LongConstStringTypeMember.ToCharArray();
                    private ReadOnlySpan<char> LongReadOnlySpanOfCharRVAPropertyWithGetAccessor1
                    {
                        get => new[] { 'a', 'e', 'i', 'o', 'u', 'A' };
                    }
                    private ReadOnlySpan<char> LongReadOnlySpanOfCharRVAPropertyWithGetAccessor2
                    {
                        get
                        {
                            return new[] { 'a', 'e', 'i', 'o', 'u', 'A' };
                        }
                    }
                    private ReadOnlySpan<char> LongReadOnlySpanOfCharRVAPropertyWithGetAccessor3
                    {
                        get
                        {
                            Console.WriteLine("foo");
                            return new[] { 'a', 'e', 'i', 'o', 'u', 'A' };
                        }
                    }
 
                    public Test()
                    {
                        LongReadonlyCharArrayFieldWithSimpleFieldModification = new[] { 'a' };
                        Console.WriteLine(InstanceReadonlyCharArrayField);
                    }
 
                    public void Dummy()
                    {
                        LongReadonlyCharArrayFieldWithSimpleElementModification[0] = 'a';
                        Console.WriteLine(InstanceReadonlyCharArrayField[0]);
                    }
 
                    private void TestMethod(string str, ReadOnlySpan<char> chars, ReadOnlySpan<byte> bytes)
                    {
                        const string ShortConstStringLocal = "foo";
                        const string LongConstStringLocal = "aeiouA";
                        string NonConstStringLocal = "aeiouA";
 
                        _ = chars.IndexOfAny("aeiou");
                        _ = chars.IndexOfAny([|"aeiouA"|]);
                        _ = chars.IndexOfAny("aeiouA" + NonConstStringProperty);
                        _ = chars.IndexOfAny("aeiouA" + NonConstStringLocal);
 
                        _ = chars.IndexOfAny(new[] { 'a', 'e', 'i', 'o', 'u' });
                        _ = chars.IndexOfAny([|new[] { 'a', 'e', 'i', 'o', 'u', 'A' }|]);
                        _ = chars.IndexOfAny([|new char[] { 'a', 'e', 'i', 'o', 'u', 'A' }|]);
                        _ = chars.IndexOfAny(new[] { 'a', 'e', 'i', 'o', 'u', NonConstChar });
 
                        _ = chars.IndexOfAny([|new[] { 'a', 'e', /* Comment */ 'i', 'o', 'u', 'A' }|]);
                        _ = chars.IndexOfAny([|new[]
                        {
                            // Comment
                            'a', 'e', 'i', 'o', 'u', 'A'
                        }|]);
 
                        _ = str.IndexOfAny([|new char[] { }|]);
                        _ = str.IndexOfAny([|new[] { 'a', 'e', 'i', 'o', 'u' }|]);
                        _ = str.IndexOfAny([|new[] { 'a', 'e', 'i', 'o', 'u', 'A' }|]);
                        _ = str.IndexOfAny([|new char[] { 'a', 'e', 'i', 'o', 'u', 'A' }|]);
 
                        _ = str?.IndexOfAny(new[] { 'a', 'e', 'i', 'o', 'u', 'A' });
 
                        _ = bytes.IndexOfAny(new[] { (byte)'a', (byte)'e', (byte)'i', (byte)'o', (byte)'u' });
                        _ = bytes.IndexOfAny([|new[] { (byte)'a', (byte)'e', (byte)'i', (byte)'o', (byte)'u', (byte)'A' }|]);
                        _ = bytes.IndexOfAny([|new byte[] { (byte)'a', (byte)'e', (byte)'i', (byte)'o', (byte)'u', (byte)'A' }|]);
                        _ = bytes.IndexOfAny(new[] { (byte)'a', (byte)'e', (byte)'i', (byte)'o', (byte)'u', NonConstByte });
 
                        _ = chars.IndexOfAny(ShortConstStringTypeMember);
                        _ = chars.IndexOfAny([|LongConstStringTypeMember|]);
                        _ = chars.IndexOfAny(NonConstStringProperty);
 
                        _ = chars.IndexOfAny(ShortConstStringLocal);
                        _ = chars.IndexOfAny([|LongConstStringLocal|]);
                        _ = chars.IndexOfAny(NonConstStringLocal);
 
                        _ = chars.IndexOfAny(ShortStaticReadonlyCharArrayField);
                        _ = chars.IndexOfAny([|LongStaticReadonlyCharArrayField|]);
                        _ = chars.IndexOfAny([|LongStaticReadonlyExplicitCharArrayField|]);
                        _ = chars.IndexOfAny([|InstanceReadonlyCharArrayField|]);
                        _ = chars.IndexOfAny(InstanceSettableCharArrayField);
                        _ = chars.IndexOfAny(ShortReadonlyCharArrayFieldFromToCharArray);
                        _ = chars.IndexOfAny([|LongReadonlyCharArrayFieldFromToCharArray|]);
                        _ = chars.IndexOfAny(ShortReadonlyCharArrayFieldFromConstToCharArray);
                        _ = chars.IndexOfAny([|LongReadonlyCharArrayFieldFromConstToCharArray|]);
 
                        _ = str.IndexOfAny(ShortStaticReadonlyCharArrayField);
                        _ = str.IndexOfAny([|LongStaticReadonlyCharArrayField|]);
                        _ = str.IndexOfAny([|LongStaticReadonlyExplicitCharArrayField|]);
                        _ = str.IndexOfAny([|InstanceReadonlyCharArrayField|]);
                        _ = str.IndexOfAny(InstanceSettableCharArrayField);
                        _ = str.IndexOfAny(ShortReadonlyCharArrayFieldFromToCharArray);
                        _ = str.IndexOfAny([|LongReadonlyCharArrayFieldFromToCharArray|]);
                        _ = str.IndexOfAny(ShortReadonlyCharArrayFieldFromConstToCharArray);
                        _ = str.IndexOfAny([|LongReadonlyCharArrayFieldFromConstToCharArray|]);
 
                        _ = chars.IndexOfAny([|LongStaticReadonlyCharArrayFieldWithoutAccessibility|]);
                        _ = chars.IndexOfAny(LongStaticReadonlyCharArrayFieldWithPublicAccessibility);
 
                        _ = chars.IndexOfAny(ShortReadOnlySpanOfCharRVAProperty);
                        _ = chars.IndexOfAny([|LongReadOnlySpanOfCharRVAProperty|]);
                        _ = chars.IndexOfAny(ShortReadOnlySpanOfCharFromToCharArrayProperty);
                        _ = chars.IndexOfAny([|LongReadOnlySpanOfCharFromToCharArrayProperty|]);
                        _ = chars.IndexOfAny(ShortReadOnlySpanOfCharFromConstToCharArrayProperty);
                        _ = chars.IndexOfAny([|LongReadOnlySpanOfCharFromConstToCharArrayProperty|]);
 
                        _ = bytes.IndexOfAny(ShortReadOnlySpanOfByteRVAProperty);
                        _ = bytes.IndexOfAny([|LongReadOnlySpanOfByteRVAProperty|]);
 
                        _ = chars.IndexOfAny([|LongReadOnlySpanOfCharRVAPropertyWithGetAccessor1|]);
                        _ = chars.IndexOfAny([|LongReadOnlySpanOfCharRVAPropertyWithGetAccessor2|]);
                        _ = chars.IndexOfAny(LongReadOnlySpanOfCharRVAPropertyWithGetAccessor3);
 
 
                        // We detect simple cases where the array may be modified
                        _ = str.IndexOfAny(LongReadonlyCharArrayFieldWithSimpleFieldModification);
                        _ = str.IndexOfAny(LongReadonlyCharArrayFieldWithSimpleElementModification);
 
 
                        // Uses of ToCharArray are flagged even for shorter values.
                        _ = chars.IndexOfAny([|ShortConstStringTypeMember.ToCharArray()|]);
                        _ = chars.IndexOfAny([|LongConstStringTypeMember.ToCharArray()|]);
                        _ = chars.IndexOfAny([|"aeiou".ToCharArray()|]);
                        _ = chars.IndexOfAny([|"aeiouA".ToCharArray()|]);
 
                        _ = str.IndexOfAny([|ShortConstStringTypeMember.ToCharArray()|]);
                        _ = str.IndexOfAny([|LongConstStringTypeMember.ToCharArray()|]);
                        _ = str.IndexOfAny([|"aeiou".ToCharArray()|]);
                        _ = str.IndexOfAny([|"aeiouA".ToCharArray()|]);
 
 
                        // For cases like this that we'd want to flag, a different analyzer should suggest making the field 'const' first.
                        _ = chars.IndexOfAny(NonConstStringInstanceField);
                        _ = chars.IndexOfAny(NonConstStringStaticField);
                        _ = chars.IndexOfAny(NonConstStringReadonlyInstanceField);
                        _ = chars.IndexOfAny(NonConstStringReadonlyStaticField);
 
 
                        // A few cases that could be flagged, but currently aren't:
                        _ = chars.IndexOfAny("aeiou" + 'A');
                        _ = chars.IndexOfAny("aeiou" + "A");
                        _ = chars.IndexOfAny(new[] { 'a', 'e', 'i', 'o', 'u', ConstChar });
                        _ = chars.IndexOfAny("aeiouA" + ShortConstStringTypeMember);
                        _ = chars.IndexOfAny(LongConstStringTypeMember + ShortConstStringTypeMember);
                        _ = bytes.IndexOfAny(new[] { (byte)'a', (byte)'e', (byte)'i', (byte)'o', (byte)'u', ConstByte });
                        _ = bytes.IndexOfAny(new[] { (byte)'a', (byte)'e', (byte)'i', (byte)'o', (byte)'u', (byte)ConstChar });
                        _ = chars.IndexOfAny(LongStaticReadonlyCharArrayFieldWithNonInlineLiteral);
                    }
                }
                """;
 
            await VerifyAnalyzerAsync(LanguageVersion.CSharp7_3, source);
            await VerifyAnalyzerAsync(LanguageVersion.CSharp11, source);
        }
 
        [Fact]
        public async Task TestUtf8StringLiteralsAnalyzer()
        {
            await VerifyAnalyzerAsync(LanguageVersion.CSharp11,
                """
                using System;
 
                internal sealed class Test
                {
                    private ReadOnlySpan<byte> ShortReadOnlySpanOfByteRVAPropertyU8 => "aeiou"u8;
                    private ReadOnlySpan<byte> LongReadOnlySpanOfByteRVAPropertyU8 => "aeiouA"u8;
 
                    private void TestMethod(ReadOnlySpan<byte> bytes)
                    {
                        _ = bytes.IndexOfAny("aeiou"u8);
                        _ = bytes.IndexOfAny([|"aeiouA"u8|]);
 
                        _ = bytes.IndexOfAny(ShortReadOnlySpanOfByteRVAPropertyU8);
                        _ = bytes.IndexOfAny([|LongReadOnlySpanOfByteRVAPropertyU8|]);
                    }
                }
                """);
        }
 
        [Fact]
        public async Task TestCollectionExpressionAnalyzer()
        {
            await VerifyAnalyzerAsync(LanguageVersion.CSharp12,
                """
                using System;
 
                internal sealed class Test
                {
                    private static readonly char[] ShortStaticReadonlyCharArrayField = ['a', 'e', 'i', 'o', 'u'];
                    private static readonly char[] LongStaticReadonlyCharArrayField = ['a', 'e', 'i', 'o', 'u', 'A'];
                    private ReadOnlySpan<char> ShortReadOnlySpanOfCharRVAProperty => ['a', 'e', 'i', 'o', 'u'];
                    private ReadOnlySpan<char> LongReadOnlySpanOfCharRVAProperty => ['a', 'e', 'i', 'o', 'u', 'A'];
                    private static readonly byte[] ShortStaticReadonlyByteArrayField = [(byte)'a', (byte)'e', (byte)'i', (byte)'o', (byte)'u'];
                    private static readonly byte[] LongStaticReadonlyByteArrayField = [(byte)'a', (byte)'e', (byte)'i', (byte)'o', (byte)'u', (byte)'A'];
                    private ReadOnlySpan<byte> ShortReadOnlySpanOfByteRVAProperty => [(byte)'a', (byte)'e', (byte)'i', (byte)'o', (byte)'u'];
                    private ReadOnlySpan<byte> LongReadOnlySpanOfByteRVAProperty => [(byte)'a', (byte)'e', (byte)'i', (byte)'o', (byte)'u', (byte)'A'];
 
                    private void TestMethod(ReadOnlySpan<char> chars, ReadOnlySpan<byte> bytes)
                    {
                        _ = chars.IndexOfAny([]);
                        _ = chars.IndexOfAny(['a', 'e', 'i', 'o', 'u']);
                        _ = chars.IndexOfAny([|['a', 'e', 'i', 'o', 'u', 'A']|]);
 
                        _ = chars.IndexOfAny(ShortStaticReadonlyCharArrayField);
                        _ = chars.IndexOfAny([|LongStaticReadonlyCharArrayField|]);
                        _ = chars.IndexOfAny(ShortReadOnlySpanOfCharRVAProperty);
                        _ = chars.IndexOfAny([|LongReadOnlySpanOfCharRVAProperty|]);
 
                        _ = bytes.IndexOfAny([]);
                        _ = bytes.IndexOfAny([(byte)'a', (byte)'e', (byte)'i', (byte)'o', (byte)'u']);
                        _ = bytes.IndexOfAny([|[(byte)'a', (byte)'e', (byte)'i', (byte)'o', (byte)'u', (byte)'A']|]);
 
                        _ = bytes.IndexOfAny(ShortStaticReadonlyByteArrayField);
                        _ = bytes.IndexOfAny([|LongStaticReadonlyByteArrayField|]);
                        _ = bytes.IndexOfAny(ShortReadOnlySpanOfByteRVAProperty);
                        _ = bytes.IndexOfAny([|LongReadOnlySpanOfByteRVAProperty|]);
                    }
                }
                """);
        }
 
        public static IEnumerable<object[]> TestAllIndexOfAnyAndContainsAnySpanOverloads_MemberData()
        {
            return
                from method in new[] { "IndexOfAny", "LastIndexOfAny", "IndexOfAnyExcept", "LastIndexOfAnyExcept", "ContainsAny", "ContainsAnyExcept" }
                from bytes in new[] { true, false }
                from readOnlySpan in new[] { true, false }
                select new object[] { method, bytes, readOnlySpan };
        }
 
        [Theory]
        [MemberData(nameof(TestAllIndexOfAnyAndContainsAnySpanOverloads_MemberData))]
        public async Task TestAllIndexOfAnyAndContainsAnySpanOverloads(string method, bool bytes, bool readOnlySpan)
        {
            string type = bytes ? "byte" : "char";
            string argumentType = $"{(readOnlySpan ? "ReadOnly" : "")}Span<{type}>";
            string valuesExpression = bytes ? "\"aeiouA\"u8" : "\"aeiouA\"";
            string searchValuesFieldName = bytes ? "s_myBytes" : "s_myChars";
 
            string source =
                $$"""
                using System;
                using System.Buffers;
 
                internal sealed class Test
                {
                    private void TestMethod({{argumentType}} input)
                    {
                        _ = input.{{method}}([|{{valuesExpression}}|]);
                    }
                }
                """;
 
            string expected =
                $$"""
                using System;
                using System.Buffers;
 
                internal sealed class Test
                {
                    private static readonly SearchValues<{{type}}> {{searchValuesFieldName}} = SearchValues.Create({{valuesExpression}});
 
                    private void TestMethod({{argumentType}} input)
                    {
                        _ = input.{{method}}({{searchValuesFieldName}});
                    }
                }
                """;
 
            await VerifyCodeFixAsync(LanguageVersion.CSharp11, source, expected);
        }
 
        [Theory]
        [InlineData("IndexOfAny")]
        [InlineData("LastIndexOfAny")]
        public async Task TestAllIndexOfAnyStringOverloads(string method)
        {
            string source =
                $$"""
                using System;
                using System.Buffers;
 
                internal sealed class Test
                {
                    private void TestMethod(string input)
                    {
                        _ = input.{{method}}([|"aeiouA".ToCharArray()|]);
                    }
                }
                """;
 
            string expected =
                $$"""
                using System;
                using System.Buffers;
 
                internal sealed class Test
                {
                    private static readonly SearchValues<char> s_myChars = SearchValues.Create("aeiouA");
 
                    private void TestMethod(string input)
                    {
                        _ = input.AsSpan().{{method}}(s_myChars);
                    }
                }
                """;
 
            await VerifyCodeFixAsync(LanguageVersion.CSharp7_3, source, expected);
        }
 
        [Theory]
        [InlineData("const string", "= \"aeiouA\";", true)]
        [InlineData("static readonly char[]", "= new[] { 'a', 'e', 'i', 'o', 'u', 'A' };", false)]
        [InlineData("static readonly byte[]", "= new[] { (byte)'a', (byte)'e', (byte)'i', (byte)'o', (byte)'u', (byte)'A' };", false)]
        [InlineData("readonly char[]", "= new[] { 'a', 'e', 'i', 'o', 'u', 'A' };", false)]
        [InlineData("readonly char[]", "= ['a', 'e', 'i', 'o', 'u', 'A'];", false)]
        [InlineData("readonly byte[]", "= new[] { (byte)'a', (byte)'e', (byte)'i', (byte)'o', (byte)'u', (byte)'A' };", false)]
        [InlineData("readonly byte[]", "= [(byte)'a', (byte)'e', (byte)'i', (byte)'o', (byte)'u', (byte)'A'];", false)]
        [InlineData("readonly char[]", "= new char[] { 'a', 'e', 'i', 'o', 'u', 'A' };", false)]
        [InlineData("readonly char[]", "= new char[]  { 'a', 'e', 'i', 'o', 'u',  'A' };", false)]
        [InlineData("ReadOnlySpan<char>", "=> new[] { 'a', 'e', 'i', 'o', 'u', 'A' };", false)]
        [InlineData("ReadOnlySpan<char>", "=> ['a', 'e', 'i', 'o', 'u', 'A'];", false)]
        [InlineData("ReadOnlySpan<byte>", "=> new[] { (byte)'a', (byte)'e', (byte)'i', (byte)'o', (byte)'u', (byte)'A' };", false)]
        [InlineData("ReadOnlySpan<byte>", "=> [(byte)'a', (byte)'e', (byte)'i', (byte)'o', (byte)'u', (byte)'A'];", false)]
        [InlineData("ReadOnlySpan<byte>", "=> \"aeiouA\"u8;", false)]
        [InlineData("static ReadOnlySpan<char>", "=> new[] { 'a', 'e', 'i', 'o', 'u', 'A' };", true)]
        [InlineData("static ReadOnlySpan<byte>", "=> new[] { (byte)'a', (byte)'e', (byte)'i', (byte)'o', (byte)'u', (byte)'A' };", true)]
        [InlineData("static ReadOnlySpan<byte>", "=> \"aeiouA\"u8;", true)]
        [InlineData("ReadOnlySpan<byte>", "{ get => \"aeiouA\"u8; }", false)]
        [InlineData("ReadOnlySpan<byte>", "{ get { return \"aeiouA\"u8; } }", false)]
        [InlineData("ReadOnlySpan<char>", "{ get => new[] { 'a', 'e', 'i', 'o', 'u', 'A' }; }", false)]
        [InlineData("ReadOnlySpan<char>", "{ get => ['a', 'e', 'i', 'o', 'u', 'A']; }", false)]
        [InlineData("ReadOnlySpan<byte>", "{ get { return new[] { (byte)'a', (byte)'e', (byte)'i', (byte)'o', (byte)'u', (byte)'A' }; } }", false)]
        [InlineData("ReadOnlySpan<byte>", "{ get { return [(byte)'a', (byte)'e', (byte)'i', (byte)'o', (byte)'u', (byte)'A']; } }", false)]
        [InlineData("static ReadOnlySpan<byte>", "{ get => \"aeiouA\"u8; }", true)]
        [InlineData("static ReadOnlySpan<byte>", "{ get { return \"aeiouA\"u8; } }", true)]
        [InlineData("static ReadOnlySpan<char>", "{ get => new[] { 'a', 'e', 'i', 'o', 'u', 'A' }; }", true)]
        [InlineData("static ReadOnlySpan<byte>", "{ get { return new[] { (byte)'a', (byte)'e', (byte)'i', (byte)'o', (byte)'u', (byte)'A' }; } }", true)]
        [InlineData("readonly char[]", "= \"aeiouA\".ToCharArray();", false)]
        [InlineData("static readonly char[]", "= \"aeiouA\".ToCharArray();", false)]
        [InlineData("ReadOnlySpan<char>", "=> \"aeiouA\".ToCharArray();", false)]
        [InlineData("static ReadOnlySpan<char>", "=> \"aeiouA\".ToCharArray();", true)]
        [InlineData("readonly char[]", "= ConstStringTypeMember.ToCharArray();", false, "ConstStringTypeMember")]
        [InlineData("static readonly char[]", "= ConstStringTypeMember.ToCharArray();", false, "ConstStringTypeMember")]
        [InlineData("ReadOnlySpan<char>", "=> ConstStringTypeMember.ToCharArray();", false, "ConstStringTypeMember")]
        [InlineData("static ReadOnlySpan<char>", "=> ConstStringTypeMember.ToCharArray();", true)]
        public async Task TestCodeFixerNamedArguments(string modifiersAndType, string initializer, bool createWillUseMemberReference, string createExpression = null)
        {
            const string OriginalValuesName = "MyValuesTypeMember";
            const string SearchValuesFieldName = "s_myValuesTypeMember";
 
            string byteOrChar = modifiersAndType.Contains("byte", StringComparison.Ordinal) ? "byte" : "char";
            string memberDefinition = $"{modifiersAndType} {OriginalValuesName} {initializer}";
            bool isProperty = initializer.Contains("=>", StringComparison.Ordinal) || initializer.Contains("get", StringComparison.Ordinal);
 
            string cSharp11CreateExpression = null;
 
            if (createWillUseMemberReference)
            {
                Assert.Null(createExpression);
                createExpression = OriginalValuesName;
            }
            else
            {
                if (byteOrChar == "char")
                {
                    createExpression ??= "\"aeiouA\"";
                }
                else
                {
                    if (createExpression is null)
                    {
                        createExpression = initializer.TrimStart('{', ' ', '=', '>', 'g', 'e', 't').TrimEnd(' ', '}').TrimEnd(';');
 
                        if (createExpression.StartsWith("return ", StringComparison.Ordinal))
                        {
                            createExpression = createExpression.Substring("return ".Length);
                        }
                    }
 
                    cSharp11CreateExpression = "\"aeiouA\"u8";
                }
            }
 
            await TestAsync(LanguageVersion.CSharp7_3, createExpression);
            await TestAsync(LanguageVersion.CSharp11, cSharp11CreateExpression ?? createExpression);
            await TestAsync(LanguageVersion.CSharp12, cSharp11CreateExpression ?? createExpression);
 
            async Task TestAsync(LanguageVersion languageVersion, string expectedCreateExpression)
            {
                if (languageVersion < LanguageVersion.CSharp11 &&
                    (memberDefinition.Contains("u8", StringComparison.Ordinal) || expectedCreateExpression.Contains("u8", StringComparison.Ordinal)))
                {
                    // Need CSharp 11 or newer to use Utf8 string literals
                    return;
                }
 
                if (languageVersion < LanguageVersion.CSharp12 &&
                    memberDefinition.LastIndexOf(']') - memberDefinition.IndexOf('[', StringComparison.Ordinal) > 1)
                {
                    // Need CSharp 12 or newer to use collection expressions
                    return;
                }
 
                string source =
                    $$"""
                    using System;
                    using System.Buffers;
 
                    internal sealed class Test
                    {
                        {{memberDefinition}}
                        private const string ConstStringTypeMember = "aeiouA";
 
                        private void TestMethod(ReadOnlySpan<{{byteOrChar}}> span)
                        {
                            _ = span.IndexOfAny([|{{OriginalValuesName}}|]);
                        }
                    }
                    """;
 
                string newLineAfterSearchValues = isProperty && createWillUseMemberReference ? Environment.NewLine : "";
                string expectedMemberDefinition = createWillUseMemberReference ? $"{memberDefinition}{Environment.NewLine}    " : "";
 
                string expected =
                    $$"""
                    using System;
                    using System.Buffers;
 
                    internal sealed class Test
                    {
                        private static readonly SearchValues<{{byteOrChar}}> {{SearchValuesFieldName}} = SearchValues.Create({{expectedCreateExpression}});{{newLineAfterSearchValues}}
                        {{expectedMemberDefinition}}private const string ConstStringTypeMember = "aeiouA";
 
                        private void TestMethod(ReadOnlySpan<{{byteOrChar}}> span)
                        {
                            _ = span.IndexOfAny({{SearchValuesFieldName}});
                        }
                    }
                    """;
 
                await VerifyCodeFixAsync(languageVersion, source, expected);
            }
        }
 
        [Theory]
        [InlineData("static readonly char[]", "new[] { 'a', 'e', 'i', 'o', 'u', 'A' }")]
        [InlineData("readonly char[]", "new[] { 'a', 'e', 'i', 'o', 'u', 'A' }")]
        [InlineData("readonly char[]", "new char[] { 'a', 'e', 'i', 'o', 'u', 'A' }")]
        [InlineData("readonly char[]", "new char[]  { 'a', 'e', 'i', 'o', 'u',  'A' }")]
        public async Task TestCodeFixerNamedArgumentsStringIndexOfAny(string modifiersAndType, string initializer)
        {
            const string OriginalValuesName = "MyValuesTypeMember";
            const string SearchValuesFieldName = "s_myValuesTypeMember";
 
            string memberDefinition = $"{modifiersAndType} {OriginalValuesName} = {initializer};";
 
            string source =
                $$"""
                using System;
                using System.Buffers;
 
                internal sealed class Test
                {
                    {{memberDefinition}}
 
                    private void TestMethod(string text)
                    {
                        _ = text.IndexOfAny([|{{OriginalValuesName}}|]);
                    }
                }
                """;
 
            string expected =
                $$"""
                using System;
                using System.Buffers;
 
                internal sealed class Test
                {
                    private static readonly SearchValues<char> {{SearchValuesFieldName}} = SearchValues.Create("aeiouA");
 
                    private void TestMethod(string text)
                    {
                        _ = text.AsSpan().IndexOfAny({{SearchValuesFieldName}});
                    }
                }
                """;
 
            await VerifyCodeFixAsync(LanguageVersion.CSharp7_3, source, expected);
        }
 
        [Fact]
        public async Task TestCodeFixerConstStringMemberToCharArray()
        {
            string source =
                """
                using System;
                using System.Buffers;
 
                internal sealed class Test
                {
                    private const string MyValuesTypeMember = "aeiouA";
 
                    private void TestMethod(string text)
                    {
                        _ = text.IndexOfAny([|MyValuesTypeMember.ToCharArray()|]);
                    }
                }
                """;
 
            string expected =
                """
                using System;
                using System.Buffers;
 
                internal sealed class Test
                {
                    private static readonly SearchValues<char> s_myValuesTypeMember = SearchValues.Create(MyValuesTypeMember);
                    private const string MyValuesTypeMember = "aeiouA";
 
                    private void TestMethod(string text)
                    {
                        _ = text.AsSpan().IndexOfAny(s_myValuesTypeMember);
                    }
                }
                """;
 
            await VerifyCodeFixAsync(LanguageVersion.CSharp7_3, source, expected);
        }
 
        [Fact]
        public async Task TestCodeFixerLocalStringConst()
        {
            string source =
                """
                using System;
                using System.Buffers;
 
                internal sealed class Test
                {
                    private void TestMethod(ReadOnlySpan<char> chars)
                    {
                        const string Values = "aeiouA";
 
                        _ = chars.IndexOfAny([|Values|]);
                    }
                }
                """;
 
            string expected =
                """
                using System;
                using System.Buffers;
 
                internal sealed class Test
                {
                    private static readonly SearchValues<char> s_values = SearchValues.Create("aeiouA");
 
                    private void TestMethod(ReadOnlySpan<char> chars)
                    {
                        _ = chars.IndexOfAny(s_values);
                    }
                }
                """;
 
            await VerifyCodeFixAsync(LanguageVersion.CSharp7_3, source, expected);
        }
 
        [Theory]
        [InlineData(true)]
        [InlineData(false)]
        public async Task TestCodeFixerLocalStringConstToCharArray(bool spanInput)
        {
            string argumentType = spanInput ? "ReadOnlySpan<char>" : "string";
 
            string source =
                $$"""
                using System;
                using System.Buffers;
 
                internal sealed class Test
                {
                    private void TestMethod({{argumentType}} text)
                    {
                        const string Values = "aeiouA";
 
                        _ = text.IndexOfAny([|Values.ToCharArray()|]);
                    }
                }
                """;
 
            string expected =
                $$"""
                using System;
                using System.Buffers;
 
                internal sealed class Test
                {
                    private static readonly SearchValues<char> s_values = SearchValues.Create("aeiouA");
 
                    private void TestMethod({{argumentType}} text)
                    {
                        _ = text{{(spanInput ? "" : ".AsSpan()")}}.IndexOfAny(s_values);
                    }
                }
                """;
 
            await VerifyCodeFixAsync(LanguageVersion.CSharp7_3, source, expected);
        }
 
        [Theory]
        [InlineData(LanguageVersion.CSharp7_3, "\"aeiouA\"", "\"aeiouA\"")]
        [InlineData(LanguageVersion.CSharp7_3, "@\"aeiouA\"", "@\"aeiouA\"")]
        [InlineData(LanguageVersion.CSharp11, "\"aeiouA\"u8", "\"aeiouA\"u8")]
        [InlineData(LanguageVersion.CSharp12, "['a', 'e', 'i', 'o', 'u', 'A']", "\"aeiouA\"")]
        [InlineData(LanguageVersion.CSharp12, "[(byte)'a', (byte)'e', (byte)'i', (byte)'o', (byte)'u', (byte)'A']", "\"aeiouA\"u8")]
        [InlineData(LanguageVersion.CSharp7_3, "new[] { 'a', 'e', 'i', 'o', 'u', 'A' }", "\"aeiouA\"")]
        [InlineData(LanguageVersion.CSharp7_3, "new char[] { 'a', 'e', 'i', 'o', 'u', 'A' }", "\"aeiouA\"")]
        [InlineData(LanguageVersion.CSharp7_3, "new char[]  { 'a', 'e', 'i', 'o',  'u', 'A' }", "\"aeiouA\"")]
        [InlineData(LanguageVersion.CSharp7_3, "new[] { (byte)'a', (byte)'e', (byte)'i', (byte)'o', (byte)'u', (byte)'A' }", "new[] { (byte)'a', (byte)'e', (byte)'i', (byte)'o', (byte)'u', (byte)'A' }")]
        [InlineData(LanguageVersion.CSharp11, "new[] { (byte)'a', (byte)'e', (byte)'i', (byte)'o', (byte)'u', (byte)'A' }", "\"aeiouA\"u8")]
        public async Task TestCodeFixerInlineArguments(LanguageVersion languageVersion, string values, string expectedCreateArgument)
        {
            string byteOrChar = values.Contains("byte", StringComparison.Ordinal) || values.Contains("u8", StringComparison.Ordinal) ? "byte" : "char";
            string searchValuesFieldName = byteOrChar == "byte" ? "s_myBytes" : "s_myChars";
 
            string source =
                $$"""
                using System;
                using System.Buffers;
 
                internal sealed class Test
                {
                    private void TestMethod(ReadOnlySpan<{{byteOrChar}}> span)
                    {
                        _ = span.IndexOfAny([|{{values}}|]);
                    }
                }
                """;
 
            string expected =
                $$"""
                using System;
                using System.Buffers;
 
                internal sealed class Test
                {
                    private static readonly SearchValues<{{byteOrChar}}> {{searchValuesFieldName}} = SearchValues.Create({{expectedCreateArgument}});
 
                    private void TestMethod(ReadOnlySpan<{{byteOrChar}}> span)
                    {
                        _ = span.IndexOfAny({{searchValuesFieldName}});
                    }
                }
                """;
 
            await VerifyCodeFixAsync(languageVersion, source, expected);
        }
 
        [Fact]
        public async Task TestCodeFixerInlineStringLiteralToCharArray()
        {
            string source =
                """
                using System;
                using System.Buffers;
 
                internal sealed class Test
                {
                    private void TestMethod(string text)
                    {
                        _ = text.IndexOfAny([|"aeiouA".ToCharArray()|]);
                    }
                }
                """;
 
            string expected =
                """
                using System;
                using System.Buffers;
 
                internal sealed class Test
                {
                    private static readonly SearchValues<char> s_myChars = SearchValues.Create("aeiouA");
 
                    private void TestMethod(string text)
                    {
                        _ = text.AsSpan().IndexOfAny(s_myChars);
                    }
                }
                """;
 
            await VerifyCodeFixAsync(LanguageVersion.CSharp7_3, source, expected);
        }
 
        [Fact]
        public async Task TestCodeFixerDoesNotRemoveCommentsInArrayInitializer()
        {
            string source =
                """
                using System;
                using System.Buffers;
 
                internal sealed class Test
                {
                    private void TestMethod(string text)
                    {
                        _ = text.IndexOfAny([|new[] { 'a', /* Useful comment */ 'b' }|]);
                    }
                }
                """;
 
            string expected =
                """
                using System;
                using System.Buffers;
 
                internal sealed class Test
                {
                    private static readonly SearchValues<char> s_myChars = SearchValues.Create(new[] { 'a', /* Useful comment */ 'b' });
 
                    private void TestMethod(string text)
                    {
                        _ = text.AsSpan().IndexOfAny(s_myChars);
                    }
                }
                """;
 
            await VerifyCodeFixAsync(LanguageVersion.CSharp7_3, source, expected);
 
            source =
                """
                using System;
                using System.Buffers;
 
                internal sealed class Test
                {
                    private void TestMethod(string text)
                    {
                        _ = text.IndexOfAny([|new[]
                        {
                            'a',
                            'b' // Useful comment
                        }|]);
                    }
                }
                """;
 
            expected =
                """
                using System;
                using System.Buffers;
 
                internal sealed class Test
                {
                    private static readonly SearchValues<char> s_myChars = SearchValues.Create(new[]
                        {
                            'a',
                            'b' // Useful comment
                        });
 
                    private void TestMethod(string text)
                    {
                        _ = text.AsSpan().IndexOfAny(s_myChars);
                    }
                }
                """;
 
            await VerifyCodeFixAsync(LanguageVersion.CSharp7_3, source, expected);
        }
 
        [Fact]
        public async Task TestCodeFixerDoesNotUseUtf8StringLiteralsForNonAsciiChars()
        {
            string source =
                """
                using System;
                using System.Buffers;
 
                internal sealed class Test
                {
                    private void TestMethod(ReadOnlySpan<byte> span)
                    {
                        _ = span.IndexOfAny([|new[] { (byte)'ÿ', (byte)'a', (byte)'e', (byte)'i', (byte)'o', (byte)'u' }|]);
                    }
                }
                """;
 
            string expected =
                """
                using System;
                using System.Buffers;
 
                internal sealed class Test
                {
                    private static readonly SearchValues<byte> s_myBytes = SearchValues.Create(new[] { (byte)'ÿ', (byte)'a', (byte)'e', (byte)'i', (byte)'o', (byte)'u' });
 
                    private void TestMethod(ReadOnlySpan<byte> span)
                    {
                        _ = span.IndexOfAny(s_myBytes);
                    }
                }
                """;
 
            await VerifyCodeFixAsync(LanguageVersion.CSharp11, source, expected);
        }
 
        [Fact]
        public static async Task TestCodeFixerDoesNotRemoveTheOriginalMemberIfPublic()
        {
            string source =
                """
                using System;
                using System.Buffers;
 
                internal sealed class Test
                {
                    public ReadOnlySpan<char> InvalidChars => new[] { 'a', 'b', 'c', 'd', 'e', 'f' };
 
                    public int IndexOfInvalidChar(ReadOnlySpan<char> input) =>
                        input.IndexOfAny([|InvalidChars|]);
                }
                """;
 
            string expected =
                """
                using System;
                using System.Buffers;
 
                internal sealed class Test
                {
                    private static readonly SearchValues<char> s_invalidChars = SearchValues.Create("abcdef");
 
                    public ReadOnlySpan<char> InvalidChars => new[] { 'a', 'b', 'c', 'd', 'e', 'f' };
 
                    public int IndexOfInvalidChar(ReadOnlySpan<char> input) =>
                        input.IndexOfAny(s_invalidChars);
                }
                """;
 
            await VerifyCodeFixAsync(LanguageVersion.CSharp7_3, source, expected);
        }
 
        [Fact]
        public async Task TestCodeFixerAddsSystemUsingIfNeeded()
        {
            string source =
                """
                using System.Buffers;
 
                internal sealed class Test
                {
                    private void TestMethod(string text)
                    {
                        _ = text.IndexOfAny([|"aeiouA".ToCharArray()|]);
                    }
                }
                """;
 
            string expected =
                """
                using System.Buffers;
                using System;
 
                internal sealed class Test
                {
                    private static readonly SearchValues<char> s_myChars = SearchValues.Create("aeiouA");
 
                    private void TestMethod(string text)
                    {
                        _ = text.AsSpan().IndexOfAny(s_myChars);
                    }
                }
                """;
 
            await VerifyCodeFixAsync(LanguageVersion.CSharp7_3, source, expected);
        }
 
        [Theory]
        [InlineData("MyValues", "s_myValues")]
        [InlineData("myValues", "s_myValues")]
        [InlineData("_myValues", "s_myValues")]
        [InlineData("_MyValues", "s_MyValues")]
        [InlineData("s_myValues", "s_myValues", false)]
        [InlineData("s_MyValues", "s_MyValues", false)]
        [InlineData("s_myValues", "s_myValuesSearchValues", true)]
        [InlineData("s_MyValues", "s_MyValuesSearchValues", true)]
        public async Task TestCodeFixerPicksFriendlyFieldNames(string memberName, string expectedFieldName, bool memberHasOtherUses = false)
        {
            string memberDefinition = $"private readonly char[] {memberName} = \"aeiouA\".ToCharArray();";
            string otherMemberUses = memberHasOtherUses ? $" _ = {memberName};" : "";
 
            string source =
                $$"""
                using System;
                using System.Buffers;
 
                internal sealed class Test
                {
                    {{memberDefinition}}
 
                    private void TestMethod(ReadOnlySpan<char> text)
                    {
                        _ = text.IndexOfAny([|{{memberName}}|]);{{otherMemberUses}}
                    }
                }
                """;
 
            string expectedMemberDefinition = memberHasOtherUses ? $"{Environment.NewLine}    {memberDefinition}" : "";
 
            string expected =
                $$"""
                using System;
                using System.Buffers;
 
                internal sealed class Test
                {
                    private static readonly SearchValues<char> {{expectedFieldName}} = SearchValues.Create("aeiouA");{{expectedMemberDefinition}}
 
                    private void TestMethod(ReadOnlySpan<char> text)
                    {
                        _ = text.IndexOfAny({{expectedFieldName}});{{otherMemberUses}}
                    }
                }
                """;
 
            await VerifyCodeFixAsync(LanguageVersion.CSharp7_3, source, expected);
        }
 
        [Theory]
        [InlineData(true)]
        [InlineData(false)]
        public async Task TestCodeFixerAccountsForUsingStatements(bool hasSystemBuffersUsing)
        {
            string usingLine = hasSystemBuffersUsing ? "using System.Buffers;" : "";
            string systemBuffersPrefix = hasSystemBuffersUsing ? "" : "System.Buffers.";
 
            string source =
                $$"""
                using System;
                {{usingLine}}
 
                internal sealed class Test
                {
                    private void TestMethod(string text)
                    {
                        _ = text.IndexOfAny([|"aeiouA".ToCharArray()|]);
                    }
                }
                """;
 
            string expected =
                $$"""
                using System;
                {{usingLine}}
 
                internal sealed class Test
                {
                    private static readonly {{systemBuffersPrefix}}SearchValues<char> s_myChars = {{systemBuffersPrefix}}SearchValues.Create("aeiouA");
 
                    private void TestMethod(string text)
                    {
                        _ = text.AsSpan().IndexOfAny(s_myChars);
                    }
                }
                """;
 
            await VerifyCodeFixAsync(LanguageVersion.CSharp7_3, source, expected);
        }
 
        [Fact]
        public async Task TestCodeFixerAvoidsMemberNameConflicts()
        {
            string source =
                """
                using System;
                using System.Buffers;
 
                internal sealed class Test
                {
                    private static string s_myChars => "";
                    private static readonly string s_myChars1 = "";
                    private static string s_myChars2() => "";
                    private sealed class s_myChars3 { }
 
                    private void TestMethod(ReadOnlySpan<char> chars)
                    {
                        _ = chars.IndexOfAny([|"aeiouA"|]);
                    }
                }
                """;
 
            string expected =
                """
                using System;
                using System.Buffers;
 
                internal sealed class Test
                {
                    private static readonly SearchValues<char> s_myChars4 = SearchValues.Create("aeiouA");
 
                    private static string s_myChars => "";
                    private static readonly string s_myChars1 = "";
                    private static string s_myChars2() => "";
                    private sealed class s_myChars3 { }
 
                    private void TestMethod(ReadOnlySpan<char> chars)
                    {
                        _ = chars.IndexOfAny(s_myChars4);
                    }
                }
                """;
 
            await VerifyCodeFixAsync(LanguageVersion.CSharp7_3, source, expected);
        }
 
        [Fact]
        public async Task TestCodeFixerWorksInTopLevelStatementsDocument()
        {
            string source =
                """
                using System.Buffers;
 
                _ = "".IndexOfAny([|"aeiouA".ToCharArray()|]);
                """;
 
            string expected =
                """
                using System.Buffers;
                using System;
 
                _ = "".AsSpan().IndexOfAny(s_myChars);
 
                partial class Program
                {
                    private static readonly SearchValues<char> s_myChars = SearchValues.Create("aeiouA");
                }
                """;
 
            await VerifyCodeFixAsync(LanguageVersion.CSharp9, source, expected, topLevelStatements: true);
 
            source =
                """
                using System.Buffers;
 
                _ = "".IndexOfAny([|s_myValues|]);
 
                partial class Program
                {
                    private static readonly char[] s_myValues  = new[] { 'a', 'e', 'i', 'o', 'u', 'A' };
                }
                """;
 
            expected =
                """
                using System.Buffers;
                using System;
 
                _ = "".AsSpan().IndexOfAny(s_myValues);
 
                partial class Program
                {
                }
 
                partial class Program
                {
                    private static readonly SearchValues<char> s_myValues = SearchValues.Create("aeiouA");
                }
                """;
 
            await VerifyCodeFixAsync(LanguageVersion.CSharp9, source, expected, topLevelStatements: true);
        }
 
        private static async Task VerifyAnalyzerAsync(LanguageVersion languageVersion, string source) =>
            await VerifyCodeFixAsync(languageVersion, source, expected: null);
 
        private static async Task VerifyCodeFixAsync(LanguageVersion languageVersion, string source, string expected, bool topLevelStatements = false)
        {
            await new VerifyCS.Test
            {
                ReferenceAssemblies = ReferenceAssemblies.Net.Net80,
                LanguageVersion = languageVersion,
                TestCode = source,
                FixedCode = expected,
                TestState = { OutputKind = topLevelStatements ? OutputKind.ConsoleApplication : null },
            }.RunAsync();
        }
    }
}