From 1b2bab76b1d6e876d0342ee882ba1ef61181ba05 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 18 Dec 2024 08:54:52 -0800 Subject: [PATCH 1/3] Filter out inline hint spans out of hte bounds of hte span we are querying for --- .../InlineHints/AbstractInlineParameterNameHintsService.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Features/Core/Portable/InlineHints/AbstractInlineParameterNameHintsService.cs b/src/Features/Core/Portable/InlineHints/AbstractInlineParameterNameHintsService.cs index f1e03c625d84e..f5e53fc1a0d28 100644 --- a/src/Features/Core/Portable/InlineHints/AbstractInlineParameterNameHintsService.cs +++ b/src/Features/Core/Portable/InlineHints/AbstractInlineParameterNameHintsService.cs @@ -84,6 +84,13 @@ void AddHintsIfAppropriate(SyntaxNode node) foreach (var (position, argument, parameter, kind) in buffer) { + // We get hints on *nodes* that intersect the passed in text span. However, while the full node may + // intersect the span, the positions of the all the sub-nodes in it that we make hints for (like the + // positions of the arguments in an invocation) may not. So, filter out any hints that aren't actually + // in the span we care about here. + if (textSpan.IntersectsWith(position)) + continue; + if (string.IsNullOrEmpty(parameter?.Name)) continue; From 87f454228bda2e90486851baa033c6bf4752da33 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 18 Dec 2024 09:06:58 -0800 Subject: [PATCH 2/3] Add tests --- .../InlineHints/AbstractInlineHintsTests.vb | 12 +++--- .../CSharpInlineParameterNameHintsTests.vb | 43 +++++++++++++++++++ .../InlineHints/CSharpInlineTypeHintsTests.vb | 41 ++++++++++++++++++ .../InlineHints/CSharpInlineHintsService.cs | 11 ++--- ...AbstractInlineParameterNameHintsService.cs | 2 +- .../AbstractInlineTypeHintsService.cs | 19 +++++--- .../VisualBasicInlineHintsService.vb | 2 +- 7 files changed, 108 insertions(+), 22 deletions(-) diff --git a/src/EditorFeatures/Test2/InlineHints/AbstractInlineHintsTests.vb b/src/EditorFeatures/Test2/InlineHints/AbstractInlineHintsTests.vb index 8010a93bedcbc..65b0fff2670fb 100644 --- a/src/EditorFeatures/Test2/InlineHints/AbstractInlineHintsTests.vb +++ b/src/EditorFeatures/Test2/InlineHints/AbstractInlineHintsTests.vb @@ -4,12 +4,8 @@ Imports System.Collections.Immutable Imports System.Threading -Imports Microsoft.CodeAnalysis.Editor.InlineHints -Imports Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces Imports Microsoft.CodeAnalysis.InlineHints Imports Microsoft.CodeAnalysis.LanguageService -Imports Microsoft.CodeAnalysis.Options -Imports Microsoft.CodeAnalysis.[Shared].Utilities Imports Microsoft.CodeAnalysis.Text Namespace Microsoft.CodeAnalysis.Editor.UnitTests.InlineHints @@ -30,8 +26,10 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.InlineHints Dim snapshot = hostDocument.GetTextBuffer().CurrentSnapshot Dim document = workspace.CurrentSolution.GetDocument(hostDocument.Id) Dim tagService = document.GetRequiredLanguageService(Of IInlineParameterNameHintsService) + + Dim span = If(hostDocument.SelectedSpans.Any(), hostDocument.SelectedSpans.Single(), New TextSpan(0, snapshot.Length)) Dim inlineHints = Await tagService.GetInlineHintsAsync( - document, New Text.TextSpan(0, snapshot.Length), options, displayOptions, displayAllOverride:=False, CancellationToken.None) + document, span, options, displayOptions, displayAllOverride:=False, CancellationToken.None) Dim producedTags = From hint In inlineHints Select hint.DisplayParts.GetFullText().TrimEnd() + hint.Span.ToString @@ -88,8 +86,10 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.InlineHints Dim snapshot = hostDocument.GetTextBuffer().CurrentSnapshot Dim document = workspace.CurrentSolution.GetDocument(hostDocument.Id) Dim tagService = document.GetRequiredLanguageService(Of IInlineTypeHintsService) + + Dim span = If(hostDocument.SelectedSpans.Any(), hostDocument.SelectedSpans.Single(), New TextSpan(0, snapshot.Length)) Dim typeHints = Await tagService.GetInlineHintsAsync( - document, New Text.TextSpan(0, snapshot.Length), options, displayOptions, displayAllOverride:=ephemeral, CancellationToken.None) + document, span, options, displayOptions, displayAllOverride:=ephemeral, CancellationToken.None) Dim producedTags = From hint In typeHints Select hint.DisplayParts.GetFullText() + ":" + hint.Span.ToString() diff --git a/src/EditorFeatures/Test2/InlineHints/CSharpInlineParameterNameHintsTests.vb b/src/EditorFeatures/Test2/InlineHints/CSharpInlineParameterNameHintsTests.vb index ae22b2ce65d06..c310a6050f48b 100644 --- a/src/EditorFeatures/Test2/InlineHints/CSharpInlineParameterNameHintsTests.vb +++ b/src/EditorFeatures/Test2/InlineHints/CSharpInlineParameterNameHintsTests.vb @@ -1255,5 +1255,48 @@ class C Await VerifyParamHints(input, output) End Function + + + Public Async Function TestOnlyProduceTagsWithinSelection() As Task + Dim input = + + + +class A +{ + int testMethod(int a, int b, int c, int d, int e) + { + return x; + } + void Main() + { + testMethod(1, [|{|b:|}2, {|c:|}3, {|d:|}4|], 5); + } +} + + + + + Dim output = + + + +class A +{ + int testMethod(int a, int b, int c, int d, int e) + { + return x; + } + void Main() + { + testMethod(1, b: 2, c: 3, d: 4, 5); + } +} + + + + + Await VerifyParamHints(input, output) + End Function End Class End Namespace diff --git a/src/EditorFeatures/Test2/InlineHints/CSharpInlineTypeHintsTests.vb b/src/EditorFeatures/Test2/InlineHints/CSharpInlineTypeHintsTests.vb index 4277f072d60f1..29e6d1924f221 100644 --- a/src/EditorFeatures/Test2/InlineHints/CSharpInlineTypeHintsTests.vb +++ b/src/EditorFeatures/Test2/InlineHints/CSharpInlineTypeHintsTests.vb @@ -848,5 +848,46 @@ class A Await VerifyTypeHints(input, output) End Function + + + Public Async Function TestOnlyProduceTagsWithinSelection() As Task + Dim input = + + + +class A +{ + void Main() + { + var a = 0; + [|var {|int :|}b = 0; + var {|int :|}c = 0;|] + var d = 0; + } +} + + + + + Dim output = + + + +class A +{ + void Main() + { + var a = 0; + int b = 0; + int c = 0; + var d = 0; + } +} + + + + + Await VerifyParamHints(input, output) + End Function End Class End Namespace diff --git a/src/Features/CSharp/Portable/InlineHints/CSharpInlineHintsService.cs b/src/Features/CSharp/Portable/InlineHints/CSharpInlineHintsService.cs index 97ffaecd42a4f..8239eb0700eff 100644 --- a/src/Features/CSharp/Portable/InlineHints/CSharpInlineHintsService.cs +++ b/src/Features/CSharp/Portable/InlineHints/CSharpInlineHintsService.cs @@ -13,11 +13,6 @@ namespace Microsoft.CodeAnalysis.CSharp.InlineHints; /// The service to locate all positions where inline hints should be placed. /// [ExportLanguageService(typeof(IInlineHintsService), LanguageNames.CSharp), Shared] -internal class CSharpInlineHintsService : AbstractInlineHintsService -{ - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public CSharpInlineHintsService() - { - } -} +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class CSharpInlineHintsService() : AbstractInlineHintsService; diff --git a/src/Features/Core/Portable/InlineHints/AbstractInlineParameterNameHintsService.cs b/src/Features/Core/Portable/InlineHints/AbstractInlineParameterNameHintsService.cs index f5e53fc1a0d28..e447e36df607b 100644 --- a/src/Features/Core/Portable/InlineHints/AbstractInlineParameterNameHintsService.cs +++ b/src/Features/Core/Portable/InlineHints/AbstractInlineParameterNameHintsService.cs @@ -88,7 +88,7 @@ void AddHintsIfAppropriate(SyntaxNode node) // intersect the span, the positions of the all the sub-nodes in it that we make hints for (like the // positions of the arguments in an invocation) may not. So, filter out any hints that aren't actually // in the span we care about here. - if (textSpan.IntersectsWith(position)) + if (!textSpan.IntersectsWith(position)) continue; if (string.IsNullOrEmpty(parameter?.Name)) diff --git a/src/Features/Core/Portable/InlineHints/AbstractInlineTypeHintsService.cs b/src/Features/Core/Portable/InlineHints/AbstractInlineTypeHintsService.cs index b97e07e7cfc2a..f3ba58b93b374 100644 --- a/src/Features/Core/Portable/InlineHints/AbstractInlineTypeHintsService.cs +++ b/src/Features/Core/Portable/InlineHints/AbstractInlineTypeHintsService.cs @@ -15,7 +15,7 @@ namespace Microsoft.CodeAnalysis.InlineHints; internal abstract class AbstractInlineTypeHintsService : IInlineTypeHintsService { - protected static readonly SymbolDisplayFormat s_minimalTypeStyle = new SymbolDisplayFormat( + protected static readonly SymbolDisplayFormat s_minimalTypeStyle = new( genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters, miscellaneousOptions: SymbolDisplayMiscellaneousOptions.AllowDefaultLiteral | SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier | SymbolDisplayMiscellaneousOptions.UseSpecialTypes); @@ -55,7 +55,7 @@ public async Task> GetInlineHintsAsync( foreach (var node in root.DescendantNodes(n => n.Span.IntersectsWith(textSpan))) { - var hintOpt = TryGetTypeHint( + var hint = TryGetTypeHint( semanticModel, node, displayAllOverride, forImplicitVariableTypes, @@ -63,16 +63,23 @@ public async Task> GetInlineHintsAsync( forImplicitObjectCreation, forCollectionExpressions, cancellationToken); - if (hintOpt == null) + if (hint is not var (type, span, textChange, prefix, suffix)) continue; - var (type, span, textChange, prefix, suffix) = hintOpt.Value; + var spanStart = span.Start; + + // We get hints on *nodes* that intersect the passed in text span. However, while the full node may + // intersect the span, the positions of the all the sub-nodes in it that we make hints for (like the + // positions of the arguments in an invocation) may not. So, filter out any hints that aren't actually + // in the span we care about here. + if (!textSpan.IntersectsWith(spanStart)) + continue; using var _2 = ArrayBuilder.GetInstance(out var finalParts); finalParts.AddRange(prefix); var parts = type.ToDisplayParts(s_minimalTypeStyle); - AddParts(anonymousTypeService, finalParts, parts, semanticModel, span.Start); + AddParts(anonymousTypeService, finalParts, parts, semanticModel, spanStart); // If we have nothing to show, then don't bother adding this hint. if (finalParts.All(p => string.IsNullOrWhiteSpace(p.ToString()))) @@ -83,7 +90,7 @@ public async Task> GetInlineHintsAsync( result.Add(new InlineHint( span, taggedText, textChange, ranking: InlineHintsConstants.TypeRanking, - InlineHintHelpers.GetDescriptionFunction(span.Start, type.GetSymbolKey(cancellationToken: cancellationToken), displayOptions))); + InlineHintHelpers.GetDescriptionFunction(spanStart, type.GetSymbolKey(cancellationToken), displayOptions))); } return result.ToImmutableAndClear(); diff --git a/src/Features/VisualBasic/Portable/InlineHints/VisualBasicInlineHintsService.vb b/src/Features/VisualBasic/Portable/InlineHints/VisualBasicInlineHintsService.vb index 642cb370019ee..bfa281ed9e969 100644 --- a/src/Features/VisualBasic/Portable/InlineHints/VisualBasicInlineHintsService.vb +++ b/src/Features/VisualBasic/Portable/InlineHints/VisualBasicInlineHintsService.vb @@ -11,7 +11,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.InlineHints ''' The service to locate all positions where inline hints should be placed. ''' - Friend Class VisualBasicInlineHintsService + Friend NotInheritable Class VisualBasicInlineHintsService Inherits AbstractInlineHintsService From f87368fd0f46ec67eec45f9336a2d8516478a4dc Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 18 Dec 2024 09:08:45 -0800 Subject: [PATCH 3/3] Fix test --- .../Test2/InlineHints/CSharpInlineTypeHintsTests.vb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/EditorFeatures/Test2/InlineHints/CSharpInlineTypeHintsTests.vb b/src/EditorFeatures/Test2/InlineHints/CSharpInlineTypeHintsTests.vb index 29e6d1924f221..69a514ea513f0 100644 --- a/src/EditorFeatures/Test2/InlineHints/CSharpInlineTypeHintsTests.vb +++ b/src/EditorFeatures/Test2/InlineHints/CSharpInlineTypeHintsTests.vb @@ -887,7 +887,7 @@ class A - Await VerifyParamHints(input, output) + Await VerifyTypeHints(input, output) End Function End Class End Namespace