diff --git a/CHANGELOG.md b/CHANGELOG.md index 1622181e..8e8e90f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ### VB -> C# +* Fix lost event subscription for `Handles` clause on a sub-property of a `WithEvents` member, e.g. `Handles TextEdit1.Properties.Click` - [#1273](https://github.com/icsharpcode/CodeConverter/issues/1273) +* Fix name qualification being skipped for a whole file containing such a `Handles` clause - [#1273](https://github.com/icsharpcode/CodeConverter/issues/1273) ### C# -> VB diff --git a/CodeConverter/CSharp/EventDescriptor.cs b/CodeConverter/CSharp/EventDescriptor.cs index 00596373..d5762c02 100644 --- a/CodeConverter/CSharp/EventDescriptor.cs +++ b/CodeConverter/CSharp/EventDescriptor.cs @@ -2,4 +2,4 @@ namespace ICSharpCode.CodeConverter.CSharp; -internal record EventDescriptor(IdentifierNameSyntax VBEventName, IEventSymbol SymbolOrNull); \ No newline at end of file +internal record EventDescriptor(IdentifierNameSyntax VBEventName, IEventSymbol SymbolOrNull, string SubPropertyName = null); \ No newline at end of file diff --git a/CodeConverter/CSharp/HandledEventsAnalysis.cs b/CodeConverter/CSharp/HandledEventsAnalysis.cs index 8493d74d..dd63197c 100644 --- a/CodeConverter/CSharp/HandledEventsAnalysis.cs +++ b/CodeConverter/CSharp/HandledEventsAnalysis.cs @@ -246,6 +246,10 @@ private static MethodDeclarationSyntax DelegatingMethod(IdentifierNameSyntax met private ExpressionSyntax MemberAccess(ExpressionSyntax eventContainer, EventDescriptor e) { var csEventName = ValidSyntaxFactory.IdentifierName(_commonConversions.ConvertIdentifier(e.VBEventName.Identifier).WithoutSourceMapping()); + // `Handles WithEventsField.SubProperty.SomeEvent` subscribes on the sub-property, so add that hop back in + if (e.SubPropertyName != null) { + eventContainer = ValidSyntaxFactory.MemberAccess(eventContainer, e.SubPropertyName); + } return SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, eventContainer, csEventName); diff --git a/CodeConverter/CSharp/HandledEventsAnalyzer.cs b/CodeConverter/CSharp/HandledEventsAnalyzer.cs index 4c9c1e38..1f17fbbc 100644 --- a/CodeConverter/CSharp/HandledEventsAnalyzer.cs +++ b/CodeConverter/CSharp/HandledEventsAnalyzer.cs @@ -93,9 +93,16 @@ private async Task IsNeverWrittenOrOverriddenAsync(ISymbol symbol, Cancell return mbb.Where(mss => mss.HandlesClause?.Events.Any() == true) .SelectMany(mss => mss.HandlesClause.Events, (_, e) => { var eventSymbol = semanticModel.GetSymbolInfo(e.EventMember).Symbol as IEventSymbol; - return (CreateEventContainer(e.EventContainer, semanticModel), new EventDescriptor(e.EventMember, eventSymbol), HandlingMethod: methodSymbol); + return (CreateEventContainer(e.EventContainer, semanticModel), new EventDescriptor(e.EventMember, eventSymbol, SubPropertyName(e.EventContainer, semanticModel)), HandlingMethod: methodSymbol); }); } + + private static string SubPropertyName(EventContainerSyntax container, SemanticModel semanticModel) + { + if (container is not WithEventsPropertyEventContainerSyntax wepecs) return null; + return semanticModel.GetSymbolInfo(wepecs.Property).Symbol?.Name ?? wepecs.Property.Identifier.Text; + } + private static HandledEventsAnalysis.EventContainer CreateEventContainer(EventContainerSyntax p, SemanticModel semanticModel) { switch (p) { @@ -109,7 +116,9 @@ private static HandledEventsAnalysis.EventContainer CreateEventContainer(EventCo var name = symbol?.Name ?? weecs.Identifier.Text; return new HandledEventsAnalysis.EventContainer(HandledEventsAnalysis.EventContainerKind.Property, name); case WithEventsPropertyEventContainerSyntax wepecs: - return new HandledEventsAnalysis.EventContainer(HandledEventsAnalysis.EventContainerKind.Property, wepecs.Property.Identifier.Text); + var containerSymbol = semanticModel.GetSymbolInfo(wepecs.WithEventsContainer).Symbol; + var containerName = containerSymbol?.Name ?? wepecs.WithEventsContainer.Identifier.Text; + return new HandledEventsAnalysis.EventContainer(HandledEventsAnalysis.EventContainerKind.Property, containerName); default: throw new ArgumentOutOfRangeException(nameof(p), p, $"Unrecognized event container: `{p}`"); } diff --git a/CodeConverter/CSharp/VbNameExpander.cs b/CodeConverter/CSharp/VbNameExpander.cs index c04b5a37..c43c4c81 100644 --- a/CodeConverter/CSharp/VbNameExpander.cs +++ b/CodeConverter/CSharp/VbNameExpander.cs @@ -12,7 +12,10 @@ internal class VbNameExpander : ISyntaxExpander public static ISyntaxExpander Instance { get; } = new VbNameExpander(); public bool ShouldExpandWithinNode(SyntaxNode node, SemanticModel semanticModel) => - !ShouldExpandNode(node, semanticModel) && !IsRoslynInstanceExpressionBug(node as MemberAccessExpressionSyntax); + !ShouldExpandNode(node, semanticModel) && !IsRoslynInstanceExpressionBug(node as MemberAccessExpressionSyntax) + && !IsHandlesClause(node); + + private static bool IsHandlesClause(SyntaxNode node) => node is HandlesClauseSyntax; public bool ShouldExpandNode(SyntaxNode node, SemanticModel semanticModel) => ShouldExpandName(node) || ShouldExpandMemberAccess(node, semanticModel); diff --git a/Tests/CSharp/MemberTests/EventMemberTests.cs b/Tests/CSharp/MemberTests/EventMemberTests.cs index 0ad43198..82933bd7 100644 --- a/Tests/CSharp/MemberTests/EventMemberTests.cs +++ b/Tests/CSharp/MemberTests/EventMemberTests.cs @@ -953,4 +953,95 @@ internal virtual System.Windows.Forms.Button Button2 } }"); } + + [Fact] + public async Task HandlesSubPropertyOfWithEventsFieldAsync() + { + await TestConversionVisualBasicToCSharpAsync(@"Imports System.ComponentModel + +Public Class EditorProperties + Public Event Click As EventHandler +End Class + +Public Class Editor + Private ReadOnly _properties As New EditorProperties() + + ' DesignerSerializationVisibility.Content is what makes this property usable in a Handles clause + + Public ReadOnly Property Properties As EditorProperties + Get + Return _properties + End Get + End Property + + Public Event Click As EventHandler +End Class + + +Partial Public Class Form1 + Private Sub InitializeComponent() + Me.Editor1 = New Editor() + End Sub + Friend WithEvents Editor1 As Editor +End Class + +Partial Public Class Form1 + Private Sub Editor1_Properties_Click(sender As Object, e As EventArgs) Handles Editor1.Properties.Click + End Sub + + Private Sub Editor1_Click(sender As Object, e As EventArgs) Handles Editor1.Click + End Sub +End Class", @"using System; +using System.ComponentModel; + +public partial class EditorProperties +{ + public event EventHandler Click; +} + +public partial class Editor +{ + private readonly EditorProperties _properties = new EditorProperties(); + + // DesignerSerializationVisibility.Content is what makes this property usable in a Handles clause + [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] + public EditorProperties Properties + { + get + { + return _properties; + } + } + + public event EventHandler Click; +} + +[Microsoft.VisualBasic.CompilerServices.DesignerGenerated] +public partial class Form1 +{ + public Form1() + { + InitializeComponent(); + } + private void InitializeComponent() + { + Editor1 = new Editor(); + Editor1.Properties.Click += new EventHandler(Editor1_Properties_Click); + Editor1.Click += new EventHandler(Editor1_Click); + } + internal Editor Editor1; +} + +public partial class Form1 +{ + private void Editor1_Properties_Click(object sender, EventArgs e) + { + } + + private void Editor1_Click(object sender, EventArgs e) + { + } +} +"); + } } \ No newline at end of file