| File: AddDebuggerDisplay\AbstractAddDebuggerDisplayCodeRefactoringProvider.cs | Web Access |
| Project: src\roslyn\src\Features\Core\Portable\Microsoft.CodeAnalysis.Features.csproj (Microsoft.CodeAnalysis.Features) |
// 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 System; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeRefactorings; using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Simplification; namespace Microsoft.CodeAnalysis.AddDebuggerDisplay; internal abstract class AbstractAddDebuggerDisplayCodeRefactoringProvider< TTypeDeclarationSyntax, TMethodDeclarationSyntax> : CodeRefactoringProvider where TTypeDeclarationSyntax : SyntaxNode where TMethodDeclarationSyntax : SyntaxNode { private const string DebuggerDisplayPrefix = "{"; private const string DebuggerDisplayMethodName = "GetDebuggerDisplay"; private const string DebuggerDisplaySuffix = "(),nq}"; protected abstract bool CanNameofAccessNonPublicMembersFromAttributeArgument { get; } protected abstract bool SupportsConstantInterpolatedStrings(Document document); public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context) { var (document, _, cancellationToken) = context; var typeAndPriority = await GetRelevantTypeFromHeaderAsync(context).ConfigureAwait(false) ?? await GetRelevantTypeFromMethodAsync(context).ConfigureAwait(false); if (typeAndPriority == null) return; var (type, priority) = typeAndPriority.Value; var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); var compilation = semanticModel.Compilation; var debuggerAttributeTypeSymbol = compilation.GetTypeByMetadataName("System.Diagnostics.DebuggerDisplayAttribute"); if (debuggerAttributeTypeSymbol is null) return; var typeSymbol = (INamedTypeSymbol)semanticModel.GetRequiredDeclaredSymbol(type, cancellationToken); if (typeSymbol.IsStatic || !IsClassOrStruct(typeSymbol)) return; if (HasDebuggerDisplayAttribute(typeSymbol, compilation)) return; context.RegisterRefactoring(CodeAction.Create( FeaturesResources.Add_DebuggerDisplay_attribute, cancellationToken => ApplyAsync(document, type, debuggerAttributeTypeSymbol, cancellationToken), nameof(FeaturesResources.Add_DebuggerDisplay_attribute), priority)); } private static async Task<(TTypeDeclarationSyntax type, CodeActionPriority priority)?> GetRelevantTypeFromHeaderAsync(CodeRefactoringContext context) { var type = await context.TryGetRelevantNodeAsync<TTypeDeclarationSyntax>().ConfigureAwait(false); if (type is null) return null; return (type, CodeActionPriority.Low); } private static async Task<(TTypeDeclarationSyntax type, CodeActionPriority priority)?> GetRelevantTypeFromMethodAsync(CodeRefactoringContext context) { var (document, _, cancellationToken) = context; var method = await context.TryGetRelevantNodeAsync<TMethodDeclarationSyntax>().ConfigureAwait(false); if (method == null) return null; var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); var methodSymbol = (IMethodSymbol)semanticModel.GetRequiredDeclaredSymbol(method, cancellationToken); var isDebuggerDisplayMethod = IsDebuggerDisplayMethod(methodSymbol); if (!isDebuggerDisplayMethod && !IsToStringMethod(methodSymbol)) return null; // Show the feature if we're on a ToString or GetDebuggerDisplay method. For the former, // have this be low-pri so we don't override more important light-bulb options. var typeDecl = method.FirstAncestorOrSelf<TTypeDeclarationSyntax>(); if (typeDecl == null) return null; var priority = isDebuggerDisplayMethod ? CodeActionPriority.Default : CodeActionPriority.Low; return (typeDecl, priority); } private static bool IsToStringMethod(IMethodSymbol methodSymbol) => methodSymbol is { Name: nameof(ToString), Arity: 0, Parameters.IsEmpty: true }; private static bool IsDebuggerDisplayMethod(IMethodSymbol methodSymbol) => methodSymbol is { Name: DebuggerDisplayMethodName, Arity: 0, Parameters.IsEmpty: true }; private static bool IsClassOrStruct(ITypeSymbol typeSymbol) => typeSymbol.TypeKind is TypeKind.Class or TypeKind.Struct; private static bool HasDebuggerDisplayAttribute(ITypeSymbol typeSymbol, Compilation compilation) => typeSymbol.GetAttributes() .Select(data => data.AttributeClass) .Contains(compilation.GetTypeByMetadataName("System.Diagnostics.DebuggerDisplayAttribute")); private async Task<Document> ApplyAsync(Document document, TTypeDeclarationSyntax type, INamedTypeSymbol debuggerAttributeTypeSymbol, CancellationToken cancellationToken) { var syntaxRoot = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); var editor = new SyntaxEditor(syntaxRoot, document.Project.Solution.Services); var generator = editor.Generator; SyntaxNode attributeArgument; if (CanNameofAccessNonPublicMembersFromAttributeArgument) { if (SupportsConstantInterpolatedStrings(document)) { attributeArgument = generator.InterpolatedStringExpression( generator.CreateInterpolatedStringStartToken(isVerbatim: false), [ generator.InterpolatedStringText(generator.InterpolatedStringTextToken("{{", "{{")), generator.Interpolation(generator.NameOfExpression(generator.IdentifierName(DebuggerDisplayMethodName))), generator.InterpolatedStringText(generator.InterpolatedStringTextToken("(),nq}}", "(),nq}}")), ], generator.CreateInterpolatedStringEndToken()); } else { attributeArgument = generator.AddExpression( generator.AddExpression( generator.LiteralExpression(DebuggerDisplayPrefix), generator.NameOfExpression(generator.IdentifierName(DebuggerDisplayMethodName))), generator.LiteralExpression(DebuggerDisplaySuffix)); } } else { attributeArgument = generator.LiteralExpression( DebuggerDisplayPrefix + DebuggerDisplayMethodName + DebuggerDisplaySuffix); } var newAttribute = generator .Attribute(generator.TypeExpression(debuggerAttributeTypeSymbol), [attributeArgument]) .WithAdditionalAnnotations( Simplifier.Annotation, Simplifier.AddImportsAnnotation); editor.AddAttribute(type, newAttribute); var typeSymbol = (INamedTypeSymbol)semanticModel.GetRequiredDeclaredSymbol(type, cancellationToken); if (!typeSymbol.GetMembers().OfType<IMethodSymbol>().Any(IsDebuggerDisplayMethod)) { editor.AddMember(type, generator.MethodDeclaration( DebuggerDisplayMethodName, returnType: generator.TypeExpression(SpecialType.System_String), accessibility: Accessibility.Private, statements: [ generator.ReturnStatement(generator.InvocationExpression( generator.MemberAccessExpression( generator.ThisExpression(), generator.IdentifierName("ToString")))) ])); } return document.WithSyntaxRoot(editor.GetChangedRoot()); } }