Skip to content

Commit ffa1aac

Browse files
Merge branch 'main' into issue-72632
2 parents 87c3653 + 2c3e10f commit ffa1aac

File tree

16 files changed

+262
-154
lines changed

16 files changed

+262
-154
lines changed

src/EditorFeatures/Core.Wpf/InlineDiagnostics/AbstractDiagnosticsTaggerProvider.SingleDiagnosticKindPullTaggerProvider.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ private async Task ProduceTagsAsync(
143143
// and hence only report them for 'DiagnosticKind.AnalyzerSemantic'.
144144
if (_diagnosticKind == DiagnosticKind.AnalyzerSemantic)
145145
{
146-
var copilotDiagnostics = await document.GetCachedCopilotDiagnosticsAsync(cancellationToken).ConfigureAwait(false);
146+
var copilotDiagnostics = await document.GetCachedCopilotDiagnosticsAsync(requestedSpan.Span.ToTextSpan(), cancellationToken).ConfigureAwait(false);
147147
diagnostics = diagnostics.AddRange(copilotDiagnostics);
148148
}
149149

src/EditorFeatures/Core.Wpf/InlineRename/UI/Adornment/RenameFlyout.xaml

+3-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
xmlns:rename="clr-namespace:Microsoft.CodeAnalysis.Editor.Implementation.InlineRename"
88
xmlns:imaging="clr-namespace:Microsoft.VisualStudio.Imaging;assembly=Microsoft.VisualStudio.Imaging"
99
xmlns:imagecatalog="clr-namespace:Microsoft.VisualStudio.Imaging;assembly=Microsoft.VisualStudio.ImageCatalog"
10+
xmlns:platformimaging="clr-namespace:Microsoft.VisualStudio.PlatformUI;assembly=Microsoft.VisualStudio.Imaging"
1011
xmlns:utils="clr-namespace:Microsoft.CodeAnalysis.Utilities"
1112
xmlns:vsui="clr-namespace:Microsoft.VisualStudio.PlatformUI;assembly=Microsoft.VisualStudio.Shell.15.0"
1213
mc:Ignorable="d"
@@ -32,7 +33,8 @@
3233
Background="{DynamicResource {x:Static vsui:EnvironmentColors.ToolWindowBackgroundBrushKey}}"
3334
BorderBrush="{DynamicResource {x:Static vsui:EnvironmentColors.ToolWindowBorderBrushKey}}"
3435
BorderThickness="1"
35-
x:Name="Outline">
36+
x:Name="Outline"
37+
platformimaging:ImageThemingUtilities.ImageBackgroundColor="{Binding Path=Background, RelativeSource={RelativeSource Self}, Converter={StaticResource BrushToColorConverter}}">
3638
<StackPanel x:Name="MainPanel" Orientation="Vertical" Margin="5" >
3739
<Grid x:Name="IdentifierAndExpandButtonGrid" Margin="0 0 0 5">
3840
<Grid.ColumnDefinitions>

src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameUserInputComboBox.xaml

+99-52
Large diffs are not rendered by default.

src/EditorFeatures/Core/Copilot/CopilotTaggerProvider.cs

+1-2
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,7 @@ protected override async Task ProduceTagsAsync(TaggerContext<ITextMarkerTag> con
8484
var prompts = await service.GetAvailablePromptTitlesAsync(document, cancellationToken).ConfigureAwait(false);
8585
if (prompts.Length > 0)
8686
{
87-
// Invoke analysis call into the Copilot service for the containing method's span.
88-
await service.AnalyzeDocumentAsync(document, new(spanToTag.SnapshotSpan.Start, 0), prompts[0], cancellationToken).ConfigureAwait(false);
87+
await service.AnalyzeDocumentAsync(document, spanToTag.SnapshotSpan.Span.ToTextSpan(), prompts[0], cancellationToken).ConfigureAwait(false);
8988
}
9089
}
9190
}

src/EditorFeatures/Test2/CodeFixes/CodeFixServiceTests.vb

+84
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Imports System.Reflection
99
Imports System.Threading
1010
Imports Microsoft.CodeAnalysis.CodeActions
1111
Imports Microsoft.CodeAnalysis.CodeFixes
12+
Imports Microsoft.CodeAnalysis.Copilot
1213
Imports Microsoft.CodeAnalysis.Diagnostics
1314
Imports Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests
1415
Imports Microsoft.CodeAnalysis.Editor.UnitTests
@@ -17,6 +18,8 @@ Imports Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces
1718
Imports Microsoft.CodeAnalysis.ErrorLogger
1819
Imports Microsoft.CodeAnalysis.Host
1920
Imports Microsoft.CodeAnalysis.Host.Mef
21+
Imports Microsoft.CodeAnalysis.Text
22+
Imports Microsoft.CodeAnalysis.UnitTests
2023
Imports Roslyn.Utilities
2124

2225
Namespace Microsoft.CodeAnalysis.Editor.Implementation.CodeFixes.UnitTests
@@ -263,5 +266,86 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.CodeFixes.UnitTests
263266
#Enable Warning RS0005
264267
End Function
265268
End Class
269+
270+
<Fact>
271+
Public Async Function TestCopilotCodeAnalysisServiceWithoutSyntaxTree() As Task
272+
Dim workspaceDefinition =
273+
<Workspace>
274+
<Project Language="NoCompilation" AssemblyName="TestAssembly" CommonReferencesPortable="true">
275+
<Document>
276+
var x = {}; // e.g., TypeScript code or anything else that doesn't support compilations
277+
</Document>
278+
</Project>
279+
</Workspace>
280+
281+
Dim composition = EditorTestCompositions.EditorFeatures.AddParts(
282+
GetType(NoCompilationContentTypeDefinitions),
283+
GetType(NoCompilationContentTypeLanguageService),
284+
GetType(NoCompilationCopilotCodeAnalysisService))
285+
286+
Using workspace = EditorTestWorkspace.Create(workspaceDefinition, composition:=composition)
287+
288+
Dim document = workspace.CurrentSolution.Projects.Single().Documents.Single()
289+
Dim diagnosticsXml =
290+
<Diagnostics>
291+
<Error Id=<%= "TestId" %>
292+
MappedFile=<%= document.Name %> MappedLine="0" MappedColumn="0"
293+
OriginalFile=<%= document.Name %> OriginalLine="0" OriginalColumn="0"
294+
Message=<%= "Test Message" %>/>
295+
</Diagnostics>
296+
Dim diagnostics = DiagnosticProviderTests.GetExpectedDiagnostics(workspace, diagnosticsXml)
297+
298+
Dim copilotCodeAnalysisService = document.Project.Services.GetService(Of ICopilotCodeAnalysisService)()
299+
Dim noCompilationCopilotCodeAnalysisService = DirectCast(copilotCodeAnalysisService, NoCompilationCopilotCodeAnalysisService)
300+
301+
NoCompilationCopilotCodeAnalysisService.Diagnostics = diagnostics.SelectAsArray(Of Diagnostic)(
302+
Function(d) d.ToDiagnosticAsync(document.Project, CancellationToken.None).Result)
303+
Dim codefixService = workspace.ExportProvider.GetExportedValue(Of ICodeFixService)
304+
305+
' Make sure we don't crash
306+
Dim unused = Await codefixService.GetMostSevereFixAsync(
307+
document, Text.TextSpan.FromBounds(0, 0), New DefaultCodeActionRequestPriorityProvider(), CodeActionOptions.DefaultProvider, CancellationToken.None)
308+
End Using
309+
End Function
310+
311+
<ExportLanguageService(GetType(ICopilotCodeAnalysisService), NoCompilationConstants.LanguageName, ServiceLayer.Test), [Shared], PartNotDiscoverable>
312+
Private Class NoCompilationCopilotCodeAnalysisService
313+
Implements ICopilotCodeAnalysisService
314+
315+
<ImportingConstructor>
316+
<Obsolete(MefConstruction.ImportingConstructorMessage, True)>
317+
Public Sub New()
318+
End Sub
319+
320+
Public Shared Property Diagnostics As ImmutableArray(Of Diagnostic) = ImmutableArray(Of Diagnostic).Empty
321+
322+
Public Function IsRefineOptionEnabledAsync() As Task(Of Boolean) Implements ICopilotCodeAnalysisService.IsRefineOptionEnabledAsync
323+
Return Task.FromResult(True)
324+
End Function
325+
326+
Public Function IsCodeAnalysisOptionEnabledAsync() As Task(Of Boolean) Implements ICopilotCodeAnalysisService.IsCodeAnalysisOptionEnabledAsync
327+
Return Task.FromResult(True)
328+
End Function
329+
330+
Public Function IsAvailableAsync(cancellationToken As CancellationToken) As Task(Of Boolean) Implements ICopilotCodeAnalysisService.IsAvailableAsync
331+
Return Task.FromResult(True)
332+
End Function
333+
334+
Public Function GetAvailablePromptTitlesAsync(document As Document, cancellationToken As CancellationToken) As Task(Of ImmutableArray(Of String)) Implements ICopilotCodeAnalysisService.GetAvailablePromptTitlesAsync
335+
Return Task.FromResult(ImmutableArray.Create("Title"))
336+
End Function
337+
338+
Public Function AnalyzeDocumentAsync(document As Document, span As TextSpan?, promptTitle As String, cancellationToken As CancellationToken) As Task Implements ICopilotCodeAnalysisService.AnalyzeDocumentAsync
339+
Return Task.CompletedTask
340+
End Function
341+
342+
Public Function GetCachedDocumentDiagnosticsAsync(document As Document, span As TextSpan?, promptTitles As ImmutableArray(Of String), cancellationToken As CancellationToken) As Task(Of ImmutableArray(Of Diagnostic)) Implements ICopilotCodeAnalysisService.GetCachedDocumentDiagnosticsAsync
343+
Return Task.FromResult(Diagnostics)
344+
End Function
345+
346+
Public Function StartRefinementSessionAsync(oldDocument As Document, newDocument As Document, primaryDiagnostic As Diagnostic, cancellationToken As CancellationToken) As Task Implements ICopilotCodeAnalysisService.StartRefinementSessionAsync
347+
Return Task.CompletedTask
348+
End Function
349+
End Class
266350
End Class
267351
End Namespace

src/EditorFeatures/Test2/Diagnostics/DiagnosticProviderTests.vb

+1-1
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,7 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests
302302
Return analyzerService
303303
End Function
304304

305-
Private Shared Function GetExpectedDiagnostics(workspace As EditorTestWorkspace, diagnostics As XElement) As List(Of DiagnosticData)
305+
Friend Shared Function GetExpectedDiagnostics(workspace As EditorTestWorkspace, diagnostics As XElement) As List(Of DiagnosticData)
306306
Dim result As New List(Of DiagnosticData)
307307
Dim mappedLine As Integer, mappedColumn As Integer, originalLine As Integer, originalColumn As Integer
308308
Dim Id As String, message As String, originalFile As String, mappedFile As String

src/Features/CSharp/Portable/Copilot/CSharpCopilotCodeFixProvider.cs

+13-17
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
using System;
66
using System.Collections.Immutable;
77
using System.Composition;
8-
using System.Diagnostics;
98
using System.Linq;
109
using System.Threading;
1110
using System.Threading.Tasks;
@@ -71,20 +70,17 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context)
7170

7271
var hasMultiplePrompts = promptTitles.Length > 1;
7372

74-
// Find the containing method, if any, and also update the fix span to the entire method.
75-
// TODO: count location in doc-comment as part of the method.
73+
// Find the containing method for each diagnostic, and register a fix if any part of the method interect with context span.
7674
var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
77-
var containingMethod = CSharpSyntaxFacts.Instance.GetContainingMethodDeclaration(root, context.Span.Start, useFullSpan: false);
78-
if (containingMethod is not BaseMethodDeclarationSyntax)
79-
return;
80-
8175
foreach (var diagnostic in context.Diagnostics)
8276
{
83-
Debug.Assert(containingMethod.FullSpan.IntersectsWith(diagnostic.Location.SourceSpan));
84-
85-
var fix = TryGetFix(document, containingMethod, diagnostic, hasMultiplePrompts);
86-
if (fix != null)
87-
context.RegisterCodeFix(fix, diagnostic);
77+
var containingMethod = CSharpSyntaxFacts.Instance.GetContainingMethodDeclaration(root, diagnostic.Location.SourceSpan.Start, useFullSpan: false);
78+
if (containingMethod?.Span.IntersectsWith(context.Span) is true)
79+
{
80+
var fix = TryGetFix(document, containingMethod, diagnostic, hasMultiplePrompts);
81+
if (fix != null)
82+
context.RegisterCodeFix(fix, diagnostic);
83+
}
8884
}
8985
}
9086

@@ -105,8 +101,8 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context)
105101
// Parse the proposed Copilot fix into a method declaration.
106102
// Guard against failure cases where the proposed fixed code does not parse into a method declaration.
107103
// TODO: consider do this early when we create the diagnostic and add a flag in the property bag to speedup lightbulb computation
108-
var fixMethodDeclaration = SyntaxFactory.ParseMemberDeclaration(fix, options: method.SyntaxTree.Options);
109-
if (fixMethodDeclaration is null || !fixMethodDeclaration.IsKind(SyntaxKind.MethodDeclaration) || fixMethodDeclaration.GetDiagnostics().Count() > 3)
104+
var memberDeclaration = SyntaxFactory.ParseMemberDeclaration(fix, options: method.SyntaxTree.Options);
105+
if (memberDeclaration is null || memberDeclaration is not BaseMethodDeclarationSyntax baseMethodDeclaration || baseMethodDeclaration.GetDiagnostics().Count() > 3)
110106
return null;
111107

112108
var title = hasMultiplePrompts
@@ -125,9 +121,9 @@ async Task<Document> GetFixedDocumentAsync(SyntaxNode method, string fix, Cancel
125121
var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false);
126122

127123
// TODO: Replace all the whitespace trivia with elastic trivia, and any other trivia related improvements
128-
var newMethod = fixMethodDeclaration
129-
.WithLeadingTrivia(fixMethodDeclaration.HasLeadingTrivia ? fixMethodDeclaration.GetLeadingTrivia() : method.GetLeadingTrivia())
130-
.WithTrailingTrivia(fixMethodDeclaration.HasTrailingTrivia ? fixMethodDeclaration.GetTrailingTrivia() : method.GetTrailingTrivia())
124+
var newMethod = memberDeclaration
125+
.WithLeadingTrivia(memberDeclaration.HasLeadingTrivia ? memberDeclaration.GetLeadingTrivia() : method.GetLeadingTrivia())
126+
.WithTrailingTrivia(memberDeclaration.HasTrailingTrivia ? memberDeclaration.GetTrailingTrivia() : method.GetTrailingTrivia())
131127
.WithAdditionalAnnotations(Formatter.Annotation, WarningAnnotation);
132128

133129
editor.ReplaceNode(method, newMethod);

src/Features/Core/Portable/Copilot/Extensions.cs

+4-14
Original file line numberDiff line numberDiff line change
@@ -13,27 +13,17 @@ namespace Microsoft.CodeAnalysis.Copilot;
1313

1414
internal static class Extensions
1515
{
16-
public static async Task<ImmutableArray<DiagnosticData>> GetCachedCopilotDiagnosticsAsync(this TextDocument document, TextSpan span, CancellationToken cancellationToken)
17-
{
18-
var diagnostics = await document.GetCachedCopilotDiagnosticsAsync(cancellationToken).ConfigureAwait(false);
19-
if (diagnostics.IsEmpty)
20-
return [];
21-
22-
var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);
23-
return diagnostics.WhereAsArray(diagnostic => span.IntersectsWith(diagnostic.DataLocation.UnmappedFileSpan.GetClampedTextSpan(text)));
24-
}
25-
26-
public static async Task<ImmutableArray<DiagnosticData>> GetCachedCopilotDiagnosticsAsync(this TextDocument document, CancellationToken cancellationToken)
16+
public static async Task<ImmutableArray<DiagnosticData>> GetCachedCopilotDiagnosticsAsync(this TextDocument document, TextSpan? span, CancellationToken cancellationToken)
2717
{
2818
if (document is not Document sourceDocument)
29-
return ImmutableArray<DiagnosticData>.Empty;
19+
return [];
3020

3121
var copilotCodeAnalysisService = sourceDocument.GetLanguageService<ICopilotCodeAnalysisService>();
3222
if (copilotCodeAnalysisService is null)
33-
return ImmutableArray<DiagnosticData>.Empty;
23+
return [];
3424

3525
var promptTitles = await copilotCodeAnalysisService.GetAvailablePromptTitlesAsync(sourceDocument, cancellationToken).ConfigureAwait(false);
36-
var copilotDiagnostics = await copilotCodeAnalysisService.GetCachedDocumentDiagnosticsAsync(sourceDocument, promptTitles, cancellationToken).ConfigureAwait(false);
26+
var copilotDiagnostics = await copilotCodeAnalysisService.GetCachedDocumentDiagnosticsAsync(sourceDocument, span, promptTitles, cancellationToken).ConfigureAwait(false);
3727
return copilotDiagnostics.SelectAsArray(d => DiagnosticData.Create(d, sourceDocument));
3828
}
3929
}

src/Features/Core/Portable/Copilot/ICopilotCodeAnalysisService.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ internal interface ICopilotCodeAnalysisService : ILanguageService
6161
/// <remarks>
6262
/// A prompt's title serves as the ID of the prompt, which can be used to selectively trigger analysis and retrive cached results.
6363
/// </remarks>
64-
Task<ImmutableArray<Diagnostic>> GetCachedDocumentDiagnosticsAsync(Document document, ImmutableArray<string> promptTitles, CancellationToken cancellationToken);
64+
Task<ImmutableArray<Diagnostic>> GetCachedDocumentDiagnosticsAsync(Document document, TextSpan? span, ImmutableArray<string> promptTitles, CancellationToken cancellationToken);
6565

6666
/// <summary>
6767
/// Method to start a Copilot refinement session on top of the changes between the given

src/Features/LanguageServer/Protocol/Features/CodeFixes/CodeFixService.cs

+3-14
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
using Microsoft.CodeAnalysis.Host;
2424
using Microsoft.CodeAnalysis.Host.Mef;
2525
using Microsoft.CodeAnalysis.Internal.Log;
26-
using Microsoft.CodeAnalysis.LanguageService;
2726
using Microsoft.CodeAnalysis.PooledObjects;
2827
using Microsoft.CodeAnalysis.Shared.Extensions;
2928
using Microsoft.CodeAnalysis.Shared.Utilities;
@@ -255,20 +254,10 @@ private static async Task<ImmutableArray<DiagnosticData>> GetCopilotDiagnosticsA
255254
CodeActionRequestPriority? priority,
256255
CancellationToken cancellationToken)
257256
{
258-
if (!(priority is null or CodeActionRequestPriority.Low)
259-
|| document is not Document sourceDocument)
260-
{
261-
return [];
262-
}
263-
264-
// Expand the fixable range for Copilot diagnostics to containing method.
265-
// TODO: Share the below code with other places we compute containing method for Copilot analysis.
266-
var root = await sourceDocument.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
267-
var syntaxFacts = sourceDocument.GetRequiredLanguageService<ISyntaxFactsService>();
268-
var containingMethod = syntaxFacts.GetContainingMethodDeclaration(root, range.Start, useFullSpan: false);
269-
range = containingMethod?.Span ?? range;
257+
if (priority is null or CodeActionRequestPriority.Low)
258+
return await document.GetCachedCopilotDiagnosticsAsync(range, cancellationToken).ConfigureAwait(false);
270259

271-
return await document.GetCachedCopilotDiagnosticsAsync(range, cancellationToken).ConfigureAwait(false);
260+
return [];
272261
}
273262

274263
private static SortedDictionary<TextSpan, List<DiagnosticData>> ConvertToMap(

src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DocumentDiagnosticSource.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ public override async Task<ImmutableArray<DiagnosticData>> GetDiagnosticsAsync(
3434
// Add cached Copilot diagnostics when computing analyzer semantic diagnostics.
3535
if (DiagnosticKind == DiagnosticKind.AnalyzerSemantic)
3636
{
37-
var copilotDiagnostics = await Document.GetCachedCopilotDiagnosticsAsync(cancellationToken).ConfigureAwait(false);
37+
var copilotDiagnostics = await Document.GetCachedCopilotDiagnosticsAsync(span: null, cancellationToken).ConfigureAwait(false);
3838
allSpanDiagnostics = allSpanDiagnostics.AddRange(copilotDiagnostics);
3939
}
4040

src/Tools/ExternalAccess/Copilot/Internal/Analyzer/AbstractCopilotCodeAnalysisService.cs

+10-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
using System;
66
using System.Collections.Concurrent;
7+
using System.Collections.Generic;
78
using System.Collections.Immutable;
89
using System.Runtime.CompilerServices;
910
using System.Threading;
@@ -116,7 +117,7 @@ private void CacheAndRefreshDiagnosticsIfNeeded(Document document, string prompt
116117
diagnosticsRefresher.RequestWorkspaceRefresh();
117118
}
118119

119-
public async Task<ImmutableArray<Diagnostic>> GetCachedDocumentDiagnosticsAsync(Document document, ImmutableArray<string> promptTitles, CancellationToken cancellationToken)
120+
public async Task<ImmutableArray<Diagnostic>> GetCachedDocumentDiagnosticsAsync(Document document, TextSpan? span, ImmutableArray<string> promptTitles, CancellationToken cancellationToken)
120121
{
121122
if (await ShouldSkipAnalysisAsync(document, cancellationToken).ConfigureAwait(false))
122123
return [];
@@ -144,9 +145,17 @@ public async Task<ImmutableArray<Diagnostic>> GetCachedDocumentDiagnosticsAsync(
144145
}
145146
}
146147

148+
if (span.HasValue)
149+
return await GetDiagnosticsIntersectWithSpanAsync(document, diagnostics, span.Value, cancellationToken).ConfigureAwait(false);
150+
147151
return diagnostics.ToImmutable();
148152
}
149153

154+
protected virtual Task<ImmutableArray<Diagnostic>> GetDiagnosticsIntersectWithSpanAsync(Document document, IReadOnlyList<Diagnostic> diagnostics, TextSpan span, CancellationToken cancellationToken)
155+
{
156+
return Task.FromResult(diagnostics.WhereAsArray((diagnostic, _) => diagnostic.Location.SourceSpan.IntersectsWith(span), state: (object)null));
157+
}
158+
150159
public async Task StartRefinementSessionAsync(Document oldDocument, Document newDocument, Diagnostic? primaryDiagnostic, CancellationToken cancellationToken)
151160
{
152161
if (await IsRefineOptionEnabledAsync().ConfigureAwait(false))

0 commit comments

Comments
 (0)