File: ValueTracking\CSharpValueTrackingTests.cs
Web Access
Project: src\src\EditorFeatures\Test\Microsoft.CodeAnalysis.EditorFeatures.UnitTests.csproj (Microsoft.CodeAnalysis.EditorFeatures.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.Linq;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Remote.Testing;
using Microsoft.CodeAnalysis.Test.Utilities;
using Xunit;
 
namespace Microsoft.CodeAnalysis.Editor.UnitTests.ValueTracking;
 
[UseExportProvider]
public class CSharpValueTrackingTests : AbstractBaseValueTrackingTests
{
    protected override TestWorkspace CreateWorkspace(string code, TestComposition composition)
        => TestWorkspace.CreateCSharp(code, composition: composition);
 
    [Theory, CombinatorialData]
    public async Task TestProperty(TestHost testHost)
    {
        var code =
@"
class C
{
    public string $$S { get; set; } = """";
 
    public void SetS(string s)
    {
        S = s;
    }
 
    public string GetS() => S;
}
";
        using var workspace = CreateWorkspace(code, testHost);
 
        //
        // property S 
        //  |> S = s [Code.cs:7]
        //  |> public string S { get; set; } [Code.cs:3]
        //
        await ValidateItemsAsync(
            workspace,
            itemInfo: new[]
            {
                (7, "s"),
                (3, "public string S { get; set; } = \"\";"),
            });
    }
 
    [Theory, CombinatorialData]
    public async Task TestPropertyWithThis(TestHost testHost)
    {
        var code =
@"
class C
{
    public string $$S { get; set; } = """";
 
    public void SetS(string s)
    {
        this.S = s;
    }
 
    public string GetS() => this.S;
}
";
        using var workspace = CreateWorkspace(code, testHost);
 
        //
        // property S 
        //  |> S = s [Code.cs:7]
        //  |> public string S { get; set; } [Code.cs:3]
        //
        await ValidateItemsAsync(
            workspace,
            itemInfo: new[]
            {
                (7, "s"),
                (3, "public string S { get; set; } = \"\";"),
            });
    }
 
    [Theory, CombinatorialData]
    public async Task TestField(TestHost testHost)
    {
        var code =
@"
class C
{
    private string $$_s = """";
 
    public void SetS(string s)
    {
        _s = s;
    }
 
    public string GetS() => _s;
}";
        using var workspace = CreateWorkspace(code, testHost);
        var initialItems = await GetTrackedItemsAsync(workspace);
 
        //
        // field _s 
        //  |> _s = s [Code.cs:7]
        //  |> string _s = "" [Code.cs:3]
        //
        await ValidateItemsAsync(
            workspace,
            itemInfo: new[]
            {
                (7, "s"),
                (3, "_s = \"\"")
            });
    }
 
    [Theory, CombinatorialData]
    public async Task TestFieldWithThis(TestHost testHost)
    {
        var code =
@"
class C
{
    private string $$_s = """";
 
    public void SetS(string s)
    {
        this._s = s;
    }
 
    public string GetS() => this._s;
}";
        using var workspace = CreateWorkspace(code, testHost);
        var initialItems = await GetTrackedItemsAsync(workspace);
 
        //
        // field _s 
        //  |> this._s = s [Code.cs:7]
        //  |> string _s = "" [Code.cs:3]
        //
        await ValidateItemsAsync(
            workspace,
            itemInfo: new[]
            {
                (7, "s"),
                (3, "_s = \"\"")
            });
    }
 
    [Theory, CombinatorialData]
    public async Task TestLocal(TestHost testHost)
    {
        var code =
@"
class C
{
    public int Add(int x, int y)
    {
        var $$z = x;
        z += y;
        return z;
    }
}";
        using var workspace = CreateWorkspace(code, testHost);
        var initialItems = await GetTrackedItemsAsync(workspace);
 
        //
        // local variable z
        //  |> z += y [Code.cs:6]
        //  |> var z = x [Code.cs:5]
        //
        Assert.Equal(2, initialItems.Length);
        ValidateItem(initialItems[0], 6);
        ValidateItem(initialItems[1], 5);
    }
 
    [Theory, CombinatorialData]
    public async Task TestParameter(TestHost testHost)
    {
        var code =
@"
class C
{
    public int Add(int $$x, int y)
    {
        x += y;
        return x;
    }
}";
        using var workspace = CreateWorkspace(code, testHost);
        var initialItems = await GetTrackedItemsAsync(workspace);
 
        //
        // parameter x 
        //  |> x += y [Code.cs:5]
        //  |> Add(int x, int y) [Code.cs:3]
        //
        Assert.Equal(2, initialItems.Length);
        ValidateItem(initialItems[0], 5);
        ValidateItem(initialItems[1], 3);
    }
 
    [Theory, CombinatorialData]
    public async Task TestMissingOnMethod(TestHost testHost)
    {
        var code =
@"
class C
{
    public int $$Add(int x, int y)
    {
        x += y;
        return x;
    }
}";
        using var workspace = CreateWorkspace(code, testHost);
        var initialItems = await GetTrackedItemsAsync(workspace);
        Assert.Empty(initialItems);
    }
 
    [Theory, CombinatorialData]
    public async Task TestMissingOnClass(TestHost testHost)
    {
        var code =
@"
class $$C
{
    public int Add(int x, int y)
    {
        x += y;
        return x;
    }
}";
        using var workspace = CreateWorkspace(code, testHost);
        var initialItems = await GetTrackedItemsAsync(workspace);
        Assert.Empty(initialItems);
    }
 
    [Theory, CombinatorialData]
    public async Task TestMissingOnNamespace(TestHost testHost)
    {
        var code =
@"
namespace $$N
{
    class C
    {
        public int Add(int x, int y)
        {
            x += y;
            return x;
        }
    }
}";
        using var workspace = CreateWorkspace(code, testHost);
        var initialItems = await GetTrackedItemsAsync(workspace);
        Assert.Empty(initialItems);
    }
 
    [Theory, CombinatorialData]
    public async Task MethodTracking1(TestHost testHost)
    {
        var code =
@"
class C
{
    public string S { get; set; } = """";
 
    public void SetS(string s)
    {
        S$$ = s;
    }
 
    public string GetS() => S;
}
 
class Other
{
    public void CallS(C c, string str)
    {
        c.SetS(str);
    }
 
    public void CallS(C c)
    {
        CallS(c, CalculateDefault(c));
    }
 
    private string CalculateDefault(C c)
    {
        if (c is null)
        {
            return ""null"";
        }
 
        if (string.IsNullOrEmpty(c.S))
        {
            return ""defaultstring"";
        }
 
        return """";
    }
}
";
        using var workspace = CreateWorkspace(code, testHost);
        var initialItems = await GetTrackedItemsAsync(workspace);
 
        //
        // S = s; [Code.cs:7]
        //  |> S = [|s|] [Code.cs:7]
        //    |> [|c.SetS(str)|]; [Code.cs:17]
        //    |> c.SetS([|str|]); [Code.cs:17]
        //      |> CallS([|c|], CalculateDefault(c)) [Code.cs:22]
        //      |> CallS(c, [|CalculateDefault(c)|]) [Code.cs:22]
        //         |>  return "" [Code.cs:37]
        //         |>  return "defaultstring" [Code.cs:34]
        //         |> return "null" [Code.cs:29]
        // 
        Assert.Equal(1, initialItems.Length);
        ValidateItem(initialItems[0], 7);
 
        var items = await ValidateChildrenAsync(
            workspace,
            initialItems.Single(),
            childInfo: new[]
            {
                (17, "str"), // |> c.SetS([|str|]); [Code.cs:17]
                (17, "c.SetS(str)"), // |> [|c.SetS(str)|]; [Code.cs:17]
            });
 
        // |> [|c.SetS(s)|]; [Code.cs:17]
        await ValidateChildrenEmptyAsync(workspace, items[1]);
 
        // |> c.SetS([|s|]); [Code.cs:17]
        items = await ValidateChildrenAsync(
            workspace,
            items[0],
            childInfo: new[]
            {
                (22, "c" ), // |> CallS([|c|], CalculateDefault(c)) [Code.cs:22]
                (22, "c" ), // |> CallS(c, CalculateDefault([|c|])) [Code.cs:22]
                (22, "CalculateDefault(c)" ), // |> CallS(c, [|CalculateDefault(c)|]) [Code.cs:22]
                (22, "CallS(c, CalculateDefault(c))" ) // |> [|CallS(c, CalculateDefault(c))|] [Code.cs:22]
            });
 
        // |> CallS([|c|], CalculateDefault(c)) [Code.cs:22]
        await ValidateChildrenEmptyAsync(workspace, items[0]);
        // |> CallS(c, CalculateDefault([|c|])) [Code.cs:22]
        await ValidateChildrenEmptyAsync(workspace, items[1]);
        // |> CallS(c, [|CalculateDefault(c)|]) [Code.cs:22]
        await ValidateChildrenEmptyAsync(workspace, items[3]);
 
        // |> CallS(c, [|CalculateDefault(c)|]) [Code.cs:22]
        var children = await ValidateChildrenAsync(
            workspace,
            items[2],
            childInfo: new[]
            {
                (37, "\"\""), // |>  return "" [Code.cs:37]
                (34, "\"defaultstring\""), // |>  return "defaultstring" [Code.cs:34]
                (29, "\"null\""), // |> return "null" [Code.cs:29]
            });
 
        foreach (var child in children)
        {
            await ValidateChildrenEmptyAsync(workspace, child);
        }
    }
 
    [Theory, CombinatorialData]
    public async Task MethodTracking2(TestHost testHost)
    {
        var code =
@"
class C
{
    public string S { get; set; } = """";
 
    public void SetS(string s)
    {
        S$$ = s;
    }
 
    public string GetS() => S;
}
 
class Other
{
    private readonly string _adornment;
    public Other(string adornment)
    {
        _adornment = adornment;
    }
 
    public void CallS(C c, string s)
    {
        c.SetS(s);
    }
 
    public void CallS(C c)
    {
        CallS(c, CalculateDefault(c) + _adornment);
    }
 
    private string CalculateDefault(C c)
    {
        if (c is null)
        {
            return ""null"";
        }
 
        if (string.IsNullOrEmpty(c.S))
        {
            return ""defaultstring"";
        }
 
        return """";
    }
}
 
class Program
{
    public static void Main(string[] args)
    {
        var other = new Other(""some value"");
        var c = new C();
        other.CallS(c);
    }
}
";
        using var workspace = CreateWorkspace(code, testHost);
        var initialItems = await GetTrackedItemsAsync(workspace);
 
        //
        // S = s; [Code.cs:7]
        //  |> S = [|s|] [Code.cs:7]
        //    |> [|c.SetS(s)|]; [Code.cs:23]
        //    |> c.SetS([|s|]); [Code.cs:23]
        //      |> CallS([|c|], CalculateDefault(c) + _adornment) [Code.cs:28]
        //        |> other.CallS([|c|]); [Code.cs:53]
        //      |> CallS(c, CalculateDefault(c) + [|_adornment|]) [Code.cs:28]
        //        |> _adornment = [|adornment|]; [Code.cs:18]
        //          |> var other = new Other([|"some value"|]); [Code.cs:51]
        //      |> CallS(c, CalculateDefault([|c|]) + _adornment) [Code.cs:28]
        //        |> other.CallS([|c|]); [Code.cs:53]
        //      |> CallS(c, [|CalculateDefault(c)|] + _adornment) [Code.cs:28]
        //        |>  return "" [Code.cs:37]
        //        |>  return "defaultstring" [Code.cs:34]
        //        |> return "null" [Code.cs:29]
        //      |> [|CallS(c, CalculateDefault(c) + _adornment)|] [Code.cs:28]
        //
        Assert.Equal(1, initialItems.Length);
        ValidateItem(initialItems[0], 7);
 
        var items = await ValidateChildrenAsync(
            workspace,
            initialItems.Single(),
            childInfo: new[]
            {
                (23, "s"), // |> c.SetS([|s|]); [Code.cs:23]
                (23, "c.SetS(s)"), // |> c.SetS(s); [Code.cs:23]
            });
 
        // |> c.SetS(s); [Code.cs:23]
        await ValidateChildrenEmptyAsync(workspace, items[1]);
 
        // |> c.SetS([|s|]); [Code.cs:23]
        items = await ValidateChildrenAsync(
            workspace,
            items[0],
            childInfo: new[]
            {
                (28, "c" ), // |> CallS([|c|], CalculateDefault(c) + _adornment) [Code.cs:28]
                (28, "_adornment" ), // |> CallS(c, CalculateDefault(c) + [|_adornment|]) [Code.cs:28]
                (28, "c" ), // |> CallS(c, CalculateDefault([|c|]) + _adornment) [Code.cs:28]
                (28, "CalculateDefault(c)" ), // |> CallS(c, [|CalculateDefault|](c) + _adornment) [Code.cs:28]
                (28, "CallS(c, CalculateDefault(c) + _adornment)" ), // |> [|CallS(c, CalculateDefault(c) + _adornment)|] [Code.cs:28]
            });
 
        // |> CallS([|c|], CalculateDefault(c) + _adornment) [Code.cs:28]
        var children = await ValidateChildrenAsync(
            workspace,
            items[0],
            childInfo: new[]
            {
                (53, "other.CallS(c)"), // |> other.CallS([|c|]); [Code.cs:53]
            });
 
        await ValidateChildrenEmptyAsync(workspace, children);
 
        // |> CallS(c, CalculateDefault([|c|]) + _adornment) [Code.cs:28]
        children = await ValidateChildrenAsync(
            workspace,
            items[2],
            childInfo: new[]
            {
                (53, "other.CallS(c)"), // |> other.CallS([|c|]); [Code.cs:53]
            });
 
        await ValidateChildrenEmptyAsync(workspace, children);
 
        // |> CallS(c, CalculateDefault(c) + [|_adornment|]) [Code.cs:28]
        children = await ValidateChildrenAsync(
            workspace,
            items[1],
            childInfo: new[]
            {
                (18, "adornment"), // |> _adornment = [|adornment|] [Code.cs:18]
            });
 
        children = await ValidateChildrenAsync(
            workspace,
            children.Single(),
            childInfo: new[]
            {
                (51, "\"some value\"") // |> var other = new Other([|"some value"|]); [Code.cs:51]
            });
        await ValidateChildrenEmptyAsync(workspace, children);
 
        // |> CallS(c, [|CalculateDefault(c)|] + _adornment) [Code.cs:28]
        children = await ValidateChildrenAsync(
            workspace,
            items[3],
            childInfo: new[]
            {
                (43, "\"\""), // |>  return "" [Code.cs:37]
                (40, "\"defaultstring\""), // |>  return "defaultstring" [Code.cs:34]
                (35, "\"null\""), // |> return "null" [Code.cs:29]
            });
 
        await ValidateChildrenEmptyAsync(workspace, children);
 
        // |> [|CallS(c, CalculateDefault(c) + _adornment)|] [Code.cs:28]
        await ValidateChildrenEmptyAsync(workspace, items[4]);
    }
 
    [Theory, CombinatorialData]
    public async Task MethodTracking3(TestHost testHost)
    {
        var code =
@"
using System.Threading.Tasks;
 
namespace N
{
    class C
    {
        public int Add(int x, int y)
        {
            x += y;
            return x;
        }
 
        public Task<int> AddAsync(int x, int y) => Task.FromResult(Add(x,y));
 
        public async Task<int> Double(int x)
        {
            x = await AddAsync(x, x);
            return $$x;
        }
    }
}";
        //
        //  |> return [|x|] [Code.cs:18]
        //    |> x = await AddAsync([|x|], x) [Code.cs:17]
        //    |> x = await AddAsync(x, [|x|]) [Code.cs:17]
        //    |> x = await [|AddAsync(x, x)|] [Code.cs:17]
        //      |> [|Task.FromResult|](Add(x, y)) [Code.cs:13]
        //      |> Task.FromResult([|Add(x, y)|]) [Code.cs:13]
        //        |> return x [Code.cs:11]
        using var workspace = CreateWorkspace(code, testHost);
        var initialItems = await GetTrackedItemsAsync(workspace);
        Assert.Equal(1, initialItems.Length);
        ValidateItem(initialItems.Single(), 18, "x"); // |> return [|x|] [Code.cs:18]
 
        var children = await ValidateChildrenAsync(
            workspace,
            initialItems.Single(),
            childInfo: new[]
            {
                (17, "x"), // |> x = await AddAsync([|x|], x) [Code.cs:17]
                (17, "x"), // |> x = await AddAsync(x, [|x|]) [Code.cs:17]
                (17, "AddAsync(x, x)") // |> x = await [|AddAsync(x, x)|] [Code.cs:17]
            });
 
        // |> x = await [|AddAsync(x, x)|] [Code.cs:17]
        children = await ValidateChildrenAsync(
            workspace,
            children[2],
            childInfo: new[]
            {
                (13, "x"), // |> Task.FromResult(Add([|x|], y)) [Code.cs:13]
                (13, "y"), // |> Task.FromResult(Add(x, [|y|])) [Code.cs:13]
                (13, "Add(x,y)"),  // |> Task.FromResult([|Add(x, y)|]) [Code.cs:13]
                (13, "Task.FromResult(Add(x,y))"), // |> [|Task.FromResult|](Add(x, y)) [Code.cs:13]
            });
 
        // |> [|Task.FromResult|](Add(x, y)) [Code.cs:13]
        await ValidateChildrenEmptyAsync(workspace, children[3]);
 
        // |> Task.FromResult([|Add(x, y)|]) [Code.cs:13]
        children = await ValidateChildrenAsync(
            workspace,
            children[2],
            childInfo: new[]
            {
                (10, "x") // |> return x [Code.cs:10]
            });
    }
 
    [Theory, CombinatorialData]
    public async Task OutParam(TestHost testHost)
    {
        var code = @"
class C
{
    bool TryConvertInt(object o, out int i)
    {
        if (int.TryParse(o.ToString(), out i))
        {
            return true;
        }
 
        return false;
    }
 
    void M()
    {
        int i = 0;
        object o = ""5"";
 
        if (TryConvertInt(o, out i))
        {
            Console.WriteLine($$i);
        }
        else
        {
            i = 2;
        }
    }
}";
 
        //
        //  |> Console.WriteLine($$i); [Code.cs:20]
        //    |> i = [|2|] [Code.cs:24]
        //    |> if (TryConvertInt(o, out [|i|])) [Code.cs:18]
        //      |> if (int.TryParse(o.ToString(), out [|i|])) [Code.cs:5]
        //    |> int i = 0 [Code.cs:15]
        using var workspace = CreateWorkspace(code, testHost);
        var initialItems = await GetTrackedItemsAsync(workspace);
 
        Assert.Equal(1, initialItems.Length);
 
        // |> Console.WriteLine($$i);[Code.cs:20]
        ValidateItem(initialItems.Single(), 20, "i");
 
        var children = await ValidateChildrenAsync(
            workspace,
            initialItems.Single(),
            childInfo: new[]
            {
                (24, "2"), // |> i = [|2|] [Code.cs:24]
                (18, "i"), // |> if (TryConvertInt(o, out [|i|])) [Code.cs:18]
                (15, "0"), // |> int i = 0 [Code.cs:15]
            });
 
        // |> i = [|2|] [Code.cs:24]
        await ValidateChildrenEmptyAsync(workspace, children[0]);
 
        // |> if (TryConvertInt(o, out [|i|])) [Code.cs:18]
        children = await ValidateChildrenAsync(
            workspace,
            children[1],
            childInfo: new[]
            {
                (5, "i") // |> if (int.TryParse(o.ToString(), out [|i|])) [Code.cs:5]
            });
 
        await ValidateChildrenEmptyAsync(workspace, children.Single());
    }
 
    [Theory, CombinatorialData]
    public async Task TestVariableReferenceStart(TestHost testHost)
    {
        var code =
@"
class Test
{
    public static void M()
    {
        int x = GetM();
        Console.Write(x);
        var y = $$x + 1;
    }
 
    public static int GetM()
    {
        var x = 0;
        return x;
    }
}";
 
        //
        //  |> var y = x + 1; [Code.cs:7]
        //    |> int x = GetM() [Code.cs:5]
        //      |> return x; [Code.cs:13]
        //        |> var x = 0; [Code.cs:12]
        using var workspace = CreateWorkspace(code, testHost);
 
        var items = await ValidateItemsAsync(
            workspace,
            itemInfo: new[]
            {
                (7, "x") // |> var y = [|x|] + 1; [Code.cs:7]
            });
 
        items = await ValidateChildrenAsync(
            workspace,
            items.Single(),
            childInfo: new[]
            {
                (5, "GetM()") // |> int x = [|GetM()|] [Code.cs:5]
            });
 
        items = await ValidateChildrenAsync(
            workspace,
            items.Single(),
            childInfo: new[]
            {
                (13, "x") // |> return [|x|]; [Code.cs:13]
            });
 
        items = await ValidateChildrenAsync(
            workspace,
            items.Single(),
            childInfo: new[]
            {
                (12, "0") // |> var x = [|0|]; [Code.cs:12]
            });
 
        await ValidateChildrenEmptyAsync(workspace, items.Single());
    }
 
    [Theory, CombinatorialData]
    public async Task TestVariableReferenceStart2(TestHost testHost)
    {
        var code =
@"
class Test
{
    public static void M()
    {
        int x = GetM();
        Console.Write($$x);
        var y = x + 1;
    }
 
    public static int GetM()
    {
        var x = 0;
        return x;
    }
}";
 
        //
        //  |> Console.Write(x); [Code.cs:6]
        //    |> int x = GetM() [Code.cs:5]
        //      |> return x; [Code.cs:13]
        //        |> var x = 0; [Code.cs:12]
        using var workspace = CreateWorkspace(code, testHost);
 
        var items = await ValidateItemsAsync(
            workspace,
            itemInfo: new[]
            {
                (6, "x") // |> Console.Write([|x|]); [Code.cs:7]
            });
 
        items = await ValidateChildrenAsync(
            workspace,
            items.Single(),
            childInfo: new[]
            {
                (5, "GetM()") // |> int x = [|GetM()|] [Code.cs:5]
            });
 
        items = await ValidateChildrenAsync(
            workspace,
            items.Single(),
            childInfo: new[]
            {
                (13, "x") // |> return [|x|]; [Code.cs:13]
            });
 
        items = await ValidateChildrenAsync(
            workspace,
            items.Single(),
            childInfo: new[]
            {
                (12, "0") // |> var x = [|0|]; [Code.cs:12]
            });
 
        await ValidateChildrenEmptyAsync(workspace, items.Single());
    }
 
    [Theory, CombinatorialData]
    public async Task TestVariableReferenceStart3(TestHost testHost)
    {
        var code =
@"
class Test
{
    public static void M()
    {
        int x = GetM();
        Console.Write($$x);
        var y = x + 1;
        x += 1;
        Console.Write(x);
        Console.Write(y);
    }
 
    public static int GetM()
    {
        var x = 0;
        return x;
    }
}";
 
        //
        //  |> Console.Write(x); [Code.cs:6]
        //    |> int x = GetM() [Code.cs:5]
        //      |> return x; [Code.cs:13]
        //        |> var x = 0; [Code.cs:12]
        //    |> x += 1 [Code.cs:8]
        using var workspace = CreateWorkspace(code, testHost);
 
        var items = await ValidateItemsAsync(
            workspace,
            itemInfo: new[]
            {
                (6, "x") // |> Console.Write([|x|]); [Code.cs:7]
            });
 
        items = await ValidateChildrenAsync(
            workspace,
            items.Single(),
            childInfo: new[]
            {
                (8, "1"),      // |> x += 1; [Codec.s:8]
                (5, "GetM()"), // |> int x = [|GetM()|] [Code.cs:5]
            });
 
        await ValidateChildrenEmptyAsync(workspace, items[0]);
 
        items = await ValidateChildrenAsync(
            workspace,
            items[1],
            childInfo: new[]
            {
                (16, "x") // |> return [|x|]; [Code.cs:13]
            });
 
        items = await ValidateChildrenAsync(
            workspace,
            items.Single(),
            childInfo: new[]
            {
                (15, "0") // |> var x = [|0|]; [Code.cs:12]
            });
 
        await ValidateChildrenEmptyAsync(workspace, items.Single());
    }
 
    [Theory, CombinatorialData]
    public async Task TestMultipleDeclarators(TestHost testHost)
    {
        var code =
@"
class Test
{
    public static void M()
    {
        int x = GetM(), z = 5;
        Console.Write($$x);
        var y = x + 1 + z;
        x += 1;
        Console.Write(x);
        Console.Write(y);
    }
 
    public static int GetM()
    {
        var x = 0;
        return x;
    }
}";
 
        //
        //  |> Console.Write(x); [Code.cs:6]
        //    |> int x = GetM() [Code.cs:5]
        //      |> return x; [Code.cs:13]
        //        |> var x = 0; [Code.cs:12]
        //    |> x += 1 [Code.cs:8]
        using var workspace = CreateWorkspace(code, testHost);
 
        var items = await ValidateItemsAsync(
            workspace,
            itemInfo: new[]
            {
                (6, "x") // |> Console.Write([|x|]); [Code.cs:7]
            });
 
        items = await ValidateChildrenAsync(
            workspace,
            items.Single(),
            childInfo: new[]
            {
                (8, "1"),      // |> x += 1; [Codec.s:8]
                (5, "GetM()"), // |> int x = [|GetM()|] [Code.cs:5]
            });
 
        await ValidateChildrenEmptyAsync(workspace, items[0]);
 
        items = await ValidateChildrenAsync(
            workspace,
            items[1],
            childInfo: new[]
            {
                (16, "x") // |> return [|x|]; [Code.cs:13]
            });
 
        items = await ValidateChildrenAsync(
            workspace,
            items.Single(),
            childInfo: new[]
            {
                (15, "0") // |> var x = [|0|]; [Code.cs:12]
            });
 
        await ValidateChildrenEmptyAsync(workspace, items.Single());
    }
 
    [Theory, CombinatorialData]
    public async Task TestIndex(TestHost testHost)
    {
        var code =
@"
class Test
{
    public int this[string $$key] => 0;
 
    public int M(Test localTest)
    {
        var assignedVariable = this[""test""];
        System.Console.WriteLine(this[""test""]);
        
        return localTest[""test""];
    }
}";
 
        //
        //  |> public int this[string [|key|]] => 0; [Code.cs:4]
        //    |> return [|localTest|][[|"test"|]]; [Code.cs:10]
        //    |> System.Console.WriteLine(this[[|"test"|]]); [Code.cs:8]
        //    |> var [|assignedVariable = this["test"]|]; [Code.cs:7]
        using var workspace = CreateWorkspace(code, testHost);
 
        var items = await ValidateItemsAsync(
            workspace,
            itemInfo: new[]
            {
                (3, "string key") // |>public int this[[|string key|]] => 0; [Code.cs:4]
            });
 
        items = await ValidateChildrenAsync(
            workspace,
            items.Single(),
            childInfo: new[]
            {
                (10, "localTest"), // return [|localTest|]["test"]; [Code.cs:10] (This is included because it is part of a return statement, and follows same logic as other references for if it is tracked)
                (10, "\"test\""),  // return localTest[[|"test"|]]; [Code.cs:10]
                (8, "\"test\""),   // System.Console.WriteLine(this[[|"test"|]]); [Code.cs:8]
                (7, "\"test\""),   // var assignedVariable = this[[|"test"|]]; [Code.cs:7]
            });
 
        await ValidateChildrenEmptyAsync(workspace, items[0]);
        await ValidateChildrenEmptyAsync(workspace, items[1]);
        await ValidateChildrenEmptyAsync(workspace, items[2]);
        await ValidateChildrenEmptyAsync(workspace, items[3]);
    }
 
    [Theory, CombinatorialData]
    public async Task TestPropertyValue(TestHost testHost)
    {
        var code =
@"
class Test
{
    private int _i;
    public int I 
    {
        get => _i;
        set 
        {
            _i = $$value;
        }
    }
 
    public int M(Test localTest)
    {
        localTest.I = 5;
    }
}";
        //  _i = [|value|]; [Code.cs:9]
        //    |> localTest.I = [|5|]; [Code.cs:15]
        using var workspace = CreateWorkspace(code, testHost);
 
        var items = await ValidateItemsAsync(
            workspace,
            itemInfo: new[]
            {
                (9, "value") // _i = [|value|]; [Code.cs:9]
            });
 
        items = await ValidateChildrenAsync(
            workspace,
            items.Single(),
            childInfo: new[]
            {
                (15, "5") // localTest.I = [|5|]; [Code.cs:15]
            });
 
        await ValidateChildrenEmptyAsync(workspace, items.Single());
    }
}