Skip to content

Commit 8d9e041

Browse files
authored
On-the-fly-docs Pass along additional context (#77510)
* wip * works end to end * some feedback * feedback * fix bugs * tests * revert settings * fix code analysis service * fix formatting * Feedback
1 parent f296737 commit 8d9e041

17 files changed

+355
-30
lines changed

src/Compilers/Test/Core/Traits/Traits.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,7 @@ public static class Features
290290
public const string NetCore = nameof(NetCore);
291291
public const string NormalizeModifiersOrOperators = nameof(NormalizeModifiersOrOperators);
292292
public const string ObjectBrowser = nameof(ObjectBrowser);
293+
public const string OnTheFlyDocs = nameof(OnTheFlyDocs);
293294
public const string Options = nameof(Options);
294295
public const string Organizing = nameof(Organizing);
295296
public const string Outlining = nameof(Outlining);
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System.Linq;
6+
using System.Threading.Tasks;
7+
using Microsoft.CodeAnalysis.CSharp.QuickInfo;
8+
using Microsoft.CodeAnalysis.CSharp.Syntax;
9+
using Microsoft.CodeAnalysis.Test.Utilities;
10+
using Xunit;
11+
12+
namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.OnTheFlyDocs;
13+
14+
[UseExportProvider]
15+
[Trait(Traits.Feature, Traits.Features.OnTheFlyDocs)]
16+
public sealed class OnTheFlyDocsUtilitiesTests
17+
{
18+
[Fact]
19+
public async Task TestAdditionalContextNoContext()
20+
{
21+
var testCode = """
22+
class C
23+
{
24+
void AddMethod(int a, int b)
25+
{
26+
return a + b;
27+
}
28+
}
29+
""";
30+
31+
using var workspace = EditorTestWorkspace.CreateCSharp(testCode);
32+
var solution = workspace.CurrentSolution;
33+
var document = solution.Projects.First().Documents.First();
34+
35+
var syntaxTree = await document.GetSyntaxTreeAsync();
36+
var semanticModel = await document.GetSemanticModelAsync();
37+
38+
var methodDeclaration = syntaxTree!.GetRoot()
39+
.DescendantNodes()
40+
.OfType<MethodDeclarationSyntax>()
41+
.First();
42+
43+
var methodSymbol = semanticModel!.GetDeclaredSymbol(methodDeclaration);
44+
45+
var result = OnTheFlyDocsUtilities.GetAdditionalOnTheFlyDocsContext(solution, methodSymbol!);
46+
Assert.True(result.All(item => item == null));
47+
}
48+
49+
[Fact]
50+
public async Task TestAdditionalContextWithTypeParameters()
51+
{
52+
var testCode = """
53+
class C
54+
{
55+
int AddMethod(A a, int b)
56+
{
57+
return a.x + b;
58+
}
59+
}
60+
61+
class A
62+
{
63+
public int x;
64+
}
65+
""";
66+
67+
using var workspace = EditorTestWorkspace.CreateCSharp(testCode);
68+
var solution = workspace.CurrentSolution;
69+
var document = solution.Projects.First().Documents.First();
70+
71+
var syntaxTree = await document.GetSyntaxTreeAsync();
72+
var semanticModel = await document.GetSemanticModelAsync();
73+
74+
var methodDeclaration = syntaxTree!.GetRoot()
75+
.DescendantNodes()
76+
.OfType<MethodDeclarationSyntax>()
77+
.First();
78+
79+
var methodSymbol = semanticModel!.GetDeclaredSymbol(methodDeclaration);
80+
81+
var result = OnTheFlyDocsUtilities.GetAdditionalOnTheFlyDocsContext(solution, methodSymbol!);
82+
Assert.NotNull(result.First());
83+
Assert.Null(result.Last());
84+
}
85+
86+
[Fact]
87+
public async Task TestAdditionalContextWithTypeArguments()
88+
{
89+
var testCode = """
90+
class C
91+
{
92+
void Method<T, U>(T t, U u) where T : class where U : struct
93+
{
94+
}
95+
96+
void CallMethod()
97+
{
98+
Method<CustomClass, CustomStruct>(new CustomClass(), new CustomStruct());
99+
}
100+
}
101+
102+
class CustomClass
103+
{
104+
public string Name { get; set; }
105+
}
106+
107+
struct CustomStruct
108+
{
109+
public int Value { get; set; }
110+
}
111+
""";
112+
113+
using var workspace = EditorTestWorkspace.CreateCSharp(testCode);
114+
var solution = workspace.CurrentSolution;
115+
var document = solution.Projects.First().Documents.First();
116+
117+
var syntaxTree = await document.GetSyntaxTreeAsync();
118+
var semanticModel = await document.GetSemanticModelAsync();
119+
120+
var methodInvocation = syntaxTree!.GetRoot()
121+
.DescendantNodes()
122+
.OfType<InvocationExpressionSyntax>()
123+
.First();
124+
125+
var methodSymbol = semanticModel!.GetSymbolInfo(methodInvocation).Symbol;
126+
127+
var result = OnTheFlyDocsUtilities.GetAdditionalOnTheFlyDocsContext(solution, methodSymbol!);
128+
Assert.True(result.All(item => item is not null));
129+
}
130+
}

src/EditorFeatures/Core.Wpf/QuickInfo/OnTheFlyDocsView.xaml.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,8 @@ private async Task SetResultTextAsync(ICopilotCodeAnalysisService copilotService
179179

180180
try
181181
{
182-
var (responseString, isQuotaExceeded) = await copilotService.GetOnTheFlyDocsAsync(_onTheFlyDocsInfo.SymbolSignature, _onTheFlyDocsInfo.DeclarationCode, _onTheFlyDocsInfo.Language, cancellationToken).ConfigureAwait(false);
182+
var prompt = await copilotService.GetOnTheFlyDocsPromptAsync(_onTheFlyDocsInfo, cancellationToken).ConfigureAwait(false);
183+
var (responseString, isQuotaExceeded) = await copilotService.GetOnTheFlyDocsResponseAsync(prompt, cancellationToken).ConfigureAwait(false);
183184
var copilotRequestTime = stopwatch.Elapsed;
184185

185186
await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);

src/EditorFeatures/Test2/CodeFixes/CodeFixServiceTests.vb

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ Imports Microsoft.CodeAnalysis.ErrorLogger
1919
Imports Microsoft.CodeAnalysis.FindSymbols
2020
Imports Microsoft.CodeAnalysis.Host
2121
Imports Microsoft.CodeAnalysis.Host.Mef
22+
Imports Microsoft.CodeAnalysis.QuickInfo
2223
Imports Microsoft.CodeAnalysis.Text
2324
Imports Microsoft.CodeAnalysis.UnitTests
2425
Imports Roslyn.Utilities
@@ -359,10 +360,6 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.CodeFixes.UnitTests
359360
Return Task.CompletedTask
360361
End Function
361362

362-
Public Function GetOnTheFlyDocsAsync(symbolSignature As String, declarationCode As ImmutableArray(Of String), language As String, cancellationToken As CancellationToken) As Task(Of (responseString As String, isQuotaExceeded As Boolean)) Implements ICopilotCodeAnalysisService.GetOnTheFlyDocsAsync
363-
Return Task.FromResult(("", False))
364-
End Function
365-
366363
Public Function IsFileExcludedAsync(filePath As String, cancellationToken As CancellationToken) As Task(Of Boolean) Implements ICopilotCodeAnalysisService.IsFileExcludedAsync
367364
Return Task.FromResult(False)
368365
End Function
@@ -371,6 +368,14 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.CodeFixes.UnitTests
371368
Return Task.FromResult((New Dictionary(Of String, String), False))
372369
End Function
373370

371+
Public Function GetOnTheFlyDocsPromptAsync(onTheFlyDocsInfo As OnTheFlyDocsInfo, cancellationToken As CancellationToken) As Task(Of String) Implements ICopilotCodeAnalysisService.GetOnTheFlyDocsPromptAsync
372+
Return Task.FromResult(String.Empty)
373+
End Function
374+
375+
Public Function GetOnTheFlyDocsResponseAsync(prompt As String, cancellationToken As CancellationToken) As Task(Of (responseString As String, isQuotaExceeded As Boolean)) Implements ICopilotCodeAnalysisService.GetOnTheFlyDocsResponseAsync
376+
Return Task.FromResult((String.Empty, False))
377+
End Function
378+
374379
Public Function IsImplementNotImplementedExceptionsAvailableAsync(cancellationToken As CancellationToken) As Task(Of Boolean) Implements ICopilotCodeAnalysisService.IsImplementNotImplementedExceptionsAvailableAsync
375380
Return Task.FromResult(False)
376381
End Function

src/Features/CSharp/Portable/QuickInfo/CSharpSemanticQuickInfoProvider.cs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -193,14 +193,21 @@ protected override NullableFlowState GetNullabilityAnalysis(SemanticModel semant
193193
}
194194
}
195195

196-
var maxLength = 1000;
197-
var symbolStrings = symbol.DeclaringSyntaxReferences.Select(reference =>
196+
var solution = document.Project.Solution;
197+
var declarationCode = symbol.DeclaringSyntaxReferences.Select(reference =>
198198
{
199199
var span = reference.Span;
200-
var sourceText = reference.SyntaxTree.GetText(cancellationToken);
201-
return sourceText.GetSubText(new Text.TextSpan(span.Start, Math.Min(maxLength, span.Length))).ToString();
200+
var syntaxReferenceDocument = solution.GetDocument(reference.SyntaxTree);
201+
if (syntaxReferenceDocument is not null)
202+
{
203+
return new OnTheFlyDocsRelevantFileInfo(syntaxReferenceDocument, span);
204+
}
205+
206+
return null;
202207
}).ToImmutableArray();
203208

204-
return new OnTheFlyDocsInfo(symbol.ToDisplayString(), symbolStrings, symbol.Language, hasContentExcluded);
209+
var additionalContext = OnTheFlyDocsUtilities.GetAdditionalOnTheFlyDocsContext(solution, symbol);
210+
211+
return new OnTheFlyDocsInfo(symbol.ToDisplayString(), declarationCode, symbol.Language, hasContentExcluded, additionalContext);
205212
}
206213
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System.Collections.Immutable;
6+
using System.Linq;
7+
using Microsoft.CodeAnalysis.QuickInfo;
8+
using Microsoft.CodeAnalysis.Shared.Extensions;
9+
10+
namespace Microsoft.CodeAnalysis.CSharp.QuickInfo;
11+
12+
internal static class OnTheFlyDocsUtilities
13+
{
14+
public static ImmutableArray<OnTheFlyDocsRelevantFileInfo?> GetAdditionalOnTheFlyDocsContext(Solution solution, ISymbol symbol)
15+
{
16+
var parameters = symbol.GetParameters();
17+
var typeArguments = symbol.GetTypeArguments();
18+
19+
var parameterStrings = parameters.Select(parameter =>
20+
{
21+
var typeSymbol = parameter.Type;
22+
return GetOnTheFlyDocsRelevantFileInfo(typeSymbol);
23+
24+
}).ToImmutableArray();
25+
26+
var typeArgumentStrings = typeArguments.Select(typeArgument =>
27+
{
28+
return GetOnTheFlyDocsRelevantFileInfo(typeArgument);
29+
30+
}).ToImmutableArray();
31+
32+
return parameterStrings.AddRange(typeArgumentStrings);
33+
34+
OnTheFlyDocsRelevantFileInfo? GetOnTheFlyDocsRelevantFileInfo(ITypeSymbol typeSymbol)
35+
{
36+
var typeSyntaxReference = typeSymbol.DeclaringSyntaxReferences.FirstOrDefault();
37+
if (typeSyntaxReference is not null)
38+
{
39+
var typeSpan = typeSyntaxReference.Span;
40+
var syntaxReferenceDocument = solution.GetDocument(typeSyntaxReference.SyntaxTree);
41+
if (syntaxReferenceDocument is not null)
42+
{
43+
return new OnTheFlyDocsRelevantFileInfo(syntaxReferenceDocument, typeSpan);
44+
}
45+
}
46+
47+
return null;
48+
}
49+
}
50+
}

src/Features/CSharpTest/Copilot/CSharpImplementNotImplementedExceptionFixProviderTests.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
using Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions;
1515
using Microsoft.CodeAnalysis.FindSymbols;
1616
using Microsoft.CodeAnalysis.Host.Mef;
17+
using Microsoft.CodeAnalysis.QuickInfo;
1718
using Microsoft.CodeAnalysis.Test.Utilities;
1819
using Microsoft.CodeAnalysis.Testing;
1920
using Microsoft.CodeAnalysis.Text;
@@ -696,5 +697,15 @@ public Task<bool> IsImplementNotImplementedExceptionsAvailableAsync(Cancellation
696697
{
697698
return Task.FromResult(true);
698699
}
700+
701+
public Task<string> GetOnTheFlyDocsPromptAsync(OnTheFlyDocsInfo onTheFlyDocsInfo, CancellationToken cancellationToken)
702+
{
703+
throw new NotImplementedException();
704+
}
705+
706+
public Task<(string responseString, bool isQuotaExceeded)> GetOnTheFlyDocsResponseAsync(string prompt, CancellationToken cancellationToken)
707+
{
708+
throw new NotImplementedException();
709+
}
699710
}
700711
}

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

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
using Microsoft.CodeAnalysis.DocumentationComments;
1111
using Microsoft.CodeAnalysis.FindSymbols;
1212
using Microsoft.CodeAnalysis.Host;
13+
using Microsoft.CodeAnalysis.QuickInfo;
1314
using Microsoft.CodeAnalysis.Text;
1415

1516
namespace Microsoft.CodeAnalysis.Copilot;
@@ -65,15 +66,17 @@ internal interface ICopilotCodeAnalysisService : ILanguageService
6566
Task StartRefinementSessionAsync(Document oldDocument, Document newDocument, Diagnostic? primaryDiagnostic, CancellationToken cancellationToken);
6667

6768
/// <summary>
68-
/// Method to fetch the on-the-fly documentation based on a a symbols <paramref name="symbolSignature"/>
69-
/// and the code for the symbols in <paramref name="declarationCode"/>.
70-
/// <para>
71-
/// <paramref name="symbolSignature"/> is a formatted string representation of an <see cref="ISymbol"/>.<br/>
72-
/// <paramref name="declarationCode"/> is a list of a code definitions from an <see cref="ISymbol"/>.
73-
/// <paramref name="language"/> is the language of the originating <see cref="ISymbol"/>.
74-
/// </para>
69+
/// Retrieves the prompt
7570
/// </summary>
76-
Task<(string responseString, bool isQuotaExceeded)> GetOnTheFlyDocsAsync(string symbolSignature, ImmutableArray<string> declarationCode, string language, CancellationToken cancellationToken);
71+
/// <param name="onTheFlyDocsInfo">Type containing code and other context about the symbol being examined.</param>
72+
/// <returns></returns>
73+
Task<string> GetOnTheFlyDocsPromptAsync(OnTheFlyDocsInfo onTheFlyDocsInfo, CancellationToken cancellationToken);
74+
75+
/// <summary>
76+
/// Retrieves the response from Copilot summarizing what a symbol is being used for and whether or not the quota has exceeded.
77+
/// </summary>
78+
/// <param name="prompt">The input text used to generate the response.</param>
79+
Task<(string responseString, bool isQuotaExceeded)> GetOnTheFlyDocsResponseAsync(string prompt, CancellationToken cancellationToken);
7780

7881
/// <summary>
7982
/// Determines if the given <paramref name="filePath"/> is excluded in the workspace.

src/Features/Core/Portable/QuickInfo/OnTheFlyDocsInfo.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,13 @@ namespace Microsoft.CodeAnalysis.QuickInfo;
1313
/// <param name="declarationCode">the symbol's declaration code</param>
1414
/// <param name="language">the language of the symbol</param>
1515
/// <param name="hasComments">whether the symbol has comments</param>
16-
internal sealed class OnTheFlyDocsInfo(string symbolSignature, ImmutableArray<string> declarationCode, string language, bool isContentExcluded, bool hasComments = false)
16+
internal sealed class OnTheFlyDocsInfo(string symbolSignature, ImmutableArray<OnTheFlyDocsRelevantFileInfo?> declarationCode, string language, bool isContentExcluded, ImmutableArray<OnTheFlyDocsRelevantFileInfo?> additionalContext, bool hasComments = false)
1717
{
1818
public string SymbolSignature { get; } = symbolSignature;
19-
public ImmutableArray<string> DeclarationCode { get; } = declarationCode;
19+
public ImmutableArray<OnTheFlyDocsRelevantFileInfo?> DeclarationCode { get; } = declarationCode;
2020
public string Language { get; } = language;
2121
public bool IsContentExcluded { get; set; } = isContentExcluded;
22+
public ImmutableArray<OnTheFlyDocsRelevantFileInfo?> AdditionalContext { get; } = additionalContext;
2223

2324
// Added for telemetry collection purposes.
2425
public bool HasComments { get; set; } = hasComments;
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using Microsoft.CodeAnalysis.Text;
6+
7+
namespace Microsoft.CodeAnalysis.QuickInfo;
8+
9+
internal sealed record OnTheFlyDocsRelevantFileInfo
10+
{
11+
public Document Document { get; }
12+
public TextSpan TextSpan { get; }
13+
14+
public OnTheFlyDocsRelevantFileInfo(Document document, TextSpan textSpan)
15+
{
16+
Document = document;
17+
TextSpan = textSpan;
18+
}
19+
}
20+

0 commit comments

Comments
 (0)