Skip to content

Commit fe6e153

Browse files
Refactor existing renamer
1 parent b1e4ced commit fe6e153

4 files changed

Lines changed: 152 additions & 133 deletions

File tree

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Threading.Tasks;
5+
using ICSharpCode.CodeConverter.Util;
6+
using Microsoft.CodeAnalysis;
7+
using Microsoft.CodeAnalysis.FindSymbols;
8+
using Microsoft.CodeAnalysis.Rename;
9+
10+
namespace ICSharpCode.CodeConverter.Shared
11+
{
12+
internal static class SymbolRenamer
13+
{
14+
public static IEnumerable<(ISymbol Original, string NewName)> GetSymbolsWithNewNames(
15+
IEnumerable<ISymbol> toRename, Func<string, bool> canUse, bool canKeepOne)
16+
{
17+
var symbolsWithNewNames = toRename.OrderByDescending(x => x.DeclaredAccessibility).ThenByDescending(x => x.Kind == SymbolKind.Parameter || x.Kind == SymbolKind.Property).Skip(canKeepOne ? 1 :0).Select(tr =>
18+
{
19+
string newName = NameGenerator.GenerateUniqueName(GetBaseName(tr), canUse);
20+
return (Original: tr, NewName: newName);
21+
});
22+
return symbolsWithNewNames;
23+
}
24+
25+
public static string GetName(ISymbol m) {
26+
if (m.CanBeReferencedByName)
27+
return m.Name;
28+
if (m.ExplicitInterfaceImplementations().Any())
29+
return m.Name.Split('.').Last();
30+
return m.Name;
31+
}
32+
33+
public static async Task<Project> PerformRenamesAsync(Project project, IReadOnlyCollection<(ISymbol Original, string NewName)> symbolsWithNewNames)
34+
{
35+
var solution = project.Solution;
36+
foreach (var (originalSymbol, newName) in symbolsWithNewNames) {
37+
project = solution.GetProject(project.Id);
38+
var compilation = await project.GetCompilationAsync();
39+
ISymbol currentDeclaration = SymbolFinder.FindSimilarSymbols(originalSymbol, compilation).FirstOrDefault();
40+
if (currentDeclaration == null)
41+
continue; //Must have already renamed this symbol for a different reason
42+
solution = await Renamer.RenameSymbolAsync(solution, currentDeclaration, newName, solution.Workspace.Options);
43+
}
44+
45+
return solution.GetProject(project.Id);
46+
}
47+
48+
private static string GetBaseName(ISymbol declaration)
49+
{
50+
string prefix = declaration.Kind.ToString().ToLowerInvariant()[0] + "_";
51+
string name = GetName(declaration);
52+
return prefix + name.Substring(0, 1).ToUpperInvariant() + name.Substring(1);
53+
}
54+
55+
public static IEnumerable<INamespaceOrTypeSymbol> GetNamespacesAndTypesInAssembly(Project project, Compilation compilation)
56+
{
57+
return compilation.GlobalNamespace.FollowProperty((INamespaceOrTypeSymbol n) => n.GetMembers().OfType<INamespaceOrTypeSymbol>().Where(s => s.IsDefinedInSource() && s?.ContainingAssembly?.Name == project.AssemblyName));
58+
}
59+
60+
public static IEnumerable<(ISymbol Original, string NewName)> GetSymbolsWithNewNames(IReadOnlyCollection<ISymbol> symbolGroup, HashSet<string> names, bool caseSensitive)
61+
{
62+
var canRename = symbolGroup.Where(s => s.IsDefinedInSource() && s.CanBeReferencedByName).ToArray();
63+
var specialSymbolUsingName = canRename.Length < symbolGroup.Count;
64+
var methodSymbols = canRename.OfType<IMethodSymbol>().ToArray();
65+
var canKeepOneNormalMemberName = !specialSymbolUsingName && !methodSymbols.Any();
66+
symbolGroup = canRename.Except(methodSymbols).ToArray();
67+
(ISymbol Original, string NewName)[] methodsWithNewNames = GetMethodSymbolsWithNewNames(methodSymbols.ToArray(), names, specialSymbolUsingName, caseSensitive);
68+
return GetSymbolsWithNewNames(symbolGroup, names.Add, canKeepOneNormalMemberName).Concat(methodsWithNewNames);
69+
}
70+
71+
private static (ISymbol Original, string NewName)[] GetMethodSymbolsWithNewNames(IMethodSymbol[] methodSymbols,
72+
HashSet<string> names,
73+
bool specialSymbolUsingName, bool caseSensitive)
74+
{
75+
var stringComparer = caseSensitive ? StringComparer.Ordinal : StringComparer.OrdinalIgnoreCase;
76+
var methodsBySignature = methodSymbols
77+
.ToLookup(m => m.GetUnqualifiedMethodSignature(caseSensitive))
78+
.Where(g => g.Count() > 1)
79+
.SelectMany(clashingMethodGroup =>
80+
{
81+
var thisMethodGroupNames = new HashSet<string>(stringComparer);
82+
var symbolsWithNewNames = GetSymbolsWithNewNames(clashingMethodGroup,
83+
n => !names.Contains(n) && thisMethodGroupNames.Add(n),
84+
!specialSymbolUsingName).ToArray();
85+
return symbolsWithNewNames;
86+
}).ToArray();
87+
88+
foreach (var newMethodNames in methodsBySignature.Select(m => m.NewName))
89+
{
90+
names.Add(newMethodNames);
91+
}
92+
93+
return methodsBySignature;
94+
}
95+
}
96+
}

CodeConverter/VB/CSToVBProjectContentsConverter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ public CSToVBProjectContentsConverter(ConversionOptions conversionOptions, IProg
5252
public async Task InitializeSourceAsync(Project project)
5353
{
5454
// TODO: Don't throw away solution-wide effects - write them to referencing files, and use in conversion of any other projects being converted at the same time.
55-
project = await CaseConflictResolver.RenameClashingSymbolsAsync(project);
55+
project = await ClashingMemberRenamer.RenameClashingSymbolsAsync(project);
5656
_sourceCsProject = project;
5757
_convertedVbProject = project.ToProjectFromAnyOptions(_vbCompilationOptions, _vbParseOptions);
5858
_vbReferenceProject = project.CreateReferenceOnlyProjectFromAnyOptions(_vbCompilationOptions, _vbParseOptions);

CodeConverter/VB/CaseConflictResolver.cs

Lines changed: 0 additions & 132 deletions
This file was deleted.
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Threading.Tasks;
5+
using ICSharpCode.CodeConverter.Shared;
6+
using ICSharpCode.CodeConverter.Util;
7+
using Microsoft.CodeAnalysis;
8+
9+
namespace ICSharpCode.CodeConverter.VB
10+
{
11+
internal static class ClashingMemberRenamer
12+
{
13+
/// <summary>
14+
/// Renames symbols in a CSharp project so that they don't clash on case within the same named scope, attempting to rename the least public ones first.
15+
/// This is because C# is case sensitive but VB is case insensitive.
16+
/// </summary>
17+
/// <remarks>
18+
/// Cases in different named scopes should be dealt with by <seealso cref="DocumentExtensions.ExpandAsync"/>.
19+
/// For names scoped within a type member, see <seealso cref="SemanticModelSymbolSetExtensions.GetCsLocalSymbolsPerScope"/>.
20+
/// </remarks>
21+
public static async Task<Project> RenameClashingSymbolsAsync(Project project)
22+
{
23+
var compilation = await project.GetCompilationAsync();
24+
var memberRenames = SymbolRenamer.GetNamespacesAndTypesInAssembly(project, compilation)
25+
.SelectMany(x => GetSymbolsWithNewNames(x, compilation));
26+
return await SymbolRenamer.PerformRenamesAsync(project, memberRenames.ToList());
27+
}
28+
29+
private static IEnumerable<(ISymbol Original, string NewName)> GetSymbolsWithNewNames(INamespaceOrTypeSymbol containerSymbol, Compilation compilation)
30+
{
31+
var members = containerSymbol.GetMembers().Where(m => m.Locations.Any(loc => loc.SourceTree != null && compilation.ContainsSyntaxTree(loc.SourceTree))).ToArray();
32+
var symbolSets = GetLocalSymbolSets(containerSymbol, compilation, members).Concat(members.AsEnumerable().Yield());
33+
return symbolSets.SelectMany(GetUniqueNamesForSymbolSet);
34+
}
35+
36+
public static IEnumerable<IEnumerable<ISymbol>> GetLocalSymbolSets(INamespaceOrTypeSymbol containerSymbol, Compilation compilation, IReadOnlyCollection<ISymbol> members)
37+
{
38+
if (!(containerSymbol is ITypeSymbol)) return Enumerable.Empty<IEnumerable<ISymbol>>();
39+
40+
var semanticModels = containerSymbol.Locations.Select(loc => loc.SourceTree).Distinct()
41+
.Where(compilation.ContainsSyntaxTree)
42+
.Select(sourceTree => compilation.GetSemanticModel(sourceTree, true));
43+
return semanticModels.SelectMany(semanticModel => members.SelectMany(semanticModel.GetCsSymbolsPerScope));
44+
}
45+
46+
private static IEnumerable<(ISymbol Original, string NewName)> GetUniqueNamesForSymbolSet(IEnumerable<ISymbol> symbols) {
47+
var membersByCaseInsensitiveName = symbols.ToLookup(SymbolRenamer.GetName, m => m, StringComparer.OrdinalIgnoreCase);
48+
var names = new HashSet<string>(membersByCaseInsensitiveName.Select(ms => ms.Key),
49+
StringComparer.OrdinalIgnoreCase);
50+
var symbolsWithNewNames = membersByCaseInsensitiveName.Where(ms => ms.Count() > 1)
51+
.SelectMany(symbolGroup => SymbolRenamer.GetSymbolsWithNewNames(symbolGroup.ToArray(), names, false));
52+
return symbolsWithNewNames;
53+
}
54+
}
55+
}

0 commit comments

Comments
 (0)