Skip to content

Commit f5f90ad

Browse files
committed
fixed analyzer packaging (now part of core lib)
1 parent 37ddfac commit f5f90ad

13 files changed

Lines changed: 640 additions & 252 deletions

File tree

‎EntityFrameworkCore.Triggered.Analyzers/EntityFrameworkCore.Triggered.Analyzers.CodeFixes/MigrateToAsyncTriggerCodeFixProvider.cs‎

Lines changed: 71 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using Microsoft.CodeAnalysis.CodeFixes;
99
using Microsoft.CodeAnalysis.CSharp;
1010
using Microsoft.CodeAnalysis.CSharp.Syntax;
11+
using Microsoft.CodeAnalysis.Editing;
1112

1213
namespace EntityFrameworkCore.Triggered.Analyzers.CodeFixes;
1314

@@ -22,120 +23,103 @@ public sealed class MigrateToAsyncTriggerCodeFixProvider : CodeFixProvider
2223
public override async Task RegisterCodeFixesAsync(CodeFixContext context)
2324
{
2425
var diagnostic = context.Diagnostics.First();
25-
var properties = diagnostic.Properties;
26+
var document = context.Document;
2627

27-
if (!properties.TryGetValue("AsyncInterfaceShortName", out var asyncInterfaceName) ||
28-
!properties.TryGetValue("SyncInterfaceShortName", out var syncInterfaceName) ||
29-
!properties.TryGetValue("SyncMethodName", out var syncMethodName) ||
30-
!properties.TryGetValue("AsyncMethodName", out var asyncMethodName))
28+
var root = await document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
29+
var semanticModel = await document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false);
30+
if (root is null || semanticModel is null)
3131
return;
3232

33-
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
34-
if (root == null) return;
33+
var methodDeclaration = root.FindNode(diagnostic.Location.SourceSpan).FirstAncestorOrSelf<MethodDeclarationSyntax>();
34+
if (methodDeclaration is null || !TriggerMigrationMatcher.TryMatch(methodDeclaration, semanticModel, context.CancellationToken, out var match))
35+
return;
36+
37+
if (methodDeclaration.ExplicitInterfaceSpecifier is { } specifier && GetRightmostIdentifier(specifier.Name) != match.Entry.SyncInterfaceShortName)
38+
return;
3539

36-
var node = root.FindNode(diagnostic.Location.SourceSpan);
37-
var methodDeclaration = node.FirstAncestorOrSelf<MethodDeclarationSyntax>();
38-
if (methodDeclaration == null) return;
40+
var baseType = await FindDeclaringBaseTypeAsync(document.Project.Solution, match, context.CancellationToken).ConfigureAwait(false);
3941

40-
var classDeclaration = methodDeclaration.FirstAncestorOrSelf<ClassDeclarationSyntax>();
41-
if (classDeclaration == null) return;
42+
// The interface is inherited from a base class or written through an alias; renaming it here is not safe
43+
if (baseType is null)
44+
return;
4245

4346
context.RegisterCodeFix(
4447
CodeAction.Create(
45-
title: $"Migrate to {asyncInterfaceName}",
46-
createChangedDocument: ct => MigrateToAsync(context.Document, root, classDeclaration, methodDeclaration, syncInterfaceName!, asyncInterfaceName!, syncMethodName!, asyncMethodName!, ct),
48+
title: $"Migrate to {match.Entry.AsyncInterfaceShortName}",
49+
createChangedSolution: ct => MigrateToAsyncAsync(document, methodDeclaration, baseType.Value.Document, baseType.Value.Node, match.Entry, ct),
4750
equivalenceKey: "MigrateToAsyncTrigger"),
4851
diagnostic);
4952
}
5053

51-
private static Task<Document> MigrateToAsync(
52-
Document document,
53-
SyntaxNode root,
54-
ClassDeclarationSyntax classDeclaration,
55-
MethodDeclarationSyntax methodDeclaration,
56-
string syncInterfaceName,
57-
string asyncInterfaceName,
58-
string syncMethodName,
59-
string asyncMethodName,
60-
CancellationToken cancellationToken)
54+
private static async Task<(Document Document, BaseTypeSyntax Node)?> FindDeclaringBaseTypeAsync(Solution solution, TriggerMigrationMatch match, CancellationToken cancellationToken)
6155
{
62-
var newRoot = root;
63-
64-
// Replace interface name in base list
65-
if (classDeclaration.BaseList != null)
56+
foreach (var syntaxReference in match.Method.ContainingType.DeclaringSyntaxReferences)
6657
{
67-
var newBaseList = classDeclaration.BaseList;
68-
foreach (var baseType in classDeclaration.BaseList.Types)
58+
if (await syntaxReference.GetSyntaxAsync(cancellationToken).ConfigureAwait(false) is not TypeDeclarationSyntax { BaseList: { } baseList })
59+
continue;
60+
61+
var document = solution.GetDocument(baseList.SyntaxTree);
62+
var semanticModel = document is null ? null : await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
63+
if (semanticModel is null)
64+
continue;
65+
66+
foreach (var baseType in baseList.Types)
6967
{
70-
var typeName = GetBaseTypeName(baseType);
71-
if (typeName == syncInterfaceName)
68+
if (SymbolEqualityComparer.Default.Equals(semanticModel.GetTypeInfo(baseType.Type, cancellationToken).Type, match.TriggerInterface)
69+
&& GetRightmostIdentifier(baseType.Type) == match.Entry.SyncInterfaceShortName)
7270
{
73-
var newBaseType = ReplaceInterfaceName(baseType, syncInterfaceName, asyncInterfaceName);
74-
newBaseList = newBaseList.ReplaceNode(baseType, newBaseType);
71+
return (document!, baseType);
7572
}
7673
}
77-
78-
if (newBaseList != classDeclaration.BaseList)
79-
{
80-
newRoot = newRoot.ReplaceNode(classDeclaration.BaseList, newBaseList);
81-
}
8274
}
8375

84-
// Find the method again in the updated tree
85-
var updatedMethod = newRoot.FindNode(methodDeclaration.Identifier.Span)
86-
.FirstAncestorOrSelf<MethodDeclarationSyntax>();
76+
return null;
77+
}
78+
79+
private static async Task<Solution> MigrateToAsyncAsync(
80+
Document methodDocument,
81+
MethodDeclarationSyntax methodDeclaration,
82+
Document baseTypeDocument,
83+
BaseTypeSyntax baseType,
84+
TriggerMappingEntry entry,
85+
CancellationToken cancellationToken)
86+
{
87+
var editor = new SolutionEditor(methodDocument.Project.Solution);
88+
89+
var baseTypeEditor = await editor.GetDocumentEditorAsync(baseTypeDocument.Id, cancellationToken).ConfigureAwait(false);
90+
baseTypeEditor.ReplaceNode(baseType.Type, RenameRightmostIdentifier(baseType.Type, entry.AsyncInterfaceShortName));
91+
92+
var newMethod = methodDeclaration.WithIdentifier(
93+
SyntaxFactory.Identifier(entry.AsyncMethodName).WithTriviaFrom(methodDeclaration.Identifier));
8794

88-
if (updatedMethod != null && updatedMethod.Identifier.Text == syncMethodName)
95+
if (newMethod.ExplicitInterfaceSpecifier is { } specifier)
8996
{
90-
var newMethod = updatedMethod.WithIdentifier(
91-
SyntaxFactory.Identifier(asyncMethodName)
92-
.WithTriviaFrom(updatedMethod.Identifier));
93-
newRoot = newRoot.ReplaceNode(updatedMethod, newMethod);
97+
newMethod = newMethod.WithExplicitInterfaceSpecifier(
98+
specifier.WithName((NameSyntax)RenameRightmostIdentifier(specifier.Name, entry.AsyncInterfaceShortName)));
9499
}
95100

96-
return Task.FromResult(document.WithSyntaxRoot(newRoot));
97-
}
101+
var methodEditor = await editor.GetDocumentEditorAsync(methodDocument.Id, cancellationToken).ConfigureAwait(false);
102+
methodEditor.ReplaceNode(methodDeclaration, newMethod);
98103

99-
private static string? GetBaseTypeName(BaseTypeSyntax baseType)
100-
{
101-
return baseType.Type switch
102-
{
103-
SimpleNameSyntax simple => simple.Identifier.Text,
104-
QualifiedNameSyntax qualified => qualified.Right.Identifier.Text,
105-
_ => null
106-
};
104+
return editor.GetChangedSolution();
107105
}
108106

109-
private static BaseTypeSyntax ReplaceInterfaceName(BaseTypeSyntax baseType, string oldName, string newName)
107+
private static string? GetRightmostIdentifier(TypeSyntax type) => type switch
110108
{
111-
switch (baseType.Type)
112-
{
113-
case GenericNameSyntax generic when generic.Identifier.Text == oldName:
114-
var newGeneric = generic.WithIdentifier(
115-
SyntaxFactory.Identifier(newName).WithTriviaFrom(generic.Identifier));
116-
return baseType.WithType(newGeneric);
117-
118-
case SimpleNameSyntax simple when simple.Identifier.Text == oldName:
119-
var newSimple = SyntaxFactory.IdentifierName(
120-
SyntaxFactory.Identifier(newName).WithTriviaFrom(simple.Identifier));
121-
return baseType.WithType(newSimple);
122-
123-
case QualifiedNameSyntax qualified when qualified.Right.Identifier.Text == oldName:
124-
SimpleNameSyntax newRight;
125-
if (qualified.Right is GenericNameSyntax rightGeneric)
126-
{
127-
newRight = rightGeneric.WithIdentifier(
128-
SyntaxFactory.Identifier(newName).WithTriviaFrom(rightGeneric.Identifier));
129-
}
130-
else
131-
{
132-
newRight = SyntaxFactory.IdentifierName(
133-
SyntaxFactory.Identifier(newName).WithTriviaFrom(qualified.Right.Identifier));
134-
}
135-
return baseType.WithType(qualified.WithRight(newRight));
109+
SimpleNameSyntax simple => simple.Identifier.ValueText,
110+
QualifiedNameSyntax qualified => qualified.Right.Identifier.ValueText,
111+
AliasQualifiedNameSyntax aliasQualified => aliasQualified.Name.Identifier.ValueText,
112+
_ => null
113+
};
136114

137-
default:
138-
return baseType;
139-
}
140-
}
115+
private static TypeSyntax RenameRightmostIdentifier(TypeSyntax type, string newName) => type switch
116+
{
117+
SimpleNameSyntax simple => RenameSimpleName(simple, newName),
118+
QualifiedNameSyntax qualified => qualified.WithRight(RenameSimpleName(qualified.Right, newName)),
119+
AliasQualifiedNameSyntax aliasQualified => aliasQualified.WithName(RenameSimpleName(aliasQualified.Name, newName)),
120+
_ => type
121+
};
122+
123+
private static SimpleNameSyntax RenameSimpleName(SimpleNameSyntax name, string newName)
124+
=> name.WithIdentifier(SyntaxFactory.Identifier(newName).WithTriviaFrom(name.Identifier));
141125
}

0 commit comments

Comments
 (0)