-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Initial stubs for doing analysis of copilot changes to help us drive post-change cleanup work. #78146
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
CyrusNajmabadi
merged 15 commits into
dotnet:main
from
CyrusNajmabadi:copilotChangeAnalysis2
Apr 25, 2025
Merged
Initial stubs for doing analysis of copilot changes to help us drive post-change cleanup work. #78146
Changes from 1 commit
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
92cc8ef
Initial stubs
CyrusNajmabadi 52c3a04
Async token
CyrusNajmabadi 4478cca
Change value
CyrusNajmabadi 3358369
Fix
CyrusNajmabadi 5580627
Allow null
CyrusNajmabadi 640ccf4
Merge branch 'moveDiagnosticServiceDown' into copilotChangeAnalysis2
CyrusNajmabadi f596a12
Simplify
CyrusNajmabadi a0191e8
Simplufy
CyrusNajmabadi ae531dd
Add null
CyrusNajmabadi 5e03218
Merge remote-tracking branch 'upstream/main' into copilotChangeAnalysis2
CyrusNajmabadi f2ab1ae
Add correlation id
CyrusNajmabadi 9ee106f
Merge remote-tracking branch 'upstream/main' into copilotChangeAnalysis2
CyrusNajmabadi b016537
Move to string
CyrusNajmabadi ed93b03
Merge branch 'main' into copilotChangeAnalysis2
CyrusNajmabadi 612252b
Add resource string
CyrusNajmabadi File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
128 changes: 128 additions & 0 deletions
128
src/EditorFeatures/Core.Wpf/Copilot/CopilotWpfTextCreationListener.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,128 @@ | ||
| // 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.Generic; | ||
| using System.Collections.Immutable; | ||
| using System.ComponentModel.Composition; | ||
| using System.Linq; | ||
| using System.Threading; | ||
| using System.Threading.Tasks; | ||
| using Microsoft.CodeAnalysis.Collections; | ||
| using Microsoft.CodeAnalysis.Editor; | ||
| using Microsoft.CodeAnalysis.Editor.Shared.Extensions; | ||
| using Microsoft.CodeAnalysis.Editor.Shared.Utilities; | ||
| using Microsoft.CodeAnalysis.Host.Mef; | ||
| using Microsoft.CodeAnalysis.Shared.TestHooks; | ||
| using Microsoft.CodeAnalysis.Text; | ||
| using Microsoft.CodeAnalysis.Threading; | ||
| using Microsoft.VisualStudio.Language.Proposals; | ||
| using Microsoft.VisualStudio.Language.Suggestions; | ||
| using Microsoft.VisualStudio.Text.Editor; | ||
| using Microsoft.VisualStudio.Utilities; | ||
|
|
||
| namespace Microsoft.CodeAnalysis.Copilot; | ||
|
|
||
| [Export(typeof(IWpfTextViewCreationListener))] | ||
| [ContentType(ContentTypeNames.RoslynContentType)] | ||
| [TextViewRole(PredefinedTextViewRoles.Document)] | ||
| internal sealed class CopilotWpfTextViewCreationListener : IWpfTextViewCreationListener | ||
| { | ||
| private readonly IThreadingContext _threadingContext; | ||
| private readonly Lazy<SuggestionServiceBase> _suggestionServiceBase; | ||
|
|
||
| private readonly AsyncBatchingWorkQueue<SuggestionAcceptedEventArgs> _workQueue; | ||
|
|
||
| private int _started; | ||
|
|
||
| [ImportingConstructor] | ||
| [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] | ||
| public CopilotWpfTextViewCreationListener( | ||
| IThreadingContext threadingContext, | ||
| Lazy<SuggestionServiceBase> suggestionServiceBase, | ||
| IAsynchronousOperationListenerProvider listenerProvider) | ||
| { | ||
| _threadingContext = threadingContext; | ||
| _suggestionServiceBase = suggestionServiceBase; | ||
| _workQueue = new AsyncBatchingWorkQueue<SuggestionAcceptedEventArgs>( | ||
| DelayTimeSpan.Idle, | ||
| ProcessEventsAsync, | ||
| listenerProvider.GetListener(FeatureAttribute.CopilotChangeAnalysis), | ||
| _threadingContext.DisposalToken); | ||
| } | ||
|
|
||
| public void TextViewCreated(IWpfTextView textView) | ||
| { | ||
| // On the first roslyn text view created, kick off work to hydrate the suggestion service and register to events | ||
| // from it. | ||
| if (Interlocked.CompareExchange(ref _started, 1, 0) == 0) | ||
| { | ||
| _ = Task.Run(() => | ||
| { | ||
| var suggestionService = _suggestionServiceBase.Value; | ||
| suggestionService.SuggestionAccepted += OnSuggestionAccepted; | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| private void OnSuggestionAccepted(object sender, SuggestionAcceptedEventArgs e) | ||
| { | ||
| if (e.FinalProposal.Edits.Count == 0) | ||
| return; | ||
|
|
||
| _workQueue.AddWork(e); | ||
| } | ||
|
|
||
| private async ValueTask ProcessEventsAsync( | ||
| ImmutableSegmentedList<SuggestionAcceptedEventArgs> list, CancellationToken cancellationToken) | ||
| { | ||
| foreach (var eventArgs in list) | ||
| await ProcessEventAsync(eventArgs, cancellationToken).ConfigureAwait(false); | ||
| } | ||
|
|
||
| private static async ValueTask ProcessEventAsync( | ||
| SuggestionAcceptedEventArgs eventArgs, CancellationToken cancellationToken) | ||
| { | ||
| foreach (var editGroup in eventArgs.FinalProposal.Edits.GroupBy(e => e.Span.Snapshot)) | ||
| { | ||
| cancellationToken.ThrowIfCancellationRequested(); | ||
|
|
||
| var snapshot = editGroup.Key; | ||
| var document = snapshot.GetOpenDocumentInCurrentContextWithChanges(); | ||
|
|
||
| if (document is null) | ||
| continue; | ||
|
|
||
| var normalizedEdits = Normalize(editGroup); | ||
| if (normalizedEdits.IsDefaultOrEmpty) | ||
| continue; | ||
|
|
||
| var changeAnalysisService = document.Project.Solution.Services.GetRequiredService<ICopilotChangeAnalysisService>(); | ||
| await changeAnalysisService.AnalyzeChangeAsync(document, normalizedEdits, cancellationToken).ConfigureAwait(false); | ||
| } | ||
| } | ||
|
|
||
| private static ImmutableArray<TextChange> Normalize(IEnumerable<ProposedEdit> editGroup) | ||
| { | ||
| using var _ = PooledObjects.ArrayBuilder<TextChange>.GetInstance(out var builder); | ||
| foreach (var edit in editGroup) | ||
| builder.Add(new TextChange(edit.Span.Span.ToTextSpan(), edit.ReplacementText)); | ||
|
|
||
| // Ensure everything is sorted. | ||
| builder.Sort(static (c1, c2) => c1.Span.Start - c2.Span.Start); | ||
|
|
||
| // Now, go through and make sure no edit overlaps another. | ||
| for (int i = 1, n = builder.Count; i < n; i++) | ||
| { | ||
| var lastEdit = builder[i - 1]; | ||
| var currentEdit = builder[i]; | ||
|
|
||
| if (lastEdit.Span.OverlapsWith(currentEdit.Span)) | ||
| return default; | ||
| } | ||
|
|
||
| // Things look good. Can process these sorted edits. | ||
| return builder.ToImmutableAndClear(); | ||
| } | ||
| } |
49 changes: 49 additions & 0 deletions
49
src/Features/Core/Portable/Copilot/CopilotChangeAnalysis.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| // 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.Generic; | ||
| using System.Collections.Immutable; | ||
| using System.Runtime.Serialization; | ||
| using Microsoft.CodeAnalysis.Diagnostics; | ||
|
|
||
| namespace Microsoft.CodeAnalysis.Copilot; | ||
|
|
||
| /// <param name="TotalAnalysisTime">Total time to do all analysis (including diagnostics, code fixes, and application).</param> | ||
| /// <param name="TotalDiagnosticComputationTime">Total time to do all diagnostic computation over all diagnostic kinds.</param> | ||
| [DataContract] | ||
| internal readonly record struct CopilotChangeAnalysis( | ||
| [property: DataMember(Order = 0)] bool Succeeded, | ||
| [property: DataMember(Order = 1)] TimeSpan TotalAnalysisTime, | ||
| [property: DataMember(Order = 2)] TimeSpan TotalDiagnosticComputationTime, | ||
| [property: DataMember(Order = 3)] ImmutableArray<CopilotDiagnosticAnalysis> DiagnosticAnalyses, | ||
| [property: DataMember(Order = 4)] CopilotCodeFixAnalysis CodeFixAnalysis); | ||
|
|
||
| /// <param name="Kind">What diagnostic kind this is analysis data for.</param> | ||
| /// <param name="ComputationTime">How long it took to produce the diagnostics for this diagnostic kind.</param> | ||
| /// <param name="IdToCount">Mapping from <see cref="Diagnostic.Id"/> to the number of diagnostics produced for that id.</param> | ||
| /// <param name="CategoryToCount">Mapping from <see cref="Diagnostic.Category"/> to the number of diagnostics produced for that category.</param> | ||
| /// <param name="SeverityToCount">Mapping from <see cref="Diagnostic.Severity"/> to the number of diagnostics produced for that severity.</param> | ||
| [DataContract] | ||
| internal readonly record struct CopilotDiagnosticAnalysis( | ||
| [property: DataMember(Order = 0)] DiagnosticKind Kind, | ||
| [property: DataMember(Order = 1)] TimeSpan ComputationTime, | ||
| [property: DataMember(Order = 2)] Dictionary<string, int> IdToCount, | ||
| [property: DataMember(Order = 3)] Dictionary<string, int> CategoryToCount, | ||
| [property: DataMember(Order = 4)] Dictionary<DiagnosticSeverity, int> SeverityToCount); | ||
|
|
||
| /// <param name="TotalComputationTime">Total time to compute code fixes for the changed regions.</param> | ||
| /// <param name="TotalApplicationTime">Total time to apply code fixes for the changed regions.</param> | ||
| /// <param name="DiagnosticIdToCount">Mapping from diagnostic id to to how many diagnostics with that id had fixes.</param> | ||
| /// <param name="DiagnosticIdToApplicationTime">Mapping from diagnostic id to the total time taken to fix diagnostics with that id.</param> | ||
| /// <param name="DiagnosticIdToProviderName">Mapping from diagnostic id to the name of the provider that provided the fix.</param> | ||
| /// <param name="ProviderNameToApplicationTime">Mapping from provider name to the total time taken to fix diagnostics with that provider.</param> | ||
| [DataContract] | ||
| internal readonly record struct CopilotCodeFixAnalysis( | ||
| [property: DataMember(Order = 0)] TimeSpan TotalComputationTime, | ||
| [property: DataMember(Order = 1)] TimeSpan TotalApplicationTime, | ||
| [property: DataMember(Order = 2)] Dictionary<string, int> DiagnosticIdToCount, | ||
| [property: DataMember(Order = 3)] Dictionary<string, TimeSpan> DiagnosticIdToApplicationTime, | ||
| [property: DataMember(Order = 4)] Dictionary<string, HashSet<string>> DiagnosticIdToProviderName, | ||
| [property: DataMember(Order = 5)] Dictionary<string, TimeSpan> ProviderNameToApplicationTime); |
88 changes: 88 additions & 0 deletions
88
src/Features/Core/Portable/Copilot/ICopilotChangeAnalysisService.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,88 @@ | ||
| // 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.Composition; | ||
| using System.Linq; | ||
| using System.Threading; | ||
| using System.Threading.Tasks; | ||
| using Microsoft.CodeAnalysis.CodeFixes; | ||
| using Microsoft.CodeAnalysis.Diagnostics; | ||
| using Microsoft.CodeAnalysis.Host; | ||
| using Microsoft.CodeAnalysis.Host.Mef; | ||
| using Microsoft.CodeAnalysis.Remote; | ||
| using Microsoft.CodeAnalysis.Shared; | ||
| using Microsoft.CodeAnalysis.Text; | ||
|
|
||
| namespace Microsoft.CodeAnalysis.Copilot; | ||
|
|
||
| internal interface ICopilotChangeAnalysisService : IWorkspaceService | ||
| { | ||
| /// <summary> | ||
| /// Kicks of work to analyze a change that copilot suggested making to a document. <paramref name="document"/> is | ||
| /// the state of the document prior to the edits, and <paramref name="changes"/> are the changes Copilot wants to | ||
| /// make to it. <paramref name="changes"/> must be sorted and normalized before calling this. | ||
| /// </summary> | ||
| Task<CopilotChangeAnalysis> AnalyzeChangeAsync(Document document, ImmutableArray<TextChange> changes, CancellationToken cancellationToken); | ||
| } | ||
|
|
||
| [ExportWorkspaceServiceFactory(typeof(ICopilotChangeAnalysisService)), Shared] | ||
| [method: ImportingConstructor] | ||
| [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] | ||
| internal sealed class DefaultCopilotChangeAnalysisServiceFactory( | ||
| ICodeFixService codeFixService, | ||
| IDiagnosticAnalyzerService diagnosticAnalyzerService) : IWorkspaceServiceFactory | ||
| { | ||
| public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) | ||
| => new DefaultCopilotChangeAnalysisService(codeFixService, diagnosticAnalyzerService, workspaceServices); | ||
|
|
||
| private sealed class DefaultCopilotChangeAnalysisService( | ||
| ICodeFixService codeFixService, | ||
| IDiagnosticAnalyzerService diagnosticAnalyzerService, | ||
| HostWorkspaceServices workspaceServices) : ICopilotChangeAnalysisService | ||
| { | ||
| private readonly ICodeFixService _codeFixService = codeFixService; | ||
| private readonly IDiagnosticAnalyzerService _diagnosticAnalyzerService = diagnosticAnalyzerService; | ||
| private readonly HostWorkspaceServices _workspaceServices = workspaceServices; | ||
|
|
||
| public async Task<CopilotChangeAnalysis> AnalyzeChangeAsync( | ||
| Document document, | ||
| ImmutableArray<TextChange> changes, | ||
| CancellationToken cancellationToken) | ||
| { | ||
| if (!document.SupportsSemanticModel) | ||
| return default; | ||
|
|
||
| Contract.ThrowIfTrue(!changes.IsSorted(static (c1, c2) => c1.Span.Start - c2.Span.Start), "'changes' was not sorted."); | ||
| Contract.ThrowIfTrue(new NormalizedTextSpanCollection(changes.Select(c => c.Span)).Count != changes.Length, "'changes' was not normalized."); | ||
| Contract.ThrowIfTrue(document.Project.Solution.Workspace != _workspaceServices.Workspace); | ||
|
|
||
| var client = await RemoteHostClient.TryGetClientAsync( | ||
| _workspaceServices.Workspace, cancellationToken).ConfigureAwait(false); | ||
|
|
||
| if (client != null) | ||
| { | ||
| var value = await client.TryInvokeAsync<IRemoteCopilotChangeAnalysisService, CopilotChangeAnalysis>( | ||
| // Don't need to sync the entire solution over. Just the cone of projects this document it contained within. | ||
| document.Project, | ||
| (service, checksum, cancellationToken) => service.AnalyzeChangeAsync(checksum, document.Id, changes, cancellationToken), | ||
| cancellationToken).ConfigureAwait(false); | ||
| return value.HasValue ? value.Value : default; | ||
| } | ||
| else | ||
| { | ||
| return await AnalyzeChangeInCurrentProcessAsync(document, changes, cancellationToken).ConfigureAwait(false); | ||
| } | ||
| } | ||
|
|
||
| private async Task<CopilotChangeAnalysis> AnalyzeChangeInCurrentProcessAsync( | ||
| Document document, | ||
| ImmutableArray<TextChange> changes, | ||
| CancellationToken cancellationToken) | ||
| { | ||
| return default; | ||
| } | ||
| } | ||
| } | ||
19 changes: 19 additions & 0 deletions
19
src/Features/Core/Portable/Copilot/IRemoteCopilotChangeAnalysisService.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| // 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.Immutable; | ||
| using System.Threading; | ||
| using System.Threading.Tasks; | ||
| using Microsoft.CodeAnalysis.Host; | ||
| using Microsoft.CodeAnalysis.Text; | ||
|
|
||
| namespace Microsoft.CodeAnalysis.Copilot; | ||
|
|
||
| /// <summary>Remote version of <see cref="ICopilotChangeAnalysisService"/></summary> | ||
| internal interface IRemoteCopilotChangeAnalysisService : IWorkspaceService | ||
| { | ||
| /// <inheritdoc cref="ICopilotChangeAnalysisService.AnalyzeChangeAsync"/> | ||
| ValueTask<CopilotChangeAnalysis> AnalyzeChangeAsync( | ||
| Checksum solutionChecksum, DocumentId documentId, ImmutableArray<TextChange> edits, CancellationToken cancellationToken); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
40 changes: 40 additions & 0 deletions
40
src/Workspaces/Remote/ServiceHub/Services/Copilot/RemoteCopilotChangeAnalysisService.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| // 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.Immutable; | ||
| using System.Threading; | ||
| using System.Threading.Tasks; | ||
| using Microsoft.CodeAnalysis.Copilot; | ||
| using Microsoft.CodeAnalysis.Shared.Extensions; | ||
| using Microsoft.CodeAnalysis.Text; | ||
|
|
||
| namespace Microsoft.CodeAnalysis.Remote; | ||
|
|
||
| internal sealed partial class RemoteCopilotChangeAnalysisService( | ||
| in BrokeredServiceBase.ServiceConstructionArguments arguments) | ||
| : BrokeredServiceBase(arguments), IRemoteCopilotChangeAnalysisService | ||
| { | ||
| internal sealed class Factory : FactoryBase<IRemoteCopilotChangeAnalysisService> | ||
| { | ||
| protected override IRemoteCopilotChangeAnalysisService CreateService(in ServiceConstructionArguments arguments) | ||
| => new RemoteCopilotChangeAnalysisService(arguments); | ||
| } | ||
|
|
||
| public ValueTask AnalyzeChangeAsync( | ||
| Checksum solutionChecksum, | ||
| DocumentId documentId, | ||
| ImmutableArray<TextChange> edits, | ||
| CancellationToken cancellationToken) | ||
| { | ||
| return RunServiceAsync(solutionChecksum, async solution => | ||
| { | ||
| var document = await solution.GetRequiredDocumentAsync( | ||
| documentId, includeSourceGenerated: true, cancellationToken).ConfigureAwait(false); | ||
|
|
||
| var service = solution.Services.GetRequiredService<ICopilotChangeAnalysisService>(); | ||
| await service.AnalyzeChangeAsync( | ||
| document, edits, cancellationToken).ConfigureAwait(false); | ||
| }, cancellationToken); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this will be implemented in a followup PR today.