File: Snippets\CSharpForSnippetProviderTests.cs
Web Access
Project: src\src\Features\CSharpTest\Microsoft.CodeAnalysis.CSharp.Features.UnitTests.csproj (Microsoft.CodeAnalysis.CSharp.Features.UnitTests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Test.Utilities;
using Microsoft.CodeAnalysis.Testing;
using Roslyn.Test.Utilities;
using Xunit;
 
namespace Microsoft.CodeAnalysis.CSharp.UnitTests.Snippets;
 
[Trait(Traits.Feature, Traits.Features.Snippets)]
public sealed class CSharpForSnippetProviderTests : AbstractCSharpSnippetProviderTests
{
    protected override string SnippetIdentifier => "for";
 
    [Fact]
    public async Task InsertForSnippetInMethodTest()
    {
        await VerifySnippetAsync("""
            class Program
            {
                public void Method()
                {
                    $$
                }
            }
            """, """
            class Program
            {
                public void Method()
                {
                    for (int {|0:i|} = 0; {|0:i|} < {|1:length|}; {|0:i|}++)
                    {
                        $$
                    }
                }
            }
            """);
    }
 
    [Fact]
    public async Task InsertForSnippetInMethodUsedIncrementorTest()
    {
        await VerifySnippetAsync("""
            class Program
            {
                public void Method()
                {
                    int i;
                    $$
                }
            }
            """, """
            class Program
            {
                public void Method()
                {
                    int i;
                    for (int {|0:j|} = 0; {|0:j|} < {|1:length|}; {|0:j|}++)
                    {
                        $$
                    }
                }
            }
            """);
    }
 
    [Fact]
    public async Task InsertForSnippetInMethodUsedIncrementorsTest()
    {
        await VerifySnippetAsync("""
            class Program
            {
                public void Method()
                {
                    int i, j, k;
                    $$
                }
            }
            """, """
            class Program
            {
                public void Method()
                {
                    int i, j, k;
                    for (int {|0:i1|} = 0; {|0:i1|} < {|1:length|}; {|0:i1|}++)
                    {
                        $$
                    }
                }
            }
            """);
    }
 
    [Fact]
    public async Task InsertForSnippetInGlobalContextTest()
    {
        await VerifySnippetAsync("""
            $$
            """, """
            for (int {|0:i|} = 0; {|0:i|} < {|1:length|}; {|0:i|}++)
            {
                $$
            }
            """);
    }
 
    [Fact]
    public async Task InsertForSnippetInConstructorTest()
    {
        await VerifySnippetAsync("""
            class Program
            {
                public Program()
                {
                    $$
                }
            }
            """, """
            class Program
            {
                public Program()
                {
                    for (int {|0:i|} = 0; {|0:i|} < {|1:length|}; {|0:i|}++)
                    {
                        $$
                    }
                }
            }
            """);
    }
 
    [Fact]
    public async Task InsertForSnippetInLocalFunctionTest()
    {
        // TODO: fix this test when bug with simplifier failing to find correct node is fixed
        await VerifySnippetAsync("""
            class Program
            {
                public void Method()
                {
                    void LocalFunction()
                    {
                        $$
                    }
                }
            }
            """, """
            class Program
            {
                public void Method()
                {
                    void LocalFunction()
                    {
                        for (global::System.Int32 {|0:i|} = 0; {|0:i|} < {|1:length|}; {|0:i|}++)
                        {
                            $$
                        }
                    }
                }
            }
            """);
    }
 
    [Fact]
    public async Task InsertForSnippetInAnonymousFunctionTest()
    {
        // TODO: fix this test when bug with simplifier failing to find correct node is fixed
        await VerifySnippetAsync("""
            class Program
            {
                public void Method()
                {
                    var action = delegate()
                    {
                        $$
                    };
                }
            }
            """, """
            class Program
            {
                public void Method()
                {
                    var action = delegate()
                    {
                        for (global::System.Int32 {|0:i|} = 0; {|0:i|} < {|1:length|}; {|0:i|}++)
                        {
                            $$
                        }
                    };
                }
            }
            """);
    }
 
    [Fact]
    public async Task InsertForSnippetInParenthesizedLambdaExpressionTest()
    {
        // TODO: fix this test when bug with simplifier failing to find correct node is fixed
        await VerifySnippetAsync("""
            class Program
            {
                public void Method()
                {
                    var action = () =>
                    {
                        $$
                    };
                }
            }
            """, """
            class Program
            {
                public void Method()
                {
                    var action = () =>
                    {
                        for (global::System.Int32 {|0:i|} = 0; {|0:i|} < {|1:length|}; {|0:i|}++)
                        {
                            $$
                        }
                    };
                }
            }
            """);
    }
 
    [Fact]
    public async Task ProduceVarWithSpecificCodeStyleTest()
    {
        await VerifySnippetAsync("""
            class Program
            {
                public void Method()
                {
                    $$
                }
            }
            """, """
            class Program
            {
                public void Method()
                {
                    for (var {|0:i|} = 0; {|0:i|} < {|1:length|}; {|0:i|}++)
                    {
                        $$
                    }
                }
            }
            """,
            editorconfig: """
            root = true
            
            [*]
            csharp_style_var_for_built_in_types = true
            """);
    }
 
    [Theory]
    [MemberData(nameof(CommonSnippetTestData.IntegerTypes), MemberType = typeof(CommonSnippetTestData))]
    public async Task InsertInlineForSnippetInMethodTest(string inlineExpressionType)
    {
        await VerifySnippetAsync($$"""
            class Program
            {
                public void Method({{inlineExpressionType}} l)
                {
                    l.$$
                }
            }
            """, $$"""
            class Program
            {
                public void Method({{inlineExpressionType}} l)
                {
                    for ({{inlineExpressionType}} {|0:i|} = 0; {|0:i|} < l; {|0:i|}++)
                    {
                        $$
                    }
                }
            }
            """);
    }
 
    [Theory]
    [MemberData(nameof(CommonSnippetTestData.IntegerTypes), MemberType = typeof(CommonSnippetTestData))]
    public async Task InsertInlineForSnippetInGlobalContextTest(string inlineExpressionType)
    {
        await VerifySnippetAsync($$"""
            {{inlineExpressionType}} l;
            l.$$
            """, $$"""
            {{inlineExpressionType}} l;
            for ({{inlineExpressionType}} {|0:i|} = 0; {|0:i|} < l; {|0:i|}++)
            {
                $$
            }
            """);
    }
 
    [Theory]
    [MemberData(nameof(CommonSnippetTestData.NotIntegerTypesWithoutLengthOrCountProperty), MemberType = typeof(CommonSnippetTestData))]
    public async Task NoInlineForSnippetForIncorrectTypeInMethodTest(string inlineExpressionType)
    {
        await VerifySnippetIsAbsentAsync($$"""
            class Program
            {
                public void Method({{inlineExpressionType}} l)
                {
                    l.$$
                }
            }
            """);
    }
 
    [Theory]
    [MemberData(nameof(CommonSnippetTestData.NotIntegerTypesWithoutLengthOrCountProperty), MemberType = typeof(CommonSnippetTestData))]
    public async Task NoInlineForSnippetForIncorrectTypeInGlobalContextTest(string inlineExpressionType)
    {
        await VerifySnippetIsAbsentAsync($$"""
            {{inlineExpressionType}} l;
            l.$$
            """);
    }
 
    [Fact]
    public async Task ProduceVarWithSpecificCodeStyleForInlineSnippetTest()
    {
        await VerifySnippetAsync("""
            class Program
            {
                public void Method(int l)
                {
                    l.$$
                }
            }
            """, """
            class Program
            {
                public void Method(int l)
                {
                    for (var {|0:i|} = 0; {|0:i|} < l; {|0:i|}++)
                    {
                        $$
                    }
                }
            }
            """,
            editorconfig: """
            root = true
            
            [*]
            csharp_style_var_for_built_in_types = true
            """);
    }
 
    [Fact]
    public async Task NoInlineForSnippetNotDirectlyExpressionStatementTest()
    {
        await VerifySnippetIsAbsentAsync("""
            class Program
            {
                public void Method(int l)
                {
                    System.Console.WriteLine(l.$$);
                }
            }
            """);
    }
 
    [Theory]
    [InlineData("// comment")]
    [InlineData("/* comment */")]
    [InlineData("#region test")]
    public async Task CorrectlyDealWithLeadingTriviaInInlineSnippetInMethodTest1(string trivia)
    {
        await VerifySnippetAsync($$"""
            class Program
            {
                void M(int len)
                {
                    {{trivia}}
                    len.$$
                }
            }
            """, $$"""
            class Program
            {
                void M(int len)
                {
                    {{trivia}}
                    for (int {|0:i|} = 0; {|0:i|} < len; {|0:i|}++)
                    {
                        $$
                    }
                }
            }
            """);
    }
 
    [Theory]
    [InlineData("#if true")]
    [InlineData("#pragma warning disable CS0108")]
    [InlineData("#nullable enable")]
    public async Task CorrectlyDealWithLeadingTriviaInInlineSnippetInMethodTest2(string trivia)
    {
        await VerifySnippetAsync($$"""
            class Program
            {
                void M(int len)
                {
            {{trivia}}
                    len.$$
                }
            }
            """, $$"""
            class Program
            {
                void M(int len)
                {
            {{trivia}}
                    for (int {|0:i|} = 0; {|0:i|} < len; {|0:i|}++)
                    {
                        $$
                    }
                }
            }
            """);
    }
 
    [Theory]
    [InlineData("// comment")]
    [InlineData("/* comment */")]
    public async Task CorrectlyDealWithLeadingTriviaInInlineSnippetInGlobalStatementTest1(string trivia)
    {
        await VerifySnippetAsync($$"""
            {{trivia}}
            10.$$
            """, $$"""
            {{trivia}}
            for (int {|0:i|} = 0; {|0:i|} < 10; {|0:i|}++)
            {
                $$
            }
            """);
    }
 
    [Theory]
    [InlineData("#region test")]
    [InlineData("#if true")]
    [InlineData("#pragma warning disable CS0108")]
    [InlineData("#nullable enable")]
    public async Task CorrectlyDealWithLeadingTriviaInInlineSnippetInGlobalStatementTest2(string trivia)
    {
        await VerifySnippetAsync($$"""
            {{trivia}}
            10.$$
            """, $$"""

            {{trivia}}
            for (int {|0:i|} = 0; {|0:i|} < 10; {|0:i|}++)
            {
                $$
            }
            """);
    }
 
    [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/69598")]
    [MemberData(nameof(CommonSnippetTestData.IntegerTypes), MemberType = typeof(CommonSnippetTestData))]
    public async Task InsertInlineForSnippetWhenDottingBeforeContextualKeywordTest1(string intType)
    {
        await VerifySnippetAsync($$"""
            using System.Collections.Generic;
 
            class C
            {
                void M({{intType}} @int)
                {
                    @int.$$
                    var a = 0;
                }
            }
            """, $$"""
            using System.Collections.Generic;
 
            class C
            {
                void M({{intType}} @int)
                {
                    for ({{intType}} {|0:i|} = 0; {|0:i|} < @int; {|0:i|}++)
                    {
                        $$
                    }
                    var a = 0;
                }
            }
            """);
    }
 
    [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/69598")]
    [MemberData(nameof(CommonSnippetTestData.IntegerTypes), MemberType = typeof(CommonSnippetTestData))]
    public async Task InsertInlineForSnippetWhenDottingBeforeContextualKeywordTest2(string intType)
    {
        await VerifySnippetAsync($$"""
            using System.Collections.Generic;
 
            class C
            {
                async void M({{intType}} @int, Task t)
                {
                    @int.$$
                    await t;
                }
            }
            """, $$"""
            using System.Collections.Generic;
 
            class C
            {
                async void M({{intType}} @int, Task t)
                {
                    for ({{intType}} {|0:i|} = 0; {|0:i|} < @int; {|0:i|}++)
                    {
                        $$
                    }
                    await t;
                }
            }
            """);
    }
 
    [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/69598")]
    [InlineData("Task")]
    [InlineData("Task<int>")]
    [InlineData("System.Threading.Tasks.Task<int>")]
    public async Task InsertInlineForSnippetWhenDottingBeforeNameSyntaxTest(string nameSyntax)
    {
        await VerifySnippetAsync($$"""
            using System.Threading.Tasks;
            using System.Collections.Generic;
 
            class C
            {
                void M(int @int)
                {
                    @int.$$
                    {{nameSyntax}} t = null;
                }
            }
            """, $$"""
            using System.Threading.Tasks;
            using System.Collections.Generic;
 
            class C
            {
                void M(int @int)
                {
                    for (int {|0:i|} = 0; {|0:i|} < @int; {|0:i|}++)
                    {
                        $$
                    }
                    {{nameSyntax}} t = null;
                }
            }
            """);
    }
 
    [Theory]
    [MemberData(nameof(CommonSnippetTestData.IntegerTypes), MemberType = typeof(CommonSnippetTestData))]
    public async Task InsertInlineForSnippetWhenDottingBeforeMemberAccessExpressionOnTheNextLineTest(string intType)
    {
        await VerifySnippetAsync($$"""
            using System;
 
            class C
            {
                void M({{intType}} @int)
                {
                    @int.$$
                    Console.WriteLine();
                }
            }
            """, $$"""
            using System;
 
            class C
            {
                void M({{intType}} @int)
                {
                    for ({{intType}} {|0:i|} = 0; {|0:i|} < @int; {|0:i|}++)
                    {
                        $$
                    }
                    Console.WriteLine();
                }
            }
            """);
    }
 
    [Theory]
    [MemberData(nameof(CommonSnippetTestData.IntegerTypes), MemberType = typeof(CommonSnippetTestData))]
    public async Task NoInlineForSnippetWhenDottingBeforeMemberAccessExpressionOnTheSameLineTest(string intType)
    {
        await VerifySnippetIsAbsentAsync($$"""
            class C
            {
                void M({{intType}} @int)
                {
                    @int.$$ToString();
                }
            }
            """);
    }
 
    [Theory]
    [MemberData(nameof(CommonSnippetTestData.IntegerTypes), MemberType = typeof(CommonSnippetTestData))]
    public async Task NoInlineForSnippetWhenDottingBeforeContextualKeywordOnTheSameLineTest(string intType)
    {
        await VerifySnippetIsAbsentAsync($$"""
            class C
            {
                void M({{intType}} @int)
                {
                    @int.$$var a = 0;
                }
            }
            """);
    }
 
    [Theory]
    [InlineData("int[]", "Length")]
    [InlineData("Span<byte>", "Length")]
    [InlineData("ReadOnlySpan<long>", "Length")]
    [InlineData("ImmutableArray<C>", "Length")]
    [InlineData("List<int>", "Count")]
    [InlineData("HashSet<byte>", "Count")]
    [InlineData("Dictionary<long>", "Count")]
    [InlineData("ImmutableList<C>", "Count")]
    public async Task InsertInlineForSnippetForCommonTypesWithLengthOrCountPropertyTest(string type, string propertyName)
    {
        await VerifySnippetAsync($$"""
            using System;
            using System.Collections.Generic;
            using System.Collections.Immutable;
            
            public class C
            {
                void M({{type}} type)
                {
                    type.$$
                }
            }
            """, $$"""
            using System;
            using System.Collections.Generic;
            using System.Collections.Immutable;
 
            public class C
            {
                void M({{type}} type)
                {
                    for (int {|0:i|} = 0; {|0:i|} < type.{{propertyName}}; {|0:i|}++)
                    {
                        $$
                    }
                }
            }
            """,
            referenceAssemblies: ReferenceAssemblies.Net.Net80);
    }
 
    [Theory]
    [CombinatorialData]
    public async Task InsertInlineForSnippetForTypeWithAccessibleLengthOrCountPropertyTest(
        [CombinatorialValues("public", "internal", "protected internal")] string propertyAccessibility,
        [CombinatorialValues("Length", "Count")] string propertyName)
    {
        await VerifySnippetAsync($$"""
            class C
            {
                void M(MyType type)
                {
                    type.$$
                }
            }
 
            public class MyType
            {
                {{propertyAccessibility}} int {{propertyName}} { get; }
            }
            """, $$"""
            class C
            {
                void M(MyType type)
                {
                    for (int {|0:i|} = 0; {|0:i|} < type.{{propertyName}}; {|0:i|}++)
                    {
                        $$
                    }
                }
            }
 
            public class MyType
            {
                {{propertyAccessibility}} int {{propertyName}} { get; }
            }
            """);
    }
 
    [Theory]
    [CombinatorialData]
    public async Task InsertInlineForSnippetForTypeWithAccessibleLengthOrCountPropertyGetterTest(
        [CombinatorialValues("", "internal", "protected internal")] string getterAccessibility,
        [CombinatorialValues("Length", "Count")] string propertyName)
    {
        await VerifySnippetAsync($$"""
            class C
            {
                void M(MyType type)
                {
                    type.$$
                }
            }
 
            public class MyType
            {
                public int {{propertyName}} { {{getterAccessibility}} get; }
            }
            """, $$"""
            class C
            {
                void M(MyType type)
                {
                    for (int {|0:i|} = 0; {|0:i|} < type.{{propertyName}}; {|0:i|}++)
                    {
                        $$
                    }
                }
            }
 
            public class MyType
            {
                public int {{propertyName}} { {{getterAccessibility}} get; }
            }
            """);
    }
 
    [Theory]
    [CombinatorialData]
    public async Task InsertInlineForSnippetForTypesWithLengthOrCountPropertyOfDifferentIntegerTypesTest(
        [CombinatorialValues("byte", "sbyte", "short", "ushort", "int", "uint", "long", "ulong", "nint", "nuint")] string integerType,
        [CombinatorialValues("Length", "Count")] string propertyName)
    {
        await VerifySnippetAsync($$"""
            class C
            {
                void M(MyType type)
                {
                    type.$$
                }
            }
 
            public class MyType
            {
                public {{integerType}} {{propertyName}} { get; }
            }
            """, $$"""
            class C
            {
                void M(MyType type)
                {
                    for ({{integerType}} {|0:i|} = 0; {|0:i|} < type.{{propertyName}}; {|0:i|}++)
                    {
                        $$
                    }
                }
            }
 
            public class MyType
            {
                public {{integerType}} {{propertyName}} { get; }
            }
            """);
    }
 
    [Theory]
    [InlineData("Length")]
    [InlineData("Count")]
    public async Task InsertInlineForSnippetForTypeWithLengthOrCountPropertyInBaseClassTest(string propertyName)
    {
        await VerifySnippetAsync($$"""
            class C
            {
                void M(MyType type)
                {
                    type.$$
                }
            }
 
            public class MyType : MyTypeBase
            {
            }
 
            public class MyTypeBase
            {
                public int {{propertyName}} { get; }
            }
            """, $$"""
            class C
            {
                void M(MyType type)
                {
                    for (int {|0:i|} = 0; {|0:i|} < type.{{propertyName}}; {|0:i|}++)
                    {
                        $$
                    }
                }
            }
 
            public class MyType : MyTypeBase
            {
            }
            
            public class MyTypeBase
            {
                public int {{propertyName}} { get; }
            }
            """);
    }
 
    [Theory]
    [InlineData("Length")]
    [InlineData("Count")]
    public async Task NoInlineForSnippetWhenLengthOrCountPropertyHasNoGetterTest(string propertyName)
    {
        await VerifySnippetIsAbsentAsync($$"""
            class C
            {
                void M(MyType type)
                {
                    type.$$
                }
            }
            
            public class MyType
            {
                public int {{propertyName}} { set { } }
            }
            """);
    }
 
    [Theory]
    [CombinatorialData]
    public async Task NoInlineForSnippetForInaccessibleLengthPropertyTest(
        [CombinatorialValues("private", "protected", "private protected")] string propertyAccessibility,
        [CombinatorialValues("Length", "Count")] string propertyName)
    {
        await VerifySnippetIsAbsentAsync($$"""
            class C
            {
                void M(MyType type)
                {
                    type.$$
                }
            }
            
            public class MyType
            {
                {{propertyAccessibility}} int {{propertyName}} { get; }
            }
            """);
    }
 
    [Theory]
    [CombinatorialData]
    public async Task NoInlineForSnippetForInaccessibleLengthOrCountPropertyGetterTest(
        [CombinatorialValues("private", "protected", "private protected")] string getterAccessibility,
        [CombinatorialValues("Length", "Count")] string propertyName)
    {
        await VerifySnippetIsAbsentAsync($$"""
            class C
            {
                void M(MyType type)
                {
                    type.$$
                }
            }
            
            public class MyType
            {
                public int {{propertyName}} { {{getterAccessibility}} get; }
            }
            """);
    }
 
    [Theory]
    [CombinatorialData]
    public async Task NoInlineForSnippetForLengthPropertyOfIncorrectTypeTest(
        [CombinatorialValues("object", "string", "System.DateTime", "System.Action")] string notIntegerType,
        [CombinatorialValues("Length", "Count")] string propertyName)
    {
        await VerifySnippetIsAbsentAsync($$"""
            class C
            {
                void M(MyType type)
                {
                    type.$$
                }
            }
            
            public class MyType
            {
                public {{notIntegerType}} {{propertyName}} { get; }
            }
            """);
    }
 
    [Fact]
    public async Task NoInlineForSnippetForTypeWithBothLengthAndCountPropertyTest()
    {
        await VerifySnippetIsAbsentAsync("""
            class C
            {
                void M(MyType type)
                {
                    type.$$
                }
            }
            
            public class MyType
            {
                public int Length { get; }
                public int Count { get; }
            }
            """);
    }
 
    [Theory]
    [InlineData("MyType")]
    [MemberData(nameof(CommonSnippetTestData.IntegerTypes), MemberType = typeof(CommonSnippetTestData))]
    public async Task NoInlineForSnippetForTypeItselfTest(string validTypes)
    {
        await VerifySnippetIsAbsentAsync($$"""
            class C
            {
                void M()
                {
                    {{validTypes}}.$$
                }
            }
 
            class MyType
            {
                public int Count => 0;
            }
            """);
    }
 
    [Theory]
    [InlineData("MyType")]
    [MemberData(nameof(CommonSnippetTestData.IntegerTypes), MemberType = typeof(CommonSnippetTestData))]
    public async Task NoInlineForSnippetForTypeItselfTest_Parenthesized(string validTypes)
    {
        await VerifySnippetIsAbsentAsync($$"""
            class C
            {
                void M()
                {
                    ({{validTypes}}).$$
                }
            }
 
            class MyType
            {
                public int Count => 0;
            }
            """);
    }
 
    [Theory]
    [InlineData("MyType")]
    [MemberData(nameof(CommonSnippetTestData.IntegerTypes), MemberType = typeof(CommonSnippetTestData))]
    public async Task NoInlineForSnippetForTypeItselfTest_BeforeContextualKeyword(string validTypes)
    {
        await VerifySnippetIsAbsentAsync($$"""
            using System.Threading.Tasks;
 
            class C
            {
                async void M()
                {
                    {{validTypes}}.$$
                    await Task.Delay(10);
                }
            }
 
            class MyType
            {
                public int Count => 0;
            }
            """);
    }
 
    [Fact]
    public async Task InsertInlineForSnippetForVariableNamedLikeTypeTest()
    {
        await VerifySnippetAsync("""
            class C
            {
                void M()
                {
                    MyType MyType = default;
                    MyType.$$
                }
            }
 
            class MyType
            {
                public int Length => 0;
            }
            """, """
            class C
            {
                void M()
                {
                    MyType MyType = default;
                    for (int {|0:i|} = 0; {|0:i|} < MyType.Length; {|0:i|}++)
                    {
                        $$
                    }
                }
            }
 
            class MyType
            {
                public int Length => 0;
            }
            """);
    }
}