Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion CodeConverter/CSharp/EventDescriptor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@

namespace ICSharpCode.CodeConverter.CSharp;

internal record EventDescriptor(IdentifierNameSyntax VBEventName, IEventSymbol SymbolOrNull);
internal record EventDescriptor(IdentifierNameSyntax VBEventName, IEventSymbol SymbolOrNull, string SubPropertyName = null);
4 changes: 4 additions & 0 deletions CodeConverter/CSharp/HandledEventsAnalysis.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
13 changes: 11 additions & 2 deletions CodeConverter/CSharp/HandledEventsAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,16 @@ private async Task<bool> 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) {
Expand All @@ -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}`");
}
Expand Down
5 changes: 4 additions & 1 deletion CodeConverter/CSharp/VbNameExpander.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
91 changes: 91 additions & 0 deletions Tests/CSharp/MemberTests/EventMemberTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
<DesignerSerializationVisibility(DesignerSerializationVisibility.Content)>
Public ReadOnly Property Properties As EditorProperties
Get
Return _properties
End Get
End Property

Public Event Click As EventHandler
End Class

<Microsoft.VisualBasic.CompilerServices.DesignerGenerated>
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)
{
}
}
");
}
}