Skip to content

Commit 6e73565

Browse files
Merge pull request #571 from icsharpcode/issue/569/winforms-names
Winforms control names
2 parents fe73cc5 + dffec18 commit 6e73565

File tree

11 files changed

+172
-47
lines changed

11 files changed

+172
-47
lines changed

CodeConverter/CSharp/AdditionalInitializers.cs

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,18 @@
55
using Microsoft.CodeAnalysis;
66
using Microsoft.CodeAnalysis.CSharp;
77
using Microsoft.CodeAnalysis.CSharp.Syntax;
8-
using ExpressionSyntax = Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax;
98

109
namespace ICSharpCode.CodeConverter.CSharp
1110
{
12-
public class AdditionalInitializers
11+
internal class AdditionalInitializers
1312
{
1413
public AdditionalInitializers(bool shouldAddTypeWideInitToThisPart)
1514
{
1615
ShouldAddTypeWideInitToThisPart = shouldAddTypeWideInitToThisPart;
1716
}
1817

19-
public List<(ExpressionSyntax Field, SyntaxKind AssignmentKind, ExpressionSyntax Initializer)> AdditionalStaticInitializers { get; } = new List<(ExpressionSyntax, SyntaxKind, ExpressionSyntax)>();
20-
public List<(ExpressionSyntax Field, SyntaxKind AssignmentKind, ExpressionSyntax Initializer)> AdditionalInstanceInitializers { get; } = new List<(ExpressionSyntax, SyntaxKind, ExpressionSyntax)>();
18+
public List<Assignment> AdditionalStaticInitializers { get; } = new List<Assignment>();
19+
public List<Assignment> AdditionalInstanceInitializers { get; } = new List<Assignment>();
2120
public bool ShouldAddTypeWideInitToThisPart { get; }
2221

2322
public IReadOnlyCollection<MemberDeclarationSyntax> WithAdditionalInitializers(ITypeSymbol parentType,
@@ -40,7 +39,7 @@ public IReadOnlyCollection<MemberDeclarationSyntax> WithAdditionalInitializers(I
4039
}
4140

4241
private List<MemberDeclarationSyntax> WithAdditionalInitializers(List<MemberDeclarationSyntax> convertedMembers,
43-
SyntaxToken convertIdentifier, IReadOnlyCollection<(ExpressionSyntax Field, SyntaxKind AssignmentKind, ExpressionSyntax Initializer)> additionalInitializers,
42+
SyntaxToken convertIdentifier, IReadOnlyCollection<Assignment> additionalInitializers,
4443
SyntaxTokenList modifiers, IEnumerable<ConstructorDeclarationSyntax> constructorsEnumerable, bool addConstructor, bool addedConstructorRequiresInitializeComponent)
4544
{
4645
if (!additionalInitializers.Any() && (!addConstructor || !addedConstructorRequiresInitializeComponent)) return convertedMembers;
@@ -64,17 +63,23 @@ private List<MemberDeclarationSyntax> WithAdditionalInitializers(List<MemberDecl
6463
}
6564

6665
private ConstructorDeclarationSyntax WithAdditionalInitializers(ConstructorDeclarationSyntax oldConstructor,
67-
IReadOnlyCollection<(ExpressionSyntax Field, SyntaxKind AssignmentKind, ExpressionSyntax Initializer)> additionalConstructorAssignments)
66+
IReadOnlyCollection<Assignment> additionalConstructorAssignments)
6867
{
69-
var initializerStatements = additionalConstructorAssignments.Select(assignment =>
70-
SyntaxFactory.ExpressionStatement(SyntaxFactory.AssignmentExpression(
71-
assignment.AssignmentKind, assignment.Field, assignment.Initializer))
72-
).ToList();
68+
var preInitializerStatements = CreateAssignmentStatement(additionalConstructorAssignments.Where(x => !x.PostAssignment));
69+
var postInitializerStatements = CreateAssignmentStatement(additionalConstructorAssignments.Where(x => x.PostAssignment));
7370
var oldConstructorBody = oldConstructor.Body ?? SyntaxFactory.Block(SyntaxFactory.ExpressionStatement(oldConstructor.ExpressionBody.Expression));
7471
var newConstructor = oldConstructor.WithBody(oldConstructorBody.WithStatements(
75-
oldConstructorBody.Statements.InsertRange(0, initializerStatements)));
72+
oldConstructorBody.Statements.InsertRange(0, preInitializerStatements).AddRange(postInitializerStatements)));
7673

7774
return newConstructor;
7875
}
76+
77+
private static List<ExpressionStatementSyntax> CreateAssignmentStatement(IEnumerable<Assignment> additionalConstructorAssignments)
78+
{
79+
return additionalConstructorAssignments.Select(assignment =>
80+
SyntaxFactory.ExpressionStatement(SyntaxFactory.AssignmentExpression(
81+
assignment.AssignmentKind, assignment.Field, assignment.Initializer))
82+
).ToList();
83+
}
7984
}
8085
}

CodeConverter/CSharp/Assignment.cs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
using System.Collections.Generic;
2+
using Microsoft.CodeAnalysis.CSharp;
3+
using Microsoft.CodeAnalysis.CSharp.Syntax;
4+
5+
namespace ICSharpCode.CodeConverter.CSharp
6+
{
7+
internal struct Assignment
8+
{
9+
public ExpressionSyntax Field;
10+
public SyntaxKind AssignmentKind;
11+
public ExpressionSyntax Initializer;
12+
public bool PostAssignment;
13+
14+
public Assignment(ExpressionSyntax field, SyntaxKind assignmentKind, ExpressionSyntax initializer, bool postAssignment = false)
15+
{
16+
Field = field;
17+
AssignmentKind = assignmentKind;
18+
Initializer = initializer;
19+
PostAssignment = postAssignment;
20+
}
21+
22+
public override bool Equals(object obj) => obj is Assignment other &&
23+
(other.Field, other.AssignmentKind, other.Initializer, other.PostAssignment).Equals((Field, AssignmentKind, Initializer, PostAssignment));
24+
25+
26+
public override int GetHashCode() => (Field, AssignmentKind, Initializer, PostAssignment).GetHashCode();
27+
28+
public void Deconstruct(out ExpressionSyntax field, out SyntaxKind assignmentKind, out ExpressionSyntax initializer)
29+
{
30+
field = Field;
31+
assignmentKind = AssignmentKind;
32+
initializer = Initializer;
33+
}
34+
35+
public static implicit operator Assignment((ExpressionSyntax Field, SyntaxKind AssignmentKind, ExpressionSyntax Initializer) value)
36+
{
37+
return new Assignment(value.Field, value.AssignmentKind, value.Initializer);
38+
}
39+
}
40+
}

CodeConverter/CSharp/CommonConversions.cs

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,7 @@ public SyntaxToken ConvertIdentifier(SyntaxToken id, bool isAttribute = false, S
283283
text = propertyFieldSymbol.AssociatedSymbol.Name;
284284
} else if (normalizedText.EndsWith("Event", StringComparison.OrdinalIgnoreCase) && idSymbol is IFieldSymbol eventFieldSymbol && eventFieldSymbol.AssociatedSymbol?.IsKind(SymbolKind.Event) == true) {
285285
text = eventFieldSymbol.AssociatedSymbol.Name;
286-
} else if (MustInlinePropertyWithEventsAccess(id.Parent, idSymbol)) {
286+
} else if (WinformsConversions.MustInlinePropertyWithEventsAccess(id.Parent, idSymbol)) {
287287
// For C# Winforms designer, we need to use direct field access - see other usage of MustInlinePropertyWithEventsAccess
288288
text = "_" + text;
289289
}
@@ -317,20 +317,6 @@ private static string WithDeclarationCasing(SyntaxToken id, ISymbol idSymbol, st
317317
return text;
318318
}
319319

320-
/// <remarks>
321-
/// Co-ordinates inlining property events, see <see cref="MethodBodyExecutableStatementVisitor.GetPostAssignmentStatements"/>
322-
/// Also see usages of IsDesignerGeneratedTypeWithInitializeComponent
323-
/// </remarks>
324-
public static bool MustInlinePropertyWithEventsAccess(SyntaxNode anyNodePossiblyWithinMethod, ISymbol potentialPropertySymbol)
325-
{
326-
return InMethodCalledInitializeComponent(anyNodePossiblyWithinMethod) && potentialPropertySymbol is IPropertySymbol prop && prop.IsWithEvents;
327-
}
328-
329-
public static bool InMethodCalledInitializeComponent(SyntaxNode anyNodePossiblyWithinMethod)
330-
{
331-
return anyNodePossiblyWithinMethod.GetAncestor<VBSyntax.MethodBlockSyntax>()?.SubOrFunctionStatement.Identifier.Text == "InitializeComponent";
332-
}
333-
334320
public static SyntaxToken CsEscapedIdentifier(string text)
335321
{
336322
if (SyntaxFacts.GetKeywordKind(text) != CSSyntaxKind.None) text = "@" + text;

CodeConverter/CSharp/DeclarationNodeVisitor.cs

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -206,11 +206,17 @@ private async Task<IEnumerable<MemberDeclarationSyntax>> ConvertMembersAsync(VBS
206206
var requiresInitializeComponent = namedTypeSymbol.IsDesignerGeneratedTypeWithInitializeComponent(_vbCompilation);
207207

208208
if (shouldAddTypeWideInitToThisPart) {
209-
var constructorFieldInitializersFromOtherParts = GetConstructorFieldInitializersFromOtherParts(namedTypeSymbol);
209+
var otherPartsOfType = GetAllPartsOfType(parentType, namedTypeSymbol).ToArray();
210+
var constructorFieldInitializersFromOtherParts = otherPartsOfType
211+
.Where(t => (!Equals(t.Type.SyntaxTree.FilePath, _semanticModel.SyntaxTree.FilePath) || !t.Type.Span.Equals(parentType.Span)))
212+
.SelectMany(r => GetFieldsIdentifiersWithInitializer(r.Type, r.SemanticModel));
210213
additionalInitializers.AdditionalInstanceInitializers.AddRange(constructorFieldInitializersFromOtherParts);
211214
if (requiresInitializeComponent) {
212215
// Constructor event handlers not required since they'll be inside InitializeComponent - see other use of IsDesignerGeneratedTypeWithInitializeComponent
213-
directlyConvertedMembers = directlyConvertedMembers.Concat(methodsWithHandles.CreateDelegatingMethodsRequiredByInitializeComponent());
216+
directlyConvertedMembers = directlyConvertedMembers
217+
.Concat(methodsWithHandles.CreateDelegatingMethodsRequiredByInitializeComponent());
218+
additionalInitializers.AdditionalInstanceInitializers
219+
.AddRange(WinformsConversions.GetNameAssignments(otherPartsOfType));
214220
} else {
215221
additionalInitializers.AdditionalInstanceInitializers.AddRange(methodsWithHandles.GetConstructorEventHandlers());
216222
}
@@ -231,24 +237,23 @@ async Task<MemberDeclarationSyntax[]> GetDirectlyConvertedMembers(AdditionalInit
231237
}
232238
}
233239

234-
private IEnumerable<(ExpressionSyntax, CSSyntaxKind SimpleAssignmentExpression, ExpressionSyntax)> GetConstructorFieldInitializersFromOtherParts(ITypeSymbol parentType)
240+
private IEnumerable<(VBSyntax.TypeBlockSyntax Type, SemanticModel SemanticModel)> GetAllPartsOfType(VBSyntax.TypeBlockSyntax parentTypeBlock, ITypeSymbol parentType)
235241
{
236242
return parentType.DeclaringSyntaxReferences
237-
.Where(r => !Equals(r.SyntaxTree.FilePath, _semanticModel.SyntaxTree.FilePath))
238-
.Select(t => (Type: t.GetSyntax().Parent as VBSyntax.TypeBlockSyntax, SemanticModel: _vbCompilation.GetSemanticModel(t.SyntaxTree)))
239-
.Where(t => t.Type != null)
240-
.SelectMany(r => GetFieldsIdentifiersWithInitializer(r.Type, r.SemanticModel));
243+
.Select(t => t.GetSyntax().Parent as VBSyntax.TypeBlockSyntax)
244+
.Where(t => t != null)
245+
.Select(t => (Type: t, SemanticModel: _vbCompilation.GetSemanticModel(t.SyntaxTree)));
241246
}
242247

243-
private IEnumerable<(ExpressionSyntax, CSSyntaxKind SimpleAssignmentExpression, ExpressionSyntax)> GetFieldsIdentifiersWithInitializer(VBSyntax.TypeBlockSyntax tbs, SemanticModel semanticModel)
248+
private IEnumerable<Assignment> GetFieldsIdentifiersWithInitializer(VBSyntax.TypeBlockSyntax tbs, SemanticModel semanticModel)
244249
{
245250
return tbs.Members.OfType<VBSyntax.FieldDeclarationSyntax>()
246251
.SelectMany(f => f.Declarators.SelectMany(d => d.Names.Select(n => (n, d.Initializer))))
247252
.Where(f => !semanticModel.IsDefinitelyStatic(f.n, f.Initializer?.Value))
248253
.Select(f => CreateInitializer(f));
249254
}
250255

251-
private (ExpressionSyntax, CSSyntaxKind SimpleAssignmentExpression, ExpressionSyntax) CreateInitializer((VBSyntax.ModifiedIdentifierSyntax n, VBSyntax.EqualsValueSyntax Initializer) f)
256+
private Assignment CreateInitializer((VBSyntax.ModifiedIdentifierSyntax n, VBSyntax.EqualsValueSyntax Initializer) f)
252257
{
253258
var csId = CommonConversions.ConvertIdentifier(f.n.Identifier);
254259
string initializerFunctionName = CommonConversions.GetInitialValueFunctionName(f.n);

CodeConverter/CSharp/ExpressionNodeVisitor.cs

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -276,13 +276,9 @@ public override async Task<CSharpSyntaxNode> VisitLiteralExpression(VBasic.Synta
276276

277277
var val = node.Token.Value;
278278
var text = node.Token.Text;
279-
if (node.Parent is VBSyntax.AssignmentStatementSyntax assignment && CommonConversions.InMethodCalledInitializeComponent(node.Parent) &&
280-
assignment.Left is VBSyntax.MemberAccessExpressionSyntax maes && !(maes.Expression is VBSyntax.MeExpressionSyntax) &&
281-
maes.Name.ToString() == "Name" &&
282-
val is string valStr) {
283-
// Update name so field is regenerated correctly by winforms designer
284-
val = "_" + valStr;
285-
text = "\"_" + valStr + "\"";
279+
if (WinformsConversions.ShouldPrefixAssignedNameWithUnderscore(node.Parent as VBSyntax.AssignmentStatementSyntax) && val is string valStr) {
280+
val = "_" + valStr;
281+
text = "\"_" + valStr + "\"";
286282
}
287283

288284
return CommonConversions.Literal(val, text, convertedType);

CodeConverter/CSharp/MethodWithHandles.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
namespace ICSharpCode.CodeConverter.CSharp
1313
{
14-
public class MethodWithHandles
14+
internal class MethodWithHandles
1515
{
1616
private IdentifierNameSyntax _methodId;
1717
private readonly SyntaxGenerator _csSyntaxGenerator;
@@ -173,10 +173,10 @@ private static ExpressionSyntax MemberAccess(ExpressionSyntax eventSource, (Synt
173173
eventSource, SyntaxFactory.IdentifierName(e.EventSymbolName));
174174
}
175175

176-
public IEnumerable<(ExpressionSyntax EventField, SyntaxKind AssignmentKind, ExpressionSyntax HandlerId)> GetConstructorEventHandlers()
176+
public IEnumerable<Assignment> GetConstructorEventHandlers()
177177
{
178178
return HandledClassEventCSharpIds.Select(e =>
179-
(MemberAccess(SyntaxFactory.IdentifierName(e.EventContainerName), e), SyntaxKind.AddAssignmentExpression, Invocable(_methodId, e.ParametersToDiscard))
179+
new Assignment(MemberAccess(SyntaxFactory.IdentifierName(e.EventContainerName), e), SyntaxKind.AddAssignmentExpression, Invocable(_methodId, e.ParametersToDiscard))
180180
);
181181
}
182182

CodeConverter/CSharp/MethodsWithHandles.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ public IEnumerable<MemberDeclarationSyntax> GetDeclarationsForFieldBackedPropert
4747
/// </summary>
4848
public SyntaxList<StatementSyntax> GetPostAssignmentStatements(Microsoft.CodeAnalysis.VisualBasic.Syntax.AssignmentStatementSyntax node, ISymbol potentialPropertySymbol)
4949
{
50-
if (CommonConversions.MustInlinePropertyWithEventsAccess(node, potentialPropertySymbol))
50+
if (WinformsConversions.MustInlinePropertyWithEventsAccess(node, potentialPropertySymbol))
5151
{
5252
var fieldName = SyntaxFactory.IdentifierName("_" + potentialPropertySymbol.Name);
5353
var handledMethods = _handledMethodsFromPropertyWithEventName[potentialPropertySymbol.Name].ToArray();
@@ -67,7 +67,7 @@ public IEnumerable<StatementSyntax> GetInitializeComponentClassEventHandlers()
6767
return _methodWithHandleses.SelectMany(m => m.GetInitializeComponentClassEventHandlers()).ToArray();
6868
}
6969

70-
public (ExpressionSyntax EventField, SyntaxKind AssignmentKind, ExpressionSyntax HandlerId)[] GetConstructorEventHandlers()
70+
public Assignment[] GetConstructorEventHandlers()
7171
{
7272
return _methodWithHandleses.SelectMany(m => m.GetConstructorEventHandlers()).ToArray();
7373
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using ICSharpCode.CodeConverter.Util;
5+
using Microsoft.CodeAnalysis;
6+
using Microsoft.CodeAnalysis.CSharp;
7+
using VBasic = Microsoft.CodeAnalysis.VisualBasic;
8+
using VBSyntax = Microsoft.CodeAnalysis.VisualBasic.Syntax;
9+
10+
namespace ICSharpCode.CodeConverter.CSharp
11+
{
12+
internal static class WinformsConversions
13+
{
14+
/// <remarks>
15+
/// Co-ordinates inlining property events, see <see cref="MethodBodyExecutableStatementVisitor.GetPostAssignmentStatements"/>
16+
/// Also see usages of IsDesignerGeneratedTypeWithInitializeComponent
17+
/// </remarks>
18+
public static bool MustInlinePropertyWithEventsAccess(SyntaxNode anyNodePossiblyWithinMethod, ISymbol potentialPropertySymbol)
19+
{
20+
return InMethodCalledInitializeComponent(anyNodePossiblyWithinMethod) && potentialPropertySymbol is IPropertySymbol prop && prop.IsWithEvents;
21+
}
22+
23+
public static bool InMethodCalledInitializeComponent(SyntaxNode anyNodePossiblyWithinMethod)
24+
{
25+
var methodBlockSyntax = anyNodePossiblyWithinMethod.GetAncestor<VBSyntax.MethodBlockSyntax>();
26+
return IsInitializeComponent(methodBlockSyntax);
27+
}
28+
29+
private static bool IsInitializeComponent(VBSyntax.MethodBlockSyntax methodBlockSyntax)
30+
{
31+
return methodBlockSyntax?.SubOrFunctionStatement.Identifier.Text == "InitializeComponent";
32+
}
33+
34+
/// <summary>
35+
/// We replace a field with a property to handle event subscription, so need to update the name so the winforms designer regenerates the file correctly in future
36+
/// </summary>
37+
/// <returns></returns>
38+
public static bool ShouldPrefixAssignedNameWithUnderscore(VBSyntax.StatementSyntax statementOrNull)
39+
{
40+
return statementOrNull is VBSyntax.AssignmentStatementSyntax assignment && InMethodCalledInitializeComponent(assignment) &&
41+
assignment.Left is VBSyntax.MemberAccessExpressionSyntax maes &&
42+
!(maes.Expression is VBSyntax.MeExpressionSyntax) &&
43+
maes.Name.ToString() == "Name";
44+
}
45+
46+
private static T LastOrDefaultDescendant<T>(this VBasic.VisualBasicSyntaxNode syntaxNode) {
47+
return syntaxNode.DescendantNodes().OfType<T>().LastOrDefault();
48+
}
49+
50+
internal static IEnumerable<Assignment> GetNameAssignments((VBSyntax.TypeBlockSyntax Type, SemanticModel SemanticModel)[] otherPartsOfType)
51+
{
52+
return otherPartsOfType.SelectMany(typePart =>
53+
typePart.Type.Members.OfType<VBSyntax.MethodBlockSyntax>()
54+
.Where(IsInitializeComponent)
55+
.SelectMany(GetAssignments)
56+
);
57+
}
58+
59+
private static IEnumerable<Assignment> GetAssignments(VBSyntax.MethodBlockSyntax initializeComponent)
60+
{
61+
return initializeComponent.Statements
62+
.OfType<VBSyntax.AssignmentStatementSyntax>()
63+
.Where(ShouldPrefixAssignedNameWithUnderscore)
64+
.Select(s => (s.Left as VBSyntax.MemberAccessExpressionSyntax)?.Expression.LastOrDefaultDescendant<VBSyntax.IdentifierNameSyntax>())
65+
.Where(s => s != null)
66+
.Select(id => {
67+
var nameAccess = ValidSyntaxFactory.MemberAccess(SyntaxFactory.IdentifierName("_" + id.Identifier.Text), "Name");
68+
var originalRuntimeNameToRestore = SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, SyntaxFactory.Literal(id.Identifier.Text));
69+
return new Assignment(nameAccess, SyntaxKind.SimpleAssignmentExpression, originalRuntimeNameToRestore, true);
70+
});
71+
}
72+
}
73+
}

Tests/CSharp/MemberTests/EventMemberTests.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -525,6 +525,7 @@ public partial class TestHandlesAdded
525525
public TestHandlesAdded()
526526
{
527527
InitializeComponent();
528+
_POW_btnV2DBM.Name = ""POW_btnV2DBM"";
528529
}
529530
530531
public void InitializeComponent()

0 commit comments

Comments
 (0)