Skip to content

Commit e2c33e7

Browse files
committed
Add 'WinRTGeneratedBindableCustomPropertyWithBasesMemberAnalyzer'
1 parent 374b2d0 commit e2c33e7

File tree

5 files changed

+191
-3
lines changed

5 files changed

+191
-3
lines changed

src/CommunityToolkit.Mvvm.SourceGenerators/AnalyzerReleases.Shipped.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,9 @@ Rule ID | Category | Severity | Notes
8484
--------|----------|----------|-------
8585
MVVMTK0041 | CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator | Error | See https://aka.ms/mvvmtoolkit/errors/mvvmtk0041
8686
MVVMTK0042 | CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator | Info | See https://aka.ms/mvvmtoolkit/errors/mvvmtk0042
87-
MVVMTK0043| CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator | Error | See https://aka.ms/mvvmtoolkit/errors/mvvmtk0043
88-
MVVMTK0044| CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator | Error | See https://aka.ms/mvvmtoolkit/errors/mvvmtk0044
89-
MVVMTK0045| CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator | Warning | See https://aka.ms/mvvmtoolkit/errors/mvvmtk0045
87+
MVVMTK0043 | CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator | Error | See https://aka.ms/mvvmtoolkit/errors/mvvmtk0043
88+
MVVMTK0044 | CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator | Error | See https://aka.ms/mvvmtoolkit/errors/mvvmtk0044
89+
MVVMTK0045 | CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator | Warning | See https://aka.ms/mvvmtoolkit/errors/mvvmtk0045
9090
MVVMTK0046 | CommunityToolkit.Mvvm.SourceGenerators.RelayCommandGenerator | Warning | See https://aka.ms/mvvmtoolkit/errors/mvvmtk0046
91+
MVVMTK0047 | CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator | Warning | See https://aka.ms/mvvmtoolkit/errors/mvvmtk0047
92+
MVVMTK0048 | CommunityToolkit.Mvvm.SourceGenerators.RelayCommandGenerator | Warning | See https://aka.ms/mvvmtoolkit/errors/mvvmtk0048

src/CommunityToolkit.Mvvm.SourceGenerators/CommunityToolkit.Mvvm.SourceGenerators.projitems

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
<Compile Include="$(MSBuildThisFileDirectory)ComponentModel\TransitiveMembersGenerator.Execute.cs" />
4242
<Compile Include="$(MSBuildThisFileDirectory)Diagnostics\Analyzers\AsyncVoidReturningRelayCommandMethodAnalyzer.cs" />
4343
<Compile Include="$(MSBuildThisFileDirectory)Diagnostics\Analyzers\InvalidGeneratedPropertyObservablePropertyAttributeAnalyzer.cs" />
44+
<Compile Include="$(MSBuildThisFileDirectory)Diagnostics\Analyzers\WinRTGeneratedBindableCustomPropertyWithBasesMemberAnalyzer.cs" />
4445
<Compile Include="$(MSBuildThisFileDirectory)Diagnostics\Analyzers\WinRTRelayCommandIsNotGeneratedBindableCustomPropertyCompatibleAnalyzer.cs" />
4546
<Compile Include="$(MSBuildThisFileDirectory)Diagnostics\Analyzers\WinRTObservablePropertyOnFieldsIsNotAotCompatibleAnalyzer.cs" />
4647
<Compile Include="$(MSBuildThisFileDirectory)Diagnostics\Analyzers\PropertyNameCollisionObservablePropertyAttributeAnalyzer.cs" />
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
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+
#if ROSLYN_4_11_0_OR_GREATER
6+
7+
using System.Collections.Generic;
8+
using System.Collections.Immutable;
9+
using CommunityToolkit.Mvvm.SourceGenerators.Extensions;
10+
using Microsoft.CodeAnalysis;
11+
using Microsoft.CodeAnalysis.Diagnostics;
12+
using static CommunityToolkit.Mvvm.SourceGenerators.Diagnostics.DiagnosticDescriptors;
13+
14+
namespace CommunityToolkit.Mvvm.SourceGenerators;
15+
16+
/// <summary>
17+
/// A diagnostic analyzer that generates an error when <c>[GeneratedBindableCustomProperty]</c> is used on types with invalid generated base members.
18+
/// </summary>
19+
[DiagnosticAnalyzer(LanguageNames.CSharp)]
20+
public sealed class WinRTGeneratedBindableCustomPropertyWithBasesMemberAnalyzer : DiagnosticAnalyzer
21+
{
22+
/// <inheritdoc/>
23+
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create(
24+
WinRTGeneratedBindableCustomPropertyWithBaseObservablePropertyOnField,
25+
WinRTGeneratedBindableCustomPropertyWithBaseRelayCommand);
26+
27+
/// <inheritdoc/>
28+
public override void Initialize(AnalysisContext context)
29+
{
30+
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
31+
context.EnableConcurrentExecution();
32+
33+
context.RegisterCompilationStartAction(static context =>
34+
{
35+
// This analyzer is only enabled when CsWinRT is also used
36+
if (!context.Options.AnalyzerConfigOptionsProvider.GlobalOptions.IsUsingWindowsRuntimePack())
37+
{
38+
return;
39+
}
40+
41+
// Get the symbol for [ObservableProperty], [RelayCommand] and [GeneratedBindableCustomProperty]
42+
if (context.Compilation.GetTypeByMetadataName("CommunityToolkit.Mvvm.ComponentModel.ObservablePropertyAttribute") is not INamedTypeSymbol observablePropertySymbol ||
43+
context.Compilation.GetTypeByMetadataName("CommunityToolkit.Mvvm.Input.RelayCommandAttribute") is not INamedTypeSymbol relayCommandSymbol ||
44+
context.Compilation.GetTypeByMetadataName("WinRT.GeneratedBindableCustomPropertyAttribute") is not INamedTypeSymbol generatedBindableCustomPropertySymbol)
45+
{
46+
return;
47+
}
48+
49+
context.RegisterSymbolAction(context =>
50+
{
51+
// Ensure we do have a valid type
52+
if (context.Symbol is not INamedTypeSymbol typeSymbol)
53+
{
54+
return;
55+
}
56+
57+
// We only care about it if it's using [GeneratedBindableCustomProperty]
58+
if (!typeSymbol.TryGetAttributeWithType(generatedBindableCustomPropertySymbol, out AttributeData? generatedBindableCustomPropertyAttribute))
59+
{
60+
return;
61+
}
62+
63+
// Warn on all [ObservableProperty] fields
64+
foreach (IFieldSymbol fieldSymbol in FindObservablePropertyFields(typeSymbol, relayCommandSymbol))
65+
{
66+
context.ReportDiagnostic(Diagnostic.Create(
67+
WinRTGeneratedBindableCustomPropertyWithBaseObservablePropertyOnField,
68+
generatedBindableCustomPropertyAttribute.GetLocation(),
69+
typeSymbol,
70+
fieldSymbol.ContainingType,
71+
fieldSymbol.Name));
72+
}
73+
74+
// Warn on all [RelayCommand] methods
75+
foreach (IMethodSymbol methodSymbol in FindRelayCommandMethods(typeSymbol, relayCommandSymbol))
76+
{
77+
context.ReportDiagnostic(Diagnostic.Create(
78+
WinRTGeneratedBindableCustomPropertyWithBaseRelayCommand,
79+
generatedBindableCustomPropertyAttribute.GetLocation(),
80+
typeSymbol,
81+
methodSymbol));
82+
}
83+
}, SymbolKind.NamedType);
84+
});
85+
}
86+
87+
/// <summary>
88+
/// Finds all methods in the base types that have the <c>[RelayCommand]</c> attribute.
89+
/// </summary>
90+
/// <param name="typeSymbol">The <see cref="INamedTypeSymbol"/> instance to inspect.</param>
91+
/// <param name="relayCommandSymbol">The symbol for the <c>[RelayCommand]</c></param>
92+
/// <returns>All <see cref="IMethodSymbol"/> instances for matching members.</returns>
93+
private static IEnumerable<IMethodSymbol> FindRelayCommandMethods(INamedTypeSymbol typeSymbol, INamedTypeSymbol relayCommandSymbol)
94+
{
95+
// Check whether the base type (if any) is from the same assembly, and stop if it isn't. We do not
96+
// want to include methods from the same type, as those will already be caught by another analyzer.
97+
if (!SymbolEqualityComparer.Default.Equals(typeSymbol.ContainingAssembly, typeSymbol.BaseType))
98+
{
99+
yield break;
100+
}
101+
102+
foreach (ISymbol memberSymbol in typeSymbol.BaseType.GetAllMembersFromSameAssembly())
103+
{
104+
if (memberSymbol is IMethodSymbol methodSymbol &&
105+
methodSymbol.HasAttributeWithType(relayCommandSymbol))
106+
{
107+
yield return methodSymbol;
108+
}
109+
}
110+
}
111+
112+
/// <summary>
113+
/// Finds all fields in the base types that have the <c>[ObservableProperty]</c> attribute.
114+
/// </summary>
115+
/// <param name="typeSymbol">The <see cref="INamedTypeSymbol"/> instance to inspect.</param>
116+
/// <param name="observablePropertySymbol">The symbol for the <c>[ObservableProperty]</c></param>
117+
/// <returns>All <see cref="IFieldSymbol"/> instances for matching members.</returns>
118+
private static IEnumerable<IFieldSymbol> FindObservablePropertyFields(INamedTypeSymbol typeSymbol, INamedTypeSymbol observablePropertySymbol)
119+
{
120+
foreach (ISymbol memberSymbol in typeSymbol.GetAllMembersFromSameAssembly())
121+
{
122+
if (memberSymbol is IFieldSymbol fieldSymbol &&
123+
fieldSymbol.HasAttributeWithType(observablePropertySymbol))
124+
{
125+
yield return fieldSymbol;
126+
}
127+
}
128+
}
129+
}
130+
131+
#endif

src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -780,4 +780,36 @@ internal static class DiagnosticDescriptors
780780
isEnabledByDefault: true,
781781
description: "Using [RelayCommand] on methods within a type also using [GeneratedBindableCustomProperty] is not supported, and a manually declared command property should be used instead (the [GeneratedBindableCustomProperty] generator cannot see the generated command property that is produced by the MVVM Toolkit generator).",
782782
helpLinkUri: "https://aka.ms/mvvmtoolkit/errors/mvvmtk0046");
783+
784+
/// <summary>
785+
/// Gets a <see cref="DiagnosticDescriptor"/> for when <c>[GeneratedBindableCustomProperty]</c> is used on a type that also uses <c>[ObservableProperty]</c> on any declared or inherited fields.
786+
/// <para>
787+
/// Format: <c>"The type {0} using [GeneratedBindableCustomProperty] is also using [ObservableProperty] on its declared (or inherited) field {1}.{2}: combining the two generators is not supported, and partial properties should be used instead (the [GeneratedBindableCustomProperty] generator cannot see the generated property that is produced by the MVVM Toolkit generator)"</c>.
788+
/// </para>
789+
/// </summary>
790+
public static readonly DiagnosticDescriptor WinRTGeneratedBindableCustomPropertyWithBaseObservablePropertyOnField = new(
791+
id: "MVVMTK0047",
792+
title: "Using [GeneratedBindableCustomProperty] is not compatible with [ObservableProperty] on fields",
793+
messageFormat: """The type {0} using [GeneratedBindableCustomProperty] is also using [RelayCommand] on its declared (or inherited) method {1}: combining the two generators is not supported, and a manually declared command property should be used instead (the [GeneratedBindableCustomProperty] generator cannot see the generated property that is produced by the MVVM Toolkit generator)""",
794+
category: typeof(ObservablePropertyGenerator).FullName,
795+
defaultSeverity: DiagnosticSeverity.Warning,
796+
isEnabledByDefault: true,
797+
description: "Using [GeneratedBindableCustomProperty] on types that also use [ObservableProperty] on any declared (or inherited) fields is not supported, and partial properties should be used instead (the [GeneratedBindableCustomProperty] generator cannot see the generated property that is produced by the MVVM Toolkit generator).",
798+
helpLinkUri: "https://aka.ms/mvvmtoolkit/errors/mvvmtk0047");
799+
800+
/// <summary>
801+
/// Gets a <see cref="DiagnosticDescriptor"/> for when <c>[GeneratedBindableCustomProperty]</c> is used on a type that also uses <c>[RelayCommand]</c> on any declared or inherited methods.
802+
/// <para>
803+
/// Format: <c>"The type {0} using [GeneratedBindableCustomProperty] is also using [RelayCommand] on its inherited method {1}: combining the two generators is not supported, and a manually declared command property should be used instead (the [GeneratedBindableCustomProperty] generator cannot see the generated property that is produced by the MVVM Toolkit generator)"</c>.
804+
/// </para>
805+
/// </summary>
806+
public static readonly DiagnosticDescriptor WinRTGeneratedBindableCustomPropertyWithBaseRelayCommand = new(
807+
id: "MVVMTK0048",
808+
title: "Using [GeneratedBindableCustomProperty] is not compatible with [RelayCommand]",
809+
messageFormat: """The type {0} using [GeneratedBindableCustomProperty] is also using [RelayCommand] on its inherited method {1}: combining the two generators is not supported, and a manually declared command property should be used instead (the [GeneratedBindableCustomProperty] generator cannot see the generated property that is produced by the MVVM Toolkit generator)""",
810+
category: typeof(RelayCommandGenerator).FullName,
811+
defaultSeverity: DiagnosticSeverity.Warning,
812+
isEnabledByDefault: true,
813+
description: "Using [GeneratedBindableCustomProperty] on types that also use [RelayCommand] on any inherited methods is not supported, and a manually declared command property should be used instead (the [GeneratedBindableCustomProperty] generator cannot see the generated property that is produced by the MVVM Toolkit generator).",
814+
helpLinkUri: "https://aka.ms/mvvmtoolkit/errors/mvvmtk0048");
783815
}

src/CommunityToolkit.Mvvm.SourceGenerators/Extensions/INamedTypeSymbolExtensions.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,28 @@ public static IEnumerable<ISymbol> GetAllMembers(this INamedTypeSymbol symbol)
2828
}
2929
}
3030

31+
/// <summary>
32+
/// Gets all member symbols from a given <see cref="INamedTypeSymbol"/> instance, including inherited ones, only if they are declared in source.
33+
/// </summary>
34+
/// <param name="symbol">The input <see cref="INamedTypeSymbol"/> instance.</param>
35+
/// <returns>A sequence of all member symbols for <paramref name="symbol"/>.</returns>
36+
public static IEnumerable<ISymbol> GetAllMembersFromSameAssembly(this INamedTypeSymbol symbol)
37+
{
38+
for (INamedTypeSymbol? currentSymbol = symbol; currentSymbol is { SpecialType: not SpecialType.System_Object }; currentSymbol = currentSymbol.BaseType)
39+
{
40+
// Stop early when we reach a base type from another assembly
41+
if (!SymbolEqualityComparer.Default.Equals(currentSymbol.ContainingAssembly, symbol.ContainingAssembly))
42+
{
43+
yield break;
44+
}
45+
46+
foreach (ISymbol memberSymbol in currentSymbol.GetMembers())
47+
{
48+
yield return memberSymbol;
49+
}
50+
}
51+
}
52+
3153
/// <summary>
3254
/// Gets all member symbols from a given <see cref="INamedTypeSymbol"/> instance, including inherited ones.
3355
/// </summary>

0 commit comments

Comments
 (0)