/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Analyzers/Core/CodeFixes/ImplementInterface/AbstractImplementInterfaceService.cs
310 строк
14 KB
Cyrus Najmabadi
Simplify
01 авг 2025, 16:38
01 авг 2025, 16:38
8c4a56d
Код
Авторство
О чём код?
// 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.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.ImplementType; using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; namespace Microsoft.CodeAnalysis.ImplementInterface; using static ImplementHelpers; internal abstract partial class AbstractImplementInterfaceService<TTypeDeclarationSyntax> : IImplementInterfaceService where TTypeDeclarationSyntax : SyntaxNode { protected const string DisposingName = "disposing"; protected abstract SyntaxGeneratorInternal SyntaxGeneratorInternal { get; } protected abstract string ToDisplayString(IMethodSymbol disposeImplMethod, SymbolDisplayFormat format); protected abstract bool CanImplementImplicitly { get; } protected abstract bool HasHiddenExplicitImplementation { get; } protected abstract bool TryInitializeState(Document document, SemanticModel model, SyntaxNode interfaceNode, CancellationToken cancellationToken, [NotNullWhen(true)] out SyntaxNode? classOrStructDecl, [NotNullWhen(true)] out INamedTypeSymbol? classOrStructType, out ImmutableArray<INamedTypeSymbol> interfaceTypes); protected abstract bool AllowDelegateAndEnumConstraints(ParseOptions options); protected abstract SyntaxNode AddCommentInsideIfStatement(SyntaxNode ifDisposingStatement, SyntaxTriviaList trivia); protected abstract SyntaxNode CreateFinalizer(SyntaxGenerator generator, INamedTypeSymbol classType, string disposeMethodDisplayString); protected abstract bool IsTypeInInterfaceBaseList([NotNullWhen(true)] SyntaxNode? type); protected abstract void AddInterfaceTypes(TTypeDeclarationSyntax typeDeclaration, ArrayBuilder<SyntaxNode> result); public ImmutableArray<SyntaxNode> GetInterfaceTypes(SyntaxNode typeDeclaration) { using var _ = ArrayBuilder<SyntaxNode>.GetInstance(out var result); if (typeDeclaration is TTypeDeclarationSyntax typeSyntax) AddInterfaceTypes(typeSyntax, result); return result.ToImmutableAndClear(); } public async Task<Document> ImplementInterfaceAsync( Document document, ImplementTypeOptions options, SyntaxNode node, CancellationToken cancellationToken) { using (Logger.LogBlock(FunctionId.Refactoring_ImplementInterface, cancellationToken)) { var model = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); var state = State.Generate(this, document, model, node, cancellationToken); if (state == null) return document; // While implementing just one default action, like in the case of pressing enter after interface name in VB, // choose to implement with the dispose pattern as that's the Dev12 behavior. var implementDisposePattern = ShouldImplementDisposePattern(model.Compilation, state.Info, explicitly: false); var generator = new ImplementInterfaceGenerator( this, document, state.Info, options, new() { OnlyRemaining = true, ImplementDisposePattern = implementDisposePattern }); return await generator.ImplementInterfaceAsync(cancellationToken).ConfigureAwait(false); } } private async Task<ImplementInterfaceInfo?> AnalyzeAsync(Document document, SyntaxNode interfaceType, CancellationToken cancellationToken) { var model = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); return State.Generate(this, document, model, interfaceType, cancellationToken)?.Info; } protected TNode AddComment<TNode>(string comment, TNode node) where TNode : SyntaxNode => AddComments([comment], node); protected TNode AddComments<TNode>(string comment1, string comment2, TNode node) where TNode : SyntaxNode => AddComments([comment1, comment2], node); protected TNode AddComments<TNode>(string[] comments, TNode node) where TNode : SyntaxNode => node.WithPrependedLeadingTrivia(CreateCommentTrivia(comments)); protected SyntaxTriviaList CreateCommentTrivia( params ReadOnlySpan<string> comments) { using var _ = ArrayBuilder<SyntaxTrivia>.GetInstance(out var trivia); foreach (var comment in comments) { trivia.Add(this.SyntaxGeneratorInternal.SingleLineComment(" " + comment)); trivia.Add(this.SyntaxGeneratorInternal.ElasticCarriageReturnLineFeed); } return [.. trivia]; } private async Task<Document> ImplementInterfaceAsync( Document document, ImplementInterfaceInfo info, ImplementTypeOptions options, ImplementInterfaceConfiguration configuration, CancellationToken cancellationToken) { var generator = new ImplementInterfaceGenerator( this, document, info, options, configuration); return await generator.ImplementInterfaceAsync(cancellationToken).ConfigureAwait(false); } public ImmutableArray<ISymbol> ImplementInterfaceMember( Document document, ImplementInterfaceInfo info, ImplementTypeOptions options, ImplementInterfaceConfiguration configuration, Compilation compilation, ISymbol interfaceMember) { var generator = new ImplementInterfaceGenerator( this, document, info, options, configuration); var syntaxFacts = document.GetRequiredLanguageService<ISyntaxFactsService>(); var supportsImplementingLessAccessibleMember = syntaxFacts.SupportsImplicitImplementationOfNonPublicInterfaceMembers(document.Project.ParseOptions!); var implementedMembers = generator.GenerateMembers( compilation, interfaceMember, conflictingMember: null, memberName: interfaceMember.Name, generateInvisibly: generator.ShouldGenerateInvisibleMember(document.Project.ParseOptions!, interfaceMember, interfaceMember.Name, supportsImplementingLessAccessibleMember), generateAbstractly: configuration.Abstractly, addNew: false, interfaceMember.RequiresUnsafeModifier() && !syntaxFacts.IsUnsafeContext(info.ContextNode), options.PropertyGenerationBehavior); return implementedMembers; } public async Task<ImmutableArray<CodeAction>> GetCodeActionsAsync(Document document, SyntaxNode? interfaceType, CancellationToken cancellationToken) { var options = await document.GetImplementTypeOptionsAsync(cancellationToken).ConfigureAwait(false); if (!this.IsTypeInInterfaceBaseList(interfaceType)) return []; var info = await this.AnalyzeAsync( document, interfaceType, cancellationToken).ConfigureAwait(false); if (info is null) return []; using var _ = ArrayBuilder<CodeAction>.GetInstance(out var codeActions); await foreach (var implementOptions in GetImplementOptionsAsync(document, info, cancellationToken).ConfigureAwait(false)) { var title = GetTitle(implementOptions); var equivalenceKey = GetEquivalenceKey(info, implementOptions); codeActions.Add(CodeAction.Create( title, cancellationToken => this.ImplementInterfaceAsync( document, info, options, implementOptions, cancellationToken), equivalenceKey)); } return codeActions.ToImmutableAndClear(); } private static async IAsyncEnumerable<ImplementInterfaceConfiguration> GetImplementOptionsAsync( Document document, ImplementInterfaceInfo state, [EnumeratorCancellation] CancellationToken cancellationToken) { var compilation = await document.Project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); var syntaxFacts = document.GetRequiredLanguageService<ISyntaxFactsService>(); var supportsImplicitImplementationOfNonPublicInterfaceMembers = syntaxFacts.SupportsImplicitImplementationOfNonPublicInterfaceMembers(document.Project.ParseOptions!); if (state.MembersWithoutExplicitOrImplicitImplementationWhichCanBeImplicitlyImplemented.Length > 0) { var totalMemberCount = 0; var inaccessibleMemberCount = 0; foreach (var (_, members) in state.MembersWithoutExplicitOrImplicitImplementationWhichCanBeImplicitlyImplemented) { foreach (var member in members) { totalMemberCount++; if (ContainsTypeLessAccessibleThan(member, state.ClassOrStructType, supportsImplicitImplementationOfNonPublicInterfaceMembers)) inaccessibleMemberCount++; } } // If all members to implement are inaccessible, then "Implement interface" codeaction // will be the same as "Implement interface explicitly", so there is no point in having both of them if (totalMemberCount != inaccessibleMemberCount) yield return new() { OnlyRemaining = true }; if (ShouldImplementDisposePattern(compilation, state, explicitly: false)) yield return new() { OnlyRemaining = true, ImplementDisposePattern = true, }; var delegatableMembers = GetDelegatableMembers(document, state, cancellationToken); foreach (var member in delegatableMembers) yield return new() { ThroughMember = member }; if (state.ClassOrStructType.IsAbstract) yield return new() { OnlyRemaining = true, Abstractly = true }; } if (state.MembersWithoutExplicitImplementation.Length > 0) { yield return new() { Explicitly = true }; if (ShouldImplementDisposePattern(compilation, state, explicitly: true)) yield return new() { ImplementDisposePattern = true, Explicitly = true }; } if (AnyImplementedImplicitly(state)) yield return new() { OnlyRemaining = true, Explicitly = true }; } private static string GetTitle(ImplementInterfaceConfiguration options) { if (options.ImplementDisposePattern) { return options.Explicitly ? CodeFixesResources.Implement_interface_explicitly_with_Dispose_pattern : CodeFixesResources.Implement_interface_with_Dispose_pattern; } else if (options.Explicitly) { return options.OnlyRemaining ? CodeFixesResources.Implement_remaining_members_explicitly : CodeFixesResources.Implement_all_members_explicitly; } else if (options.Abstractly) { return CodeFixesResources.Implement_interface_abstractly; } else if (options.ThroughMember != null) { return string.Format(CodeFixesResources.Implement_interface_through_0, options.ThroughMember.Name); } else { return CodeFixesResources.Implement_interface; } } private static string GetEquivalenceKey( ImplementInterfaceInfo state, ImplementInterfaceConfiguration options) { var interfaceType = state.InterfaceTypes.First(); var typeName = interfaceType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); // Legacy part of the equivalence key. Kept the same to avoid test churn. var codeActionTypeName = options.ImplementDisposePattern ? "Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceWithDisposePatternCodeAction" : "Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction"; // Consider code actions equivalent if they correspond to the same interface being implemented elsewhere // in the same manner. Note: 'implement through member' means implementing the same interface through // an applicable member with the same name in the destination. return options.Explicitly.ToString() + ";" + options.Abstractly.ToString() + ";" + options.OnlyRemaining.ToString() + ":" + typeName + ";" + codeActionTypeName + ";" + options.ThroughMember?.Name; } private static bool AnyImplementedImplicitly(ImplementInterfaceInfo state) { if (state.MembersWithoutExplicitOrImplicitImplementation.Length != state.MembersWithoutExplicitImplementation.Length) { return true; } for (var i = 0; i < state.MembersWithoutExplicitOrImplicitImplementation.Length; i++) { var (typeA, membersA) = state.MembersWithoutExplicitOrImplicitImplementation[i]; var (typeB, membersB) = state.MembersWithoutExplicitImplementation[i]; if (!typeA.Equals(typeB)) { return true; } if (!membersA.SequenceEqual(membersB)) { return true; } } return false; } private static ImmutableArray<ISymbol> GetDelegatableMembers( Document document, ImplementInterfaceInfo state, CancellationToken cancellationToken) { var firstInterfaceType = state.InterfaceTypes.First(); return ImplementHelpers.GetDelegatableMembers( document, state.ClassOrStructType, t => t.GetAllInterfacesIncludingThis().Contains(firstInterfaceType), cancellationToken); } }