/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Features/VisualBasic/Portable/CodeFixes/GenerateEvent/GenerateEventCodeFixProvider.vb
409 строк
22 KB
Jason Malinowski
Ensure FindSourceDefinitionAsync works in frozen compilation cases
18 апр 2025, 02:42
18 апр 2025, 02:42
39e3a54
Код
Авторство
О чём код?
' 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. Imports System.Collections.Immutable Imports System.Composition Imports System.Diagnostics.CodeAnalysis Imports System.Threading Imports Microsoft.CodeAnalysis.CodeActions Imports Microsoft.CodeAnalysis.CodeFixes Imports Microsoft.CodeAnalysis.CodeGeneration Imports Microsoft.CodeAnalysis.FindSymbols Imports Microsoft.CodeAnalysis.LanguageService Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Namespace Microsoft.CodeAnalysis.VisualBasic.CodeFixes.GenerateEvent <ExportCodeFixProvider(LanguageNames.VisualBasic, Name:=PredefinedCodeFixProviderNames.GenerateEvent), [Shared]> <ExtensionOrder(After:=PredefinedCodeFixProviderNames.GenerateEnumMember)> Partial Friend Class GenerateEventCodeFixProvider Inherits CodeFixProvider Friend Const BC30401 As String = "BC30401" ' error BC30401: 'goo' cannot implement 'E' because there is no matching event on interface 'MyInterface'. Friend Const BC30590 As String = "BC30590" ' error BC30590: Event 'MyEvent' cannot be found. Friend Const BC30456 As String = "BC30456" ' error BC30456: 'x' is not a member of 'y'. Friend Const BC30451 As String = "BC30451" ' error BC30451: 'x' is not declared, it may be inaccessible due to its protection level. <ImportingConstructor> <SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification:="Used in test code: https://github.com/dotnet/roslyn/issues/42814")> Public Sub New() End Sub Public NotOverridable Overrides ReadOnly Property FixableDiagnosticIds As ImmutableArray(Of String) Get Return ImmutableArray.Create(BC30401, BC30590, BC30456, BC30451) End Get End Property Public Overrides Function GetFixAllProvider() As FixAllProvider ' Fix All is not supported by this code fix ' https://github.com/dotnet/roslyn/issues/34474 Return Nothing End Function Public NotOverridable Overrides Async Function RegisterCodeFixesAsync(context As CodeFixContext) As Task Dim root = Await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(False) Dim token = root.FindToken(context.Span.Start) If Not token.Span.IntersectsWith(context.Span) Then Return End If Dim result As CodeAction = Nothing For Each node In token.GetAncestors(Of SyntaxNode).Where(Function(c) c.Span.IntersectsWith(context.Span) AndAlso IsCandidate(c)) Dim qualifiedName = TryCast(node, QualifiedNameSyntax) If qualifiedName IsNot Nothing Then result = Await GenerateEventFromImplementsAsync(context.Document, qualifiedName, context.CancellationToken).ConfigureAwait(False) End If Dim handlesClauseItem = TryCast(node, HandlesClauseItemSyntax) If handlesClauseItem IsNot Nothing Then result = Await GenerateEventFromHandlesAsync(context.Document, handlesClauseItem, context.CancellationToken).ConfigureAwait(False) End If Dim handlerStatement = TryCast(node, AddRemoveHandlerStatementSyntax) If handlerStatement IsNot Nothing Then result = Await GenerateEventFromAddRemoveHandlerAsync(context.Document, handlerStatement, context.CancellationToken).ConfigureAwait(False) End If If result IsNot Nothing Then context.RegisterCodeFix(result, context.Diagnostics) Return End If Next End Function Private Shared Async Function GenerateEventFromAddRemoveHandlerAsync(document As Document, handlerStatement As AddRemoveHandlerStatementSyntax, cancellationToken As CancellationToken) As Task(Of CodeAction) Dim semanticModel = Await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(False) Dim handlerExpression = GetHandlerExpression(handlerStatement) Dim delegateSymbol As IMethodSymbol = Nothing If Not TryGetDelegateSymbol(handlerExpression, semanticModel, delegateSymbol, cancellationToken) Then Return Nothing End If Dim eventExpression = handlerStatement.EventExpression Dim eventSymbol = semanticModel.GetSymbolInfo(eventExpression, cancellationToken).GetAnySymbol() If eventSymbol IsNot Nothing Then Return Nothing End If Dim containingSymbol = semanticModel.GetEnclosingNamedType(handlerStatement.SpanStart, cancellationToken) If containingSymbol Is Nothing Then Return Nothing End If Dim targetType As INamedTypeSymbol = Nothing Dim actualEventName As String = Nothing If Not TryGetNameAndTargetType(eventExpression, containingSymbol, semanticModel, targetType, actualEventName, cancellationToken) Then Return Nothing End If If Not ResolveTargetType(targetType, semanticModel) Then Return Nothing End If ' Target type may be in other project so we need to find its source definition Dim sourceDefinition = Await SymbolFinder.FindSourceDefinitionAsync(targetType, document.Project.Solution, cancellationToken).ConfigureAwait(False) targetType = TryCast(sourceDefinition, INamedTypeSymbol) If targetType Is Nothing Then Return Nothing End If Return Await GenerateCodeActionAsync(document, semanticModel, delegateSymbol, actualEventName, targetType, cancellationToken).ConfigureAwait(False) End Function Private Shared Async Function GenerateCodeActionAsync( document As Document, semanticModel As SemanticModel, delegateSymbol As IMethodSymbol, actualEventName As String, targetType As INamedTypeSymbol, cancellationToken As CancellationToken) As Task(Of CodeAction) Dim codeGenService = document.Project.Solution.Services.GetLanguageServices(targetType.Language).GetService(Of ICodeGenerationService) Dim syntaxFactService = document.Project.Solution.Services.GetLanguageServices(targetType.Language).GetService(Of ISyntaxFactsService) Dim eventHandlerName As String = actualEventName + "Handler" Dim existingSymbols = Await DeclarationFinder.FindSourceDeclarationsWithNormalQueryAsync( document.Project.Solution, eventHandlerName, Not syntaxFactService.IsCaseSensitive, SymbolFilter.Type, cancellationToken).ConfigureAwait(False) If existingSymbols.Any(Function(existingSymbol) existingSymbol IsNot Nothing _ AndAlso Equals(existingSymbol.ContainingNamespace, targetType.ContainingNamespace)) Then ' There already exists a delegate that matches the event handler name Return Nothing End If ' We also need to generate the delegate type Dim delegateType = CodeGenerationSymbolFactory.CreateDelegateTypeSymbol( attributes:=Nothing, accessibility:=Accessibility.Public, modifiers:=Nothing, returnType:=semanticModel.Compilation.GetSpecialType(SpecialType.System_Void), refKind:=RefKind.None, name:=eventHandlerName, parameters:=delegateSymbol.GetParameters()) Dim generatedEvent = CodeGenerationSymbolFactory.CreateEventSymbol( attributes:=ImmutableArray(Of AttributeData).Empty, accessibility:=Accessibility.Public, modifiers:=Nothing, explicitInterfaceImplementations:=Nothing, type:=delegateType, name:=actualEventName) ' Point the delegate back at the event symbol. This way the generators know to generate parameters ' instead of an 'As' clause. delegateType.AssociatedSymbol = generatedEvent Return New GenerateEventCodeAction(document.Project.Solution, targetType, generatedEvent, codeGenService) End Function Private Shared Function GetHandlerExpression(handlerStatement As AddRemoveHandlerStatementSyntax) As ExpressionSyntax Dim unaryExpression = TryCast(handlerStatement.DelegateExpression.DescendantNodesAndSelf().Where(Function(n) n.IsKind(SyntaxKind.AddressOfExpression)).FirstOrDefault, UnaryExpressionSyntax) If unaryExpression Is Nothing Then Return handlerStatement.DelegateExpression Else Return unaryExpression.Operand End If End Function Private Shared Function TryGetDelegateSymbol(handlerExpression As ExpressionSyntax, semanticModel As SemanticModel, ByRef delegateSymbol As IMethodSymbol, cancellationToken As CancellationToken) As Boolean delegateSymbol = TryCast(semanticModel.GetSymbolInfo(handlerExpression, cancellationToken).GetAnySymbol(), IMethodSymbol) If delegateSymbol Is Nothing Then Dim typeSymbol = TryCast(semanticModel.GetTypeInfo(handlerExpression, cancellationToken).Type, INamedTypeSymbol) If typeSymbol IsNot Nothing AndAlso typeSymbol.DelegateInvokeMethod IsNot Nothing Then delegateSymbol = typeSymbol.DelegateInvokeMethod Else Return False End If End If If delegateSymbol.Arity <> 0 AndAlso delegateSymbol.TypeArguments.Any(Function(n) n.TypeKind = TypeKind.TypeParameter) Then Return False End If Return True End Function Private Shared Function ResolveTargetType(ByRef targetType As INamedTypeSymbol, semanticModel As SemanticModel) As Boolean If targetType Is Nothing OrElse Not (targetType.TypeKind = TypeKind.Class OrElse targetType.TypeKind = TypeKind.Interface) OrElse targetType.IsAnonymousType Then Return False End If targetType = DirectCast(targetType.GetSymbolKey().Resolve(semanticModel.Compilation).Symbol, INamedTypeSymbol) If targetType Is Nothing Then Return False End If Return True End Function Private Shared Function TryGetNameAndTargetType(eventExpression As ExpressionSyntax, containingSymbol As INamedTypeSymbol, semanticModel As SemanticModel, ByRef targetType As INamedTypeSymbol, ByRef actualEventName As String, cancellationToken As CancellationToken) As Boolean Dim eventType As INamedTypeSymbol = Nothing If TypeOf eventExpression Is IdentifierNameSyntax Then actualEventName = CType(eventExpression, IdentifierNameSyntax).Identifier.ValueText ElseIf TypeOf eventExpression Is MemberAccessExpressionSyntax Then Dim memberAccess = CType(eventExpression, MemberAccessExpressionSyntax) Dim qualifier As ExpressionSyntax = Nothing Dim arity As Integer memberAccess.DecomposeName(qualifier, actualEventName, arity) eventType = TryCast(semanticModel.GetTypeInfo(qualifier, cancellationToken).Type, INamedTypeSymbol) Else Return False End If If eventExpression.DescendantTokens().Where(Function(n) n.IsKind(SyntaxKind.MeKeyword, SyntaxKind.MyClassKeyword)).Any Then targetType = containingSymbol Return True ElseIf eventExpression.DescendantTokens().Where(Function(n) n.IsKind(SyntaxKind.MyBaseKeyword)).Any Then targetType = containingSymbol.BaseType Return True ElseIf TypeOf eventExpression Is IdentifierNameSyntax Then targetType = containingSymbol Return True ElseIf TypeOf eventExpression Is MemberAccessExpressionSyntax Then If eventType IsNot Nothing Then targetType = eventType Return True End If End If Return False End Function Private Shared Function IsCandidate(node As SyntaxNode) As Boolean Return TypeOf node Is HandlesClauseItemSyntax OrElse TypeOf node Is QualifiedNameSyntax OrElse TypeOf node Is AddRemoveHandlerStatementSyntax End Function Private Shared Async Function GenerateEventFromImplementsAsync(document As Document, node As QualifiedNameSyntax, cancellationToken As CancellationToken) As Task(Of CodeAction) If node.Right.IsMissing Then Return Nothing End If ' We must be trying to implement an event If Not node.IsParentKind(SyntaxKind.ImplementsClause) OrElse Not node.Parent.IsParentKind(SyntaxKind.EventStatement) Then Return Nothing End If ' Does this name already bind? Dim semanticModel = Await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(False) Dim nameToGenerate = semanticModel.GetSymbolInfo(node, cancellationToken).Symbol If nameToGenerate IsNot Nothing Then Return Nothing End If Dim targetType = TryCast(Await SymbolFinder.FindSourceDefinitionAsync(semanticModel.GetSymbolInfo(node.Left, cancellationToken).Symbol, document.Project.Solution, cancellationToken).ConfigureAwait(False), INamedTypeSymbol) If targetType Is Nothing OrElse (targetType.TypeKind <> TypeKind.Interface AndAlso targetType.TypeKind <> TypeKind.Class) Then Return Nothing End If Dim boundEvent = TryCast(semanticModel.GetDeclaredSymbol(node.Parent.Parent, cancellationToken), IEventSymbol) If boundEvent Is Nothing Then Return Nothing End If Dim codeGenService = document.Project.Solution.Services.GetLanguageServices(targetType.Language).GetService(Of ICodeGenerationService) Dim actualEventName = node.Right.Identifier.ValueText ' If we support parameterized events (C#) and it's an event declaration with a parameter list ' (not a type), we need to generate a delegate type in the C# file. Dim eventSyntax = node.GetAncestor(Of EventStatementSyntax)() If eventSyntax.ParameterList IsNot Nothing Then Dim eventType = TryCast(boundEvent.Type, INamedTypeSymbol) If eventType Is Nothing Then Return Nothing End If Dim returnType = If(eventType.DelegateInvokeMethod IsNot Nothing, eventType.DelegateInvokeMethod.ReturnType, semanticModel.Compilation.GetSpecialType(SpecialType.System_Void)) Dim parameters = If(eventType.DelegateInvokeMethod IsNot Nothing, eventType.DelegateInvokeMethod.Parameters, ImmutableArray(Of IParameterSymbol).Empty) Dim eventHandlerType = CodeGenerationSymbolFactory.CreateDelegateTypeSymbol( eventType.GetAttributes(), eventType.DeclaredAccessibility, modifiers:=Nothing, returnType:=returnType, refKind:=RefKind.None, name:=actualEventName + "EventHandler", typeParameters:=eventType.TypeParameters, parameters:=parameters) Dim generatedEvent = CodeGenerationSymbolFactory.CreateEventSymbol( boundEvent.GetAttributes(), boundEvent.DeclaredAccessibility, modifiers:=Nothing, type:=eventHandlerType, explicitInterfaceImplementations:=Nothing, name:=actualEventName) ' Point the delegate back at the event symbol. This way the generators know to generate parameters ' instead of an 'As' clause. eventHandlerType.AssociatedSymbol = generatedEvent Return New GenerateEventCodeAction(document.Project.Solution, targetType, generatedEvent, codeGenService) Else ' Event with no parameters. Dim generatedMember = CodeGenerationSymbolFactory.CreateEventSymbol(boundEvent, name:=actualEventName) Return New GenerateEventCodeAction(document.Project.Solution, targetType, generatedMember, codeGenService) End If End Function Private Shared Async Function GenerateEventFromHandlesAsync(document As Document, handlesClauseItem As HandlesClauseItemSyntax, cancellationToken As CancellationToken) As Task(Of CodeAction) If handlesClauseItem.IsMissing OrElse handlesClauseItem.EventContainer.IsMissing OrElse handlesClauseItem.EventMember.IsMissing Then Return Nothing End If Dim semanticModel = Await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(False) ' Does this handlesClauseItem actually bind? Dim symbol = semanticModel.GetSymbolInfo(handlesClauseItem, cancellationToken).Symbol If symbol IsNot Nothing Then Return Nothing End If Dim targetType As INamedTypeSymbol = Nothing Dim keywordEventContainer = TryCast(handlesClauseItem.EventContainer, KeywordEventContainerSyntax) If keywordEventContainer IsNot Nothing Then ' Me/MyClass/MyBase Dim containingSymbol = semanticModel.GetEnclosingNamedType(handlesClauseItem.SpanStart, cancellationToken) If containingSymbol Is Nothing Then Return Nothing End If If keywordEventContainer.Keyword.IsKind(SyntaxKind.MeKeyword, SyntaxKind.MyClassKeyword) Then targetType = containingSymbol ElseIf keywordEventContainer.Keyword.IsKind(SyntaxKind.MyBaseKeyword) Then targetType = containingSymbol.BaseType End If Else ' Withevents property. We'll generate into its type. Dim withEventsProperty = TryCast(semanticModel.GetSymbolInfo(handlesClauseItem.EventContainer, cancellationToken).Symbol, IPropertySymbol) If withEventsProperty Is Nothing OrElse Not withEventsProperty.IsWithEvents Then Return Nothing End If targetType = TryCast(Await SymbolFinder.FindSourceDefinitionAsync(withEventsProperty.Type, document.Project.Solution, cancellationToken).ConfigureAwait(False), INamedTypeSymbol) End If targetType = TryCast(Await SymbolFinder.FindSourceDefinitionAsync(targetType, document.Project.Solution, cancellationToken).ConfigureAwait(False), INamedTypeSymbol) If targetType Is Nothing OrElse Not (targetType.TypeKind = TypeKind.Class OrElse targetType.TypeKind = TypeKind.Interface) OrElse targetType.IsAnonymousType Then Return Nothing End If ' Our target type may be from a CSharp file, in which case we should resolve it to our VB compilation. Dim originalTargetType = targetType targetType = DirectCast(targetType.GetSymbolKey(cancellationToken).Resolve(semanticModel.Compilation, cancellationToken:=cancellationToken).Symbol, INamedTypeSymbol) If targetType Is Nothing Then Return Nothing End If If semanticModel.LookupSymbols(handlesClauseItem.SpanStart, container:=targetType, name:=handlesClauseItem.EventMember.Identifier.ValueText). Any(Function(x) x.MatchesKind(SymbolKind.Event) AndAlso x.Name = handlesClauseItem.EventMember.Identifier.ValueText) Then Return Nothing End If If targetType.GetMembers(handlesClauseItem.EventMember.Identifier.ValueText).Any() Then Return Nothing End If Dim codeGenService = document.Project.Solution.Services.GetLanguageServices(originalTargetType.Language).GetService(Of ICodeGenerationService) ' Let's bind the method declaration so we can get its parameters. Dim boundMethod = semanticModel.GetDeclaredSymbol(handlesClauseItem.GetAncestor(Of MethodStatementSyntax)(), cancellationToken) If boundMethod Is Nothing Then Return Nothing End If Dim actualEventName = handlesClauseItem.EventMember.Identifier.ValueText ' We need to generate the delegate, too. Dim delegateType = CodeGenerationSymbolFactory.CreateDelegateTypeSymbol( attributes:=Nothing, accessibility:=Accessibility.Public, modifiers:=Nothing, returnType:=semanticModel.Compilation.GetSpecialType(SpecialType.System_Void), refKind:=RefKind.None, name:=actualEventName + "Handler", parameters:=boundMethod.GetParameters()) Dim generatedEvent = CodeGenerationSymbolFactory.CreateEventSymbol( attributes:=Nothing, accessibility:=Accessibility.Public, modifiers:=Nothing, explicitInterfaceImplementations:=Nothing, type:=delegateType, name:=actualEventName) ' Point the delegate back at the event symbol. This way the generators know to generate parameters ' instead of an 'As' clause. delegateType.AssociatedSymbol = generatedEvent Return New GenerateEventCodeAction( document.Project.Solution, originalTargetType, generatedEvent, codeGenService) End Function End Class End Namespace