File: ExternalAccess\UnitTesting\SolutionCrawler\UnitTestingWorkCoordinator.UnitTestingWorkItem.cs
Web Access
Project: src\src\Features\Core\Portable\Microsoft.CodeAnalysis.Features.csproj (Microsoft.CodeAnalysis.Features)
// 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.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using Microsoft.CodeAnalysis.ExternalAccess.UnitTesting.Api;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.ExternalAccess.UnitTesting.SolutionCrawler;
 
internal partial class UnitTestingSolutionCrawlerRegistrationService
{
    internal partial class UnitTestingWorkCoordinator
    {
        // this is internal only type
        private readonly struct UnitTestingWorkItem
        {
            // project related workitem
            public readonly ProjectId ProjectId;
 
            // document related workitem
            public readonly DocumentId? DocumentId;
            public readonly string Language;
            public readonly UnitTestingInvocationReasons InvocationReasons;
            public readonly bool IsLowPriority;
 
            // extra info
            public readonly SyntaxPath? ActiveMember;
 
            /// <summary>
            /// Non-empty if this work item is intended to be executed only for specific incremental analyzer(s).
            /// Otherwise, the work item is applicable to all relevant incremental analyzers.
            /// </summary>
            public readonly ImmutableHashSet<IUnitTestingIncrementalAnalyzer> SpecificAnalyzers;
 
            /// <summary>
            /// Gets all the applicable analyzers to execute for this work item.
            /// If this work item has any specific analyzer(s), then returns the intersection of <see cref="SpecificAnalyzers"/>
            /// and the given <paramref name="allAnalyzers"/>.
            /// Otherwise, returns <paramref name="allAnalyzers"/>.
            /// </summary>
            public IEnumerable<IUnitTestingIncrementalAnalyzer> GetApplicableAnalyzers(ImmutableArray<IUnitTestingIncrementalAnalyzer> allAnalyzers)
                => SpecificAnalyzers?.Count > 0 ? SpecificAnalyzers.Where(allAnalyzers.Contains) : allAnalyzers;
 
            // retry
            public readonly bool IsRetry;
 
            // common
            public readonly IAsyncToken AsyncToken;
 
            private UnitTestingWorkItem(
                DocumentId? documentId,
                ProjectId projectId,
                string language,
                UnitTestingInvocationReasons invocationReasons,
                bool isLowPriority,
                SyntaxPath? activeMember,
                ImmutableHashSet<IUnitTestingIncrementalAnalyzer> specificAnalyzers,
                bool retry,
                IAsyncToken asyncToken)
            {
                Debug.Assert(documentId == null || documentId.ProjectId == projectId);
 
                DocumentId = documentId;
                ProjectId = projectId;
                Language = language;
                InvocationReasons = invocationReasons;
                IsLowPriority = isLowPriority;
 
                ActiveMember = activeMember;
                SpecificAnalyzers = specificAnalyzers;
 
                IsRetry = retry;
 
                AsyncToken = asyncToken;
            }
 
            public UnitTestingWorkItem(DocumentId documentId, string language, UnitTestingInvocationReasons invocationReasons, bool isLowPriority, SyntaxPath? activeMember, IAsyncToken asyncToken)
                : this(documentId, documentId.ProjectId, language, invocationReasons, isLowPriority, activeMember, [], retry: false, asyncToken)
            {
            }
 
            public UnitTestingWorkItem(DocumentId documentId, string language, UnitTestingInvocationReasons invocationReasons, bool isLowPriority, IUnitTestingIncrementalAnalyzer? analyzer, IAsyncToken asyncToken)
                : this(documentId, documentId.ProjectId, language, invocationReasons, isLowPriority, activeMember: null,
                       analyzer == null ? [] : [analyzer],
                       retry: false, asyncToken)
            {
            }
 
            public object Key => DocumentId ?? (object)ProjectId;
 
            private ImmutableHashSet<IUnitTestingIncrementalAnalyzer> Union(ImmutableHashSet<IUnitTestingIncrementalAnalyzer> analyzers)
            {
                if (analyzers.IsEmpty)
                {
                    return SpecificAnalyzers;
                }
 
                if (SpecificAnalyzers.IsEmpty)
                {
                    return analyzers;
                }
 
                return SpecificAnalyzers.Union(analyzers);
            }
 
            public UnitTestingWorkItem Retry(IAsyncToken asyncToken)
            {
                return new UnitTestingWorkItem(
                    DocumentId, ProjectId, Language, InvocationReasons, IsLowPriority, ActiveMember, SpecificAnalyzers,
                    retry: true, asyncToken: asyncToken);
            }
 
            public UnitTestingWorkItem With(
                UnitTestingInvocationReasons invocationReasons, SyntaxPath? currentMember,
                ImmutableHashSet<IUnitTestingIncrementalAnalyzer> analyzers, bool retry, IAsyncToken asyncToken)
            {
                // dispose old one
                AsyncToken.Dispose();
 
                // create new work item
                return new UnitTestingWorkItem(
                    DocumentId, ProjectId, Language,
                    InvocationReasons.With(invocationReasons),
                    IsLowPriority,
                    ActiveMember == currentMember ? currentMember : null,
                    Union(analyzers), IsRetry || retry,
                    asyncToken);
            }
 
            public UnitTestingWorkItem WithAsyncToken(IAsyncToken asyncToken)
            {
                return new UnitTestingWorkItem(
                    DocumentId, ProjectId, Language, InvocationReasons, IsLowPriority, ActiveMember, SpecificAnalyzers,
                    retry: false, asyncToken: asyncToken);
            }
 
            public UnitTestingWorkItem ToProjectWorkItem(IAsyncToken asyncToken)
            {
                RoslynDebug.Assert(DocumentId != null);
 
                // create new work item that represents work per project
                return new UnitTestingWorkItem(
                    documentId: null,
                    DocumentId.ProjectId,
                    Language,
                    InvocationReasons,
                    IsLowPriority,
                    ActiveMember,
                    SpecificAnalyzers,
                    IsRetry,
                    asyncToken);
            }
 
            public UnitTestingWorkItem With(ImmutableHashSet<IUnitTestingIncrementalAnalyzer> specificAnalyzers, IAsyncToken asyncToken)
            {
                return new UnitTestingWorkItem(DocumentId, ProjectId, Language, InvocationReasons,
                    IsLowPriority, ActiveMember, specificAnalyzers, IsRetry, asyncToken);
            }
 
            public override string ToString()
                => $"{DocumentId?.ToString() ?? ProjectId.ToString()}, ({InvocationReasons}), LowPriority:{IsLowPriority}, ActiveMember:{ActiveMember != null}, Retry:{IsRetry}, ({string.Join("|", SpecificAnalyzers.Select(a => a.GetType().Name))})";
        }
    }
}