File: Completion\AbstractArgumentProviderTests`1.cs
Web Access
Project: src\src\EditorFeatures\TestUtilities\Microsoft.CodeAnalysis.EditorFeatures.Test.Utilities.csproj (Microsoft.CodeAnalysis.EditorFeatures.Test.Utilities)
// 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.Completion;
using Microsoft.CodeAnalysis.Editor.UnitTests;
using Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions;
using Microsoft.CodeAnalysis.LanguageService;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.VisualStudio.Composition;
using Roslyn.Test.Utilities;
using Roslyn.Utilities;
using Xunit;
 
namespace Microsoft.CodeAnalysis.Test.Utilities.Completion;
 
[UseExportProvider]
public abstract class AbstractArgumentProviderTests<TWorkspaceFixture> : TestBase
    where TWorkspaceFixture : TestWorkspaceFixture, new()
{
    private static readonly TestComposition s_baseComposition = EditorTestCompositions.EditorFeatures.AddExcludedPartTypes(typeof(ArgumentProvider));
 
    private readonly TestFixtureHelper<TWorkspaceFixture> _fixtureHelper = new();
 
    private ExportProvider? _lazyExportProvider;
 
    protected ExportProvider ExportProvider
        => _lazyExportProvider ??= GetComposition().ExportProviderFactory.CreateExportProvider();
 
    protected virtual TestComposition GetComposition()
        => s_baseComposition.AddParts(GetArgumentProviderType());
 
    private protected ReferenceCountedDisposable<TWorkspaceFixture> GetOrCreateWorkspaceFixture()
        => _fixtureHelper.GetOrCreateFixture();
 
    internal abstract Type GetArgumentProviderType();
 
    protected abstract (SyntaxNode argumentList, ImmutableArray<SyntaxNode> arguments) GetArgumentList(SyntaxToken token);
 
    private protected async Task VerifyDefaultValueAsync(
        string markup,
        string? expectedDefaultValue,
        string? previousDefaultValue = null,
        OptionsCollection? options = null)
    {
        using var workspaceFixture = GetOrCreateWorkspaceFixture();
 
        var workspace = workspaceFixture.Target.GetWorkspace(markup, GetComposition());
        var code = workspaceFixture.Target.Code;
        var position = workspaceFixture.Target.Position;
 
        options?.SetGlobalOptions(workspace.GlobalOptions);
 
        var document = workspaceFixture.Target.UpdateDocument(code, SourceCodeKind.Regular);
 
        var provider = workspace.ExportProvider.GetExportedValues<ArgumentProvider>().Single();
        Assert.IsType(GetArgumentProviderType(), provider);
 
        var root = await document.GetRequiredSyntaxRootAsync(CancellationToken.None);
        var semanticModel = await document.GetRequiredSemanticModelAsync(CancellationToken.None);
        var parameter = GetParameterSymbolInfo(workspace, semanticModel, root, position, CancellationToken.None);
        Contract.ThrowIfNull(parameter);
 
        var context = new ArgumentContext(provider, semanticModel, position, parameter, previousDefaultValue, CancellationToken.None);
        await provider.ProvideArgumentAsync(context);
 
        Assert.Equal(expectedDefaultValue, context.DefaultValue);
    }
 
    private IParameterSymbol GetParameterSymbolInfo(Workspace workspace, SemanticModel semanticModel, SyntaxNode root, int position, CancellationToken cancellationToken)
    {
        var token = root.FindToken(position);
        var (argumentList, arguments) = GetArgumentList(token);
        var symbols = semanticModel.GetSymbolInfo(argumentList.GetRequiredParent(), cancellationToken).GetAllSymbols();
 
        // if more than one symbol is found, filter to only include symbols with a matching number of arguments
        if (symbols.Length > 1)
        {
            symbols = symbols.WhereAsArray(
                symbol =>
                {
                    var parameters = symbol.GetParameters();
                    if (arguments.Length < GetMinimumArgumentCount(parameters))
                        return false;
 
                    if (arguments.Length > GetMaximumArgumentCount(parameters))
                        return false;
 
                    return true;
                });
        }
 
        var symbol = symbols.Single();
        var parameters = symbol.GetParameters();
 
        var syntaxFacts = workspace.Services.GetLanguageServices(root.Language).GetRequiredService<ISyntaxFactsService>();
        Contract.ThrowIfTrue(arguments.Any(argument => syntaxFacts.IsNamedArgument(argument)), "Named arguments are not currently supported by this test.");
        Contract.ThrowIfTrue(parameters.Any(parameter => parameter.IsParams), "'params' parameters are not currently supported by this test.");
 
        var index = arguments.Any()
            ? arguments.IndexOf(arguments.Single(argument => argument.FullSpan.Start <= position && argument.FullSpan.End >= position))
            : 0;
 
        return parameters[index];
 
        // Local functions
        static int GetMinimumArgumentCount(ImmutableArray<IParameterSymbol> parameters)
            => parameters.Count(parameter => !parameter.IsOptional && !parameter.IsParams);
 
        static int GetMaximumArgumentCount(ImmutableArray<IParameterSymbol> parameters)
            => parameters.Any(parameter => parameter.IsParams) ? int.MaxValue : parameters.Length;
    }
}