Skip to content
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

Support for per-file content exclusion in Rename Suggestions #74903

Merged
merged 9 commits into from
Sep 19, 2024
Merged
Show file tree
Hide file tree
Changes from 3 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 global.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"sdk": {
"version": "9.0.100-preview.5.24307.3",
"version": "8.0.300",
olegtk marked this conversation as resolved.
Show resolved Hide resolved
"allowPrerelease": false,
"rollForward": "patch"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,13 @@ internal sealed class CSharpEditorInlineRenameService([ImportMany] IEnumerable<I
/// Uses semantic information of renamed symbol to produce a map containing contextual information for use in Copilot rename feature
/// </summary>
/// <returns>Map where key indicates the kind of semantic information, and value is an array of relevant code snippets.</returns>
public override async Task<ImmutableDictionary<string, ImmutableArray<string>>> GetRenameContextAsync(
public override async Task<ImmutableDictionary<string, ImmutableArray<(string filePath, string content)>>> GetRenameContextAsync(
IInlineRenameInfo inlineRenameInfo, IInlineRenameLocationSet inlineRenameLocationSet, CancellationToken cancellationToken)
{
using var _1 = PooledHashSet<TextSpan>.GetInstance(out var seen);
using var _2 = ArrayBuilder<string>.GetInstance(out var definitions);
using var _3 = ArrayBuilder<string>.GetInstance(out var references);
using var _4 = ArrayBuilder<string>.GetInstance(out var docComments);
using var _2 = ArrayBuilder<(string filePath, string content)>.GetInstance(out var definitions);
using var _3 = ArrayBuilder<(string filePath, string content)>.GetInstance(out var references);
using var _4 = ArrayBuilder<(string filePath, string content)>.GetInstance(out var docComments);

foreach (var renameDefinition in inlineRenameInfo.DefinitionLocations.Take(MaxDefinitionCount))
{
Expand All @@ -59,7 +59,11 @@ await TryGetSurroundingNodeSpanAsync<MemberDeclarationSyntax>(renameDefinition.D
var docComment = symbol?.GetDocumentationCommentXml(expandIncludes: true, cancellationToken: cancellationToken);
if (!string.IsNullOrWhiteSpace(docComment))
{
docComments.Add(docComment!);
var filePath = renameDefinition.Document.FilePath;
if (filePath != null)
{
docComments.Add((filePath, docComment));
}
}
}

Expand All @@ -79,7 +83,7 @@ await TryGetSurroundingNodeSpanAsync<BaseMethodDeclarationSyntax>(renameLocation
AddSpanOfInterest(documentText, renameLocation.TextSpan, containingStatementOrDeclarationSpan, references);
}

var contextBuilder = ImmutableDictionary.CreateBuilder<string, ImmutableArray<string>>();
var contextBuilder = ImmutableDictionary.CreateBuilder<string, ImmutableArray<(string filePath, string content)>>();
if (!definitions.IsEmpty)
{
contextBuilder.Add("definition", definitions.ToImmutable());
Expand All @@ -95,7 +99,7 @@ await TryGetSurroundingNodeSpanAsync<BaseMethodDeclarationSyntax>(renameLocation

return contextBuilder.ToImmutableDictionary();

void AddSpanOfInterest(SourceText documentText, TextSpan fallbackSpan, TextSpan? surroundingSpanOfInterest, ArrayBuilder<string> resultBuilder)
void AddSpanOfInterest(SourceText documentText, TextSpan fallbackSpan, TextSpan? surroundingSpanOfInterest, ArrayBuilder<(string filePath, string content)> resultBuilder)
{
int startPosition, endPosition, startLine = 0, endLine = 0, lineCount = 0;
if (surroundingSpanOfInterest is not null)
Expand Down Expand Up @@ -149,10 +153,13 @@ void AddSpanOfInterest(SourceText documentText, TextSpan fallbackSpan, TextSpan?
var length = endPosition - startPosition;

surroundingSpanOfInterest = new TextSpan(startPosition, length);

if (seen.Add(surroundingSpanOfInterest.Value))
{
resultBuilder.Add(documentText.GetSubText(surroundingSpanOfInterest.Value).ToString());
var filePath = documentText.GetDocumentWithFrozenPartialSemantics(cancellationToken)?.FilePath;
olegtk marked this conversation as resolved.
Show resolved Hide resolved
if (filePath != null)
{
resultBuilder.Add((filePath, documentText.GetSubText(surroundingSpanOfInterest.Value).ToString()));
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Rename
[Trait(Traits.Feature, Traits.Features.Rename)]
public class CSharpInlineRenameServiceTests
{
private class ContextDictionaryComparer : IEqualityComparer<ImmutableDictionary<string, ImmutableArray<string>>?>
private class ContextDictionaryComparer : IEqualityComparer<ImmutableDictionary<string, ImmutableArray<(string filePath, string content)>>?>
{
public static ContextDictionaryComparer Instance = new();

public bool Equals(ImmutableDictionary<string, ImmutableArray<string>>? x, ImmutableDictionary<string, ImmutableArray<string>>? y)
public bool Equals(ImmutableDictionary<string, ImmutableArray<(string filePath, string content)>>? x, ImmutableDictionary<string, ImmutableArray<(string filePath, string content)>>? y)
{
if (x == y)
return true;
Expand All @@ -52,8 +52,8 @@ public bool Equals(ImmutableDictionary<string, ImmutableArray<string>>? x, Immut
return true;
}

public int GetHashCode(ImmutableDictionary<string, ImmutableArray<string>>? obj)
=> EqualityComparer<ImmutableDictionary<string, ImmutableArray<string>>?>.Default.GetHashCode(obj);
public int GetHashCode(ImmutableDictionary<string, ImmutableArray<(string filePath, string content)>>? obj)
=> EqualityComparer<ImmutableDictionary<string, ImmutableArray<(string filePath, string content)>>?>.Default.GetHashCode(obj);
}

private static async Task VerifyGetRenameContextAsync(
Expand All @@ -67,8 +67,8 @@ private static async Task VerifyGetRenameContextAsync(
var inlineRenameInfo = await inlineRenameService.GetRenameInfoAsync(document, cursorPosition, cancellationToken).ConfigureAwait(false);
var inlineRenameLocationSet = await inlineRenameInfo.FindRenameLocationsAsync(options, cancellationToken).ConfigureAwait(false);
var context = await inlineRenameService.GetRenameContextAsync(inlineRenameInfo, inlineRenameLocationSet, cancellationToken).ConfigureAwait(false);
var expectedContext = JsonSerializer.Deserialize<ImmutableDictionary<string, ImmutableArray<string>>>(expectedContextJson);
AssertEx.AreEqual(expectedContext, context, comparer: ContextDictionaryComparer.Instance);
var expectedContext = JsonSerializer.Deserialize<ImmutableDictionary<string, ImmutableArray<(string filePath, string content)>>>(expectedContextJson);
AssertEx.AreEqual<ImmutableDictionary<string, ImmutableArray<(string filePath, string content)>>?>(expectedContext, context, comparer: ContextDictionaryComparer.Instance);
}

[Fact]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,17 +171,17 @@ await Task.Delay(_smartRenameSession.AutomaticFetchDelay, cancellationToken)
if (IsUsingSemanticContext)
{
var document = this.BaseViewModel.Session.TriggerDocument;
var smartRenameContext = ImmutableDictionary<string, string[]>.Empty;
var smartRenameContext = ImmutableDictionary<string, ImmutableArray<(string filePath, string content)>>.Empty;
try
{
var editorRenameService = document.GetRequiredLanguageService<IEditorInlineRenameService>();
var renameLocations = await this.BaseViewModel.Session.AllRenameLocationsTask.JoinAsync(cancellationToken)
.ConfigureAwait(false);
var context = await editorRenameService.GetRenameContextAsync(this.BaseViewModel.Session.RenameInfo, renameLocations, cancellationToken)
.ConfigureAwait(false);
smartRenameContext = ImmutableDictionary.CreateRange<string, string[]>(
smartRenameContext = ImmutableDictionary.CreateRange<string, ImmutableArray<(string filePath, string content)>>(
context
.Select(n => new KeyValuePair<string, string[]>(n.Key, n.Value.ToArray())));
.Select(n => new KeyValuePair<string, ImmutableArray<(string filePath, string content)>>(n.Key, n.Value)));
}
catch (Exception e) when (FatalError.ReportAndCatch(e, ErrorSeverity.Diagnostic))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@
using System.Collections.Immutable;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.Text.Editor.SmartRename;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.EditorFeatures.Lightup;
Expand All @@ -19,7 +19,10 @@ namespace Microsoft.CodeAnalysis.EditorFeatures.Lightup;
internal readonly struct ISmartRenameSessionWrapper : INotifyPropertyChanged, IDisposable
{
internal const string WrappedTypeName = "Microsoft.VisualStudio.Text.Editor.SmartRename.ISmartRenameSession";
internal const string WrappedRenameContextTypeName = "Microsoft.VisualStudio.Text.Editor.SmartRename.RenameContext";
private static readonly Type s_wrappedType;
private static readonly Type s_wrappedRenameContextType;
private static readonly Type s_wrappedRenameContextListType;

private static readonly Func<object, TimeSpan> s_automaticFetchDelayAccessor;
private static readonly Func<object, bool> s_isAvailableAccessor;
Expand All @@ -28,9 +31,12 @@ namespace Microsoft.CodeAnalysis.EditorFeatures.Lightup;
private static readonly Func<object, string> s_statusMessageAccessor;
private static readonly Func<object, bool> s_statusMessageVisibilityAccessor;
private static readonly Func<object, IReadOnlyList<string>> s_suggestedNamesAccessor;
private static readonly Func<object?, object?> s_renameContextImmutableListCreateBuilderAccessor;
private static readonly Action<object, object> s_renameContextImmutableListBuilderAddAccessor;
private static readonly Func<object, object> s_renameContextImmutableListBuilderToArrayAccessor;

private static readonly Func<object, CancellationToken, Task<IReadOnlyList<string>>> s_getSuggestionsAsync;
private static readonly Func<object, ImmutableDictionary<string, string[]>, CancellationToken, Task<IReadOnlyList<string>>> s_getSuggestionsAsync_WithContext;
private static readonly Func<object, object, CancellationToken, Task<IReadOnlyList<string>>> s_getSuggestionsAsync_WithContext;
private static readonly Action<object> s_onCancel;
private static readonly Action<object, string> s_onSuccess;

Expand All @@ -39,6 +45,8 @@ namespace Microsoft.CodeAnalysis.EditorFeatures.Lightup;
static ISmartRenameSessionWrapper()
{
s_wrappedType = typeof(AggregateFocusInterceptor).Assembly.GetType(WrappedTypeName, throwOnError: false, ignoreCase: false);
s_wrappedRenameContextType = typeof(AggregateFocusInterceptor).Assembly.GetType(WrappedRenameContextTypeName, throwOnError: false, ignoreCase: false);
s_wrappedRenameContextListType = typeof(List<>).MakeGenericType(s_wrappedRenameContextType);

s_automaticFetchDelayAccessor = LightupHelpers.CreatePropertyAccessor<object, TimeSpan>(s_wrappedType, nameof(AutomaticFetchDelay), TimeSpan.Zero);
s_isAvailableAccessor = LightupHelpers.CreatePropertyAccessor<object, bool>(s_wrappedType, nameof(IsAvailable), false);
Expand All @@ -48,8 +56,24 @@ static ISmartRenameSessionWrapper()
s_statusMessageVisibilityAccessor = LightupHelpers.CreatePropertyAccessor<object, bool>(s_wrappedType, nameof(StatusMessageVisibility), false);
s_suggestedNamesAccessor = LightupHelpers.CreatePropertyAccessor<object, IReadOnlyList<string>>(s_wrappedType, nameof(SuggestedNames), []);

var renameContextImmutableListType = typeof(ImmutableArray<>).MakeGenericType(s_wrappedRenameContextType);
s_renameContextImmutableListCreateBuilderAccessor = LightupHelpers.CreateGenericFunctionAccessor<object?, object?>(typeof(ImmutableArray),
nameof(ImmutableArray.CreateBuilder),
s_wrappedRenameContextType,
SpecializedTasks.Null<object>());

s_renameContextImmutableListBuilderAddAccessor = LightupHelpers.CreateActionAccessor<object, object>(typeof(ImmutableArray<>.Builder).MakeGenericType(s_wrappedRenameContextType), "Add", s_wrappedRenameContextType);
s_renameContextImmutableListBuilderToArrayAccessor = LightupHelpers.CreateFunctionAccessor<object, object>(typeof(ImmutableArray<>.Builder).MakeGenericType(s_wrappedRenameContextType),
"ToImmutable",
typeof(ImmutableArray<>).MakeGenericType(s_wrappedRenameContextType));

var immutableArrayOfRenameContextType = typeof(ImmutableArray<>).MakeGenericType(s_wrappedRenameContextType);
s_getSuggestionsAsync = LightupHelpers.CreateFunctionAccessor<object, CancellationToken, Task<IReadOnlyList<string>>>(s_wrappedType, nameof(GetSuggestionsAsync), typeof(CancellationToken), SpecializedTasks.EmptyReadOnlyList<string>());
s_getSuggestionsAsync_WithContext = LightupHelpers.CreateFunctionAccessor<object, ImmutableDictionary<string, string[]>, CancellationToken, Task<IReadOnlyList<string>>>(s_wrappedType, nameof(GetSuggestionsAsync), typeof(ImmutableDictionary<string, string[]>), typeof(CancellationToken), SpecializedTasks.EmptyReadOnlyList<string>());
s_getSuggestionsAsync_WithContext = LightupHelpers.CreateFunctionAccessor<object, object, CancellationToken, Task<IReadOnlyList<string>>>(s_wrappedType,
nameof(GetSuggestionsAsync),
immutableArrayOfRenameContextType,
typeof(CancellationToken),
SpecializedTasks.EmptyReadOnlyList<string>());
s_onCancel = LightupHelpers.CreateActionAccessor<object>(s_wrappedType, nameof(OnCancel));
s_onSuccess = LightupHelpers.CreateActionAccessor<object, string>(s_wrappedType, nameof(OnSuccess), typeof(string));
}
Expand Down Expand Up @@ -96,8 +120,27 @@ public static bool IsInstance([NotNullWhen(true)] object? instance)
public Task<IReadOnlyList<string>> GetSuggestionsAsync(CancellationToken cancellationToken)
=> s_getSuggestionsAsync(_instance, cancellationToken);

public Task<IReadOnlyList<string>> GetSuggestionsAsync(ImmutableDictionary<string, string[]> context, CancellationToken cancellationToken)
=> s_getSuggestionsAsync_WithContext(_instance, context, cancellationToken);
public Task<IReadOnlyList<string>> GetSuggestionsAsync(ImmutableDictionary<string, ImmutableArray<(string filePath, string content)>> context, CancellationToken cancellationToken)
{
var renameContextArrayBuilder = s_renameContextImmutableListCreateBuilderAccessor(null);

if (renameContextArrayBuilder != null)
{
foreach (var (key, value) in context)
{
foreach (var (filePath, content) in value)
{
s_renameContextImmutableListBuilderAddAccessor(renameContextArrayBuilder, Activator.CreateInstance(s_wrappedRenameContextType, key, content, filePath));
}
}

var renameContextArray = s_renameContextImmutableListBuilderToArrayAccessor(renameContextArrayBuilder);

return s_getSuggestionsAsync_WithContext(_instance, renameContextArray, cancellationToken);
}

return s_getSuggestionsAsync(_instance, cancellationToken);
}

public void OnCancel()
=> s_onCancel(_instance);
Expand Down
56 changes: 56 additions & 0 deletions src/EditorFeatures/Core.Wpf/Lightup/LightupHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using Microsoft.CodeAnalysis.Contracts.EditAndContinue;
olegtk marked this conversation as resolved.
Show resolved Hide resolved

namespace Microsoft.CodeAnalysis.EditorFeatures.Lightup;

Expand Down Expand Up @@ -221,6 +222,61 @@ public static Action<T, TArg> CreateActionAccessor<T, TArg>(Type? type, string m
return expression.Compile();
}

/// <summary>
/// Generates a compiled accessor method for a method which cannot be bound at compile time.
/// </summary>
/// <typeparam name="T">The compile-time type representing the instance on which the property is defined. This
/// may be a superclass of the actual type on which the property is declared if the declaring type is not
/// available at compile time.</typeparam>
/// <typeparam name="TResult">The compile-type type representing the result of the property. This may be a
/// superclass of the actual type of the property if the property type is not available at compile
/// time.</typeparam>
/// <param name="type">The runtime time on which the property is defined. If this value is null, the runtime
/// time is assumed to not exist, and a fallback accessor returning <paramref name="defaultValue"/> will be
/// generated.</param>
/// <param name="methodName">The name of the method to access.</param>
/// <param name="defaultValue">The value to return if the method is not available at runtime.</param>
/// <returns>An accessor method to access the specified runtime property.</returns>
public static Func<T, TResult> CreateGenericFunctionAccessor<T, TResult>(Type? type, string methodName, Type genericArgumentType, TResult defaultValue)
{
if (methodName is null)
{
throw new ArgumentNullException(nameof(methodName));
}

if (type == null)
{
return CreateFallbackFunction<T, TResult>(defaultValue);
}

if (!typeof(T).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo()))
{
throw new InvalidOperationException($"Type '{type}' is not assignable to type '{typeof(T)}'");
}

var method = type.GetTypeInfo().GetDeclaredMethods(methodName).Single(method => method.GetParameters().Length == 0);

if (method == null)
{
return CreateFallbackFunction<T, TResult>(defaultValue);
}

if (!typeof(TResult).GetTypeInfo().IsAssignableFrom(method.ReturnType.GetTypeInfo()))
{
throw new InvalidOperationException($"Method '{method}' produces a value of type '{method.ReturnType}', which is not assignable to type '{typeof(TResult)}'");
}

method = method.MakeGenericMethod(genericArgumentType);

var parameter = Expression.Parameter(typeof(T), GenerateParameterName(typeof(T)));

var expression =
Expression.Lambda<Func<T, TResult>>(
Expression.Convert(Expression.Call(null, method), typeof(TResult)),
parameter);
return expression.Compile();
}

/// <summary>
/// Generates a compiled accessor method for a method which cannot be bound at compile time.
/// </summary>
Expand Down
Loading
Loading