|
// 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.
#nullable disable
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.ImplementType;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
namespace Microsoft.CodeAnalysis.ImplementInterface
{
internal abstract partial class AbstractImplementInterfaceService : IImplementInterfaceService
{
protected const string DisposingName = "disposing";
protected AbstractImplementInterfaceService()
{
}
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, out SyntaxNode classOrStructDecl, out INamedTypeSymbol classOrStructType, out IEnumerable<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);
public async Task<Document> ImplementInterfaceAsync(
Document document, ImplementTypeGenerationOptions options, SyntaxNode node, CancellationToken cancellationToken)
{
using (Logger.LogBlock(FunctionId.Refactoring_ImplementInterface, cancellationToken))
{
var model = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
var state = State.Generate(this, document, model, node, cancellationToken);
if (state == null)
{
return document;
}
// TODO: https://github.com/dotnet/roslyn/issues/60990
// 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 action = ShouldImplementDisposePattern(state, explicitly: false)
? ImplementInterfaceWithDisposePatternCodeAction.CreateImplementWithDisposePatternCodeAction(this, document, options, state)
: ImplementInterfaceCodeAction.CreateImplementCodeAction(this, document, options, state);
return await action.GetUpdatedDocumentAsync(cancellationToken).ConfigureAwait(false);
}
}
public ImmutableArray<CodeAction> GetCodeActions(Document document, ImplementTypeGenerationOptions options, SemanticModel model, SyntaxNode node, CancellationToken cancellationToken)
{
var state = State.Generate(this, document, model, node, cancellationToken);
return GetActions(document, options, state).ToImmutableArray();
}
private IEnumerable<CodeAction> GetActions(Document document, ImplementTypeGenerationOptions options, State state)
{
if (state == null)
{
yield break;
}
if (state.MembersWithoutExplicitOrImplicitImplementationWhichCanBeImplicitlyImplemented.Length > 0)
{
var totalMemberCount = 0;
var inaccessibleMemberCount = 0;
foreach (var (_, members) in state.MembersWithoutExplicitOrImplicitImplementationWhichCanBeImplicitlyImplemented)
{
foreach (var member in members)
{
totalMemberCount++;
if (AccessibilityHelper.IsLessAccessibleThan(member, state.ClassOrStructType))
{
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 ImplementInterfaceCodeAction.CreateImplementCodeAction(this, document, options, state);
}
if (ShouldImplementDisposePattern(state, explicitly: false))
{
yield return ImplementInterfaceWithDisposePatternCodeAction.CreateImplementWithDisposePatternCodeAction(this, document, options, state);
}
var delegatableMembers = GetDelegatableMembers(state);
foreach (var member in delegatableMembers)
{
yield return ImplementInterfaceCodeAction.CreateImplementThroughMemberCodeAction(this, document, options, state, member);
}
if (state.ClassOrStructType.IsAbstract)
{
yield return ImplementInterfaceCodeAction.CreateImplementAbstractlyCodeAction(this, document, options, state);
}
}
if (state.MembersWithoutExplicitImplementation.Length > 0)
{
yield return ImplementInterfaceCodeAction.CreateImplementExplicitlyCodeAction(this, document, options, state);
if (ShouldImplementDisposePattern(state, explicitly: true))
{
yield return ImplementInterfaceWithDisposePatternCodeAction.CreateImplementExplicitlyWithDisposePatternCodeAction(this, document, options, state);
}
}
if (AnyImplementedImplicitly(state))
{
yield return ImplementInterfaceCodeAction.CreateImplementRemainingExplicitlyCodeAction(this, document, options, state);
}
}
private static bool AnyImplementedImplicitly(State 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 IList<ISymbol> GetDelegatableMembers(State state)
{
var fields =
state.ClassOrStructType.GetMembers()
.OfType<IFieldSymbol>()
.Where(f => !f.IsImplicitlyDeclared)
.Where(f => f.Type.GetAllInterfacesIncludingThis().Contains(state.InterfaceTypes.First()))
.OfType<ISymbol>();
// Select all properties with zero parameters that also have a getter
var properties =
state.ClassOrStructType.GetMembers()
.OfType<IPropertySymbol>()
.Where(p => (!p.IsImplicitlyDeclared) && (p.Parameters.Length == 0) && (p.GetMethod != null))
.Where(p => p.Type.GetAllInterfacesIncludingThis().Contains(state.InterfaceTypes.First()))
.OfType<ISymbol>();
return fields.Concat(properties).ToList();
}
protected static TNode AddComment<TNode>(SyntaxGenerator g, string comment, TNode node) where TNode : SyntaxNode
=> AddComments(g, new[] { comment }, node);
protected static TNode AddComments<TNode>(SyntaxGenerator g, string comment1, string comment2, TNode node) where TNode : SyntaxNode
=> AddComments(g, new[] { comment1, comment2, }, node);
protected static TNode AddComments<TNode>(SyntaxGenerator g, string[] comments, TNode node) where TNode : SyntaxNode
=> node.WithPrependedLeadingTrivia(CreateCommentTrivia(g, comments));
protected static SyntaxTriviaList CreateCommentTrivia(SyntaxGenerator generator, params string[] comments)
{
using var _ = ArrayBuilder<SyntaxTrivia>.GetInstance(out var trivia);
foreach (var comment in comments)
{
trivia.Add(generator.SingleLineComment(" " + comment));
trivia.Add(generator.ElasticCarriageReturnLineFeed);
}
return new SyntaxTriviaList(trivia);
}
}
}
|