|
// 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.Shared.TestHooks;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.SolutionCrawler
{
internal partial class SolutionCrawlerRegistrationService
{
internal partial class WorkCoordinator
{
// this is internal only type
internal readonly struct WorkItem
{
// project related workitem
public readonly ProjectId ProjectId;
// document related workitem
public readonly DocumentId? DocumentId;
public readonly string Language;
public readonly InvocationReasons 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<IIncrementalAnalyzer> 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<IIncrementalAnalyzer> GetApplicableAnalyzers(ImmutableArray<IIncrementalAnalyzer> allAnalyzers)
=> SpecificAnalyzers?.Count > 0 ? SpecificAnalyzers.Where(allAnalyzers.Contains) : allAnalyzers;
// retry
public readonly bool IsRetry;
// common
public readonly IAsyncToken AsyncToken;
public bool MustRefresh
{
get
{
// in current design, we need to re-run all incremental analyzer on document open and close
// so that incremental analyzer who only cares about opened document can have a chance to clean up
// its state.
return InvocationReasons.Contains(PredefinedInvocationReasons.DocumentOpened) ||
InvocationReasons.Contains(PredefinedInvocationReasons.DocumentClosed);
}
}
private WorkItem(
DocumentId? documentId,
ProjectId projectId,
string language,
InvocationReasons invocationReasons,
bool isLowPriority,
SyntaxPath? activeMember,
ImmutableHashSet<IIncrementalAnalyzer> 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 WorkItem(DocumentId documentId, string language, InvocationReasons invocationReasons, bool isLowPriority, SyntaxPath? activeMember, IAsyncToken asyncToken)
: this(documentId, documentId.ProjectId, language, invocationReasons, isLowPriority, activeMember, ImmutableHashSet.Create<IIncrementalAnalyzer>(), retry: false, asyncToken)
{
}
public WorkItem(DocumentId documentId, string language, InvocationReasons invocationReasons, bool isLowPriority, IIncrementalAnalyzer? analyzer, IAsyncToken asyncToken)
: this(documentId, documentId.ProjectId, language, invocationReasons, isLowPriority, activeMember: null,
analyzer == null ? ImmutableHashSet.Create<IIncrementalAnalyzer>() : ImmutableHashSet.Create(analyzer),
retry: false, asyncToken)
{
}
public object Key => DocumentId ?? (object)ProjectId;
public WorkItem Retry(IAsyncToken asyncToken)
{
return new WorkItem(
DocumentId, ProjectId, Language, InvocationReasons, IsLowPriority, ActiveMember, SpecificAnalyzers,
retry: true, asyncToken: asyncToken);
}
public WorkItem With(
InvocationReasons invocationReasons, SyntaxPath? currentMember,
ImmutableHashSet<IIncrementalAnalyzer> specificAnalyzers, bool retry, IAsyncToken asyncToken)
{
// dispose old one
AsyncToken.Dispose();
// create new work item
return new WorkItem(
DocumentId, ProjectId, Language,
InvocationReasons.With(invocationReasons),
IsLowPriority,
ActiveMember == currentMember ? currentMember : null,
ComputeNewSpecificAnalyzers(specificAnalyzers, SpecificAnalyzers), IsRetry || retry,
asyncToken);
static ImmutableHashSet<IIncrementalAnalyzer> ComputeNewSpecificAnalyzers(ImmutableHashSet<IIncrementalAnalyzer> specificAnalyzers1, ImmutableHashSet<IIncrementalAnalyzer> specificAnalyzers2)
{
// An empty analyzer list means run all analyzers, so empty always wins over any specific
if (specificAnalyzers1.IsEmpty || specificAnalyzers2.IsEmpty)
{
return ImmutableHashSet<IIncrementalAnalyzer>.Empty;
}
// Otherwise, if both sets have analyzers we use a union of the two
return specificAnalyzers1.Union(specificAnalyzers2);
}
}
public WorkItem WithAsyncToken(IAsyncToken asyncToken)
{
return new WorkItem(
DocumentId, ProjectId, Language, InvocationReasons, IsLowPriority, ActiveMember, SpecificAnalyzers,
retry: false, asyncToken: asyncToken);
}
public WorkItem ToProjectWorkItem(IAsyncToken asyncToken)
{
RoslynDebug.Assert(DocumentId != null);
// create new work item that represents work per project
return new WorkItem(
documentId: null,
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))})";
}
}
}
}
|