Skip to content

Commit 825cc5e

Browse files
Overescape identifiers - fixes # 1043
Don't worry about exactly which context, just escape in any case
1 parent 703a678 commit 825cc5e

9 files changed

Lines changed: 53 additions & 40 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
99

1010

1111
### VB -> C#
12-
12+
* Remove square brackets from identifiers [#1043](https://github.com/icsharpcode/CodeConverter/issues/1043)
1313

1414
### C# -> VB
1515

CodeConverter/CSharp/CommonConversions.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -341,8 +341,11 @@ private static string WithDeclarationName(SyntaxToken id, ISymbol idSymbol, stri
341341

342342
public static SyntaxToken CsEscapedIdentifier(string text)
343343
{
344-
text = text.TrimStart('[').TrimEnd(']');
345-
if (SyntaxFacts.GetKeywordKind(text) != CSSyntaxKind.None) text = "@" + text;
344+
if (!CS.SyntaxFacts.IsValidIdentifier(text)) {
345+
text = new string(text.Where(CS.SyntaxFacts.IsIdentifierPartCharacter).ToArray());
346+
if (!CS.SyntaxFacts.IsIdentifierStartCharacter(text[0])) text = "a" + text;
347+
}
348+
if (SyntaxFacts.GetKeywordKind(text) != CSSyntaxKind.None || SyntaxFacts.GetContextualKeywordKind(text) != CSSyntaxKind.None) text = "@" + text;
346349
return SyntaxFactory.Identifier(text);
347350
}
348351

CodeConverter/CSharp/ExpressionNodeVisitor.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -726,7 +726,7 @@ await node.Name.AcceptAsync<ExpressionSyntax>(TriviaConvertingExpressionVisitor)
726726
);
727727
}
728728

729-
private string GenerateUniqueVariableName(VisualBasicSyntaxNode existingNode, string varNameBase) => NameGenerator.GetUniqueVariableNameInScope(_semanticModel, _generatedNames, existingNode, varNameBase);
729+
private string GenerateUniqueVariableName(VisualBasicSyntaxNode existingNode, string varNameBase) => NameGenerator.CS.GetUniqueVariableNameInScope(_semanticModel, _generatedNames, existingNode, varNameBase);
730730

731731
private static ExpressionSyntax DeclareVariableInline(ExpressionSyntax csExpressionSyntax, string temporaryName)
732732
{

CodeConverter/CSharp/MethodBodyExecutableStatementVisitor.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -962,7 +962,7 @@ public override async Task<SyntaxList<StatementSyntax>> VisitWithBlock(VBSyntax.
962962

963963
private string GetUniqueVariableNameInScope(VBasic.VisualBasicSyntaxNode node, string variableNameBase)
964964
{
965-
return NameGenerator.GetUniqueVariableNameInScope(_semanticModel, _generatedNames, node, variableNameBase);
965+
return NameGenerator.CS.GetUniqueVariableNameInScope(_semanticModel, _generatedNames, node, variableNameBase);
966966
}
967967

968968
public override async Task<SyntaxList<StatementSyntax>> VisitTryBlock(VBSyntax.TryBlockSyntax node)

CodeConverter/CSharp/PerScopeState.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ public SyntaxList<StatementSyntax> CreateStatements(VBasic.VisualBasicSyntaxNode
100100
{
101101
var localFunctions = GetParameterlessFunctions();
102102
var newNames = localFunctions.ToDictionary(f => f.Id, f =>
103-
NameGenerator.GetUniqueVariableNameInScope(semanticModel, generatedNames, vbNode, f.Prefix)
103+
NameGenerator.CS.GetUniqueVariableNameInScope(semanticModel, generatedNames, vbNode, f.Prefix)
104104
);
105105
var functions = localFunctions.Select(f => f.AsLocalFunction(newNames[f.Id]));
106106
statements = ReplaceNames(functions.Concat(statements), newNames);
@@ -121,7 +121,7 @@ public async Task<SyntaxList<StatementSyntax>> CreateLocalsAsync(VBasic.VisualBa
121121
HoistToParent(variable);
122122
} else {
123123
// The variable comes from the VB scope, only check for conflict with other hoisted definitions
124-
string name = NameGenerator.GenerateUniqueVariableName(generatedNames, variable.OriginalVariableName);
124+
string name = NameGenerator.CS.GenerateUniqueVariableName(generatedNames, CommonConversions.CsEscapedIdentifier(variable.OriginalVariableName).Text);
125125
if (variable.Nested) {
126126
newNames.Add(variable.Id, name);
127127
} else if (name != variable.OriginalVariableName) {
@@ -135,7 +135,7 @@ public async Task<SyntaxList<StatementSyntax>> CreateLocalsAsync(VBasic.VisualBa
135135

136136
var additionalDeclarationInfo = GetDeclarations();
137137
foreach (var additionalLocal in additionalDeclarationInfo) {
138-
newNames.Add(additionalLocal.Id, NameGenerator.GetUniqueVariableNameInScope(semanticModel, generatedNames, vbNode, additionalLocal.Prefix));
138+
newNames.Add(additionalLocal.Id, NameGenerator.CS.GetUniqueVariableNameInScope(semanticModel, generatedNames, vbNode, additionalLocal.Prefix));
139139
var decl = CommonConversions.CreateVariableDeclarationAndAssignment(newNames[additionalLocal.Id],
140140
additionalLocal.Initializer, additionalLocal.Type);
141141
preDeclarations.Add(CS.SyntaxFactory.LocalDeclarationStatement(decl));
@@ -158,7 +158,7 @@ public async Task<SyntaxList<MemberDeclarationSyntax>> CreateVbStaticFieldsAsync
158158

159159
var fieldInfo = GetFields();
160160
var newNames = fieldInfo.ToDictionary(f => f.OriginalVariableName, f =>
161-
NameGenerator.GetUniqueVariableNameInScope(semanticModel, generatedNames, typeNode, f.FieldName)
161+
NameGenerator.CS.GetUniqueVariableNameInScope(semanticModel, generatedNames, typeNode, f.FieldName)
162162
);
163163
foreach (var field in fieldInfo) {
164164
var decl = (field.Initializer != null)
Lines changed: 36 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,35 @@
11
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
22

3+
using ICSharpCode.CodeConverter.CSharp;
4+
35
namespace ICSharpCode.CodeConverter.Common;
46

5-
internal static class NameGenerator
7+
internal class NameGenerator
68
{
7-
public static string GenerateUniqueName(string baseName, Func<string, bool> canUse)
8-
{
9-
return GenerateUniqueName(baseName, string.Empty, canUse);
10-
}
9+
private readonly Func<string, string> _getValidIdentifier;
1110

12-
private static string GenerateUniqueName(string baseName, string extension, Func<string, bool> canUse)
13-
{
14-
if (!string.IsNullOrEmpty(extension) && !extension.StartsWith(".", StringComparison.InvariantCulture)) {
15-
extension = "." + extension;
16-
}
11+
public static NameGenerator CS { get; } = new NameGenerator(x => CommonConversions.CsEscapedIdentifier(x).Text);
12+
public static NameGenerator Generic { get; } = new NameGenerator(x => x);
1713

18-
var name = baseName + extension;
19-
var index = 1;
14+
private NameGenerator(Func<string, string> getValidIdentifier) => _getValidIdentifier = getValidIdentifier;
2015

21-
// Check for collisions
22-
while (!canUse(name)) {
23-
name = baseName + index + extension;
24-
index++;
25-
}
26-
27-
return name;
28-
}
16+
public string GenerateUniqueName(string baseName, Func<string, bool> canUse) => GenerateUniqueName(baseName, string.Empty, canUse);
2917

30-
public static string GetUniqueVariableNameInScope(SemanticModel semanticModel, HashSet<string> generatedNames, VBasic.VisualBasicSyntaxNode node, string variableNameBase)
18+
public string GetUniqueVariableNameInScope(SemanticModel semanticModel, HashSet<string> generatedNames, VBasic.VisualBasicSyntaxNode node, string variableNameBase)
3119
{
3220
// Need to check not just the symbols this node has access to, but whether there are any nested blocks which have access to this node and contain a conflicting name
3321
var scopeStarts = GetScopeStarts(node);
3422
return GenerateUniqueVariableNameInScope(semanticModel, generatedNames, variableNameBase, scopeStarts);
3523
}
3624

37-
private static string GenerateUniqueVariableNameInScope(SemanticModel semanticModel, HashSet<string> generatedNames,
25+
public string GenerateUniqueVariableName(HashSet<string> generatedNames, string variableNameBase)
26+
{
27+
string uniqueName = GenerateUniqueName(variableNameBase, string.Empty, n => !generatedNames.Contains(n));
28+
generatedNames.Add(uniqueName);
29+
return uniqueName;
30+
}
31+
32+
private string GenerateUniqueVariableNameInScope(SemanticModel semanticModel, HashSet<string> generatedNames,
3833
string variableNameBase, List<int> scopeStarts)
3934
{
4035
string uniqueName = GenerateUniqueName(variableNameBase, string.Empty,
@@ -47,16 +42,29 @@ private static string GenerateUniqueVariableNameInScope(SemanticModel semanticMo
4742
return uniqueName;
4843
}
4944

45+
private string GenerateUniqueName(string baseName, string extension, Func<string, bool> canUse)
46+
{
47+
baseName = _getValidIdentifier(baseName);
48+
if (!string.IsNullOrEmpty(extension) && !extension.StartsWith(".", StringComparison.InvariantCulture)) {
49+
extension = "." + extension;
50+
}
51+
52+
var name = baseName + extension;
53+
var index = 1;
54+
55+
// Check for collisions
56+
while (!canUse(name)) {
57+
name = baseName + index + extension;
58+
index++;
59+
}
60+
61+
return name;
62+
}
63+
5064
private static List<int> GetScopeStarts(VBasic.VisualBasicSyntaxNode node)
5165
{
5266
return node.GetAncestorOrThis<VBSyntax.StatementSyntax>().DescendantNodesAndSelf()
5367
.OfType<VBSyntax.StatementSyntax>().Select(n => n.SpanStart).ToList();
5468
}
5569

56-
public static string GenerateUniqueVariableName(HashSet<string> generatedNames, string variableNameBase)
57-
{
58-
string uniqueName = GenerateUniqueName(variableNameBase, string.Empty, n => !generatedNames.Contains(n));
59-
generatedNames.Add(uniqueName);
60-
return uniqueName;
61-
}
6270
}

CodeConverter/Common/SymbolRenamer.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ namespace ICSharpCode.CodeConverter.Common;
55

66
internal static class SymbolRenamer
77
{
8+
private static readonly NameGenerator NameGenerator = NameGenerator.Generic;
9+
810
public static IEnumerable<(ISymbol Original, string NewName)> GetSymbolsWithNewNames(
911
IEnumerable<ISymbol> toRename, Func<string, bool> canUse, bool canKeepOne)
1012
{

CodeConverter/VB/NodesVisitor.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -475,7 +475,7 @@ private ImplementsClauseSyntax CreateImplementsClauseSyntaxOrNull(ISymbol member
475475
#pragma warning restore RS1024 // Compare symbols correctly
476476
string explicitMemberName = UndottedMemberName(memberInfo.Name);
477477
var hasDuplicateNames = memberNames[explicitMemberName].Count() > 1;
478-
if (hasDuplicateNames) id = SyntaxFactory.Identifier(NameGenerator.GenerateUniqueName(explicitMemberName, n => !memberNames.Contains(n) && _addedNames.Add(n)));
478+
if (hasDuplicateNames) id = SyntaxFactory.Identifier(NameGenerator.Generic.GenerateUniqueName(explicitMemberName, n => !memberNames.Contains(n) && _addedNames.Add(n)));
479479
} else {
480480
var containingType = memberInfo.ContainingType;
481481
var baseClassesAndInterfaces = containingType.GetAllBaseClassesAndInterfaces(true);

Tests/CSharp/ExpressionTests/ExpressionTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1029,7 +1029,7 @@ End Sub
10291029
10301030
public static partial class MyExtensions
10311031
{
1032-
public static void NewColumn(Type type, string strV1 = null, string code = ""code"", int argInt = 1)
1032+
public static void NewColumn(Type @type, string strV1 = null, string code = ""code"", int argInt = 1)
10331033
{
10341034
}
10351035
@@ -2524,7 +2524,7 @@ Static i As Integer
25242524
@"
25252525
internal partial class SurroundingClass
25262526
{
2527-
private int _[Step]_i;
2527+
private int _Step_i;
25282528
25292529
public void Step()
25302530
{

0 commit comments

Comments
 (0)