diff --git a/azure-pipelines.yml b/azure-pipelines.yml
index 003ac28af6fc3..4aff1d181fdf4 100644
--- a/azure-pipelines.yml
+++ b/azure-pipelines.yml
@@ -118,7 +118,7 @@ jobs:
jobName: Build_Unix_Debug
testArtifactName: Transport_Artifacts_Unix_Debug
configuration: Debug
- vmImageName: 'ubuntu-16.04'
+ vmImageName: 'ubuntu-18.04'
- template: eng/pipelines/test-unix-job.yml
parameters:
@@ -127,7 +127,7 @@ jobs:
buildJobName: Build_Unix_Debug
testArtifactName: Transport_Artifacts_Unix_Debug
configuration: Debug
- testArguments: --testCoreClr --helixQueueName Ubuntu.1604.Amd64.Open
+ testArguments: --testCoreClr --helixQueueName Ubuntu.1804.Amd64.Open
- template: eng/pipelines/test-unix-job-single-machine.yml
parameters:
@@ -137,7 +137,7 @@ jobs:
testArtifactName: Transport_Artifacts_Unix_Debug
configuration: Debug
testArguments: --testCoreClr
- queueName: 'BuildPool.Ubuntu.1604.amd64.Open'
+ queueName: 'BuildPool.Ubuntu.1804.amd64.Open'
- template: eng/pipelines/test-unix-job.yml
parameters:
@@ -201,7 +201,7 @@ jobs:
- job: Correctness_SourceBuild
pool:
name: NetCorePublic-Pool
- queue: BuildPool.Ubuntu.1604.amd64.Open
+ queue: BuildPool.Ubuntu.1804.amd64.Open
timeoutInMinutes: 90
steps:
- template: eng/pipelines/checkout-unix-task.yml
diff --git a/src/EditorFeatures/Core/ReferenceHighlighting/ReferenceHighlightingViewTaggerProvider.cs b/src/EditorFeatures/Core/ReferenceHighlighting/ReferenceHighlightingViewTaggerProvider.cs
index 4f888bfd6ab21..3313cbbff9531 100644
--- a/src/EditorFeatures/Core/ReferenceHighlighting/ReferenceHighlightingViewTaggerProvider.cs
+++ b/src/EditorFeatures/Core/ReferenceHighlighting/ReferenceHighlightingViewTaggerProvider.cs
@@ -53,7 +53,7 @@ public ReferenceHighlightingViewTaggerProvider(
{
}
- protected override TaggerDelay EventChangeDelay => TaggerDelay.Short;
+ protected override TaggerDelay EventChangeDelay => TaggerDelay.Medium;
protected override ITaggerEventSource CreateEventSource(ITextView textView, ITextBuffer subjectBuffer)
{
diff --git a/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerConstants.cs b/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerConstants.cs
index 4e65651ba0120..8ace9eb3e26a0 100644
--- a/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerConstants.cs
+++ b/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerConstants.cs
@@ -29,19 +29,12 @@ internal static TimeSpan ComputeTimeDelay(this TaggerDelay behavior, ITextBuffer
}
internal static TimeSpan ComputeTimeDelay(this TaggerDelay behavior)
- {
- switch (behavior)
+ => behavior switch
{
- case TaggerDelay.NearImmediate:
- return TimeSpan.FromMilliseconds(NearImmediateDelay);
- case TaggerDelay.Short:
- return TimeSpan.FromMilliseconds(ShortDelay);
- case TaggerDelay.Medium:
- return TimeSpan.FromMilliseconds(MediumDelay);
- case TaggerDelay.OnIdle:
- default:
- return TimeSpan.FromMilliseconds(IdleDelay);
- }
- }
+ TaggerDelay.NearImmediate => TimeSpan.FromMilliseconds(NearImmediateDelay),
+ TaggerDelay.Short => TimeSpan.FromMilliseconds(ShortDelay),
+ TaggerDelay.Medium => TimeSpan.FromMilliseconds(MediumDelay),
+ _ => TimeSpan.FromMilliseconds(IdleDelay),
+ };
}
}
diff --git a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource.cs b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource.cs
index d63217a358497..49c52f8c0d839 100644
--- a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource.cs
+++ b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource.cs
@@ -8,6 +8,7 @@
using System.Diagnostics;
using System.Linq;
using System.Threading;
+using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Editor.Shared.Tagging;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.Options;
@@ -61,9 +62,14 @@ private sealed partial class TagSource : ForegroundThreadAffinitizedObject
///
/// Work queue that collects event notifications and kicks off the work to process them.
- /// The value that is passed here tells us if this is the initial tag computation or not.
///
- private readonly AsyncBatchingWorkQueue _eventWorkQueue;
+ private Task _eventWorkQueue = Task.CompletedTask;
+
+ ///
+ /// Series of tokens used to cancel previous outstanding work when new work comes in. Also used as the lock
+ /// to ensure threadsafe writing of _eventWorkQueue.
+ ///
+ private readonly CancellationSeries _cancellationSeries;
///
/// Work queue that collects high priority requests to call TagsChanged with.
@@ -120,12 +126,7 @@ public TagSource(
_dataSource = dataSource;
_asyncListener = asyncListener;
- _eventWorkQueue = new AsyncBatchingWorkQueue(
- _dataSource.EventChangeDelay.ComputeTimeDelay(),
- ProcessEventsAsync,
- EqualityComparer.Default,
- asyncListener,
- _disposalTokenSource.Token);
+ _cancellationSeries = new CancellationSeries(_disposalTokenSource.Token);
_highPriTagsChangedQueue = new AsyncBatchingWorkQueue(
TaggerDelay.NearImmediate.ComputeTimeDelay(),
@@ -153,12 +154,11 @@ public TagSource(
DebugRecordInitialStackTrace();
_eventSource = CreateEventSource();
-
Connect();
// Start computing the initial set of tags immediately. We want to get the UI
// to a complete state as soon as possible.
- _eventWorkQueue.AddWork(/*initialTags*/ true);
+ EnqueueWork(initialTags: true);
return;
@@ -199,6 +199,8 @@ private void Dispose()
// Stop computing any initial tags if we've been asked for them.
_disposalTokenSource.Cancel();
+ _cancellationSeries.Dispose();
+
_disposed = true;
_dataSource.RemoveTagSource(_textViewOpt, _subjectBuffer);
GC.SuppressFinalize(this);
diff --git a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs
index 4b5ace4751ced..ae44c1c4de74f 100644
--- a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs
+++ b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs
@@ -2,8 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
-#nullable disable
-
+using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
@@ -15,6 +14,7 @@
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.PooledObjects;
+using Microsoft.CodeAnalysis.Shared.TestHooks;
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Editor;
@@ -28,10 +28,7 @@ internal partial class AbstractAsynchronousTaggerProvider
{
private partial class TagSource
{
- private void OnEventSourceChanged(object sender, TaggerEventArgs _)
- => _eventWorkQueue.AddWork(/*initialTags*/ false);
-
- private void OnCaretPositionChanged(object sender, CaretPositionChangedEventArgs e)
+ private void OnCaretPositionChanged(object? _, CaretPositionChangedEventArgs e)
{
this.AssertIsForeground();
@@ -66,7 +63,7 @@ private void RemoveAllTags()
RaiseTagsChanged(snapshot.TextBuffer, new DiffResult(added: null, removed: new(oldTagTree.GetSpans(snapshot).Select(s => s.Span))));
}
- private void OnSubjectBufferChanged(object sender, TextContentChangedEventArgs e)
+ private void OnSubjectBufferChanged(object? _, TextContentChangedEventArgs e)
{
this.AssertIsForeground();
UpdateTagsForTextChange(e);
@@ -166,13 +163,42 @@ private TagSpanIntervalTree GetTagTree(ITextSnapshot snapshot, ImmutableDi
: new TagSpanIntervalTree(snapshot.TextBuffer, _dataSource.SpanTrackingMode);
}
- private async Task ProcessEventsAsync(ImmutableArray events, CancellationToken cancellationToken)
+ private void OnEventSourceChanged(object? _1, TaggerEventArgs _2)
+ {
+ EnqueueWork(initialTags: false);
+ }
+
+ private void EnqueueWork(bool initialTags)
{
- // Can only have at most a single `true` and `false` value in this as we are deduping these notification values.
- Contract.ThrowIfTrue(events.Length > 2);
- var initialTags = events.Contains(true);
- await this.ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
- await RecomputeTagsForegroundAsync(initialTags, cancellationToken).ConfigureAwait(false);
+ lock (_cancellationSeries)
+ {
+ try
+ {
+ // cancel the last piece of computation work and enqueue the next. This doesn't apply for the very
+ // first tag request we make. We don't want that to be cancellable as we want that result to be
+ // shown as soon as possible.
+ var cancellationToken = initialTags ? _disposalTokenSource.Token : _cancellationSeries.CreateNext();
+
+ // Continue after the preceeding task unilaterally. Note that we pass LazyCancellation so that
+ // we still wait for that task to complete even if cancelled before we proceed. This is necessary
+ // as that prior task may mutate state (even if cancelled) so we cannot proceed until we know it
+ // is completely done.
+ _eventWorkQueue = _eventWorkQueue.ContinueWithAfterDelayFromAsync(
+ async _ =>
+ {
+ await this.ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
+ await RecomputeTagsForegroundAsync(initialTags, cancellationToken).ConfigureAwait(false);
+ },
+ cancellationToken,
+ (int)_dataSource.EventChangeDelay.ComputeTimeDelay().TotalMilliseconds,
+ TaskContinuationOptions.None,
+ TaskScheduler.Default).CompletesAsyncOperation(_asyncListener.BeginAsyncOperation(nameof(EnqueueWork)));
+ }
+ catch (ObjectDisposedException)
+ {
+ // can happen if our type was disposed and we try to get a new token from _cancellationSeries.
+ }
+ }
}
///
@@ -187,6 +213,8 @@ private async Task ProcessEventsAsync(ImmutableArray events, CancellationT
private async Task RecomputeTagsForegroundAsync(bool initialTags, CancellationToken cancellationToken)
{
this.AssertIsForeground();
+ if (cancellationToken.IsCancellationRequested)
+ return;
using (Logger.LogBlock(FunctionId.Tagger_TagSource_RecomputeTags, cancellationToken))
{
@@ -210,6 +238,8 @@ private async Task RecomputeTagsForegroundAsync(bool initialTags, CancellationTo
oldState, spansToTag, caretPosition, textChangeRange, oldTagTrees, cancellationToken);
await ProduceTagsAsync(context).ConfigureAwait(false);
+ cancellationToken.ThrowIfCancellationRequested();
+
// Process the result to determine what changed.
var newTagTrees = ComputeNewTagTrees(oldTagTrees, context);
var bufferToChanges = ProcessNewTagTrees(spansToTag, oldTagTrees, newTagTrees, cancellationToken);
@@ -217,8 +247,13 @@ private async Task RecomputeTagsForegroundAsync(bool initialTags, CancellationTo
// Then switch back to the UI thread to update our state and kick off the work to notify the editor.
await this.ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
+ // Once we assign our state, we're uncancellable. We must report the changed information
+ // to the editor. The only case where it's ok not to is if the tagger itself is disposed.
+ cancellationToken = CancellationToken.None;
+
this.CachedTagTrees = newTagTrees;
this.State = context.State;
+
OnTagsChangedForBuffer(bufferToChanges, initialTags);
}
}
@@ -229,7 +264,7 @@ private ImmutableArray GetSpansAndDocumentsToTag()
// TODO: Update to tag spans from all related documents.
- var snapshotToDocumentMap = new Dictionary();
+ using var _ = PooledDictionary.GetInstance(out var snapshotToDocumentMap);
var spansToTag = _dataSource.GetSpansToTag(_textViewOpt, _subjectBuffer);
var spansAndDocumentsToTag = spansToTag.SelectAsArray(span =>
@@ -292,7 +327,7 @@ private ImmutableDictionary> ComputeNewTa
return newTagTrees;
}
- private TagSpanIntervalTree ComputeNewTagTree(
+ private TagSpanIntervalTree? ComputeNewTagTree(
ImmutableDictionary> oldTagTrees,
ITextBuffer textBuffer,
IEnumerable> newTags,
@@ -473,7 +508,7 @@ private static DiffResult ComputeDifference(
return new DiffResult(new(added), new(removed));
- static ITagSpan NextOrNull(IEnumerator> enumerator)
+ static ITagSpan? NextOrNull(IEnumerator> enumerator)
=> enumerator.MoveNext() ? enumerator.Current : null;
}
@@ -481,7 +516,7 @@ static ITagSpan NextOrNull(IEnumerator> enumerator)
/// Returns the TagSpanIntervalTree containing the tags for the given buffer. If no tags
/// exist for the buffer at all, null is returned.
///
- private TagSpanIntervalTree TryGetTagIntervalTreeForBuffer(ITextBuffer buffer)
+ private TagSpanIntervalTree? TryGetTagIntervalTreeForBuffer(ITextBuffer buffer)
{
this.AssertIsForeground();
diff --git a/src/Features/Core/Portable/EditAndContinue/EditAndContinueCapabilities.cs b/src/Features/Core/Portable/EditAndContinue/EditAndContinueCapabilities.cs
index 02978f6882471..0258fdfe359dd 100644
--- a/src/Features/Core/Portable/EditAndContinue/EditAndContinueCapabilities.cs
+++ b/src/Features/Core/Portable/EditAndContinue/EditAndContinueCapabilities.cs
@@ -37,6 +37,6 @@ internal enum EditAndContinueCapabilities
///
/// Creating a new type definition.
///
- NewTypeDefinition = 1 << 2
+ NewTypeDefinition = 1 << 4
}
}
diff --git a/src/Features/Core/Portable/EditAndContinue/EditAndContinueWorkspaceService.cs b/src/Features/Core/Portable/EditAndContinue/EditAndContinueWorkspaceService.cs
index 02a1146aad429..d5bec15d92e9b 100644
--- a/src/Features/Core/Portable/EditAndContinue/EditAndContinueWorkspaceService.cs
+++ b/src/Features/Core/Portable/EditAndContinue/EditAndContinueWorkspaceService.cs
@@ -86,8 +86,15 @@ public async ValueTask StartDebuggingSessionAsync(Solution solution, IManagedEdi
SpecializedCollections.EmptyEnumerable>();
var runtimeCapabilities = await debuggerService.GetCapabilitiesAsync(cancellationToken).ConfigureAwait(false);
+
var capabilities = ParseCapabilities(runtimeCapabilities);
+ // For now, runtimes aren't returning capabilities, we just fall back to a known set.
+ if (capabilities == EditAndContinueCapabilities.None)
+ {
+ capabilities = EditAndContinueCapabilities.Baseline | EditAndContinueCapabilities.AddMethodToExistingType | EditAndContinueCapabilities.AddStaticFieldToExistingType | EditAndContinueCapabilities.AddInstanceFieldToExistingType | EditAndContinueCapabilities.NewTypeDefinition;
+ }
+
var newSession = new DebuggingSession(solution, debuggerService, capabilities, _compilationOutputsProvider, initialDocumentStates, _debuggingSessionTelemetry, _editSessionTelemetry);
var previousSession = Interlocked.CompareExchange(ref _debuggingSession, newSession, null);
Contract.ThrowIfFalse(previousSession == null, "New debugging session can't be started until the existing one has ended.");
diff --git a/src/Features/Core/Portable/Workspace/CompileTimeSolutionProvider.cs b/src/Features/Core/Portable/Workspace/CompileTimeSolutionProvider.cs
index 387a15328a824..83316d0df9185 100644
--- a/src/Features/Core/Portable/Workspace/CompileTimeSolutionProvider.cs
+++ b/src/Features/Core/Portable/Workspace/CompileTimeSolutionProvider.cs
@@ -46,7 +46,9 @@ public Factory()
public CompileTimeSolutionProvider(Workspace workspace)
{
_workspace = workspace;
- _enabled = workspace.Services.GetRequiredService().IsExperimentEnabled(WellKnownExperimentNames.RazorLspEditorFeatureFlag);
+ // TODO:
+ //_enabled = workspace.Services.GetRequiredService().IsExperimentEnabled(WellKnownExperimentNames.RazorLspEditorFeatureFlag);
+ _enabled = false;
workspace.WorkspaceChanged += (s, e) =>
{
diff --git a/src/VisualStudio/Core/Def/EditorConfigSettings/SettingsEditorFactory.cs b/src/VisualStudio/Core/Def/EditorConfigSettings/SettingsEditorFactory.cs
index 9d5c0f56592f0..8b8bb0770e07b 100644
--- a/src/VisualStudio/Core/Def/EditorConfigSettings/SettingsEditorFactory.cs
+++ b/src/VisualStudio/Core/Def/EditorConfigSettings/SettingsEditorFactory.cs
@@ -4,7 +4,9 @@
using System;
using System.ComponentModel.Composition;
+using System.Linq;
using System.Runtime.InteropServices;
+using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Editor.EditorConfigSettings;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.Host.Mef;
@@ -79,6 +81,13 @@ public int CreateEditorInstance(uint grfCreateDoc,
pgrfCDW = 0;
pbstrEditorCaption = null;
+ if (!_workspace.CurrentSolution.Projects.Any(p => p.Language == LanguageNames.CSharp || p.Language == LanguageNames.VisualBasic))
+ {
+ // If there are no VB or C# projects loaded in the solution (so an editorconfig file in a C++ project) then we want their
+ // editorfactory to present the file instead of use showing ours
+ return VSConstants.VS_E_UNSUPPORTEDFORMAT;
+ }
+
// Validate inputs
if ((grfCreateDoc & (VSConstants.CEF_OPENFILE | VSConstants.CEF_SILENT)) == 0)
{
diff --git a/src/VisualStudio/Core/Def/Implementation/EditAndContinue/ManagedHotReloadLanguageService.cs b/src/VisualStudio/Core/Def/Implementation/EditAndContinue/ManagedHotReloadLanguageService.cs
index 9a66932e1340b..a9de1576599eb 100644
--- a/src/VisualStudio/Core/Def/Implementation/EditAndContinue/ManagedHotReloadLanguageService.cs
+++ b/src/VisualStudio/Core/Def/Implementation/EditAndContinue/ManagedHotReloadLanguageService.cs
@@ -20,7 +20,7 @@
namespace Microsoft.VisualStudio.LanguageServices.EditAndContinue
{
[Shared]
- [Export(typeof(IManagedEditAndContinueLanguageService))]
+ [Export(typeof(IManagedHotReloadLanguageService))]
[ExportMetadata("UIContext", Guids.EncCapableProjectExistsInWorkspaceUIContextString)]
internal sealed class ManagedHotReloadLanguageService : IManagedHotReloadLanguageService
{
diff --git a/src/Workspaces/Core/Portable/CodeActions/CodeAction.cs b/src/Workspaces/Core/Portable/CodeActions/CodeAction.cs
index 06256f7418270..4c47010ceceba 100644
--- a/src/Workspaces/Core/Portable/CodeActions/CodeAction.cs
+++ b/src/Workspaces/Core/Portable/CodeActions/CodeAction.cs
@@ -68,7 +68,7 @@ internal virtual ImmutableArray NestedCodeActions
///
/// Gets custom tags for the CodeAction.
///
- public ImmutableArray CustomTags { get; protected set; } = ImmutableArray.Empty;
+ internal ImmutableArray CustomTags { get; set; } = ImmutableArray.Empty;
///
/// Used by the CodeFixService and CodeRefactoringService to add the Provider Name as a CustomTag.
@@ -373,13 +373,10 @@ internal abstract class SimpleCodeAction : CodeAction
{
public SimpleCodeAction(
string title,
- string? equivalenceKey,
- IEnumerable? customTags = null)
+ string? equivalenceKey)
{
Title = title;
EquivalenceKey = equivalenceKey;
-
- CustomTags = customTags.ToImmutableArrayOrEmpty();
}
public sealed override string Title { get; }
@@ -392,9 +389,8 @@ public CodeActionWithNestedActions(
string title,
ImmutableArray nestedActions,
bool isInlinable,
- CodeActionPriority priority = CodeActionPriority.Medium,
- IEnumerable? customTags = null)
- : base(title, ComputeEquivalenceKey(nestedActions), customTags)
+ CodeActionPriority priority = CodeActionPriority.Medium)
+ : base(title, ComputeEquivalenceKey(nestedActions))
{
Debug.Assert(nestedActions.Length > 0);
NestedCodeActions = nestedActions;
@@ -434,9 +430,8 @@ internal class DocumentChangeAction : SimpleCodeAction
public DocumentChangeAction(
string title,
Func> createChangedDocument,
- string? equivalenceKey = null,
- IEnumerable? customTags = null)
- : base(title, equivalenceKey, customTags)
+ string? equivalenceKey = null)
+ : base(title, equivalenceKey)
{
_createChangedDocument = createChangedDocument;
}
@@ -452,9 +447,8 @@ internal class SolutionChangeAction : SimpleCodeAction
public SolutionChangeAction(
string title,
Func> createChangedSolution,
- string? equivalenceKey = null,
- IEnumerable? customTags = null)
- : base(title, equivalenceKey, customTags)
+ string? equivalenceKey = null)
+ : base(title, equivalenceKey)
{
_createChangedSolution = createChangedSolution;
}
@@ -467,9 +461,8 @@ internal class NoChangeAction : SimpleCodeAction
{
public NoChangeAction(
string title,
- string? equivalenceKey = null,
- IEnumerable? customTags = null)
- : base(title, equivalenceKey, customTags)
+ string? equivalenceKey = null)
+ : base(title, equivalenceKey)
{
}
diff --git a/src/Workspaces/Core/Portable/PublicAPI.Unshipped.txt b/src/Workspaces/Core/Portable/PublicAPI.Unshipped.txt
index 7271abb715d8e..68ca8f2b61011 100644
--- a/src/Workspaces/Core/Portable/PublicAPI.Unshipped.txt
+++ b/src/Workspaces/Core/Portable/PublicAPI.Unshipped.txt
@@ -1,7 +1,5 @@
abstract Microsoft.CodeAnalysis.CodeFixes.DocumentBasedFixAllProvider.FixAllAsync(Microsoft.CodeAnalysis.CodeFixes.FixAllContext fixAllContext, Microsoft.CodeAnalysis.Document document, System.Collections.Immutable.ImmutableArray diagnostics) -> System.Threading.Tasks.Task
const Microsoft.CodeAnalysis.Classification.ClassificationTypeNames.RecordClassName = "record class name" -> string
-Microsoft.CodeAnalysis.CodeActions.CodeAction.CustomTags.get -> System.Collections.Immutable.ImmutableArray
-Microsoft.CodeAnalysis.CodeActions.CodeAction.CustomTags.set -> void
Microsoft.CodeAnalysis.CodeFixes.DocumentBasedFixAllProvider
Microsoft.CodeAnalysis.CodeFixes.DocumentBasedFixAllProvider.DocumentBasedFixAllProvider() -> void
Microsoft.CodeAnalysis.CommandLineProject
diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.cs
index 5440f5d1bf570..df12fa4d8bfbc 100644
--- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.cs
+++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.cs
@@ -629,6 +629,17 @@ private async Task BuildFinalStateFromInProgressStateAsync(
try
{
var compilationWithGenerators = state.CompilationWithGeneratedDocuments;
+
+ // If compilationWithGenerators is the same as compilationWithoutGenerators, then it means a prior run of generators
+ // didn't produce any files. In that case, we'll just make compilationWithGenerators null so we avoid doing any
+ // transformations of it multiple times. Otherwise the transformations below and in FinalizeCompilationAsync will try
+ // to update both at once, which is functionally fine but just unnecessary work. This function is always allowed to return
+ // null for compilationWithGenerators in the end, so there's no harm there.
+ if (compilationWithGenerators == compilationWithoutGenerators)
+ {
+ compilationWithGenerators = null;
+ }
+
var intermediateProjects = state.IntermediateProjects;
while (intermediateProjects.Length > 0)
diff --git a/src/Workspaces/CoreTest/SolutionTests/SolutionWithSourceGeneratorTests.cs b/src/Workspaces/CoreTest/SolutionTests/SolutionWithSourceGeneratorTests.cs
index 8fae975486f7b..5e515eaf59705 100644
--- a/src/Workspaces/CoreTest/SolutionTests/SolutionWithSourceGeneratorTests.cs
+++ b/src/Workspaces/CoreTest/SolutionTests/SolutionWithSourceGeneratorTests.cs
@@ -312,11 +312,16 @@ public async Task TreeNotReusedIfParseOptionsChangeChangeBetweenRuns()
Assert.NotSame(generatedTreeBeforeChange, generatedTreeAfterChange);
}
- [Fact]
- public async Task ChangeToDocumentThatDoesNotImpactGeneratedDocumentReusesDeclarationTree()
+ [Theory, CombinatorialData]
+ public async Task ChangeToDocumentThatDoesNotImpactGeneratedDocumentReusesDeclarationTree(bool generatorProducesTree)
{
using var workspace = CreateWorkspace();
- var analyzerReference = new TestGeneratorReference(new SingleFileTestGenerator("// StaticContent"));
+
+ // We'll use either a generator that produces a single tree, or no tree, to ensure we efficiently handle both cases
+ ISourceGenerator generator = generatorProducesTree ? new SingleFileTestGenerator("// StaticContent")
+ : new CallbackGenerator(onInit: _ => { }, onExecute: _ => { });
+
+ var analyzerReference = new TestGeneratorReference(generator);
var project = AddEmptyProject(workspace.CurrentSolution)
.AddAnalyzerReference(analyzerReference)
.AddDocument("RegularDocument.cs", "// Source File", filePath: "RegularDocument.cs").Project;
diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeFixes/CustomCodeActions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeFixes/CustomCodeActions.cs
index 888238efefd53..976f450bbb254 100644
--- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeFixes/CustomCodeActions.cs
+++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeFixes/CustomCodeActions.cs
@@ -3,8 +3,6 @@
// See the LICENSE file in the project root for more information.
using System;
-using System.Collections.Generic;
-using System.Collections.Immutable;
using System.Threading;
using System.Threading.Tasks;
using Roslyn.Utilities;
@@ -17,13 +15,10 @@ internal abstract class SimpleCodeAction : CodeAction
{
public SimpleCodeAction(
string title,
- string? equivalenceKey = null,
- IEnumerable? customTags = null)
+ string? equivalenceKey = null)
{
Title = title;
EquivalenceKey = equivalenceKey;
-
- CustomTags = customTags.ToImmutableArrayOrEmpty();
}
public sealed override string Title { get; }
@@ -37,9 +32,8 @@ internal class DocumentChangeAction : SimpleCodeAction
public DocumentChangeAction(
string title,
Func> createChangedDocument,
- string? equivalenceKey = null,
- IEnumerable? customTags = null)
- : base(title, equivalenceKey, customTags)
+ string? equivalenceKey = null)
+ : base(title, equivalenceKey)
{
_createChangedDocument = createChangedDocument;
}
@@ -55,9 +49,8 @@ internal class SolutionChangeAction : SimpleCodeAction
public SolutionChangeAction(
string title,
Func> createChangedSolution,
- string? equivalenceKey = null,
- IEnumerable? customTags = null)
- : base(title, equivalenceKey, customTags)
+ string? equivalenceKey = null)
+ : base(title, equivalenceKey)
{
_createChangedSolution = createChangedSolution;
}