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

Optimize helper we use to determine the relevant node of a particular kind the user is on. #73812

Merged
merged 16 commits into from
Jun 3, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,9 @@ private static void ThrowSequenceContainsMoreThanOneElement()
return result;
}

public static T? FirstOrDefault<T>(this in TemporaryArray<T> array)
=> array.Count > 0 ? array[0] : default;

public static T? FirstOrDefault<T, TArg>(this in TemporaryArray<T> array, Func<T, TArg, bool> predicate, TArg arg)
{
foreach (var item in array)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.CSharp.Utilities;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.LanguageService;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Simplification;

namespace Microsoft.CodeAnalysis.CSharp.CodeRefactorings.UseType;
Expand Down Expand Up @@ -94,25 +92,24 @@ private static async Task<SyntaxNode> GetDeclarationAsync(CodeRefactoringContext
// `ref var` is a bit of an interesting construct. 'ref' looks like a modifier, but is actually a
// type-syntax. Ensure the user can get the feature anywhere on this construct
var type = await context.TryGetRelevantNodeAsync<TypeSyntax>().ConfigureAwait(false);
if (type?.Parent is RefTypeSyntax)
type = (TypeSyntax)type.Parent;
var typeParent = type?.Parent;
if (typeParent is RefTypeSyntax refType)
type = refType;

if (type?.Parent is VariableDeclarationSyntax)
return type.Parent;

var foreachStatement = await context.TryGetRelevantNodeAsync<ForEachStatementSyntax>().ConfigureAwait(false);
if (foreachStatement != null)
return foreachStatement;
var foreachStatement1 = await context.TryGetRelevantNodeAsync<ForEachStatementSyntax>().ConfigureAwait(false);
if (foreachStatement1 != null)
return foreachStatement1;

var syntaxFacts = context.Document.GetLanguageService<ISyntaxFactsService>();
if (type?.Parent is DeclarationExpressionSyntax or VariableDeclarationSyntax)
return type.Parent;

var typeNode = await context.TryGetRelevantNodeAsync<TypeSyntax>().ConfigureAwait(false);
var typeNodeParent = typeNode?.Parent;
if (typeNodeParent != null &&
(typeNodeParent.Kind() is SyntaxKind.DeclarationExpression or SyntaxKind.VariableDeclaration ||
(typeNodeParent.IsKind(SyntaxKind.ForEachStatement) && !syntaxFacts.IsExpressionOfForeach(typeNode))))
if (type?.Parent is ForEachStatementSyntax foreachStatement2 &&
foreachStatement2.Type == type)
{
return typeNodeParent;
return foreachStatement2;
}

return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,10 @@
namespace Microsoft.CodeAnalysis.CSharp.CodeRefactorings.UseExplicitType;

[ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = PredefinedCodeRefactoringProviderNames.UseExplicitType), Shared]
internal class UseExplicitTypeCodeRefactoringProvider : AbstractUseTypeCodeRefactoringProvider
[method: ImportingConstructor]
[method: SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")]
internal sealed class UseExplicitTypeCodeRefactoringProvider() : AbstractUseTypeCodeRefactoringProvider
{
[ImportingConstructor]
[SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")]
public UseExplicitTypeCodeRefactoringProvider()
{
}

protected override string Title
=> CSharpAnalyzersResources.Use_explicit_type;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,10 @@
namespace Microsoft.CodeAnalysis.CSharp.CodeRefactorings.UseImplicitType;

[ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = PredefinedCodeRefactoringProviderNames.UseImplicitType), Shared]
internal partial class UseImplicitTypeCodeRefactoringProvider : AbstractUseTypeCodeRefactoringProvider
[method: ImportingConstructor]
[method: SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")]
internal sealed partial class UseImplicitTypeCodeRefactoringProvider() : AbstractUseTypeCodeRefactoringProvider
{
[ImportingConstructor]
[SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")]
public UseImplicitTypeCodeRefactoringProvider()
{
}

protected override string Title
=> CSharpAnalyzersResources.Use_implicit_type;

Expand Down
22 changes: 7 additions & 15 deletions src/Features/Core/Portable/CodeRefactoringHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,17 @@ internal static class CodeRefactoringHelpers
/// Determines if a <paramref name="node"/> is under-selected given <paramref name="selection"/>.
/// </para>
/// <para>
/// Under-selection is defined as omitting whole nodes from either the beginning or the end. It can be used e.g.
/// to detect that following selection `1 + [|2 + 3|]` is under-selecting the whole expression node tree.
/// Under-selection is defined as omitting whole nodes from either the beginning or the end. It can be used e.g. to
/// detect that following selection `1 + [|2 + 3|]` is under-selecting the whole expression node tree.
/// </para>
/// <para>
/// Returns false if only and precisely one <see cref="SyntaxToken"/> is selected. In that case the <paramref
/// name="selection"/> is treated more as a caret location.
/// </para>
/// <para>
/// It's intended to be used in conjunction with <see
/// cref="IRefactoringHelpersService.GetRelevantNodesAsync{TSyntaxNode}(Document, TextSpan, bool,
/// CancellationToken)"/> that, for non-empty selections, returns the smallest encompassing node. A node that
/// can, for certain refactorings, be too large given user-selection even though it is the smallest that can be
/// retrieved.
/// It's intended to be used in conjunction with <see cref="IRefactoringHelpersService.AddRelevantNodes"/> that, for
/// non-empty selections, returns the smallest encompassing node. A node that can, for certain refactorings, be too
/// large given user-selection even though it is the smallest that can be retrieved.
/// </para>
/// <para>
/// When <paramref name="selection"/> doesn't intersect the node in any way it's not considered to be
Expand Down Expand Up @@ -105,26 +103,20 @@ public static bool IsNodeUnderselected(SyntaxNode? node, TextSpan selection)
/// Returns unchanged <paramref name="span"/> in case <see cref="TextSpan.IsEmpty"/>.
/// Returns empty Span with original <see cref="TextSpan.Start"/> in case it contains only whitespace.
/// </remarks>
public static async Task<TextSpan> GetTrimmedTextSpanAsync(Document document, TextSpan span, CancellationToken cancellationToken)
public static TextSpan GetTrimmedTextSpan(ParsedDocument document, TextSpan span)
{
if (span.IsEmpty)
{
return span;
}

var sourceText = await document.GetValueTextAsync(cancellationToken).ConfigureAwait(false);
var sourceText = document.Text;
var start = span.Start;
var end = span.End;

while (start < end && char.IsWhiteSpace(sourceText[end - 1]))
{
end--;
}

while (start < end && char.IsWhiteSpace(sourceText[start]))
{
start++;
}

return TextSpan.FromBounds(start, end);
}
Expand Down
Loading