Skip to content

Commit f0e42eb

Browse files
committed
Fix for WPF0092
1 parent bead6ee commit f0e42eb

4 files changed

Lines changed: 112 additions & 18 deletions

File tree

WpfAnalyzers.Test/WPF0092RegisterClassHandlerDelegateType/CodeFix.cs

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
public static class CodeFix
77
{
88
private static readonly RoutedEventCallbackAnalyzer Analyzer = new();
9-
private static readonly RenameMemberFix Fix = new();
9+
private static readonly UseCorrectDelegateFix Fix = new();
1010
private static readonly ExpectedDiagnostic ExpectedDiagnostic = ExpectedDiagnostic.Create(Descriptors.WPF0092WrongDelegateType);
1111

1212
[TestCase("new Action<object, RoutedEventArgs>((sender, e) => { })")]
@@ -32,4 +32,45 @@ static C()
3232
}".AssertReplace("new Action<object, RoutedEventArgs>((sender, e) => { })", expression);
3333
RoslynAssert.Diagnostics(Analyzer, ExpectedDiagnostic.WithMessage("Use correct handler type"), code);
3434
}
35+
36+
[Test]
37+
public static void ExplicitAction()
38+
{
39+
var before = @"
40+
namespace N;
41+
42+
using System;
43+
using System.Windows;
44+
using System.Windows.Controls;
45+
46+
public static class C
47+
{
48+
static C()
49+
{
50+
EventManager.RegisterClassHandler(
51+
typeof(PasswordBox),
52+
PasswordBox.PasswordChangedEvent,
53+
↓new Action<object, RoutedEventArgs>((sender, e) => { }));
54+
}
55+
}";
56+
57+
var after = @"
58+
namespace N;
59+
60+
using System;
61+
using System.Windows;
62+
using System.Windows.Controls;
63+
64+
public static class C
65+
{
66+
static C()
67+
{
68+
EventManager.RegisterClassHandler(
69+
typeof(PasswordBox),
70+
PasswordBox.PasswordChangedEvent,
71+
new RoutedEventHandler((sender, e) => { }));
72+
}
73+
}";
74+
RoslynAssert.CodeFix(Analyzer, Fix, ExpectedDiagnostic, before, after);
75+
}
3576
}

WpfAnalyzers/Analyzers/RoutedEventCallbackAnalyzer.cs

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -32,62 +32,65 @@ private static void Handle(SyntaxNodeAnalysisContext context)
3232
{
3333
if (EventManager.RegisterClassHandler.Match(invocation, context.SemanticModel, context.CancellationToken) is { } registerClassHandler)
3434
{
35-
if (ShouldRename(registerClassHandler.Target, registerClassHandler.EventArgument, registerClassHandler.DelegateArgument) is var (location, properties, expectedName))
35+
if (ShouldRename(registerClassHandler.Target, registerClassHandler.EventArgument, registerClassHandler.DelegateArgument) is var (location, nameProperties, expectedName))
3636
{
3737
context.ReportDiagnostic(
3838
Diagnostic.Create(
3939
Descriptors.WPF0090RegisterClassHandlerCallbackNameShouldMatchEvent,
4040
location,
41-
properties,
41+
nameProperties,
4242
expectedName));
4343
}
4444

45-
if (WrongType(registerClassHandler.EventArgument, registerClassHandler.DelegateArgument))
45+
if (WrongType(registerClassHandler.EventArgument, registerClassHandler.DelegateArgument) is { } typeProperties)
4646
{
4747
context.ReportDiagnostic(
4848
Diagnostic.Create(
4949
Descriptors.WPF0092WrongDelegateType,
50-
registerClassHandler.DelegateArgument.GetLocation()));
50+
registerClassHandler.DelegateArgument.GetLocation(),
51+
typeProperties));
5152
}
5253
}
5354
else if (EventManager.AddHandler.Match(invocation, context.SemanticModel, context.CancellationToken) is { } addHandler)
5455
{
55-
if (ShouldRename(addHandler.Target, addHandler.EventArgument, addHandler.DelegateArgument) is var (location, properties, expectedName))
56+
if (ShouldRename(addHandler.Target, addHandler.EventArgument, addHandler.DelegateArgument) is var (location, nameProperties, expectedName))
5657
{
5758
context.ReportDiagnostic(
5859
Diagnostic.Create(
5960
Descriptors.WPF0091AddAndRemoveHandlerCallbackNameShouldMatchEvent,
6061
location,
61-
properties,
62+
nameProperties,
6263
expectedName));
6364
}
6465

65-
if (WrongType(addHandler.EventArgument, addHandler.DelegateArgument))
66+
if (WrongType(addHandler.EventArgument, addHandler.DelegateArgument) is { } typeProperties)
6667
{
6768
context.ReportDiagnostic(
6869
Diagnostic.Create(
6970
Descriptors.WPF0092WrongDelegateType,
70-
addHandler.DelegateArgument.GetLocation()));
71+
addHandler.DelegateArgument.GetLocation(),
72+
typeProperties));
7173
}
7274
}
7375
else if (EventManager.RemoveHandler.Match(invocation, context.SemanticModel, context.CancellationToken) is { } removeHandler)
7476
{
75-
if (ShouldRename(removeHandler.Target, removeHandler.EventArgument, removeHandler.DelegateArgument) is var (location, properties, expectedName))
77+
if (ShouldRename(removeHandler.Target, removeHandler.EventArgument, removeHandler.DelegateArgument) is var (location, nameProperties, expectedName))
7678
{
7779
context.ReportDiagnostic(
7880
Diagnostic.Create(
7981
Descriptors.WPF0091AddAndRemoveHandlerCallbackNameShouldMatchEvent,
8082
location,
81-
properties,
83+
nameProperties,
8284
expectedName));
8385
}
8486

85-
if (WrongType(removeHandler.EventArgument, removeHandler.DelegateArgument))
87+
if (WrongType(removeHandler.EventArgument, removeHandler.DelegateArgument) is { } typeProperties)
8688
{
8789
context.ReportDiagnostic(
8890
Diagnostic.Create(
8991
Descriptors.WPF0092WrongDelegateType,
90-
removeHandler.DelegateArgument.GetLocation()));
92+
removeHandler.DelegateArgument.GetLocation(),
93+
typeProperties));
9194
}
9295
}
9396

@@ -132,17 +135,18 @@ private static void Handle(SyntaxNodeAnalysisContext context)
132135
}
133136
}
134137

135-
bool WrongType(ArgumentSyntax eventArgument, ArgumentSyntax callbackArg)
138+
ImmutableDictionary<string, string?>? WrongType(ArgumentSyntax eventArgument, ArgumentSyntax callbackArg)
136139
{
137140
if (context.SemanticModel.GetSymbolSafe(eventArgument.Expression, context.CancellationToken) is { } routedSymbol &&
138141
routedSymbol.Name.EndsWith("Event", StringComparison.Ordinal) &&
139142
routedSymbol.ContainingType.TryFindEvent(routedSymbol.Name.Substring(0, routedSymbol.Name.Length - 5), out var accessor) &&
140-
context.SemanticModel.GetType(callbackArg.Expression, context.CancellationToken) is { } actualType)
143+
context.SemanticModel.GetType(callbackArg.Expression, context.CancellationToken) is { } actualType &&
144+
!TypeSymbolComparer.Equal(actualType, accessor.Type))
141145
{
142-
return !TypeSymbolComparer.Equal(actualType, accessor.Type);
146+
return ImmutableDictionary<string, string?>.Empty.Add(nameof(ITypeSymbol), accessor.Type.MetadataName);
143147
}
144148

145-
return false;
149+
return null;
146150
}
147151
}
148152
}

WpfAnalyzers/CodeFixes/RenameMemberFix.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context)
4141
foreach (var diagnostic in context.Diagnostics)
4242
{
4343
if (syntaxRoot is { } &&
44-
syntaxRoot.FindToken(diagnostic.Location.SourceSpan.Start) is { Parent: { } parent } token &&
44+
syntaxRoot.FindToken(diagnostic.Location.SourceSpan.Start) is { Parent: { } } token &&
4545
token.IsKind(SyntaxKind.IdentifierToken) &&
4646
semanticModel is { } &&
4747
semanticModel.TryGetSymbol(token, context.CancellationToken, out ISymbol? symbol) &&
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
namespace WpfAnalyzers;
2+
3+
using System.Collections.Immutable;
4+
using System.Composition;
5+
using System.Threading.Tasks;
6+
7+
using Gu.Roslyn.CodeFixExtensions;
8+
9+
using Microsoft.CodeAnalysis;
10+
using Microsoft.CodeAnalysis.CodeFixes;
11+
using Microsoft.CodeAnalysis.CSharp;
12+
using Microsoft.CodeAnalysis.CSharp.Syntax;
13+
14+
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(UseCorrectDelegateFix))]
15+
[Shared]
16+
internal class UseCorrectDelegateFix : DocumentEditorCodeFixProvider
17+
{
18+
public override ImmutableArray<string> FixableDiagnosticIds { get; } = ImmutableArray.Create(
19+
Descriptors.WPF0092WrongDelegateType.Id);
20+
21+
protected override async Task RegisterCodeFixesAsync(DocumentEditorCodeFixContext context)
22+
{
23+
var document = context.Document;
24+
var syntaxRoot = await document.GetSyntaxRootAsync(context.CancellationToken)
25+
.ConfigureAwait(false);
26+
27+
foreach (var diagnostic in context.Diagnostics)
28+
{
29+
if (syntaxRoot is { } &&
30+
syntaxRoot.TryFindNodeOrAncestor(diagnostic, out ArgumentSyntax? argument) &&
31+
diagnostic.Properties.TryGetValue(nameof(ITypeSymbol), out var typeName) &&
32+
typeName is { })
33+
{
34+
switch (argument.Expression)
35+
{
36+
case ObjectCreationExpressionSyntax { Type: { } createdType }:
37+
context.RegisterCodeFix(
38+
$"Use: {typeName}.",
39+
(editor, _) => editor.ReplaceNode(
40+
createdType,
41+
x => SyntaxFactory.ParseTypeName(typeName).WithTriviaFrom(x)),
42+
"Use correct delegate type",
43+
diagnostic);
44+
break;
45+
}
46+
}
47+
}
48+
}
49+
}

0 commit comments

Comments
 (0)