File: Options\SymbolNamesWithValueOptionTests.cs
Web Access
Project: src\src\RoslynAnalyzers\Utilities.UnitTests\Analyzer.Utilities.UnitTests.csproj (Analyzer.Utilities.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;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Text;
using Test.Utilities;
using Xunit;
 
namespace Analyzer.Utilities.UnitTests.Options
{
    public sealed class SymbolNamesWithValueOptionTests
    {
        [Fact]
        public void WhenNoSymbolNames_ReturnsEmpty()
        {
            // Arrange & act
            var options = SymbolNamesWithValueOption<Unit>.Create(ImmutableArray<string>.Empty, GetCompilation(), null, static name => new SymbolNamesWithValueOption<Unit>.NameParts(name, Unit.Default));
 
            // Assert
            Assert.Equal(SymbolNamesWithValueOption<Unit>.Empty, options);
        }
 
        [Fact]
        public void SymbolNamePartsFuncIsCalledForEachSymbol()
        {
            // Arrange
            var callCount = 0;
            var symbolNames = ImmutableArray.Create("a", "b");
            SymbolNamesWithValueOption<Unit>.NameParts func(string symbolName)
            {
                if (symbolNames.Contains(symbolName))
                {
                    callCount++;
                }
 
                return new SymbolNamesWithValueOption<Unit>.NameParts(symbolName, Unit.Default);
            }
 
            // Act
            SymbolNamesWithValueOption<Unit>.Create(symbolNames, GetCompilation(), null, func);
 
            // Assert
            Assert.Equal(2, callCount);
        }
 
        [Fact]
        public void UnqualifiedSymbolNamesAreProcessedAsNames()
        {
            // Arrange
            // Note that there is no check for the existence of the member
            var compilation = GetCompilation();
            var symbolNames = ImmutableArray.Create("MyNamespace", "MyClass", "MyMethod()", "MyProperty", "MyEvent", "MyField");
 
            // Act
            var options = SymbolNamesWithValueOption<Unit>.Create(symbolNames, compilation, null, static name => new SymbolNamesWithValueOption<Unit>.NameParts(name, Unit.Default));
 
            // Assert
            Assert.Equal(symbolNames.Length, options.GetTestAccessor().Names.Count);
            Assert.Empty(options.GetTestAccessor().Symbols);
            Assert.Empty(options.GetTestAccessor().WildcardNamesBySymbolKind);
        }
 
        [Fact]
        public void QualifiedSymbolNamesWithoutPrefixAreIgnored()
        {
            // Arrange
            var compilation = GetCompilation(@"
using System;
 
public namespace MyNamespace
{
    public class MyClass
    {
        public int MyField;
        public int MyProperty { get; set; }
        public event EventHandler<EventArgs> MyEvent;
        public void MyMethod() {}
    }
}");
            var symbolNames = ImmutableArray.Create(
                "MyNamespace.MySubNamespace",
                "MyNamespace.MyClass",
                "MyNamespace.MyClass.MyField",
                "MyNamespace.MyClass.MyProperty",
                "MyNamespace.MyClass.MyEvent",
                "MyNamespace.MyClass.MyMethod()");
 
            // Act
            var options = SymbolNamesWithValueOption<Unit>.Create(symbolNames, compilation, null, static name => new SymbolNamesWithValueOption<Unit>.NameParts(name, Unit.Default));
 
            // Assert
            Assert.Empty(options.GetTestAccessor().Names);
            Assert.Empty(options.GetTestAccessor().Symbols);
            Assert.Empty(options.GetTestAccessor().WildcardNamesBySymbolKind);
        }
 
        [Fact]
        public void QualifiedSymbolNamesWithPrefixAreProcessedAsSymbols()
        {
            // Arrange
            var compilation = GetCompilation(@"
using System;
 
public namespace MyNamespace
{
    public class MyClass
    {
        public int MyField;
        public int MyProperty { get; set; }
        public event EventHandler<EventArgs> MyEvent;
        public void MyMethod() {}
    }
}");
            var symbolNames = ImmutableArray.Create(
                "N:MyNamespace",
                "T:MyNamespace.MyClass",
                "F:MyNamespace.MyClass.MyField",
                "P:MyNamespace.MyClass.MyProperty",
                "E:MyNamespace.MyClass.MyEvent",
                "M:MyNamespace.MyClass.MyMethod()");
 
            // Act
            var options = SymbolNamesWithValueOption<Unit>.Create(symbolNames, compilation, null, static name => new SymbolNamesWithValueOption<Unit>.NameParts(name, Unit.Default));
 
            // Assert
            Assert.Empty(options.GetTestAccessor().Names);
            Assert.Equal(symbolNames.Length, options.GetTestAccessor().Symbols.Count);
            Assert.Empty(options.GetTestAccessor().WildcardNamesBySymbolKind);
        }
 
        [Fact]
        public void UnfoundQualifiedSymbolNamesWithPrefixAreExcluded()
        {
            // Arrange
            var compilation = GetCompilation();
            var symbolNames = ImmutableArray.Create(
                "N:MyNamespace",
                "T:MyNamespace.MyClass",
                "F:MyNamespace.MyClass.MyField",
                "P:MyNamespace.MyClass.MyProperty",
                "E:MyNamespace.MyClass.MyEvent",
                "M:MyNamespace.MyClass.MyMethod()");
 
            // Act
            var options = SymbolNamesWithValueOption<Unit>.Create(symbolNames, compilation, null, static name => new SymbolNamesWithValueOption<Unit>.NameParts(name, Unit.Default));
 
            // Assert
            Assert.Empty(options.GetTestAccessor().Names);
            Assert.Empty(options.GetTestAccessor().Symbols);
            Assert.Empty(options.GetTestAccessor().WildcardNamesBySymbolKind);
        }
 
        [Fact]
        public void UnsupportedWildcardConstructionsAreIgnored()
        {
            // Arrange
            var compilation = GetCompilation();
            var symbolNames = ImmutableArray.Create(
                "*",        // only wildcard symbol
                "*a",       // wildcard symbol is not last
                "a*a",      // wildcard symbol is not last
                "*a*");     // more than one wildcard symbol
 
            // Act
            var options = SymbolNamesWithValueOption<Unit>.Create(symbolNames, compilation, null, static name => new SymbolNamesWithValueOption<Unit>.NameParts(name, Unit.Default));
 
            // Assert
            Assert.Empty(options.GetTestAccessor().Names);
            Assert.Empty(options.GetTestAccessor().Symbols);
            Assert.Empty(options.GetTestAccessor().WildcardNamesBySymbolKind);
        }
 
        [Fact]
        public void WildcardConstructionsWithoutPrefixAreCorrectlyClassified()
        {
            // Arrange
            var compilation = GetCompilation();
            var symbolNames = ImmutableArray.Create(
                "MyNamespace*",
                "MyNamespace.MyClass*",
                "MyNamespace.MyClass.MyField*",
                "MyNamespace.MyClass.MyProperty*",
                "MyNamespace.MyClass.MyEvent*",
                "MyNamespace.MyClass.MyMethod(*");
 
            // Act
            var options = SymbolNamesWithValueOption<Unit>.Create(symbolNames, compilation, null, static name => new SymbolNamesWithValueOption<Unit>.NameParts(name, Unit.Default));
 
            // Assert
            Assert.Empty(options.GetTestAccessor().Names);
            Assert.Empty(options.GetTestAccessor().Symbols);
            Assert.Single(options.GetTestAccessor().WildcardNamesBySymbolKind);
            Assert.Equal(symbolNames.Length, options.GetTestAccessor().WildcardNamesBySymbolKind[SymbolNamesWithValueOption<Unit>.AllKinds].Count);
            Assert.True(options.GetTestAccessor().WildcardNamesBySymbolKind[SymbolNamesWithValueOption<Unit>.AllKinds].ContainsKey("MyNamespace"));
            Assert.True(options.GetTestAccessor().WildcardNamesBySymbolKind[SymbolNamesWithValueOption<Unit>.AllKinds].ContainsKey("MyNamespace.MyClass"));
            Assert.True(options.GetTestAccessor().WildcardNamesBySymbolKind[SymbolNamesWithValueOption<Unit>.AllKinds].ContainsKey("MyNamespace.MyClass.MyField"));
            Assert.True(options.GetTestAccessor().WildcardNamesBySymbolKind[SymbolNamesWithValueOption<Unit>.AllKinds].ContainsKey("MyNamespace.MyClass.MyProperty"));
            Assert.True(options.GetTestAccessor().WildcardNamesBySymbolKind[SymbolNamesWithValueOption<Unit>.AllKinds].ContainsKey("MyNamespace.MyClass.MyEvent"));
            Assert.True(options.GetTestAccessor().WildcardNamesBySymbolKind[SymbolNamesWithValueOption<Unit>.AllKinds].ContainsKey("MyNamespace.MyClass.MyMethod("));
        }
 
        [Fact]
        public void WildcardConstructionsWithPrefixAreCorrectlyClassified()
        {
            // Arrange
            var compilation = GetCompilation();
            var symbolNames = ImmutableArray.Create(
                "N:MyNamespace*",
                "T:MyNamespace.MyClass*",
                "F:MyNamespace.MyClass.MyField*",
                "P:MyNamespace.MyClass.MyProperty*",
                "E:MyNamespace.MyClass.MyEvent*",
                "M:MyNamespace.MyClass.MyMethod(*");
 
            // Act
            var options = SymbolNamesWithValueOption<Unit>.Create(symbolNames, compilation, null, static name => new SymbolNamesWithValueOption<Unit>.NameParts(name, Unit.Default));
 
            // Assert
            Assert.Empty(options.GetTestAccessor().Names);
            Assert.Empty(options.GetTestAccessor().Symbols);
            Assert.Equal(symbolNames.Length, options.GetTestAccessor().WildcardNamesBySymbolKind.Count);
            Assert.True(options.GetTestAccessor().WildcardNamesBySymbolKind[SymbolKind.Namespace].ContainsKey("MyNamespace"));
            Assert.True(options.GetTestAccessor().WildcardNamesBySymbolKind[SymbolKind.NamedType].ContainsKey("MyNamespace.MyClass"));
            Assert.True(options.GetTestAccessor().WildcardNamesBySymbolKind[SymbolKind.Field].ContainsKey("MyNamespace.MyClass.MyField"));
            Assert.True(options.GetTestAccessor().WildcardNamesBySymbolKind[SymbolKind.Property].ContainsKey("MyNamespace.MyClass.MyProperty"));
            Assert.True(options.GetTestAccessor().WildcardNamesBySymbolKind[SymbolKind.Event].ContainsKey("MyNamespace.MyClass.MyEvent"));
            Assert.True(options.GetTestAccessor().WildcardNamesBySymbolKind[SymbolKind.Method].ContainsKey("MyNamespace.MyClass.MyMethod("));
        }
 
        [Fact]
        public void ValueCanBeAssociatedWithAllSymbolNames()
        {
            // Arrange
            var compilation = GetCompilation(@"
using System;
 
public namespace MyNamespace
{
    public class MyClass {}
}");
            var symbolNames = ImmutableArray.Create(
                "MyClass->SomeValue1",
                "T:MyNamespace.MyClass->SomeValue2",
                "MyClass*->SomeValue3",
                "T:MyClass*->SomeValue4");
 
            var namedTypeSymbol = (INamedTypeSymbol)compilation.GetSymbolsWithName("MyClass").Single();
 
            // Act
            var options = SymbolNamesWithValueOption<string>.Create(symbolNames, compilation, null,
                getSymbolNamePartsFunc: symbolName =>
                {
                    var split = symbolName.Split(new[] { "->" }, StringSplitOptions.RemoveEmptyEntries);
                    return new SymbolNamesWithValueOption<string>.NameParts(split[0], split[1]);
                });
 
            // Assert
            Assert.Single(options.GetTestAccessor().Names);
            Assert.Equal("SomeValue1", options.GetTestAccessor().Names["MyClass"]);
            Assert.Single(options.GetTestAccessor().Symbols);
            Assert.Equal("SomeValue2", options.GetTestAccessor().Symbols[namedTypeSymbol]);
            Assert.Equal(2, options.GetTestAccessor().WildcardNamesBySymbolKind.Count);
            Assert.Single(options.GetTestAccessor().WildcardNamesBySymbolKind[SymbolNamesWithValueOption<Unit>.AllKinds]);
            Assert.Equal("SomeValue3", options.GetTestAccessor().WildcardNamesBySymbolKind[SymbolNamesWithValueOption<Unit>.AllKinds]["MyClass"]);
            Assert.Single(options.GetTestAccessor().WildcardNamesBySymbolKind[SymbolKind.NamedType]);
            Assert.Equal("SomeValue4", options.GetTestAccessor().WildcardNamesBySymbolKind[SymbolKind.NamedType]["MyClass"]);
        }
 
        [Fact]
        [WorkItem(1242125, "https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1242125")]
        public void FastPathDoesNotCache()
        {
            var compilation = GetCompilation(@"
using System;
 
public namespace MyNamespace
{
    public class MyClass {}
}");
 
            var namedTypeSymbol = (INamedTypeSymbol)compilation.GetSymbolsWithName("MyClass").Single();
 
            var options = SymbolNamesWithValueOption<Unit>.Empty;
 
            // Check for containment
            var contained = options.Contains(namedTypeSymbol);
            Assert.False(contained);
            Assert.Empty(options.GetTestAccessor().WildcardMatchResult);
            Assert.Empty(options.GetTestAccessor().SymbolToDeclarationId);
        }
 
        [Theory]
        // Symbol name
        [InlineData("My*", "MyCompany")]
        [InlineData("My*", "MyOuterClass")]
        [InlineData("My*", "MyInnerClass")]
        [InlineData("My*", "MyField")]
        [InlineData("My*", "MyProperty")]
        [InlineData("My*", "MyEvent")]
        [InlineData("My*", "MyMethod")]
        [InlineData("My*", "MyMethod2")]
        // Fully qualified name with prefix
        [InlineData("N:MyCompany*", "MyCompany")]
        [InlineData("N:MyCompany.MyProduct*", "MyProduct")]
        [InlineData("N:MyCompany.MyProduct.MyFeature*", "MyFeature")]
        [InlineData("T:MyCompany.MyProduct.MyFeature.MyOuterClass*", "MyOuterClass")]
        [InlineData("T:MyCompany.MyProduct.MyFeature.MyOuterClass.MyInnerClass*", "MyInnerClass")]
        [InlineData("F:MyCompany.MyProduct.MyFeature.MyOuterClass.MyInnerClass.MyField*", "MyField")]
        [InlineData("M:MyCompany.MyProduct.MyFeature.MyOuterClass.MyInnerClass..ctor*", ".ctor")]
        [InlineData("P:MyCompany.MyProduct.MyFeature.MyOuterClass.MyInnerClass.MyProperty*", "MyProperty")]
        [InlineData("P:MyCompany.MyProduct.MyFeature.MyOuterClass.MyInnerClass.Item*", "this[]")]
        [InlineData("E:MyCompany.MyProduct.MyFeature.MyOuterClass.MyInnerClass.MyEvent*", "MyEvent")]
        [InlineData("M:MyCompany.MyProduct.MyFeature.MyOuterClass.MyInnerClass.MyMethod*", "MyMethod")]
        [InlineData("M:MyCompany.MyProduct.MyFeature.MyOuterClass.MyInnerClass.MyMethod2(System.Str*", "MyMethod2")]
        // Fully qualified name without prefix
        [InlineData("MyCompany*", "MyCompany")]
        [InlineData("MyCompany.MyProduct*", "MyProduct")]
        [InlineData("MyCompany.MyProduct.MyFeature*", "MyFeature")]
        [InlineData("MyCompany.MyProduct.MyFeature.MyOuterClass*", "MyOuterClass")]
        [InlineData("MyCompany.MyProduct.MyFeature.MyOuterClass.MyInnerClass*", "MyInnerClass")]
        [InlineData("MyCompany.MyProduct.MyFeature.MyOuterClass.MyInnerClass.MyField*", "MyField")]
        [InlineData("MyCompany.MyProduct.MyFeature.MyOuterClass.MyInnerClass..ctor*", ".ctor")]
        [InlineData("MyCompany.MyProduct.MyFeature.MyOuterClass.MyInnerClass.MyProperty*", "MyProperty")]
        [InlineData("MyCompany.MyProduct.MyFeature.MyOuterClass.MyInnerClass.Item*", "this[]")]
        [InlineData("MyCompany.MyProduct.MyFeature.MyOuterClass.MyInnerClass.MyEvent*", "MyEvent")]
        [InlineData("MyCompany.MyProduct.MyFeature.MyOuterClass.MyInnerClass.MyMethod*", "MyMethod")]
        [InlineData("MyCompany.MyProduct.MyFeature.MyOuterClass.MyInnerClass.MyMethod2(System.Str*", "MyMethod2")]
        public void WildcardMatch(string patternName, string symbolName)
        {
            // Arrange
            var compilation = GetCompilation(@"
public namespace MyCompany.MyProduct.MyFeature
{
    public class MyOuterClass
    {
        public class MyInnerClass
        {
            public int MyField;
            public MyInnerClass() {}
            public MyInnerClass(int i) {}
            public int MyProperty { get; set; }
            public int this[]
            {
                get { return 42; }
                set {}
            }
            public int this[string s]
            {
                get { return 42; }
                set {}
            }
            public event EventHandler<EventArgs> MyEvent;
            public void MyMethod() {}
            public void MyMethod2(string s) {}
        }
    }
}");
            var symbolNames = ImmutableArray.Create(patternName);
            var symbol = FindSymbol(compilation, symbolName);
            var options = SymbolNamesWithValueOption<Unit>.Create(symbolNames, compilation, null, static name => new SymbolNamesWithValueOption<Unit>.NameParts(name, Unit.Default));
 
            // Act
            var isFound = options.Contains(symbol);
 
            // Assert
            Assert.True(isFound);
            Assert.True(options.GetTestAccessor().WildcardMatchResult.ContainsKey(symbol));
 
            static ISymbol FindSymbol(Compilation compilation, string symbolName)
            {
                var innerClassSymbol = (INamedTypeSymbol)compilation.GetSymbolsWithName("MyInnerClass", SymbolFilter.Type).Single();
 
                var currentType = innerClassSymbol;
                while (currentType != null)
                {
                    if (currentType.Name == symbolName)
                    {
                        return currentType;
                    }
 
                    currentType = currentType.ContainingType;
                }
 
                var currentNamespace = innerClassSymbol.ContainingNamespace;
                while (currentNamespace != null)
                {
                    if (currentNamespace.Name == symbolName)
                    {
                        return currentNamespace;
                    }
 
                    currentNamespace = currentNamespace.ContainingNamespace;
                }
 
                foreach (var member in innerClassSymbol.GetMembers())
                {
                    if (member.Name == symbolName)
                    {
                        return member;
                    }
                }
 
                throw new InvalidOperationException("Cannot find symbol name: " + symbolName);
            }
        }
 
        private static Compilation GetCompilation(params string[] sources)
        {
            var cancellationToken = CancellationToken.None;
            const string TestProjectName = "SymbolNamesTestProject";
            var projectId = ProjectId.CreateNewId(debugName: TestProjectName);
 
            var references = Task.Run(() => AdditionalMetadataReferences.Default.ResolveAsync(LanguageNames.CSharp, cancellationToken)).GetAwaiter().GetResult();
 
#pragma warning disable CA2000 // Dispose objects before losing scope - Current solution/project takes the dispose ownership of the created AdhocWorkspace
            var project = new AdhocWorkspace().CurrentSolution
#pragma warning restore CA2000 // Dispose objects before losing scope
                .AddProject(projectId, TestProjectName, TestProjectName, LanguageNames.CSharp)
                .AddMetadataReferences(projectId, references)
                .WithProjectCompilationOptions(projectId, new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary))
                .WithProjectParseOptions(projectId, new CSharpParseOptions())
                .GetProject(projectId)!;
 
            int count = 0;
            foreach (var source in sources)
            {
                string newFileName = $"Test{count++}.cs";
                DocumentId documentId = DocumentId.CreateNewId(projectId, debugName: newFileName);
                project = project.AddDocument(newFileName, SourceText.From(source)).Project;
            }
 
            Assert.True(project.SupportsCompilation);
            return project.GetCompilationAsync(cancellationToken).Result!;
        }
    }
}