|
// 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.Collections.Generic;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis.CodeGeneration;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Utilities;
using static Microsoft.CodeAnalysis.CodeGeneration.CodeGenerationHelpers;
using static Microsoft.CodeAnalysis.CSharp.CodeGeneration.CSharpCodeGenerationHelpers;
namespace Microsoft.CodeAnalysis.CSharp.CodeGeneration
{
internal static class EventGenerator
{
private static MemberDeclarationSyntax? AfterMember(
SyntaxList<MemberDeclarationSyntax> members,
MemberDeclarationSyntax eventDeclaration)
{
if (eventDeclaration.Kind() == SyntaxKind.EventFieldDeclaration)
{
// Field style events go after the last field event, or after the last field.
var lastEvent = members.LastOrDefault(m => m is EventFieldDeclarationSyntax);
return lastEvent ?? LastField(members);
}
if (eventDeclaration.Kind() == SyntaxKind.EventDeclaration)
{
// Property style events go after existing events, then after existing constructors.
var lastEvent = members.LastOrDefault(m => m is EventDeclarationSyntax);
return lastEvent ?? LastConstructor(members);
}
return null;
}
private static MemberDeclarationSyntax? BeforeMember(
SyntaxList<MemberDeclarationSyntax> members,
MemberDeclarationSyntax eventDeclaration)
{
// If it's a field style event, then it goes before everything else if we don't have any
// existing fields/events.
if (eventDeclaration.Kind() == SyntaxKind.FieldDeclaration)
{
return members.FirstOrDefault();
}
// Otherwise just place it before the methods.
return FirstMethod(members);
}
internal static CompilationUnitSyntax AddEventTo(
CompilationUnitSyntax destination,
IEventSymbol @event,
CSharpCodeGenerationContextInfo info,
IList<bool> availableIndices,
CancellationToken cancellationToken)
{
var declaration = GenerateEventDeclaration(@event, CodeGenerationDestination.CompilationUnit, info, cancellationToken);
// Place the event depending on its shape. Field style events go with fields, property
// style events go with properties. If there
var members = Insert(destination.Members, declaration, info, availableIndices,
after: list => AfterMember(list, declaration), before: list => BeforeMember(list, declaration));
return destination.WithMembers(members.ToSyntaxList());
}
internal static TypeDeclarationSyntax AddEventTo(
TypeDeclarationSyntax destination,
IEventSymbol @event,
CSharpCodeGenerationContextInfo info,
IList<bool>? availableIndices,
CancellationToken cancellationToken)
{
var declaration = GenerateEventDeclaration(@event, GetDestination(destination), info, cancellationToken);
var members = Insert(destination.Members, declaration, info, availableIndices,
after: list => AfterMember(list, declaration),
before: list => BeforeMember(list, declaration));
// Find the best place to put the field. It should go after the last field if we already
// have fields, or at the beginning of the file if we don't.
return AddMembersTo(destination, members, cancellationToken);
}
public static MemberDeclarationSyntax GenerateEventDeclaration(
IEventSymbol @event, CodeGenerationDestination destination, CSharpCodeGenerationContextInfo info, CancellationToken cancellationToken)
{
var reusableSyntax = GetReuseableSyntaxNodeForSymbol<MemberDeclarationSyntax>(@event, info);
if (reusableSyntax != null)
{
return reusableSyntax;
}
var declaration = !info.Context.GenerateMethodBodies || @event.IsAbstract || @event.AddMethod == null || @event.RemoveMethod == null
? GenerateEventFieldDeclaration(@event, destination, info)
: GenerateEventDeclarationWorker(@event, destination, info);
return ConditionallyAddDocumentationCommentTo(declaration, @event, info, cancellationToken);
}
private static MemberDeclarationSyntax GenerateEventFieldDeclaration(
IEventSymbol @event, CodeGenerationDestination destination, CSharpCodeGenerationContextInfo info)
{
return AddFormatterAndCodeGeneratorAnnotationsTo(
AddAnnotationsTo(@event,
SyntaxFactory.EventFieldDeclaration(
AttributeGenerator.GenerateAttributeLists(@event.GetAttributes(), info),
GenerateModifiers(@event, destination, info),
SyntaxFactory.VariableDeclaration(
@event.Type.GenerateTypeSyntax(),
SyntaxFactory.SingletonSeparatedList(SyntaxFactory.VariableDeclarator(@event.Name.ToIdentifierToken()))))));
}
private static MemberDeclarationSyntax GenerateEventDeclarationWorker(
IEventSymbol @event, CodeGenerationDestination destination, CSharpCodeGenerationContextInfo info)
{
var explicitInterfaceSpecifier = GenerateExplicitInterfaceSpecifier(@event.ExplicitInterfaceImplementations);
return AddFormatterAndCodeGeneratorAnnotationsTo(SyntaxFactory.EventDeclaration(
attributeLists: AttributeGenerator.GenerateAttributeLists(@event.GetAttributes(), info),
modifiers: GenerateModifiers(@event, destination, info),
type: @event.Type.GenerateTypeSyntax(),
explicitInterfaceSpecifier: explicitInterfaceSpecifier,
identifier: @event.Name.ToIdentifierToken(),
accessorList: GenerateAccessorList(@event, destination, info)));
}
private static AccessorListSyntax GenerateAccessorList(
IEventSymbol @event, CodeGenerationDestination destination, CSharpCodeGenerationContextInfo info)
{
var accessors = new List<AccessorDeclarationSyntax?>
{
GenerateAccessorDeclaration(@event, @event.AddMethod, SyntaxKind.AddAccessorDeclaration, destination, info),
GenerateAccessorDeclaration(@event, @event.RemoveMethod, SyntaxKind.RemoveAccessorDeclaration, destination, info),
};
return SyntaxFactory.AccessorList(accessors.WhereNotNull().ToSyntaxList());
}
private static AccessorDeclarationSyntax? GenerateAccessorDeclaration(
IEventSymbol @event,
IMethodSymbol? accessor,
SyntaxKind kind,
CodeGenerationDestination destination,
CSharpCodeGenerationContextInfo info)
{
var hasBody = info.Context.GenerateMethodBodies && HasAccessorBodies(@event, destination, accessor);
return accessor == null
? null
: GenerateAccessorDeclaration(accessor, kind, hasBody);
}
private static AccessorDeclarationSyntax GenerateAccessorDeclaration(
IMethodSymbol accessor,
SyntaxKind kind,
bool hasBody)
{
return AddAnnotationsTo(accessor, SyntaxFactory.AccessorDeclaration(kind)
.WithBody(hasBody ? GenerateBlock(accessor) : null)
.WithSemicolonToken(hasBody ? default : SyntaxFactory.Token(SyntaxKind.SemicolonToken)));
}
private static BlockSyntax GenerateBlock(IMethodSymbol accessor)
{
return SyntaxFactory.Block(
StatementGenerator.GenerateStatements(CodeGenerationMethodInfo.GetStatements(accessor)));
}
private static bool HasAccessorBodies(
IEventSymbol @event,
CodeGenerationDestination destination,
IMethodSymbol? accessor)
{
return destination != CodeGenerationDestination.InterfaceType &&
!@event.IsAbstract &&
accessor != null &&
!accessor.IsAbstract;
}
private static SyntaxTokenList GenerateModifiers(
IEventSymbol @event, CodeGenerationDestination destination, CSharpCodeGenerationContextInfo info)
{
var tokens = ArrayBuilder<SyntaxToken>.GetInstance();
// Only "static" allowed if we're an explicit impl.
if (@event.ExplicitInterfaceImplementations.Any())
{
if (@event.IsStatic)
tokens.Add(SyntaxFactory.Token(SyntaxKind.StaticKeyword));
}
else
{
// If we're generating into an interface, then allow modifiers for static abstract members
if (destination is CodeGenerationDestination.InterfaceType)
{
if (@event.IsStatic)
{
tokens.Add(SyntaxFactory.Token(SyntaxKind.StaticKeyword));
// We only generate the abstract keyword in interfaces for static abstract members
if (@event.IsAbstract)
tokens.Add(SyntaxFactory.Token(SyntaxKind.AbstractKeyword));
}
}
else
{
CSharpCodeGenerationHelpers.AddAccessibilityModifiers(@event.DeclaredAccessibility, tokens, info, Accessibility.Private);
if (@event.IsStatic)
tokens.Add(SyntaxFactory.Token(SyntaxKind.StaticKeyword));
// An event is readonly if its accessors are readonly.
// If one accessor is readonly and the other one is not,
// the event is malformed and cannot be properly displayed.
// See https://github.com/dotnet/roslyn/issues/34213
// Don't show the readonly modifier if the containing type is already readonly
if (@event.AddMethod?.IsReadOnly == true && !@event.ContainingType.IsReadOnly)
tokens.Add(SyntaxFactory.Token(SyntaxKind.ReadOnlyKeyword));
if (@event.IsAbstract)
tokens.Add(SyntaxFactory.Token(SyntaxKind.AbstractKeyword));
if (@event.IsOverride)
tokens.Add(SyntaxFactory.Token(SyntaxKind.OverrideKeyword));
}
}
if (CodeGenerationEventInfo.GetIsUnsafe(@event))
tokens.Add(SyntaxFactory.Token(SyntaxKind.UnsafeKeyword));
return tokens.ToSyntaxTokenListAndFree();
}
}
}
|