Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion eng/targets/Settings.props
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<PackageTags>Roslyn CodeAnalysis Compiler CSharp VB VisualBasic Parser Scanner Lexer Emit CodeGeneration Metadata IL Compilation Scripting Syntax Semantics</PackageTags>
<ThirdPartyNoticesFilePath>$(MSBuildThisFileDirectory)..\..\src\NuGet\ThirdPartyNotices.rtf</ThirdPartyNoticesFilePath>

<VSSDKTargetPlatformRegRootSuffix>RoslynDev</VSSDKTargetPlatformRegRootSuffix>
<VSSDKTargetPlatformRegRootSuffix>Exp</VSSDKTargetPlatformRegRootSuffix>

<!-- Workaround for old Microsoft.VisualStudio.Progression.* packages -->
<NoWarn>$(NoWarn);VSIXCompatibility1001</NoWarn>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,8 @@ private async Task SetResultTextAsync(ICopilotCodeAnalysisService copilotService

try
{
var (responseString, isQuotaExceeded) = await copilotService.GetOnTheFlyDocsAsync(_onTheFlyDocsInfo.SymbolSignature, _onTheFlyDocsInfo.DeclarationCode, _onTheFlyDocsInfo.Language, cancellationToken).ConfigureAwait(false);
var prompt = await copilotService.GetOnTheFlyDocsPromptAsync(_onTheFlyDocsInfo, cancellationToken).ConfigureAwait(false);
var (responseString, isQuotaExceeded) = await copilotService.GetOnTheFlyDocsResponseAsync(prompt, cancellationToken).ConfigureAwait(false);
var copilotRequestTime = stopwatch.Elapsed;

await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
Expand Down
13 changes: 9 additions & 4 deletions src/EditorFeatures/Test2/CodeFixes/CodeFixServiceTests.vb
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Imports Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces
Imports Microsoft.CodeAnalysis.ErrorLogger
Imports Microsoft.CodeAnalysis.Host
Imports Microsoft.CodeAnalysis.Host.Mef
Imports Microsoft.CodeAnalysis.QuickInfo
Imports Microsoft.CodeAnalysis.Text
Imports Microsoft.CodeAnalysis.UnitTests
Imports Roslyn.Utilities
Expand Down Expand Up @@ -354,17 +355,21 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.CodeFixes.UnitTests
Return Task.CompletedTask
End Function

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
Return Task.FromResult(("", False))
End Function

Public Function IsFileExcludedAsync(filePath As String, cancellationToken As CancellationToken) As Task(Of Boolean) Implements ICopilotCodeAnalysisService.IsFileExcludedAsync
Return Task.FromResult(False)
End Function

Public Function GetDocumentationCommentAsync(proposal As DocumentationCommentProposal, cancellationToken As CancellationToken) As Task(Of (responseDictionary As Dictionary(Of String, String), isQuotaExceeded As Boolean)) Implements ICopilotCodeAnalysisService.GetDocumentationCommentAsync
Return Task.FromResult((New Dictionary(Of String, String), False))
End Function

Public Function GetOnTheFlyDocsPromptAsync(onTheFlyDocsInfo As OnTheFlyDocsInfo, cancellationToken As CancellationToken) As Task(Of String) Implements ICopilotCodeAnalysisService.GetOnTheFlyDocsPromptAsync
Return Task.FromResult(String.Empty)
End Function

Public Function GetOnTheFlyDocsResponseAsync(prompt As String, cancellationToken As CancellationToken) As Task(Of (responseString As String, isQuotaExceeded As Boolean)) Implements ICopilotCodeAnalysisService.GetOnTheFlyDocsResponseAsync
Return Task.FromResult((String.Empty, False))
End Function
End Class
End Class
End Namespace
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Composition;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection.Metadata;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Copilot;
Expand Down Expand Up @@ -193,14 +194,63 @@ protected override NullableFlowState GetNullabilityAnalysis(SemanticModel semant
}
}

var maxLength = 1000;
var symbolStrings = symbol.DeclaringSyntaxReferences.Select(reference =>
var solution = document.Project.Solution;
var declarationCode = symbol.DeclaringSyntaxReferences.Select(reference =>
{
var span = reference.Span;
var sourceText = reference.SyntaxTree.GetText(cancellationToken);
return sourceText.GetSubText(new Text.TextSpan(span.Start, Math.Min(maxLength, span.Length))).ToString();
var syntaxReferenceDocument = solution.GetDocument(reference.SyntaxTree);
if (syntaxReferenceDocument is not null)
{
return new OnTheFlyDocsRelevantFileInfo(syntaxReferenceDocument, span);
}

return null;
}).ToImmutableArray();

var additionalContext = GetAdditionalOnTheFlyDocsContext(solution, symbol);

return new OnTheFlyDocsInfo(symbol.ToDisplayString(), declarationCode, symbol.Language, hasContentExcluded, additionalContext);
}

private static ImmutableArray<OnTheFlyDocsRelevantFileInfo?> GetAdditionalOnTheFlyDocsContext(Solution solution, ISymbol symbol)
{
var parameters = symbol.GetParameters();
var typeArguments = symbol.GetTypeArguments();
var parameterStrings = parameters.Select(parameter =>
{
var typeSymbol = parameter.Type;
var typeSyntaxReference = typeSymbol.DeclaringSyntaxReferences.FirstOrDefault();
if (typeSyntaxReference is not null)
{
var typeSpan = typeSyntaxReference.Span;
var syntaxReferenceDocument = solution.GetDocument(typeSyntaxReference.SyntaxTree);
if (syntaxReferenceDocument is not null)
{
return new OnTheFlyDocsRelevantFileInfo(syntaxReferenceDocument, typeSpan);
}
}

return null;

}).ToImmutableArray();

var typeArgumentStrings = typeArguments.Select(typeArgument =>
{
var typeSyntaxReference = typeArgument.DeclaringSyntaxReferences.FirstOrDefault();
if (typeSyntaxReference is not null)
{
var typeSpan = typeSyntaxReference.Span;
var syntaxReferenceDocument = solution.GetDocument(typeSyntaxReference.SyntaxTree);
if (syntaxReferenceDocument is not null)
{
return new OnTheFlyDocsRelevantFileInfo(syntaxReferenceDocument, typeSpan);
}
}

return null;

}).ToImmutableArray();

return new OnTheFlyDocsInfo(symbol.ToDisplayString(), symbolStrings, symbol.Language, hasContentExcluded);
return parameterStrings.AddRange(typeArgumentStrings);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.DocumentationComments;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.QuickInfo;
using Microsoft.CodeAnalysis.Text;

namespace Microsoft.CodeAnalysis.Copilot;
Expand Down Expand Up @@ -62,16 +63,9 @@ internal interface ICopilotCodeAnalysisService : ILanguageService
/// </summary>
Task StartRefinementSessionAsync(Document oldDocument, Document newDocument, Diagnostic? primaryDiagnostic, CancellationToken cancellationToken);

/// <summary>
/// Method to fetch the on-the-fly documentation based on a a symbols <paramref name="symbolSignature"/>
/// and the code for the symbols in <paramref name="declarationCode"/>.
/// <para>
/// <paramref name="symbolSignature"/> is a formatted string representation of an <see cref="ISymbol"/>.<br/>
/// <paramref name="declarationCode"/> is a list of a code definitions from an <see cref="ISymbol"/>.
/// <paramref name="language"/> is the language of the originating <see cref="ISymbol"/>.
/// </para>
/// </summary>
Task<(string responseString, bool isQuotaExceeded)> GetOnTheFlyDocsAsync(string symbolSignature, ImmutableArray<string> declarationCode, string language, CancellationToken cancellationToken);
Task<string> GetOnTheFlyDocsPromptAsync(OnTheFlyDocsInfo onTheFlyDocsInfo, CancellationToken cancellationToken);

Task<(string responseString, bool isQuotaExceeded)> GetOnTheFlyDocsResponseAsync(string prompt, CancellationToken cancellationToken);

/// <summary>
/// Determines if the given <paramref name="filePath"/> is excluded in the workspace.
Expand Down
5 changes: 3 additions & 2 deletions src/Features/Core/Portable/QuickInfo/OnTheFlyDocsInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,13 @@ namespace Microsoft.CodeAnalysis.QuickInfo;
/// <param name="declarationCode">the symbol's declaration code</param>
/// <param name="language">the language of the symbol</param>
/// <param name="hasComments">whether the symbol has comments</param>
internal sealed class OnTheFlyDocsInfo(string symbolSignature, ImmutableArray<string> declarationCode, string language, bool isContentExcluded, bool hasComments = false)
internal sealed class OnTheFlyDocsInfo(string symbolSignature, ImmutableArray<OnTheFlyDocsRelevantFileInfo?> declarationCode, string language, bool isContentExcluded, ImmutableArray<OnTheFlyDocsRelevantFileInfo?> additionalContext, bool hasComments = false)
{
public string SymbolSignature { get; } = symbolSignature;
public ImmutableArray<string> DeclarationCode { get; } = declarationCode;
public ImmutableArray<OnTheFlyDocsRelevantFileInfo?> DeclarationCode { get; } = declarationCode;
public string Language { get; } = language;
public bool IsContentExcluded { get; set; } = isContentExcluded;
public ImmutableArray<OnTheFlyDocsRelevantFileInfo?> AdditionalContext { get; } = additionalContext;

// Added for telemetry collection purposes.
public bool HasComments { get; set; } = hasComments;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// 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 Microsoft.CodeAnalysis.Text;

namespace Microsoft.CodeAnalysis.QuickInfo;

internal sealed class OnTheFlyDocsRelevantFileInfo(Document document, TextSpan textSpan)
{
public Document Document { get; } = document;
public TextSpan TextSpan { get; } = textSpan;

}

Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,5 @@ internal interface IExternalCSharpCopilotCodeAnalysisService
Task<ImmutableArray<Diagnostic>> AnalyzeDocumentAsync(Document document, TextSpan? span, string promptTitle, CancellationToken cancellationToken);
Task<ImmutableArray<Diagnostic>> GetCachedDiagnosticsAsync(Document document, string promptTitle, CancellationToken cancellationToken);
Task StartRefinementSessionAsync(Document oldDocument, Document newDocument, Diagnostic? primaryDiagnostic, CancellationToken cancellationToken);
Task<(string responseString, bool isQuotaExceeded)> GetOnTheFlyDocsAsync(string symbolSignature, ImmutableArray<string> declarationCode, string language, CancellationToken cancellationToken);
Task<bool> IsFileExcludedAsync(string filePath, CancellationToken cancellationToken);
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.DocumentationComments;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.QuickInfo;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;

Expand Down Expand Up @@ -39,7 +40,8 @@ internal abstract class AbstractCopilotCodeAnalysisService(IDiagnosticsRefresher
protected abstract Task<ImmutableArray<Diagnostic>> AnalyzeDocumentCoreAsync(Document document, TextSpan? span, string promptTitle, CancellationToken cancellationToken);
protected abstract Task<ImmutableArray<Diagnostic>> GetCachedDiagnosticsCoreAsync(Document document, string promptTitle, CancellationToken cancellationToken);
protected abstract Task StartRefinementSessionCoreAsync(Document oldDocument, Document newDocument, Diagnostic? primaryDiagnostic, CancellationToken cancellationToken);
protected abstract Task<(string responseString, bool isQuotaExceeded)> GetOnTheFlyDocsCoreAsync(string symbolSignature, ImmutableArray<string> declarationCode, string language, CancellationToken cancellationToken);
protected abstract Task<string> GetOnTheFlyDocsPromptCoreAsync(OnTheFlyDocsInfo onTheFlyDocsInfo, CancellationToken cancellationToken);
protected abstract Task<(string responseString, bool isQuotaExceeded)> GetOnTheFlyDocsResponseCoreAsync(string prompt, CancellationToken cancellationToken);
protected abstract Task<bool> IsFileExcludedCoreAsync(string filePath, CancellationToken cancellationToken);
protected abstract Task<(Dictionary<string, string>? responseDictionary, bool isQuotaExceeded)> GetDocumentationCommentCoreAsync(DocumentationCommentProposal proposal, CancellationToken cancellationToken);

Expand Down Expand Up @@ -174,12 +176,16 @@ public async Task StartRefinementSessionAsync(Document oldDocument, Document new
await StartRefinementSessionCoreAsync(oldDocument, newDocument, primaryDiagnostic, cancellationToken).ConfigureAwait(false);
}

public async Task<(string responseString, bool isQuotaExceeded)> GetOnTheFlyDocsAsync(string symbolSignature, ImmutableArray<string> declarationCode, string language, CancellationToken cancellationToken)
public async Task<string> GetOnTheFlyDocsPromptAsync(OnTheFlyDocsInfo onTheFlyDocsInfo, CancellationToken cancellationToken)
{
return await GetOnTheFlyDocsPromptCoreAsync(onTheFlyDocsInfo, cancellationToken).ConfigureAwait(false);
}
public async Task<(string responseString, bool isQuotaExceeded)> GetOnTheFlyDocsResponseAsync(string prompt, CancellationToken cancellationToken)
{
if (!await IsAvailableAsync(cancellationToken).ConfigureAwait(false))
return (string.Empty, false);

return await GetOnTheFlyDocsCoreAsync(symbolSignature, declarationCode, language, cancellationToken).ConfigureAwait(false);
return await GetOnTheFlyDocsResponseCoreAsync(prompt, cancellationToken).ConfigureAwait(false);
}

public async Task<bool> IsFileExcludedAsync(string filePath, CancellationToken cancellationToken)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.LanguageService;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.QuickInfo;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;

Expand All @@ -26,23 +27,29 @@ internal sealed class CSharpCopilotCodeAnalysisService : AbstractCopilotCodeAnal
{
private IExternalCSharpCopilotCodeAnalysisService? AnalysisService { get; }
private IExternalCSharpCopilotGenerateDocumentationService? GenerateDocumentationService { get; }
private IExternalCSharpOnTheFlyDocsService? OnTheFlyDocsService { get; }

[ImportingConstructor]
[Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
public CSharpCopilotCodeAnalysisService(
[Import(AllowDefault = true)] IExternalCSharpCopilotCodeAnalysisService? externalCopilotService,
[Import(AllowDefault = true)] IExternalCSharpCopilotGenerateDocumentationService? externalCSharpCopilotGenerateDocumentationService,
[Import(AllowDefault = true)] IExternalCSharpOnTheFlyDocsService? externalCSharpOnTheFlyDocsService,
IDiagnosticsRefresher diagnosticsRefresher
) : base(diagnosticsRefresher)
{
if (externalCopilotService is null)
FatalError.ReportAndCatch(new NullReferenceException("ExternalCSharpCopilotCodeAnalysisService is unavailable."), ErrorSeverity.Diagnostic);
FatalError.ReportAndCatch(new ArgumentNullException(nameof(externalCopilotService)), ErrorSeverity.Diagnostic);

if (externalCSharpCopilotGenerateDocumentationService is null)
FatalError.ReportAndCatch(new NullReferenceException("ExternalCSharpCopilotGenerateDocumentationService is unavailable."), ErrorSeverity.Diagnostic);
FatalError.ReportAndCatch(new ArgumentNullException(nameof(externalCSharpCopilotGenerateDocumentationService)), ErrorSeverity.Diagnostic);

if (externalCSharpOnTheFlyDocsService is null)
FatalError.ReportAndCatch(new ArgumentNullException(nameof(externalCSharpOnTheFlyDocsService)), ErrorSeverity.Diagnostic);

AnalysisService = externalCopilotService;
GenerateDocumentationService = externalCSharpCopilotGenerateDocumentationService;
OnTheFlyDocsService = externalCSharpOnTheFlyDocsService;
}

protected override Task<ImmutableArray<Diagnostic>> AnalyzeDocumentCoreAsync(Document document, TextSpan? span, string promptTitle, CancellationToken cancellationToken)
Expand Down Expand Up @@ -85,10 +92,18 @@ protected override Task StartRefinementSessionCoreAsync(Document oldDocument, Do
return Task.CompletedTask;
}

protected override Task<(string responseString, bool isQuotaExceeded)> GetOnTheFlyDocsCoreAsync(string symbolSignature, ImmutableArray<string> declarationCode, string language, CancellationToken cancellationToken)
protected override Task<string> GetOnTheFlyDocsPromptCoreAsync(OnTheFlyDocsInfo onTheFlyDocsInfo, CancellationToken cancellationToken)
{
if (AnalysisService is not null)
return AnalysisService.GetOnTheFlyDocsAsync(symbolSignature, declarationCode, language, cancellationToken);
if (OnTheFlyDocsService is not null)
return OnTheFlyDocsService.GetOnTheFlyDocsPromptAsync(new CopilotOnTheFlyDocsInfoWrapper(onTheFlyDocsInfo), cancellationToken);

return Task.FromResult(string.Empty);
}

protected override Task<(string responseString, bool isQuotaExceeded)> GetOnTheFlyDocsResponseCoreAsync(string prompt, CancellationToken cancellationToken)
{
if (OnTheFlyDocsService is not null)
return OnTheFlyDocsService.GetOnTheFlyDocsResponseAsync(prompt, cancellationToken);

return Task.FromResult((string.Empty, false));
}
Expand Down
Loading