File: Microsoft.NetCore.Analyzers\Performance\RecommendCaseInsensitiveStringComparison.Base.Tests.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;
 
namespace Microsoft.NetCore.Analyzers.Performance.UnitTests
{
    public abstract class RecommendCaseInsensitiveStringComparison_Base_Tests
    {
        private static readonly (string, string)[] Cultures = new[] {
            ("ToLower", "CurrentCultureIgnoreCase"),
            ("ToUpper", "CurrentCultureIgnoreCase"),
            ("ToLowerInvariant", "InvariantCultureIgnoreCase"),
            ("ToUpperInvariant", "InvariantCultureIgnoreCase")
        };
 
        private static readonly (string, string)[] IncompatibleCaseChangingMethods = new[]
        {
            ("ToLower", "ToLowerInvariant"),
            ("ToLower", "ToUpperInvariant"),
            ("ToUpper", "ToLowerInvariant"),
            ("ToUpper", "ToUpperInvariant"),
            ("ToLowerInvariant", "ToLower"),
            ("ToLowerInvariant", "ToUpper"),
            ("ToUpperInvariant", "ToLower"),
            ("ToUpperInvariant", "ToUpper"),
        };
 
        private static readonly string[] ContainsStartsWith = new[] { "Contains", "StartsWith" };
        private static readonly string[] UnnamedArgs = new[] { "", ", 1", ", 1, 1" };
 
        private static readonly (string, string)[] CSharpComparisonOperators = new[] {
            ("==", ""),
            ("!=", "!")
        };
        private static readonly (string, string)[] VisualBasicComparisonOperators = new[] {
            ("=", ""),
            ("<>", "Not ")
        };
 
        private const string CSharpSeparator = ": ";
        private const string VisualBasicSeparator = ":=";
 
        private static (string, string)[] GetNamedArguments(string separator) => new (string, string)[] {
            ("", ""),
            ($", startIndex{separator}1", $", startIndex{separator}1"),
            ($", startIndex{separator}1, count{separator}1", $", startIndex{separator}1, count{separator}1")
        };
 
        // In C#, when the user names the arguments and passes them in a different order than the original, the fixer will
        // be able to get the arguments from IInvocationOperation.Arguments in the order the user passed.
        private static (string, string) CSharpIndexOfNamedArguments = new($", count: 1, startIndex: 1", $", count: 1, startIndex: 1");
 
        // In VB, when the user names the arguments and passes them in a different order than the original, the fixer will
        // not be able to get the arguments from IINvocation.Arguments in the order the user passed, and instead will
        // get them in the original order as defined in the method signature.
        private static (string, string) VisualBasicIndexOfNamedArguments = new($", count:=1, startIndex:=1", $", startIndex:=1, count:=1");
 
        // Test cases for Contains, StartsWith and all overloads of IndexOf
        // where the instance string is case-changed and the string argument is passed unmodified.
        public static IEnumerable<object[]> DiagnosedAndFixedData()
        {
            foreach ((string caseChanging, string replacement) in Cultures)
            {
                foreach (string method in ContainsStartsWith)
                {
                    yield return new object[] { $"a.{caseChanging}().{method}(b)", $"a.{method}(b, StringComparison.{replacement})" };
                }
 
                // IndexOf overloads
                foreach (string arguments in UnnamedArgs)
                {
                    yield return new object[] { $"a.{caseChanging}().IndexOf(b{arguments})", $"a.IndexOf(b{arguments}, StringComparison.{replacement})" };
                }
            }
        }
 
        // Test cases for Contains, StartsWith and all overloads of IndexOf
        // where the instance string is unmodified and the string argument is passed case-changed.
        public static IEnumerable<object[]> DiagnosedAndFixedInvertedData()
        {
            foreach ((string caseChanging, string replacement) in Cultures)
            {
                foreach (string method in ContainsStartsWith)
                {
                    yield return new object[] { $"a.{method}(b.{caseChanging}())", $"a.{method}(b, StringComparison.{replacement})" };
                }
 
                // IndexOf overloads
                foreach (string arguments in UnnamedArgs)
                {
                    yield return new object[] { $"a.IndexOf(b.{caseChanging}(){arguments})", $"a.IndexOf(b{arguments}, StringComparison.{replacement})" };
                }
            }
        }
 
        public static IEnumerable<object[]> CSharpDiagnosedAndFixedNamedData() => DiagnosedAndFixedNamedData(CSharpSeparator, CSharpIndexOfNamedArguments);
        public static IEnumerable<object[]> VisualBasicDiagnosedAndFixedNamedData() => DiagnosedAndFixedNamedData(VisualBasicSeparator, VisualBasicIndexOfNamedArguments);
 
        // Test cases for Contains, StartsWith and all overloads of IndexOf
        // where the instance string is case-changed and the string argument is passed unmodified and explicitly named.
        private static IEnumerable<object[]> DiagnosedAndFixedNamedData(string separator, (string, string) langSpecificOrderedNamedArgs)
        {
            foreach ((string caseChanging, string replacement) in Cultures)
            {
                foreach (string method in ContainsStartsWith)
                {
                    yield return new object[] { $"a.{caseChanging}().{method}(value{separator}b)", $"a.{method}(value{separator}b, comparisonType{separator}StringComparison.{replacement})" };
                }
 
                // IndexOf overloads
                foreach ((string originalArguments, string expectedArguments) in GetNamedArguments(separator))
                {
                    yield return new object[] { $"a.{caseChanging}().IndexOf(value{separator}b{originalArguments})", $"a.IndexOf(value{separator}b{expectedArguments}, comparisonType{separator}StringComparison.{replacement})" };
                }
 
                yield return new object[] { $"a.{caseChanging}().IndexOf(value{separator}b{langSpecificOrderedNamedArgs.Item1})", $"a.IndexOf(value{separator}b{langSpecificOrderedNamedArgs.Item2}, comparisonType{separator}StringComparison.{replacement})" };
            }
        }
 
        public static IEnumerable<object[]> CSharpDiagnosedAndFixedInvertedNamedData() => DiagnosedAndFixedInvertedNamedData(CSharpSeparator, CSharpIndexOfNamedArguments);
        public static IEnumerable<object[]> VisualBasicDiagnosedAndFixedInvertedNamedData() => DiagnosedAndFixedInvertedNamedData(VisualBasicSeparator, VisualBasicIndexOfNamedArguments);
 
        // Test cases for Contains, StartsWith and all overloads of IndexOf
        // where the instance string is unmodified and the string argument is passed case-changed and explicitly named.
        private static IEnumerable<object[]> DiagnosedAndFixedInvertedNamedData(string separator, (string, string) langSpecificOrderedNamedArgs)
        {
            foreach ((string caseChanging, string replacement) in Cultures)
            {
                foreach (string method in ContainsStartsWith)
                {
                    yield return new object[] { $"a.{method}(value{separator}b.{caseChanging}())", $"a.{method}(value{separator}b, comparisonType{separator}StringComparison.{replacement})" };
                }
 
                // IndexOf overloads
                foreach ((string originalArguments, string expectedArguments) in GetNamedArguments(separator))
                {
                    yield return new object[] { $"a.IndexOf(value{separator}b.{caseChanging}(){originalArguments})", $"a.IndexOf(value{separator}b{expectedArguments}, comparisonType{separator}StringComparison.{replacement})" };
                }
 
                yield return new object[] { $"a.{caseChanging}().IndexOf(value{separator}b{langSpecificOrderedNamedArgs.Item1})", $"a.IndexOf(value{separator}b{langSpecificOrderedNamedArgs.Item2}, comparisonType{separator}StringComparison.{replacement})" };
            }
        }
 
        // Test cases for Contains, StartsWith and all overloads of IndexOf
        // where the instance string is case-changed and the string argument is unmodified
        // and the result of the method then calls another method.
        public static IEnumerable<object[]> DiagnosedAndFixedWithAppendedMethodData()
        {
            foreach ((string caseChanging, string replacement) in Cultures)
            {
                foreach (string method in ContainsStartsWith)
                {
                    yield return new object[] { $"a.{caseChanging}().{method}(b)", $"a.{method}(b, StringComparison.{replacement})", ".Equals(myBoolean)" };
                }
 
                // IndexOf overloads
                foreach (string arguments in UnnamedArgs)
                {
                    yield return new object[] { $"a.{caseChanging}().IndexOf(b{arguments})", $"a.IndexOf(b{arguments}, StringComparison.{replacement})", ".Equals(-1)" };
                }
            }
        }
 
        // Test cases for Contains, StartsWith and all overloads of IndexOf
        // where the instance string is unmodified and the string argument is case-changed
        // and the result of the method then calls another method.
        public static IEnumerable<object[]> DiagnosedAndFixedWithAppendedMethodInvertedData()
        {
            foreach ((string caseChanging, string replacement) in Cultures)
            {
                foreach (string method in ContainsStartsWith)
                {
                    yield return new object[] { $"a.{method}(b.{caseChanging}())", $"a.{method}(b, StringComparison.{replacement})", ".Equals(myBoolean)" };
                }
 
                // IndexOf overloads
                foreach (string arguments in UnnamedArgs)
                {
                    yield return new object[] { $"a.IndexOf(b.{caseChanging}(){arguments})", $"a.IndexOf(b{arguments}, StringComparison.{replacement})", ".Equals(-1)" };
                }
            }
        }
 
        public static IEnumerable<object[]> CSharpDiagnosedAndFixedWithAppendedMethodNamedData() => DiagnosedAndFixedWithAppendedMethodNamedData(CSharpSeparator, CSharpIndexOfNamedArguments);
        public static IEnumerable<object[]> VisualBasicDiagnosedAndFixedWithAppendedMethodNamedData() => DiagnosedAndFixedWithAppendedMethodNamedData(VisualBasicSeparator, VisualBasicIndexOfNamedArguments);
 
        // Test cases for Contains, StartsWith and all overloads of IndexOf
        // where the instance string is case-changed and the string argument is unmodified and explicitly named
        // and the result of the method then calls another method.
        private static IEnumerable<object[]> DiagnosedAndFixedWithAppendedMethodNamedData(string separator, (string, string) langSpecificOrderedNamedArgs)
        {
            foreach ((string caseChanging, string replacement) in Cultures)
            {
                foreach (string method in ContainsStartsWith)
                {
                    yield return new object[] { $"a.{caseChanging}().{method}(value{separator}b)", $"a.{method}(value{separator}b, comparisonType{separator}StringComparison.{replacement})", ".Equals(myBoolean)" };
                }
 
                // IndexOf overloads
                foreach ((string originalArguments, string expectedArguments) in GetNamedArguments(separator))
                {
                    yield return new object[] { $"a.{caseChanging}().IndexOf(value{separator}b{originalArguments})", $"a.IndexOf(value{separator}b{expectedArguments}, comparisonType{separator}StringComparison.{replacement})", ".Equals(-1)" };
                }
 
                yield return new object[] { $"a.{caseChanging}().IndexOf(value{separator}b{langSpecificOrderedNamedArgs.Item1})", $"a.IndexOf(value{separator}b{langSpecificOrderedNamedArgs.Item2}, comparisonType{separator}StringComparison.{replacement})", ".Equals(-1)" };
            }
        }
 
        // Test cases for Contains, StartsWith
        // where the instance string is case-changed and the string argument is unmodified
        // and the boolean result is consumed in a condition statement.
        public static IEnumerable<object[]> DiagnosedAndFixedImplicitBooleanData()
        {
            foreach ((string caseChanging, string replacement) in Cultures)
            {
                foreach (string method in ContainsStartsWith)
                {
                    yield return new object[] { $"a.{caseChanging}().{method}(b)", $"a.{method}(b, StringComparison.{replacement})", "" };
                }
 
                // Inverted
                foreach (string method in ContainsStartsWith)
                {
                    yield return new object[] { $"a.{method}(b.{caseChanging}())", $"a.{method}(b, StringComparison.{replacement})", "" };
                }
            }
        }
 
        // Test cases for Contains, StartsWith and all overloads of IndexOf
        // where the instance string is a case-changed literal and the string argument is an unmodified string literal.
        public static IEnumerable<object[]> DiagnosedAndFixedStringLiteralsData()
        {
            foreach ((string caseChanging, string replacement) in Cultures)
            {
                foreach (string method in ContainsStartsWith)
                {
                    yield return new object[] { $"\"aBc\".{caseChanging}().{method}(\"CdE\")", $"\"aBc\".{method}(\"CdE\", StringComparison.{replacement})" };
                }
 
                // IndexOf overloads
                foreach (string arguments in UnnamedArgs)
                {
                    yield return new object[] { $"\"aBc\".{caseChanging}().IndexOf(\"CdE\"{arguments})", $"\"aBc\".IndexOf(\"CdE\"{arguments}, StringComparison.{replacement})" };
                }
            }
        }
 
        // Test cases for Contains, StartsWith and all overloads of IndexOf
        // where the instance string is an unmodified literal and the string argument is a case-changed string literal.
        public static IEnumerable<object[]> DiagnosedAndFixedStringLiteralsInvertedData()
        {
            foreach ((string caseChanging, string replacement) in Cultures)
            {
                foreach (string method in ContainsStartsWith)
                {
                    yield return new object[] { $"\"aBc\".{method}(\"CdE\".{caseChanging}())", $"\"aBc\".{method}(\"CdE\", StringComparison.{replacement})" };
                }
 
                // IndexOf overloads
                foreach (string arguments in UnnamedArgs)
                {
                    yield return new object[] { $"\"aBc\".IndexOf(\"CdE\".{caseChanging}(){arguments})", $"\"aBc\".IndexOf(\"CdE\"{arguments}, StringComparison.{replacement})" };
                }
            }
        }
 
        public static IEnumerable<object[]> CSharpDiagnosedAndFixedStringLiteralsNamedData() => DiagnosedAndFixedStringLiteralsNamedData(CSharpSeparator, CSharpIndexOfNamedArguments);
        public static IEnumerable<object[]> VisualBasicDiagnosedAndFixedStringLiteralsNamedData() => DiagnosedAndFixedStringLiteralsNamedData(VisualBasicSeparator, VisualBasicIndexOfNamedArguments);
 
        // Test cases for Contains, StartsWith and all overloads of IndexOf
        // where the instance string is a case-changed literal and the string argument is an unmodified, explicitly named string literal.
        private static IEnumerable<object[]> DiagnosedAndFixedStringLiteralsNamedData(string separator, (string, string) langSpecificOrderedNamedArgs)
        {
            foreach ((string caseChanging, string replacement) in Cultures)
            {
                foreach (string method in ContainsStartsWith)
                {
                    yield return new object[] { $"\"aBc\".{caseChanging}().{method}(value{separator}\"CdE\")", $"\"aBc\".{method}(value{separator}\"CdE\", comparisonType{separator}StringComparison.{replacement})" };
                }
 
                // IndexOf overloads
                foreach ((string originalArguments, string expectedArguments) in GetNamedArguments(separator))
                {
                    yield return new object[] { $"\"aBc\".{caseChanging}().IndexOf(value{separator}\"CdE\"{originalArguments})", $"\"aBc\".IndexOf(value{separator}\"CdE\"{expectedArguments}, comparisonType{separator}StringComparison.{replacement})" };
                }
 
                yield return new object[] { $"\"aBc\".{caseChanging}().IndexOf(value{separator}\"CdE\"{langSpecificOrderedNamedArgs.Item1})", $"\"aBc\".IndexOf(value{separator}\"CdE\"{langSpecificOrderedNamedArgs.Item2}, comparisonType{separator}StringComparison.{replacement})" };
            }
        }
 
        public static IEnumerable<object[]> CSharpDiagnosedAndFixedStringLiteralsInvertedNamedData() => DiagnosedAndFixedStringLiteralsInvertedNamedData(CSharpSeparator, CSharpIndexOfNamedArguments);
        public static IEnumerable<object[]> VisualBasicDiagnosedAndFixedStringLiteralsInvertedNamedData() => DiagnosedAndFixedStringLiteralsInvertedNamedData(VisualBasicSeparator, VisualBasicIndexOfNamedArguments);
 
        // Test cases for Contains, StartsWith and all overloads of IndexOf
        // where the instance string is an unmodified literal and the string argument is a case-changed, explicitly named string literal.
        private static IEnumerable<object[]> DiagnosedAndFixedStringLiteralsInvertedNamedData(string separator, (string, string) langSpecificOrderedNamedArgs)
        {
            foreach ((string caseChanging, string replacement) in Cultures)
            {
                foreach (string method in ContainsStartsWith)
                {
                    yield return new object[] { $"\"aBc\".{method}(value{separator}\"CdE\".{caseChanging}())", $"\"aBc\".{method}(value{separator}\"CdE\", comparisonType{separator}StringComparison.{replacement})" };
                }
 
                // IndexOf overloads
                foreach ((string originalArguments, string expectedArguments) in GetNamedArguments(separator))
                {
                    yield return new object[] { $"\"aBc\".IndexOf(value{separator}\"CdE\".{caseChanging}(){originalArguments})", $"\"aBc\".IndexOf(value{separator}\"CdE\"{expectedArguments}, comparisonType{separator}StringComparison.{replacement})" };
                }
 
                yield return new object[] { $"\"aBc\".{caseChanging}().IndexOf(value{separator}\"CdE\"{langSpecificOrderedNamedArgs.Item1})", $"\"aBc\".IndexOf(value{separator}\"CdE\"{langSpecificOrderedNamedArgs.Item2}, comparisonType{separator}StringComparison.{replacement})" };
            }
        }
 
        // Test cases for Contains, StartsWith and all overloads of IndexOf
        // where the instance string is a method invocation that is case-changed, and the string argument is unmodified and comes from another method invocation.
        public static IEnumerable<object[]> DiagnosedAndFixedStringReturningMethodsData()
        {
            foreach ((string caseChanging, string replacement) in Cultures)
            {
                foreach (string method in ContainsStartsWith)
                {
                    yield return new object[] { $"GetStringA().{caseChanging}().{method}(GetStringB())", $"GetStringA().{method}(GetStringB(), StringComparison.{replacement})" };
                }
 
                // IndexOf overloads
                foreach (string arguments in UnnamedArgs)
                {
                    yield return new object[] { $"GetStringA().{caseChanging}().IndexOf(GetStringB(){arguments})", $"GetStringA().IndexOf(GetStringB(){arguments}, StringComparison.{replacement})" };
                }
            }
        }
 
        // Test cases for Contains, StartsWith and all overloads of IndexOf
        // where the instance string is a method invocation and is unmodified, and the string argument is case-changed and comes from another method invocation.
        public static IEnumerable<object[]> DiagnosedAndFixedStringReturningMethodsInvertedData()
        {
            foreach ((string caseChanging, string replacement) in Cultures)
            {
                foreach (string method in ContainsStartsWith)
                {
                    yield return new object[] { $"GetStringA().{method}(GetStringB().{caseChanging}())", $"GetStringA().{method}(GetStringB(), StringComparison.{replacement})" };
                }
 
                // IndexOf overloads
                foreach (string arguments in UnnamedArgs)
                {
                    yield return new object[] { $"GetStringA().IndexOf(GetStringB().{caseChanging}(){arguments})", $"GetStringA().IndexOf(GetStringB(){arguments}, StringComparison.{replacement})" };
                }
            }
        }
 
        public static IEnumerable<object[]> CSharpDiagnosedAndFixedStringReturningMethodsNamedData() => DiagnosedAndFixedStringReturningMethodsNamedData(CSharpSeparator, CSharpIndexOfNamedArguments);
        public static IEnumerable<object[]> VisualBasicDiagnosedAndFixedStringReturningMethodsNamedData() => DiagnosedAndFixedStringReturningMethodsNamedData(VisualBasicSeparator, VisualBasicIndexOfNamedArguments);
 
        // Test cases for Contains, StartsWith and all overloads of IndexOf
        // where the instance string is a method invocation that is case-changed, and the string argument is unmodified, comes from another method invocation, and is explicitly named.
        private static IEnumerable<object[]> DiagnosedAndFixedStringReturningMethodsNamedData(string separator, (string, string) langSpecificOrderedNamedArgs)
        {
            foreach ((string caseChanging, string replacement) in Cultures)
            {
                foreach (string method in ContainsStartsWith)
                {
                    yield return new object[] { $"GetStringA().{caseChanging}().{method}(value{separator}GetStringB())", $"GetStringA().{method}(value{separator}GetStringB(), comparisonType{separator}StringComparison.{replacement})" };
                }
 
                // IndexOf overloads
                foreach ((string originalArguments, string expectedArguments) in GetNamedArguments(separator))
                {
                    yield return new object[] { $"GetStringA().{caseChanging}().IndexOf(value{separator}GetStringB(){originalArguments})", $"GetStringA().IndexOf(value{separator}GetStringB(){expectedArguments}, comparisonType{separator}StringComparison.{replacement})" };
                }
 
                yield return new object[] { $"GetStringA().{caseChanging}().IndexOf(value{separator}GetStringB(){langSpecificOrderedNamedArgs.Item1})", $"GetStringA().IndexOf(value{separator}GetStringB(){langSpecificOrderedNamedArgs.Item2}, comparisonType{separator}StringComparison.{replacement})" };
            }
        }
 
        public static IEnumerable<object[]> CSharpDiagnosedAndFixedStringReturningMethodsInvertedNamedData() => DiagnosedAndFixedStringReturningMethodsInvertedNamedData(CSharpSeparator, CSharpIndexOfNamedArguments);
        public static IEnumerable<object[]> VisualBasicDiagnosedAndFixedStringReturningMethodsInvertedNamedData() => DiagnosedAndFixedStringReturningMethodsInvertedNamedData(VisualBasicSeparator, VisualBasicIndexOfNamedArguments);
 
        // Test cases for Contains, StartsWith and all overloads of IndexOf
        // where the instance string is unmodified and comes from a method invocation, and the string argument is case-changed, comes from another method invocation, and is explicitly named.
        private static IEnumerable<object[]> DiagnosedAndFixedStringReturningMethodsInvertedNamedData(string separator, (string, string) langSpecificOrderedNamedArgs)
        {
            foreach ((string caseChanging, string replacement) in Cultures)
            {
                foreach (string method in ContainsStartsWith)
                {
                    yield return new object[] { $"GetStringA().{method}(value{separator}GetStringB().{caseChanging}())", $"GetStringA().{method}(value{separator}GetStringB(), comparisonType{separator}StringComparison.{replacement})" };
                }
 
                // IndexOf overloads
                foreach ((string originalArguments, string expectedArguments) in GetNamedArguments(separator))
                {
                    yield return new object[] { $"GetStringA().IndexOf(value{separator}GetStringB().{caseChanging}(){originalArguments})", $"GetStringA().IndexOf(value{separator}GetStringB(){expectedArguments}, comparisonType{separator}StringComparison.{replacement})" };
                }
 
                yield return new object[] { $"GetStringA().{caseChanging}().IndexOf(value{separator}GetStringB(){langSpecificOrderedNamedArgs.Item1})", $"GetStringA().IndexOf(value{separator}GetStringB(){langSpecificOrderedNamedArgs.Item2}, comparisonType{separator}StringComparison.{replacement})" };
            }
        }
 
        // Test cases for Contains, StartsWith and all overloads of IndexOf
        // where the case-changed invocation string literal is enclosed in parenthesis, and the argument is another string literal.
        public static IEnumerable<object[]> DiagnosedAndFixedParenthesizedData()
        {
            foreach ((string caseChanging, string replacement) in Cultures)
            {
                foreach (string method in ContainsStartsWith)
                {
                    yield return new object[] { $"(\"aBc\".{caseChanging}()).{method}(\"CdE\")", $"\"aBc\".{method}(\"CdE\", StringComparison.{replacement})" };
                }
 
                // IndexOf overloads
                foreach (string arguments in UnnamedArgs)
                {
                    yield return new object[] { $"(\"aBc\".{caseChanging}()).IndexOf(\"CdE\"{arguments})", $"\"aBc\".IndexOf(\"CdE\"{arguments}, StringComparison.{replacement})" };
                }
            }
        }
 
        // Test cases for Contains, StartsWith and all overloads of IndexOf
        // where the invocation is a string literal and the argument is a case-changed string literal enclosed in parenthesis.
        public static IEnumerable<object[]> DiagnosedAndFixedParenthesizedInvertedData()
        {
            foreach ((string caseChanging, string replacement) in Cultures)
            {
                foreach (string method in ContainsStartsWith)
                {
                    yield return new object[] { $"\"aBc\".{method}((\"CdE\".{caseChanging}()))", $"\"aBc\".{method}(\"CdE\", StringComparison.{replacement})" };
                }
 
                // IndexOf overloads
                foreach (string arguments in UnnamedArgs)
                {
                    yield return new object[] { $"\"aBc\".IndexOf(\"CdE\".{caseChanging}(){arguments})", $"\"aBc\".IndexOf(\"CdE\"{arguments}, StringComparison.{replacement})" };
                }
            }
        }
 
        public static IEnumerable<object[]> CSharpDiagnosedAndFixedParenthesizedNamedData() => DiagnosedAndFixedParenthesizedNamedData(CSharpSeparator, CSharpIndexOfNamedArguments);
        public static IEnumerable<object[]> VisualBasicDiagnosedAndFixedParenthesizedNamedData() => DiagnosedAndFixedParenthesizedNamedData(VisualBasicSeparator, VisualBasicIndexOfNamedArguments);
 
        // Test cases for Contains, StartsWith and all overloads of IndexOf
        // where the case-changed invocation string literal is enclosed in parenthesis, and the argument is another string literal, explicitly named.
        private static IEnumerable<object[]> DiagnosedAndFixedParenthesizedNamedData(string separator, (string, string) langSpecificOrderedNamedArgs)
        {
            foreach ((string caseChanging, string replacement) in Cultures)
            {
                foreach (string method in ContainsStartsWith)
                {
                    yield return new object[] { $"(\"aBc\".{caseChanging}()).{method}(value{separator}\"CdE\")", $"\"aBc\".{method}(value{separator}\"CdE\", comparisonType{separator}StringComparison.{replacement})" };
                    yield return new object[] { $"(GetString().{caseChanging}()).{method}(value{separator}GetString())", $"GetString().{method}(value{separator}GetString(), comparisonType{separator}StringComparison.{replacement})" };
                }
 
                // IndexOf overloads
                foreach ((string originalArguments, string expectedArguments) in GetNamedArguments(separator))
                {
                    yield return new object[] { $"(\"aBc\".{caseChanging}()).IndexOf(value{separator}\"CdE\"{originalArguments})", $"\"aBc\".IndexOf(value{separator}\"CdE\"{expectedArguments}, comparisonType{separator}StringComparison.{replacement})" };
                    yield return new object[] { $"(GetString().{caseChanging}()).IndexOf(value{separator}GetString(){originalArguments})", $"GetString().IndexOf(value{separator}GetString(){expectedArguments}, comparisonType{separator}StringComparison.{replacement})" };
                }
 
                yield return new object[] { $"a.{caseChanging}().IndexOf(value{separator}b{langSpecificOrderedNamedArgs.Item1})", $"a.IndexOf(value{separator}b{langSpecificOrderedNamedArgs.Item2}, comparisonType{separator}StringComparison.{replacement})" };
            }
        }
 
        public static IEnumerable<object[]> CSharpDiagnosedAndFixedParenthesizedNamedInvertedData() => DiagnosedAndFixedParenthesizedNamedInvertedData(CSharpSeparator, CSharpIndexOfNamedArguments);
        public static IEnumerable<object[]> VisualBasicDiagnosedAndFixedParenthesizedNamedInvertedData() => DiagnosedAndFixedParenthesizedNamedInvertedData(VisualBasicSeparator, VisualBasicIndexOfNamedArguments);
 
        // Test cases for Contains, StartsWith and all overloads of IndexOf
        // where the invocation is a string literal and the argument is a case-changed string literal enclosed in parenthesis and explicitly named.
        private static IEnumerable<object[]> DiagnosedAndFixedParenthesizedNamedInvertedData(string separator, (string, string) langSpecificOrderedNamedArgs)
        {
            foreach ((string caseChanging, string replacement) in Cultures)
            {
                foreach (string method in ContainsStartsWith)
                {
                    yield return new object[] { $"(\"aBc\").{method}(value{separator}(\"cDe\".{caseChanging}()))", $"(\"aBc\").{method}(value{separator}\"cDe\", comparisonType{separator}StringComparison.{replacement})" };
                    yield return new object[] { $"(GetString()).{method}(value{separator}(GetString().{caseChanging}()))", $"(GetString()).{method}(value{separator}GetString(), comparisonType{separator}StringComparison.{replacement})" };
                }
 
                // IndexOf overloads
                foreach ((string originalArguments, string expectedArguments) in GetNamedArguments(separator))
                {
                    yield return new object[] { $"(\"aBc\").IndexOf(value{separator}(\"cDe\".{caseChanging}()){originalArguments})", $"(\"aBc\").IndexOf(value{separator}\"cDe\"{expectedArguments}, comparisonType{separator}StringComparison.{replacement})" };
                    yield return new object[] { $"(GetString()).IndexOf(value{separator}(GetString().{caseChanging}()){originalArguments})", $"(GetString()).IndexOf(value{separator}GetString(){expectedArguments}, comparisonType{separator}StringComparison.{replacement})" };
                }
 
                yield return new object[] { $"a.{caseChanging}().IndexOf(value{separator}b{langSpecificOrderedNamedArgs.Item1})", $"a.IndexOf(value{separator}b{langSpecificOrderedNamedArgs.Item2}, comparisonType{separator}StringComparison.{replacement})" };
            }
        }
 
        public static IEnumerable<object[]> CSharpDiagnosedAndFixedParenthesizedComplexCasesData() => DiagnosedAndFixedParenthesizedComplexCasesData(CSharpSeparator, CSharpIndexOfNamedArguments);
        public static IEnumerable<object[]> VisualBasicDiagnosedAndFixedParenthesizedComplexCasesData() => DiagnosedAndFixedParenthesizedComplexCasesData(VisualBasicSeparator, VisualBasicIndexOfNamedArguments);
 
        // Test cases for Contains, StartsWith and all overloads of IndexOf
        // where the invocation and the argument are both enclosed in various combinations of parenthesis.
        private static IEnumerable<object[]> DiagnosedAndFixedParenthesizedComplexCasesData(string separator, (string, string) langSpecificOrderedNamedArgs)
        {
            foreach ((string caseChanging, string replacement) in Cultures)
            {
                foreach (string method in ContainsStartsWith)
                {
                    yield return new object[] { $"((GetString().{caseChanging}())).{method}(value{separator}((GetString().{caseChanging}())))", $"GetString().{method}(value{separator}GetString(), comparisonType{separator}StringComparison.{replacement})" };
                    yield return new object[] { $"((a.{caseChanging}())).{method}(value{separator}((b.{caseChanging}())))", $"a.{method}(value{separator}b, comparisonType{separator}StringComparison.{replacement})" };
                }
 
                // IndexOf overloads
                foreach ((string originalArguments, string expectedArguments) in GetNamedArguments(separator))
                {
                    yield return new object[] { $"((GetString().{caseChanging}())).IndexOf(value{separator}((GetString().{caseChanging}())){originalArguments})", $"GetString().IndexOf(value{separator}GetString(){expectedArguments}, comparisonType{separator}StringComparison.{replacement})" };
                }
 
                yield return new object[] { $"a.{caseChanging}().IndexOf(value{separator}b{langSpecificOrderedNamedArgs.Item1})", $"a.IndexOf(value{separator}b{langSpecificOrderedNamedArgs.Item2}, comparisonType{separator}StringComparison.{replacement})" };
            }
        }
 
        // Variety of test cases that should not emit a diagnostic.
        public static IEnumerable<object[]> NoDiagnosticData()
        {
            // Test needs to define a char ch and an object obj
            foreach (string method in new[] { "Contains", "IndexOf", "StartsWith" })
            {
                yield return new object[] { $"\"aBc\".{method}(\"cDe\")" };
                yield return new object[] { $"\"aBc\".{method}(\"cDe\", StringComparison.CurrentCultureIgnoreCase)" };
                yield return new object[] { $"\"aBc\".ToUpper().{method}(\"cDe\", StringComparison.InvariantCulture)" };
                yield return new object[] { $"\"aBc\".{method}(ch)" };
 
                // Inverted
                yield return new object[] { $"\"aBc\".{method}(\"cDe\".ToUpper(), StringComparison.InvariantCulture)" };
            }
 
            // StarstWith does not have a (char, StringComparison) overload
            foreach (string method in new[] { "Contains", "IndexOf" })
            {
                yield return new object[] { $"\"aBc\".{method}(ch, StringComparison.Ordinal)" };
                yield return new object[] { $"\"aBc\".ToLowerInvariant().{method}(ch, StringComparison.CurrentCulture)" };
            }
 
            // CompareTo
            yield return new object[] { "\"aBc\".CompareTo(obj)" };
            yield return new object[] { "\"aBc\".ToLower().CompareTo(obj)" };
            yield return new object[] { "\"aBc\".CompareTo(\"cDe\")" };
        }
 
        public static IEnumerable<object[]> DiagnosticNoFixStartsWithContainsIndexOfData()
        {
            foreach ((string left, string right) in IncompatibleCaseChangingMethods)
            {
                yield return new object[] { $"a.{left}().StartsWith(b.{right}())" };
                yield return new object[] { $"a.{left}().Contains(b.{right}())" };
                yield return new object[] { $"\"aBc\".{left}().StartsWith(\"cDe\".{right}())" };
                yield return new object[] { $"\"aBc\".{left}().Contains(\"cDe\".{right}())" };
                yield return new object[] { $"GetStringA().{left}().StartsWith(GetStringB().{right}())" };
                yield return new object[] { $"GetStringA().{left}().Contains(GetStringB().{right}())" };
 
                yield return new object[] { $"a.{left}().StartsWith(b.{right}())" };
                yield return new object[] { $"a.{left}().Contains(b.{right}())" };
                yield return new object[] { $"\"aBc\".{left}().StartsWith(\"cDe\".{right}())" };
                yield return new object[] { $"\"aBc\".{left}().Contains(\"cDe\".{right}())" };
 
                // IndexOf overloads
                foreach (string arguments in UnnamedArgs)
                {
                    yield return new object[] { $"a.{left}().IndexOf(b.{right}(){arguments})" };
                    yield return new object[] { $"a.{left}().IndexOf(b.{right}(){arguments})" };
                }
            }
        }
 
        // Test cases for CompareTo, with the invocation string case-changed, that should emit a diagnostic but should not offer a fix.
        public static IEnumerable<object[]> DiagnosticNoFixCompareToData()
        {
            // Tests need to define strings a, b, and methods GetStringA, GetStringB
            foreach ((string caseChanging, _) in Cultures)
            {
                yield return new object[] { $"a.{caseChanging}().CompareTo(b)" };
                yield return new object[] { $"\"aBc\".{caseChanging}().CompareTo(\"CdE\")" };
                yield return new object[] { $"GetStringA().{caseChanging}().CompareTo(GetStringB())" };
                yield return new object[] { $"(\"aBc\".{caseChanging}()).CompareTo(\"CdE\")" };
            }
        }
 
        // Test cases for CompareTo, with the argument string case-changed, that should emit a diagnostic but should not offer a fix.
        public static IEnumerable<object[]> DiagnosticNoFixCompareToInvertedData()
        {
            // Tests need to define strings a, b, and methods GetStringA, GetStringB
            foreach ((string caseChanging, _) in Cultures)
            {
                yield return new object[] { $"a.CompareTo(b.{caseChanging}())" };
                yield return new object[] { $"\"aBc\".CompareTo(\"CdE\".{caseChanging}())" };
                yield return new object[] { $"GetStringA().CompareTo(GetStringB().{caseChanging}())" };
                yield return new object[] { $"(\"aBc\").CompareTo(\"CdE\".{caseChanging}())" };
            }
        }
 
        public static IEnumerable<object[]> CSharpDiagnosticNoFixCompareToNamedData() => DiagnosticNoFixCompareToNamedData(CSharpSeparator);
        public static IEnumerable<object[]> VisualBasicDiagnosticNoFixCompareToNamedData() => DiagnosticNoFixCompareToNamedData(VisualBasicSeparator);
 
        // Test cases for CompareTo, with the invocation string case-changed and the argument string explicitly named,
        // that should emit a diagnostic but should not offer a fix.
        private static IEnumerable<object[]> DiagnosticNoFixCompareToNamedData(string separator)
        {
            // Tests need to define strings a, b
            foreach ((string caseChanging, _) in Cultures)
            {
                yield return new object[] { $"a.{caseChanging}().CompareTo(strB{separator}b)" };
                yield return new object[] { $"(\"aBc\".{caseChanging}()).CompareTo(strB{separator}\"CdE\")" };
            }
        }
 
        public static IEnumerable<object[]> CSharpDiagnosticNoFixCompareToInvertedNamedData() => DiagnosticNoFixCompareToInvertedNamedData(CSharpSeparator);
        public static IEnumerable<object[]> VisualBasicDiagnosticNoFixCompareToInvertedNamedData() => DiagnosticNoFixCompareToInvertedNamedData(VisualBasicSeparator);
 
        // Test cases for CompareTo, with the invocation string unmodified and the argument string case-changed and explicitly named,
        // that should emit a diagnostic but should not offer a fix.
        private static IEnumerable<object[]> DiagnosticNoFixCompareToInvertedNamedData(string separator)
        {
            // Tests need to define strings a, b
            foreach ((string caseChanging, _) in Cultures)
            {
                yield return new object[] { $"a.CompareTo(strB{separator}b.{caseChanging}())" };
                yield return new object[] { $"(\"aBc\").CompareTo(strB{separator}\"CdE\".{caseChanging}())" };
            }
        }
 
        public static IEnumerable<object[]> CSharpDiagnosedAndFixedEqualityToEqualsData() =>
            DiagnosedAndFixedEqualityToEqualsData(CSharpComparisonOperators);
        public static IEnumerable<object[]> VisualBasicDiagnosedAndFixedEqualityToEqualsData() =>
            DiagnosedAndFixedEqualityToEqualsData(VisualBasicComparisonOperators);
 
        // Test cases for string equality binary operations with one side being case-changed,
        // or both sides being case-changed to the same culture.
        private static IEnumerable<object[]> DiagnosedAndFixedEqualityToEqualsData(ValueTuple<string, string>[] comparisonOperators)
        {
#pragma warning disable format
            foreach (string casing in new[]{ "Lower", "Upper" })
            {
                foreach ((string before, string after) in comparisonOperators)
                {
                    yield return new object[] { $"a.To{casing}() {before} b.To{casing}()",          $"{after}a.Equals(b, StringComparison.CurrentCultureIgnoreCase)" };
                    yield return new object[] { $"a.To{casing}() {before} \"abc\"",              $"{after}a.Equals(\"abc\", StringComparison.CurrentCultureIgnoreCase)" };
                    yield return new object[] { $"a.To{casing}() {before} b",                    $"{after}a.Equals(b, StringComparison.CurrentCultureIgnoreCase)" };
 
                    yield return new object[] { $"a.To{casing}Invariant() {before} b.To{casing}Invariant()", $"{after}a.Equals(b, StringComparison.InvariantCultureIgnoreCase)" };
                    yield return new object[] { $"a.To{casing}Invariant() {before} \"abc\"",              $"{after}a.Equals(\"abc\", StringComparison.InvariantCultureIgnoreCase)" };
                    yield return new object[] { $"a.To{casing}Invariant() {before} b",                    $"{after}a.Equals(b, StringComparison.InvariantCultureIgnoreCase)" };
 
                    yield return new object[] { $"a {before} b.To{casing}()",          $"{after}a.Equals(b, StringComparison.CurrentCultureIgnoreCase)" };
                    yield return new object[] { $"a {before} b.To{casing}Invariant()", $"{after}a.Equals(b, StringComparison.InvariantCultureIgnoreCase)" };
 
                    yield return new object[] { $"\"abc\" {before} b.To{casing}()",                                $"{after}\"abc\".Equals(b, StringComparison.CurrentCultureIgnoreCase)" };
                    yield return new object[] { $"\"abc\" {before} b.To{casing}Invariant()",                       $"{after}\"abc\".Equals(b, StringComparison.InvariantCultureIgnoreCase)" };
                    yield return new object[] { $"\"abc\".To{casing}() {before} b.To{casing}()",                   $"{after}\"abc\".Equals(b, StringComparison.CurrentCultureIgnoreCase)" };
                    yield return new object[] { $"\"abc\".To{casing}Invariant() {before} b.To{casing}Invariant()", $"{after}\"abc\".Equals(b, StringComparison.InvariantCultureIgnoreCase)" };
 
                    yield return new object[] { $"GetString().To{casing}() {before} a.To{casing}()",          $"{after}GetString().Equals(a, StringComparison.CurrentCultureIgnoreCase)" };
                    yield return new object[] { $"GetString().To{casing}() {before} a",                       $"{after}GetString().Equals(a, StringComparison.CurrentCultureIgnoreCase)" };
                    yield return new object[] { $"GetString().To{casing}Invariant() {before} a",              $"{after}GetString().Equals(a, StringComparison.InvariantCultureIgnoreCase)" };
                    yield return new object[] { $"GetString().To{casing}Invariant() {before} \"abc\"",        $"{after}GetString().Equals(\"abc\", StringComparison.InvariantCultureIgnoreCase)" };
                }
            }
#pragma warning restore format
        }
 
        public static IEnumerable<object[]> CSharpDiagnosticNoFixEqualsData() => DiagnosticNoFixEqualsData(CSharpComparisonOperators);
        public static IEnumerable<object[]> VisualBasicDiagnosticNoFixEqualsData() => DiagnosticNoFixEqualsData(VisualBasicComparisonOperators);
 
        // Test cases for string equality binary operations that should emit a diagnostic but should not offer a fix
        // because both sides are getting case-changed with different culture.
        private static IEnumerable<object[]> DiagnosticNoFixEqualsData(ValueTuple<string, string>[] comparisonOperators)
        {
            foreach ((string op, _) in comparisonOperators)
            {
                foreach (string casing in new[] { "Lower", "Upper" })
                {
                    yield return new object[] { $"a.To{casing}() {op} b.To{casing}Invariant()" };
                    yield return new object[] { $"\"abc\".To{casing}() {op} b.To{casing}Invariant()" };
                    yield return new object[] { $"GetString().To{casing}() {op} a.To{casing}Invariant()" };
 
                    yield return new object[] { $"a.To{casing}Invariant() {op} b.To{casing}()" };
                    yield return new object[] { $"\"abc\".To{casing}Invariant() {op} b.To{casing}()" };
                    yield return new object[] { $"GetString().To{casing}Invariant() {op} a.To{casing}()" };
                }
 
                yield return new object[] { $"\"aBc\".ToLower() {op} \"cDe\".ToLowerInvariant()" };
                yield return new object[] { $"\"aBc\".ToLower() {op} \"cDe\".ToUpperInvariant()" };
                yield return new object[] { $"\"aBc\".ToUpper() {op} \"cDe\".ToLowerInvariant()" };
                yield return new object[] { $"\"aBc\".ToUpper() {op} \"cDe\".ToUpperInvariant()" };
 
                yield return new object[] { $"\"aBc\".ToLowerInvariant() {op} \"cDe\".ToLower()" };
                yield return new object[] { $"\"aBc\".ToLowerInvariant() {op} \"cDe\".ToUpper()" };
                yield return new object[] { $"\"aBc\".ToUpperInvariant() {op} \"cDe\".ToLower()" };
                yield return new object[] { $"\"aBc\".ToUpperInvariant() {op} \"cDe\".ToUpper()" };
 
            }
        }
    }
}