Skip to content

Commit c562aa0

Browse files
committed
Handle more coerce cases.
1 parent a7d20a2 commit c562aa0

8 files changed

Lines changed: 246 additions & 2 deletions

File tree

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
namespace ValidCode.DependencyProperties;
2+
3+
using System;
4+
using System.Windows;
5+
6+
public class Chart : FrameworkElement
7+
{
8+
/// <summary>Identifies the <see cref="Time"/> dependency property.</summary>
9+
public static readonly DependencyProperty TimeProperty = DependencyProperty.Register(
10+
nameof(Time),
11+
typeof(DateTimeOffset),
12+
typeof(Chart),
13+
new FrameworkPropertyMetadata(
14+
default(DateTimeOffset),
15+
FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
16+
propertyChangedCallback: null,
17+
coerceValueCallback: (_, o) => Date.Min(DateTimeOffset.Now, (DateTimeOffset)o)));
18+
19+
public DateTimeOffset Time
20+
{
21+
get => (DateTimeOffset)this.GetValue(TimeProperty);
22+
set => this.SetValue(TimeProperty, value);
23+
}
24+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
namespace ValidCode.DependencyProperties;
2+
3+
using System;
4+
5+
public readonly struct Date
6+
{
7+
public static DateTimeOffset Min(DateTimeOffset x, DateTimeOffset y) => x < y ? x : y;
8+
}

WpfAnalyzers.Test.Netcore/WPF0024ParameterShouldBeNullableTests/Valid.cs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,41 @@ private static object CoerceText(DependencyObject d, object o)
8989
}
9090
}
9191
}
92+
";
93+
RoslynAssert.Valid(Analyzer, code);
94+
}
95+
96+
[Test]
97+
public static void CoerceValueCallback()
98+
{
99+
var code = @"
100+
#pragma warning disable WPF0023
101+
namespace N;
102+
103+
using System;
104+
using System.Windows;
105+
106+
public class Chart : FrameworkElement
107+
{
108+
/// <summary>Identifies the <see cref=""Time""/> dependency property.</summary>
109+
public static readonly DependencyProperty TimeProperty = DependencyProperty.Register(
110+
nameof(Time),
111+
typeof(DateTimeOffset),
112+
typeof(Chart),
113+
new FrameworkPropertyMetadata(
114+
default(DateTimeOffset),
115+
FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
116+
propertyChangedCallback: null,
117+
coerceValueCallback: (_, o) => Min(DateTimeOffset.Now, (DateTimeOffset)o)));
118+
119+
public DateTimeOffset Time
120+
{
121+
get => (DateTimeOffset)this.GetValue(TimeProperty);
122+
set => this.SetValue(TimeProperty, value);
123+
}
124+
125+
private static DateTimeOffset Min(DateTimeOffset x, DateTimeOffset y) => x < y ? x : y;
126+
}
92127
";
93128
RoslynAssert.Valid(Analyzer, code);
94129
}

WpfAnalyzers.Test/WPF0006CoerceValueCallbackShouldMatchRegisteredNameTests/CodeFix.cs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,4 +372,72 @@ private static object CoerceFontSize(DependencyObject d, object baseValue)
372372
}";
373373
RoslynAssert.CodeFix(Analyzer, Fix, ExpectedDiagnostic, before, after);
374374
}
375+
376+
[TestCase("private static object? WrongName(DependencyObject d, object? baseValue)", "private static object? CoerceValue(DependencyObject d, object? baseValue)")]
377+
[TestCase("private static object? WrongName(DependencyObject d, object baseValue)", "private static object? CoerceValue(DependencyObject d, object baseValue)")]
378+
[TestCase("private static object WrongName(DependencyObject d, object? baseValue)", "private static object CoerceValue(DependencyObject d, object? baseValue)")]
379+
public static void Nullable(string beforeSignature, string afterSignature)
380+
{
381+
var before = @"
382+
#pragma warning disable CS8603
383+
namespace N
384+
{
385+
using System;
386+
using System.Collections.ObjectModel;
387+
using System.Windows;
388+
using System.Windows.Controls;
389+
390+
public class FooControl : Control
391+
{
392+
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(
393+
nameof(Value),
394+
typeof(double),
395+
typeof(FooControl),
396+
new PropertyMetadata(1, null, ↓WrongName));
397+
398+
public double Value
399+
{
400+
get { return (double)this.GetValue(ValueProperty); }
401+
set { this.SetValue(ValueProperty, value); }
402+
}
403+
404+
private static object? WrongName(DependencyObject d, object? baseValue)
405+
{
406+
return baseValue;
407+
}
408+
}
409+
}".AssertReplace("private static object? WrongName(DependencyObject d, object? baseValue)", beforeSignature);
410+
var after = @"
411+
#pragma warning disable CS8603
412+
namespace N
413+
{
414+
using System;
415+
using System.Collections.ObjectModel;
416+
using System.Windows;
417+
using System.Windows.Controls;
418+
419+
public class FooControl : Control
420+
{
421+
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(
422+
nameof(Value),
423+
typeof(double),
424+
typeof(FooControl),
425+
new PropertyMetadata(1, null, CoerceValue));
426+
427+
public double Value
428+
{
429+
get { return (double)this.GetValue(ValueProperty); }
430+
set { this.SetValue(ValueProperty, value); }
431+
}
432+
433+
private static object? CoerceValue(DependencyObject d, object? baseValue)
434+
{
435+
return baseValue;
436+
}
437+
}
438+
}".AssertReplace("private static object? CoerceValue(DependencyObject d, object? baseValue)", afterSignature);
439+
440+
RoslynAssert.CodeFix(Analyzer, Fix, ExpectedDiagnostic, before, after);
441+
RoslynAssert.FixAll(Analyzer, Fix, ExpectedDiagnostic, before, after);
442+
}
375443
}

WpfAnalyzers.Test/WPF0006CoerceValueCallbackShouldMatchRegisteredNameTests/Valid.cs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,4 +204,39 @@ private static void OnBarChanged(DependencyObject d, DependencyPropertyChangedEv
204204

205205
RoslynAssert.Valid(Analyzer, code);
206206
}
207+
208+
[Test]
209+
public static void Min()
210+
{
211+
var code = @"
212+
#pragma warning disable WPF0023
213+
namespace N;
214+
215+
using System;
216+
using System.Windows;
217+
218+
public class Chart : FrameworkElement
219+
{
220+
/// <summary>Identifies the <see cref=""Time""/> dependency property.</summary>
221+
public static readonly DependencyProperty TimeProperty = DependencyProperty.Register(
222+
nameof(Time),
223+
typeof(DateTimeOffset),
224+
typeof(Chart),
225+
new FrameworkPropertyMetadata(
226+
default(DateTimeOffset),
227+
FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
228+
propertyChangedCallback: null,
229+
coerceValueCallback: (_, o) => Min(DateTimeOffset.Now, (DateTimeOffset)o)));
230+
231+
public DateTimeOffset Time
232+
{
233+
get => (DateTimeOffset)this.GetValue(TimeProperty);
234+
set => this.SetValue(TimeProperty, value);
235+
}
236+
237+
private static DateTimeOffset Min(DateTimeOffset x, DateTimeOffset y) => x < y ? x : y;
238+
}
239+
";
240+
RoslynAssert.Valid(Analyzer, code);
241+
}
207242
}

WpfAnalyzers.Test/WPF0020CastValueToCorrectTypeTests/CodeFix.cs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -982,4 +982,38 @@ private static void OnValueChanged(DependencyObject d, DependencyPropertyChanged
982982
}";
983983
RoslynAssert.CodeFix(Analyzer, Fix, ExpectedDiagnostic, new[] { fooControlCode, before }, after);
984984
}
985+
986+
[Test]
987+
public static void CoerceMinTime()
988+
{
989+
var code = @"
990+
namespace N;
991+
992+
using System;
993+
using System.Windows;
994+
995+
public class Chart : FrameworkElement
996+
{
997+
/// <summary>Identifies the <see cref=""Time""/> dependency property.</summary>
998+
public static readonly DependencyProperty TimeProperty = DependencyProperty.Register(
999+
nameof(Time),
1000+
typeof(TimeSpan),
1001+
typeof(Chart),
1002+
new FrameworkPropertyMetadata(
1003+
default(TimeSpan),
1004+
FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
1005+
propertyChangedCallback: null,
1006+
coerceValueCallback: (_, o) => Min(DateTimeOffset.Now, (↓DateTimeOffset)o)));
1007+
1008+
public TimeSpan Time
1009+
{
1010+
get => (TimeSpan)this.GetValue(TimeProperty);
1011+
set => this.SetValue(TimeProperty, value);
1012+
}
1013+
1014+
private static DateTimeOffset Min(DateTimeOffset x, DateTimeOffset y) => x < y ? x : y;
1015+
}
1016+
";
1017+
RoslynAssert.Diagnostics(Analyzer, Gu.Roslyn.Asserts.ExpectedDiagnostic.Create(Descriptors.WPF0020CastValueToCorrectType), code);
1018+
}
9851019
}

WpfAnalyzers.Test/WPF0020CastValueToCorrectTypeTests/Valid.cs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -603,4 +603,38 @@ public enum FooEnum
603603
}";
604604
RoslynAssert.Valid(Analyzer, fooCode, code, enumCode);
605605
}
606+
607+
[Test]
608+
public static void CoerceMinTime()
609+
{
610+
var code = @"
611+
namespace N;
612+
613+
using System;
614+
using System.Windows;
615+
616+
public class Chart : FrameworkElement
617+
{
618+
/// <summary>Identifies the <see cref=""Time""/> dependency property.</summary>
619+
public static readonly DependencyProperty TimeProperty = DependencyProperty.Register(
620+
nameof(Time),
621+
typeof(DateTimeOffset),
622+
typeof(Chart),
623+
new FrameworkPropertyMetadata(
624+
default(DateTimeOffset),
625+
FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
626+
propertyChangedCallback: null,
627+
coerceValueCallback: (_, o) => Min(DateTimeOffset.Now, (DateTimeOffset)o)));
628+
629+
public DateTimeOffset Time
630+
{
631+
get => (DateTimeOffset)this.GetValue(TimeProperty);
632+
set => this.SetValue(TimeProperty, value);
633+
}
634+
635+
private static DateTimeOffset Min(DateTimeOffset x, DateTimeOffset y) => x < y ? x : y;
636+
}
637+
";
638+
RoslynAssert.Valid(Analyzer, code);
639+
}
606640
}

WpfAnalyzers/Analyzers/PropertyMetadataAnalyzer.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,10 @@ context.Node is ObjectCreationExpressionSyntax objectCreation &&
8686
if (propertyMetadata.CoerceValueArgument is { } coerceValueArgument &&
8787
CoerceValueCallback.Match(coerceValueArgument, context.SemanticModel, context.CancellationToken) is { Identifier: { } coerceNode, Target: { } coerce })
8888
{
89-
if (TypeSymbolComparer.Equal(coerce.ContainingType, context.ContainingSymbol.ContainingType) &&
89+
if (coerce.Parameters.Length == 2 &&
90+
coerce.Parameters[0].Type == KnownSymbols.DependencyObject &&
91+
coerce.Parameters[1].Type == KnownSymbols.Object &&
92+
TypeSymbolComparer.Equal(coerce.ContainingType, context.ContainingSymbol.ContainingType) &&
9093
!coerce.Name.IsParts("Coerce", registeredName))
9194
{
9295
using var walker = InvocationWalker.InContainingClass(coerce, context.SemanticModel, context.CancellationToken);
@@ -127,7 +130,10 @@ context.Node is ObjectCreationExpressionSyntax objectCreation &&
127130
context.ReportDiagnostic(Diagnostic.Create(Descriptors.WPF0023ConvertToLambda, coerceValueArgument.GetLocation()));
128131
}
129132

130-
if (context.SemanticModel.GetNullableContext(declaration.SpanStart).HasFlag(NullableContext.Enabled) &&
133+
if (coerce.Parameters.Length == 2 &&
134+
coerce.Parameters[0].Type == KnownSymbols.DependencyObject &&
135+
coerce.Parameters[1].Type == KnownSymbols.Object &&
136+
context.SemanticModel.GetNullableContext(declaration.SpanStart).HasFlag(NullableContext.Enabled) &&
131137
declaration.ParameterList is { Parameters: { Count: 2 } parameters } &&
132138
parameters[1] is { Type: { } type and not NullableTypeSyntax })
133139
{

0 commit comments

Comments
 (0)