Skip to content

Commit 53f7436

Browse files
committed
Add 'UseFieldDeclarationCorrectlyAnalyzer' and tests
1 parent 8792538 commit 53f7436

File tree

5 files changed

+135
-1
lines changed

5 files changed

+135
-1
lines changed

components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/AnalyzerReleases.Shipped.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,4 @@ WCTDP0016 | CommunityToolkit.GeneratedDependencyPropertyDependencyPropertyGenera
2626
WCTDP0017 | CommunityToolkit.GeneratedDependencyPropertyDependencyPropertyGenerator | Info |
2727
WCTDP0018 | CommunityToolkit.GeneratedDependencyPropertyDependencyPropertyGenerator | Error |
2828
WCTDP0019 | CommunityToolkit.GeneratedDependencyPropertyDependencyPropertyGenerator | Error |
29+
WCTDP0020 | CommunityToolkit.GeneratedDependencyPropertyDependencyPropertyGenerator | Warning |
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System.Collections.Immutable;
6+
using System.Linq;
7+
using CommunityToolkit.GeneratedDependencyProperty.Constants;
8+
using CommunityToolkit.GeneratedDependencyProperty.Extensions;
9+
using Microsoft.CodeAnalysis;
10+
using Microsoft.CodeAnalysis.Diagnostics;
11+
using static CommunityToolkit.GeneratedDependencyProperty.Diagnostics.DiagnosticDescriptors;
12+
13+
namespace CommunityToolkit.GeneratedDependencyProperty;
14+
15+
/// <summary>
16+
/// A diagnostic analyzer that generates a warning whenever a dependency property is declared as an incorrect field.
17+
/// </summary>
18+
[DiagnosticAnalyzer(LanguageNames.CSharp)]
19+
public sealed class UseFieldDeclarationCorrectlyAnalyzer : DiagnosticAnalyzer
20+
{
21+
/// <inheritdoc/>
22+
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = [IncorrectDependencyPropertyFieldDeclaration];
23+
24+
/// <inheritdoc/>
25+
public override void Initialize(AnalysisContext context)
26+
{
27+
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
28+
context.EnableConcurrentExecution();
29+
30+
31+
context.RegisterCompilationStartAction(static context =>
32+
{
33+
// Get the XAML mode to use
34+
bool useWindowsUIXaml = context.Options.AnalyzerConfigOptionsProvider.GlobalOptions.GetMSBuildBooleanPropertyValue(WellKnownPropertyNames.DependencyPropertyGeneratorUseWindowsUIXaml);
35+
36+
// Get the 'DependencyProperty' symbol
37+
if (context.Compilation.GetTypeByMetadataName(WellKnownTypeNames.DependencyProperty(useWindowsUIXaml)) is not { } dependencyPropertySymbol)
38+
{
39+
return;
40+
}
41+
42+
context.RegisterSymbolAction(context =>
43+
{
44+
IFieldSymbol fieldSymbol = (IFieldSymbol)context.Symbol;
45+
46+
// We only care about fields with are of type 'DependencyProperty'
47+
if (!SymbolEqualityComparer.Default.Equals(fieldSymbol.Type, dependencyPropertySymbol))
48+
{
49+
return;
50+
}
51+
52+
// Fields should always be public, static, readonly, and with nothing else on them
53+
if (fieldSymbol is
54+
{ DeclaredAccessibility: not Accessibility.Public } or
55+
{ IsStatic: false } or
56+
{ IsRequired: true } or
57+
{ IsReadOnly: false } or
58+
{ IsVolatile: true } or
59+
{ NullableAnnotation: NullableAnnotation.Annotated })
60+
{
61+
context.ReportDiagnostic(Diagnostic.Create(
62+
IncorrectDependencyPropertyFieldDeclaration,
63+
fieldSymbol.Locations.FirstOrDefault(),
64+
fieldSymbol));
65+
}
66+
}, SymbolKind.Field);
67+
});
68+
}
69+
}

components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/UseGeneratedDependencyPropertyOnManualPropertyAnalyzer.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,6 @@ public override void Initialize(AnalysisContext context)
6666
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
6767
context.EnableConcurrentExecution();
6868

69-
7069
context.RegisterCompilationStartAction(static context =>
7170
{
7271
// Get the XAML mode to use

components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,4 +262,17 @@ internal static class DiagnosticDescriptors
262262
isEnabledByDefault: true,
263263
description: "All attributes targeting the generated dependency property for a property annotated with [GeneratedDependencyProperty] must have arguments using supported expressions.",
264264
helpLinkUri: "https://aka.ms/toolkit/labs/windows");
265+
266+
/// <summary>
267+
/// <c>"The field '{0}' is a dependency property, but it is not declared correctly (all dependency property fields should be declared as 'public static readonly', and not be nullable)"</c>.
268+
/// </summary>
269+
public static readonly DiagnosticDescriptor IncorrectDependencyPropertyFieldDeclaration = new(
270+
id: "WCTDP0020",
271+
title: "Incorrect dependency property field declaration",
272+
messageFormat: "The field '{0}' is a dependency property, but it is not declared correctly (all dependency property fields should be declared as 'public static readonly', and not be nullable)",
273+
category: typeof(DependencyPropertyGenerator).FullName,
274+
defaultSeverity: DiagnosticSeverity.Warning,
275+
isEnabledByDefault: true,
276+
description: "All dependency property fields should be declared as 'public static readonly', and not be nullable.",
277+
helpLinkUri: "https://learn.microsoft.com/windows/uwp/xaml-platform/custom-dependency-properties#checklist-for-defining-a-dependency-property");
265278
}

components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_Analyzers.cs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1817,4 +1817,56 @@ public class TestAttribute(string P) : Attribute
18171817

18181818
await CSharpAnalyzerTest<InvalidPropertyForwardedAttributeDeclarationAnalyzer>.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13);
18191819
}
1820+
1821+
[TestMethod]
1822+
public async Task UseFieldDeclarationCorrectlyAnalyzer_NotDependencyProperty_DoesNotWarn()
1823+
{
1824+
string source = $$"""
1825+
using Windows.UI.Xaml;
1826+
1827+
public class MyObject : DependencyObject
1828+
{
1829+
private static string TestProperty = "Blah";
1830+
}
1831+
""";
1832+
1833+
await CSharpAnalyzerTest<UseFieldDeclarationCorrectlyAnalyzer>.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13);
1834+
}
1835+
1836+
[TestMethod]
1837+
public async Task UseFieldDeclarationCorrectlyAnalyzer_ValidField_DoesNotWarn()
1838+
{
1839+
const string source = """
1840+
using Windows.UI.Xaml;
1841+
1842+
public class MyObject : DependencyObject
1843+
{
1844+
public static readonly DependencyProperty TestProperty = DependencyProperty.Register("Test", typeof(string), typeof(MyObject), null);
1845+
}
1846+
""";
1847+
1848+
await CSharpAnalyzerTest<UseFieldDeclarationCorrectlyAnalyzer>.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13);
1849+
}
1850+
1851+
[TestMethod]
1852+
[DataRow("private static readonly DependencyProperty")]
1853+
[DataRow("public readonly DependencyProperty")]
1854+
[DataRow("public static DependencyProperty")]
1855+
[DataRow("public static volatile DependencyProperty")]
1856+
[DataRow("public static readonly DependencyProperty?")]
1857+
public async Task UseFieldDeclarationCorrectlyAnalyzer_Warns(string fieldDeclaration)
1858+
{
1859+
string source = $$"""
1860+
using Windows.UI.Xaml;
1861+
1862+
#nullable enable
1863+
1864+
public class MyObject : DependencyObject
1865+
{
1866+
{{fieldDeclaration}} {|WCTDP0020:TestProperty|};
1867+
}
1868+
""";
1869+
1870+
await CSharpAnalyzerTest<UseFieldDeclarationCorrectlyAnalyzer>.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13);
1871+
}
18201872
}

0 commit comments

Comments
 (0)