Skip to content

Commit aa12670

Browse files
committed
Add 'PropertyDeclarationWithPropertyNameSuffixAnalyzer'
1 parent 8f971c9 commit aa12670

File tree

9 files changed

+139
-21
lines changed

9 files changed

+139
-21
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,4 @@ WCTDP0012 | CommunityToolkit.GeneratedDependencyPropertyDependencyPropertyGenera
2222
WCTDP0013 | CommunityToolkit.GeneratedDependencyPropertyDependencyPropertyGenerator | Error |
2323
WCTDP0014 | CommunityToolkit.GeneratedDependencyPropertyDependencyPropertyGenerator | Error |
2424
WCTDP0015 | CommunityToolkit.GeneratedDependencyPropertyDependencyPropertyGenerator | Error |
25+
WCTDP0016 | CommunityToolkit.GeneratedDependencyPropertyDependencyPropertyGenerator | Info |

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

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,7 @@ public override void Initialize(AnalysisContext context)
4242

4343
context.RegisterSymbolAction(context =>
4444
{
45-
// Validate that we do have a property
46-
if (context.Symbol is not IPropertySymbol propertySymbol)
47-
{
48-
return;
49-
}
45+
IPropertySymbol propertySymbol = (IPropertySymbol)context.Symbol;
5046

5147
// If the property is not using '[GeneratedDependencyProperty]', there's nothing to do
5248
if (!propertySymbol.TryGetAttributeWithAnyType(generatedDependencyPropertyAttributeSymbols, out AttributeData? attributeData))

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

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,7 @@ public override void Initialize(AnalysisContext context)
4242

4343
context.RegisterSymbolAction(context =>
4444
{
45-
// Validate that we do have a property
46-
if (context.Symbol is not IPropertySymbol propertySymbol)
47-
{
48-
return;
49-
}
45+
IPropertySymbol propertySymbol = (IPropertySymbol)context.Symbol;
5046

5147
// If the property is not using '[GeneratedDependencyProperty]', there's nothing to do
5248
if (!propertySymbol.TryGetAttributeWithAnyType(generatedDependencyPropertyAttributeSymbols, out AttributeData? attributeData))

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

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,7 @@ public override void Initialize(AnalysisContext context)
3939

4040
context.RegisterSymbolAction(context =>
4141
{
42-
// Validate that we do have a property
43-
if (context.Symbol is not IPropertySymbol propertySymbol)
44-
{
45-
return;
46-
}
42+
IPropertySymbol propertySymbol = (IPropertySymbol)context.Symbol;
4743

4844
// If the property is not using '[GeneratedDependencyProperty]', there's nothing to do
4945
if (!propertySymbol.TryGetAttributeWithAnyType(generatedDependencyPropertyAttributeSymbols, out AttributeData? attributeData))

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public override void Initialize(AnalysisContext context)
3636

3737
context.RegisterSymbolAction(context =>
3838
{
39-
// Validate that we do have a property, and that it is of some type that can be explicitly nullable.
39+
// Validate that we have a property that is of some type that can be explicitly nullable.
4040
// We're intentionally ignoring 'Nullable<T>' values here, since those are by defintiion nullable.
4141
// Additionally, we only care about properties that are explicitly marked as not nullable.
4242
// Lastly, we can skip required properties, since for those it's completely fine to be non-nullable.

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

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,7 @@ public override void Initialize(AnalysisContext context)
3535

3636
context.RegisterSymbolAction(context =>
3737
{
38-
// We're intentionally only looking for properties here
39-
if (context.Symbol is not IPropertySymbol propertySymbol)
40-
{
41-
return;
42-
}
38+
IPropertySymbol propertySymbol = (IPropertySymbol)context.Symbol;
4339

4440
// If the property isn't using '[GeneratedDependencyProperty]', there's nothing to do
4541
if (!propertySymbol.TryGetAttributeWithAnyType(generatedDependencyPropertyAttributeSymbols, out AttributeData? attributeData))
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
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 CommunityToolkit.GeneratedDependencyProperty.Constants;
7+
using CommunityToolkit.GeneratedDependencyProperty.Extensions;
8+
using Microsoft.CodeAnalysis;
9+
using Microsoft.CodeAnalysis.Diagnostics;
10+
using static CommunityToolkit.GeneratedDependencyProperty.Diagnostics.DiagnosticDescriptors;
11+
12+
namespace CommunityToolkit.GeneratedDependencyProperty;
13+
14+
/// <summary>
15+
/// A diagnostic analyzer that generates a diagnostic whenever <c>[GeneratedDependencyProperty]</c> is used on a property with the 'Property' suffix in its name.
16+
/// </summary>
17+
[DiagnosticAnalyzer(LanguageNames.CSharp)]
18+
public sealed class PropertyDeclarationWithPropertyNameSuffixAnalyzer : DiagnosticAnalyzer
19+
{
20+
/// <inheritdoc/>
21+
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = [PropertyDeclarationWithPropertySuffix];
22+
23+
/// <inheritdoc/>
24+
public override void Initialize(AnalysisContext context)
25+
{
26+
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
27+
context.EnableConcurrentExecution();
28+
29+
context.RegisterCompilationStartAction(static context =>
30+
{
31+
// Get the '[GeneratedDependencyProperty]' symbol (there might be multiples, due to embedded mode)
32+
ImmutableArray<INamedTypeSymbol> generatedDependencyPropertyAttributeSymbols = context.Compilation.GetTypesByMetadataName(WellKnownTypeNames.GeneratedDependencyPropertyAttribute);
33+
34+
context.RegisterSymbolAction(context =>
35+
{
36+
IPropertySymbol propertySymbol = (IPropertySymbol)context.Symbol;
37+
38+
// We only want to lookup the attribute if the property name actually ends with the 'Property' suffix
39+
if (!propertySymbol.Name.EndsWith("Property"))
40+
{
41+
return;
42+
}
43+
44+
// Emit a diagnostic if the property is using '[GeneratedDependencyProperty]'
45+
if (propertySymbol.TryGetAttributeWithAnyType(generatedDependencyPropertyAttributeSymbols, out AttributeData? attributeData))
46+
{
47+
context.ReportDiagnostic(Diagnostic.Create(
48+
PropertyDeclarationWithPropertySuffix,
49+
attributeData.GetLocation(),
50+
propertySymbol));
51+
}
52+
}, SymbolKind.Property);
53+
});
54+
}
55+
}

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,4 +205,17 @@ internal static class DiagnosticDescriptors
205205
isEnabledByDefault: true,
206206
description: "Properties annotated with [GeneratedDependencyProperty] and setting 'DefaultValueCallback' must use the name of a method with a valid signature (it must be a static method with no parameters, returning a value compatible with the property type: either the same type, or 'object').",
207207
helpLinkUri: "https://aka.ms/toolkit/labs/windows");
208+
209+
/// <summary>
210+
/// <c>"The property '{0}' is using [GeneratedDependencyProperty] and has a name ending with the 'Property' suffix, which is redundant (the generated dependency property will always add the 'Property' suffix to the name of its associated property)"</c>.
211+
/// </summary>
212+
public static readonly DiagnosticDescriptor PropertyDeclarationWithPropertySuffix = new(
213+
id: "WCTDP0016",
214+
title: "Using [GeneratedDependencyProperty] on a property with the 'Property' suffix",
215+
messageFormat: """The property '{0}' is using [GeneratedDependencyProperty] and has a name ending with the 'Property' suffix, which is redundant (the generated dependency property will always add the 'Property' suffix to the name of its associated property)""",
216+
category: typeof(DependencyPropertyGenerator).FullName,
217+
defaultSeverity: DiagnosticSeverity.Info,
218+
isEnabledByDefault: true,
219+
description: "Properties annotated with [GeneratedDependencyProperty] should not have the 'Property' suffix in their name, as it is redundant (the generated dependency properties will always add the 'Property' suffix to the name of their associated properties).",
220+
helpLinkUri: "https://aka.ms/toolkit/labs/windows");
208221
}

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

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1155,4 +1155,69 @@ public partial class MyControl : Control
11551155

11561156
await CSharpAnalyzerTest<InvalidPropertyDefaultValueCallbackTypeAnalyzer>.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13);
11571157
}
1158+
1159+
[TestMethod]
1160+
[DataRow("Name")]
1161+
[DataRow("TestProperty")]
1162+
public async Task PropertyDeclarationWithPropertyNameSuffixAnalyzer_NoAttribute_DoesNotWarn(string propertyName)
1163+
{
1164+
string source = $$"""
1165+
using Windows.UI.Xaml.Controls;
1166+
1167+
#nullable enable
1168+
1169+
namespace MyApp;
1170+
1171+
public partial class MyControl : Control
1172+
{
1173+
public partial string? {|CS9248:{{propertyName}}|} { get; set; }
1174+
}
1175+
""";
1176+
1177+
await CSharpAnalyzerTest<PropertyDeclarationWithPropertyNameSuffixAnalyzer>.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13);
1178+
}
1179+
1180+
[TestMethod]
1181+
[DataRow("Name")]
1182+
[DataRow("PropertyGroup")]
1183+
public async Task PropertyDeclarationWithPropertyNameSuffixAnalyzer_ValidName_DoesNotWarn(string propertyName)
1184+
{
1185+
string source = $$"""
1186+
using CommunityToolkit.WinUI;
1187+
using Windows.UI.Xaml.Controls;
1188+
1189+
#nullable enable
1190+
1191+
namespace MyApp;
1192+
1193+
public partial class MyControl : Control
1194+
{
1195+
[GeneratedDependencyProperty]
1196+
public partial string? {|CS9248:{{propertyName}}|} { get; set; }
1197+
}
1198+
""";
1199+
1200+
await CSharpAnalyzerTest<PropertyDeclarationWithPropertyNameSuffixAnalyzer>.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13);
1201+
}
1202+
1203+
[TestMethod]
1204+
public async Task PropertyDeclarationWithPropertyNameSuffixAnalyzer_InvalidName_Warns()
1205+
{
1206+
const string source = """
1207+
using CommunityToolkit.WinUI;
1208+
using Windows.UI.Xaml.Controls;
1209+
1210+
#nullable enable
1211+
1212+
namespace MyApp;
1213+
1214+
public partial class MyControl : Control
1215+
{
1216+
[{|WCTDP0016:GeneratedDependencyProperty|}]
1217+
public partial string? {|CS9248:TestProperty|} { get; set; }
1218+
}
1219+
""";
1220+
1221+
await CSharpAnalyzerTest<PropertyDeclarationWithPropertyNameSuffixAnalyzer>.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13);
1222+
}
11581223
}

0 commit comments

Comments
 (0)