File: Analyzers\Designer\DesignerEventAnalyzer.vb
Web Access
Project: ..\..\..\src\winforms\src\System.Windows.Forms.Analyzers.VisualBasic\src\System.Windows.Forms.Analyzers.VisualBasic.vbproj (System.Windows.Forms.Analyzers.VisualBasic)
' Licensed to the .NET Foundation under one or more agreements.
' The .NET Foundation licenses this file to you under the MIT license.
 
Imports System.Collections.Immutable
Imports System.Windows.Forms.Analyzers
Imports System.Windows.Forms.Analyzers.Diagnostics
Imports Microsoft.CodeAnalysis
Imports Microsoft.CodeAnalysis.Diagnostics
Imports Microsoft.CodeAnalysis.Operations
Imports Microsoft.CodeAnalysis.VisualBasic
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
 
Namespace Global.System.Windows.Forms.VisualBasic.Analyzers.Designer
 
    ''' <summary>
    '''  Preserves the Designer's WithEvents/Handles event model without restricting local components.
    ''' </summary>
    <DiagnosticAnalyzer(LanguageNames.VisualBasic)>
    Public NotInheritable Class DesignerEventAnalyzer
        Inherits DiagnosticAnalyzer
 
        ''' <inheritdoc/>
        Public Overrides ReadOnly Property SupportedDiagnostics As ImmutableArray(Of DiagnosticDescriptor)
            Get
                Return ImmutableArray.Create(
                    SharedDiagnosticDescriptors.s_designerMissingWithEvents,
                    SharedDiagnosticDescriptors.s_designerAddHandler)
            End Get
        End Property
 
        ''' <inheritdoc/>
        Public Overrides Sub Initialize(context As AnalysisContext)
            context.EnableConcurrentExecution()
            context.ConfigureGeneratedCodeAnalysis(
                GeneratedCodeAnalysisFlags.Analyze Or GeneratedCodeAnalysisFlags.ReportDiagnostics)
            context.RegisterCompilationStartAction(
                Sub(startContext)
                    Dim facts As New DesignerTypeFacts(startContext.Compilation)
                    startContext.RegisterSyntaxNodeAction(
                        Sub(nodeContext) AnalyzeInitializeComponent(nodeContext, facts),
                        SyntaxKind.SubBlock)
                End Sub)
        End Sub
 
        Private Shared Sub AnalyzeInitializeComponent(context As SyntaxNodeAnalysisContext, facts As DesignerTypeFacts)
            Dim methodBlock = DirectCast(context.Node, MethodBlockSyntax)
            If Not DesignerTypeFacts.IsDesignerFile(methodBlock.SyntaxTree) _
                OrElse Not String.Equals(methodBlock.SubOrFunctionStatement.Identifier.ValueText,
                    "InitializeComponent", StringComparison.OrdinalIgnoreCase) Then
                Return
            End If
 
            Dim method = TryCast(context.SemanticModel.GetDeclaredSymbol(
                methodBlock.SubOrFunctionStatement, context.CancellationToken), IMethodSymbol)
            If method Is Nothing _
                OrElse Not DesignerTypeFacts.IsInitializeComponent(method) _
                OrElse Not facts.IsDesignerDeclaration(method.ContainingType, methodBlock.SyntaxTree) Then
                Return
            End If
 
            Dim body As IOperation = context.SemanticModel.GetOperation(methodBlock, context.CancellationToken)
            If body Is Nothing Then
                Return
            End If
 
            Dim initializedMembers As New HashSet(Of ISymbol)(SymbolEqualityComparer.Default)
            For Each operation As IOperation In body.DescendantsAndSelf()
                context.CancellationToken.ThrowIfCancellationRequested()
                Dim assignment = TryCast(operation, ISimpleAssignmentOperation)
                If assignment Is Nothing Then
                    Continue For
                End If
 
                Dim member As ISymbol = facts.GetInitializedComponent(assignment, method.ContainingType)
                If member Is Nothing OrElse Not initializedMembers.Add(member) Then
                    Continue For
                End If
 
                If TypeOf member Is IFieldSymbol Then
                    Dim location As Location = member.Locations.FirstOrDefault(Function(item) item.IsInSource)
                    If location IsNot Nothing Then
                        context.ReportDiagnostic(Diagnostic.Create(
                            SharedDiagnosticDescriptors.s_designerMissingWithEvents, location, member.Name))
                    End If
                End If
            Next
 
            For Each operation As IOperation In body.DescendantsAndSelf()
                context.CancellationToken.ThrowIfCancellationRequested()
                Dim assignment = TryCast(operation, IEventAssignmentOperation)
                If assignment Is Nothing OrElse Not assignment.Adds _
                    OrElse DesignerTypeFacts.IsInNestedFunction(assignment) Then
                    Continue For
                End If
 
                Dim eventReference = TryCast(assignment.EventReference, IEventReferenceOperation)
                If eventReference?.Instance Is Nothing Then
                    Continue For
                End If
 
                Dim receiver As String = Nothing
                Dim instance = TryCast(eventReference.Instance, IInstanceReferenceOperation)
                If instance IsNot Nothing AndAlso instance.ReferenceKind = InstanceReferenceKind.ContainingTypeInstance Then
                    Select Case instance.Syntax.Kind()
                        Case SyntaxKind.MyBaseExpression
                            receiver = "MyBase"
                        Case SyntaxKind.MyClassExpression
                            receiver = "MyClass"
                        Case Else
                            receiver = "Me"
                    End Select
                Else
                    Dim member As ISymbol = DesignerTypeFacts.GetInstanceMember(eventReference.Instance)
                    Dim propertySymbol = TryCast(member, IPropertySymbol)
                    If member IsNot Nothing AndAlso
                        (initializedMembers.Contains(member) OrElse
                         (propertySymbol IsNot Nothing AndAlso propertySymbol.IsWithEvents _
                          AndAlso facts.IsComponent(propertySymbol.Type))) Then
                        receiver = member.Name
                    End If
                End If
 
                If receiver IsNot Nothing Then
                    context.ReportDiagnostic(Diagnostic.Create(
                        SharedDiagnosticDescriptors.s_designerAddHandler,
                        assignment.Syntax.GetFirstToken().GetLocation(),
                        $"{receiver}.{eventReference.Event.Name}"))
                End If
            Next
        End Sub
    End Class
End Namespace