Skip to content

Commit 0c61473

Browse files
Fix the TypeClassParameter analyzer (#244)
- Fix GetRenamedSolution() to work on Visual Studio 2022
1 parent 8f4bf1f commit 0c61473

File tree

2 files changed

+100
-44
lines changed

2 files changed

+100
-44
lines changed

CodeFixes/Refactoring/TypeClassParameter/CodeFixer.cs

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -85,31 +85,32 @@ private static async Task<Solution> Replace(
8585
}
8686

8787
private static async Task<Solution?> Replace2(
88-
Document realDocument,
89-
ParameterSyntax realNode,
88+
Document document,
89+
ParameterSyntax node,
9090
CancellationToken cancellationToken)
9191
{
92-
var documentId = realDocument.Id;
93-
var realRoot = realNode.SyntaxTree
92+
var documentId = document.Id;
93+
var root = node.SyntaxTree
9494
.GetRoot(cancellationToken);
95-
var solution = realDocument.Project
96-
.Solution
97-
.WithDocumentSyntaxRoot(
98-
documentId, realRoot.TrackNodes(realNode));
99-
if (solution.GetDocument(documentId) is not {} document
100-
|| await document.GetSyntaxRootAsync(cancellationToken)
101-
.ConfigureAwait(false) is not {} root)
95+
var solution = document.Project
96+
.Solution;
97+
if (await document.GetSemanticModelAsync(cancellationToken)
98+
.ConfigureAwait(false) is not {} model)
10299
{
103100
return null;
104101
}
105-
var node = root.FindNode(realNode.Span);
106-
if (await Documents.GetSymbols(document, node, cancellationToken)
107-
.ConfigureAwait(false) is not {} symbols
102+
var symbolizer = new Symbolizer(model, cancellationToken);
103+
if (symbolizer.ToSymbol(node) is not {} parameterSymbol
104+
|| parameterSymbol.ContainingSymbol
105+
is not IMethodSymbol methodSymbol
108106
|| node.Parent is not {} parent)
109107
{
110108
return null;
111109
}
112-
var (model, parameterSymbol, methodSymbol) = symbols;
110+
/*
111+
The kind of symbols to look up could be restricted to several
112+
types, but for safety, it is not restricted.
113+
*/
113114
var allSymbolNameSet = new HashSet<string>(
114115
model.LookupSymbols(parent.SpanStart)
115116
.Select(s => s.Name));
@@ -124,7 +125,7 @@ private static async Task<Solution> Replace(
124125
.ToList();
125126
return namesakes.Any()
126127
? await kit.GetRenamedSolution(
127-
namesakes[0], name, allSymbolNameSet, documentId, realNode)
128+
namesakes[0], name, allSymbolNameSet, documentId, node)
128129
: await kit.GetNewSolution(parameterSymbol, methodSymbol, root);
129130
}
130131

CodeFixes/Refactoring/TypeClassParameter/SolutionKit.cs

Lines changed: 83 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ namespace StyleChecker.CodeFixes.Refactoring.TypeClassParameter;
66
using System.Threading.Tasks;
77
using Maroontress.Roastery;
88
using Microsoft.CodeAnalysis;
9+
using Microsoft.CodeAnalysis.CSharp.Syntax;
910
using Microsoft.CodeAnalysis.FindSymbols;
1011
using Microsoft.CodeAnalysis.Formatting;
1112
using Microsoft.CodeAnalysis.Rename;
@@ -68,8 +69,8 @@ public class SolutionKit(
6869
/// <param name="documentId">
6970
/// The document ID.
7071
/// </param>
71-
/// <param name="realNode">
72-
/// The syntax node being tracked.
72+
/// <param name="node">
73+
/// The parameter syntax node (of a method or a local function).
7374
/// </param>
7475
/// <returns>
7576
/// The renamed solution.
@@ -79,40 +80,51 @@ public class SolutionKit(
7980
string name,
8081
ISet<string> allSymbolNameSet,
8182
DocumentId documentId,
82-
SyntaxNode realNode)
83+
ParameterSyntax node)
8384
{
84-
if (GetRenamingIndex(allSymbolNameSet, 0, name) is not {} k)
85+
if (await Document.GetSemanticModelAsync(CancellationToken)
86+
.ConfigureAwait(false) is not {} model)
8587
{
8688
return null;
8789
}
88-
var options = default(SymbolRenameOptions);
89-
var newSolution = await Renamer.RenameSymbolAsync(
90-
Solution,
91-
symbol,
92-
options,
93-
$"{name}_{k}",
94-
CancellationToken)
95-
.ConfigureAwait(false);
96-
var projectId = Document.Project.Id;
97-
if (newSolution.GetProject(projectId) is not {} project)
90+
var symbolier = new Symbolizer(model, CancellationToken);
91+
if (GetRenamingIndex(allSymbolNameSet, 0, name) is not {} k
92+
|| symbolier.ToSymbol(node) is not {} parameter)
9893
{
9994
return null;
10095
}
101-
var newDocument = project.Documents
102-
.Where(d => d.Id == documentId)
103-
.First();
104-
if (await newDocument.GetSyntaxRootAsync(CancellationToken)
105-
.ConfigureAwait(false) is not {} root
106-
|| root.GetCurrentNode(realNode) is not {} node
107-
|| await Documents.GetSymbols(newDocument, node, CancellationToken)
108-
.ConfigureAwait(false) is not {} symbols)
96+
var options = default(SymbolRenameOptions);
97+
var newName = $"{name}_{k}";
98+
99+
/*
100+
Note:
101+
102+
SyntaxNode.TrackNodes() and SyntaxNode.GetCurrentNode() do not
103+
always work before and after a call to Renamer.RenameSymbolAsync()
104+
(especially not in Visual Studio IDE). Instead, use
105+
SymbolFinder.FindSimilarSymbols() to track nodes by their
106+
corresponding symbols.
107+
*/
108+
var newSolution = await RenameSymbolAsync(
109+
Solution, symbol, options, newName);
110+
var projectId = Document.Project.Id;
111+
if (newSolution.GetProject(projectId) is not {} newProject
112+
|| newProject.Documents
113+
.FirstOrDefault(d => d.Id == documentId) is not {} newDocument
114+
|| await GetSyntaxRootAsync(newDocument) is not {} newRoot
115+
|| await GetCompilationAsync(newProject) is not {} newCompilation
116+
|| FindFirstSimilarSymbol(parameter, newCompilation)
117+
is not {} newParameter
118+
|| newParameter.DeclaringSyntaxReferences
119+
.Select(async r => await GetSyntaxAsync(r))
120+
.FirstOrDefault() is not {} newNode
121+
|| newParameter.ContainingSymbol is not IMethodSymbol newMethod)
109122
{
110-
return null;
123+
return newSolution;
111124
}
112125
var kit = new SolutionKit(
113126
newSolution, newDocument, TypeName, CancellationToken);
114-
return await kit.GetNewSolution(
115-
symbols.Parameter, symbols.Method, root);
127+
return await kit.GetNewSolution(newParameter, newMethod, newRoot);
116128
}
117129

118130
/// <summary>
@@ -144,9 +156,7 @@ public class SolutionKit(
144156
}
145157
var index = parameter.Index;
146158

147-
var allReferences = await SymbolFinder.FindReferencesAsync(
148-
methodSymbol, Solution, CancellationToken)
149-
.ConfigureAwait(false);
159+
var allReferences = await FindReferencesAsync(methodSymbol, Solution);
150160
var documentGroups = allReferences.SelectMany(r => r.Locations)
151161
.GroupBy(w => w.Document);
152162
if (DocumentUpdater.UpdateMainDocument(
@@ -197,4 +207,49 @@ public class SolutionKit(
197207
set.Add(id);
198208
return index;
199209
}
210+
211+
private async Task<Solution> RenameSymbolAsync(
212+
Solution solution,
213+
ISymbol symbol,
214+
SymbolRenameOptions options,
215+
string newName)
216+
{
217+
return await Renamer.RenameSymbolAsync(
218+
solution, symbol, options, newName, CancellationToken)
219+
.ConfigureAwait(false);
220+
}
221+
222+
private async Task<SyntaxNode?> GetSyntaxRootAsync(Document document)
223+
{
224+
return await document.GetSyntaxRootAsync(CancellationToken)
225+
.ConfigureAwait(false);
226+
}
227+
228+
private async Task<Compilation?> GetCompilationAsync(Project project)
229+
{
230+
return await project.GetCompilationAsync(CancellationToken)
231+
.ConfigureAwait(false);
232+
}
233+
234+
private T FindFirstSimilarSymbol<T>(T symbol, Compilation compilation)
235+
where T : ISymbol
236+
{
237+
return SymbolFinder.FindSimilarSymbols(
238+
symbol, compilation, CancellationToken)
239+
.FirstOrDefault();
240+
}
241+
242+
private async Task<IEnumerable<ReferencedSymbol>> FindReferencesAsync(
243+
ISymbol symbol, Solution solution)
244+
{
245+
return await SymbolFinder.FindReferencesAsync(
246+
symbol, solution, CancellationToken)
247+
.ConfigureAwait(false);
248+
}
249+
250+
private async Task<SyntaxNode> GetSyntaxAsync(SyntaxReference reference)
251+
{
252+
return await reference.GetSyntaxAsync(CancellationToken)
253+
.ConfigureAwait(false);
254+
}
200255
}

0 commit comments

Comments
 (0)